@yemi33/minions 0.1.2381 → 0.1.2383
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/minions.js +42 -2
- package/dashboard/js/refresh.js +8 -2
- package/dashboard/js/render-other.js +38 -0
- package/dashboard/js/render-work-items.js +61 -20
- package/dashboard/js/settings.js +7 -8
- package/dashboard/pages/engine.html +6 -0
- package/dashboard.js +110 -27
- package/docs/README.md +1 -0
- package/docs/claude-md-propagation.md +118 -0
- package/docs/completion-reports.md +20 -1
- package/docs/engine-restart.md +10 -0
- package/docs/runtime-adapters.md +54 -22
- package/docs/skills.md +2 -0
- package/docs/workspace-manifests.md +1 -1
- package/docs/worktree-lifecycle.md +23 -0
- package/engine/acp-transport.js +63 -35
- package/engine/ado-comment.js +3 -1
- package/engine/agent-worker-pool.js +11 -4
- package/engine/cc-worker-pool.js +4 -2
- package/engine/claude-md-context.js +195 -0
- package/engine/cli.js +9 -1
- package/engine/comment-format.js +51 -14
- package/engine/consolidation.js +47 -3
- package/engine/db/migrations/026-pre-dispatch-rejections.js +55 -0
- package/engine/dispatch.js +80 -23
- package/engine/execution-model.js +68 -0
- package/engine/gh-comment.js +6 -3
- package/engine/github.js +6 -7
- package/engine/lifecycle.js +195 -56
- package/engine/llm.js +59 -83
- package/engine/memory-store.js +3 -1
- package/engine/pipeline.js +147 -20
- package/engine/playbook.js +39 -6
- package/engine/pooled-agent-process.js +28 -26
- package/engine/prd-store.js +14 -6
- package/engine/quarantine-refs.js +33 -0
- package/engine/queries.js +8 -6
- package/engine/restart-health.js +69 -4
- package/engine/runtimes/claude.js +100 -25
- package/engine/runtimes/codex.js +19 -0
- package/engine/runtimes/contract.js +239 -0
- package/engine/runtimes/copilot.js +85 -8
- package/engine/runtimes/index.js +37 -9
- package/engine/shared.js +157 -64
- package/engine/spawn-agent.js +79 -113
- package/engine/spawn-phase-watchdog.js +8 -37
- package/engine/supervisor.js +72 -16
- package/engine/timeout.js +87 -16
- package/engine/untrusted-fence.js +2 -1
- package/engine.js +434 -125
- package/package.json +1 -1
- package/playbooks/fix.md +20 -0
- package/playbooks/review.md +2 -0
- package/skills/check-self-authored-review-comment/SKILL.md +34 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# CLAUDE.md propagation to non-Claude runtimes
|
|
2
|
+
|
|
3
|
+
**Work item:** W-mrdon0pe000l045a
|
|
4
|
+
**Config flag:** `engine.propagateClaudeMdForNonClaudeRuntimes` (default `true`)
|
|
5
|
+
**Per-project override:** `project.propagateClaudeMdForNonClaudeRuntimes`
|
|
6
|
+
**Module:** `engine/claude-md-context.js` · **Injection:** `engine/playbook.js`
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
`CLAUDE.md` auto-discovery is a **Claude-Code-CLI-only** convention — the `claude`
|
|
11
|
+
binary reads repo-authored `CLAUDE.md` files itself at startup (unless `--bare`).
|
|
12
|
+
The **Copilot** and **Codex** runtimes have **no equivalent auto-load** for
|
|
13
|
+
`CLAUDE.md` at all. So repo-authored `CLAUDE.md` files — which frequently carry
|
|
14
|
+
load-bearing "must be kept in sync" instructions and name blessed tooling — are
|
|
15
|
+
structurally invisible to a Copilot/Codex-based fleet.
|
|
16
|
+
|
|
17
|
+
### Motivating incident
|
|
18
|
+
|
|
19
|
+
`W-mrdkv6zc00071fef` (a `fluidclientframework.android` dependency bump in
|
|
20
|
+
`office/src`) missed a **third** sync-point file (`settings.gradle.kts:116`) that
|
|
21
|
+
`loop/app/android/android/CLAUDE.md` explicitly warns about, and that
|
|
22
|
+
`updateDeps.bat` (named in that same `CLAUDE.md`) would have caught via its build
|
|
23
|
+
verification step. Neither the implementer nor the reviewer ever saw that file's
|
|
24
|
+
content because Copilot has no `CLAUDE.md` auto-load mechanism. Propagating
|
|
25
|
+
`CLAUDE.md` content directly addresses recurrence of this bug class.
|
|
26
|
+
|
|
27
|
+
## Relationship to Copilot's native `AGENTS.md` discovery (NOT the same gap)
|
|
28
|
+
|
|
29
|
+
Copilot has its **own native `AGENTS.md` auto-load**, which (post-#817, "Delegate
|
|
30
|
+
repository harness discovery to native runtimes") Minions leaves to the runtime
|
|
31
|
+
CLI itself rather than suppressing or synthesizing.
|
|
32
|
+
|
|
33
|
+
`CLAUDE.md` propagation is **independent and additive**:
|
|
34
|
+
|
|
35
|
+
- `CLAUDE.md` carries **zero split-brain risk** for Copilot/Codex, because those
|
|
36
|
+
runtimes **never read it themselves** — there is nothing to conflict with.
|
|
37
|
+
- Therefore propagation is **orthogonal** to Copilot's native `AGENTS.md`
|
|
38
|
+
discovery: whether or not the runtime loads its own `AGENTS.md` has **no
|
|
39
|
+
effect** on `CLAUDE.md` propagation.
|
|
40
|
+
|
|
41
|
+
| Concern | Owner | What governs it |
|
|
42
|
+
|---------|-------|-----------------|
|
|
43
|
+
| Copilot's OWN `AGENTS.md` auto-load | the Copilot CLI (native) | delegated to the runtime; Minions does not suppress or synthesize it |
|
|
44
|
+
| Repo `CLAUDE.md` invisible to non-Claude runtimes | `propagateClaudeMdForNonClaudeRuntimes` | injects a "Project instructions (CLAUDE.md)" prompt layer |
|
|
45
|
+
|
|
46
|
+
## Capability flag
|
|
47
|
+
|
|
48
|
+
Each runtime adapter (`engine/runtimes/<name>.js`) declares
|
|
49
|
+
`capabilities.claudeMdNativeDiscovery`:
|
|
50
|
+
|
|
51
|
+
| Runtime | `claudeMdNativeDiscovery` | Propagation |
|
|
52
|
+
|---------|---------------------------|-------------|
|
|
53
|
+
| `claude` | `true` | **skipped** — the Claude CLI already reads CLAUDE.md; re-injecting would double it |
|
|
54
|
+
| `copilot` | `false` | **injected** |
|
|
55
|
+
| `codex` | `false` | **injected** |
|
|
56
|
+
|
|
57
|
+
Unknown runtimes are treated as **not** auto-discovering (propagate), since an
|
|
58
|
+
unrecognized runtime is far more likely to lack native CLAUDE.md loading than to
|
|
59
|
+
have it. Engine code gates on the capability flag, never on `runtime.name`.
|
|
60
|
+
|
|
61
|
+
## Bounded discovery algorithm
|
|
62
|
+
|
|
63
|
+
`office/src` is a huge monorepo — a full tree walk collecting every `CLAUDE.md`
|
|
64
|
+
is not viable. Instead the module mirrors how a human/tool resolves "nearest
|
|
65
|
+
applicable instructions":
|
|
66
|
+
|
|
67
|
+
1. **Collect path hints** for the dispatch (`engine.js#_deriveClaudeMdPathHints`):
|
|
68
|
+
`item.meta.workdir` and `item.references[*].path`.
|
|
69
|
+
2. **Walk UP** from each hint directory toward the **project root**, `stat`-ing
|
|
70
|
+
`CLAUDE.md` at each ancestor level (`discoverClaudeMdFiles`).
|
|
71
|
+
3. **Always include the project-root `CLAUDE.md`** as a fallback — if there are
|
|
72
|
+
no path hints, at minimum the project root is checked (discovery is never
|
|
73
|
+
skipped entirely).
|
|
74
|
+
4. **Nearest-wins ordering:** deepest directory first, project root last, so the
|
|
75
|
+
most specific instructions lead.
|
|
76
|
+
5. **Bounds (cheap by construction):**
|
|
77
|
+
- `MAX_WALK_DEPTH = 24` ancestor levels per hint (deep-path guard),
|
|
78
|
+
- `MAX_FILES = 8` total collected files,
|
|
79
|
+
- path-traversal guard — hints that escape the project root are dropped,
|
|
80
|
+
- the walk **never ascends above the project root**.
|
|
81
|
+
|
|
82
|
+
This only `stat`s `CLAUDE.md` at a small number of ancestor directories, so it
|
|
83
|
+
does not meaningfully slow dispatch even in a monorepo.
|
|
84
|
+
|
|
85
|
+
## Injection & byte cap
|
|
86
|
+
|
|
87
|
+
`engine/playbook.js#renderPlaybook` adds `CLAUDE.md` propagation as a context
|
|
88
|
+
layer alongside the existing `pinned.md` → `notes.md` →
|
|
89
|
+
`knowledge/agents/<agentId>.md` layers:
|
|
90
|
+
|
|
91
|
+
- Header: `## Project instructions (CLAUDE.md)`, with a short provenance note so
|
|
92
|
+
the agent understands why it appears and that "keep in sync" rules are
|
|
93
|
+
authoritative.
|
|
94
|
+
- Content is wrapped in an `<UNTRUSTED-INPUT>` fence (repo-authored, semi-trusted).
|
|
95
|
+
- Each file is labeled by its repo-relative path (`### <relPath>`).
|
|
96
|
+
- The whole block is capped at `ENGINE_DEFAULTS.maxNotesPromptBytes` (the same
|
|
97
|
+
budget the sibling notes / agent-memory layers use), so it can never balloon a
|
|
98
|
+
prompt on a large monorepo. Individual files are truncated with a
|
|
99
|
+
"read the full file if needed" marker when they exceed the remaining budget.
|
|
100
|
+
|
|
101
|
+
Injection only happens when **all** hold:
|
|
102
|
+
|
|
103
|
+
1. the resolved runtime's `claudeMdNativeDiscovery` is falsy (Copilot/Codex),
|
|
104
|
+
2. `resolvePropagateClaudeMdForNonClaudeRuntimes(project, engine)` returns true,
|
|
105
|
+
3. the project has a `localPath`, and
|
|
106
|
+
4. at least one non-empty `CLAUDE.md` was discovered.
|
|
107
|
+
|
|
108
|
+
## Config resolution
|
|
109
|
+
|
|
110
|
+
`shared.resolvePropagateClaudeMdForNonClaudeRuntimes(project, engine)`:
|
|
111
|
+
|
|
112
|
+
1. per-project boolean `project.propagateClaudeMdForNonClaudeRuntimes`, else
|
|
113
|
+
2. fleet-wide boolean `engine.propagateClaudeMdForNonClaudeRuntimes`, else
|
|
114
|
+
3. hardcoded default `true`.
|
|
115
|
+
|
|
116
|
+
Only an explicit boolean counts as "set" at either level (undefined/null/string
|
|
117
|
+
fall through). Editable from **Settings → Copilot Tuning → "Propagate CLAUDE.md
|
|
118
|
+
to non-Claude runtimes"** or via `POST /api/settings`.
|
|
@@ -90,6 +90,7 @@ Do **not** invent, regenerate, or share the nonce across dispatches — each spa
|
|
|
90
90
|
|---|---|---|
|
|
91
91
|
| `noop` | boolean | Canonical no-op signal. See [No-op semantics](#no-op-semantics). |
|
|
92
92
|
| `noopReason` | string | Human-readable rationale shown when `noop: true`. Falls back to `summary` if absent. |
|
|
93
|
+
| `reviewFindingResolution` | object | Required when a review-feedback fix leaves the PR branch unchanged. Shape: `{findingId, disposition, currentCodeEvidence}`; see [Review-fix no-op evidence](#review-fix-no-op-evidence). |
|
|
93
94
|
| `files_changed` | string \| array | Comma-separated list (or array) of key files changed. |
|
|
94
95
|
| `affected_files` | string[] | Optional array of relative file paths this dispatch touched or plans to touch. Used by the dispatcher to emit a conflict warning (`WI <new-id> may conflict with in-progress <existing-id> on files: [list]`) when a new WI's `affected_files` overlaps with an in-progress WI's `affected_files`. Logging-only — dispatch is never blocked. Stored back onto the work item after completion so re-dispatches can also participate in overlap detection. |
|
|
95
96
|
| `tests` | string | `pass`, `fail`, `skipped`, `N/A`, or a free-form note like `skipped — relying on PR pipeline`. |
|
|
@@ -223,7 +224,7 @@ All three classes are **never retryable** — they signal a structural/environme
|
|
|
223
224
|
|
|
224
225
|
## No-op semantics
|
|
225
226
|
|
|
226
|
-
A no-op completion declares that the agent correctly **declined** to do the work — the change was already shipped on master, the dispatch premise was wrong,
|
|
227
|
+
A no-op completion declares that the agent correctly **declined** to do the work — the change was already shipped on master, the dispatch premise was wrong, or an exact review finding was proven resolved/invalid with current-code evidence.
|
|
227
228
|
|
|
228
229
|
To signal a no-op:
|
|
229
230
|
|
|
@@ -245,6 +246,24 @@ Engine behavior when `noop: true` (`engine/lifecycle.js` `parseCompletionNoop` +
|
|
|
245
246
|
|
|
246
247
|
Without `noop: true`, an empty PR field will be flagged as a missing-PR-attachment failure and auto-retried up to `ENGINE_DEFAULTS.maxRetries` times.
|
|
247
248
|
|
|
249
|
+
### Review-fix no-op evidence
|
|
250
|
+
|
|
251
|
+
Review-feedback fixes have a stricter no-op contract because all agents can share one platform credential. `viewerDidAuthor: true` does not identify the Minions agent that authored a finding, and reviewer/fixer agent equality is allowed.
|
|
252
|
+
|
|
253
|
+
If the live PR branch did not advance, the completion must set `noop: true` and include:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"reviewFindingResolution": {
|
|
258
|
+
"findingId": "review-0123456789abcdef",
|
|
259
|
+
"disposition": "already-resolved",
|
|
260
|
+
"currentCodeEvidence": "engine/lifecycle.js:4600 already enforces the condition; test/unit/example.test.js covers it."
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The `findingId` is injected into the fix playbook from trusted `minionsReview` provenance. `disposition` is `already-resolved` or `invalid`, and `currentCodeEvidence` must cite a current `file:line` or commit. An identity-only no-op is retried and does not increment `_noOpFixes`.
|
|
266
|
+
|
|
248
267
|
## PR-comment follow-ups
|
|
249
268
|
|
|
250
269
|
Fix and review agents can spin off new Minions work items in response to PR
|
package/docs/engine-restart.md
CHANGED
|
@@ -70,6 +70,16 @@ If an agent is killed as an orphan and the work item retries, cooldowns use expo
|
|
|
70
70
|
|
|
71
71
|
The engine.js *process itself* (not an agent) can crash and get auto-respawned by three independent mechanisms: `engine/supervisor.js#checkEngine` (dead-PID), `engine/supervisor.js#checkEngineHung` (stale-heartbeat), and `engine/watchdog.js` (OS-scheduled external recovery); `dashboard.js`'s in-process 30s watchdog also auto-restarts on a dead PID (the user-triggered Restart Engine button is excluded). Each successful respawn calls `shared.recordEngineRespawn(source)`, which appends `{ts, source}` to `control.json`'s `engineRespawns[]` (rolling window, default `CRASH_LOOP_WINDOW_MS` = 10 min). Once respawns within the window reach `CRASH_LOOP_THRESHOLD` (default 3), `control.crashLoopAlert` is set (dashboard-visible via `/api/status`) and a deduped (one-per-day) `notes/inbox/` alert is written; the alert clears automatically once the window rolls past the incident. Override the window/threshold via `MINIONS_CRASH_LOOP_WINDOW_MS` / `MINIONS_CRASH_LOOP_THRESHOLD` env vars.
|
|
72
72
|
|
|
73
|
+
### 7. Exclusive Restart Topology
|
|
74
|
+
|
|
75
|
+
Managed dashboard spawns must bind the requested port; automatic fallback remains available only for standalone `node dashboard.js` use. Before a supervisor respawn, every matching stale daemon is killed and confirmed dead. If any PID survives, the supervisor skips the replacement and retries on its next tick instead of creating a second engine or a dashboard on another port.
|
|
76
|
+
|
|
77
|
+
`minions restart` verifies ownership of the exact engine, dashboard, and supervisor PIDs it spawned and rejects duplicate daemon processes. A failed verification tears down the partial stack and leaves `stop-intent.json` set so watchdogs cannot race another replacement into the failed topology.
|
|
78
|
+
|
|
79
|
+
PID-bearing file locks are reaped immediately when their recorded owner is dead. The five-minute stale threshold remains for legacy locks without owner metadata and the longer safety window remains for live holders.
|
|
80
|
+
|
|
81
|
+
Dashboard hot polling avoids recurring event-loop stalls: freshness-directory walks are coalesced briefly, plan scans are cached for 30 seconds and processed in cooperative batches, and quarantine-ref Git reads run asynchronously in parallel.
|
|
82
|
+
|
|
73
83
|
## Safe Restart Pattern
|
|
74
84
|
|
|
75
85
|
```bash
|
package/docs/runtime-adapters.md
CHANGED
|
@@ -8,7 +8,8 @@ behavior is hidden behind an adapter object resolved through `resolveRuntime()`.
|
|
|
8
8
|
|
|
9
9
|
| File | Role |
|
|
10
10
|
|------|------|
|
|
11
|
-
| `engine/runtimes/
|
|
11
|
+
| `engine/runtimes/contract.js` | Versioned adapter contract, capability gating, opaque invocation-option transport, and optional-hook facades. |
|
|
12
|
+
| `engine/runtimes/index.js` | Adapter registry and the only runtime facade imported by orchestration code. |
|
|
12
13
|
| `engine/runtimes/claude.js` | Claude Code adapter. Owns binary probe, `--system-prompt-file`, JSONL parser, model shorthands, budget cap, bare mode. |
|
|
13
14
|
| `engine/runtimes/copilot.js` | GitHub Copilot CLI adapter. Owns standalone-vs-`gh-copilot` resolution, stdin-only prompt delivery, `https://api.githubcopilot.com/models` discovery, effort `'max' → 'xhigh'` mapping. |
|
|
14
15
|
| `engine/runtimes/codex.js` | OpenAI Codex CLI adapter. Owns `@openai/codex`/native binary resolution, `codex exec --json -` prompt delivery, `codex debug models --bundled` discovery, `.agents/skills` roots, and Codex JSONL parsing. |
|
|
@@ -30,6 +31,7 @@ methods that genuinely differ.
|
|
|
30
31
|
|
|
31
32
|
| Member | Type / Returns | Purpose |
|
|
32
33
|
|--------|----------------|---------|
|
|
34
|
+
| `apiVersion` | number | Contract version. Bundled adapters use `1`; incompatible versions fail at registration. |
|
|
33
35
|
| `name` | string | Adapter identifier (matches the registry key). |
|
|
34
36
|
| `capabilities` | flag object (see below) | Feature flags consumed by engine code. |
|
|
35
37
|
| `resolveBinary({ env, config? })` | `{ bin, native, leadingArgs }` or null | Probe PATH, npm-globals, package overrides; cached to `engine/<name>-caps.json`. `leadingArgs` is `[]` for direct binaries and `['copilot']` for the `gh copilot` extension fallback. |
|
|
@@ -39,7 +41,8 @@ methods that genuinely differ.
|
|
|
39
41
|
| `modelsCache` | absolute path | Cache file for `listModels()` results. |
|
|
40
42
|
| `spawnScript` | absolute path | Path to the runtime-agnostic spawn wrapper (currently `engine/spawn-agent.js` for both runtimes). |
|
|
41
43
|
| `buildArgs(opts)` | `string[]` | CLI args excluding the binary itself. |
|
|
42
|
-
| `
|
|
44
|
+
| `resolveInvocationOptions({ options, engineConfig, config, failureClass })` | object | Optional; maps harness-specific config and retry policy into semantic invocation options. Core code never reads harness-specific invocation keys. |
|
|
45
|
+
| `buildSpawnFlags(opts)` | `string[]` | Optional compatibility flags for older spawn wrappers. New fields travel in the opaque `--runtime-options` bag automatically. |
|
|
43
46
|
| `buildPrompt(promptText, sysPromptText)` | string | Final prompt delivered to the CLI. Adapters that lack `--system-prompt-file` (Copilot) prepend a `<system>` block here. |
|
|
44
47
|
| `getUserAssetDirs({ homeDir })` | `string[]` | Runtime-native global asset roots passed to spawn as `--add-dir` so worktrees still see them. |
|
|
45
48
|
| `getSkillRoots({ homeDir, project? })` | `[{ scope, dir, projectName? }]` | Where `collectSkillFiles` looks for native + project skill markdown. |
|
|
@@ -52,11 +55,39 @@ methods that genuinely differ.
|
|
|
52
55
|
| `parseStreamChunk(line)` | event object or null | Single JSONL line → typed event. |
|
|
53
56
|
| `parseError(rawOutput)` | `{ message, code, retriable }` | Codes: `auth-failure`, `context-limit`, `budget-exceeded`, `model-unavailable` (retriable=true for upstream overload/503; retriable=false for invalid/typo'd model id — Copilot enriches the message via `_warmModelCache()` so it lists the available models), `crash`, null. |
|
|
54
57
|
| `createStreamConsumer(ctx)` | consumer object | Stream accumulator used by `engine/llm.js`. |
|
|
55
|
-
| `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
(
|
|
59
|
-
|
|
58
|
+
| `prepareWorkspace(ctx)` | result object | Optional synchronous hook for harness-owned workspace state, such as trust configuration. |
|
|
59
|
+
| `isSpawnStartupEvent(event)` | boolean | Optional startup-event classifier used by the spawn-phase watchdog. |
|
|
60
|
+
| `shouldSuppressPostMutationError(ctx)` | boolean | Optional harness quirk policy for an error received after a confirmed mutation. |
|
|
61
|
+
| `buildWorkerArgs(opts)` | string[] | Required with `acpWorkerPool`; owns persistent-worker CLI flags. |
|
|
62
|
+
| `encodePooledOutput(event)` | string | Required with `acpWorkerPool`; translates semantic pooled events into the adapter's normal parser format. |
|
|
63
|
+
| `detectPermissionGate`, `getPromptDeliveryMode`, `usesSystemPromptFile`, `classifyFailure` | misc | Adapter-owned policy that orchestration reads through the facade instead of branching on `runtime.name`. |
|
|
64
|
+
|
|
65
|
+
`engine/runtimes/contract.js` is authoritative. Registration normalizes legacy
|
|
66
|
+
partial test/tooling adapters, validates bundled adapters strictly, and rejects
|
|
67
|
+
an explicit unsupported `apiVersion`.
|
|
68
|
+
|
|
69
|
+
### Stable invocation boundary
|
|
70
|
+
|
|
71
|
+
Minions passes semantic values such as `model`, `effort`, and `sessionId`; it
|
|
72
|
+
does not construct harness CLI argv. The selected adapter:
|
|
73
|
+
|
|
74
|
+
1. maps its config through `resolveInvocationOptions()`;
|
|
75
|
+
2. receives the full options object through an opaque base64url JSON
|
|
76
|
+
`--runtime-options` channel when the spawn wrapper is used; and
|
|
77
|
+
3. converts those options to real CLI flags only in `buildArgs()`.
|
|
78
|
+
|
|
79
|
+
Legacy named wrapper flags remain for backward compatibility, but they are no
|
|
80
|
+
longer the extension point. A new harness option can be added entirely inside
|
|
81
|
+
its adapter without teaching `engine.js`, `engine/llm.js`, or
|
|
82
|
+
`engine/spawn-agent.js` another CLI flag. Large image payloads are written to a
|
|
83
|
+
protected per-call sidecar; only its path enters the options argument, and the
|
|
84
|
+
wrapper hydrates then deletes it before invoking the adapter.
|
|
85
|
+
|
|
86
|
+
Retirement gates for the temporary compatibility surfaces are tracked in
|
|
87
|
+
`docs/deprecated.json`: `legacy-runtime-named-spawn-flags`,
|
|
88
|
+
`versionless-runtime-adapter-compat`, and
|
|
89
|
+
`preapprove-workspace-mcps-wrapper`. Calendar dates never override their
|
|
90
|
+
external-usage gates.
|
|
60
91
|
|
|
61
92
|
## Capability Flags
|
|
62
93
|
|
|
@@ -81,7 +112,7 @@ real behavioral split appears between adapters.
|
|
|
81
112
|
| `resumePromptCarryover` | ✗ | ✓ | ✓ | CC resume turns prepend recent visible Q&A in stdin when the runtime session store is opaque to Minions. |
|
|
82
113
|
| `resumeBookkeepingTurn` | ✓ | — | — | Claude CLI injects a synthetic "Continue from where you left off." meta turn on `--resume`; CC prompts must tell the model not to treat it as user intent. |
|
|
83
114
|
| `streamConsumer` | ✓ | ✓ | ✓ | Adapter implements `createStreamConsumer(ctx)` — required by `engine/llm.js` accumulator. |
|
|
84
|
-
| `imageInput` | ✓ | ✓ |
|
|
115
|
+
| `imageInput` | ✓ | ✓ | ✓ | Runtime accepts `images: [{mimeType, dataBase64}]` and delivers them to the CLI using adapter-owned transport. When false, `_resolveImageOpts` returns a typed `model-unavailable` error. |
|
|
85
116
|
| `acpWorkerPool` | ✗ | ✓ | ✗ | Runtime supports `<bin> --acp` (Agent Client Protocol), so it can be routed through a persistent worker pool instead of cold-spawning a fresh CLI process per turn/dispatch. Two independent consumers gate on this flag: `engine/cc-worker-pool.js` (CC/doc-chat tabs, `resolveCcUseWorkerPool`) and `engine/agent-worker-pool.js` (fleet agent dispatch, `resolveAgentUseWorkerPool`). Both resolvers hard-return `false` whenever this flag isn't `true` on the resolved runtime, regardless of any operator override, so a future non-ACP runtime can never be silently pooled. See [ACP Worker Pool](#acp-worker-pool-fleet-agent-dispatch) below. |
|
|
86
117
|
|
|
87
118
|
When a behavior is genuinely uniform across all current adapters, it still gets
|
|
@@ -197,9 +228,9 @@ agent fleet to Copilot, and vice versa. Both paths fall through to
|
|
|
197
228
|
`engine.defaultCli` (the fleet-wide default), but they never see each other's
|
|
198
229
|
overrides.
|
|
199
230
|
|
|
200
|
-
|
|
201
|
-
resolution. Engine code MUST go through
|
|
202
|
-
directly.
|
|
231
|
+
The helpers in `engine/shared.js` are the single source of truth for
|
|
232
|
+
runtime/model and cross-runtime policy resolution. Engine code MUST go through
|
|
233
|
+
them instead of reading config keys directly.
|
|
203
234
|
|
|
204
235
|
| Helper | Chain |
|
|
205
236
|
|--------|-------|
|
|
@@ -207,6 +238,7 @@ directly.
|
|
|
207
238
|
| `resolveCcCli(engine)` | `engine.ccCli` → `engine.defaultCli` → `'copilot'` |
|
|
208
239
|
| `resolveAgentModel(agent, engine)` | `agent.model` → `engine.defaultModel` → undefined |
|
|
209
240
|
| `resolveCcModel(engine)` | `engine.ccModel` → `engine.defaultModel` → undefined |
|
|
241
|
+
| `resolveAgentAllowedTools(config)` | Legacy `config.claude.allowedTools` fleet ceiling → undefined. Despite its legacy namespace, it applies to every selected runtime. |
|
|
210
242
|
| `resolveAgentMaxBudget(agent, engine)` | `agent.maxBudgetUsd` → `engine.maxBudgetUsd`. Honors literal `0`. |
|
|
211
243
|
| `resolveAgentBareMode(agent, engine)` | `agent.bareMode` → `engine.claudeBareMode` → false. Strict null check so per-agent `false` overrides engine `true`. |
|
|
212
244
|
|
|
@@ -285,8 +317,9 @@ codifies are documented in [`docs/harness-propagation.md`](./harness-propagation
|
|
|
285
317
|
|
|
286
318
|
## The "No Runtime-Name Branching" Rule
|
|
287
319
|
|
|
288
|
-
The whole point of this layer: **
|
|
289
|
-
|
|
320
|
+
The whole point of this layer: **orchestration code MUST use the runtime facade,
|
|
321
|
+
capabilities, and adapter hooks rather than concrete imports, harness config
|
|
322
|
+
keys, or `runtime.name === 'claude'` comparisons.**
|
|
290
323
|
|
|
291
324
|
If you find yourself wanting to write `if (runtime.name === 'copilot')` in
|
|
292
325
|
engine code, the correct response is one of:
|
|
@@ -296,19 +329,18 @@ engine code, the correct response is one of:
|
|
|
296
329
|
3. Move the special case into the adapter's `buildArgs` / `buildPrompt` /
|
|
297
330
|
`parseOutput` where it belongs.
|
|
298
331
|
|
|
299
|
-
Tests in `test/unit/runtime-adapters.test.js`
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
code.
|
|
332
|
+
Tests in `test/unit/runtime-adapters.test.js` enforce the contract.
|
|
333
|
+
`test/unit/runtime-boundary.test.js` rejects concrete adapter imports,
|
|
334
|
+
runtime-name branches, and harness-specific invocation config in core paths.
|
|
303
335
|
|
|
304
336
|
## Adding a New Runtime
|
|
305
337
|
|
|
306
|
-
1. Create `engine/runtimes/<name>.js` implementing
|
|
307
|
-
|
|
308
|
-
Set a useful `installHint`.
|
|
338
|
+
1. Create `engine/runtimes/<name>.js` implementing adapter API version `1`.
|
|
339
|
+
Keep all CLI flags, output schemas, workspace quirks, and worker behavior in
|
|
340
|
+
that file. Set a useful `installHint`.
|
|
309
341
|
2. Register it in `engine/runtimes/index.js`:
|
|
310
342
|
```js
|
|
311
|
-
|
|
343
|
+
registerRuntime('<name>', require('./<name>'), { strict: true });
|
|
312
344
|
```
|
|
313
345
|
3. Add unit coverage modeled on `test/unit/runtime-adapters.test.js`.
|
|
314
346
|
|
|
@@ -324,6 +356,6 @@ flags — never special-case in engine code.
|
|
|
324
356
|
- `docs/copilot-cli-schema.md` — empirical Copilot CLI behavior captured during
|
|
325
357
|
the spike that produced the Copilot adapter; cite it when changing
|
|
326
358
|
Copilot-specific capability flags.
|
|
327
|
-
- `engine/runtimes/
|
|
359
|
+
- `engine/runtimes/contract.js` — authoritative adapter contract and facade.
|
|
328
360
|
- `engine/shared.js` `resolveAgentCli` / `resolveCcCli` etc. — the only
|
|
329
361
|
supported way to read runtime / model selection from config.
|
package/docs/skills.md
CHANGED
|
@@ -4,6 +4,8 @@ Agents emit a fenced `skill` block when they discover a durable, reusable workfl
|
|
|
4
4
|
|
|
5
5
|
The engine extracts valid blocks from agent stdout and from inbox findings (`notes/inbox/*.md`) and writes them into the selected runtime's native personal skills directory (e.g. `~/.claude/skills/<name>/SKILL.md` for Claude, the equivalent personal skills directory for Copilot). Project-scoped skills are landed via PR into the matching project's playbook tree.
|
|
6
6
|
|
|
7
|
+
Packaged `scope: minions` skills are synchronized into every registered runtime's personal skill directory during `minions init` and upgrades. A packaged skill replaces an existing same-name personal copy so contract fixes take effect immediately.
|
|
8
|
+
|
|
7
9
|
## Block format
|
|
8
10
|
|
|
9
11
|
````
|
|
@@ -54,7 +54,7 @@ A config that never mentions `workspace_manifest` produces the exact same dispat
|
|
|
54
54
|
| Phase | Location | What it does |
|
|
55
55
|
| ----- | -------- | ------------ |
|
|
56
56
|
| **Dispatch — repo gate** | `engine.js spawnAgent()` (right after project resolution) | Calls `shared.agentCanUseRepo(agent, project)`. Mismatch → `completeDispatch(... FAILURE_CLASS.WORKSPACE_MANIFEST_REPO, agentRetryable: false)` and `cleanupTempAgent` runs. Non-retryable because the structural answer is "widen the manifest or pick a different agent". |
|
|
57
|
-
| **Spawn — tool merge** | `engine.js spawnAgent()` → `_buildAgentSpawnFlags(..., allowedTools)` | `shared.
|
|
57
|
+
| **Spawn — tool merge** | `engine.js spawnAgent()` → `_buildAgentSpawnFlags(..., allowedTools)` | `shared.resolveAgentAllowedTools(config)` preserves the legacy fleet baseline for every selected runtime, then `shared.mergeManifestAllowedTools(baseline, manifest.allowed_tools)` produces the intersection. Result is passed through the Claude / Copilot / Codex adapter option path. Empty manifest list (`[]`) = deny-all. Same resolved option bag is reused on the steering-resume codepath. |
|
|
58
58
|
| **Agent context** | Future work (playbook.js) | Manifest can be surfaced into the agent prompt so the agent sees its declared scope. Today, `shared.resolveAgentManifest(agent)` returns the resolved struct any caller can read. |
|
|
59
59
|
| **URL fetch** | Advisory today | `allowed_external_urls` is validated at config load. No runtime helper function — a future runtime adapter hook can wire URL-fetch intercepts against the manifest. |
|
|
60
60
|
| **Memory scope** | Advisory today | `memory_scope` field is stored in the manifest (`private` = agent only sees its own `knowledge/agents/<id>.md`; `shared` = full team knowledge (current default); `read-only-shared` = reads shared memory but should not write inbox/notes). No runtime enforcement helper — full enforcement at the consolidation layer is future work. |
|
|
@@ -47,6 +47,20 @@ recycle worktree dirs across branches.
|
|
|
47
47
|
→ `git checkout --detach origin/<main>` → mark IDLE.
|
|
48
48
|
- **State** in SQLite (`worktree_pool`); git ops outside the transaction.
|
|
49
49
|
|
|
50
|
+
## Fresh ordinary branch bases
|
|
51
|
+
|
|
52
|
+
When the pool is disabled or has no usable entry, a brand-new ordinary mutating
|
|
53
|
+
branch is created only after `git fetch origin <mainBranch>` succeeds, and its
|
|
54
|
+
base is always `origin/<mainBranch>`. A network or authentication failure stops
|
|
55
|
+
before `git worktree add` or agent spawn and leaves the work item retryable; the
|
|
56
|
+
engine never falls back to a potentially stale local `<mainBranch>`.
|
|
57
|
+
|
|
58
|
+
This fail-closed rule applies only when the requested branch is confirmed absent
|
|
59
|
+
from origin. PR-targeted / `useExistingBranch`, shared-branch, and retry paths
|
|
60
|
+
that intentionally preserve an existing branch continue using that branch
|
|
61
|
+
unchanged. Warm-pool borrowing keeps its existing fetch-and-checkout of
|
|
62
|
+
`origin/<mainBranch>`.
|
|
63
|
+
|
|
50
64
|
## Ownership marker is never "dirty" (#284)
|
|
51
65
|
|
|
52
66
|
The reused-worktree preflight (`assertCleanSharedWorktree`) builds its
|
|
@@ -77,6 +91,15 @@ distinguishes the **original worktree-preflight issue** from a
|
|
|
77
91
|
|
|
78
92
|
## Quarantine path (dirty / divergent)
|
|
79
93
|
|
|
94
|
+
Before quarantine, a reused worktree that is filesystem-clean and strictly
|
|
95
|
+
ahead of `origin/<branch>` is pushed fast-forward to origin. Dispatch close
|
|
96
|
+
and stale-orphan cleanup run the same preservation check before pool return or
|
|
97
|
+
worktree removal. Dirty trees and branches confirmed behind origin retain the
|
|
98
|
+
quarantine fallback; transient probe, fetch, auth, and push failures leave clean
|
|
99
|
+
ahead commits in place for retry. Recoverable
|
|
100
|
+
`refs/minions/quarantine/*` and `refs/minions/quarantine-wip/*` refs are listed
|
|
101
|
+
on Dashboard → Engine under **Quarantined Work**.
|
|
102
|
+
|
|
80
103
|
When `discoverFromWorkItems` finds a worktree in a `WORKTREE_DIRTY`,
|
|
81
104
|
`WORKTREE_DIVERGENT`, or post-stuck state, `_quarantineDirtyWorktree`
|
|
82
105
|
moves it to `<root>/.quarantined/<basename>-<utc>` and lets the next tick
|
package/engine/acp-transport.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* engine/acp-transport.js — Shared ACP (Agent Client Protocol) transport layer
|
|
3
3
|
* (P-4c6e8a72, extracted from engine/cc-worker-pool.js).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* module owns
|
|
7
|
-
*
|
|
5
|
+
* ACP-capable harnesses expose a long-lived JSON-RPC server over stdin/stdout.
|
|
6
|
+
* This module owns protocol plumbing that is identical regardless of which
|
|
7
|
+
* adapter or consumer is using the worker:
|
|
8
8
|
*
|
|
9
|
-
* - process spawn (`spawnAcp`) —
|
|
10
|
-
* runtime adapter
|
|
9
|
+
* - process spawn (`spawnAcp`) — delegates binary and argv construction to
|
|
10
|
+
* the selected runtime adapter.
|
|
11
11
|
* - newline-delimited JSON-RPC framing, request/response correlation, and
|
|
12
12
|
* `session/update` notification dispatch (the `Worker` class).
|
|
13
13
|
* - typed error envelope (`ERROR_CODES` / `typedError`).
|
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
|
|
46
46
|
const { spawn } = require('child_process');
|
|
47
47
|
const crypto = require('crypto');
|
|
48
|
+
const { normalizeExecutionModel } = require('./execution-model');
|
|
49
|
+
const {
|
|
50
|
+
resolveRuntime,
|
|
51
|
+
resolveRuntimeByCapability,
|
|
52
|
+
buildWorkerArgs,
|
|
53
|
+
} = require('./runtimes');
|
|
48
54
|
|
|
49
55
|
// W-mpmwxni2000c25c7-c — typed error codes the transport emits through every
|
|
50
56
|
// failure exit so a consumer (CC streaming handler / doc-chat pool wrapper /
|
|
@@ -56,8 +62,7 @@ const ERROR_CODES = Object.freeze({
|
|
|
56
62
|
// because a transient PATH / fs glitch may recover.
|
|
57
63
|
WORKER_SPAWN_FAILED: 'worker-spawn-failed',
|
|
58
64
|
// The worker process exited DURING the ACP handshake (initialize or
|
|
59
|
-
// session/new)
|
|
60
|
-
// version is too old. Also fires when session/new returns no
|
|
65
|
+
// session/new), or session/new returned no
|
|
61
66
|
// sessionId. Retriable: the caller swaps to a fallback model / a re-auth
|
|
62
67
|
// may unblock the next attempt.
|
|
63
68
|
ACP_HANDSHAKE_FAILED: 'acp-handshake-failed',
|
|
@@ -89,25 +94,29 @@ function typedError(message, code, retriable = true) {
|
|
|
89
94
|
// same knob instead of hardcoding their own copy.
|
|
90
95
|
const WARM_MAX_CONCURRENT = 3;
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
function resolveWorkerRuntime(runtime) {
|
|
98
|
+
const adapter = typeof runtime === 'string'
|
|
99
|
+
? resolveRuntime(runtime)
|
|
100
|
+
: (runtime || resolveRuntimeByCapability('acpWorkerPool'));
|
|
101
|
+
if (adapter?.capabilities?.acpWorkerPool !== true) {
|
|
102
|
+
throw new Error(`Runtime "${adapter?.name || '<unknown>'}" does not support ACP workers`);
|
|
103
|
+
}
|
|
104
|
+
return adapter;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Real spawn implementation. Binary resolution and worker argv are both
|
|
108
|
+
// adapter-owned so harness CLI flag changes do not leak into the transport.
|
|
109
|
+
function spawnAcp({ runtime, cwd, env } = {}) {
|
|
110
|
+
const adapter = resolveWorkerRuntime(runtime);
|
|
102
111
|
const workerEnv = env || process.env;
|
|
103
|
-
const resolved =
|
|
112
|
+
const resolved = adapter.resolveBinary({ env: workerEnv });
|
|
104
113
|
if (!resolved || !resolved.bin) {
|
|
105
114
|
throw new Error(
|
|
106
|
-
|
|
115
|
+
`${adapter.name} binary not resolvable -- ${adapter.installHint || `install the ${adapter.name} CLI`}`
|
|
107
116
|
);
|
|
108
117
|
}
|
|
109
118
|
const { bin, native, leadingArgs = [] } = resolved;
|
|
110
|
-
const acpArgs = [...leadingArgs,
|
|
119
|
+
const acpArgs = [...leadingArgs, ...buildWorkerArgs(adapter, { cwd, env: workerEnv })];
|
|
111
120
|
// Mirror engine/spawn-agent.js: native binaries run directly; a non-native
|
|
112
121
|
// (JS entry / npm shim) runs under process.execPath as `node <bin> ...`.
|
|
113
122
|
const execBin = native ? bin : process.execPath;
|
|
@@ -181,6 +190,19 @@ function buildSessionNewParams({ cwd, mcpServers, model, effort }) {
|
|
|
181
190
|
return params;
|
|
182
191
|
}
|
|
183
192
|
|
|
193
|
+
function extractSessionModel(result) {
|
|
194
|
+
for (const value of [
|
|
195
|
+
result?.models?.currentModelId,
|
|
196
|
+
result?.currentModelId,
|
|
197
|
+
result?.modelId,
|
|
198
|
+
result?.model,
|
|
199
|
+
]) {
|
|
200
|
+
const model = normalizeExecutionModel(value);
|
|
201
|
+
if (model) return model;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
184
206
|
function extractChunkText(content) {
|
|
185
207
|
if (content == null) return '';
|
|
186
208
|
if (typeof content === 'string') return content;
|
|
@@ -246,9 +268,11 @@ function mapAcpToolCallToToolUse(update) {
|
|
|
246
268
|
*/
|
|
247
269
|
class Worker {
|
|
248
270
|
constructor({
|
|
271
|
+
runtime,
|
|
249
272
|
id, model, effort, mcpServers, mcpServersHash, processEnv, processContextHash,
|
|
250
273
|
systemPromptHash, cwd, internals, trace,
|
|
251
274
|
}) {
|
|
275
|
+
this.runtime = resolveWorkerRuntime(runtime);
|
|
252
276
|
this.id = id;
|
|
253
277
|
this.model = model;
|
|
254
278
|
this.effort = effort;
|
|
@@ -283,13 +307,11 @@ class Worker {
|
|
|
283
307
|
async _spawnAndInit() {
|
|
284
308
|
let proc;
|
|
285
309
|
try {
|
|
286
|
-
proc = this._internals.spawnAcp({ cwd: this.cwd, env: this.processEnv });
|
|
310
|
+
proc = this._internals.spawnAcp({ runtime: this.runtime, cwd: this.cwd, env: this.processEnv });
|
|
287
311
|
} catch (err) {
|
|
288
|
-
|
|
289
|
-
// on PATH) or EACCES. Surface as worker-spawn-failed so the consumer
|
|
290
|
-
// can show "install the CLI / fix PATH" guidance.
|
|
312
|
+
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
291
313
|
throw typedError(
|
|
292
|
-
|
|
314
|
+
`${hint} (${err.message})`,
|
|
293
315
|
ERROR_CODES.WORKER_SPAWN_FAILED,
|
|
294
316
|
true
|
|
295
317
|
);
|
|
@@ -307,11 +329,9 @@ class Worker {
|
|
|
307
329
|
const earlyExitPromise = new Promise((_, reject) => {
|
|
308
330
|
earlyExitReject = (code) => {
|
|
309
331
|
this.killed = true;
|
|
310
|
-
|
|
311
|
-
// always missing `copilot login`, stale CLI, or daemon crash on
|
|
312
|
-
// boot). Retriable so re-auth or a CLI upgrade can recover.
|
|
332
|
+
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
313
333
|
const err = typedError(
|
|
314
|
-
|
|
334
|
+
`${hint} (exit ${code})`,
|
|
315
335
|
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
316
336
|
true
|
|
317
337
|
);
|
|
@@ -327,8 +347,9 @@ class Worker {
|
|
|
327
347
|
// proc 'error' event fires when the OS can't actually start the child
|
|
328
348
|
// (ENOENT after a successful spawn() call, etc.). Treat as a spawn
|
|
329
349
|
// failure even though we made it past the synchronous spawn() above.
|
|
350
|
+
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
330
351
|
const wrapped = typedError(
|
|
331
|
-
|
|
352
|
+
`${hint} (${err.message})`,
|
|
332
353
|
ERROR_CODES.WORKER_SPAWN_FAILED,
|
|
333
354
|
true
|
|
334
355
|
);
|
|
@@ -350,11 +371,13 @@ class Worker {
|
|
|
350
371
|
earlyExitPromise,
|
|
351
372
|
]);
|
|
352
373
|
this.sessionId = result && result.sessionId;
|
|
374
|
+
this.currentModel = extractSessionModel(result) || normalizeExecutionModel(this.model);
|
|
353
375
|
if (!this.sessionId) {
|
|
354
376
|
// Handshake completed without an error but the daemon didn't hand
|
|
355
377
|
// back a sessionId — protocol violation or partial init failure.
|
|
378
|
+
const hint = this.runtime.workerErrorHint || `${this.runtime.name} persistent worker failed`;
|
|
356
379
|
throw typedError(
|
|
357
|
-
|
|
380
|
+
`${hint} (session/new returned no sessionId)`,
|
|
358
381
|
ERROR_CODES.ACP_HANDSHAKE_FAILED,
|
|
359
382
|
true
|
|
360
383
|
);
|
|
@@ -369,7 +392,7 @@ class Worker {
|
|
|
369
392
|
// Post-handshake exit = the daemon died mid-conversation. Retriable
|
|
370
393
|
// because the next call will cold-spawn a fresh worker.
|
|
371
394
|
const err = typedError(
|
|
372
|
-
|
|
395
|
+
`${this.runtime.name} persistent worker process exited`,
|
|
373
396
|
ERROR_CODES.WORKER_DIED,
|
|
374
397
|
true
|
|
375
398
|
);
|
|
@@ -563,7 +586,8 @@ class Worker {
|
|
|
563
586
|
this._notify('session/cancel', { sessionId: this.inflight.sessionId });
|
|
564
587
|
}
|
|
565
588
|
|
|
566
|
-
async newSession(
|
|
589
|
+
async newSession(options = {}) {
|
|
590
|
+
const { mcpServers, systemPromptHash, model, effort, cwd } = options;
|
|
567
591
|
// Cancel any inflight before swapping the underlying session.
|
|
568
592
|
if (this.inflight) {
|
|
569
593
|
this.cancel();
|
|
@@ -582,8 +606,9 @@ class Worker {
|
|
|
582
606
|
// Bug B (issue #2479): if the caller is rotating the session because the
|
|
583
607
|
// system prompt changed, they may also be passing a fresh model/effort —
|
|
584
608
|
// update bookkeeping BEFORE session/new so the new fields land on the
|
|
585
|
-
// daemon.
|
|
586
|
-
|
|
609
|
+
// daemon. An explicit `model: undefined` clears a prior pooled override and
|
|
610
|
+
// lets the new session select its own default.
|
|
611
|
+
if (Object.prototype.hasOwnProperty.call(options, 'model')) this.model = model;
|
|
587
612
|
if (effort !== undefined) this.effort = effort;
|
|
588
613
|
// agent-worker-pool.js reuses a warm worker across dispatches with
|
|
589
614
|
// different cwds (unlike cc-worker-pool.js, whose tabs never change
|
|
@@ -593,6 +618,7 @@ class Worker {
|
|
|
593
618
|
cwd: this.cwd, mcpServers: mcpServers || [], model: this.model, effort: this.effort,
|
|
594
619
|
}));
|
|
595
620
|
this.sessionId = result && result.sessionId;
|
|
621
|
+
this.currentModel = extractSessionModel(result) || normalizeExecutionModel(this.model);
|
|
596
622
|
this.systemPromptHash = systemPromptHash;
|
|
597
623
|
this.firstSystemPromptSent = false;
|
|
598
624
|
}
|
|
@@ -623,11 +649,13 @@ module.exports = {
|
|
|
623
649
|
ERROR_CODES,
|
|
624
650
|
typedError,
|
|
625
651
|
spawnAcp,
|
|
652
|
+
resolveWorkerRuntime,
|
|
626
653
|
killImmediate,
|
|
627
654
|
hashMcpServers,
|
|
628
655
|
hashProcessContext,
|
|
629
656
|
buildTrustedSessionContext,
|
|
630
657
|
buildSessionNewParams,
|
|
658
|
+
extractSessionModel,
|
|
631
659
|
extractChunkText,
|
|
632
660
|
mapAcpToolCallToToolUse,
|
|
633
661
|
Worker,
|
package/engine/ado-comment.js
CHANGED
|
@@ -106,6 +106,7 @@ async function _defaultAcquireToken() {
|
|
|
106
106
|
* @param {string} args.agentId minions agent id (marker)
|
|
107
107
|
* @param {string} args.kind comment kind (marker)
|
|
108
108
|
* @param {string} [args.workItemId] originating work-item id (marker)
|
|
109
|
+
* @param {string} [args.model] concrete runtime execution model
|
|
109
110
|
* @param {number} [args.timeoutMs=30000]
|
|
110
111
|
* @param {Function} [args.acquireToken] () => Promise<string> — injectable token source
|
|
111
112
|
* @param {Function} [args.fetchImpl=fetch] injectable fetch
|
|
@@ -120,6 +121,7 @@ async function postAdoPrComment({
|
|
|
120
121
|
agentId,
|
|
121
122
|
kind,
|
|
122
123
|
workItemId,
|
|
124
|
+
model,
|
|
123
125
|
resolved = false,
|
|
124
126
|
timeoutMs = 30000,
|
|
125
127
|
acquireToken = _defaultAcquireToken,
|
|
@@ -131,7 +133,7 @@ async function postAdoPrComment({
|
|
|
131
133
|
_validatePrNumber(prNumber);
|
|
132
134
|
|
|
133
135
|
// buildMinionsCommentBody validates marker fields and throws on bad input.
|
|
134
|
-
const finalBody = buildMinionsCommentBody({ agentId, kind, workItemId, body });
|
|
136
|
+
const finalBody = buildMinionsCommentBody({ agentId, kind, workItemId, model, body });
|
|
135
137
|
|
|
136
138
|
const token = await acquireToken();
|
|
137
139
|
if (!token || typeof token !== 'string') {
|