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 +7 -7
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/mission-context.d.ts +73 -0
- package/dist/core/mission-context.js +66 -0
- package/dist/core/operator-mission.d.ts +7 -0
- package/dist/core/operator-mission.js +44 -0
- package/dist/plugin/gate.d.ts +1 -1
- package/dist/plugin/gate.js +9 -1
- package/dist/plugin/index.js +9 -3
- package/dist/plugin/mission-review.js +3 -2
- package/dist/plugin/profiled.js +111 -44
- package/dist/plugin/v2.js +5 -2
- package/dist/runtime-mission-assets.d.ts +5 -1
- package/dist/runtime-mission-assets.js +46 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,8 @@ 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.13.
|
|
23
|
-
· [Release notes](docs/release-v0.13.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
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.13.
|
|
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;
|
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.13.
|
|
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
|
}
|
package/dist/plugin/gate.d.ts
CHANGED
|
@@ -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;
|
|
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;
|
package/dist/plugin/gate.js
CHANGED
|
@@ -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;
|
|
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)
|
package/dist/plugin/index.js
CHANGED
|
@@ -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
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
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
|
-
|
|
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.
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2018
|
-
|
|
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: "
|
|
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(
|
|
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
|
|
2456
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
:
|
|
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
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
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
|
-
:
|
|
3708
|
-
? `
|
|
3709
|
-
:
|
|
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
|
-
|
|
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,
|
|
553
|
-
coverage
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
After compaction recover handoff; inspect diff/results
|
|
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
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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}
|