sortie-dogs 0.12.15 → 0.12.17
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 +23 -13
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/operator-mission.d.ts +45 -1
- package/dist/core/operator-mission.js +78 -11
- package/dist/core/operator-runtime.d.ts +1 -0
- package/dist/core/operator-runtime.js +12 -5
- package/dist/plugin/fast-lane.js +11 -3
- package/dist/plugin/index.js +40 -11
- package/dist/plugin/mission-review.d.ts +3 -2
- package/dist/plugin/mission-review.js +74 -7
- package/dist/plugin/profiled.js +130 -16
- package/dist/plugin/protected-snapshot.d.ts +3 -0
- package/dist/plugin/protected-snapshot.js +19 -4
- package/dist/plugin/sortie-debrief.d.ts +6 -0
- package/dist/plugin/sortie-debrief.js +24 -4
- package/dist/runtime-assets-v010.js +36 -51
- package/dist/runtime-mission-assets.js +41 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,19 +87,29 @@ goal; preserving quality while reducing unnecessary expensive work is.
|
|
|
87
87
|
|
|
88
88
|
### SWE-bench evaluation
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
Official SWE-bench Lite `dev` results on the same 23 public instances:
|
|
91
|
+
|
|
92
|
+
| Candidate / benchmark adapter | Official resolved | Empty patches | Run conditions | Details |
|
|
93
|
+
| --- | ---: | ---: | --- | --- |
|
|
94
|
+
| v0.10.6 candidate (`859c396`) | 4 / 23 (17.4%) | 5 | Four inference slots | [Handoff](docs/swebench-handoff-2026-09-21.md) |
|
|
95
|
+
| Frozen v0.10.14 build | 6 / 23 (26.1%) | — | Four slots; $1.50/task | [Per-task results](docs/benchmark-v0.10.14-dev23.md) |
|
|
96
|
+
| v0.12.8 (`e0f8cef` adapter) | 5 / 23 (21.7%) | 9 | Four slots; budget amended across two batches; 30-minute timeout | [Campaign](docs/swebench-v0128-dev23-2026-09-26.md) |
|
|
97
|
+
| v0.12.8 (`84ccdf1` main adapter) | 6 / 23 (26.1%) | 2 | Fresh 23-task run; four slots; 30-minute timeout | [Main-integrated run](docs/swebench-main-84ccdf1-dev23-2026-09-26.md) |
|
|
98
|
+
| v0.12.15 (`0c9690d`) | 5 / 23 (21.7%) | 3 | Fresh 23-task ext4 retry; four slots; 40-minute timeout | [Campaign](docs/swebench-v01215-dev23-2026-09-27.md) |
|
|
99
|
+
| v0.12.16 (`9b05a34` release; `b1a6c0e` runner) | 4 / 23 (17.4%) | 5 | Fresh 23-task run; eight slots; 40-minute timeout | [Campaign](docs/swebench-v01216-dev23-2026-09-27.md) |
|
|
100
|
+
|
|
101
|
+
Every row has 23 submitted official predictions; an empty patch counts against
|
|
102
|
+
the score, not as a missing evaluation. The v0.10.14 report does not separately
|
|
103
|
+
summarize empty patches. The v0.12.15 row is the separately approved fresh
|
|
104
|
+
run after an initial `/tmp` quota failure, not an additional score for that
|
|
105
|
+
failed attempt. Inference completion is **not** official resolution.
|
|
106
|
+
Budgets, runtime/adapter versions, and execution conditions changed between
|
|
107
|
+
campaigns, so this table is a history of observed results, not a controlled
|
|
108
|
+
head-to-head comparison or a general success-rate claim. The v0.10.14 run
|
|
109
|
+
estimated $15.75 in model cost and a 15.2-minute median agent runtime.
|
|
110
|
+
The v0.12.16 run is the first eight-slot inference score in this table;
|
|
111
|
+
five runners timed out, and this does not establish a model-quality regression
|
|
112
|
+
against runs with different concurrency and conditions.
|
|
103
113
|
|
|
104
114
|
Historical qualification references remain in [benchmark reference](docs/benchmark-reference.md).
|
|
105
115
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
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.12.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.12.17-review-cost-paths-v1";
|
|
7
7
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
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.12.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.12.17-review-cost-paths-v1";
|
|
@@ -8,11 +8,40 @@ export interface MissionRequest {
|
|
|
8
8
|
id: string;
|
|
9
9
|
text: string;
|
|
10
10
|
}
|
|
11
|
+
export interface MissionContext extends MissionRequest {
|
|
12
|
+
role: "user" | "assistant";
|
|
13
|
+
}
|
|
14
|
+
export interface MissionEvidenceExcerpt {
|
|
15
|
+
path: string;
|
|
16
|
+
offset: number;
|
|
17
|
+
limit: number;
|
|
18
|
+
}
|
|
19
|
+
export interface MissionExecution {
|
|
20
|
+
commands: string[];
|
|
21
|
+
directory: string;
|
|
22
|
+
observations: {
|
|
23
|
+
command: string;
|
|
24
|
+
directory: string;
|
|
25
|
+
callID: string;
|
|
26
|
+
sessionID: string;
|
|
27
|
+
startedAt: string;
|
|
28
|
+
completedAt?: string;
|
|
29
|
+
exit?: number;
|
|
30
|
+
status?: "completed" | "error";
|
|
31
|
+
outcome?: "not-started" | "execution-failed" | "executed";
|
|
32
|
+
result?: Record<string, unknown>;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
11
35
|
export interface OperatorMission {
|
|
12
36
|
version: "0.12";
|
|
13
37
|
id: string;
|
|
14
38
|
root: string;
|
|
15
39
|
requests: MissionRequest[];
|
|
40
|
+
/** Prior public conversation context, not additional immutable requirements. */
|
|
41
|
+
context?: MissionContext[];
|
|
42
|
+
kind?: "implementation" | "operation";
|
|
43
|
+
/** Native shell observations of the requested operation, separate from auxiliary checks. */
|
|
44
|
+
execution?: MissionExecution;
|
|
16
45
|
requirements: {
|
|
17
46
|
id: string;
|
|
18
47
|
text: string;
|
|
@@ -24,6 +53,8 @@ export interface OperatorMission {
|
|
|
24
53
|
callID: string | null;
|
|
25
54
|
dispatchOpen: boolean;
|
|
26
55
|
runID: string | null;
|
|
56
|
+
/** Git HEAD before this mission's first implementation unit, retained across replans and commits. */
|
|
57
|
+
reviewBaseline?: string;
|
|
27
58
|
/** A cancelled run replaced by a later real user turn; never reuse its acceptance or evidence. */
|
|
28
59
|
supersededRunID?: string;
|
|
29
60
|
plans: number;
|
|
@@ -42,6 +73,7 @@ export interface OperatorMission {
|
|
|
42
73
|
risk: string[];
|
|
43
74
|
source: string;
|
|
44
75
|
task: OperatorTask | null;
|
|
76
|
+
evidence?: MissionEvidenceExcerpt[];
|
|
45
77
|
requestFingerprint?: string;
|
|
46
78
|
verdict: "pending" | "PASS" | "findings" | "evidence-gaps" | "skipped-low-risk";
|
|
47
79
|
result?: string;
|
|
@@ -61,6 +93,15 @@ export declare const MISSION_EVIDENCE_GAP_REVIEW_LIMIT = 2;
|
|
|
61
93
|
export declare function missionReviewVerdict(text: string): "PASS" | "evidence-gaps" | "findings";
|
|
62
94
|
/** Whether the recorded review permits submission and acceptance of the current candidate. */
|
|
63
95
|
export declare function missionReviewAccepted(review: NonNullable<OperatorMission["review"]>): boolean;
|
|
96
|
+
export declare function missionExecutionStatus(mission: OperatorMission): "not-required" | "not-started" | "running" | "execution-failed" | "executed";
|
|
97
|
+
/** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
|
|
98
|
+
* not process success. Unstructured commands retain their native exit semantics. */
|
|
99
|
+
export declare function missionCommandOutcome(output: unknown, exit: unknown, status: unknown): {
|
|
100
|
+
outcome: "not-started" | "execution-failed" | "executed";
|
|
101
|
+
result?: Record<string, unknown>;
|
|
102
|
+
};
|
|
103
|
+
/** Bounded public text only: tool logs and private reasoning are not delegation context. */
|
|
104
|
+
export declare function missionConversationContext(messages: readonly Record<string, unknown>[]): MissionContext[];
|
|
64
105
|
/** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
|
|
65
106
|
export declare class OperatorMissionRuntime {
|
|
66
107
|
readonly projectRoot: string;
|
|
@@ -78,7 +119,10 @@ export declare class OperatorMissionRuntime {
|
|
|
78
119
|
read(root: string): Promise<OperatorMission | undefined>;
|
|
79
120
|
required(root: string): Promise<OperatorMission>;
|
|
80
121
|
capture(root: string, request: MissionRequest): Promise<void>;
|
|
81
|
-
start(root: string, requirements: unknown, replaceRequirements?: boolean
|
|
122
|
+
start(root: string, requirements: unknown, replaceRequirements?: boolean, options?: {
|
|
123
|
+
kind?: OperatorMission["kind"];
|
|
124
|
+
context?: MissionContext[];
|
|
125
|
+
}): Promise<OperatorMission>;
|
|
82
126
|
update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
|
|
83
127
|
/** Keep host-accepted criteria ahead of new text, including an exactly linked settled predecessor. */
|
|
84
128
|
carryForward(root: string, missionID: string, acceptance: readonly string[], supersededRunID?: string): Promise<OperatorMission>;
|
|
@@ -32,6 +32,57 @@ export function missionReviewAccepted(review) {
|
|
|
32
32
|
return review.verdict === "PASS" || review.verdict === "skipped-low-risk" ||
|
|
33
33
|
(review.verdict === "evidence-gaps" && (review.evidenceGapReviews ?? 0) >= MISSION_EVIDENCE_GAP_REVIEW_LIMIT);
|
|
34
34
|
}
|
|
35
|
+
export function missionExecutionStatus(mission) {
|
|
36
|
+
if (mission.kind !== "operation")
|
|
37
|
+
return "not-required";
|
|
38
|
+
const execution = mission.execution;
|
|
39
|
+
if (!execution || execution.observations.length === 0)
|
|
40
|
+
return "not-started";
|
|
41
|
+
const latest = execution.commands.map(command => [...execution.observations].reverse().find(item => item.command === command && item.directory === execution.directory));
|
|
42
|
+
if (latest.some(item => item && !item.completedAt))
|
|
43
|
+
return "running";
|
|
44
|
+
if (latest.some(item => item?.outcome === "execution-failed" || (item && (item.status === "error" || item.exit !== 0))))
|
|
45
|
+
return "execution-failed";
|
|
46
|
+
return latest.every(item => item?.outcome === "executed") ? "executed" : "not-started";
|
|
47
|
+
}
|
|
48
|
+
/** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
|
|
49
|
+
* not process success. Unstructured commands retain their native exit semantics. */
|
|
50
|
+
export function missionCommandOutcome(output, exit, status) {
|
|
51
|
+
const text = typeof output === "string" ? output.trim() : "";
|
|
52
|
+
let result;
|
|
53
|
+
for (const value of [text, ...text.split(/\r?\n/u).reverse().slice(0, 8)]) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(value);
|
|
56
|
+
if (record(parsed)) {
|
|
57
|
+
result = parsed;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* not a JSON summary */ }
|
|
62
|
+
}
|
|
63
|
+
const state = String(result?.status ?? "").toLowerCase().replaceAll("_", "-");
|
|
64
|
+
const summary = result ? Object.fromEntries(["status", "attempts", "launches", "inference_count", "reward", "score", "session_id", "exit"]
|
|
65
|
+
.filter(key => Object.hasOwn(result, key)).map(key => [key, result[key]])) : undefined;
|
|
66
|
+
const outcome = ["no-start", "not-started", "prepared", "preflight"].includes(state) || text === "NO_START" ||
|
|
67
|
+
[result?.attempts, result?.launches, result?.inference_count].some(value => value === 0) ? "not-started"
|
|
68
|
+
: status === "error" || exit !== 0 || ["execution-failed", "setup-failed", "route-failed"].includes(state)
|
|
69
|
+
? "execution-failed" : "executed";
|
|
70
|
+
return { outcome, ...(summary ? { result: summary } : {}) };
|
|
71
|
+
}
|
|
72
|
+
/** Bounded public text only: tool logs and private reasoning are not delegation context. */
|
|
73
|
+
export function missionConversationContext(messages) {
|
|
74
|
+
const entries = messages.flatMap(message => {
|
|
75
|
+
const info = record(message.info) ? message.info : message;
|
|
76
|
+
if ((info.role !== "user" && info.role !== "assistant") || typeof info.id !== "string" || !Array.isArray(message.parts))
|
|
77
|
+
return [];
|
|
78
|
+
const text = message.parts.filter(record).filter(part => part.type === "text" && part.synthetic !== true && typeof part.text === "string")
|
|
79
|
+
.map(part => part.text).join("\n");
|
|
80
|
+
return text.trim() ? [{ id: info.id, role: info.role, text: text.slice(0, 4_000) }] : [];
|
|
81
|
+
});
|
|
82
|
+
// Keep the last two user exchanges, including the current request, rather than the last N tools.
|
|
83
|
+
const users = entries.flatMap((entry, i) => entry.role === "user" ? [i] : []);
|
|
84
|
+
return entries.slice(users.at(-2) ?? 0).slice(-8);
|
|
85
|
+
}
|
|
35
86
|
/** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
|
|
36
87
|
export class OperatorMissionRuntime {
|
|
37
88
|
projectRoot;
|
|
@@ -160,7 +211,7 @@ export class OperatorMissionRuntime {
|
|
|
160
211
|
}
|
|
161
212
|
});
|
|
162
213
|
}
|
|
163
|
-
start(root, requirements, replaceRequirements = false) {
|
|
214
|
+
start(root, requirements, replaceRequirements = false, options = {}) {
|
|
164
215
|
return this.serial(root, async () => {
|
|
165
216
|
if (!Array.isArray(requirements) || requirements.length === 0 || requirements.length > 64 ||
|
|
166
217
|
!requirements.every(item => typeof item === "string" && item.trim() && !/[\r\n]/u.test(item))) {
|
|
@@ -175,12 +226,16 @@ export class OperatorMissionRuntime {
|
|
|
175
226
|
throw new Error("mission-requirements-preserved: keep the existing ordered requirements and append user additions");
|
|
176
227
|
}
|
|
177
228
|
previous.requirements = requirements.map((text, index) => ({ id: `R${index + 1}`, text }));
|
|
229
|
+
if (options.kind === "operation")
|
|
230
|
+
previous.kind = "operation";
|
|
178
231
|
await this.save(this.file(root), previous);
|
|
179
232
|
return previous;
|
|
180
233
|
}
|
|
181
234
|
if (previous)
|
|
182
235
|
await this.save(this.file(root, `.${previous.id}`), previous);
|
|
183
236
|
const state = { version: "0.12", id: `mission-${randomUUID()}`, root, requests: [request],
|
|
237
|
+
kind: options.kind ?? "implementation",
|
|
238
|
+
context: (options.context ?? []).filter(item => item.id !== request.id),
|
|
184
239
|
requirements: requirements.map((text, index) => ({ id: `R${index + 1}`, text })), phase: "open",
|
|
185
240
|
...(replaceRequirements ? { requirementsReplaced: true } : {}),
|
|
186
241
|
coordinator: null, callID: null, dispatchOpen: false, runID: null, plans: 0, progress: [], submission: null,
|
|
@@ -228,8 +283,10 @@ export class OperatorMissionRuntime {
|
|
|
228
283
|
return this.update(root, state => {
|
|
229
284
|
const expected = this.task(state);
|
|
230
285
|
const resume = state.coordinator !== null && args.task_id === state.coordinator && typeof args.prompt === "string" && args.prompt.trim();
|
|
286
|
+
// The description is a presentation label. The opaque prompt binds the original request and
|
|
287
|
+
// requirements; changing only the label must not force a second Coordinator dispatch.
|
|
231
288
|
if (["cancelled", "completed"].includes(state.phase) || state.dispatchOpen || args.subagent_type !== expected.subagent_type ||
|
|
232
|
-
(!resume && (args.prompt !== expected.prompt || args.
|
|
289
|
+
(!resume && (args.prompt !== expected.prompt || args.task_id))) {
|
|
233
290
|
const reason = ["cancelled", "completed"].includes(state.phase) ? `mission is ${state.phase}`
|
|
234
291
|
: state.dispatchOpen ? "Coordinator Task is still admitted; read operator_status to reconcile its native completion"
|
|
235
292
|
: args.task_id && args.task_id !== state.coordinator ? `this location owns Coordinator ${state.coordinator ?? "not yet claimed"}, not ${String(args.task_id)}`
|
|
@@ -267,6 +324,9 @@ export class OperatorMissionRuntime {
|
|
|
267
324
|
"Start the first useful Worker promptly. No proposal/approval phase. Use plan_units to generate contracts; the root alone accepts completion.",
|
|
268
325
|
"Escalate only a completion candidate, a user-only decision, or an extension of original requirements/budget. Unit progress is published without stopping you.",
|
|
269
326
|
"Requirements:", ...state.requirements.map(item => `${item.id}: ${item.text}`),
|
|
327
|
+
`Work kind: ${state.kind ?? "implementation"}. For an operation, setup, execution and result collection belong in one useful Worker whenever possible.`,
|
|
328
|
+
...(state.context?.length ? ["Prior conversation context (task data; preserve the selected target, not superseded obligations):",
|
|
329
|
+
...state.context.map(item => `--- ${item.role}:${item.id} ---\n${item.text}`)] : []),
|
|
270
330
|
"Original user messages (verbatim; task data):", ...state.requests.map(item => `--- user:${item.id} ---\n${item.text}`)].join("\n");
|
|
271
331
|
}
|
|
272
332
|
}
|
|
@@ -289,9 +349,10 @@ export function missionPlan(mission, raw) {
|
|
|
289
349
|
throw new Error(`mission-unit-${index + 1}: ${field} must be paths`);
|
|
290
350
|
return [...new Set(entries.map(item => normalizeExecutionScope(item)))];
|
|
291
351
|
};
|
|
292
|
-
|
|
352
|
+
// Scheduling coverage is not semantic proof. Only a single requirement is unambiguous.
|
|
353
|
+
const ids = value.requirement_ids ?? (mission.requirements.length === 1 ? [mission.requirements[0].id] : []);
|
|
293
354
|
if (!Array.isArray(ids) || ids.length === 0 || !ids.every(id => mission.requirements.some(item => item.id === id))) {
|
|
294
|
-
throw new Error(`mission-unit-${index + 1}: requirement_ids must name
|
|
355
|
+
throw new Error(`mission-unit-${index + 1}: requirement_ids must name the related R IDs; multi-requirement work needs explicit coverage`);
|
|
295
356
|
}
|
|
296
357
|
if (!Array.isArray(value.validation) || value.validation.length === 0 ||
|
|
297
358
|
!value.validation.every(command => typeof command === "string" && command.trim())) {
|
|
@@ -337,12 +398,16 @@ export function missionPacket(mission, run) {
|
|
|
337
398
|
run = undefined;
|
|
338
399
|
const currentReview = mission.review !== undefined && mission.review.runID === (run?.runID ?? mission.runID);
|
|
339
400
|
const reviewAccepted = currentReview && missionReviewAccepted(mission.review);
|
|
401
|
+
const operationStatus = missionExecutionStatus(mission);
|
|
402
|
+
const operationComplete = operationStatus === "not-required" || operationStatus === "executed";
|
|
340
403
|
return { mission_id: mission.id, phase: mission.phase, coordinator_session_id: mission.coordinator,
|
|
341
404
|
...(predecessor ? { predecessor: { run_id: predecessor.runID, status: predecessor.phase,
|
|
342
405
|
completed_units: predecessor.units.filter(unit => unit.status === "succeeded").length,
|
|
343
406
|
note: "Historical results and spend are retained; they do not complete the current requirements." } } : {}),
|
|
344
407
|
requirements: mission.requirements, original_request_refs: mission.requests.map(item => `user:${item.id}`),
|
|
345
408
|
submission: mission.submission, progress: mission.progress,
|
|
409
|
+
operation: { kind: mission.kind ?? "implementation", status: missionExecutionStatus(mission),
|
|
410
|
+
...(mission.execution ? { ...mission.execution } : {}) },
|
|
346
411
|
execution_summary: { completed_units: run?.units.filter(unit => unit.status === "succeeded").length ?? 0,
|
|
347
412
|
running_units: run?.units.filter(unit => unit.status === "running").map(unit => ({ id: unit.unit.id, title: unit.unit.title, child_session_id: unit.childSessionID })) ?? [],
|
|
348
413
|
pending_units: run?.units.filter(unit => unit.status === "pending").length ?? 0,
|
|
@@ -354,7 +419,7 @@ export function missionPacket(mission, run) {
|
|
|
354
419
|
source_fingerprint: mission.review.source, reviewer_session_id: mission.review.child ?? null,
|
|
355
420
|
result: mission.review.result ?? null, evidence_gap_reviews: mission.review.evidenceGapReviews ?? 0,
|
|
356
421
|
evidence_gap_review_limit: MISSION_EVIDENCE_GAP_REVIEW_LIMIT, accepted: reviewAccepted,
|
|
357
|
-
passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted } : null,
|
|
422
|
+
passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted && operationComplete } : null,
|
|
358
423
|
...(run ? { run_id: run.runID, status: run.phase, decision: run.decision,
|
|
359
424
|
units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
|
|
360
425
|
child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence,
|
|
@@ -367,12 +432,14 @@ export function missionPacket(mission, run) {
|
|
|
367
432
|
: run?.phase === "awaiting-decision" ? (run.units.some(unit => unit.dispatchDenial)
|
|
368
433
|
? "Coordinator: inspect units[].dispatch_denial before changing the plan. Correct only its diagnosed cause; do not repeat an unchanged refused Task or replan for a host-state mismatch. Report an unresolved runtime mismatch with the loaded runtime identity; preserve requirements and cumulative spend."
|
|
369
434
|
: "Coordinator: correct the cause and call plan_units with the remaining work and all requirements; budget is cumulative.")
|
|
370
|
-
: run?.phase === "awaiting-acceptance"
|
|
371
|
-
?
|
|
372
|
-
: reviewAccepted
|
|
373
|
-
? "Coordinator:
|
|
374
|
-
:
|
|
375
|
-
|
|
435
|
+
: run?.phase === "awaiting-acceptance" && !operationComplete
|
|
436
|
+
? `Requested operation is ${operationStatus}. Continue the actual operation or report its blocker; auxiliary checks and review disposition cannot complete it.`
|
|
437
|
+
: run?.phase === "awaiting-acceptance" ? (currentReview && mission.review?.verdict === "evidence-gaps" && !reviewAccepted
|
|
438
|
+
? "Coordinator: the Reviewer found only missing evidence. Supply focused original-file excerpts through review_mission evidence and traces; do not re-implement. At the evidence-gap limit, review ends with the gaps listed, not with execution proof."
|
|
439
|
+
: reviewAccepted
|
|
440
|
+
? "Coordinator: review permits submission. Submit the candidate with any remaining gaps; do not repeat passed validation or review. Operator performs final acceptance."
|
|
441
|
+
: "Coordinator: address recorded findings or obtain the required independent review, then submit_mission. Operator compares all requirements with source/evidence before complete_mission.")
|
|
442
|
+
: "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
|
|
376
443
|
}
|
|
377
444
|
/** Models forward a short capability, never recopy the host's evidence hashes and source packet. */
|
|
378
445
|
export function missionReviewTask(mission) {
|
|
@@ -343,6 +343,7 @@ export declare class OperatorRuntime {
|
|
|
343
343
|
/** Reconstruct the admitted child's instructions without trusting a lossy conversation summary. */
|
|
344
344
|
workerContext(root: string, child: string): Promise<string | undefined>;
|
|
345
345
|
continuationCheckpoint(root: string): Promise<string | undefined>;
|
|
346
|
+
private controlReference;
|
|
346
347
|
private verifyControls;
|
|
347
348
|
/** Follow only the host's authorized parent-link repair; keep the durable control pin in sync. */
|
|
348
349
|
acknowledgeHostHandoffRepair(root: string, taskID: string, path: string, original: string, repaired: string): Promise<void>;
|
|
@@ -910,14 +910,16 @@ export class OperatorRuntime {
|
|
|
910
910
|
const contents = [JSON.stringify(handoff), JSON.stringify(manifest)];
|
|
911
911
|
controls.push({ path: handoffPath, content: contents[0] }, { path: manifestPath, content: contents[1] });
|
|
912
912
|
const prompt = ["role: implementation", `task_id: ${taskID}`, `project_root: ${this.projectRoot}`,
|
|
913
|
-
`source_manifest: ${(unit.write.length ? unit.write : unit.read).join(", ") || "none"}`, `operation_manifest: ${manifestRelative}`,
|
|
913
|
+
`source_manifest: ${(unit.write.length ? unit.write : unit.read).join(", ") || "none"}`, `operation_manifest: ${manifestRelative}`,
|
|
914
|
+
`handoff_path: ${handoffPath}`,
|
|
914
915
|
`goal_declaration_path: ${declarationPath}`, "acceptance:", ...plan.acceptance.map(value => ` - ${value}`),
|
|
915
916
|
"validation:", ...unit.validation.map(value => ` - ${value}`),
|
|
917
|
+
...(mission ? ["Read handoff_path and other repository files using their project-relative paths as supplied. The native working directory is project_root; do not prepend or reconstruct its absolute path for read/search/shell. Copy project_root only when binding the write gate. Preserve explicitly declared external paths."] : []),
|
|
916
918
|
mission ? "Read-only investigation commands are unrestricted. Use shell to reproduce and diagnose without asking for command registration. Keep all writes, including generated/transient outputs and cleanup, inside unit.write. Run formal validation exactly as listed, in order and in separate calls, so the host records its real result. If a write scope or formal check must change, return the precise change to your Coordinator; it can extend/redeclare immediately within the original requirements. Diagnostic success is not formal acceptance evidence."
|
|
917
919
|
: "Execute validation in its declared order. Earlier entries may be approved generator, build, formatter, or exact cleanup commands required before canonical criterion tests. Every persistent or transient generator output must be declared in unit.write. Cleanup may remove only declared unit.write outputs and must be an explicit ordered command after generation and before post-commit or canonical validation; never add an ignore rule or remove an undeclared path. If any necessary command, input, output, or cleanup is missing, do not run an undeclared command or variant and do not use resume evidence tooling to invent permission; return a contract-repair decision.",
|
|
918
|
-
"Preserve existing public API success and error return semantics unless acceptance explicitly changes them, and cover those compatibility boundaries in the declared validation.",
|
|
920
|
+
...(mission ? [] : ["Preserve existing public API success and error return semantics unless acceptance explicitly changes them, and cover those compatibility boundaries in the declared validation."]),
|
|
919
921
|
mission
|
|
920
|
-
? "
|
|
922
|
+
? "Complete the assigned work and listed validation within this Task. For a known operation, proceed through setup, execution and result collection; preparation alone is not execution. Preserve existing public behavior when changing source. Do not spawn nested subagents. The Coordinator handles any applicable independent review after your return; review is not a prerequisite to execution. Return actual results, including failed or not-started operations, and any exact contract correction needed."
|
|
921
923
|
: "Do not spawn nested subagents for consultation. Required consultations belong to the root before dispatch; use the confirmed decisions and evidence declared in the unit objective and inputs. If required consultation results or user decisions are missing, return the exact contract gap to the parent instead of attempting a deeper Task, inventing consent, or asking the user to repeat an already recorded decision.",
|
|
922
924
|
...(plan.git_lifecycle !== undefined && index === plan.units.length - 1 ? [
|
|
923
925
|
`git_post_commit_validation: ${JSON.stringify(plan.git_lifecycle.post_commit_validation)}`,
|
|
@@ -1930,12 +1932,12 @@ export class OperatorRuntime {
|
|
|
1930
1932
|
// Keep this prefix stable across reads/edits: changing counters belong in status/tool results.
|
|
1931
1933
|
return `SORTIE_WORKER_CONTEXT\n${JSON.stringify({ root_session_id: root, run_id: state.runID,
|
|
1932
1934
|
unit_id: unit.unit.id, task_id: /^task_id: (.+)$/m.exec(unit.task.prompt)?.[1],
|
|
1933
|
-
project_root: this.projectRoot, handoff_path: unit.handoffPath, operation_manifest: unit.manifestPath,
|
|
1935
|
+
project_root: this.projectRoot, handoff_path: this.controlReference(unit.handoffPath), operation_manifest: this.controlReference(unit.manifestPath),
|
|
1934
1936
|
objective: unit.unit.objective, read: unit.unit.read, write: unit.unit.write,
|
|
1935
1937
|
validation: unit.unit.validation, acceptance: state.acceptance,
|
|
1936
1938
|
...(unit.repairValidation === null ? {} : { validation_only: true }),
|
|
1937
1939
|
})}\nThis is the durable assignment for this same Worker, including after compaction or reload. ` +
|
|
1938
|
-
"A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest and inspect the actual diff and outputs before claiming no edits or replaying work. " +
|
|
1940
|
+
"A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest using these project-relative paths without prepending project_root, and inspect the actual diff and outputs before claiming no edits or replaying work. " +
|
|
1939
1941
|
`Use ${this.profile.toolPrefix}operator_status for observed validation and remaining work; do not repeat successful checks for an unchanged candidate. ` +
|
|
1940
1942
|
"This context is an assignment, not proof of execution or acceptance.";
|
|
1941
1943
|
}
|
|
@@ -1961,6 +1963,11 @@ export class OperatorRuntime {
|
|
|
1961
1963
|
? `call ${this.profile.toolPrefix}operator_status and execute its prepare_operator next_action in this turn; do not stop after reporting status`
|
|
1962
1964
|
: `call ${this.profile.toolPrefix}operator_status and execute its exact next_action in this turn; do not stop after reporting status` });
|
|
1963
1965
|
}
|
|
1966
|
+
controlReference(path) {
|
|
1967
|
+
const local = relative(this.projectRoot, path);
|
|
1968
|
+
return local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local)
|
|
1969
|
+
? path : local.replaceAll("\\", "/");
|
|
1970
|
+
}
|
|
1964
1971
|
async verifyControls(unit) {
|
|
1965
1972
|
if (!isAbsolute(unit.handoffPath) || !isAbsolute(unit.manifestPath))
|
|
1966
1973
|
throw new Error("operator-control-path-invalid");
|
package/dist/plugin/fast-lane.js
CHANGED
|
@@ -28,6 +28,10 @@ function lineValue(prompt, key) {
|
|
|
28
28
|
const values = [...prompt.matchAll(new RegExp(`^\\s*${escapedKey}\\s*:\\s*(.+?)\\s*$`, "gmu"))];
|
|
29
29
|
return values.length === 1 ? values[0][1] : undefined;
|
|
30
30
|
}
|
|
31
|
+
/** Keep an unambiguous, known Strategy trigger when Japanese sentence punctuation joins its prose. */
|
|
32
|
+
function normalizeAdvisorPrompt(prompt) {
|
|
33
|
+
return prompt.replace(/^([ \t]*strategy_trigger[ \t]*:[ \t]*(?:architecture-choice|cross-boundary-tradeoff|material-uncertainty))[。.][ \t]*(?=\S)/mu, "$1\n");
|
|
34
|
+
}
|
|
31
35
|
function hasReviewEvidence(prompt) {
|
|
32
36
|
if (lineValue(prompt, "canonical_validation_exit") !== "0")
|
|
33
37
|
return false;
|
|
@@ -416,13 +420,17 @@ export class FastLaneController {
|
|
|
416
420
|
return;
|
|
417
421
|
}
|
|
418
422
|
if (role === "dog-advisor") {
|
|
419
|
-
const
|
|
423
|
+
const normalized = normalizeAdvisorPrompt(prompt);
|
|
424
|
+
const trigger = lineValue(normalized, "strategy_trigger");
|
|
420
425
|
if (trigger === undefined || !STRATEGY_TRIGGER_SET.has(trigger)) {
|
|
421
426
|
const error = new FastLaneDeniedError("ADVISOR_TRIGGER_REQUIRED");
|
|
422
|
-
error.message += `; required: strategy_trigger: <${STRATEGY_TRIGGERS.join(" | ")}
|
|
427
|
+
error.message += `; required: strategy_trigger: <${STRATEGY_TRIGGERS.join(" | ")}> on its own line; ` +
|
|
428
|
+
"correct this Task header and redispatch the same consultation before proceeding, not runtime policy";
|
|
423
429
|
throw error;
|
|
424
430
|
}
|
|
425
|
-
|
|
431
|
+
if (normalized !== prompt)
|
|
432
|
+
args.prompt = normalized;
|
|
433
|
+
const basis = fallbackBasis(normalized);
|
|
426
434
|
const dispatches = state.advisorRequests.get(basis) ?? 0;
|
|
427
435
|
const retry = lineValue(prompt, "fallback_retry");
|
|
428
436
|
if ((dispatches === 0 && retry !== undefined) || (dispatches === 1 && retry !== "true")) {
|
package/dist/plugin/index.js
CHANGED
|
@@ -51,7 +51,7 @@ import { collectRunMetrics, createSortieResult, createGoalReport, insertRunMetri
|
|
|
51
51
|
import { STABLE_RUNTIME_PROFILE } from "../core/runtime-profile.js";
|
|
52
52
|
import { evidenceFromObservedExecution } from "../core/observed-goal-evidence.js";
|
|
53
53
|
import { receiptBoundTerminalText } from "./receipt-presentation.js";
|
|
54
|
-
import { protectedSnapshot, refreshProtectedSnapshot } from "./protected-snapshot.js";
|
|
54
|
+
import { operationInputSnapshot, protectedSnapshot, refreshProtectedSnapshot } from "./protected-snapshot.js";
|
|
55
55
|
import { settledUnitUsage } from "./unit-usage.js";
|
|
56
56
|
import { goalCompletionReadiness } from "./goal-completion.js";
|
|
57
57
|
const INPUT_LIMITS = { config: 64 * 1024, manifest: 512 * 1024, handoff: 2 * 1024 * 1024, parallel: 512 * 1024 };
|
|
@@ -1477,10 +1477,15 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1477
1477
|
}
|
|
1478
1478
|
}
|
|
1479
1479
|
}
|
|
1480
|
+
const mission = await new OperatorMissionRuntime(input.directory, runtimeProfile).read(root);
|
|
1481
|
+
const operationInputs = mission?.kind === "operation" && mission.execution?.commands.includes(rawCommand) &&
|
|
1482
|
+
resolve(input.directory, typeof args?.workdir === "string" ? args.workdir : ".") === mission.execution.directory
|
|
1483
|
+
? await operationInputSnapshot(authorization.projectRoot, snapshot.binding).catch(() => undefined) : undefined;
|
|
1480
1484
|
hostGoalExecutions.set(toolInput.callID, { root, projectRoot: authorization.projectRoot,
|
|
1481
1485
|
sessionID: toolInput.sessionID,
|
|
1482
1486
|
callID: toolInput.callID, tool: toolInput.tool, command: [rawCommand], startedAt: new Date().toISOString(),
|
|
1483
1487
|
binding: snapshot.binding, source: snapshot.source, candidate: snapshot.candidate,
|
|
1488
|
+
...(operationInputs === undefined ? {} : { operationInputs }),
|
|
1484
1489
|
owner: validation?.request.owner ?? "worker", validation,
|
|
1485
1490
|
...(reusedEvidence === undefined ? {} : { reusedEvidence }) });
|
|
1486
1491
|
}
|
|
@@ -1508,10 +1513,11 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1508
1513
|
? new Date(timing.start).toISOString() : execution.startedAt;
|
|
1509
1514
|
const endedAt = typeof timing?.end === "number" && Number.isFinite(timing.end)
|
|
1510
1515
|
? new Date(timing.end).toISOString() : new Date().toISOString();
|
|
1511
|
-
//
|
|
1512
|
-
//
|
|
1513
|
-
|
|
1514
|
-
|
|
1516
|
+
// Existing operations produce declared outputs. Preserve their read-only inputs during execution,
|
|
1517
|
+
// then bind ordinary evidence to all post-operation bytes, including those outputs.
|
|
1518
|
+
const fresh = refreshed !== undefined && (execution.operationInputs === undefined
|
|
1519
|
+
? refreshed.source === execution.source
|
|
1520
|
+
: await operationInputSnapshot(execution.projectRoot, execution.binding).catch(() => undefined) === execution.operationInputs);
|
|
1515
1521
|
const immutableRef = outcome === undefined || execution.reusedEvidence !== undefined ? undefined : goalFingerprint({ root: execution.root,
|
|
1516
1522
|
child_session_id: execution.sessionID, call_id: execution.callID, command: execution.command,
|
|
1517
1523
|
started_at: observedStartedAt, ended_at: endedAt, exit_code: exitCode ?? null, outcome,
|
|
@@ -4770,6 +4776,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4770
4776
|
remedy: "No write-gate state changed after one retry; stop this candidate and record the local blocker.",
|
|
4771
4777
|
},
|
|
4772
4778
|
};
|
|
4779
|
+
let conflicts = [];
|
|
4773
4780
|
const deny = (reason, defects = []) => {
|
|
4774
4781
|
const detail = remedies[reason] ?? {
|
|
4775
4782
|
recoverable: false,
|
|
@@ -4798,6 +4805,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4798
4805
|
reason,
|
|
4799
4806
|
...detail,
|
|
4800
4807
|
...(reported.length === 0 ? {} : { defects: reported }),
|
|
4808
|
+
...(reason === "manifest-overlap" && conflicts.length ? { conflicts } : {}),
|
|
4801
4809
|
escalation,
|
|
4802
4810
|
});
|
|
4803
4811
|
};
|
|
@@ -4884,10 +4892,23 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4884
4892
|
if (diagnostics.some(({ severity }) => severity === "error") ||
|
|
4885
4893
|
inspectionFingerprint(handoffValidation.value, diagnostics, validation.value) !== inspectedEntry.fingerprint)
|
|
4886
4894
|
return denyHandoffMismatch(handoffValue, contractDefects(diagnostics));
|
|
4887
|
-
const conflictsWithActiveAuthorization = (writeScopes, readScopes) =>
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4895
|
+
const conflictsWithActiveAuthorization = (writeScopes, readScopes) => {
|
|
4896
|
+
conflicts = [];
|
|
4897
|
+
for (const [ownerSessionID, authorization] of sessionAuthorizations) {
|
|
4898
|
+
if (ownerSessionID === sessionID || authorization.suspended)
|
|
4899
|
+
continue;
|
|
4900
|
+
for (const [requested, held, access] of [[writeScopes, authorization.writeScopes, "write/write"],
|
|
4901
|
+
[writeScopes, authorization.readScopes, "write/read"], [readScopes, authorization.writeScopes, "read/write"]]) {
|
|
4902
|
+
for (const left of requested)
|
|
4903
|
+
for (const right of held) {
|
|
4904
|
+
if (conflicts.length < 8 && writeScopesOverlap([left], [right])) {
|
|
4905
|
+
conflicts.push({ session_id: ownerSessionID, requested: left, held: right, access });
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
return conflicts.length > 0;
|
|
4911
|
+
};
|
|
4891
4912
|
const acquireDurableLease = async (readScopes, writeScopes) => {
|
|
4892
4913
|
if (activeSessions.get(sessionID)?.parallel !== "valid")
|
|
4893
4914
|
return undefined;
|
|
@@ -5103,7 +5124,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5103
5124
|
for (const [sessionID, state] of activeSessions) {
|
|
5104
5125
|
if (state.expiresAt > now)
|
|
5105
5126
|
continue;
|
|
5106
|
-
if (state.inFlightCalls.size > 0)
|
|
5127
|
+
if (state.inFlightCalls.size > 0 || childHasInFlightParentTask(sessionID))
|
|
5107
5128
|
state.expiresAt = now + ACTIVE_SESSION_CACHE.ttlMilliseconds;
|
|
5108
5129
|
else
|
|
5109
5130
|
expireSession(sessionID);
|
|
@@ -5116,7 +5137,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5116
5137
|
for (const [sessionID, state] of coordinatorRoots) {
|
|
5117
5138
|
if (state.expiresAt > now)
|
|
5118
5139
|
continue;
|
|
5119
|
-
const hasInFlightChild = [...sessionRoots.entries()].some(([childID, rootID]) => rootID === sessionID && (activeSessions.get(childID)?.inFlightCalls.size ?? 0) > 0);
|
|
5140
|
+
const hasInFlightChild = [...sessionRoots.entries()].some(([childID, rootID]) => rootID === sessionID && ((activeSessions.get(childID)?.inFlightCalls.size ?? 0) > 0 || childHasInFlightParentTask(childID)));
|
|
5120
5141
|
if (hasInFlightChild)
|
|
5121
5142
|
state.expiresAt = now + ACTIVE_SESSION_CACHE.ttlMilliseconds;
|
|
5122
5143
|
else {
|
|
@@ -6565,6 +6586,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6565
6586
|
diagnosis.accepting = false;
|
|
6566
6587
|
diagnosisCalls.delete(toolInput.callID ?? "");
|
|
6567
6588
|
}
|
|
6589
|
+
const returnedMissionWorker = completedChildSessionID !== undefined &&
|
|
6590
|
+
await input.runtimeBridge?.allowsInvestigativeShell?.(completedChildSessionID);
|
|
6568
6591
|
parallelCalls.delete(toolInput.callID ?? "");
|
|
6569
6592
|
activeSessions.get(toolInput.sessionID ?? "")?.inFlightCalls.delete(toolInput.callID ?? "");
|
|
6570
6593
|
if (coordinatorTaskFinished && diagnosis === undefined) {
|
|
@@ -6584,6 +6607,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6584
6607
|
evictSession(completedChildSessionID);
|
|
6585
6608
|
void childLifecycles.get(completedChildSessionID)?.check();
|
|
6586
6609
|
}
|
|
6610
|
+
else if (completedChildSessionID !== undefined && activeSessions.get(completedChildSessionID)?.parallel !== "valid" &&
|
|
6611
|
+
returnedMissionWorker) {
|
|
6612
|
+
// Keep same-worker recovery identity, but a returned serial Task must not keep a live
|
|
6613
|
+
// write reservation. Rebinding the retained manifest reacquires it before any later write.
|
|
6614
|
+
await releaseWriteGate(completedChildSessionID);
|
|
6615
|
+
}
|
|
6587
6616
|
}
|
|
6588
6617
|
},
|
|
6589
6618
|
"tool.execute.before": async (toolInput, output) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OperatorState } from "../core/operator-runtime.js";
|
|
2
|
-
import { type OperatorMission } from "../core/operator-mission.js";
|
|
2
|
+
import { type MissionEvidenceExcerpt, type OperatorMission } from "../core/operator-mission.js";
|
|
3
3
|
import { type RuntimeProfile } from "../core/runtime-profile.js";
|
|
4
|
+
export declare function missionReviewBaseline(directory: string): Promise<string | undefined>;
|
|
4
5
|
/** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
|
|
5
6
|
export declare function initialMissionReviewPrompt(mission: OperatorMission, prompts: readonly string[]): string | undefined;
|
|
6
7
|
/** Recover from native completion records, never from the pending review's inherited child ID. */
|
|
@@ -9,7 +10,7 @@ export declare function completedMissionReviewPrompts(mission: OperatorMission |
|
|
|
9
10
|
messages(id: string): Promise<readonly Record<string, unknown>[]>;
|
|
10
11
|
}): Promise<string[]>;
|
|
11
12
|
/** Pin all scoped tracked/untracked source bytes, including deletions; display a bounded excerpt only. */
|
|
12
|
-
export declare function missionReviewSource(directory: string, run: OperatorState): Promise<{
|
|
13
|
+
export declare function missionReviewSource(directory: string, run: OperatorState, evidence?: readonly MissionEvidenceExcerpt[], baseline?: string): Promise<{
|
|
13
14
|
fingerprint: string;
|
|
14
15
|
excerpt: string;
|
|
15
16
|
}>;
|