sortie-dogs 0.13.22 → 0.14.1
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/README.md +42 -16
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/goal-bound.d.ts +6 -0
- package/dist/core/initialize.js +5 -3
- package/dist/core/operator-mission.d.ts +10 -1
- package/dist/core/operator-mission.js +7 -1
- package/dist/core/operator-runtime.d.ts +8 -0
- package/dist/core/operator-runtime.js +33 -0
- package/dist/plugin/index.js +74 -17
- package/dist/plugin/mission-review.d.ts +2 -0
- package/dist/plugin/mission-review.js +22 -3
- package/dist/plugin/profiled.js +170 -14
- package/dist/plugin/runtime-bridge.d.ts +12 -0
- package/dist/plugin/tool-schema.js +5 -3
- package/dist/plugin/unit-usage.d.ts +1 -1
- package/dist/plugin/unit-usage.js +5 -3
- package/dist/runtime-mission-assets.d.ts +3 -1
- package/dist/runtime-mission-assets.js +33 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ evidence-backed result, not more agents for their own sake.
|
|
|
19
19
|
[](https://www.npmjs.com/package/sortie-dogs)
|
|
20
20
|
[](LICENSE)
|
|
21
21
|
|
|
22
|
-
**Current release: [v0.
|
|
23
|
-
· [Release notes](docs/release-v0.
|
|
22
|
+
**Current release: [v0.14.1](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.14.1)**
|
|
23
|
+
· [Release notes](docs/release-v0.14.1.md) · [Execution vision](docs/v0.14.0-execution-vision.md)
|
|
24
24
|
|
|
25
25
|
[Quick start](#quick-start) · [Workflow](#mission-workflow) · [Models](#default-models) ·
|
|
26
26
|
[Measured results](#measured-results) · [Configuration](#configuration) · [Documentation](#documentation)
|
|
27
27
|
|
|
28
|
-
> **Beta:** the
|
|
28
|
+
> **Beta:** the package is still stabilizing before 1.0. Both host integrations are
|
|
29
29
|
> implemented; Codex Missions have completed implementation, validation, correction and acceptance.
|
|
30
30
|
> [Host-specific execution boundaries](docs/codex.md#native-permissions-and-approval) remain explicit.
|
|
31
31
|
|
|
@@ -35,9 +35,9 @@ evidence-backed result, not more agents for their own sake.
|
|
|
35
35
|
|
|
36
36
|
- **Autonomy:** retain the original request across delegation, correction, compaction and restart.
|
|
37
37
|
Finish in-request work through the existing host authority instead of routine approval round trips.
|
|
38
|
-
- **Efficiency:**
|
|
39
|
-
Coordinator
|
|
40
|
-
|
|
38
|
+
- **Efficiency:** Operator recommends an initial author from the instruction and known context.
|
|
39
|
+
Coordinator can implement substantial work directly or delegate remaining work as execution makes
|
|
40
|
+
that useful. Continue the same unit with its edits, valid evidence and cumulative budget.
|
|
41
41
|
- **Visibility:** show real commands, exits, timing, model routes, review disposition and final
|
|
42
42
|
acceptance. Unknown execution or cost stays unknown, not an invented success or zero.
|
|
43
43
|
- **Quality:** meaningful formal checks and risk-based review establish completion, not model prose.
|
|
@@ -57,7 +57,7 @@ Use an existing Codex CLI signed in with **ChatGPT**. On Windows, PowerShell **7
|
|
|
57
57
|
available; POSIX uses bash. Sortie does not install Codex, start login or use a metered API fallback.
|
|
58
58
|
|
|
59
59
|
```sh
|
|
60
|
-
npm install --save-dev sortie-dogs@0.
|
|
60
|
+
npm install --save-dev sortie-dogs@0.14.1
|
|
61
61
|
npx --no-install sortie-dogs codex init .
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -86,7 +86,7 @@ bridge. [Codex guide: SDK, permissions, progress, resume and manifest route](doc
|
|
|
86
86
|
Use **OpenCode V2**. In the target project:
|
|
87
87
|
|
|
88
88
|
```sh
|
|
89
|
-
npm install --save-dev sortie-dogs@0.
|
|
89
|
+
npm install --save-dev sortie-dogs@0.14.1
|
|
90
90
|
npx --no-install sortie-dogs init .
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -108,14 +108,16 @@ v0.10 is installed. [OpenCode setup and configuration](docs/configuration.md) ·
|
|
|
108
108
|
|
|
109
109
|
```text
|
|
110
110
|
Known unit: User → Operator → Worker → Validation → Review / Correction → Acceptance
|
|
111
|
-
|
|
111
|
+
Direct: User → Operator → Coordinator → Validation → Review / Correction → Acceptance
|
|
112
|
+
Handoff: Coordinator → Worker (same unit, retained edits/context/checks/budget)
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
1. **Operator** saves original requirements
|
|
115
|
+
1. **Operator** saves original requirements, recommends an initial executor from already-known
|
|
116
|
+
context, and owns user decisions and final acceptance. No routing-only investigation is required.
|
|
115
117
|
2. **Worker** investigates, implements, runs meaningful checks and performs requested Git delivery
|
|
116
118
|
in the same useful unit. A known unit/check can go directly to Worker without a planning handoff.
|
|
117
|
-
3. **Coordinator
|
|
118
|
-
|
|
119
|
+
3. **Coordinator** starts useful work, implements and validates directly when efficient, and
|
|
120
|
+
delegates remaining work when useful. The Operator recommendation is advisory, not an execution lock.
|
|
119
121
|
4. **Review** is initially independent for high-risk changes; low-risk skips are explicit.
|
|
120
122
|
Findings can be corrected and validated by the same Reviewer, followed by recorded author
|
|
121
123
|
self-recheck. That is not a second independent PASS.
|
|
@@ -176,6 +178,30 @@ general speed or success-rate guarantee.
|
|
|
176
178
|
|
|
177
179
|
### SWE-bench evaluation
|
|
178
180
|
|
|
181
|
+
#### v0.14.0 dev23 (2026-10-15 UTC)
|
|
182
|
+
|
|
183
|
+
**Official resolution: 10/23 (43.48%); Mission completion: 22/23.** One pass through the normal
|
|
184
|
+
OpenCode CLI + Sortie Mission path, using the fixed released v0.14.0 package.
|
|
185
|
+
|
|
186
|
+
- All 23 frozen patches were officially scored: **10 resolved, 13 unresolved**, zero empty patches,
|
|
187
|
+
evaluation errors or infrastructure failures. The cost-stopped `sqlfluff__sqlfluff-2419` patch
|
|
188
|
+
resolved officially, but its Mission remains incomplete; official correctness does not imply acceptance.
|
|
189
|
+
- Pinned public Lite dev rows; one inference attempt per task, no retries. Official grading:
|
|
190
|
+
`split=dev`, `max_workers=1`, `retry_count=0`. Inference concurrency was **1** due to low host memory.
|
|
191
|
+
- Actual Operator/Coordinator/Reviewer model: `openai/gpt-6.1-sol#xhigh`;
|
|
192
|
+
Worker: `openai/gpt-6-luna-fast#max`. Ten cases used Workers; 13 used Coordinator direct execution.
|
|
193
|
+
- Inference wall time **2h53m28s**; separate official grading **7m19s**.
|
|
194
|
+
- Known estimated inference cost **$16.76**, with one pending usage record. Separate holds:
|
|
195
|
+
**$2** for unresolved usage and **$5** for implementation/supervision; these are not known expenses.
|
|
196
|
+
Costs are API-equivalent estimates, not invoices; the $2/task monitor is not an exact billing cap.
|
|
197
|
+
- Release/runner commit `e69a3b2317a76eeea1229034f52902812ff1d580`;
|
|
198
|
+
package SHA-256 `120922c31dd6d85040210d68456192b4e6aa45dc87a11451476f3ed576a68e30`.
|
|
199
|
+
|
|
200
|
+
Local evidence (generated artifacts are not committed):
|
|
201
|
+
`_testenv/autodev/sortie-dogs-dev23/benchmark-v0140-20261015/one-pass-summary.json`.
|
|
202
|
+
|
|
203
|
+
#### Historical v0.12.24 test300
|
|
204
|
+
|
|
179
205
|
**SWE-bench Lite test300: 170/300 (56.67%)**, fixed **v0.12.24**, one pass@1 campaign.
|
|
180
206
|
Nine empty patches, zero official evaluation errors, frozen predictions and trajectories for all
|
|
181
207
|
300 instances. Workers ran Luna Fast/max; management/review ran GPT-6 Sol/xhigh.
|
|
@@ -193,8 +219,8 @@ known expense. Official local evaluation and leaderboard acceptance are separate
|
|
|
193
219
|
**8/23 (34.8%)**, versus v0.12.25's 7/23; all seven prior resolutions retained, no empty patches or
|
|
194
220
|
official evaluation errors. [Fixed conditions, stopped-run accounting and costs](docs/benchmarks/swebench-lite-history.md#v0131-dev23-2026-09-30).
|
|
195
221
|
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
Each score belongs to its fixed candidate. SWE-bench remains an optional measurement, not a release
|
|
223
|
+
gate. Different splits, budgets, models and conditions are not controlled comparisons or proof of a fix's effect.
|
|
198
224
|
|
|
199
225
|
## Configuration
|
|
200
226
|
|
|
@@ -216,7 +242,7 @@ Install the desired package version and rerun **your host's** initializer:
|
|
|
216
242
|
|
|
217
243
|
An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
|
|
218
244
|
update it. [Global/update instructions](docs/configuration.md#global-availability).
|
|
219
|
-
The current Mission asset marker is `0.
|
|
245
|
+
The current Mission asset marker is `0.14.1-review-continuation-v1`; the unchanged Codex skill marker is
|
|
220
246
|
`0.13.11-codex-skill-v2`. Installed markers alone do not prove a running host reloaded the new version.
|
|
221
247
|
|
|
222
248
|
There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
|
|
@@ -232,7 +258,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
|
|
|
232
258
|
[SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
|
|
233
259
|
- **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
|
|
234
260
|
[Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
|
|
235
|
-
- **Releases:** [v0.13.21](docs/release-v0.13.21.md) · [v0.13.20](docs/release-v0.13.20.md) · [v0.13.19](docs/release-v0.13.19.md) · [v0.13.18](docs/release-v0.13.18.md) · [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
|
|
261
|
+
- **Releases:** [v0.14.1](docs/release-v0.14.1.md) · [v0.14.0](docs/release-v0.14.0.md) · [v0.13.22](docs/release-v0.13.22.md) · [v0.13.21](docs/release-v0.13.21.md) · [v0.13.20](docs/release-v0.13.20.md) · [v0.13.19](docs/release-v0.13.19.md) · [v0.13.18](docs/release-v0.13.18.md) · [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
|
|
236
262
|
[v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
|
|
237
263
|
|
|
238
264
|
## Community
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.14.1-review-continuation-v1";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.14.1-review-continuation-v1";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
@@ -204,6 +204,12 @@ export type GoalFlightEvent = (GoalEventBase & {
|
|
|
204
204
|
readonly cost_usd: number | null;
|
|
205
205
|
readonly native_session_id?: string;
|
|
206
206
|
readonly native_started_at?: string;
|
|
207
|
+
/** Multiple actual authors of one handed-off unit; missing usage stays unknown. */
|
|
208
|
+
readonly native_usage_segments?: readonly {
|
|
209
|
+
readonly sessionID: string;
|
|
210
|
+
readonly startedAt: string;
|
|
211
|
+
readonly endedAt?: string;
|
|
212
|
+
}[];
|
|
207
213
|
/** Host-native validation identity retained in this same goal ledger; never model-authored evidence. */
|
|
208
214
|
readonly native_validation_observations?: readonly {
|
|
209
215
|
readonly thread_id: string;
|
package/dist/core/initialize.js
CHANGED
|
@@ -136,10 +136,12 @@ function classifyVersionTransition(installedValue, currentValue, v010Restore = f
|
|
|
136
136
|
const adjacentPreOneLine = installed.major === 0 && current.major === 0 &&
|
|
137
137
|
current.minor === installed.minor + 1;
|
|
138
138
|
// v0.11 used a different runtime. The v010 asset migration restores the 0.10
|
|
139
|
-
// preview into the 0.12
|
|
139
|
+
// preview into the 0.12–0.14 Mission line without selecting a v0.11 execution path.
|
|
140
140
|
const restoredPreview = v010Restore && installed.major === 0 && installed.minor === 10 &&
|
|
141
|
-
current.major === 0 &&
|
|
142
|
-
|
|
141
|
+
current.major === 0 && [12, 13, 14].includes(current.minor);
|
|
142
|
+
const retainedMission = v010Restore && installed.major === 0 && installed.minor === 12 &&
|
|
143
|
+
current.major === 0 && current.minor === 14;
|
|
144
|
+
return sameLine || adjacentPreOneLine || restoredPreview || retainedMission ? "compatible-update" : "incompatible";
|
|
143
145
|
}
|
|
144
146
|
async function metadata(path) {
|
|
145
147
|
try {
|
|
@@ -62,7 +62,7 @@ export interface MissionAttempt {
|
|
|
62
62
|
/** Fingerprint of the settled scoped candidate against which a later Rescue is proposed. */
|
|
63
63
|
candidateID?: string;
|
|
64
64
|
kind: "implementation" | "normal_remediation" | "astra_rescue" | "reviewer_correction" | "direct_execution";
|
|
65
|
-
status: "pending" | "dispatched" | "succeeded" | "failed" | "cancelled" | "unconfirmed";
|
|
65
|
+
status: "pending" | "dispatched" | "succeeded" | "failed" | "cancelled" | "unconfirmed" | "handed-off";
|
|
66
66
|
callID?: string;
|
|
67
67
|
childSessionID?: string;
|
|
68
68
|
dispatchFingerprint?: string;
|
|
@@ -217,6 +217,10 @@ export interface OperatorMission {
|
|
|
217
217
|
context?: MissionContext[];
|
|
218
218
|
/** Shared public context/working notes; observations, never extra requirements or acceptance. */
|
|
219
219
|
continuity?: MissionContinuity;
|
|
220
|
+
/** Operator recommendation from already-known context; not a requirement or execution lock. */
|
|
221
|
+
preferredExecutor?: "coordinator" | "worker";
|
|
222
|
+
/** Known unit facts passed to the Coordinator without pre-starting a Worker. */
|
|
223
|
+
suggestedUnit?: Record<string, unknown>;
|
|
220
224
|
/** Explicit continue deliveries, keyed by the original real turn. */
|
|
221
225
|
steering?: {
|
|
222
226
|
requestID: string;
|
|
@@ -315,6 +319,11 @@ export interface OperatorMission {
|
|
|
315
319
|
admittedAt: number;
|
|
316
320
|
reviewIdentity: string;
|
|
317
321
|
};
|
|
322
|
+
/** Exact terminal of an unused inline grant; no implementation calls followed admission. */
|
|
323
|
+
noWriteReceipt?: {
|
|
324
|
+
promptID: string;
|
|
325
|
+
messageID: string;
|
|
326
|
+
};
|
|
318
327
|
status: "prepared" | "running" | "ready" | "failed" | "cancelled";
|
|
319
328
|
selfRecheck?: MissionSelfRecheck;
|
|
320
329
|
}[];
|
|
@@ -328,6 +328,7 @@ export class OperatorMissionRuntime {
|
|
|
328
328
|
}
|
|
329
329
|
}));
|
|
330
330
|
return { original_requests: state.requests, requirements: state.requirements, prohibited_write: state.prohibitedWrite ?? [],
|
|
331
|
+
...(state.preferredExecutor ? { preferred_executor: state.preferredExecutor } : {}),
|
|
331
332
|
launch_conditions: state.launchConditions ?? [],
|
|
332
333
|
conversation: sources.some(source => source.session_id === state.root && source.entries > 0 && !("snapshot_available" in source)) ? [] : state.context ?? [],
|
|
333
334
|
continuity: { notes: state.continuity?.notes, sources, progress: state.progress,
|
|
@@ -709,6 +710,9 @@ export class OperatorMissionRuntime {
|
|
|
709
710
|
return [`mission_id: ${state.id}`, `project_root: ${this.projectRoot}`, "Use the user's language below for all replies and Task titles.",
|
|
710
711
|
"Own investigation, implementation, formal validation, corrections and Worker/Scout/Advisor/independent Reviewer dispatch.",
|
|
711
712
|
"Use plan_units with executor=self for direct work in this session, or delegate promptly when useful. finish_direct_unit records native checks without a Worker handoff. No proposal/approval phase; root alone accepts completion.",
|
|
713
|
+
"Start useful work from the supplied context. Reconsider the recommendation through normal execution, not a separate routing investigation. You may implement substantial work here or delegate remaining work without asking Operator again; explicit user choices still govern.",
|
|
714
|
+
...(state.preferredExecutor ? [`Operator recommendation (advisory): ${state.preferredExecutor}. This is not an immutable requirement or a model override.`] : []),
|
|
715
|
+
...(state.suggestedUnit ? [`Known unit facts (reuse or adapt during actual work): ${JSON.stringify(state.suggestedUnit)}`] : []),
|
|
712
716
|
"Escalate only a completion candidate, a user-only decision, or an extension of original requirements/budget. Unit progress is published without stopping you.",
|
|
713
717
|
"Requirements:", ...state.requirements.map(item => `${item.id}: ${item.text}`),
|
|
714
718
|
`Confirmed launch conditions (fixed limits, not consumption or remaining budget): ${JSON.stringify(state.launchConditions ?? [])}`,
|
|
@@ -887,7 +891,8 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
|
|
|
887
891
|
}
|
|
888
892
|
export function missionReviewIndependent(mission, child) {
|
|
889
893
|
return child !== undefined && !mission.sourceAuthors?.includes(child) &&
|
|
890
|
-
!(mission.corrections ?? []).some(correction => correction.author === child
|
|
894
|
+
!(mission.corrections ?? []).some(correction => correction.author === child &&
|
|
895
|
+
!(correction.status === "failed" && correction.noWriteReceipt));
|
|
891
896
|
}
|
|
892
897
|
export function missionPacket(mission, run, includeWorkQuantity = true) {
|
|
893
898
|
const predecessor = run && mission.runID !== run.runID && (mission.supersededRunID === run.runID || run.phase === "cancelled") ? run : undefined;
|
|
@@ -932,6 +937,7 @@ export function missionPacket(mission, run, includeWorkQuantity = true) {
|
|
|
932
937
|
normal_remediation_used: unit.normalRemediationUsed ?? false,
|
|
933
938
|
...(unit.failure ? { failure: { outcome: unit.failure.outcome, exit_code: unit.failure.exitCode } } : {}),
|
|
934
939
|
...(unit.dispatchDenial ? { dispatch_denial: unit.dispatchDenial } : {}) })) } : {}),
|
|
940
|
+
...(mission.preferredExecutor ? { preferred_executor: mission.preferredExecutor } : {}),
|
|
935
941
|
next_action: mission.phase === "completed" ? "Mission completed. Report the accepted result and retained review gaps; no further dispatch or completion call is needed."
|
|
936
942
|
: mission.phase === "submitted" && mission.submission?.status === "ready"
|
|
937
943
|
? "Operator: use acceptance_summary to compare the submitted candidate with the verbatim original requests and actual evidence, inspect concrete gaps only, then complete_mission if satisfied. Do not routinely search archives or reread all source. Historical PASS is not current PASS. Report remaining evidence gaps; they are not a review PASS."
|
|
@@ -141,6 +141,12 @@ interface UnitState {
|
|
|
141
141
|
validationCwd?: Readonly<Record<string, string>>;
|
|
142
142
|
validationRegisteredAt?: Readonly<Record<string, string>>;
|
|
143
143
|
};
|
|
144
|
+
/** Actual direct author segments retained when the same unit is handed to a Worker. */
|
|
145
|
+
directHandoffs?: (NonNullable<UnitState["directExecution"]> & {
|
|
146
|
+
callID: string;
|
|
147
|
+
handedOffAt: string;
|
|
148
|
+
settledCallID?: string;
|
|
149
|
+
})[];
|
|
144
150
|
resultClass: string | null;
|
|
145
151
|
failure?: SerialDispatchSettlement["failure"];
|
|
146
152
|
normalRemediationUsed?: boolean;
|
|
@@ -372,6 +378,8 @@ export declare class OperatorRuntime {
|
|
|
372
378
|
private nextOnce;
|
|
373
379
|
admitWorker(root: string, actor: string, callID: string, args: unknown): Promise<OperatorTask>;
|
|
374
380
|
admitDirect(root: string, actor: string, callID: string): Promise<OperatorState>;
|
|
381
|
+
/** Yield an unfinished ordinary direct unit; keep its contract, reservation and source history. */
|
|
382
|
+
handoffDirect(root: string, actor: string, context: Record<string, unknown>): Promise<OperatorState>;
|
|
375
383
|
/** Continue an admitted independent Review in-place; no second native Task or prompt. */
|
|
376
384
|
admitReviewerDirect(root: string, author: string, callID: string, promptID: string): Promise<OperatorState>;
|
|
377
385
|
private admitWorkerOnce;
|
|
@@ -1454,6 +1454,37 @@ export class OperatorRuntime {
|
|
|
1454
1454
|
return state;
|
|
1455
1455
|
});
|
|
1456
1456
|
}
|
|
1457
|
+
/** Yield an unfinished ordinary direct unit; keep its contract, reservation and source history. */
|
|
1458
|
+
handoffDirect(root, actor, context) {
|
|
1459
|
+
return this.serial(root, async () => {
|
|
1460
|
+
const state = await this.required(root);
|
|
1461
|
+
const unit = state.units.find(item => item.status !== "succeeded");
|
|
1462
|
+
if (actor !== (state.operatorSessionID ?? root) || state.phase !== "running" || !unit ||
|
|
1463
|
+
unit.status !== "running" || unit.directExecution?.actor !== actor || !unit.callID ||
|
|
1464
|
+
unit.directExecution.finishedAt || unit.reviewerCorrection || unit.repairValidation || unit.terminalRescue) {
|
|
1465
|
+
throw new Error("mission-direct-handoff-unavailable");
|
|
1466
|
+
}
|
|
1467
|
+
await this.verifyControls(unit);
|
|
1468
|
+
const segment = { ...structuredClone(unit.directExecution), callID: unit.callID, handedOffAt: new Date().toISOString() };
|
|
1469
|
+
// Keep the original controls immutable. Publish continuation context and the new
|
|
1470
|
+
// Task reference together in the atomic run save, so a crash has no half-updated pins.
|
|
1471
|
+
const continuation = { actor, unit_id: unit.unit.id, at: segment.handedOffAt,
|
|
1472
|
+
instruction: "Continue existing changes and remaining action. The authoritative handoff retains the exact original requirements. Reuse current source-bound checks; inspect missing facts only. Handoff itself is not validation.",
|
|
1473
|
+
continuity: context.continuity,
|
|
1474
|
+
checks: segment.checks.map(({ callID, command, directory, exitCode, startedAt, endedAt }) => ({ callID, command, directory, exitCode, startedAt, endedAt })),
|
|
1475
|
+
validation_cwd: segment.validationCwd ?? unit.unit.validation_cwd, details_ref: this.statePath(root) };
|
|
1476
|
+
unit.task = { ...unit.task, prompt: `${unit.task.prompt.replace(/\ndirect_handoff: .+$/mu, "")}\ndirect_handoff: ${JSON.stringify(continuation)}` };
|
|
1477
|
+
(unit.directHandoffs ??= []).push(segment);
|
|
1478
|
+
delete unit.directExecution;
|
|
1479
|
+
unit.status = "pending";
|
|
1480
|
+
unit.childSessionID = null;
|
|
1481
|
+
state.units[state.units.indexOf(unit)] = { ...unit,
|
|
1482
|
+
unit: segment.validationCwd ? { ...unit.unit, validation_cwd: segment.validationCwd } : unit.unit };
|
|
1483
|
+
// Keep the original call as the reservation anchor until Worker admission replaces it.
|
|
1484
|
+
await this.save(state);
|
|
1485
|
+
return state;
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1457
1488
|
/** Continue an admitted independent Review in-place; no second native Task or prompt. */
|
|
1458
1489
|
admitReviewerDirect(root, author, callID, promptID) {
|
|
1459
1490
|
return this.serial(root, async () => {
|
|
@@ -1848,6 +1879,8 @@ export class OperatorRuntime {
|
|
|
1848
1879
|
else
|
|
1849
1880
|
delete unit.failure;
|
|
1850
1881
|
unit.status = result.disposition;
|
|
1882
|
+
for (const segment of unit.directHandoffs ?? [])
|
|
1883
|
+
segment.settledCallID ??= result.callID;
|
|
1851
1884
|
if (unit.directExecution)
|
|
1852
1885
|
unit.directExecution.finishedAt = new Date().toISOString();
|
|
1853
1886
|
if (unit.terminalRescue) {
|
package/dist/plugin/index.js
CHANGED
|
@@ -1553,17 +1553,26 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1553
1553
|
});
|
|
1554
1554
|
}
|
|
1555
1555
|
}
|
|
1556
|
-
async function nativeUnitCost(child, startedAt) {
|
|
1556
|
+
async function nativeUnitCost(child, startedAt, endedAt) {
|
|
1557
1557
|
if (!child || !input.client?.session?.messages)
|
|
1558
1558
|
return null;
|
|
1559
1559
|
try {
|
|
1560
1560
|
const response = await input.client.session.messages({ path: { id: child }, query: { directory: input.directory } });
|
|
1561
|
-
return isRecord(response) && Array.isArray(response.data) ? settledUnitUsage(response.data, startedAt) : null;
|
|
1561
|
+
return isRecord(response) && Array.isArray(response.data) ? settledUnitUsage(response.data, startedAt, endedAt) : null;
|
|
1562
1562
|
}
|
|
1563
1563
|
catch {
|
|
1564
1564
|
return null;
|
|
1565
1565
|
}
|
|
1566
1566
|
}
|
|
1567
|
+
async function unitUsageSegments(root, taskID, callID, child) {
|
|
1568
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, taskID, callID);
|
|
1569
|
+
return origin ? [...origin.segments, ...(child ? [{ sessionID: child,
|
|
1570
|
+
startedAt: origin.segments.at(-1).endedAt }] : [])] : undefined;
|
|
1571
|
+
}
|
|
1572
|
+
async function priceUsageSegments(segments) {
|
|
1573
|
+
const costs = await Promise.all(segments.map(segment => nativeUnitCost(segment.sessionID, segment.startedAt, segment.endedAt)));
|
|
1574
|
+
return costs.length && costs.every((cost) => cost !== null) ? costs.reduce((sum, cost) => sum + cost, 0) : null;
|
|
1575
|
+
}
|
|
1567
1576
|
async function reconcileUnitUsage(root) {
|
|
1568
1577
|
const ledger = await goalLedger(root), snapshot = await ledger.readGoal();
|
|
1569
1578
|
const reconciled = new Set(snapshot.records.flatMap(({ event }) => event.kind === "unit.usage-reconciled" ? [event.reservation_id] : []));
|
|
@@ -1571,7 +1580,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1571
1580
|
if (event.kind !== "unit.settled" || event.goal_id !== snapshot.state.goal_id || event.cost_usd !== null ||
|
|
1572
1581
|
!event.native_session_id || reconciled.has(event.reservation_id))
|
|
1573
1582
|
continue;
|
|
1574
|
-
const cost = await
|
|
1583
|
+
const cost = event.native_usage_segments ? await priceUsageSegments(event.native_usage_segments)
|
|
1584
|
+
: await nativeUnitCost(event.native_session_id, event.native_started_at);
|
|
1575
1585
|
if (cost === null)
|
|
1576
1586
|
continue;
|
|
1577
1587
|
await ledger.appendGoal({ kind: "unit.usage-reconciled", at: new Date().toISOString(), goal_id: event.goal_id,
|
|
@@ -1594,8 +1604,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1594
1604
|
const pending = state.outstanding_reservations;
|
|
1595
1605
|
for (const reservation of pending) {
|
|
1596
1606
|
const dispatch = await input.runtimeBridge?.recoverMissionDispatch?.(root, reservation.unit_id);
|
|
1607
|
+
const origin = dispatch && await input.runtimeBridge?.missionDispatchOrigin?.(root, reservation.unit_id, dispatch.callID);
|
|
1597
1608
|
if (!dispatch || !state.goal_id || goalFingerprint({ goal_id: state.goal_id, unit_id: reservation.unit_id,
|
|
1598
|
-
call_id: dispatch.callID }) !== reservation.reservation_id)
|
|
1609
|
+
call_id: origin?.callID ?? dispatch.callID }) !== reservation.reservation_id)
|
|
1599
1610
|
continue;
|
|
1600
1611
|
if (!dispatch.cancelled && dispatch.nativeOutcome === "completed" && goalReservations.get(dispatch.callID)?.reservationID === reservation.reservation_id) {
|
|
1601
1612
|
// Reuse host-observed command exits/bindings when still present. A terminal outcome alone
|
|
@@ -1616,13 +1627,16 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1616
1627
|
}
|
|
1617
1628
|
const startedAt = (await ledger.readGoal()).records.find(({ event }) => event.kind === "dispatch.reserved" &&
|
|
1618
1629
|
event.reservation_id === reservation.reservation_id)?.event.at;
|
|
1630
|
+
const usageSegments = await unitUsageSegments(root, reservation.unit_id, dispatch.callID, dispatch.childSessionID);
|
|
1619
1631
|
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
1620
1632
|
reservation_id: reservation.reservation_id,
|
|
1621
1633
|
receipt_id: goalFingerprint({ recovered_host_task: dispatch.callID, reservation: reservation.reservation_id }),
|
|
1622
1634
|
goal_id: state.goal_id, unit_id: reservation.unit_id,
|
|
1623
1635
|
disposition: dispatch.cancelled ? "cancelled" : "failed", result_class: dispatch.cancelled ? "interrupted" : "process-defect",
|
|
1624
1636
|
progress_fingerprint: null, evidence: [], elapsed_ms: null,
|
|
1625
|
-
cost_usd: await nativeUnitCost(dispatch.childSessionID, startedAt),
|
|
1637
|
+
cost_usd: usageSegments ? await priceUsageSegments(usageSegments) : await nativeUnitCost(dispatch.childSessionID, startedAt),
|
|
1638
|
+
...(usageSegments ? { native_usage_segments: usageSegments,
|
|
1639
|
+
native_session_id: dispatch.childSessionID ?? usageSegments.at(-1)?.sessionID } : {}),
|
|
1626
1640
|
...(dispatch.childSessionID ? { native_session_id: dispatch.childSessionID, native_started_at: startedAt } : {}) });
|
|
1627
1641
|
// A missed after hook closes execution, but cannot turn unobserved validation into acceptance.
|
|
1628
1642
|
await input.runtimeBridge?.onSerialSettlement?.({ rootSessionID: root, callID: dispatch.callID,
|
|
@@ -2317,13 +2331,28 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2317
2331
|
if (state.phase === "terminal" || state.receipt !== null) {
|
|
2318
2332
|
throw new Error("SORTIE_GOAL_CONTROL_DENIED: terminal");
|
|
2319
2333
|
}
|
|
2334
|
+
const inheritedTaskID = handoffValue(handoffEntries(prompt), ["task_id"]);
|
|
2335
|
+
const origin = inheritedTaskID && await input.runtimeBridge?.missionDispatchOrigin?.(goalRoot(sessionID), inheritedTaskID, callID);
|
|
2336
|
+
const retainHandoffReservation = () => {
|
|
2337
|
+
if (!origin)
|
|
2338
|
+
return;
|
|
2339
|
+
const reservationID = goalFingerprint({ goal_id: state.goal_id, unit_id: inheritedTaskID, call_id: origin.callID });
|
|
2340
|
+
const retained = state.outstanding_reservations.find(item => item.reservation_id === reservationID && item.unit_id === inheritedTaskID);
|
|
2341
|
+
if (!retained)
|
|
2342
|
+
throw new Error("mission-handoff-reservation-unavailable");
|
|
2343
|
+
for (const [previousCall, reservation] of goalReservations) {
|
|
2344
|
+
if (reservation.reservationID === reservationID)
|
|
2345
|
+
goalReservations.delete(previousCall);
|
|
2346
|
+
}
|
|
2347
|
+
goalReservations.set(callID, { root: goalRoot(sessionID), reservationID, unitID: inheritedTaskID, started: Date.parse(origin.startedAt) });
|
|
2348
|
+
};
|
|
2320
2349
|
if (state.replan_required && !state.replan_used) {
|
|
2321
2350
|
state = await ledger.appendGoal({ kind: "goal.replanned", at: new Date().toISOString(),
|
|
2322
2351
|
goal_id: state.goal_id, revision: state.revision, reason: "no-progress" });
|
|
2323
2352
|
}
|
|
2324
2353
|
// No-progress is diagnostic, not another implicit execution budget. Useful
|
|
2325
2354
|
// repair can continue within the same explicit unit/time/cost limits below.
|
|
2326
|
-
if (state.budget !== null && state.consumed_units + state.outstanding_reservations.length >= state.budget.max_units) {
|
|
2355
|
+
if (!origin && state.budget !== null && state.consumed_units + state.outstanding_reservations.length >= state.budget.max_units) {
|
|
2327
2356
|
await terminalGoal(sessionID, "stop_budget", "stopped");
|
|
2328
2357
|
throw budgetDenied("units");
|
|
2329
2358
|
}
|
|
@@ -2337,6 +2366,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2337
2366
|
await terminalGoal(sessionID, "stop_budget", "stopped");
|
|
2338
2367
|
throw budgetDenied("cost_usd");
|
|
2339
2368
|
}
|
|
2369
|
+
if (origin) {
|
|
2370
|
+
retainHandoffReservation();
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2340
2373
|
if (state.goal_id === null)
|
|
2341
2374
|
return;
|
|
2342
2375
|
const unitID = handoffValue(handoffEntries(prompt), ["task_id"]) ?? callID;
|
|
@@ -2400,7 +2433,16 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2400
2433
|
...execution, immutableRef: execution.immutableRef, endedAt: execution.endedAt, fresh: execution.fresh,
|
|
2401
2434
|
exitCode: execution.exitCode, outcome: execution.outcome
|
|
2402
2435
|
}, state, reservation.unitID));
|
|
2403
|
-
const
|
|
2436
|
+
const handedChecks = await input.runtimeBridge?.missionHandoffChecks?.(reservation.root, reservation.unitID, callID) ?? [];
|
|
2437
|
+
const inherited = handedChecks.flatMap(check => {
|
|
2438
|
+
const laterFailure = [...hostGoalExecutions.values()].some(execution => execution.root === reservation.root &&
|
|
2439
|
+
execution.sessionID === childSessionID && execution.endedAt && execution.outcome !== "pass" && execution.outcome !== "skip" &&
|
|
2440
|
+
Date.parse(execution.startedAt) >= Date.parse(check.startedAt));
|
|
2441
|
+
return laterFailure ? [] : observedHostGoalEvidence({ ...check, owner: "coordinator", outcome: "pass",
|
|
2442
|
+
immutableRef: goalFingerprint({ direct_check: check }),
|
|
2443
|
+
correction: { taskID: reservation.unitID, dispatchCallID: check.dispatchCallID, commands: check.command } }, state, reservation.unitID);
|
|
2444
|
+
});
|
|
2445
|
+
const hostEvidence = [...new Map((directEvidence ?? [...observedEvidence, ...inherited]).map(entry => [entry.evidence_id, entry])).values()];
|
|
2404
2446
|
// A worker may return after a user scope epoch changed. Settle its spend/reservation, but only
|
|
2405
2447
|
// adopt observations still bound to the current accepted source/candidate/criterion contract.
|
|
2406
2448
|
const acceptedEvidence = hostEvidence.filter((entry) => validGoalEvidence(entry, state) &&
|
|
@@ -2432,13 +2474,16 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2432
2474
|
const processDefect = !correctionChecks?.failure && failedAcceptanceExecution === undefined && (childSessionID === undefined || hostBindingDefect ||
|
|
2433
2475
|
goalValidationDefects.has(childSessionID) || !validated);
|
|
2434
2476
|
const resultClass = validated ? "acceptance" : interrupted ? "interrupted" : processDefect ? "process-defect" : "acceptance";
|
|
2477
|
+
const usageSegments = await unitUsageSegments(reservation.root, reservation.unitID, callID, childSessionID);
|
|
2435
2478
|
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
2436
2479
|
reservation_id: reservation.reservationID, receipt_id: goalFingerprint({ call_id: callID, output: outputText.slice(0, 2048) }),
|
|
2437
2480
|
goal_id: state.goal_id, unit_id: reservation.unitID,
|
|
2438
2481
|
disposition: validated ? "succeeded" : interrupted ? "cancelled" : "failed", result_class: resultClass,
|
|
2439
2482
|
progress_fingerprint: progress && validated ? goalFingerprint(settlementEvidence) : null,
|
|
2440
2483
|
evidence: settlementEvidence, elapsed_ms: Math.max(0, Date.now() - reservation.started),
|
|
2441
|
-
cost_usd: await nativeUnitCost(childSessionID, new Date(reservation.started).toISOString()),
|
|
2484
|
+
cost_usd: usageSegments ? await priceUsageSegments(usageSegments) : await nativeUnitCost(childSessionID, new Date(reservation.started).toISOString()),
|
|
2485
|
+
...(usageSegments ? { native_usage_segments: usageSegments,
|
|
2486
|
+
native_session_id: childSessionID ?? usageSegments.at(-1)?.sessionID } : {}),
|
|
2442
2487
|
...(childSessionID ? { native_session_id: childSessionID, native_started_at: new Date(reservation.started).toISOString() } : {}) });
|
|
2443
2488
|
const result = {
|
|
2444
2489
|
rootSessionID: reservation.root, callID, unitID: reservation.unitID,
|
|
@@ -2484,9 +2529,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2484
2529
|
}
|
|
2485
2530
|
if (request.direct) {
|
|
2486
2531
|
const direct = request.direct;
|
|
2532
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, request.unitID, direct.callID);
|
|
2487
2533
|
const defect = [...snapshot.records].reverse().map(record => record.event).find(event => event.kind === "unit.settled" &&
|
|
2488
2534
|
event.unit_id === request.unitID && event.evidence.length === 0 && event.native_session_id === request.childSessionID &&
|
|
2489
|
-
event.reservation_id === goalFingerprint({ goal_id: goal.goal_id, unit_id: request.unitID, call_id: direct.callID }) &&
|
|
2535
|
+
event.reservation_id === goalFingerprint({ goal_id: goal.goal_id, unit_id: request.unitID, call_id: origin?.callID ?? direct.callID }) &&
|
|
2490
2536
|
event.receipt_id === goalFingerprint({ call_id: direct.callID,
|
|
2491
2537
|
output: "Host-observed direct unit finished; controller session remains active." }));
|
|
2492
2538
|
if (defect?.kind !== "unit.settled" || Math.abs(Date.parse(defect.at) - Date.parse(direct.finishedAt)) > 1000) {
|
|
@@ -8287,7 +8333,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8287
8333
|
cost_status: state.consumed_cost_usd === null ? "unknown-usage" : reserved > 0 ? "in-flight-not-final" : "settled",
|
|
8288
8334
|
cost_source: "native-usage-price-table",
|
|
8289
8335
|
cost_scope: "worker-only", campaign_remaining_usd: null,
|
|
8290
|
-
cost_note: "Implementation token-price estimates from complete native usage; scoped Reviewer corrections use this same Worker-unit ledger. Missing requests remain unknown and are reconciled on later status. Excludes orchestration/read-only review and external campaign spend. Zero settled cost does not mean free execution." };
|
|
8336
|
+
cost_note: "Implementation token-price estimates from complete native usage; direct-author segments and scoped Reviewer corrections use this same Worker-unit ledger. Missing requests remain unknown and are reconciled on later status. Excludes orchestration/read-only review and external campaign spend. Zero settled cost does not mean free execution." };
|
|
8291
8337
|
return snapshot;
|
|
8292
8338
|
},
|
|
8293
8339
|
extendMissionUnitBudget: (root, maxUnits) => serializeChatTransition(root, async () => {
|
|
@@ -8353,12 +8399,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8353
8399
|
activateSession(actor);
|
|
8354
8400
|
if (!goalReservations.has(direct.callID)) {
|
|
8355
8401
|
const snapshot = await (await goalLedger(root)).readGoal();
|
|
8356
|
-
const
|
|
8357
|
-
if (
|
|
8358
|
-
goalReservations.set(direct.callID, { root, reservationID, unitID: direct.taskID, started: Date.parse(direct.startedAt) });
|
|
8359
|
-
}
|
|
8360
|
-
else
|
|
8402
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, direct.taskID, direct.callID);
|
|
8403
|
+
if (origin)
|
|
8361
8404
|
await reserveGoalDispatch(root, direct.callID, prompt);
|
|
8405
|
+
else {
|
|
8406
|
+
const reservationID = goalFingerprint({ goal_id: snapshot.state.goal_id, unit_id: direct.taskID, call_id: direct.callID });
|
|
8407
|
+
if (snapshot.state.outstanding_reservations.some(item => item.reservation_id === reservationID)) {
|
|
8408
|
+
goalReservations.set(direct.callID, { root, reservationID, unitID: direct.taskID, started: Date.parse(direct.startedAt) });
|
|
8409
|
+
}
|
|
8410
|
+
else
|
|
8411
|
+
await reserveGoalDispatch(root, direct.callID, prompt);
|
|
8412
|
+
}
|
|
8362
8413
|
}
|
|
8363
8414
|
const binding = await input.runtimeBridge?.missionReadBinding?.(root, actor, handoffPath);
|
|
8364
8415
|
if (!binding)
|
|
@@ -8395,6 +8446,10 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8395
8446
|
await releaseWriteGate(actor);
|
|
8396
8447
|
releaseSessionEnforcement(actor);
|
|
8397
8448
|
},
|
|
8449
|
+
assertDirectUnitQuiescent: async (actor) => {
|
|
8450
|
+
if (activeSessions.get(actor)?.inFlightCalls.size)
|
|
8451
|
+
throw new Error("mission-direct-tools-running");
|
|
8452
|
+
},
|
|
8398
8453
|
failDirectUnit: async (root, actor) => {
|
|
8399
8454
|
const direct = await input.runtimeBridge?.directMissionExecution?.(actor);
|
|
8400
8455
|
if (!direct || direct.root !== root)
|
|
@@ -8404,7 +8459,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8404
8459
|
// Cold terminal recovery can happen before the actor's next tool/context hook.
|
|
8405
8460
|
if (!goalReservations.has(direct.callID)) {
|
|
8406
8461
|
const snapshot = await (await goalLedger(root)).readGoal();
|
|
8407
|
-
const
|
|
8462
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, direct.taskID, direct.callID);
|
|
8463
|
+
const reservationID = goalFingerprint({ goal_id: snapshot.state.goal_id, unit_id: direct.taskID, call_id: origin?.callID ?? direct.callID });
|
|
8408
8464
|
if (snapshot.state.outstanding_reservations.some(item => item.reservation_id === reservationID) ||
|
|
8409
8465
|
snapshot.records.some(({ event }) => event.kind === "unit.settled" && event.reservation_id === reservationID)) {
|
|
8410
8466
|
goalReservations.set(direct.callID, { root, reservationID, unitID: direct.taskID, started: Date.parse(direct.startedAt) });
|
|
@@ -8449,7 +8505,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
8449
8505
|
missionWorkerTerminal: async (root, terminal, writeScopes) => {
|
|
8450
8506
|
const childSessionID = terminal.childSessionID;
|
|
8451
8507
|
const snapshot = await (await goalLedger(root)).readGoal();
|
|
8452
|
-
const
|
|
8508
|
+
const origin = await input.runtimeBridge?.missionDispatchOrigin?.(root, terminal.taskID, terminal.callID);
|
|
8509
|
+
const reservationID = goalFingerprint({ goal_id: snapshot.state.goal_id, unit_id: terminal.taskID, call_id: origin?.callID ?? terminal.callID });
|
|
8453
8510
|
if (snapshot.state.outstanding_reservations.some(item => item.reservation_id === reservationID) ||
|
|
8454
8511
|
!snapshot.records.some(({ event }) => event.kind === "unit.settled" && event.goal_id === snapshot.state.goal_id &&
|
|
8455
8512
|
event.unit_id === terminal.taskID && event.reservation_id === reservationID)) {
|
|
@@ -115,6 +115,8 @@ export declare function reviewerCorrectionValidationFresh(validation: readonly s
|
|
|
115
115
|
}[];
|
|
116
116
|
nextOccurrence?: number;
|
|
117
117
|
}>;
|
|
118
|
+
/** Preserve a current ordered prefix while the next author executes the remaining recipe. */
|
|
119
|
+
export declare function directHandoffValidationChecks(...args: Parameters<typeof reviewerCorrectionValidationFresh>): Promise<readonly ReviewerCorrectionCheck[]>;
|
|
118
120
|
/** Summary-only native reader: unavailable API/error is not a successfully observed empty history. */
|
|
119
121
|
export declare function observedMissionValidationSummary(validation: readonly string[], childSessionID: string, read?: () => Promise<unknown>, notBefore?: number, directory?: string, directories?: Readonly<Record<string, string>>): Promise<Record<string, unknown>>;
|
|
120
122
|
export declare function missionReviewBaseline(directory: string): Promise<string | undefined>;
|
|
@@ -199,6 +199,14 @@ export function reviewerCorrectionValidation(validation, child, history, notBefo
|
|
|
199
199
|
continue;
|
|
200
200
|
}
|
|
201
201
|
previousEnd = attempt.completed;
|
|
202
|
+
// A final-check-only refresh must replace its old binding, not force the
|
|
203
|
+
// unchanged prefix to run again. Repeated declarations stay ambiguous;
|
|
204
|
+
// preserve their existing occurrence rules and validate every saved input.
|
|
205
|
+
if (completed && partial.length === required.length && members.length === 1 &&
|
|
206
|
+
members[0].command === required.at(-1) && required.indexOf(members[0].command) === required.length - 1) {
|
|
207
|
+
partial.splice(0, partial.length, ...completed.slice(0, -1));
|
|
208
|
+
completed = undefined;
|
|
209
|
+
}
|
|
202
210
|
if (members[0].command !== required[partial.length] || partial.length === required.length)
|
|
203
211
|
partial.length = 0;
|
|
204
212
|
failed = undefined;
|
|
@@ -268,6 +276,16 @@ export async function reviewerCorrectionValidationFresh(validation, child, histo
|
|
|
268
276
|
}
|
|
269
277
|
return checked;
|
|
270
278
|
}
|
|
279
|
+
/** Preserve a current ordered prefix while the next author executes the remaining recipe. */
|
|
280
|
+
export async function directHandoffValidationChecks(...args) {
|
|
281
|
+
const [validation, child, history, started, checks, root, directories, registeredAt] = args;
|
|
282
|
+
for (let count = validation.length; count > 0; count--) {
|
|
283
|
+
const result = await reviewerCorrectionValidationFresh(validation.slice(0, count), child, history, started, checks, root, directories, registeredAt);
|
|
284
|
+
if (result.ready)
|
|
285
|
+
return checks.filter(check => result.matched?.some(item => item.callID === check.callID));
|
|
286
|
+
}
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
271
289
|
/** Summary-only native reader: unavailable API/error is not a successfully observed empty history. */
|
|
272
290
|
export async function observedMissionValidationSummary(validation, childSessionID, read, notBefore, directory, directories) {
|
|
273
291
|
if (!read)
|
|
@@ -356,7 +374,7 @@ export async function missionDeliveryObservation(directory, runID) {
|
|
|
356
374
|
}
|
|
357
375
|
/** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
|
|
358
376
|
export function initialMissionReviewPrompt(mission, prompts) {
|
|
359
|
-
return [mission.review?.initialPrompt, ...prompts].find(prompt => typeof prompt === "string" &&
|
|
377
|
+
return [mission.review?.initialPrompt, ...(mission.corrections ?? []).map(item => item.initialPrompt), ...prompts].find(prompt => typeof prompt === "string" &&
|
|
360
378
|
prompt.startsWith(`candidate_id: ${mission.id}\n`) && /^review_phase: initial$/mu.test(prompt) &&
|
|
361
379
|
/^canonical_validation_exit: 0$/mu.test(prompt));
|
|
362
380
|
}
|
|
@@ -367,8 +385,9 @@ export async function completedMissionReviewPrompts(mission, profile, root, requ
|
|
|
367
385
|
return [];
|
|
368
386
|
// The completion hook has already bound this prompt to a real independent Reviewer child.
|
|
369
387
|
// Avoid a second V2 history read when its page/list API is temporarily unavailable.
|
|
370
|
-
|
|
371
|
-
|
|
388
|
+
const initial = initialMissionReviewPrompt(mission, []);
|
|
389
|
+
if (initial)
|
|
390
|
+
return [initial];
|
|
372
391
|
const owner = mission.coordinator ?? root;
|
|
373
392
|
if (mission.coordinator !== null) {
|
|
374
393
|
const coordinator = await host.get(owner);
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -18,7 +18,7 @@ import { publishMissionProgress } from "./mission-progress.js";
|
|
|
18
18
|
import { formatWorkQuantity, planWorkQuantity } from "../core/work-quantity.js";
|
|
19
19
|
import { cachedWorkProgress, forgetWorkSession, observeActorWork, workRootForActor } from "../core/work-quantity-observations.js";
|
|
20
20
|
import { serializeOperatorStatus } from "./operator-status-output.js";
|
|
21
|
-
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, validationDirectoryMatches } from "./mission-review.js";
|
|
21
|
+
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, directHandoffValidationChecks, validationDirectoryMatches } from "./mission-review.js";
|
|
22
22
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
23
23
|
import { prepareValidationScratch } from "./validation-scratch.js";
|
|
24
24
|
import { SOURCE_REVIEW_RISK_TAGS } from "../core/consultation.js";
|
|
@@ -355,6 +355,47 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
355
355
|
return who.role === "dog-reviewer" && who.parent === (mission.coordinator ?? root);
|
|
356
356
|
}
|
|
357
357
|
async function restoreReviewAuthorship(root, mission, id) {
|
|
358
|
+
// An inline correction admission can end without any implementation: the
|
|
359
|
+
// controller may be the designated writer. Recover that exact native return
|
|
360
|
+
// rather than permanently treating the unused grant as source authorship.
|
|
361
|
+
const unused = mission.sourceAuthors?.includes(id) ? [] : (mission.corrections ?? []).filter(item => item.author === id && item.inlineReview && item.status === "failed" && !item.noWriteReceipt);
|
|
362
|
+
if (unused.length) {
|
|
363
|
+
const history = await reviewMessages(id);
|
|
364
|
+
const receipts = unused.flatMap(correction => {
|
|
365
|
+
const inline = correction.inlineReview;
|
|
366
|
+
const prompt = history.findIndex(message => record(message.info) && message.info.id === inline.promptID &&
|
|
367
|
+
message.info.role === "user" && message.info.sessionID === id);
|
|
368
|
+
if (prompt < 0)
|
|
369
|
+
return [];
|
|
370
|
+
const response = reviewResponseMessages(history, prompt);
|
|
371
|
+
const last = [...response].reverse().find(message => record(message.info) && message.info.role === "assistant");
|
|
372
|
+
const info = record(last?.info) ? last.info : undefined;
|
|
373
|
+
if (!info || info.sessionID !== id || info.finish !== "stop" || info.error || typeof info.id !== "string" ||
|
|
374
|
+
!record(info.time) || typeof info.time.completed !== "number" || info.time.completed < inline.admittedAt)
|
|
375
|
+
return [];
|
|
376
|
+
const parts = response.flatMap(message => Array.isArray(message.parts) ? message.parts.filter(record) : []);
|
|
377
|
+
const admitted = parts.findIndex(part => part.type === "tool" && part.tool === repairReview && record(part.state) &&
|
|
378
|
+
part.state.status === "completed" && record(part.state.time) && typeof part.state.time.end === "number" &&
|
|
379
|
+
part.state.time.end >= inline.admittedAt);
|
|
380
|
+
// Before admission this Reviewer was read-only. After it, require the
|
|
381
|
+
// observed terminal and only read/search calls; unknown tools or failed
|
|
382
|
+
// writes cannot establish that no implementation occurred.
|
|
383
|
+
if (admitted < 0 || parts.slice(admitted + 1).some(part => part.type === "tool" &&
|
|
384
|
+
(!["read", "grep", "glob"].includes(String(part.tool)) || !record(part.state) ||
|
|
385
|
+
!["completed", "error"].includes(String(part.state.status)))))
|
|
386
|
+
return [];
|
|
387
|
+
return [{ runID: correction.runID, promptID: inline.promptID, messageID: info.id }];
|
|
388
|
+
});
|
|
389
|
+
if (receipts.length)
|
|
390
|
+
mission = await missions.update(root, current => {
|
|
391
|
+
for (const receipt of receipts) {
|
|
392
|
+
const correction = current.corrections?.find(item => item.runID === receipt.runID && item.author === id &&
|
|
393
|
+
item.status === "failed" && item.inlineReview?.promptID === receipt.promptID);
|
|
394
|
+
if (correction)
|
|
395
|
+
correction.noWriteReceipt = { promptID: receipt.promptID, messageID: receipt.messageID };
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
358
399
|
if (!missionReviewIndependent(mission, id) || !mission.attempts?.some(attempt => attempt.childSessionID === id))
|
|
359
400
|
return mission;
|
|
360
401
|
const history = await reviewMessages(id);
|
|
@@ -610,6 +651,28 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
610
651
|
return direct && taskID && direct.unit.callID ? { root: direct.root, taskID, callID: direct.unit.callID,
|
|
611
652
|
startedAt: direct.unit.directExecution.startedAt } : undefined;
|
|
612
653
|
},
|
|
654
|
+
missionDispatchOrigin: async (root, taskID, callID) => {
|
|
655
|
+
const state = await operators.read(root);
|
|
656
|
+
const unit = state?.units.find(item => item.callID === callID && /^task_id: (.+)$/mu.exec(item.task.prompt)?.[1] === taskID);
|
|
657
|
+
const segments = unit?.directHandoffs?.filter(item => !item.settledCallID || item.settledCallID === callID);
|
|
658
|
+
const first = segments?.[0];
|
|
659
|
+
return first ? { callID: first.callID, startedAt: first.startedAt,
|
|
660
|
+
segments: segments.map(item => ({ sessionID: item.actor, startedAt: item.startedAt, endedAt: item.handedOffAt })) } : undefined;
|
|
661
|
+
},
|
|
662
|
+
missionHandoffChecks: async (root, taskID, callID) => {
|
|
663
|
+
const run = await operators.read(root);
|
|
664
|
+
const unit = run?.units.find(item => item.callID === callID && /^task_id: (.+)$/mu.exec(item.task.prompt)?.[1] === taskID);
|
|
665
|
+
const prior = unit?.directHandoffs?.filter(item => !item.settledCallID || item.settledCallID === callID).at(-1);
|
|
666
|
+
if (!unit || !prior)
|
|
667
|
+
return [];
|
|
668
|
+
try {
|
|
669
|
+
const history = await reviewMessages(prior.actor, prior.startedAt, true);
|
|
670
|
+
return await directHandoffValidationChecks(unit.unit.validation, prior.actor, history, Date.parse(prior.startedAt), prior.checks, input.directory, prior.validationCwd ?? unit.unit.validation_cwd, prior.validationRegisteredAt);
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
},
|
|
613
676
|
reviewerCorrectionValidationMembers: async (child, command, directory) => {
|
|
614
677
|
const correction = await reviewerCorrection(child) ?? await directExecution(child);
|
|
615
678
|
if (!correction)
|
|
@@ -654,6 +717,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
654
717
|
const unit = run?.units.find(item => /^task_id: (.+)$/m.exec(item.task.prompt)?.[1] === taskID);
|
|
655
718
|
if (!run || !unit?.callID)
|
|
656
719
|
return undefined;
|
|
720
|
+
if (run.phase === "cancelled" && unit.directHandoffs?.length && !unit.childSessionID) {
|
|
721
|
+
return { callID: unit.callID, cancelled: true };
|
|
722
|
+
}
|
|
657
723
|
// Direct work has no child Task terminal. Its explicit host-observed finish owns
|
|
658
724
|
// settlement; never mistake a prior parent response for this unit's completion.
|
|
659
725
|
if (unit.directExecution && run.phase !== "cancelled")
|
|
@@ -1150,7 +1216,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1150
1216
|
`and verifies old children before preparing a Worker. Otherwise obtain the user's scope decision.` };
|
|
1151
1217
|
if (mission.coordinator === null && mission.runID === null && !mission.dispatchOpen && mission.phase === "open") {
|
|
1152
1218
|
return { ...packet, task: missions.task(mission),
|
|
1153
|
-
next_action:
|
|
1219
|
+
next_action: initialMissionAction(mission) };
|
|
1154
1220
|
}
|
|
1155
1221
|
if (mission.coordinator === null && mission.runID === run?.runID && !mission.dispatchOpen &&
|
|
1156
1222
|
run?.phase === "awaiting-acceptance" && mission.kind === "operation" && !missionExecutionComplete(mission)) {
|
|
@@ -2199,6 +2265,44 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2199
2265
|
executor_session_id: actor, validation: direct.unit.unit.validation,
|
|
2200
2266
|
next_action: `Implement/correct and run the declared checks here, in this same session. Call ${finishDirectUnit} after the requested delivery boundary. No Worker or handoff read is needed. Retain the original requirements; independent Review and Operator acceptance still follow.` });
|
|
2201
2267
|
}
|
|
2268
|
+
async function handoffDirect(root, actor, callID, notes) {
|
|
2269
|
+
const direct = await directExecution(actor);
|
|
2270
|
+
if (direct) {
|
|
2271
|
+
await control.assertDirectUnitQuiescent(actor);
|
|
2272
|
+
const history = await reviewMessages(actor, direct.unit.directExecution.startedAt, true);
|
|
2273
|
+
const calls = new Map();
|
|
2274
|
+
for (const message of history)
|
|
2275
|
+
if (record(message.info) && message.info.sessionID === actor && Array.isArray(message.parts)) {
|
|
2276
|
+
for (const part of message.parts)
|
|
2277
|
+
if (record(part) && part.type === "tool" && typeof part.callID === "string" && part.callID !== callID)
|
|
2278
|
+
calls.set(part.callID, part);
|
|
2279
|
+
}
|
|
2280
|
+
if ([...calls.values()].some(part => !record(part.state) || !["completed", "error"].includes(String(part.state.status)) ||
|
|
2281
|
+
record(part.state.metadata) && ["running", "pending", "unknown"].includes(String(part.state.metadata.status)))) {
|
|
2282
|
+
return JSON.stringify({ status: "direct-handoff-awaits-tools", next_action: "Finish the existing native command before handing off this unit; retain its result and continue here or delegate afterwards." });
|
|
2283
|
+
}
|
|
2284
|
+
await activateDirect(actor);
|
|
2285
|
+
const mission = await missions.retainContext(root, actor, history, notes);
|
|
2286
|
+
await operators.handoffDirect(root, actor, await missions.handoffContext(mission));
|
|
2287
|
+
}
|
|
2288
|
+
const run = await operators.required(root);
|
|
2289
|
+
const unit = run.units.find(item => item.status !== "succeeded");
|
|
2290
|
+
const last = unit?.directHandoffs?.at(-1);
|
|
2291
|
+
if (actor !== (run.operatorSessionID ?? root) || !last || last.actor !== actor)
|
|
2292
|
+
throw new Error("mission-direct-handoff-unavailable");
|
|
2293
|
+
// Idempotently finish projection/release after a restart between the durable handoff and response.
|
|
2294
|
+
await missions.update(root, mission => {
|
|
2295
|
+
const previous = mission.attempts?.find(item => item.callID === last.callID);
|
|
2296
|
+
if (previous?.status === "dispatched")
|
|
2297
|
+
previous.status = "handed-off";
|
|
2298
|
+
});
|
|
2299
|
+
await control.releaseDirectUnit(actor);
|
|
2300
|
+
const next = await operators.next(root, actor);
|
|
2301
|
+
return JSON.stringify({ ...next,
|
|
2302
|
+
handoff: { from: actor, unit_id: unit.unit.id, retained_checks: last.checks.length },
|
|
2303
|
+
next_action: unit.status === "pending" ? "Dispatch the returned Worker once to continue this same unit. Existing edits, context, checks and reservation are retained. The Worker owns remaining implementation and validation; handoff is not completion."
|
|
2304
|
+
: "The handed-off unit is already admitted. Continue its existing owner; do not dispatch another Worker." });
|
|
2305
|
+
}
|
|
2202
2306
|
tools[startDirectUnit] = { description: "Execute the next already-planned ordinary unit in this Coordinator/Operator session, including a correction planned with executor=self. Native edits and declared checks become formal evidence without a Worker, handoff read or approval. Never replace an active writer; completed correction history does not restrict this choice.",
|
|
2203
2307
|
args: {}, execute: async (_args, context) => {
|
|
2204
2308
|
const { root } = await missionAuthority(context.sessionID);
|
|
@@ -2269,12 +2373,19 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2269
2373
|
description: "For an operation: the actual run/grade commands, not a preflight or NO_START check. Host observes their native shell completion separately from successful exits; no handwritten proof file is needed. For run-once/result collection, validate the collected result without rerunning the operation.", "x-sortie-optional": true };
|
|
2270
2374
|
const handoffContextSchema = { type: "string", "x-sortie-optional": true,
|
|
2271
2375
|
description: "Optional current working notes: intent/rationale, known environment and paths, completed setup/checks with results, failed approaches and remaining work. Reuse known facts with source references; no required form or new approval. Public history and Mission observations are also supplied automatically. Omit when already sufficient; do not recopy the original request." };
|
|
2272
|
-
|
|
2376
|
+
function initialMissionAction(mission) {
|
|
2377
|
+
return mission.preferredExecutor === "coordinator"
|
|
2378
|
+
? "Dispatch this Coordinator Task now with the supplied recommendation/context. Coordinator owns substantial implementation and can use plan_units executor=self or delegate remaining work when useful. Start useful work, not a route-selection investigation."
|
|
2379
|
+
: "Use the recommendation and already-known context: when a meaningful formal check and useful unit are already known, plan_units can dispatch its Worker directly. Otherwise dispatch this Coordinator Task now; it may implement here or delegate during actual work. Do not investigate merely to decide the route or discover a check for Operator planning.";
|
|
2380
|
+
}
|
|
2381
|
+
tools[startMission] = { description: `Operator: save current requirements and recommend preferred_executor from the original instruction and already-known context, without a routing investigation. The host retains the original request verbatim. Clear bounded work normally suits Worker; coupled design, ambiguous requirements or cross-system integration normally suit Coordinator direct execution. Follow explicit user choices. Coordinator may revise this advisory recommendation during useful work, not a mandatory reassessment. With known unit facts and meaningful formal checks, include unit now (and execution for an operation): worker/default can dispatch its Worker directly; coordinator retains these facts for its own execution choice. Unknown checks are resolved during actual work, not an Operator preflight. Investigation/edit/check/requested commit stay with the chosen author before applicable Review. ${MISSION_GIT_SCOPE} intent=replace follows an actual user requirement change, retaining unchanged constraints and cumulative spend; for mission-source-reconciliation-required use the saved requirements when they reflect that change. intent=new is separate work in another location.`,
|
|
2273
2382
|
args: { requirements: { ...stringList, minItems: 1, maxItems: 64 },
|
|
2274
2383
|
work_quantities: { type: ["array", "object", "string", "number", "boolean", "null"], items: {}, additionalProperties: true, "x-sortie-optional": true,
|
|
2275
2384
|
minLength: 0, maxLength: 65535, minItems: 0, maxItems: 4096,
|
|
2276
2385
|
description: "Optional display-only workload array: stable id, kind, relative_weight, bindings.paths/commands, checkpoints. Reuse this ordinary plan; never add a planning/report turn for progress. Invalid shapes/estimates fall back without changing execution." },
|
|
2277
|
-
|
|
2386
|
+
preferred_executor: { type: "string", enum: ["coordinator", "worker"], "x-sortie-optional": true,
|
|
2387
|
+
description: "Initial recommendation from the instruction and already-known context. Coordinator may adapt through actual work without approval or a separate investigation. coordinator returns Coordinator even with supplied unit facts; worker uses the direct Worker route when those facts are ready. Not a model override or immutable requirement." },
|
|
2388
|
+
unit: { ...missionUnitSchema, description: "Include already-known unit facts without investigating to fill them. worker/default returns its configured Worker directly; coordinator passes these facts to Coordinator without starting a Worker.", "x-sortie-optional": true },
|
|
2278
2389
|
execution: missionExecutionSchema,
|
|
2279
2390
|
handoff_context: handoffContextSchema,
|
|
2280
2391
|
confirmed_conditions: conditionsSchema,
|
|
@@ -2282,6 +2393,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2282
2393
|
kind: { type: "string", enum: ["implementation", "operation"], description: "Use operation for running an existing benchmark, command or procedure. The host records its actual execution separately from setup and checks.", "x-sortie-optional": true },
|
|
2283
2394
|
intent: { type: "string", enum: ["", "continue", "new", "replace"], "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
2284
2395
|
await requireRoot(context.sessionID);
|
|
2396
|
+
if (args.preferred_executor !== undefined && !["coordinator", "worker"].includes(args.preferred_executor))
|
|
2397
|
+
throw new Error("mission-preferred-executor-invalid");
|
|
2285
2398
|
if (args.intent !== undefined && !["", "continue", "new", "replace"].includes(args.intent))
|
|
2286
2399
|
throw new Error("mission-intent-invalid");
|
|
2287
2400
|
return serializeDispatchTransition(context.sessionID, async () => {
|
|
@@ -2329,6 +2442,14 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2329
2442
|
...(args.intent === "replace" && cancelled?.phase === "cancelled" ? { cancelledRunID: cancelled.runID } : {}),
|
|
2330
2443
|
});
|
|
2331
2444
|
mission = await missions.retainContext(context.sessionID, context.sessionID, history, args.handoff_context);
|
|
2445
|
+
if (args.preferred_executor !== undefined)
|
|
2446
|
+
mission = await missions.update(context.sessionID, state => {
|
|
2447
|
+
state.preferredExecutor = args.preferred_executor;
|
|
2448
|
+
});
|
|
2449
|
+
const suggestedUnit = args.unit;
|
|
2450
|
+
if (mission.preferredExecutor === "coordinator" && record(suggestedUnit) && mission.runID === null && !mission.dispatchOpen) {
|
|
2451
|
+
mission = await missions.update(context.sessionID, state => { state.suggestedUnit = structuredClone(suggestedUnit); });
|
|
2452
|
+
}
|
|
2332
2453
|
const prohibited = args.prohibited_write;
|
|
2333
2454
|
if (prohibited !== undefined) {
|
|
2334
2455
|
if (!Array.isArray(prohibited) || !prohibited.every(path => typeof path === "string"))
|
|
@@ -2367,7 +2488,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2367
2488
|
if (baseline)
|
|
2368
2489
|
mission = await missions.update(context.sessionID, state => { state.reviewBaseline = baseline; });
|
|
2369
2490
|
}
|
|
2370
|
-
if (args.unit !== undefined && mission.runID === null && !mission.dispatchOpen &&
|
|
2491
|
+
if (mission.preferredExecutor !== "coordinator" && args.unit !== undefined && mission.runID === null && !mission.dispatchOpen &&
|
|
2371
2492
|
(mission.kind !== "operation" || args.execution !== undefined || mission.execution)) {
|
|
2372
2493
|
const planned = JSON.parse(await declareMissionUnits(context.sessionID, context.sessionID, mission, [args.unit], undefined, args.execution, context.callID, args.handoff_context, args.work_quantities));
|
|
2373
2494
|
return JSON.stringify({ ...locationObservation(context.sessionID), mission_id: mission.id, requirements: mission.requirements, ...planned });
|
|
@@ -2380,7 +2501,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2380
2501
|
return JSON.stringify({ ...locationObservation(context.sessionID), ...(mission.dispatchOpen
|
|
2381
2502
|
? missionDispatchPacket(mission, await operators.read(context.sessionID))
|
|
2382
2503
|
: { mission_id: mission.id, requirements: mission.requirements, task: missions.task(mission),
|
|
2383
|
-
|
|
2504
|
+
...(mission.preferredExecutor ? { preferred_executor: mission.preferredExecutor } : {}),
|
|
2505
|
+
next_action: initialMissionAction(mission) }) });
|
|
2384
2506
|
});
|
|
2385
2507
|
} };
|
|
2386
2508
|
tools[skipMissionConsultation] = { description: "Coordinator: durably record why a concrete optional Advisor/Scout consultation is unnecessary. This is observation only: it adds no approval, consultation requirement, or dispatch gate.",
|
|
@@ -2539,6 +2661,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2539
2661
|
const recoveryErrors = [];
|
|
2540
2662
|
const addOwner = (actor, window) => owners.set(actor, [...(owners.get(actor) ?? []), window]);
|
|
2541
2663
|
for (const unit of run.units) {
|
|
2664
|
+
for (const prior of unit.directHandoffs ?? [])
|
|
2665
|
+
addOwner(prior.actor, { notBefore: prior.startedAt, notAfter: prior.handedOffAt });
|
|
2542
2666
|
if (unit.directExecution?.actor) {
|
|
2543
2667
|
addOwner(unit.directExecution.actor, { notBefore: unit.directExecution.startedAt,
|
|
2544
2668
|
...(unit.directExecution.finishedAt ? { notAfter: unit.directExecution.finishedAt } : {}) });
|
|
@@ -2751,7 +2875,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2751
2875
|
}
|
|
2752
2876
|
async function declareMissionUnits(root, actor, mission, raw, reason, execution, planningCallID, handoffNotes, workQuantities) {
|
|
2753
2877
|
await control.currentBudget(root);
|
|
2754
|
-
mission = await missions.required(root);
|
|
2878
|
+
mission = actor === root ? await reconcileMissionDispatch(root) ?? await missions.required(root) : await missions.required(root);
|
|
2755
2879
|
let previous = await operators.read(root);
|
|
2756
2880
|
if (sourceReconciliationRequired(mission, previous))
|
|
2757
2881
|
return JSON.stringify({ ...missionDispatchPacket(mission, previous),
|
|
@@ -2766,8 +2890,34 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2766
2890
|
Array.isArray(execution.commands) && execution.commands.length === 0);
|
|
2767
2891
|
const plan = missionPlan(mission, raw, input.directory);
|
|
2768
2892
|
assertMissionWritePaths(mission, plan.units.flatMap(unit => unit.write));
|
|
2769
|
-
if (actor === root && (mission.coordinator !== null || plan.units.length !== 1))
|
|
2770
|
-
|
|
2893
|
+
if (actor === root && (mission.coordinator !== null || plan.units.length !== 1)) {
|
|
2894
|
+
// A submitted Coordinator still owns corrective planning. Return its actual
|
|
2895
|
+
// continuation instead of referring to a Task that the rejection never supplied.
|
|
2896
|
+
// This is routing advice only: no replacement plan, reservation or admission.
|
|
2897
|
+
// Routing below alone chooses whether to offer a Task. A resumable parent
|
|
2898
|
+
// packet can already contain one even while its Reviewer is still pending.
|
|
2899
|
+
const { task: _dispatchTask, ...packet } = missionDispatchPacket(mission, previous);
|
|
2900
|
+
const reviewPending = mission.runID === previous?.runID && mission.review?.verdict === "pending";
|
|
2901
|
+
const active = mission.dispatchOpen || reviewPending || mission.runID === previous?.runID &&
|
|
2902
|
+
previous?.units.some(unit => unit.status === "running");
|
|
2903
|
+
const continuation = !active ? missions.task(mission) : undefined;
|
|
2904
|
+
// Existing Coordinator resumes accept a concrete prompt. Do not use the opaque
|
|
2905
|
+
// initial reference here: claim() would expand it from unchanged Mission state
|
|
2906
|
+
// and lose the unadmitted correction that prompted this continuation.
|
|
2907
|
+
const task = continuation && mission.coordinator !== null ? { ...continuation,
|
|
2908
|
+
prompt: `Continue the same Mission ${mission.id} in this existing Coordinator session, in the user's language. ` +
|
|
2909
|
+
"Retain the saved original requests, requirements, candidate, checks and cumulative budget.\n" +
|
|
2910
|
+
"Proposed correction from Operator root (not admitted; preserve requirements, candidate and cumulative budget):\n" +
|
|
2911
|
+
JSON.stringify({ units: raw, reason, execution, handoff_context: handoffNotes, work_quantities: workQuantities }) } : continuation;
|
|
2912
|
+
return JSON.stringify({ ...packet,
|
|
2913
|
+
status: active ? "mission-coordinator-active" : "mission-coordinator-required",
|
|
2914
|
+
...(task ? { task } : {}),
|
|
2915
|
+
next_action: reviewPending
|
|
2916
|
+
? "Dispatch the exact Reviewer Task from review_mission if not yet active; otherwise wait for that Reviewer. Do not start a Coordinator or another Worker while review is pending. The proposed plan has not been admitted."
|
|
2917
|
+
: active
|
|
2918
|
+
? "Continue the active owning session or wait for its native completion; do not dispatch a duplicate Coordinator or Worker. The proposed plan has not been admitted."
|
|
2919
|
+
: "Dispatch this exact Task to continue the same Mission and Coordinator session with the concrete correction and retained checks. The proposed plan has not been admitted; keep requirements, candidate and cumulative budget unchanged." });
|
|
2920
|
+
}
|
|
2771
2921
|
const same = previous?.planHash === createHash("sha256").update(JSON.stringify(plan)).digest("hex");
|
|
2772
2922
|
const replanning = previous && !["completed", "cancelled"].includes(previous.phase) && !same;
|
|
2773
2923
|
const activeDirect = previous?.units.find(unit => unit.status === "running" && unit.directExecution?.actor === actor);
|
|
@@ -2936,19 +3086,25 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2936
3086
|
: next);
|
|
2937
3087
|
}
|
|
2938
3088
|
tools[planUnits] = { description: `Coordinator or single-unit Fast-lane Operator: declare useful work with estimated read/write scope and meaningful formal checks. executor=self starts implementation/correction and formal validation HERE without a Worker or handoff read; otherwise dispatch the returned configured Worker promptly. Reuse useful author context by default; legitimate Worker/direct handoff remains available after terminal work, including correction. Retain findings, actual authorship and passed input-bound checks; do not rediscover or revalidate unchanged work merely for a new actor. ${MISSION_GIT_SCOPE} Native scope reconciliation/expand_unit retain host permissions and explicit path prohibitions. Objective is the target or corrective delta: aim for 2000 characters; the full original request/public reproduction is supplied separately, not copied here. Oversized unit instructions are retained verbatim in handoff Mission context, not rejected for another planning round. Keep every requirement covered. Final validation proves the unit; empty or dummy checks do not qualify. Diagnostics need no registration. write: [] is read-only; dir/** and native absolute paths support actual outputs. reason replans settled work, or ends your own quiescent direct unit without acceptance to correct its declaration, in the same requirements/budget; same-scope failed validation uses retry_mission_unit. Completion history is not a correction-owner lock or an acceptance prerequisite.`,
|
|
2939
|
-
args: { units: { type: "array", minItems: 1, maxItems: 32, items: missionUnitSchema
|
|
3089
|
+
args: { units: { type: "array", minItems: 1, maxItems: 32, items: missionUnitSchema, "x-sortie-optional": true,
|
|
3090
|
+
description: "Useful work units. Omit only with executor=worker to hand off your currently running direct unit, retaining its contract and existing work." },
|
|
2940
3091
|
work_quantities: tools[startMission].args.work_quantities,
|
|
2941
3092
|
execution: missionExecutionSchema,
|
|
2942
3093
|
handoff_context: handoffContextSchema,
|
|
2943
3094
|
reason: { type: "string", "x-sortie-optional": true },
|
|
2944
|
-
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here without
|
|
3095
|
+
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here. Use worker without units to yield the current direct unit, carrying remaining action in handoff_context; the same unit, edits, checks and budget continue. With units, worker/default retains configured Worker routing.", "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
2945
3096
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
2946
3097
|
if (args.executor !== undefined && !["self", "worker"].includes(args.executor))
|
|
2947
3098
|
throw new Error("mission-executor-invalid");
|
|
2948
3099
|
try {
|
|
2949
3100
|
return await serializeDispatchTransition(root, async () => {
|
|
3101
|
+
if (args.executor === "worker" && args.units === undefined)
|
|
3102
|
+
return handoffDirect(root, context.sessionID, context.callID, args.handoff_context);
|
|
2950
3103
|
const planned = await declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution, context.callID, args.handoff_context, args.work_quantities);
|
|
2951
|
-
|
|
3104
|
+
const task = JSON.parse(planned).task;
|
|
3105
|
+
// A Coordinator continuation is not a newly prepared direct unit.
|
|
3106
|
+
return args.executor === "self" && record(task) && task.subagent_type !== profileAgent(profile, "dog-operator")
|
|
3107
|
+
? startDirect(root, context.sessionID) : planned;
|
|
2952
3108
|
});
|
|
2953
3109
|
}
|
|
2954
3110
|
catch (error) {
|
|
@@ -3092,7 +3248,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3092
3248
|
current.verdict = "findings";
|
|
3093
3249
|
current.result = findings;
|
|
3094
3250
|
// The independent investigation is recorded by this real tool call, not a fictitious terminal.
|
|
3095
|
-
current.initialPrompt = current.task.prompt;
|
|
3251
|
+
current.initialPrompt = initialMissionReviewPrompt(item, [current.task.prompt]);
|
|
3096
3252
|
});
|
|
3097
3253
|
review = mission.review;
|
|
3098
3254
|
}
|
|
@@ -3289,7 +3445,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3289
3445
|
// turn. Generate verification only when a completed initial review is recoverable;
|
|
3290
3446
|
// otherwise generate an admissible initial review instead of trapping the Coordinator.
|
|
3291
3447
|
const prior = mission.review?.task?.prompt;
|
|
3292
|
-
const completed = risk.length > 0 && prior && mission
|
|
3448
|
+
const completed = risk.length > 0 && prior && !initialMissionReviewPrompt(mission, [])
|
|
3293
3449
|
? await completedMissionReviewPrompts(mission, profile, root, prior, {
|
|
3294
3450
|
get: async (id) => payload(await session("get", { path: { id }, query: { directory: input.directory } })),
|
|
3295
3451
|
messages: reviewMessages,
|
|
@@ -89,6 +89,17 @@ export interface RuntimeBridge {
|
|
|
89
89
|
callID: string;
|
|
90
90
|
startedAt: string;
|
|
91
91
|
} | undefined>;
|
|
92
|
+
/** The same unit's original reservation and completed direct-author usage segments. */
|
|
93
|
+
missionDispatchOrigin?(rootSessionID: string, taskID: string, callID: string): Promise<{
|
|
94
|
+
callID: string;
|
|
95
|
+
startedAt: string;
|
|
96
|
+
segments: readonly {
|
|
97
|
+
sessionID: string;
|
|
98
|
+
startedAt: string;
|
|
99
|
+
endedAt: string;
|
|
100
|
+
}[];
|
|
101
|
+
} | undefined>;
|
|
102
|
+
missionHandoffChecks?(rootSessionID: string, taskID: string, callID: string): Promise<readonly ReviewerCorrectionCheck[]>;
|
|
92
103
|
reviewerCorrectionValidationMembers?(childSessionID: string, command: string, directory?: string): Promise<string[] | undefined>;
|
|
93
104
|
missionValidationDirectoryMatches?(rootSessionID: string, taskID: string, childSessionID: string, commands: readonly string[], directory: string): Promise<boolean>;
|
|
94
105
|
recordReviewerCorrectionCheck?(rootSessionID: string, taskID: string, check: ReviewerCorrectionCheck): Promise<void>;
|
|
@@ -225,6 +236,7 @@ export interface RuntimeBridge {
|
|
|
225
236
|
/** Settle an unfinished direct unit against its actual actor, never a fabricated child Task. */
|
|
226
237
|
failDirectUnit(rootSessionID: string, actorSessionID: string): Promise<void>;
|
|
227
238
|
releaseDirectUnit(actorSessionID: string): Promise<void>;
|
|
239
|
+
assertDirectUnitQuiescent(actorSessionID: string): Promise<void>;
|
|
228
240
|
expandMissionWriteGate(childSessionID: string, paths: readonly string[]): Promise<void>;
|
|
229
241
|
completeRoot(rootSessionID: string, acceptanceFingerprint: string): Promise<{
|
|
230
242
|
status: "succeeded" | "awaiting-evidence";
|
|
@@ -33,11 +33,13 @@ export function toolSchema(args) {
|
|
|
33
33
|
const schema = plainSchema(value);
|
|
34
34
|
if (optionalArgument(value)) {
|
|
35
35
|
schema.description = `${typeof schema.description === "string" ? `${schema.description} ` : ""}Pass an empty string to omit this argument.`;
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
if (schema.type === "object")
|
|
36
|
+
// Structured optional arguments need the same omission sentinel we decode;
|
|
37
|
+
// otherwise V2 requires a fabricated object/array even when the tool says to omit it.
|
|
38
|
+
if (schema.type === "object" || schema.type === "array")
|
|
39
39
|
return [name, { anyOf: [schema, { type: "string", enum: [""], minLength: 0, maxLength: 0 }],
|
|
40
40
|
description: schema.description }];
|
|
41
|
+
if (schema.type === "string" && Array.isArray(schema.enum) && !schema.enum.includes(""))
|
|
42
|
+
schema.enum = [...schema.enum, ""];
|
|
41
43
|
}
|
|
42
44
|
return [name, schema];
|
|
43
45
|
})),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Price complete native requests; absent/interrupted usage is unknown, never a fabricated zero. */
|
|
2
|
-
export declare function settledUnitUsage(messages: readonly unknown[], startedAt?: string): number | null;
|
|
2
|
+
export declare function settledUnitUsage(messages: readonly unknown[], startedAt?: string, endedAt?: string): number | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { estimateModelUsageCost } from "./model-cost.js";
|
|
2
2
|
const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
3
3
|
/** Price complete native requests; absent/interrupted usage is unknown, never a fabricated zero. */
|
|
4
|
-
export function settledUnitUsage(messages, startedAt) {
|
|
4
|
+
export function settledUnitUsage(messages, startedAt, endedAt) {
|
|
5
5
|
let cost = 0, requests = 0;
|
|
6
6
|
const seen = new Set();
|
|
7
7
|
for (const message of messages) {
|
|
@@ -12,10 +12,12 @@ export function settledUnitUsage(messages, startedAt) {
|
|
|
12
12
|
continue;
|
|
13
13
|
seen.add(info.id);
|
|
14
14
|
const time = record(info.time) ? info.time : undefined;
|
|
15
|
-
if (startedAt) {
|
|
15
|
+
if (startedAt || endedAt) {
|
|
16
16
|
if (typeof time?.created !== "number")
|
|
17
17
|
return null;
|
|
18
|
-
if (time.created < Date.parse(startedAt))
|
|
18
|
+
if (startedAt && time.created < Date.parse(startedAt))
|
|
19
|
+
continue;
|
|
20
|
+
if (endedAt && time.created >= Date.parse(endedAt))
|
|
19
21
|
continue;
|
|
20
22
|
}
|
|
21
23
|
if (typeof time?.completed !== "number")
|
|
@@ -18,6 +18,8 @@ export declare const MISSION_REFLECTION_WORKFLOW = "When bounded process reflect
|
|
|
18
18
|
export declare function missionOperatorContent(profile: RuntimeProfile, version: string): string;
|
|
19
19
|
/** Keep representation oracles consistent in direct execution, Worker and Review. */
|
|
20
20
|
export declare const MISSION_REPRESENTATION_ORACLE = "Same-type representation, not name/visitor analogy; resolve conflicts.";
|
|
21
|
+
/** Planning and Review use public type families instead of only issue-named examples. */
|
|
22
|
+
export declare const MISSION_TYPE_FAMILY_ORACLE = "When changing a dispatch/exclusion set, derive the affected same-representation family from an existing\nschema/registry and its public callers, not just issue examples. Compare sibling entries under that\ncontract; reuse suitable parameterized checks. Do not widen unrelated families or require an exhaustive\nmatrix, another unit or review lane.";
|
|
21
23
|
/** Coordinator preserves source-backed return semantics in direct work or a Worker's objective. */
|
|
22
24
|
export declare const MISSION_RETURN_CONTRACT = "For a changed value-producing operation, derive its return contract from public callers and unchanged source/tests.\nPreserve established return type/container, metadata and label alignment unless the original request changes them;\nequal numeric values alone do not prove that contract. Trace which inputs determine result shape and labels,\nincluding non-primary parameters; a scalar primary input need not yield a scalar result. For a mask or conditional\nreplacement, preserve established broadcasting and missing-value propagation; an unknown condition is not proof\nthat replacement is intended. Carry the affected contract into direct work or the Worker's objective.\nReuse an adequate focused assertion; add only the smallest source-backed regression when needed, not an input matrix,\nanother unit, validation lane or review.";
|
|
23
25
|
/** Coordinator preserves source-backed mutation semantics in direct work or a Worker's objective. */
|
|
@@ -25,7 +27,7 @@ export declare const MISSION_MUTATION_CONTRACT = "For a changed mutating operati
|
|
|
25
27
|
/** Coordinator authors and plans regressions without adding another validation/review lane. */
|
|
26
28
|
export declare const MISSION_REGRESSION_PLACEMENT = "Prefer an existing relevant test suite/subtest or fixture when it can express the regression assertion.\nIf a new test module is needed, prefer a distinctive behavior-specific name over automatically mirroring the implementation module.\nPreserve established assertions and project test discovery. This is placement guidance, not another test, unit or review requirement.";
|
|
27
29
|
/** Shared by the installed Reviewer and its host-generated mission prompt. */
|
|
28
|
-
export declare const MISSION_BEHAVIOR_REVIEW = "Establish the test oracle independently of the patch: tests added with the implementation are claims to\nreview, not established API behavior. For a changed failure path, compare a pre-existing analogous public\ntest and its shared assertion helper, including default expected outputs, with the new case. A new test\nthat asserts the implementation's current result can encode the defect rather than catch it. Preserve\nthe pre-change contract unless the request changes it; do not invent a universal failure-result convention.\nFor changed observable behavior, inspect affected public caller assertions/output snapshots found by search, not only implementation-local fixtures.\nReconcile expected outputs with the original requirements and reuse adequate checks; no extra review round.\nFor requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.\nJudge allowed effects from the original request; preserve authorized opt-in behavior.\nSame-type representation, not name/visitor analogy; resolve conflicts.\nTrace a concrete rejected input through the changed code back to the public caller, checking result,\nerror and observable state together, including earlier results that can survive failure. Missing assertions\nalone are not defects; a concrete contradiction with the established contract is a finding even if tests pass.\nDuring correction, reuse the established public test harness where applicable and exercise the smallest\nregression exposing the defect before the fix and passing afterward. Do not change expected values or\nhelper defaults merely to agree with the implementation; derive them from the original contract.\nPrefer existing suites/subtests for regressions. New test entry/helper names should be distinctive\nand compose with other same-package test files, not create generic package-level collisions.\n\nFor a requested per-call mode/option, trace where the setting is chosen and where changed code uses\nretained objects or closures. Distinguish the current caller's setting from a captured creation-time\nsetting; use the requested contract to decide which governs. If actual source crosses that boundary\nand existing tests do not cover it, exercise its smallest public case, including the disabled behavior.\nDo not invent a lifecycle matrix or infer that an opt-in feature is correct from only its enabled path.\n\nFor a changed failure handler, inspect the operation it calls and the public inputs reaching it,\nincluding failures not listed in the new handler. Use the supplied source/tests and established API behavior\nto identify a concrete input that could still violate the requested contract. A passing normal input does\nnot settle a different failure outcome of that same operation. A demonstrable material defect is FINDINGS; ask for\nan excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,\nrequire an exhaustive exception inventory, or recommend catching every exception.\n\nReport FINDINGS only for concrete major or medium defects with a material impact on the original requirements,\npublic behavior, correctness or required validation. Name the consequence and smallest necessary fix.\nDo not turn minor style, wording, optional improvements or speculative edge cases into FINDINGS or EVIDENCE_GAPS.\nUse read/search to settle missing context in the current review. If a consequential uncertainty remains,\nrecord EVIDENCE_GAPS as an advisory limitation, not a demand for another evidence packet. Missing narration,\nrequirement mappings, hashes or clipped excerpts alone never justify a blocking finding.\n\nAssess behavioral requirements against actual source, tests and observed results. A required check known to\nhave failed or not run is a concrete finding; missing documentation of a check is not proof it did not run.\nFor incidental workflow constraints such as cache settings or command-path spelling, use existing host observations;\nabsence of a separate settings dump or historical log is not itself an evidence gap. Flag observed material\ncontradictions. The Operator owns final comparison with the original request. If a missing check genuinely\naffects correctness or a requested deliverable, name that consequence and the smallest useful next check.\nOn verification, compare the correction and its affected public outcomes with the original contract, not\nonly the wording of previous findings. Reuse unchanged evidence; do not expand into optional improvements\nor an exhaustive input matrix. PASS means no material finding, not exhaustive proof of every path.";
|
|
30
|
+
export declare const MISSION_BEHAVIOR_REVIEW = "Establish the test oracle independently of the patch: tests added with the implementation are claims to\nreview, not established API behavior. For a changed failure path, compare a pre-existing analogous public\ntest and its shared assertion helper, including default expected outputs, with the new case. A new test\nthat asserts the implementation's current result can encode the defect rather than catch it. Preserve\nthe pre-change contract unless the request changes it; do not invent a universal failure-result convention.\nFor changed observable behavior, inspect affected public caller assertions/output snapshots found by search, not only implementation-local fixtures.\nReconcile expected outputs with the original requirements and reuse adequate checks; no extra review round.\nFor requested side-effect changes, check remaining controls with existing client-owned state, not only isolated defaults.\nJudge allowed effects from the original request; preserve authorized opt-in behavior.\nSame-type representation, not name/visitor analogy; resolve conflicts.\nWhen changing a dispatch/exclusion set, derive the affected same-representation family from an existing\nschema/registry and its public callers, not just issue examples. Compare sibling entries under that\ncontract; reuse suitable parameterized checks. Do not widen unrelated families or require an exhaustive\nmatrix, another unit or review lane.\nTrace a concrete rejected input through the changed code back to the public caller, checking result,\nerror and observable state together, including earlier results that can survive failure. Missing assertions\nalone are not defects; a concrete contradiction with the established contract is a finding even if tests pass.\nDuring correction, reuse the established public test harness where applicable and exercise the smallest\nregression exposing the defect before the fix and passing afterward. Do not change expected values or\nhelper defaults merely to agree with the implementation; derive them from the original contract.\nPrefer existing suites/subtests for regressions. New test entry/helper names should be distinctive\nand compose with other same-package test files, not create generic package-level collisions.\n\nFor a requested per-call mode/option, trace where the setting is chosen and where changed code uses\nretained objects or closures. Distinguish the current caller's setting from a captured creation-time\nsetting; use the requested contract to decide which governs. If actual source crosses that boundary\nand existing tests do not cover it, exercise its smallest public case, including the disabled behavior.\nDo not invent a lifecycle matrix or infer that an opt-in feature is correct from only its enabled path.\n\nFor a changed failure handler, inspect the operation it calls and the public inputs reaching it,\nincluding failures not listed in the new handler. Use the supplied source/tests and established API behavior\nto identify a concrete input that could still violate the requested contract. A passing normal input does\nnot settle a different failure outcome of that same operation. A demonstrable material defect is FINDINGS; ask for\nan excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,\nrequire an exhaustive exception inventory, or recommend catching every exception.\n\nReport FINDINGS only for concrete major or medium defects with a material impact on the original requirements,\npublic behavior, correctness or required validation. Name the consequence and smallest necessary fix.\nDo not turn minor style, wording, optional improvements or speculative edge cases into FINDINGS or EVIDENCE_GAPS.\nUse read/search to settle missing context in the current review. If a consequential uncertainty remains,\nrecord EVIDENCE_GAPS as an advisory limitation, not a demand for another evidence packet. Missing narration,\nrequirement mappings, hashes or clipped excerpts alone never justify a blocking finding.\n\nAssess behavioral requirements against actual source, tests and observed results. A required check known to\nhave failed or not run is a concrete finding; missing documentation of a check is not proof it did not run.\nFor incidental workflow constraints such as cache settings or command-path spelling, use existing host observations;\nabsence of a separate settings dump or historical log is not itself an evidence gap. Flag observed material\ncontradictions. The Operator owns final comparison with the original request. If a missing check genuinely\naffects correctness or a requested deliverable, name that consequence and the smallest useful next check.\nOn verification, compare the correction and its affected public outcomes with the original contract, not\nonly the wording of previous findings. Reuse unchanged evidence; do not expand into optional improvements\nor an exhaustive input matrix. PASS means no material finding, not exhaustive proof of every path.";
|
|
29
31
|
/** Mission Reviewer uses ordinary host read/search permissions, not a supplied-packet-only protocol. */
|
|
30
32
|
export declare function missionReviewerContent(profile: RuntimeProfile): string;
|
|
31
33
|
export declare function missionCoordinatorContent(profile: RuntimeProfile, version: string): string;
|
|
@@ -161,20 +161,25 @@ ${MISSION_DEPENDENCY_WORKFLOW}
|
|
|
161
161
|
${profile.toolPrefix}cancel_operator with reason: "plain" to stop its owned children, then
|
|
162
162
|
${profile.toolPrefix}start_mission with intent: "replace" and the saved requirements. The cancelled
|
|
163
163
|
Mission is archived; cumulative spend is retained. Dispatch only the returned Coordinator Task.
|
|
164
|
-
2.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
2. Recommend preferred_executor in start_mission from the original instruction and already-known context.
|
|
165
|
+
Respect explicit user choices. Clear bounded implementation or known procedures normally suit worker;
|
|
166
|
+
coupled design, ambiguous requirements and cross-system integration normally suit coordinator.
|
|
167
|
+
Do not read/search/run diagnostics merely to classify difficulty or fill an Operator planning form.
|
|
168
|
+
A short reason in handoff_context is enough. This recommendation is advisory, not an immutable requirement.
|
|
169
|
+
Coordinator starts useful work immediately and may implement substantially itself or delegate remaining work
|
|
170
|
+
as execution makes that useful, without a separate reassessment or your approval.
|
|
171
|
+
If meaningful formal checks and unit facts are already known, include unit (and execution for a known operation).
|
|
172
|
+
worker/default receives its configured Worker directly; coordinator receives these facts without starting a Worker.
|
|
173
|
+
Unknown checks are discovered during useful author work, not a route-selection preflight.
|
|
174
|
+
Source investigation, shell/environment checks, fix design and output inventory belong with that author.
|
|
169
175
|
Use estimated read/write paths; native scope reconciliation and expand_unit cover actual outputs.
|
|
170
176
|
Write objective as the target or corrective delta: aim for 2000 characters.
|
|
171
177
|
The Worker reads the full original request natively from its handoff once, including exact public reproduction
|
|
172
178
|
inputs, paths and failures; do not copy that request into objective. Preserve it, not a summary.
|
|
173
|
-
Do not list speculative write paths or unrelated test suites as a precaution.
|
|
174
|
-
duration and
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
Investigation, edits, formal checks and any requested commit stay in that Worker before independent Review;
|
|
179
|
+
Do not list speculative write paths or unrelated test suites as a precaution. File count, instruction length,
|
|
180
|
+
duration and availability of a check do not alone determine difficulty. Dispatch the returned
|
|
181
|
+
${profileAgent(profile, "dog-operator")} Task when Coordinator is selected or unit facts are not yet known.
|
|
182
|
+
Investigation, edits, formal checks and any requested commit stay with the chosen author before independent Review;
|
|
178
183
|
do not invent a review-before-commit gate or a commit-only handoff. Preserve explicit user ordering.
|
|
179
184
|
${MISSION_GIT_SCOPE}
|
|
180
185
|
3. After a direct Worker succeeds, use its recorded result and inspect only missing source or evidence
|
|
@@ -279,6 +284,11 @@ existing user authorization and project gates; npm publication remains manual.
|
|
|
279
284
|
}
|
|
280
285
|
/** Keep representation oracles consistent in direct execution, Worker and Review. */
|
|
281
286
|
export const MISSION_REPRESENTATION_ORACLE = `Same-type representation, not name/visitor analogy; resolve conflicts.`;
|
|
287
|
+
/** Planning and Review use public type families instead of only issue-named examples. */
|
|
288
|
+
export const MISSION_TYPE_FAMILY_ORACLE = `When changing a dispatch/exclusion set, derive the affected same-representation family from an existing
|
|
289
|
+
schema/registry and its public callers, not just issue examples. Compare sibling entries under that
|
|
290
|
+
contract; reuse suitable parameterized checks. Do not widen unrelated families or require an exhaustive
|
|
291
|
+
matrix, another unit or review lane.`;
|
|
282
292
|
/** Coordinator preserves source-backed return semantics in direct work or a Worker's objective. */
|
|
283
293
|
export const MISSION_RETURN_CONTRACT = `For a changed value-producing operation, derive its return contract from public callers and unchanged source/tests.
|
|
284
294
|
Preserve established return type/container, metadata and label alignment unless the original request changes them;
|
|
@@ -314,6 +324,7 @@ For changed observable behavior, inspect affected public caller assertions/outpu
|
|
|
314
324
|
Reconcile expected outputs with the original requirements and reuse adequate checks; no extra review round.
|
|
315
325
|
${MISSION_CLIENT_STATE_CHECK}
|
|
316
326
|
${MISSION_REPRESENTATION_ORACLE}
|
|
327
|
+
${MISSION_TYPE_FAMILY_ORACLE}
|
|
317
328
|
Trace a concrete rejected input through the changed code back to the public caller, checking result,
|
|
318
329
|
error and observable state together, including earlier results that can survive failure. Missing assertions
|
|
319
330
|
alone are not defects; a concrete contradiction with the established contract is a finding even if tests pass.
|
|
@@ -470,11 +481,17 @@ ${controls(profile, ["plan_units", "start_direct_unit", "finish_direct_unit", "o
|
|
|
470
481
|
You are Coordinator. Own most of the practical work and dispatch within the saved original request.
|
|
471
482
|
You retain practical implementation authority: investigate, edit, correct, run formal checks and deliver
|
|
472
483
|
in this SAME session when your existing context makes that efficient. You are not a dispatch-only role.
|
|
484
|
+
Use the Operator's preferred_executor as a starting recommendation. Start useful work with available context;
|
|
485
|
+
do not investigate merely to confirm the recommendation. Adapt through actual work, honoring explicit user choices.
|
|
473
486
|
Use plan_units with executor: "self" for direct work, or start_direct_unit for an already prepared unit.
|
|
474
487
|
The host binds your native check results to the candidate; finish_direct_unit closes that work without
|
|
475
488
|
a Worker handoff. Independent Review and Operator final acceptance still follow. This is not limited to
|
|
476
489
|
micro-edits. Delegate to the configured Worker when that reduces work/cost or provides useful separation.
|
|
477
490
|
There is no proposal/approval/contract-repair round trip in this route.
|
|
491
|
+
To hand unfinished direct work to a Worker, call plan_units with executor: "worker", omit units, and put
|
|
492
|
+
the remaining action/useful observations in handoff_context. The host transfers this same unit after current
|
|
493
|
+
native commands settle, retaining changes, checks and budget. Do not finish it as successful or replan just
|
|
494
|
+
to change its author. Dispatch the returned Task once; Operator recommendation does not prevent this choice.
|
|
478
495
|
Use project-relative paths for repository read/search/shell; the native working directory is project_root.
|
|
479
496
|
Do not reconstruct or prepend the absolute workspace path. Preserve explicitly requested external paths.
|
|
480
497
|
|
|
@@ -495,9 +512,10 @@ For a named release in the one-attempt case-study fixture, pass its --release-re
|
|
|
495
512
|
the v0127 matched profile intentionally pins 0.12.7, not the newest release. If these identities differ,
|
|
496
513
|
select the matching runner/profile before starting rather than changing the pinned comparison or spending an arm.
|
|
497
514
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
515
|
+
Continue directly while your context makes that efficient. Delegate when the remaining work is clearer or
|
|
516
|
+
separation reduces effort; no routing-only investigation or mandatory delegation checkpoint. The configured Worker
|
|
517
|
+
can investigate, design, implement and check its assigned work; do not reduce its capability to mechanical edits.
|
|
518
|
+
When delegating, start useful work without redundant source/shell preparation. Call ${profile.toolPrefix}plan_units with concise units:
|
|
501
519
|
title, objective, read/write file or directory scopes, validation commands, and related requirement_ids
|
|
502
520
|
when splitting multiple requirements across multiple units; a single unit inherits all requirements when
|
|
503
521
|
requirement_ids is omitted. For operation missions, include execution with the actual
|
|
@@ -528,6 +546,7 @@ If peer source/tests expose a plausible related still-unhandled error, probe its
|
|
|
528
546
|
before choosing the fix; reuse adequate checks. Include this in the current author's objective/check
|
|
529
547
|
plan, not another unit or review round. Do not invent an exhaustive error matrix.
|
|
530
548
|
${MISSION_REPRESENTATION_ORACLE}
|
|
549
|
+
${MISSION_TYPE_FAMILY_ORACLE}
|
|
531
550
|
${MISSION_RETURN_CONTRACT}
|
|
532
551
|
${MISSION_MUTATION_CONTRACT}
|
|
533
552
|
${MISSION_REGRESSION_PLACEMENT}
|
|
@@ -659,8 +678,7 @@ Use bounded documented setup in ${TOOL_ENVIRONMENT}/; reuse, never delete.
|
|
|
659
678
|
User missing-only: installed/incompatible is not missing.
|
|
660
679
|
Per-call modes: check retained creation/use for captured vs current settings.
|
|
661
680
|
Tests: existing suites/subtests or distinctive regression entry names.
|
|
662
|
-
${WORKER_VALIDATION_WORKFLOW}
|
|
663
|
-
No fake success.
|
|
681
|
+
${WORKER_VALIDATION_WORKFLOW}No fake success.
|
|
664
682
|
Parent: independent Review after return, not before execution; no review-before-commit gate.
|
|
665
683
|
Do not spawn nested subagents, amend, push/publish. Return changes, command/exit/elapsed,
|
|
666
684
|
rerun reasons, unresolved/untested behavior; no proof doc.
|