sortie-dogs 0.13.12 → 0.13.14

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 CHANGED
@@ -19,8 +19,8 @@ evidence-backed result, not more agents for their own sake.
19
19
  [![Node.js](https://img.shields.io/node/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
20
20
  [![MIT License](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
21
21
 
22
- **Current release: [v0.13.12](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.12)**
23
- · [Release notes](docs/release-v0.13.12.md)
22
+ **Current release: [v0.13.14](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.14)**
23
+ · [Release notes](docs/release-v0.13.14.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)
@@ -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.13.12
60
+ npm install --save-dev sortie-dogs@0.13.14
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.13.12
89
+ npm install --save-dev sortie-dogs@0.13.14
90
90
  npx --no-install sortie-dogs init .
91
91
  ```
92
92
 
@@ -188,7 +188,7 @@ known expense. Official local evaluation and leaderboard acceptance are separate
188
188
  **8/23 (34.8%)**, versus v0.12.25's 7/23; all seven prior resolutions retained, no empty patches or
189
189
  official evaluation errors. [Fixed conditions, stopped-run accounting and costs](docs/benchmarks/swebench-lite-history.md#v0131-dev23-2026-09-30).
190
190
 
191
- All scores belong to their fixed candidates, **not v0.13.12**. SWE-bench remains an optional
191
+ All scores belong to their fixed candidates, **not v0.13.14**. SWE-bench remains an optional
192
192
  measurement, not a release gate. Different budgets, models and conditions are not controlled comparisons.
193
193
 
194
194
  ## Configuration
@@ -211,7 +211,7 @@ Install the desired package version and rerun **your host's** initializer:
211
211
 
212
212
  An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
213
213
  update it. [Global/update instructions](docs/configuration.md#global-availability).
214
- The current Mission asset marker is `0.13.12-validation-paths-v1`; the unchanged Codex skill marker is
214
+ The current Mission asset marker is `0.13.14-mission-recovery-v1`; the unchanged Codex skill marker is
215
215
  `0.13.11-codex-skill-v2`. Installed markers alone do not prove a running host reloaded the new version.
216
216
 
217
217
  There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
@@ -227,7 +227,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
227
227
  [SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
228
228
  - **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
229
229
  [Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
230
- - **Releases:** [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) ·
230
+ - **Releases:** [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) ·
231
231
  [v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
232
232
 
233
233
  ## Community
@@ -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.13.12-validation-paths-v1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.13.14-mission-recovery-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;
@@ -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.13.12-validation-paths-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.13.14-mission-recovery-v1";
7
7
  export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
@@ -0,0 +1,73 @@
1
+ export interface PublicMissionContext {
2
+ id: string;
3
+ role: "user" | "assistant";
4
+ text: string;
5
+ tools: {
6
+ call_id?: string;
7
+ tool: string;
8
+ status?: string;
9
+ input?: unknown;
10
+ output?: string;
11
+ error?: string;
12
+ exit?: number;
13
+ time?: unknown;
14
+ }[];
15
+ }
16
+ export interface MissionContextSource {
17
+ session_id: string;
18
+ path: string;
19
+ sha256: string;
20
+ entries: number;
21
+ observed_at: string;
22
+ history_available: boolean;
23
+ }
24
+ export interface MissionContinuity {
25
+ sources: MissionContextSource[];
26
+ notes?: {
27
+ text: string;
28
+ session_id: string;
29
+ recorded_at: string;
30
+ }[];
31
+ }
32
+ /** Public task data only. Keep exact text/results in a lazy snapshot, not private reasoning. */
33
+ export declare function publicMissionContext(messages: readonly Record<string, unknown>[]): PublicMissionContext[];
34
+ /** Preserve earlier available entries when the host later returns a compacted/partial history. */
35
+ export declare function retainMissionContext(directory: string, sessionID: string, messages: readonly Record<string, unknown>[], previous?: MissionContextSource): Promise<{
36
+ source: MissionContextSource;
37
+ entries: PublicMissionContext[];
38
+ }>;
39
+ /** Small inline view, with explicit pointers to every omitted byte in the exact snapshot. */
40
+ export declare function missionContextView(source: MissionContextSource, entries: readonly PublicMissionContext[], suppliedRequestIDs?: readonly string[]): {
41
+ omitted_text_entries: number;
42
+ conversation: {
43
+ truncated?: boolean | undefined;
44
+ original_characters?: number | undefined;
45
+ id: string;
46
+ role: "user" | "assistant";
47
+ text: string;
48
+ }[];
49
+ omitted_tool_entries: number;
50
+ recent_tools: {
51
+ input: string | undefined;
52
+ output: string | undefined;
53
+ error: string | undefined;
54
+ truncated: boolean;
55
+ detail_ref: {
56
+ path: string;
57
+ message_id: string;
58
+ call_id: string | undefined;
59
+ };
60
+ call_id?: string;
61
+ tool: string;
62
+ status?: string;
63
+ exit?: number;
64
+ time?: unknown;
65
+ message_id: string;
66
+ }[];
67
+ session_id: string;
68
+ path: string;
69
+ sha256: string;
70
+ entries: number;
71
+ observed_at: string;
72
+ history_available: boolean;
73
+ };
@@ -0,0 +1,66 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5
+ /** Public task data only. Keep exact text/results in a lazy snapshot, not private reasoning. */
6
+ export function publicMissionContext(messages) {
7
+ return messages.flatMap(message => {
8
+ const info = record(message.info) ? message.info : message;
9
+ if ((info.role !== "user" && info.role !== "assistant") || typeof info.id !== "string" || !Array.isArray(message.parts))
10
+ return [];
11
+ const parts = message.parts.filter(record);
12
+ const text = parts.filter(part => part.type === "text" && part.synthetic !== true && typeof part.text === "string")
13
+ .map(part => part.text).join("\n");
14
+ const tools = parts.flatMap(part => {
15
+ if (part.type !== "tool" || typeof part.tool !== "string" || !record(part.state))
16
+ return [];
17
+ const state = part.state, metadata = record(state.metadata) ? state.metadata : {};
18
+ return [{ tool: part.tool, ...(typeof part.callID === "string" ? { call_id: part.callID } : {}),
19
+ ...(typeof state.status === "string" ? { status: state.status } : {}),
20
+ ...(state.input !== undefined ? { input: state.input } : {}),
21
+ ...(typeof state.output === "string" ? { output: state.output } : {}),
22
+ ...(typeof state.error === "string" ? { error: state.error } : {}),
23
+ ...(typeof metadata.exit === "number" ? { exit: metadata.exit } : {}),
24
+ ...(part.time !== undefined ? { time: part.time } : {}) }];
25
+ });
26
+ return text.trim() || tools.length ? [{ id: info.id, role: info.role, text, tools }] : [];
27
+ });
28
+ }
29
+ /** Preserve earlier available entries when the host later returns a compacted/partial history. */
30
+ export async function retainMissionContext(directory, sessionID, messages, previous) {
31
+ const current = publicMissionContext(messages);
32
+ const retained = previous ? await readFile(previous.path, "utf8")
33
+ .then(content => JSON.parse(content).entries).catch(() => undefined) : undefined;
34
+ const merged = new Map((retained ?? []).map(entry => [entry.id, entry]));
35
+ for (const entry of current)
36
+ merged.set(entry.id, entry);
37
+ const entries = [...merged.values()];
38
+ const content = JSON.stringify({ session_id: sessionID, entries }, null, 2) + "\n";
39
+ const sha256 = createHash("sha256").update(content).digest("hex");
40
+ const path = join(directory, `context-${sha256}.json`);
41
+ if (!retained || previous?.sha256 !== sha256) {
42
+ await mkdir(directory, { recursive: true });
43
+ await writeFile(path, content);
44
+ }
45
+ return { source: { session_id: sessionID, path, sha256, entries: entries.length,
46
+ observed_at: new Date().toISOString(), history_available: current.length > 0 }, entries };
47
+ }
48
+ /** Small inline view, with explicit pointers to every omitted byte in the exact snapshot. */
49
+ export function missionContextView(source, entries, suppliedRequestIDs = []) {
50
+ const textEntries = entries.filter(entry => entry.text.trim() && !suppliedRequestIDs.includes(entry.id));
51
+ const selected = textEntries.slice(-8);
52
+ const tools = entries.flatMap(entry => entry.tools.map(tool => ({ message_id: entry.id, ...tool })));
53
+ const recentTools = tools.filter(tool => !tool.tool.startsWith("sortie_") && ["completed", "error"].includes(tool.status ?? "")).slice(-8);
54
+ return { ...source, omitted_text_entries: textEntries.length - selected.length,
55
+ conversation: selected.map(entry => ({ id: entry.id, role: entry.role, text: entry.text.slice(0, 4000),
56
+ ...(entry.text.length > 4000 ? { truncated: true, original_characters: entry.text.length } : {}) })),
57
+ omitted_tool_entries: tools.length - recentTools.length,
58
+ recent_tools: recentTools
59
+ .map(tool => {
60
+ const input = tool.input === undefined ? undefined : JSON.stringify(tool.input);
61
+ return { ...tool, input: input?.slice(0, 1000), output: tool.output?.slice(0, 1000), error: tool.error?.slice(0, 1000),
62
+ truncated: [input, tool.output, tool.error].some(value => value !== undefined && value.length > 1000),
63
+ detail_ref: { path: source.path, message_id: tool.message_id, call_id: tool.call_id } };
64
+ }),
65
+ };
66
+ }
@@ -1,5 +1,6 @@
1
1
  import { type OperatorPlan, type OperatorState, type OperatorTask, type OperatorRuntime } from "./operator-runtime.js";
2
2
  import { type RuntimeProfile } from "./runtime-profile.js";
3
+ import { type MissionContinuity } from "./mission-context.js";
3
4
  export { missionValidationCommand } from "./mission-validation-command.js";
4
5
  export declare const MISSION_REFERENCE = "SORTIE_MISSION_REF ";
5
6
  export declare const MISSION_REVIEW_REFERENCE = "SORTIE_MISSION_REVIEW_REF ";
@@ -192,6 +193,8 @@ export interface OperatorMission {
192
193
  requests: MissionRequest[];
193
194
  /** Prior public conversation context, not additional immutable requirements. */
194
195
  context?: MissionContext[];
196
+ /** Shared public context/working notes; observations, never extra requirements or acceptance. */
197
+ continuity?: MissionContinuity;
195
198
  /** Explicit continue deliveries, keyed by the original real turn. */
196
199
  steering?: {
197
200
  requestID: string;
@@ -321,6 +324,10 @@ export declare class OperatorMissionRuntime {
321
324
  constructor(projectRoot: string, profile: RuntimeProfile);
322
325
  private file;
323
326
  correctionReference(root: string, reviewIdentity: string): string;
327
+ /** Stage context for a proposed handoff; publish Mission metadata only after plan preparation succeeds. */
328
+ contextSnapshot(state: OperatorMission, sessionID: string, messages: readonly Record<string, unknown>[], notes?: string): Promise<OperatorMission>;
329
+ retainContext(root: string, sessionID: string, messages: readonly Record<string, unknown>[], notes?: string): Promise<OperatorMission>;
330
+ handoffContext(state: OperatorMission): Promise<Record<string, unknown>>;
324
331
  private load;
325
332
  /** Recover non-replacement continuations only; an explicit replacement must link the current cancelled run. */
326
333
  private loadMission;
@@ -6,6 +6,7 @@ import { parseOperatorPlan } from "./operator-runtime.js";
6
6
  import { profileAgent } from "./runtime-profile.js";
7
7
  import { normalizeCommand } from "../plugin/gate.js";
8
8
  import { missionValidationCommand } from "./mission-validation-command.js";
9
+ import { retainMissionContext, missionContextView } from "./mission-context.js";
9
10
  export { missionValidationCommand } from "./mission-validation-command.js";
10
11
  const digest = (value) => createHash("sha256").update(value).digest("hex");
11
12
  const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
@@ -184,6 +185,47 @@ export class OperatorMissionRuntime {
184
185
  correctionReference(root, reviewIdentity) {
185
186
  return JSON.stringify({ path: this.file(root), field: "corrections[]", review_identity: reviewIdentity });
186
187
  }
188
+ /** Stage context for a proposed handoff; publish Mission metadata only after plan preparation succeeds. */
189
+ async contextSnapshot(state, sessionID, messages, notes) {
190
+ const next = structuredClone(state);
191
+ const previous = next.continuity?.sources.find(source => source.session_id === sessionID);
192
+ const snapshot = await retainMissionContext(join(this.projectRoot, this.profile.stateDirectory, "missions"), sessionID, messages, previous);
193
+ next.continuity ??= { sources: [] };
194
+ next.continuity.sources = [...next.continuity.sources.filter(source => source.session_id !== sessionID), snapshot.source];
195
+ if (typeof notes === "string" && notes.trim())
196
+ next.continuity.notes = [
197
+ ...(next.continuity.notes ?? []).filter(note => note.session_id !== sessionID),
198
+ { text: notes, session_id: sessionID, recorded_at: new Date().toISOString() }
199
+ ];
200
+ return next;
201
+ }
202
+ async retainContext(root, sessionID, messages, notes) {
203
+ return this.serial(root, async () => {
204
+ const state = await this.loadMission(root);
205
+ if (!state)
206
+ throw new Error("mission-missing");
207
+ const next = await this.contextSnapshot(state, sessionID, messages, notes);
208
+ await this.save(this.file(root), next);
209
+ return next;
210
+ });
211
+ }
212
+ async handoffContext(state) {
213
+ const sources = await Promise.all((state.continuity?.sources ?? []).map(async (source) => {
214
+ try {
215
+ return missionContextView(source, JSON.parse(await readFile(source.path, "utf8")).entries, source.session_id === state.root ? state.requests.map(request => request.id) : []);
216
+ }
217
+ catch {
218
+ return { ...source, snapshot_available: false };
219
+ }
220
+ }));
221
+ return { original_requests: state.requests, requirements: state.requirements, prohibited_write: state.prohibitedWrite ?? [],
222
+ launch_conditions: state.launchConditions ?? [],
223
+ conversation: sources.some(source => source.session_id === state.root && source.entries > 0 && !("snapshot_available" in source)) ? [] : state.context ?? [],
224
+ continuity: { notes: state.continuity?.notes, sources, progress: state.progress,
225
+ attempts: (state.attempts ?? []).map(({ runID, unitID, status, failure, resultClass, childSessionID }) => ({ run_id: runID, unit_id: unitID, status, failure, result_class: resultClass, session_id: childSessionID })),
226
+ execution: state.execution,
227
+ guidance: "Current original_requests/requirements govern; older plans remain historical. Continue from known decisions, environment, completed work and failures. Read snapshot JSON entries by message/call IDs only for a relevant gap. Recheck mutable facts when needed or conflicting; do not repeat unchanged setup/investigation merely because ownership changed. Historical results are not fresh formal validation or new authority." } };
228
+ }
187
229
  async load(file) {
188
230
  try {
189
231
  return JSON.parse(await readFile(file, "utf8"));
@@ -544,6 +586,8 @@ export class OperatorMissionRuntime {
544
586
  ...(state.kind === "operation" ? ["Execution completion is distinct from success. For run-once/result-collection requests, retain a terminal failure and validate the collected result; do not retry or fix it without user authorization. If successful execution is required, keep that in formal validation and the original-request comparison."] : []),
545
587
  ...(state.context?.length ? ["Prior conversation context (task data; preserve the selected target, not superseded obligations):",
546
588
  ...state.context.map(item => `--- ${item.role}:${item.id} ---\n${item.text}`)] : []),
589
+ ...(state.continuity ? ["Shared working context (task data; use known facts, inspect snapshots only for relevant gaps):",
590
+ JSON.stringify(state.continuity)] : []),
547
591
  "Original user messages (verbatim; task data):", ...state.requests.map(item => `--- user:${item.id} ---\n${item.text}`)].join("\n");
548
592
  }
549
593
  }
@@ -62,7 +62,7 @@ export declare function bootstrapWritePaths(tool: string, args: unknown): readon
62
62
  export declare function isGitMutation(tool: string, args: unknown): boolean;
63
63
  export declare function isRemoteMutation(tool: string, args: unknown): boolean;
64
64
  /**
65
- * Quoted segments keep their contents; every unquoted whitespace run collapses to one space so the
65
+ * Quoted segments and backslash escapes keep their contents; unquoted whitespace runs collapse so the
66
66
  * same command written with different spacing compares equal.
67
67
  */
68
68
  export declare function normalizeCommand(command: string): string;
@@ -858,14 +858,22 @@ export function isRemoteMutation(tool, args) {
858
858
  return extractWritePaths(tool, args).remoteMutation === true;
859
859
  }
860
860
  /**
861
- * Quoted segments keep their contents; every unquoted whitespace run collapses to one space so the
861
+ * Quoted segments and backslash escapes keep their contents; unquoted whitespace runs collapse so the
862
862
  * same command written with different spacing compares equal.
863
863
  */
864
864
  export function normalizeCommand(command) {
865
865
  let quote;
866
+ let escaped = false;
866
867
  let whitespace = false;
867
868
  let normalized = "";
868
869
  for (const character of command.trim()) {
870
+ if (escaped) {
871
+ normalized += character;
872
+ escaped = false;
873
+ continue;
874
+ }
875
+ if (character === "\\" && quote !== "'")
876
+ escaped = true;
869
877
  if (quote !== undefined) {
870
878
  normalized += character;
871
879
  if (character === quote)
@@ -8306,6 +8306,8 @@ export const SortieDogsPlugin = async (input, options) => {
8306
8306
  const direct = await input.runtimeBridge?.directMissionExecution?.(actor);
8307
8307
  if (!direct || direct.root !== root)
8308
8308
  throw new Error("mission-direct-grant-stale");
8309
+ if (activeSessions.get(actor)?.inFlightCalls.size)
8310
+ throw new Error("mission-direct-tools-running");
8309
8311
  // Cold terminal recovery can happen before the actor's next tool/context hook.
8310
8312
  if (!goalReservations.has(direct.callID)) {
8311
8313
  const snapshot = await (await goalLedger(root)).readGoal();
@@ -8317,9 +8319,13 @@ export const SortieDogsPlugin = async (input, options) => {
8317
8319
  else
8318
8320
  throw new Error("mission-direct-reservation-unavailable");
8319
8321
  }
8320
- await settleGoalDispatch(direct.callID, { status: "error",
8321
- output: "Actual native Reviewer ended before its direct correction was validated.",
8322
- metadata: { sessionId: actor } }, undefined, []);
8322
+ // Empty successful evidence forces a failed disposition. Reuse the saved
8323
+ // validation observation (including a cold recovered exit) without claiming
8324
+ // a failed native child terminal for this still-active controller.
8325
+ await settleGoalDispatch(direct.callID, {
8326
+ output: "Direct unit stopped incomplete; no successful validation or Mission acceptance.",
8327
+ metadata: { sessionId: actor }
8328
+ }, undefined, []);
8323
8329
  await releaseWriteGate(actor);
8324
8330
  releaseSessionEnforcement(actor);
8325
8331
  },
@@ -73,7 +73,8 @@ export function observedMissionValidation(validation, childSessionID, history, n
73
73
  !record(part.state) || !["completed", "error"].includes(String(part.state.status)) || !record(part.state.input) ||
74
74
  typeof part.state.input.command !== "string")
75
75
  return [];
76
- const command = normalizeCommand(part.state.input.command);
76
+ // Match raw input first: whitespace normalization destroys Python heredoc boundaries.
77
+ const command = part.state.input.command;
77
78
  const exit = record(part.state.metadata) ? part.state.metadata.exit : undefined;
78
79
  const started = record(part.time) ? time(part.time.ran) : record(part.state.time) ? time(part.state.time.start) : null;
79
80
  const completed = record(part.time) ? time(part.time.completed) : record(part.state.time) ? time(part.state.time.end) : null;
@@ -99,7 +100,7 @@ export function observedMissionValidation(validation, childSessionID, history, n
99
100
  occurrence = (occurrence + 1) % declared.length;
100
101
  }
101
102
  const { directory: actual, ...visible } = attempt;
102
- return [{ ...visible, ...(directory && resolve(directory, actual ?? ".") !== resolve(directory)
103
+ return [{ ...visible, command: members.join(" && "), ...(directory && resolve(directory, actual ?? ".") !== resolve(directory)
103
104
  ? { directory: resolve(directory, actual ?? ".") } : {}), ...(members.length > 1 ? { observed_members: members } : {}) }];
104
105
  });
105
106
  // Retain early attempts and the latest checks if a Worker retried many times.
@@ -219,6 +219,18 @@ export function createProfiledPlugin(profile, assetVersion) {
219
219
  const result = payload(await session("messages", { path: { id }, query: { directory: input.directory } }));
220
220
  return Array.isArray(result) ? result.filter(record) : [];
221
221
  }
222
+ async function missionHistory(id) {
223
+ // Reuse the host's public paginated reader when present; bounded context is a usable fallback.
224
+ if (typeof nativeSession?.reviewMessages === "function") {
225
+ try {
226
+ const value = payload(await session("reviewMessages", { path: { id }, query: { directory: input.directory } }));
227
+ if (Array.isArray(value))
228
+ return value.filter(record);
229
+ }
230
+ catch { /* Context continuity must not add a history-service admission gate. */ }
231
+ }
232
+ return messages(id).catch(() => []);
233
+ }
222
234
  async function acceptanceValidationObservation(validation, child, notBefore) {
223
235
  if (!child)
224
236
  throw new Error("native-worker-history-session-unavailable");
@@ -989,6 +1001,14 @@ export function createProfiledPlugin(profile, assetVersion) {
989
1001
  const detail = "Operation result: execution-failed (result accepted; process did not succeed).";
990
1002
  return text.includes(detail) ? text : text.replace(/^((?:[ \t]*\r?\n)*[^\r\n]+)/u, `$1 ${detail}`);
991
1003
  }
1004
+ function compactMissionEvidence(packet, run, root) {
1005
+ if (run && run.runID === packet.run_id && Array.isArray(packet.units)) {
1006
+ const validation = missionReviewValidation(run, operators.statePath(root));
1007
+ packet.units = packet.units.map((unit, index) => ({ ...unit, evidence: validation[index].evidence,
1008
+ evidence_details_ref: validation[index].details_ref }));
1009
+ }
1010
+ return packet;
1011
+ }
992
1012
  function missionDispatchPacket(mission, run) {
993
1013
  const packet = { ...missionPacket(mission, run), project_root: input.directory,
994
1014
  coordinator_dispatch: mission.dispatchOpen ? "active" : ["completed", "cancelled"].includes(mission.phase) ? "terminal" : "resumable" };
@@ -1500,12 +1520,7 @@ export function createProfiledPlugin(profile, assetVersion) {
1500
1520
  }
1501
1521
  if (view === "progress")
1502
1522
  return JSON.stringify(missionProgress(mission, run, budget, packet));
1503
- if (view !== "full" && run?.runID === mission.runID && Array.isArray(packet.units)) {
1504
- const validation = missionReviewValidation(run, operators.statePath(root));
1505
- packet.units = packet.units.map((unit, index) => ({ ...unit, evidence: validation[index].evidence,
1506
- evidence_details_ref: validation[index].details_ref }));
1507
- }
1508
- return JSON.stringify(packet);
1523
+ return JSON.stringify(view === "full" ? packet : compactMissionEvidence(packet, run, root));
1509
1524
  }
1510
1525
  if (root && context.sessionID !== root) {
1511
1526
  const owned = await operators.read(root);
@@ -2014,14 +2029,18 @@ export function createProfiledPlugin(profile, assetVersion) {
2014
2029
  return JSON.stringify({ status: "correction-validated",
2015
2030
  execution: "same-native-task", validation: direct.unit.unit.validation,
2016
2031
  next_action: 'Compare the original requirements, retained findings and affected public behavior in this same context, then finish THIS native Task with SELF_RECHECKED and self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. No handoff reread, second Task or Operator round trip. This is author self-recheck; only the actual successful native terminal can bind it, never this tool result.' });
2017
- return JSON.stringify({ ...missionPacket(await missions.required(root), await operators.required(root)),
2018
- next_action: `Continue remaining units, or call ${reviewMission} with actual risk tags. Independent Review and Operator final acceptance remain required.` });
2032
+ const run = await operators.required(root);
2033
+ // Full protected path arrays can make V2 truncate this one-line JSON to zero lines,
2034
+ // hiding both the successful transition and the next action. Keep measured evidence
2035
+ // and durable detail references, as default status already does; never rewrite proof.
2036
+ return JSON.stringify({ ...compactMissionEvidence(missionPacket(await missions.required(root), run), run, root),
2037
+ next_action: `This direct unit is validated; do not repeat passed checks or finish this settled unit again. Continue remaining units with ${startDirectUnit}, or call ${reviewMission} with actual risk tags. Independent Review and Operator final acceptance remain required.` });
2019
2038
  });
2020
2039
  } };
2021
2040
  const stringList = { type: "array", items: { type: "string" } };
2022
2041
  const missionUnitSchema = { type: "object", additionalProperties: false,
2023
- properties: { title: { type: "string" }, objective: { type: "string", description: "Target or corrective delta, about 2000 characters. Do not copy the original request. Longer authored instructions are preserved separately without a retry gate." },
2024
- read: { ...stringList, description: "Inputs that affect validation, not an allowlist for observation. Do not include whole live session/database/log trees just to inspect them." }, write: stringList,
2042
+ properties: { title: { type: "string" }, objective: { type: "string", description: "Target or corrective delta, about 2000 characters. Do not copy the original request. Carry the remaining action, not a routine reconfirmation of supplied facts, setup or instructions. References are available for relevant gaps, not mandatory rereads. Longer authored instructions are preserved separately without a retry gate." },
2043
+ read: { ...stringList, description: "Known inputs that affect validation, not an allowlist or a checklist to reread. Avoid guessed AGENTS.md paths and whole live session/database/log trees just to inspect them; reuse already supplied instructions and known context." }, write: stringList,
2025
2044
  validation: { ...stringList, description: "Meaningful commands using observed repository test paths and runner conventions, not guessed filenames. Reuse already discovered recipes; when unknown, locate the relevant test or repository script with ordinary read/search before declaring it. This is planning guidance, not new approval or proof of execution; native checks still establish acceptance." },
2026
2045
  validation_cwd: { type: "object", additionalProperties: { type: "string" }, description: "Exact cwd per declared command; omitted commands use project_root. Registration-only correction of your active direct unit keeps the same admission and budget and requires new native checks, not historical proof." },
2027
2046
  requirement_ids: { ...stringList, description: "Related requirement IDs. A single unit inherits all requirements when omitted; specify coverage when splitting work across units." } },
@@ -2043,10 +2062,13 @@ export function createProfiledPlugin(profile, assetVersion) {
2043
2062
  const missionExecutionSchema = { type: "object", properties: { commands: stringList, directory: { type: "string" } },
2044
2063
  required: ["commands", "directory"], additionalProperties: false,
2045
2064
  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 };
2065
+ const handoffContextSchema = { type: "string", "x-sortie-optional": true,
2066
+ 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." };
2046
2067
  tools[startMission] = { description: `Operator: save current requirements; the host retains the original request verbatim. With a meaningful formal check known from user, project or task context and one useful unit, include unit NOW (and execution for an operation) and dispatch its Worker directly; plan_units remains available after start. A literal command in the original request is not required. Worker owns investigation/edit/check/requested commit before independent Review; no routine preparation or commit-only handoff. ${MISSION_GIT_SCOPE} Unknown checks or real unit decomposition use Coordinator. 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.`,
2047
2068
  args: { requirements: { ...stringList, minItems: 1, maxItems: 64 },
2048
2069
  unit: { ...missionUnitSchema, description: "When the meaningful check and single-unit scope are already known, include this unit now. Returns its configured Worker directly, combining start_mission and plan_units without another model round trip.", "x-sortie-optional": true },
2049
2070
  execution: missionExecutionSchema,
2071
+ handoff_context: handoffContextSchema,
2050
2072
  confirmed_conditions: conditionsSchema,
2051
2073
  prohibited_write: { ...stringList, description: "Only explicit path prohibitions from the user or applicable instructions. Never infer a parent glob from project/repository names, semantic 'do not modify the product', or the complement of estimated unit.write. Preserve the authorized clone and exact prohibited paths; semantic constraints stay in requirements.", "x-sortie-optional": true },
2052
2074
  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 },
@@ -2092,11 +2114,13 @@ export function createProfiledPlugin(profile, assetVersion) {
2092
2114
  return JSON.stringify({ ...relocated, budget: await control.currentBudget(context.sessionID) });
2093
2115
  }
2094
2116
  const cancelled = await operators.read(context.sessionID);
2117
+ const history = await missionHistory(context.sessionID);
2095
2118
  let mission = await missions.start(context.sessionID, requirements, args.intent === "replace" || args.intent === "new", {
2096
2119
  kind: args.kind === "operation" ? "operation" : "implementation", executionHost: input.executionHost,
2097
- context: missionConversationContext(await messages(context.sessionID)),
2120
+ context: missionConversationContext(history),
2098
2121
  ...(args.intent === "replace" && cancelled?.phase === "cancelled" ? { cancelledRunID: cancelled.runID } : {}),
2099
2122
  });
2123
+ mission = await missions.retainContext(context.sessionID, context.sessionID, history, args.handoff_context);
2100
2124
  const prohibited = args.prohibited_write;
2101
2125
  if (prohibited !== undefined) {
2102
2126
  if (!Array.isArray(prohibited) || !prohibited.every(path => typeof path === "string"))
@@ -2298,7 +2322,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2298
2322
  const commands = [...new Set(execution.commands.map(missionValidationCommand).map(normalizeCommand))];
2299
2323
  return { commands, directory: resolve(input.directory, execution.directory), observations: operation?.observations ?? [] };
2300
2324
  }
2301
- async function declareMissionUnits(root, actor, mission, raw, reason, execution, planningCallID) {
2325
+ async function declareMissionUnits(root, actor, mission, raw, reason, execution, planningCallID, handoffNotes) {
2302
2326
  await control.currentBudget(root);
2303
2327
  mission = await missions.required(root);
2304
2328
  let previous = await operators.read(root);
@@ -2452,13 +2476,16 @@ export function createProfiledPlugin(profile, assetVersion) {
2452
2476
  },
2453
2477
  }) : [];
2454
2478
  const dispatcher = actor === root ? undefined : { sessionID: actor, callID: mission.callID };
2455
- const context = { original_requests: mission.requests, requirements: mission.requirements, prohibited_write: mission.prohibitedWrite ?? [],
2456
- launch_conditions: mission.launchConditions ?? [] };
2479
+ const histories = await Promise.all([...new Set([root, actor])].map(async (id) => ({ id, messages: await missionHistory(id) })));
2480
+ for (const history of histories)
2481
+ mission = await missions.contextSnapshot(mission, history.id, history.messages, history.id === actor ? handoffNotes : undefined);
2482
+ const context = await missions.handoffContext(mission);
2457
2483
  const state = replanning && previous ? await operators.replanMission(root, previous.runID, plan, dispatcher, context)
2458
2484
  : await operators.prepareMission(root, plan, dispatcher, mission.supersededRunID, terminalChildren, mission.requirementsReplaced, context);
2459
2485
  await control.registerGoalDeclaration(root, state.units[0].task.prompt, true);
2460
2486
  control.enableUnits(root, state.units.filter(unit => unit.status === "pending").length);
2461
2487
  await missions.update(root, item => {
2488
+ item.continuity = mission.continuity;
2462
2489
  item.reviewScope = missionReviewScope(item.reviewScope, ...(previous && item.runID === previous.runID ? [previous] : []), state);
2463
2490
  if (item.runID !== state.runID)
2464
2491
  item.plans++;
@@ -2484,6 +2511,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2484
2511
  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. Keep investigation/edit/check/requested commit with the same author before independent Review, not a commit-only handoff. ${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. Reviewer FINDINGS stay with the same Reviewer for correction, formal validation and self-recheck.`,
2485
2512
  args: { units: { type: "array", minItems: 1, maxItems: 32, items: missionUnitSchema },
2486
2513
  execution: missionExecutionSchema,
2514
+ handoff_context: handoffContextSchema,
2487
2515
  reason: { type: "string", "x-sortie-optional": true },
2488
2516
  executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here without a Worker handoff. Default worker retains configured Worker routing.", "x-sortie-optional": true } }, execute: async (args, context) => {
2489
2517
  const { root, mission } = await missionAuthority(context.sessionID);
@@ -2491,7 +2519,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2491
2519
  throw new Error("mission-executor-invalid");
2492
2520
  try {
2493
2521
  return await serializeDispatchTransition(root, async () => {
2494
- const planned = await declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution, context.callID);
2522
+ const planned = await declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution, context.callID, args.handoff_context);
2495
2523
  return args.executor === "self" && record(JSON.parse(planned).task) ? startDirect(root, context.sessionID) : planned;
2496
2524
  });
2497
2525
  }
@@ -2643,13 +2671,26 @@ export function createProfiledPlugin(profile, assetVersion) {
2643
2671
  }
2644
2672
  const newFindings = review?.runID === run.runID && review.verdict === "findings" &&
2645
2673
  existing?.reviewIdentity !== missionReviewIdentity(mission);
2646
- const recovery = existing?.status === "failed" && run.phase === "awaiting-decision";
2674
+ let validationRecovery = false;
2675
+ if (existing?.status === "ready" && run.phase === "awaiting-acceptance") {
2676
+ try {
2677
+ await assertCorrectionValidation(run);
2678
+ }
2679
+ catch (error) {
2680
+ if (!(error instanceof Error) || !error.message.startsWith("mission-review-correction-validation-stale:"))
2681
+ throw error;
2682
+ // Read-only self-recheck cannot repair stale checks. Reuse the existing
2683
+ // same-author correction admission, without blessing historical evidence.
2684
+ validationRecovery = true;
2685
+ }
2686
+ }
2687
+ const recovery = existing !== undefined && ((existing.status === "failed" && run.phase === "awaiting-decision") || validationRecovery);
2647
2688
  if (existing && ((!newFindings && !recovery) || ["prepared", "running"].includes(existing.status)))
2648
2689
  return JSON.stringify({ status: `correction-${existing.status}`,
2649
2690
  ...(run.phase === "prepared" ? { task: operators.nextWorkerTask(run) } : {}),
2650
2691
  next_action: "Continue only the existing correction Task. Ready requires explicit SAME-author native self-recheck; only residual concrete Major risk requires a different Reviewer. Failure retains the candidate and cumulative spend." });
2651
2692
  if (!run.units.some(unit => unit.unit.write.length) ||
2652
- (!recovery && (run.phase !== "awaiting-acceptance" || review?.runID !== run.runID)) || review?.verdict !== "findings" ||
2693
+ (!recovery && (run.phase !== "awaiting-acceptance" || review?.runID !== run.runID)) || !review || (!validationRecovery && review.verdict !== "findings") ||
2653
2694
  !review.child || !review.initialPrompt || !review.result)
2654
2695
  throw new Error("mission-review-correction-unavailable");
2655
2696
  const original = mission.corrections?.at(-1);
@@ -2675,7 +2716,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2675
2716
  throw new Error("mission-review-correction-budget-unavailable");
2676
2717
  const reviewIdentity = missionReviewIdentity(mission);
2677
2718
  const plan = missionPlan(mission, [{ title: `Correct ${run.units[0].unit.title}`,
2678
- objective: `Correct all retained concrete Major/Medium findings in this SAME native conversation; preserve every original requirement. Exact findings are at correction_context.findings in the handoff; retained_findings_ref is optional lineage, not an instruction to reread/print the entire Mission or previous review prompt. Relevant search and focused diagnostics may widen normally; do not rediscover unchanged work. ${REVIEWER_VALIDATION_WORKFLOW} Retain the requested commit/clean boundary. THEN explicitly self-recheck the original requirements, ALL retained findings, correction and relevant impact in this Task. Correct known defects and revalidate affected checks before finishing. First line SELF_RECHECKED. Next line self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. The HOST binds current-validated to actual current source and fresh successful native checks after this exact prompt; never copy a pre-edit hash. Explain the actual comparison below. List unresolved concrete Major/Medium defects in unresolved_findings (no acceptance). Only concrete reachable residual Major risk uses residual_major:{"reachable_path":"...","consequence":"serious consequence"} and requires a different Reviewer. Tags/hashes/Medium/prose gaps alone never trigger it. This is author self-recheck, NOT independent PASS. Legacy CORRECTION_READY without self-recheck only permits a same-author read-only fallback Task, never acceptance.`,
2719
+ objective: `${validationRecovery ? "The prior correction completed, but its inherited validation is now stale. Preserve completed implementation, reproduction, original findings and historical checks; refresh only affected checks in this SAME author context. If the check recipe needs repair, use repair_review here as its existing author. Do not redo unchanged implementation/discovery or request another independent Review. " : ""}Correct all retained concrete Major/Medium findings in this SAME native conversation; preserve every original requirement. Exact findings are at correction_context.findings in the handoff; retained_findings_ref is optional lineage, not an instruction to reread/print the entire Mission or previous review prompt. Relevant search and focused diagnostics may widen normally; do not rediscover unchanged work. ${REVIEWER_VALIDATION_WORKFLOW} Retain the requested commit/clean boundary. THEN explicitly self-recheck the original requirements, ALL retained findings, correction and relevant impact in this Task. Correct known defects and revalidate affected checks before finishing. First line SELF_RECHECKED. Next line self_recheck: {"candidate":"current-validated","unresolved_findings":[],"residual_major":null}. The HOST binds current-validated to actual current source and fresh successful native checks after this exact prompt; never copy a pre-edit hash. Explain the actual comparison below. List unresolved concrete Major/Medium defects in unresolved_findings (no acceptance). Only concrete reachable residual Major risk uses residual_major:{"reachable_path":"...","consequence":"serious consequence"} and requires a different Reviewer. Tags/hashes/Medium/prose gaps alone never trigger it. This is author self-recheck, NOT independent PASS. Legacy CORRECTION_READY without self-recheck only permits a same-author read-only fallback Task, never acceptance.`,
2679
2720
  read: [...new Set([...(mission.reviewScope?.read ?? []), ...run.units.flatMap(unit => unit.unit.read)])],
2680
2721
  write: [...new Set([...(mission.reviewScope?.write ?? []), ...run.units.flatMap(unit => unit.unit.write)])],
2681
2722
  validation: run.units.flatMap(unit => unit.unit.validation),
@@ -2695,7 +2736,7 @@ export function createProfiledPlugin(profile, assetVersion) {
2695
2736
  const baseline = await missionReviewBaseline(input.directory);
2696
2737
  const dispatcher = mission.coordinator ? { sessionID: mission.coordinator, callID: mission.callID } : undefined;
2697
2738
  const prepared = await operators.prepareReviewerCorrection(root, run.runID, correctedPlan, authorID, reviewIdentity, retainedPlan.units[0].write, dispatcher, {
2698
- original_requests: mission.requests, requirements: mission.requirements, prohibited_write: mission.prohibitedWrite ?? [],
2739
+ ...await missions.handoffContext(mission),
2699
2740
  correction_context: { findings: recovery ? existing.findings : review.result,
2700
2741
  retained_findings_ref: JSON.parse(missions.correctionReference(root, reviewIdentity)),
2701
2742
  mission_id: mission.id, prior_run_id: run.runID, review_identity: reviewIdentity,
@@ -2860,15 +2901,20 @@ export function createProfiledPlugin(profile, assetVersion) {
2860
2901
  : { status: "skipped-low-risk" });
2861
2902
  });
2862
2903
  } };
2863
- async function assertCorrectionValidation(run) {
2904
+ async function correctionValidationFailure(run) {
2864
2905
  for (const unit of run.units) {
2865
2906
  if (!unit.reviewerCorrection || !unit.childSessionID)
2866
2907
  continue;
2867
2908
  const checked = await reviewerCorrectionValidationFresh(unit.unit.validation, unit.childSessionID, await messages(unit.childSessionID), Date.parse(unit.reviewerCorrection.admittedAt ?? run.createdAt), unit.reviewerCorrection.checks ?? [], input.directory, unit.directExecution?.validationCwd ?? unit.unit.validation_cwd, unit.reviewerCorrection.validationRegisteredAt);
2868
2909
  if (!checked.ready)
2869
- throw new Error(checked.reason);
2910
+ return checked.reason;
2870
2911
  }
2871
2912
  }
2913
+ async function assertCorrectionValidation(run) {
2914
+ const reason = await correctionValidationFailure(run);
2915
+ if (reason)
2916
+ throw new Error(reason);
2917
+ }
2872
2918
  /** Shared inline/fallback proof: the exact admitted native prompt's actual successful terminal. */
2873
2919
  async function observeNativeSelfRecheck(root, mission, run, author, callID, promptID, admittedAt, expectedSource, suppliedHistory) {
2874
2920
  if (!promptID || !Number.isFinite(admittedAt) || mission.runID !== run.runID || run.phase !== "awaiting-acceptance")
@@ -2894,7 +2940,11 @@ export function createProfiledPlugin(profile, assetVersion) {
2894
2940
  const checked = missionSelfRecheckReport(text, expectedSource ?? source.fingerprint, true);
2895
2941
  if (!checked)
2896
2942
  return undefined;
2897
- await assertCorrectionValidation(run);
2943
+ // A stale/missing check is an incomplete self-recheck, not a native lifecycle
2944
+ // accounting error. Settle the real terminal so later prompts can repair it.
2945
+ // Explicit acceptance still requires assertCorrectionValidation.
2946
+ if (await correctionValidationFailure(run))
2947
+ return undefined;
2898
2948
  if (expectedSource !== undefined && source.fingerprint !== expectedSource)
2899
2949
  return undefined;
2900
2950
  return { runID: run.runID, source: source.fingerprint, candidateSource: source.candidateFingerprint,
@@ -2912,27 +2962,39 @@ export function createProfiledPlugin(profile, assetVersion) {
2912
2962
  }
2913
2963
  tools[submitMission] = { description: "Coordinator: return only a completion candidate, a user-only decision, or a proven external/scope/budget blocker. Continue ordinary investigation, scope extensions and corrections yourself. Unit progress is published automatically without waking Operator.",
2914
2964
  args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema }, execute: async (args, context) => {
2915
- const { root, mission } = await missionAuthority(context.sessionID);
2916
- if (args.status === "ready") {
2917
- if (mission.kind === "operation" && !missionExecutionComplete(mission)) {
2918
- return JSON.stringify({ ...missionPacket(mission, await operators.read(root)), status: "operation-incomplete",
2919
- next_action: missionExecutionStatus(mission) === "running"
2920
- ? "The declared operation is already running. Inspect its native shell/progress; do not start another Worker or run. Wait for a terminal result, or report the existing run as blocked if its completion cannot be observed."
2921
- : "Continue the requested operation or submit its actual blocker. A successful setup/NO_START check does not authorize ready." });
2965
+ const { root } = await missionAuthority(context.sessionID);
2966
+ return serializeDispatchTransition(root, async () => {
2967
+ const mission = await missions.required(root);
2968
+ if (args.status === "ready") {
2969
+ if (mission.kind === "operation" && !missionExecutionComplete(mission)) {
2970
+ return JSON.stringify({ ...missionPacket(mission, await operators.read(root)), status: "operation-incomplete",
2971
+ next_action: missionExecutionStatus(mission) === "running"
2972
+ ? "The declared operation is already running. Inspect its native shell/progress; do not start another Worker or run. Wait for a terminal result, or report the existing run as blocked if its completion cannot be observed."
2973
+ : "Continue the requested operation or submit its actual blocker. A successful setup/NO_START check does not authorize ready." });
2974
+ }
2975
+ const run = await operators.required(root);
2976
+ if (run.phase !== "awaiting-acceptance")
2977
+ throw new Error("mission-units-incomplete");
2978
+ await assertMissionReview(root, mission);
2922
2979
  }
2923
- const run = await operators.required(root);
2924
- if (run.phase !== "awaiting-acceptance")
2925
- throw new Error("mission-units-incomplete");
2926
- await assertMissionReview(root, mission);
2927
- }
2928
- const updated = await missions.update(root, state => {
2929
- state.phase = "submitted";
2930
- state.submission = { status: args.status, summary: args.summary };
2980
+ else if (args.status === "blocked" || args.status === "needs-decision") {
2981
+ // This owner is stopping its direct work, not validating it. Keep the
2982
+ // failed checks/candidate and settle its existing reservation once.
2983
+ const direct = await activateDirect(context.sessionID);
2984
+ if (direct)
2985
+ await control.failDirectUnit(root, context.sessionID);
2986
+ }
2987
+ const updated = await missions.update(root, state => {
2988
+ state.phase = "submitted";
2989
+ state.submission = { status: args.status, summary: args.summary };
2990
+ });
2991
+ const submittedRun = await operators.read(root);
2992
+ return JSON.stringify({ acceptance_summary: await missionAcceptanceSummary(updated, submittedRun, operators, acceptanceValidationObservation),
2993
+ ...compactMissionEvidence(missionPacket(updated, submittedRun), submittedRun, root),
2994
+ next_action: args.status === "ready"
2995
+ ? "Return this result and acceptance_summary to Operator now. Compare the original requests with the candidate and actual evidence; inspect concrete gaps only, then complete_mission if satisfied. No routine archive search or full source reread. Completion still requires its final comparison and complete_mission receipt."
2996
+ : "Return the saved blocker or user-only decision to Operator now. This is incomplete delivery, not acceptance; retain the candidate, failed checks and cumulative spend. Do not repeat unchanged work or dispatch a replacement merely to finalize this result." });
2931
2997
  });
2932
- const submittedRun = await operators.read(root);
2933
- return JSON.stringify({ acceptance_summary: await missionAcceptanceSummary(updated, submittedRun, operators, acceptanceValidationObservation),
2934
- ...missionPacket(updated, submittedRun),
2935
- next_action: "Return this result and acceptance_summary to Operator now. Compare the original requests with the candidate and actual evidence; inspect concrete gaps only, then complete_mission if satisfied. No routine archive search or full source reread. Completion still requires its final comparison and complete_mission receipt." });
2936
2998
  } };
2937
2999
  tools[completeMission] = { description: "Operator only: after comparing the original request, source, actual checks and current review disposition (explicit native author self-recheck is not independent PASS), explicitly accept the whole mission. A collected terminal failure can satisfy a run-once/report request, not a request for successful execution. Returns operation outcome separately from the measured 🐾 Mission receipt; never treat Worker start, CORRECTION_READY or one passing check as completion.",
2938
3000
  args: {}, execute: async (_args, context) => {
@@ -3699,14 +3761,19 @@ export function createProfiledPlugin(profile, assetVersion) {
3699
3761
  admittedAt: inline.inlineReview.admittedAt, result: report.result, selfRecheck: report };
3700
3762
  });
3701
3763
  const latest = await missions.required(ownership.root);
3764
+ const validationFailure = !report && inline.status === "ready" && settled.phase === "awaiting-acceptance"
3765
+ ? await correctionValidationFailure(settled) : undefined;
3702
3766
  output.output = JSON.stringify({ status: report ? "review-correction-recorded" : "review-correction-incomplete",
3703
3767
  reviewer_report: raw, independent: false,
3768
+ ...(validationFailure ? { reason: validationFailure } : {}),
3704
3769
  acceptance_summary: await missionAcceptanceSummary(latest, settled, operators, acceptanceValidationObservation),
3705
3770
  next_action: report && !report.unresolvedFindings.length && !report.residualMajor
3706
3771
  ? `Host already recorded source, formal checks, delivery and actual native author self-recheck. Compare the original request with this candidate, then ${current.coordinator ? submitMission : completeMission}; no routine operator_status, new review or validation Worker.`
3707
- : !report && inline.status === "ready" && settled.phase === "awaiting-acceptance"
3708
- ? `Formal correction validation remains current, but the actual native author self-recheck is missing/failed. No acceptance. Call ${reviewMission} only for the missing read-only self-recheck in the SAME author/context, without rerunning successful checks or requesting another Reviewer.`
3709
- : `No acceptance. Continue the SAME author through ${repairReview} for actual defects; only a concrete residual Major risk after self-recheck calls for a different Reviewer.` });
3772
+ : validationFailure
3773
+ ? `No acceptance: ${validationFailure}. Call ${repairReview} to refresh affected checks in the SAME author/context. Preserve completed work and historical evidence; do not redo unchanged implementation/discovery or request another independent Reviewer.`
3774
+ : !report && inline.status === "ready" && settled.phase === "awaiting-acceptance"
3775
+ ? `Formal correction validation remains current, but the actual native author self-recheck is missing/failed. No acceptance. Call ${reviewMission} only for the missing read-only self-recheck in the SAME author/context, without rerunning successful checks or requesting another Reviewer.`
3776
+ : `No acceptance. Continue the SAME author through ${repairReview} for actual defects; only a concrete residual Major risk after self-recheck calls for a different Reviewer.` });
3710
3777
  taskOwners.delete(request.callID);
3711
3778
  return;
3712
3779
  }
package/dist/plugin/v2.js CHANGED
@@ -511,7 +511,10 @@ async function registerV2Hooks(context, hooks, background, liveState) {
511
511
  };
512
512
  });
513
513
  for (const [name, definition] of Object.entries(hooks.tool ?? {})) {
514
- editor.add({ name, description: definition.description, input: toolSchema(definition.args), options: { codemode: false },
514
+ // The approved increase is discovered through execute/search. Direct-only registrations
515
+ // are absent from that catalog; retain the other controls' existing direct entrypoints.
516
+ editor.add({ name, description: definition.description, input: toolSchema(definition.args),
517
+ options: { codemode: name === "sortie_v010_extend_mission_budget" },
515
518
  execute: async (input, execution) => {
516
519
  await background.reconcile(String(execution.sessionID ?? ""));
517
520
  let content;
@@ -693,7 +696,7 @@ async function registerV2Hooks(context, hooks, background, liveState) {
693
696
  await reconcileCorrectionPermissions(String(event.sessionID));
694
697
  if (record(event.tools)) {
695
698
  const visible = {
696
- "dog-operator": ["start_mission", "plan_units", "start_direct_unit", "finish_direct_unit", "retry_mission_unit", "operator_next", "operator_status", "expand_unit", "review_mission", "repair_review", "complete_mission", "cancel_operator", "reflection"],
699
+ "dog-operator": ["start_mission", "plan_units", "start_direct_unit", "finish_direct_unit", "retry_mission_unit", "operator_next", "operator_status", "extend_mission_budget", "expand_unit", "review_mission", "repair_review", "complete_mission", "cancel_operator", "reflection"],
697
700
  "dogs-coordinator": ["plan_units", "start_direct_unit", "finish_direct_unit", "operator_next", "operator_status", "expand_unit", "review_mission", "repair_review", "submit_mission", "skip_mission_consultation", "retry_mission_unit", "rescue_mission_unit"],
698
701
  "dog-worker-v010": ["bind_write_gate", "release_write_gate", "operator_status", "expand_unit"],
699
702
  "dog-luna-worker-v010": ["bind_write_gate", "release_write_gate", "operator_status", "expand_unit"],
@@ -1,9 +1,11 @@
1
1
  import { type RuntimeProfile } from "./core/runtime-profile.ts";
2
2
  /** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
3
3
  export declare const TOOL_ENVIRONMENT = ".sortie-env";
4
+ /** Task-specific environment limits qualify general dependency setup guidance. */
5
+ export declare const MISSION_DEPENDENCY_WORKFLOW = "Honor original dependency limits in setup and unit objectives. If the user permits installs only for\nproven-missing packages, an installed but incompatible package is not missing: do not replace it or\ndescribe it as missing compatible tooling. General registry/compatible-version guidance does not widen\nthat task-specific limit. Reuse a supplied environment; create one only when the original request permits.\nOtherwise carry out authorized dependency repair normally. If required behavior remains unverified under\nthose limits, retain in-scope work and report the limitation, not successful verification.";
4
6
  /** Ordinary requested Git delivery uses source scope, not repository control-storage scope. */
5
7
  export declare const MISSION_GIT_SCOPE = "Requested git add <paths> (with optional --) and git commit -m ... are normal source-scope Git operations;\nthey do not require .git/** scope. Preserve explicit user ordering and host Git lifecycle; attempt supported\noperations and report actual denials, not inferred gaps. Stage actual changed deliverables only: permission\nfor an unchanged file or deleted untracked scratch does not require staging it. Repair a missing in-request\noutput via expand_unit, then continue the SAME Task; covered paths need no new grant or dispatch.";
6
- export declare const VALIDATION_WORKFLOW = "## Time-aware validation workflow\n\nOperator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,\nnot another objective copy. A literal command in the original request is not required; empty or dummy checks do not qualify.\nKeep any user/project-required broad validation for the final integrated candidate,\nnot every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.\nWorker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.\nFix failures with the smallest exercising check before starting a costly suite. Do not execute the entire\nformal validation list after each patch; run it in declared order when the unit candidate is stable.\nIf broad validation is already declared, defer it until focused checks pass and known edits are finished;\ndo not remove it, substitute a tiny check for it, or claim an unrun requirement passed.\nAfter a late fix, select checks for the affected inputs; repeat broad validation when the requested\ncontract or host evidence freshness requires it, not solely because any file or Worker changed.\nDocumentation-only changes do not automatically invalidate unrelated runtime/test results; retain the\nchecked candidate and show why reuse is valid under project and host rules. Never promote stale proof.\nBefore a costly check, state its purpose and known duration (unknown if unmeasured). In the concise return,\nreport command, scope, actual exit/elapsed time and any rerun reason; never invent savings or timings.\nReview evidence gaps need original excerpts, not another patch or full test. Keep autonomous correction,\nrequired review, accepted criteria and cumulative budget intact; this workflow adds no approval or denial.\n";
8
+ export declare const VALIDATION_WORKFLOW = "## Time-aware validation workflow\n\nOperator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,\nnot another objective copy. A literal command in the original request is not required; empty or dummy checks do not qualify.\nFor a concrete public reproduction, carry its entrypoint and relevant input into the unit objective and formal reproduction check.\nPreserve its fixture layout and working directory. A standalone example project belongs in its own fixture root within allowed paths;\ninvoke from there with the active environment, not the source checkout root where import paths or configuration may mask the failure.\nDerive the shell recipe without substituting lower-level library calls; those checks may supplement, never replace, the public entrypoint.\nReuse existing checks that already exercise it. If authorized setup cannot run it, retain useful work and report the verification gap, not a passing reproduction.\nFor direct work with an unknown reproduction recipe, resolve its flags, configuration and cwd with ordinary native diagnostics before formal registration.\nReuse an already observed failing baseline; do not rerun it merely to plan. A command that never exercises the reported path is not a passing reproduction.\nUse existing project configuration within the original path constraints. Known recipes and Worker-owned investigation need no extra preparation or unit.\nKeep any user/project-required broad validation for the final integrated candidate,\nnot every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.\nWorker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.\nFix failures with the smallest exercising check before starting a costly suite. Do not execute the entire\nformal validation list after each patch; run it in declared order when the unit candidate is stable.\nIf broad validation is already declared, defer it until focused checks pass and known edits are finished;\ndo not remove it, substitute a tiny check for it, or claim an unrun requirement passed.\nAfter a late fix, select checks for the affected inputs; repeat broad validation when the requested\ncontract or host evidence freshness requires it, not solely because any file or Worker changed.\nDocumentation-only changes do not automatically invalidate unrelated runtime/test results; retain the\nchecked candidate and show why reuse is valid under project and host rules. Never promote stale proof.\nBefore a costly check, state its purpose and known duration (unknown if unmeasured). In the concise return,\nreport command, scope, actual exit/elapsed time and any rerun reason; never invent savings or timings.\nReview evidence gaps need original excerpts, not another patch or full test. Keep autonomous correction,\nrequired review, accepted criteria and cumulative budget intact; this workflow adds no approval or denial.\n";
7
9
  /** Worker-only projection; planning/review authorities retain the complete shared workflow. */
8
10
  export declare const WORKER_VALIDATION_WORKFLOW = "Batch edits, reconcile requirements/diff, run focused tests, then every registered formal check exactly in order:\nseparate foreground native shell calls, no extra tee, redirect or wrapper. Diagnostics are not formal evidence.\nRerun affected checks and required broad checks when contract/freshness requires; never drop required validation or claim stale/unrun proof.\n";
9
11
  /** Correction uses the inherited command identities; diagnostics do not acquire formal evidence. */
@@ -11,6 +13,8 @@ export declare const REVIEWER_VALIDATION_WORKFLOW = "Run inherited formal comman
11
13
  export declare function missionOperatorContent(profile: RuntimeProfile, version: string): string;
12
14
  /** Keep representation oracles consistent in direct execution, Worker and Review. */
13
15
  export declare const MISSION_REPRESENTATION_ORACLE = "Same-type representation, not name/visitor analogy; resolve conflicts.";
16
+ /** Coordinator authors and plans regressions without adding another validation/review lane. */
17
+ 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.";
14
18
  /** Shared by the installed Reviewer and its host-generated mission prompt. */
15
19
  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.\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.";
16
20
  /** Mission Reviewer uses ordinary host read/search permissions, not a supplied-packet-only protocol. */
@@ -3,6 +3,13 @@ import { STRATEGY_TRIGGERS, SOURCE_REVIEW_RISK_TAGS } from "./core/consultation.
3
3
  import { SCOUT_EVIDENCE_CODES } from "./core/scout-contract.js";
4
4
  /** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
5
5
  export const TOOL_ENVIRONMENT = ".sortie-env";
6
+ /** Task-specific environment limits qualify general dependency setup guidance. */
7
+ export const MISSION_DEPENDENCY_WORKFLOW = `Honor original dependency limits in setup and unit objectives. If the user permits installs only for
8
+ proven-missing packages, an installed but incompatible package is not missing: do not replace it or
9
+ describe it as missing compatible tooling. General registry/compatible-version guidance does not widen
10
+ that task-specific limit. Reuse a supplied environment; create one only when the original request permits.
11
+ Otherwise carry out authorized dependency repair normally. If required behavior remains unverified under
12
+ those limits, retain in-scope work and report the limitation, not successful verification.`;
6
13
  /** Ordinary requested Git delivery uses source scope, not repository control-storage scope. */
7
14
  export const MISSION_GIT_SCOPE = `Requested git add <paths> (with optional --) and git commit -m ... are normal source-scope Git operations;
8
15
  they do not require .git/** scope. Preserve explicit user ordering and host Git lifecycle; attempt supported
@@ -13,6 +20,14 @@ export const VALIDATION_WORKFLOW = `## Time-aware validation workflow
13
20
 
14
21
  Operator/Coordinator: put the known meaningful formal check from the user, project or task context in validation,
15
22
  not another objective copy. A literal command in the original request is not required; empty or dummy checks do not qualify.
23
+ For a concrete public reproduction, carry its entrypoint and relevant input into the unit objective and formal reproduction check.
24
+ Preserve its fixture layout and working directory. A standalone example project belongs in its own fixture root within allowed paths;
25
+ invoke from there with the active environment, not the source checkout root where import paths or configuration may mask the failure.
26
+ Derive the shell recipe without substituting lower-level library calls; those checks may supplement, never replace, the public entrypoint.
27
+ Reuse existing checks that already exercise it. If authorized setup cannot run it, retain useful work and report the verification gap, not a passing reproduction.
28
+ For direct work with an unknown reproduction recipe, resolve its flags, configuration and cwd with ordinary native diagnostics before formal registration.
29
+ Reuse an already observed failing baseline; do not rerun it merely to plan. A command that never exercises the reported path is not a passing reproduction.
30
+ Use existing project configuration within the original path constraints. Known recipes and Worker-owned investigation need no extra preparation or unit.
16
31
  Keep any user/project-required broad validation for the final integrated candidate,
17
32
  not every implementation unit. Do not add a full suite merely as a precaution or create a testing agent.
18
33
  Worker: reproduce, batch the related edits, inspect the complete diff, then run the focused checks.
@@ -89,6 +104,8 @@ ${controls(profile, ["start_mission", "plan_units", "start_direct_unit", "finish
89
104
  You are Operator, the user-facing strategic authority. Preserve every current requirement, prohibition,
90
105
  quality threshold and explicit model/budget choice. Follow AGENTS.md and use the user's language.
91
106
 
107
+ ${MISSION_DEPENDENCY_WORKFLOW}
108
+
92
109
  1. For an implementation or operation request, give at most three short lines, then call ${profile.toolPrefix}start_mission
93
110
  with a few concise one-line requirements including negative constraints. The host saves the original
94
111
  user message verbatim and recent public context and generates IDs; do not copy it or author contracts, hashes or a proposal.
@@ -233,6 +250,10 @@ existing user authorization and project gates; npm publication remains manual.
233
250
  }
234
251
  /** Keep representation oracles consistent in direct execution, Worker and Review. */
235
252
  export const MISSION_REPRESENTATION_ORACLE = `Same-type representation, not name/visitor analogy; resolve conflicts.`;
253
+ /** Coordinator authors and plans regressions without adding another validation/review lane. */
254
+ export const MISSION_REGRESSION_PLACEMENT = `Prefer an existing relevant test suite/subtest or fixture when it can express the regression assertion.
255
+ If a new test module is needed, prefer a distinctive behavior-specific name over automatically mirroring the implementation module.
256
+ Preserve established assertions and project test discovery. This is placement guidance, not another test, unit or review requirement.`;
236
257
  /** Shared by the installed Reviewer and its host-generated mission prompt. */
237
258
  export const MISSION_BEHAVIOR_REVIEW = `Establish the test oracle independently of the patch: tests added with the implementation are claims to
238
259
  review, not established API behavior. For a changed failure path, compare a pre-existing analogous public
@@ -429,6 +450,12 @@ run/grade commands and working directory. Setup, launch and result collection no
429
450
  do not forbid execution while assigning that Worker the requirement to execute.
430
451
  Write objective as a target or corrective delta: aim for 2000 characters.
431
452
  The Worker reads the original requests verbatim from the handoff once; do not copy them into objective.
453
+ Public conversation, recent tool results and Mission progress are supplied automatically in continuity.
454
+ When useful, include handoff_context in start_mission/plan_units: current intent and rationale, known environment,
455
+ completed setup/checks, failed approaches and remaining work with existing references. It is optional working
456
+ context, not a form or approval. Preserve useful knowledge instead of assigning unchanged discovery again.
457
+ Assign the remaining action. Reference paths support relevant gaps, not a mandatory reread checklist; avoid
458
+ routine environment/instruction reconfirmation and guessed AGENTS.md paths when that context is already supplied.
432
459
  Keep investigation, edits, formal checks and any requested commit in the same Worker before independent Review;
433
460
  do not invent a review-before-commit gate or a commit-only handoff. Preserve explicit user ordering.
434
461
  ${MISSION_GIT_SCOPE}
@@ -439,7 +466,14 @@ existing analogous source/tests for the expected contract when available. Avoid
439
466
  units just to restate the issue. A test of a neighboring name is not an adjacent check unless it runs
440
467
  the changed branch on a relevant different input; keep validation focused and do not require an extra
441
468
  test when the existing checks already exercise that boundary.
469
+ When changing an error fallback, distinguish already handled errors from errors the called operation
470
+ can still raise. Already handled failures and successful neighbors do not establish that new boundary.
471
+ Repeating the reported error with another input does not cover still-unhandled related errors.
472
+ If peer source/tests expose a plausible related still-unhandled error, probe its expected fallback
473
+ before choosing the fix; reuse adequate checks. Include this in the current author's objective/check
474
+ plan, not another unit or review round. Do not invent an exhaustive error matrix.
442
475
  ${MISSION_REPRESENTATION_ORACLE}
476
+ ${MISSION_REGRESSION_PLACEMENT}
443
477
  For read-only verification, use write: []; do not invent an output file or request write access to inputs.
444
478
  If declared build or tests create known generated paths, include those outputs in the initial write scope;
445
479
  do not add a separate setup unit just to prepare them.
@@ -452,8 +486,9 @@ Use absolute native paths for requested global installations or other external o
452
486
  a directory including a not-yet-created tree. These are execution/evidence scopes, not an additional
453
487
  permission grant: the host's native permissions still apply. Include the actual external input/output
454
488
  paths in read/write so validation and review observe them; do not substitute a repository symlink.
489
+ ${MISSION_DEPENDENCY_WORKFLOW}
455
490
  Keep all original requirements covered. Last validation command checks
456
- that unit. If your quick check shows repository-declared dependencies or the test runner are missing, keep
491
+ that unit. If your quick check shows repository-declared dependencies or the test runner are missing and setup is allowed, keep
457
492
  setup inside the first unit: declare its checks through the repository-local tool environment ${TOOL_ENVIRONMENT}/
458
493
  (for Python, ${TOOL_ENVIRONMENT}/bin/python -m pytest ...) and let that Worker create it. Never plan a separate setup
459
494
  unit or put ${TOOL_ENVIRONMENT}/ in a write scope. Host generates IDs, proof mapping, handoff, manifest and Task references. Dispatch the returned
@@ -549,20 +584,21 @@ mode: subagent
549
584
 
550
585
  Implement, test and requested commit in this Task.
551
586
  Reuse supplied AGENTS.md; for gaps prefer exact ancestor files/affected subtrees over parent globs.
552
- Read handoff_path in full first: task.objective, verbatim original_requests/unit_instruction, Mission constraints,
553
- coverage indices. Preserve user scope and ordering; prove assigned criteria, not Mission completion.
587
+ Read handoff_path in full first: task.objective, verbatim original_requests/unit_instruction, constraints/continuity,
588
+ coverage. Preserve user scope and ordering; prove assigned criteria, not Mission completion.
554
589
  Denied: reason/remedy. No routine manifest/goal/status/bind.
555
- Recovery: ${profile.toolPrefix}bind_write_gate with exact project_root and manifest_path=operation_manifest.
556
- Treat cwd/project_root and paths as opaque; never shorten or normalize segments.
557
- After compaction recover handoff; inspect diff/results before repeats.
590
+ ${profile.toolPrefix}bind_write_gate with exact project_root and manifest_path=operation_manifest.
591
+ cwd/project_root/paths: opaque; never shorten or normalize segments.
592
+ After compaction recover handoff; inspect diff/results.
558
593
  Read/search: existing permissions. Fix unit.write/outputs/checks
559
- here via ${profile.toolPrefix}expand_unit/contract updates and host repair diagnostics;
594
+ here via ${profile.toolPrefix}expand_unit/contract updates; host repair diagnostics;
560
595
  no extra approval, restart or delegation. Keep prohibitions, host Git lifecycle, cumulative budget.
561
596
  Requested add/commit needs source paths, not .git/** scope.
562
- Use pre-change test helpers as oracles, not new implementation/tests. Check
563
- result/error/state together, without a hypothetical exhaustive matrix. Keep reproduction entrypoint/input/layout; rerun or report why unverified.
597
+ Pre-change oracles, not new tests; check result/error/state. Fallback: probe other unhandled errors from peer source/tests.
598
+ No exhaustive matrix. Keep reproduction entrypoint/input/layout; rerun or report why unverified.
564
599
  ${MISSION_REPRESENTATION_ORACLE}
565
- Missing tooling: one documented bounded setup in ${TOOL_ENVIRONMENT}/; reuse, never delete.
600
+ Use bounded documented setup in ${TOOL_ENVIRONMENT}/; reuse, never delete.
601
+ User missing-only: installed/incompatible is not missing.
566
602
  Per-call modes: check retained creation/use for captured vs current settings.
567
603
  Tests: existing suites/subtests or distinctive regression entry names.
568
604
  ${WORKER_VALIDATION_WORKFLOW}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.13.12",
3
+ "version": "0.13.14",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",