Skip to content

Feat/worker engine selection#105

Merged
Qsnh merged 154 commits intomainfrom
feat/worker-engine-selection
Apr 19, 2026
Merged

Feat/worker engine selection#105
Qsnh merged 154 commits intomainfrom
feat/worker-engine-selection

Conversation

@Qsnh
Copy link
Copy Markdown
Contributor

@Qsnh Qsnh commented Apr 16, 2026

No description provided.

Qsnh and others added 30 commits April 16, 2026 02:23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gine selection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Engine

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…plicate in monitorExecution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ai.AllEngines as canonical engine list; derive validEngines map and
  error message from it instead of duplicating names in three places
- Use ai.AllEngines in buildAllEngines to eliminate the local literal slice
- Add ENGINES/Engine to web/src/lib/types.ts; replace hardcoded SelectItems
  in create-worker-sheet and worker-detail with a mapped loop
- Fix onValueChange null-safety (string | null -> string) TS errors
- Remove "what" comment in resolveEngine

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…en types

- Move validateEngine into ai.ValidateEngine alongside AllEngines; use slices.Contains
- Make EngineConfigRaw delegate to EngineConfigRawFor, eliminating duplicated switch body
- Trim redundant what-comments on AllEngines, buildAllEngines, resolveEngine, ENGINES
- Replace raw "claude" default strings with DEFAULT_ENGINE constant in frontend
- Type Worker.engine as Engine; use useState<Engine> in create-worker-sheet and worker-detail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolve engine once in ExecuteWorker and thread it through launchRuntime
and monitorExecution instead of calling resolveEngine three times per
execution. Trim the narrating half of the buildAllEngines comment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add Kimi engine support design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add Kimi engine implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(engine): add EngineKimi constant and register in AllEngines

* feat(config): add KimiConfig and wire into BeeConfig, WorkerTimeout, EngineConfigRawFor

* feat(kimi): add invoker with ExtractResultFromLog and Run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(kimi): add adapter, register factory, wire into app bootstrap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(config): add kimi to config template, wizard, and i18n

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(frontend): add kimi to ENGINES type and i18n labels

* feat(log-viewer): detect Kimi engine from top-level role field

* feat(log-viewer): add KimiParser for role-based stream-JSON format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(log-viewer): wire KimiParser into log viewer engine selection

* fix(kimi-parser): handle array content blocks in tool results

Kimi returns tool result content as an array of text blocks rather than
a plain string. The parser was falling back to an empty string for
non-string content, causing blank output in the session detail log view.

Reuse extractToolResultText from claude-parser to handle both string
and array content shapes. Add a test case covering the array format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(kimi-parser): surface sent message content when assistant returns empty response

When Kimi calls `openbee ctl message send --stdin` and the subsequent
assistant turn is a placeholder `(Empty response: {...})`, the actual
content the model wrote is embedded in the Shell command's heredoc.

Detect this pattern: cache the stdin content from each
`openbee ctl message send --stdin` Shell call, then replace any
`(Empty response:` text block with that cached content. If no cached
content exists, silently drop the placeholder text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add Kimi AI engine to supported engines table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(kimi): fall back to sent message when last assistant response is empty

When Kimi ends a session with an "(Empty response: ...)" placeholder, the
actual reply was already delivered via `openbee ctl message send --stdin`.
ExtractResultFromLog now skips "(Empty response:" text blocks and tracks
the heredoc body from the last Shell tool call as a fallback, mirroring
the existing frontend kimi-parser logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(kimi): add Kimi agent changelog entry and remove plan/spec docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(kimi): set default timeout to 30m in applyDefaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove non-deterministic map-range fallback in resolveEngine; panic
  directly when default engine is missing (unreachable via buildAllEngines)
- Remove redundant slice reclip in kimi NewInvoker after append
- Eliminate double JSON parse in detectEngine by parsing once per line
- Extract shared EngineSelectItems component to remove duplicated ENGINES.map JSX
- Fix inconsistent alignment for Kimi fields in messages.go and config.go
- Add missing "kimi is valid" case to TestValidateEngine
- Add "// Kimi setup" section comment in PromptMessages to match Pi/Claude/Codex

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- kimi/invoker.go: re-clip baseEnv slice after extraEnv appends to prevent
  concurrent Run() calls from sharing the backing array
- manager.go: resolveEngine returns error instead of panicking when no engine
  adapter is found, preventing server crashes on misconfiguration
- manager.go + config.go: replace single workerTimeout with per-engine timeout
  map so workers use their own engine's configured timeout, not the default
- create-worker-sheet.tsx: remove dead !engine guard (Engine is always non-empty)
- kimi/invoker.go + adapter.go: remove what-comments that restate obvious code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… + EnginesConfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Qsnh and others added 8 commits April 18, 2026 19:43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useExecutions: add refetchInterval 500ms so sessions list auto-refreshes
- useSessionExecutions: reduce active-execution poll from 3000ms to 500ms
- rename pages/executions.tsx → pages/sessions.tsx to match /sessions route
- update app.tsx lazy import path accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons → nav.sessions

- en.json / zh.json: rename top-level "executions" key to "sessions"
- en.json / zh.json: rename nav.executions to nav.sessions
- Update all t("executions.*") call sites to t("sessions.*")
- Update nav.executions references in sidebar, nav, and breadcrumb config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Qsnh Qsnh force-pushed the feat/worker-engine-selection branch from 7714e7d to 54e8499 Compare April 19, 2026 04:32
Qsnh and others added 19 commits April 18, 2026 22:08
* feat: add /clear slash command for session context clearing

Implements two variants scoped to the current session:
- /clear: clears all session contexts after two-step confirmation (stops running tasks first)
- /clear {workerName}: clears a specific worker's active-engine context after confirmation

Confirmation state is held in memory with a 30-second expiry; the second
command must match the first exactly (case-sensitive). Worker name lookup
is case-insensitive and errors on duplicate names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* simplify: deduplicate reply helper, unify GetByName, fix pending map leak

- Extract sendReply package-level helper; both EngineCommandHandler and
  ClearCommandHandler delegate to it instead of duplicating the logic
- GetByName now delegates to ListByName, eliminating the duplicate SQL
- Add sweepExpired goroutine to ClearCommandHandler so unconfirmed
  /clear entries don't accumulate in memory indefinitely

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* simplify: remove WHAT comments and fix silent error discard in /clear handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* simplify: fix silent error discard and extract consumePending helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The confirmation prompt and post-clear message were calling
ListSessionContexts which returns all engine rows, so codex sessions
appeared in the prompt even when claude was the active engine.

Add ListActiveSessionContexts that mirrors the WHERE filtering of
ClearSessionContexts, and use it in handleClearAll so the prompt
reflects exactly what will (or was) cleared.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ncelled

- Full /clear with no running tasks executes immediately
- /clear {worker} always executes immediately (no tasks are cancelled)
- Confirmation is only required when stopping running tasks (destructive)
- Simplify pending map from struct to time.Time, remove unused i18n keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ions, or immediate tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… and serial busy checks

- ConfigHandler.defaultEngine was a startup snapshot that became stale after /engine switches; replaced with live enginecfg.Get() call and removed the constructor param
- handleClearWorker pre-checked existence then deleted (TOCTOU); DeleteSessionContextForEngine now returns (bool, error) so a single delete both removes the row and indicates whether it existed
- GetByName called ListByName (no LIMIT) then discarded all but first row; replaced with a direct QueryRow with LIMIT 1
- checkBusy ran three DB checks serially; now runs all three concurrently and evaluates results in priority order
- Removed WHAT comment from engineMappings()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rror conflation, and dead field

- ClearCommandHandler.sweepExpired now accepts a context and exits on cancellation
- executeClearAll accepts prefetched agents/runningTasks to avoid redundant DB reads
- busyCheck.warnMsg field replaces parallel-indexed warns slice in checkBusy
- handleWorkerEngine distinguishes sql.ErrNoRows from DB errors
- Extract scanSessionAgents helper to deduplicate row-scan loop in session_store
- Simplify enginecfg.Set initialization in BuildApp (set default first, overwrite if valid)
- Remove dead AppConfig.default_engine field from Go response and TypeScript type
- Fix edit-worker-info-sheet dirty-check to use enabledEngines[0] instead of DEFAULT_ENGINE
- Add --yolo comment in kimi/invoker.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ec polling, engine label helper

- msgingest.Gateway: cap seen dedup with 10k two-generation rotation to prevent unbounded growth in long-running processes
- session_store: extract shared activeWorkerSessionContextsFilter constant so List/Clear active-contexts SQL can't drift; drop scanSessionAgents nil→[] normalization (callers len-check anyway)
- use-executions: /executions list now only polls when an active execution exists (matches useSessionExecutions behavior); shares isActiveStatus helper
- format.ts: add formatEngineLabel(engine, t) helper; use in workers and worker-detail pages to dedupe inline t(`workers.engines.${engine}`) mappings

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…BusyChecker

Constructor now takes 5 args instead of 7 by bundling the three
independent activity checks (messages, executions, immediate tasks)
behind a single SystemBusyChecker interface composed via
NewSystemBusyChecker.
Backend now exposes ReadLogSince(id, since) returning a LogSlice with
(content, size, truncated). GET /api/executions/:id/logs accepts an
optional since query parameter and responds with JSON instead of plain
text. The log viewer frontend tracks the authoritative byte size from
the server and passes it as since on each 500ms poll, so active
executions stream only the new tail instead of re-reading the entire
file every interval.
Previously DynamicAdapter.ExtractResult read enginecfg.Get() on every
call, creating a TOCTOU window where /engine firing between Run and
ExtractResult would route result parsing to the wrong engine. The
EngineResultExtractor escape hatch existed but was never wired up
through BeeProcess, so the safe path was dead code.

Run now returns a RunResult whose ExtractResult closure is bound to
the engine chosen at Run time. DynamicAdapter routes once at Run;
result extraction travels with the handle. The ExtractResult method
is removed from the EngineAdapter interface, the EngineResultExtractor
extension interface is deleted, and Feeder/Worker Manager use the
RunResult directly.
The enginecfg package no longer holds process-wide state. It now
exposes a Store struct with NewStore/Get/Set, and every subsystem
receives a *enginecfg.Store through its constructor: DynamicAdapter,
Feeder, worker.Manager, task.TaskDispatcher, EngineCommandHandler,
ClearCommandHandler, SystemConfigHandler, and app.go wiring.

This removes implicit coupling, fixes the init order hazard that was
causing mcp tests to fail (they no longer depend on a global Set from
another test), and makes per-test engine isolation trivial.
ListActiveSessionContexts joined bee_workers for the worker name lookup,
but the outer WHERE kept `engine` and `updated_at` unqualified. SQLite
rejected the query with "ambiguous column name: engine", and the error
was only logged — handleClearAll then treated the empty result as "no
sessions" and replied NoContext. /clear therefore always reported "No
session contexts to clear." while /clear <worker> (different DELETE
path, no JOIN) worked.

Qualify the ambiguous references with bee_session_contexts.*, surface
lookup failures to the user instead of swallowing them, and add a
ListActiveSessionContexts test that mirrors ClearSessionContexts so the
regression is caught next time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…us read, add Store.Resolve

- ai.NewRunResult helper removes (proc, out, err, extract) boilerplate from claude/codex/kimi/pi adapters.
- compositeBusyChecker embeds the three activity-check interfaces; method promotion replaces hand-written delegations.
- ReadLogSince now returns the execution status alongside the log slice, eliminating the duplicate GetByID query in GetLogs polling (~2× DB calls per 500ms tick).
- enginecfg.Store gains Resolve(workerEngine) used by /clear and dispatcher to fold the worker-or-default fallback.
- Trim narration comments and the stale enginecfg.Set reference in dispatcher_test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g, adapter cfg helpers

- /engine: check engine name validity before busy check; merge case 2/3
  busy guard; replace 3-goroutine concurrent busy probe with sequential
  short-circuit (SQLite serializes reads anyway). Add CmdEngine/CmdClear
  constants, replacing raw "/engine" / "/clear" string compares.
- /clear: drop the always-on sweepExpired goroutine and clearCmdHandler.Run
  runner; reap stale pending entries inside storePending (the only growth
  path). Inline executeClearAll into handleClearAll, removing the nil-as-
  sentinel re-fetch in the confirm path.
- ai.EngineConfig: add PathOrDefault and ExtraEnv helpers; collapse the
  duplicated path-and-env type-assertion blocks in the four adapter init
  funcs (claude/codex/pi/kimi) to single-line constructors.
- DynamicAdapter.Prepare: drop errgroup over a one-element-of-real-work
  loop; sequential is sufficient and removes a dependency.
- Frontend: extract SectionHeading into its own component instead of
  importing it across feature sheets; add pickDefaultEngine helper used
  by both create- and edit-worker sheets.
… edit-worker mutations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Qsnh Qsnh merged commit 58743ac into main Apr 19, 2026
@Qsnh Qsnh deleted the feat/worker-engine-selection branch April 19, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant