sortie-dogs 0.12.15 → 0.12.16
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/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/operator-mission.d.ts +43 -1
- package/dist/core/operator-mission.js +75 -10
- package/dist/core/operator-runtime.js +2 -2
- package/dist/plugin/fast-lane.js +11 -3
- package/dist/plugin/index.js +40 -11
- package/dist/plugin/mission-review.d.ts +2 -2
- package/dist/plugin/mission-review.js +33 -5
- package/dist/plugin/profiled.js +79 -11
- package/dist/plugin/protected-snapshot.d.ts +3 -0
- package/dist/plugin/protected-snapshot.js +19 -4
- package/dist/runtime-assets-v010.js +36 -51
- package/dist/runtime-mission-assets.js +35 -12
- package/package.json +1 -1
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.16-operation-efficiency-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.16-operation-efficiency-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;
|
|
@@ -42,6 +71,7 @@ export interface OperatorMission {
|
|
|
42
71
|
risk: string[];
|
|
43
72
|
source: string;
|
|
44
73
|
task: OperatorTask | null;
|
|
74
|
+
evidence?: MissionEvidenceExcerpt[];
|
|
45
75
|
requestFingerprint?: string;
|
|
46
76
|
verdict: "pending" | "PASS" | "findings" | "evidence-gaps" | "skipped-low-risk";
|
|
47
77
|
result?: string;
|
|
@@ -61,6 +91,15 @@ export declare const MISSION_EVIDENCE_GAP_REVIEW_LIMIT = 2;
|
|
|
61
91
|
export declare function missionReviewVerdict(text: string): "PASS" | "evidence-gaps" | "findings";
|
|
62
92
|
/** Whether the recorded review permits submission and acceptance of the current candidate. */
|
|
63
93
|
export declare function missionReviewAccepted(review: NonNullable<OperatorMission["review"]>): boolean;
|
|
94
|
+
export declare function missionExecutionStatus(mission: OperatorMission): "not-required" | "not-started" | "running" | "execution-failed" | "executed";
|
|
95
|
+
/** Observe the command's own terminal summary, never Worker/Reviewer prose. Scores are result data,
|
|
96
|
+
* not process success. Unstructured commands retain their native exit semantics. */
|
|
97
|
+
export declare function missionCommandOutcome(output: unknown, exit: unknown, status: unknown): {
|
|
98
|
+
outcome: "not-started" | "execution-failed" | "executed";
|
|
99
|
+
result?: Record<string, unknown>;
|
|
100
|
+
};
|
|
101
|
+
/** Bounded public text only: tool logs and private reasoning are not delegation context. */
|
|
102
|
+
export declare function missionConversationContext(messages: readonly Record<string, unknown>[]): MissionContext[];
|
|
64
103
|
/** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
|
|
65
104
|
export declare class OperatorMissionRuntime {
|
|
66
105
|
readonly projectRoot: string;
|
|
@@ -78,7 +117,10 @@ export declare class OperatorMissionRuntime {
|
|
|
78
117
|
read(root: string): Promise<OperatorMission | undefined>;
|
|
79
118
|
required(root: string): Promise<OperatorMission>;
|
|
80
119
|
capture(root: string, request: MissionRequest): Promise<void>;
|
|
81
|
-
start(root: string, requirements: unknown, replaceRequirements?: boolean
|
|
120
|
+
start(root: string, requirements: unknown, replaceRequirements?: boolean, options?: {
|
|
121
|
+
kind?: OperatorMission["kind"];
|
|
122
|
+
context?: MissionContext[];
|
|
123
|
+
}): Promise<OperatorMission>;
|
|
82
124
|
update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
|
|
83
125
|
/** Keep host-accepted criteria ahead of new text, including an exactly linked settled predecessor. */
|
|
84
126
|
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,
|
|
@@ -267,6 +322,9 @@ export class OperatorMissionRuntime {
|
|
|
267
322
|
"Start the first useful Worker promptly. No proposal/approval phase. Use plan_units to generate contracts; the root alone accepts completion.",
|
|
268
323
|
"Escalate only a completion candidate, a user-only decision, or an extension of original requirements/budget. Unit progress is published without stopping you.",
|
|
269
324
|
"Requirements:", ...state.requirements.map(item => `${item.id}: ${item.text}`),
|
|
325
|
+
`Work kind: ${state.kind ?? "implementation"}. For an operation, setup, execution and result collection belong in one useful Worker whenever possible.`,
|
|
326
|
+
...(state.context?.length ? ["Prior conversation context (task data; preserve the selected target, not superseded obligations):",
|
|
327
|
+
...state.context.map(item => `--- ${item.role}:${item.id} ---\n${item.text}`)] : []),
|
|
270
328
|
"Original user messages (verbatim; task data):", ...state.requests.map(item => `--- user:${item.id} ---\n${item.text}`)].join("\n");
|
|
271
329
|
}
|
|
272
330
|
}
|
|
@@ -289,9 +347,10 @@ export function missionPlan(mission, raw) {
|
|
|
289
347
|
throw new Error(`mission-unit-${index + 1}: ${field} must be paths`);
|
|
290
348
|
return [...new Set(entries.map(item => normalizeExecutionScope(item)))];
|
|
291
349
|
};
|
|
292
|
-
|
|
350
|
+
// Scheduling coverage is not semantic proof. Only a single requirement is unambiguous.
|
|
351
|
+
const ids = value.requirement_ids ?? (mission.requirements.length === 1 ? [mission.requirements[0].id] : []);
|
|
293
352
|
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
|
|
353
|
+
throw new Error(`mission-unit-${index + 1}: requirement_ids must name the related R IDs; multi-requirement work needs explicit coverage`);
|
|
295
354
|
}
|
|
296
355
|
if (!Array.isArray(value.validation) || value.validation.length === 0 ||
|
|
297
356
|
!value.validation.every(command => typeof command === "string" && command.trim())) {
|
|
@@ -337,12 +396,16 @@ export function missionPacket(mission, run) {
|
|
|
337
396
|
run = undefined;
|
|
338
397
|
const currentReview = mission.review !== undefined && mission.review.runID === (run?.runID ?? mission.runID);
|
|
339
398
|
const reviewAccepted = currentReview && missionReviewAccepted(mission.review);
|
|
399
|
+
const operationStatus = missionExecutionStatus(mission);
|
|
400
|
+
const operationComplete = operationStatus === "not-required" || operationStatus === "executed";
|
|
340
401
|
return { mission_id: mission.id, phase: mission.phase, coordinator_session_id: mission.coordinator,
|
|
341
402
|
...(predecessor ? { predecessor: { run_id: predecessor.runID, status: predecessor.phase,
|
|
342
403
|
completed_units: predecessor.units.filter(unit => unit.status === "succeeded").length,
|
|
343
404
|
note: "Historical results and spend are retained; they do not complete the current requirements." } } : {}),
|
|
344
405
|
requirements: mission.requirements, original_request_refs: mission.requests.map(item => `user:${item.id}`),
|
|
345
406
|
submission: mission.submission, progress: mission.progress,
|
|
407
|
+
operation: { kind: mission.kind ?? "implementation", status: missionExecutionStatus(mission),
|
|
408
|
+
...(mission.execution ? { ...mission.execution } : {}) },
|
|
346
409
|
execution_summary: { completed_units: run?.units.filter(unit => unit.status === "succeeded").length ?? 0,
|
|
347
410
|
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
411
|
pending_units: run?.units.filter(unit => unit.status === "pending").length ?? 0,
|
|
@@ -354,7 +417,7 @@ export function missionPacket(mission, run) {
|
|
|
354
417
|
source_fingerprint: mission.review.source, reviewer_session_id: mission.review.child ?? null,
|
|
355
418
|
result: mission.review.result ?? null, evidence_gap_reviews: mission.review.evidenceGapReviews ?? 0,
|
|
356
419
|
evidence_gap_review_limit: MISSION_EVIDENCE_GAP_REVIEW_LIMIT, accepted: reviewAccepted,
|
|
357
|
-
passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted } : null,
|
|
420
|
+
passed: currentReview && mission.review.verdict === "PASS", permits_submission: reviewAccepted && operationComplete } : null,
|
|
358
421
|
...(run ? { run_id: run.runID, status: run.phase, decision: run.decision,
|
|
359
422
|
units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
|
|
360
423
|
child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence,
|
|
@@ -367,12 +430,14 @@ export function missionPacket(mission, run) {
|
|
|
367
430
|
: run?.phase === "awaiting-decision" ? (run.units.some(unit => unit.dispatchDenial)
|
|
368
431
|
? "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
432
|
: "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
|
-
|
|
433
|
+
: run?.phase === "awaiting-acceptance" && !operationComplete
|
|
434
|
+
? `Requested operation is ${operationStatus}. Continue the actual operation or report its blocker; auxiliary checks and review disposition cannot complete it.`
|
|
435
|
+
: run?.phase === "awaiting-acceptance" ? (currentReview && mission.review?.verdict === "evidence-gaps" && !reviewAccepted
|
|
436
|
+
? "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."
|
|
437
|
+
: reviewAccepted
|
|
438
|
+
? "Coordinator: review permits submission. Submit the candidate with any remaining gaps; do not repeat passed validation or review. Operator performs final acceptance."
|
|
439
|
+
: "Coordinator: address recorded findings or obtain the required independent review, then submit_mission. Operator compares all requirements with source/evidence before complete_mission.")
|
|
440
|
+
: "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
|
|
376
441
|
}
|
|
377
442
|
/** Models forward a short capability, never recopy the host's evidence hashes and source packet. */
|
|
378
443
|
export function missionReviewTask(mission) {
|
|
@@ -915,9 +915,9 @@ export class OperatorRuntime {
|
|
|
915
915
|
"validation:", ...unit.validation.map(value => ` - ${value}`),
|
|
916
916
|
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
917
|
: "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.",
|
|
918
|
+
...(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
919
|
mission
|
|
920
|
-
? "
|
|
920
|
+
? "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
921
|
: "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
922
|
...(plan.git_lifecycle !== undefined && index === plan.units.length - 1 ? [
|
|
923
923
|
`git_post_commit_validation: ${JSON.stringify(plan.git_lifecycle.post_commit_validation)}`,
|
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,5 +1,5 @@
|
|
|
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
4
|
/** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
|
|
5
5
|
export declare function initialMissionReviewPrompt(mission: OperatorMission, prompts: readonly string[]): string | undefined;
|
|
@@ -9,7 +9,7 @@ export declare function completedMissionReviewPrompts(mission: OperatorMission |
|
|
|
9
9
|
messages(id: string): Promise<readonly Record<string, unknown>[]>;
|
|
10
10
|
}): Promise<string[]>;
|
|
11
11
|
/** Pin all scoped tracked/untracked source bytes, including deletions; display a bounded excerpt only. */
|
|
12
|
-
export declare function missionReviewSource(directory: string, run: OperatorState): Promise<{
|
|
12
|
+
export declare function missionReviewSource(directory: string, run: OperatorState, evidence?: readonly MissionEvidenceExcerpt[]): Promise<{
|
|
13
13
|
fingerprint: string;
|
|
14
14
|
excerpt: string;
|
|
15
15
|
}>;
|
|
@@ -2,8 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createReadStream } from "node:fs";
|
|
4
4
|
import { lstat, readlink } from "node:fs/promises";
|
|
5
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
7
8
|
import { TOOL_ENVIRONMENT } from "../runtime-mission-assets.js";
|
|
8
9
|
import { MISSION_REVIEW_REFERENCE } from "../core/operator-mission.js";
|
|
9
10
|
import { canonicalAgent } from "../core/runtime-profile.js";
|
|
@@ -85,12 +86,38 @@ export async function completedMissionReviewPrompts(mission, profile, root, requ
|
|
|
85
86
|
return prompts;
|
|
86
87
|
}
|
|
87
88
|
/** Pin all scoped tracked/untracked source bytes, including deletions; display a bounded excerpt only. */
|
|
88
|
-
export async function missionReviewSource(directory, run) {
|
|
89
|
+
export async function missionReviewSource(directory, run, evidence = []) {
|
|
89
90
|
const hash = createHash("sha256").update(JSON.stringify(run.units.map(unit => ({ unit: unit.unit, hashes: unit.hashes }))));
|
|
91
|
+
let selected = "";
|
|
92
|
+
for (const entry of evidence) {
|
|
93
|
+
const absolute = resolve(directory, entry.path);
|
|
94
|
+
const allowed = run.units.flatMap(unit => [...unit.unit.read, ...unit.unit.write]).some(scope => {
|
|
95
|
+
const root = resolve(directory, scope === "." ? scope : normalizeManifestScope(scope).path), rest = relative(root, absolute);
|
|
96
|
+
return rest === "" || (rest !== ".." && !rest.startsWith(`..${sep}`) && !isAbsolute(rest));
|
|
97
|
+
});
|
|
98
|
+
if (!allowed || !Number.isSafeInteger(entry.offset) || entry.offset < 1 ||
|
|
99
|
+
!Number.isSafeInteger(entry.limit) || entry.limit < 1 || entry.limit > 200)
|
|
100
|
+
throw new Error("mission-review-evidence: select up to 200 lines from a declared input/output");
|
|
101
|
+
const info = await lstat(absolute);
|
|
102
|
+
if (!info.isFile())
|
|
103
|
+
throw new Error("mission-review-evidence: select a regular file");
|
|
104
|
+
hash.update(JSON.stringify(entry)).update(String(info.size));
|
|
105
|
+
const stream = createReadStream(absolute);
|
|
106
|
+
stream.on("data", chunk => hash.update(chunk));
|
|
107
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
108
|
+
selected += `\n--- evidence: ${entry.path}:${entry.offset} ---\n`;
|
|
109
|
+
let number = 0;
|
|
110
|
+
for await (const line of lines) {
|
|
111
|
+
number++;
|
|
112
|
+
if (number >= entry.offset && number < entry.offset + entry.limit && Buffer.byteLength(selected) < 12_000) {
|
|
113
|
+
selected += `${number}: ${line.slice(0, 2_000)}\n`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
90
117
|
const writes = [...new Set(run.units.flatMap(unit => unit.unit.write).map(path => path === "." ? path : normalizeManifestScope(path).path))];
|
|
91
118
|
if (writes.length === 0)
|
|
92
119
|
return { fingerprint: `sha256:${hash.digest("hex")}`,
|
|
93
|
-
excerpt: "[Read-only units: no declared output files. Review the supplied observations, traces and validation evidence.]" };
|
|
120
|
+
excerpt: selected + "[Read-only units: no declared output files. Review the supplied observations, traces and validation evidence.]" };
|
|
94
121
|
// The shared dependency environment is local tooling, never reviewed or pinned source.
|
|
95
122
|
const external = [], local = [];
|
|
96
123
|
for (const path of writes) {
|
|
@@ -109,8 +136,9 @@ export async function missionReviewSource(directory, run) {
|
|
|
109
136
|
const ignored = local.length ? await git(["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--", ...scopes]) : "";
|
|
110
137
|
for (const path of ignored.split("\0").filter(Boolean))
|
|
111
138
|
untracked.add(path);
|
|
112
|
-
|
|
113
|
-
const unchanged =
|
|
139
|
+
const diff = local.length ? await git(["diff", "--no-ext-diff", "--no-textconv", "HEAD", "--", ...scopes]).catch(() => git(["diff", "--no-ext-diff", "--no-textconv", "--", ...scopes])) : "";
|
|
140
|
+
const unchanged = diff.length === 0;
|
|
141
|
+
let excerpt = selected + diff;
|
|
114
142
|
const paths = [...new Set([...names.split("\0"), ...ignored.split("\0")].filter(Boolean))].sort();
|
|
115
143
|
const omitted = [];
|
|
116
144
|
for (const path of paths) {
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -12,7 +12,7 @@ import { decoratePreviewHeadings, returnReportPanel } from "./receipt-presentati
|
|
|
12
12
|
import { sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
13
13
|
import { normalizeCommand } from "./gate.js";
|
|
14
14
|
import { normalizeRelativePath } from "../core/path.js";
|
|
15
|
-
import { MISSION_EVIDENCE_GAP_REVIEW_LIMIT, OperatorMissionRuntime, missionPacket, missionPlan, missionReviewAccepted, missionReviewTask, missionReviewTraces, missionReviewVerdict } from "../core/operator-mission.js";
|
|
15
|
+
import { MISSION_EVIDENCE_GAP_REVIEW_LIMIT, OperatorMissionRuntime, missionPacket, missionPlan, missionReviewAccepted, missionReviewTask, missionCommandOutcome, missionConversationContext, missionExecutionStatus, missionValidationCommand, missionReviewTraces, missionReviewVerdict } from "../core/operator-mission.js";
|
|
16
16
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
17
17
|
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionReviewSource } from "./mission-review.js";
|
|
18
18
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
@@ -988,6 +988,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
988
988
|
await requireRoot(context.sessionID);
|
|
989
989
|
const state = await operators.required(context.sessionID);
|
|
990
990
|
const mission = await missions.read(context.sessionID);
|
|
991
|
+
if (mission?.kind === "operation" && missionExecutionStatus(mission) !== "executed")
|
|
992
|
+
return JSON.stringify({
|
|
993
|
+
status: "not-ready", operation_status: missionExecutionStatus(mission),
|
|
994
|
+
next_action: "The requested operation has not completed. Preserve its real outcome; auxiliary checks and a bounded review do not complete it. Continue the declared operation or report its blocker.",
|
|
995
|
+
packet: missionPacket(mission, state)
|
|
996
|
+
});
|
|
991
997
|
if (mission && !["completed", "cancelled"].includes(mission.phase))
|
|
992
998
|
await assertMissionReview(context.sessionID, mission);
|
|
993
999
|
if (args.run_id !== state.runID || args.acceptance_fingerprint !== state.acceptanceFingerprint)
|
|
@@ -1005,7 +1011,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1005
1011
|
await operators.terminal(context.sessionID, result.receipt);
|
|
1006
1012
|
let panel;
|
|
1007
1013
|
if (input.returnReportTransport === "tool-result" && result.receipt?.status === "succeeded") {
|
|
1008
|
-
const text = `✅ **DONE** \`${state.runID}\` —
|
|
1014
|
+
const text = `✅ **DONE** \`${state.runID}\` — declared checks passed; Operator accepted the result.\n\n` +
|
|
1009
1015
|
`**変更点:** ${state.units.map(unit => unit.unit.title).join("; ")}\n\n` +
|
|
1010
1016
|
`**確認結果:** 宣言検証合格 — ${[...new Set(state.units.flatMap(unit => unit.unit.validation))].join("; ")}` +
|
|
1011
1017
|
(mission?.review ? `\n独立レビュー: ${mission.review.verdict === "evidence-gaps" ? "証拠不足を残して受入れ(レビューPASSではない)" : mission.review.verdict}.` : "") + "\n\n**次:** なし";
|
|
@@ -1303,6 +1309,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1303
1309
|
const stringList = { type: "array", items: { type: "string" } };
|
|
1304
1310
|
tools[startMission] = { description: "Operator: save the current user requirements. Use intent=replace when the user changes an existing request (version, parallelism, target): host cancels the previous run and archives its requirements/results while retaining spend. Supply the complete current requirements, retaining constraints the user has not changed. For separate work in a different location use intent=new. Dispatch the Coordinator immediately, or plan_units for a known single-unit procedure; item count and runtime alone do not require a Coordinator.",
|
|
1305
1311
|
args: { requirements: { ...stringList, minItems: 1, maxItems: 64 },
|
|
1312
|
+
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 },
|
|
1306
1313
|
intent: { type: "string", enum: ["", "continue", "new", "replace"], "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
1307
1314
|
await requireRoot(context.sessionID);
|
|
1308
1315
|
if (args.intent !== undefined && !["", "continue", "new", "replace"].includes(args.intent))
|
|
@@ -1317,16 +1324,34 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1317
1324
|
if (relocated)
|
|
1318
1325
|
return JSON.stringify({ ...relocated, budget: await control.currentBudget(context.sessionID) });
|
|
1319
1326
|
}
|
|
1320
|
-
const mission = await retainCancelledMissionAcceptance(context.sessionID, await missions.start(context.sessionID, args.requirements, args.intent === "replace" || args.intent === "new"
|
|
1327
|
+
const mission = await retainCancelledMissionAcceptance(context.sessionID, await missions.start(context.sessionID, args.requirements, args.intent === "replace" || args.intent === "new", {
|
|
1328
|
+
kind: args.kind === "operation" ? "operation" : "implementation",
|
|
1329
|
+
context: missionConversationContext(await messages(context.sessionID)),
|
|
1330
|
+
}));
|
|
1321
1331
|
return JSON.stringify({ ...locationObservation(context.sessionID), ...(mission.dispatchOpen
|
|
1322
1332
|
? missionDispatchPacket(mission, await operators.read(context.sessionID))
|
|
1323
1333
|
: { mission_id: mission.id, requirements: mission.requirements, task: missions.task(mission),
|
|
1324
1334
|
next_action: "Nontrivial: dispatch task now. Simple single-unit work with known scope/check: call plan_units directly. Do not create a proposal or ask for plan approval." }) });
|
|
1325
1335
|
});
|
|
1326
1336
|
} };
|
|
1327
|
-
async function declareMissionUnits(root, actor, mission, raw, reason) {
|
|
1337
|
+
async function declareMissionUnits(root, actor, mission, raw, reason, execution) {
|
|
1328
1338
|
const previous = await operators.read(root);
|
|
1329
1339
|
mission = await retainCancelledMissionAcceptance(root, mission, previous);
|
|
1340
|
+
let operation = mission.execution;
|
|
1341
|
+
// Models can serialize an optional operation field as an empty object for a normal edit.
|
|
1342
|
+
// Do not turn an implementation mission into an operation (or block its first Worker).
|
|
1343
|
+
const emptyImplementationExecution = mission.kind === "implementation" && record(execution) &&
|
|
1344
|
+
Array.isArray(execution.commands) && execution.commands.length === 0;
|
|
1345
|
+
if (execution !== undefined && !emptyImplementationExecution) {
|
|
1346
|
+
if (!record(execution) || typeof execution.directory !== "string" || !execution.directory.trim() ||
|
|
1347
|
+
!Array.isArray(execution.commands) || execution.commands.length === 0 || execution.commands.length > 16 ||
|
|
1348
|
+
!execution.commands.every(command => typeof command === "string" && command.trim()))
|
|
1349
|
+
throw new Error("mission-operation-input: supply the actual operation commands and their working directory");
|
|
1350
|
+
const commands = [...new Set(execution.commands.map(missionValidationCommand).map(normalizeCommand))];
|
|
1351
|
+
const directory = resolve(input.directory, execution.directory);
|
|
1352
|
+
// A corrected command is allowed on replan; retain prior observations for accounting.
|
|
1353
|
+
operation = { commands, directory, observations: operation?.observations ?? [] };
|
|
1354
|
+
}
|
|
1330
1355
|
const plan = missionPlan(mission, raw);
|
|
1331
1356
|
if (actor === root && (mission.coordinator !== null || plan.units.length !== 1))
|
|
1332
1357
|
throw new Error("mission-coordinator-required: dispatch the returned Coordinator task");
|
|
@@ -1376,6 +1401,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1376
1401
|
item.phase = "running";
|
|
1377
1402
|
item.submission = null;
|
|
1378
1403
|
delete item.supersededRunID;
|
|
1404
|
+
if (operation) {
|
|
1405
|
+
item.kind = "operation";
|
|
1406
|
+
item.execution = operation;
|
|
1407
|
+
}
|
|
1379
1408
|
});
|
|
1380
1409
|
return JSON.stringify(await operators.next(root, actor));
|
|
1381
1410
|
}
|
|
@@ -1384,9 +1413,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1384
1413
|
properties: { title: { type: "string" }, objective: { type: "string" },
|
|
1385
1414
|
read: { ...stringList, description: "Inputs that affect validation, not an allowlist for observation. Do not include whole live session/database/log trees just to inspect them." }, write: stringList,
|
|
1386
1415
|
validation: stringList, requirement_ids: stringList }, required: ["title", "objective", "write", "validation"] } },
|
|
1416
|
+
execution: { type: "object", properties: { commands: stringList, directory: { type: "string" } },
|
|
1417
|
+
required: ["commands", "directory"], additionalProperties: false,
|
|
1418
|
+
description: "For an operation: the actual run/grade commands, not a preflight or NO_START check. Host observes their native shell completion; no handwritten proof file is needed.", "x-sortie-optional": true },
|
|
1387
1419
|
reason: { type: "string", "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
1388
1420
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
1389
|
-
return serializeDispatchTransition(root, () => declareMissionUnits(root, context.sessionID, mission, args.units, args.reason));
|
|
1421
|
+
return serializeDispatchTransition(root, () => declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution));
|
|
1390
1422
|
} };
|
|
1391
1423
|
tools[expandUnit] = { description: "Coordinator: extend the stopped unit's write scope immediately within the original request. Keeps original requirements and cumulative budget; generates a replacement Worker contract. No Operator approval or handwritten manifest repair is needed. Use only after the Worker has returned.",
|
|
1392
1424
|
args: { unit_id: stringSchema, paths: stringList, reason: stringSchema }, execute: async (args, context) => {
|
|
@@ -1405,6 +1437,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1405
1437
|
} };
|
|
1406
1438
|
tools[reviewMission] = { description: "Coordinator: prepare the independent Reviewer task from current source, requirements and observed checks. Supply risk_tags (empty only for genuinely low risk) and concise criterion-level changed-code/test traces. Host supplies diff, IDs, manifest, mappings and evidence; keep candidate lineage across corrections. A low-risk skip is recorded, never inferred from a missing review.",
|
|
1407
1439
|
args: { risk_tags: { type: "array", items: { type: "string", enum: SOURCE_REVIEW_RISK_TAGS } },
|
|
1440
|
+
evidence: { type: "array", maxItems: 6, items: { type: "object", additionalProperties: false,
|
|
1441
|
+
properties: { path: { type: "string" }, offset: { type: "integer", minimum: 1 }, limit: { type: "integer", minimum: 1, maximum: 200 } },
|
|
1442
|
+
required: ["path", "offset", "limit"] }, description: "Focused excerpts from existing declared inputs/outputs. Use for missing review evidence instead of creating another evidence-copying Worker.", "x-sortie-optional": true },
|
|
1408
1443
|
traces: stringList }, execute: async (args, context) => {
|
|
1409
1444
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
1410
1445
|
const run = await operators.required(root);
|
|
@@ -1415,7 +1450,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1415
1450
|
if (!Array.isArray(risk) || !risk.every(tag => SOURCE_REVIEW_RISK_TAGS.includes(tag))) {
|
|
1416
1451
|
throw new Error("mission-review-input: use recognized risk tags");
|
|
1417
1452
|
}
|
|
1418
|
-
const
|
|
1453
|
+
const evidence = args.evidence;
|
|
1454
|
+
if (evidence !== undefined && (!Array.isArray(evidence) || evidence.length > 6))
|
|
1455
|
+
throw new Error("mission-review-evidence: select at most six focused excerpts");
|
|
1456
|
+
const source = await missionReviewSource(input.directory, run, evidence);
|
|
1419
1457
|
const requestFingerprint = goalFingerprint({ run: run.runID, source: source.fingerprint, risk, traces });
|
|
1420
1458
|
if (mission.review?.requestFingerprint === requestFingerprint && mission.review.verdict !== "pending") {
|
|
1421
1459
|
return JSON.stringify({ ...missionPacket(mission, run), status: "review-recorded",
|
|
@@ -1437,7 +1475,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1437
1475
|
description: `🔎 ${run.units[0].unit.title}`, prompt: [
|
|
1438
1476
|
`candidate_id: ${mission.id}`, `review_phase: ${phase}`, "canonical_validation_exit: 0", `risk_tags: [${risk.join(", ")}]`,
|
|
1439
1477
|
"Review this candidate independently. Use the language of the requirements/traces. Invoke no tools. First line: exactly PASS, FINDINGS or EVIDENCE_GAPS.",
|
|
1440
|
-
"Use EVIDENCE_GAPS only when no
|
|
1478
|
+
"Use EVIDENCE_GAPS only when no concrete source/test defect is established and a specific acceptance-relevant behavior or required validation cannot be settled by the supplied artifact. Name the affected path and why the missing evidence matters; do not request a generic route inventory or raw history for incidental workflow constraints already covered by a concise Worker trace. Any concrete defect uses FINDINGS.",
|
|
1441
1479
|
"This Reviewer's native outcome and final acceptance can only be observed after this review. List those as deferred Operator checks, not as a reason to request another review. Still assess all available source, validation and historical evidence independently.",
|
|
1442
1480
|
`acceptance: ${JSON.stringify(run.acceptance)}`, `changedLogicSummary: ${JSON.stringify(traces)}`,
|
|
1443
1481
|
...run.acceptance.map((_, i) => `acceptance[${i}] -> changedLogicSummary[${i}]`),
|
|
@@ -1447,7 +1485,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1447
1485
|
].join("\n") };
|
|
1448
1486
|
const reviewed = await missions.update(root, item => {
|
|
1449
1487
|
item.review = { runID: run.runID, risk: risk, source: source.fingerprint, requestFingerprint,
|
|
1450
|
-
task, verdict: task ? "pending" : "skipped-low-risk", ...(mission.review?.child ? { child: mission.review.child } : {}),
|
|
1488
|
+
task, evidence, verdict: task ? "pending" : "skipped-low-risk", ...(mission.review?.child ? { child: mission.review.child } : {}),
|
|
1451
1489
|
...(initialPrompt ? { initialPrompt } : {}),
|
|
1452
1490
|
...(mission.review?.evidenceGapReviews ? { evidenceGapReviews: mission.review.evidenceGapReviews } : {}) };
|
|
1453
1491
|
});
|
|
@@ -1456,13 +1494,17 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1456
1494
|
async function assertMissionReview(root, mission) {
|
|
1457
1495
|
const run = await operators.required(root), review = mission.review;
|
|
1458
1496
|
if (!review || review.runID !== run.runID || !missionReviewAccepted(review) ||
|
|
1459
|
-
review.source !== (await missionReviewSource(input.directory, run)).fingerprint)
|
|
1497
|
+
review.source !== (await missionReviewSource(input.directory, run, review.evidence)).fingerprint)
|
|
1460
1498
|
throw new Error("mission-review-required-or-stale");
|
|
1461
1499
|
}
|
|
1462
1500
|
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.",
|
|
1463
1501
|
args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema }, execute: async (args, context) => {
|
|
1464
1502
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
1465
1503
|
if (args.status === "ready") {
|
|
1504
|
+
if (mission.kind === "operation" && missionExecutionStatus(mission) !== "executed") {
|
|
1505
|
+
return JSON.stringify({ ...missionPacket(mission, await operators.read(root)), status: "operation-incomplete",
|
|
1506
|
+
next_action: "Continue the requested operation or submit its actual blocker. A successful setup/NO_START check does not authorize ready." });
|
|
1507
|
+
}
|
|
1466
1508
|
const run = await operators.required(root);
|
|
1467
1509
|
if (run.phase !== "awaiting-acceptance")
|
|
1468
1510
|
throw new Error("mission-units-incomplete");
|
|
@@ -1957,6 +1999,17 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1957
1999
|
}
|
|
1958
2000
|
else
|
|
1959
2001
|
Object.assign(output, outward);
|
|
2002
|
+
if (who.role === "dog-worker" && ["bash", "shell"].includes(request.tool) && mission?.execution &&
|
|
2003
|
+
typeof args.command === "string" && mission.execution.commands.includes(normalizeCommand(args.command)) &&
|
|
2004
|
+
resolve(input.directory, typeof args.workdir === "string" ? args.workdir : ".") === mission.execution.directory) {
|
|
2005
|
+
const command = normalizeCommand(args.command);
|
|
2006
|
+
await missions.update(root, item => {
|
|
2007
|
+
if (!item.execution.observations.some(observation => observation.callID === request.callID)) {
|
|
2008
|
+
item.execution.observations.push({ command, directory: item.execution.directory,
|
|
2009
|
+
callID: request.callID, sessionID: request.sessionID, startedAt: new Date().toISOString() });
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
1960
2013
|
}
|
|
1961
2014
|
catch (error) {
|
|
1962
2015
|
if (delegated) {
|
|
@@ -2005,8 +2058,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2005
2058
|
if (reviewed.review?.verdict === "evidence-gaps" && typeof output.output === "string") {
|
|
2006
2059
|
const count = reviewed.review.evidenceGapReviews ?? 0;
|
|
2007
2060
|
output.output += missionReviewAccepted(reviewed.review)
|
|
2008
|
-
? `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT} reached the limit.
|
|
2009
|
-
: `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT}.
|
|
2061
|
+
? `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT} reached the limit. Review is closed with gaps; this does not prove execution or requirement completion. Submit ready only when the requested result is complete, and list the gaps.`
|
|
2062
|
+
: `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT}. Supply focused original-file excerpts through review_mission evidence and traces. Do not create an evidence-copying Worker or re-implement.`;
|
|
2010
2063
|
}
|
|
2011
2064
|
}
|
|
2012
2065
|
taskOwners.delete(request.callID);
|
|
@@ -2034,6 +2087,21 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
2034
2087
|
await core["tool.execute.after"]?.({ ...translate(request, false),
|
|
2035
2088
|
...(ownership ? { sessionID: ownership.root } : {}) }, mapped);
|
|
2036
2089
|
Object.assign(output, translate(mapped, true));
|
|
2090
|
+
if (!ownership && ["bash", "shell"].includes(request.tool.toLowerCase())) {
|
|
2091
|
+
const root = await rootFor(id);
|
|
2092
|
+
const mission = root ? await missions.read(root) : undefined;
|
|
2093
|
+
if (root && mission?.execution?.observations.some(item => item.callID === request.callID && item.sessionID === id)) {
|
|
2094
|
+
const metadata = record(output.metadata) ? output.metadata : {};
|
|
2095
|
+
await missions.update(root, item => {
|
|
2096
|
+
const observation = item.execution.observations.find(value => value.callID === request.callID && value.sessionID === id);
|
|
2097
|
+
observation.completedAt = new Date().toISOString();
|
|
2098
|
+
observation.status = output.status === "error" || metadata.status === "error" ? "error" : "completed";
|
|
2099
|
+
if (Number.isSafeInteger(metadata.exit))
|
|
2100
|
+
observation.exit = metadata.exit;
|
|
2101
|
+
Object.assign(observation, missionCommandOutcome(returnedOutput, metadata.exit, observation.status));
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2037
2105
|
if (repairAccess !== null && ["bash", "shell"].includes(request.tool.toLowerCase())) {
|
|
2038
2106
|
const metadata = record(output.metadata) ? output.metadata : undefined;
|
|
2039
2107
|
const exit = metadata?.exit;
|
|
@@ -2,6 +2,9 @@ import { type GoalEvidence } from "../core/goal-bound.js";
|
|
|
2
2
|
type Binding = NonNullable<GoalEvidence["protected_binding"]>;
|
|
3
3
|
/** Live orchestration/Git bookkeeping is readable context, not the source being validated. */
|
|
4
4
|
export declare function isRuntimeControlPath(path: string): boolean;
|
|
5
|
+
/** An existing operation may create its declared outputs. Compare its other inputs during execution;
|
|
6
|
+
* ordinary evidence still pins the full post-operation source and outputs for later acceptance. */
|
|
7
|
+
export declare function operationInputSnapshot(projectRoot: string, binding: Binding): Promise<string | undefined>;
|
|
5
8
|
export declare function protectedSnapshot(authorization: {
|
|
6
9
|
manifestPath: string;
|
|
7
10
|
manifestHash: string;
|
|
@@ -11,10 +11,12 @@ export function isRuntimeControlPath(path) {
|
|
|
11
11
|
const first = path.replaceAll("\\", "/").split("/")[0];
|
|
12
12
|
return controlRoots.has(process.platform === "win32" ? first.toLowerCase() : first);
|
|
13
13
|
}
|
|
14
|
-
async function protectedScopeDigest(projectRoot, paths, manifestHash, sourcePolicy) {
|
|
14
|
+
async function protectedScopeDigest(projectRoot, paths, manifestHash, sourcePolicy, excluded = []) {
|
|
15
15
|
const entries = [];
|
|
16
16
|
const canonicalRoot = await realpath(projectRoot);
|
|
17
17
|
const visit = async (absolute) => {
|
|
18
|
+
if (excluded.some(root => !outside(root, absolute)))
|
|
19
|
+
return true;
|
|
18
20
|
const scoped = relative(projectRoot, absolute).replaceAll("\\", "/");
|
|
19
21
|
if (scoped === ".." || scoped.startsWith("../") || isAbsolute(scoped))
|
|
20
22
|
return false;
|
|
@@ -62,14 +64,27 @@ function outside(projectRoot, path) {
|
|
|
62
64
|
const scoped = relative(projectRoot, path).replaceAll("\\", "/");
|
|
63
65
|
return scoped === ".." || scoped.startsWith("../") || isAbsolute(scoped);
|
|
64
66
|
}
|
|
65
|
-
async function declaredScopeDigest(projectRoot, paths, manifestHash, source) {
|
|
67
|
+
async function declaredScopeDigest(projectRoot, paths, manifestHash, source, excluded = []) {
|
|
66
68
|
const local = paths.filter(path => !outside(projectRoot, path));
|
|
67
69
|
const external = paths.filter(path => outside(projectRoot, path));
|
|
68
|
-
const project = await protectedScopeDigest(projectRoot, local, manifestHash, source ? "project-files-v1" : undefined);
|
|
70
|
+
const project = await protectedScopeDigest(projectRoot, local, manifestHash, source ? "project-files-v1" : undefined, excluded);
|
|
69
71
|
if (project === undefined)
|
|
70
72
|
return undefined;
|
|
71
73
|
const artifacts = await declaredArtifacts(external).catch(() => undefined);
|
|
72
|
-
return artifacts && goalFingerprint({ project, external: artifacts.entries });
|
|
74
|
+
return artifacts && goalFingerprint({ project, external: artifacts.entries.filter(([path]) => !excluded.some(root => !outside(root, path))) });
|
|
75
|
+
}
|
|
76
|
+
/** An existing operation may create its declared outputs. Compare its other inputs during execution;
|
|
77
|
+
* ordinary evidence still pins the full post-operation source and outputs for later acceptance. */
|
|
78
|
+
export async function operationInputSnapshot(projectRoot, binding) {
|
|
79
|
+
const manifest = await readFile(resolve(projectRoot, binding.manifest_path)).catch(() => undefined);
|
|
80
|
+
if (!manifest || `sha256:${createHash("sha256").update(manifest).digest("hex")}` !== binding.manifest_hash)
|
|
81
|
+
return undefined;
|
|
82
|
+
const paths = binding.source_paths.map(path => resolve(projectRoot, path));
|
|
83
|
+
const outputs = binding.candidate_paths.map(path => resolve(projectRoot, path));
|
|
84
|
+
const hash = binding.manifest_hash.slice("sha256:".length);
|
|
85
|
+
return binding.source_policy === "declared-paths-v1"
|
|
86
|
+
? await declaredScopeDigest(projectRoot, paths, hash, true, outputs)
|
|
87
|
+
: await protectedScopeDigest(projectRoot, paths, hash, binding.source_policy, outputs);
|
|
73
88
|
}
|
|
74
89
|
export async function protectedSnapshot(authorization) {
|
|
75
90
|
const manifestSource = await readFile(authorization.manifestPath).catch(() => undefined);
|
|
@@ -393,59 +393,44 @@ same child, intent, read evidence and remaining budgets; continue investigation/
|
|
|
393
393
|
Only in the execution phase, call next to read authoritative queue state and its current short Task reference rather than
|
|
394
394
|
reconstructing criteria from a summary. Never use a standalone/generic worker as a fallback.
|
|
395
395
|
`;
|
|
396
|
-
/**
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
routes separately from mutation routes; a request that names one route never proves the other is absent.
|
|
411
|
-
Enumerate the value representations that reach the new rule as well: a value taken from a container element,
|
|
412
|
-
field, or dynamic holder arrives through a different representation than a directly produced value, so it is a
|
|
413
|
-
distinct entry even when its syntax form is already listed. Intercept each enumerated form or state why it
|
|
414
|
-
stays out of scope, and exercise each intercepted form in the declared validation with the new rule active;
|
|
415
|
-
a case that exercises the form while the new rule is inactive proves only the pre-existing behavior. Return
|
|
416
|
-
the enumeration and its derivation source with the unit evidence. An enumerated form without a trace or a
|
|
417
|
-
stated exclusion is an open defect, not a completed unit.
|
|
418
|
-
|
|
419
|
-
Treat independently selected syntax or dispatch dimensions as combinations, not as interchangeable labels.
|
|
420
|
-
For example, cardinality, optional-clause presence, scope, and value representation can select different
|
|
421
|
-
branches even when each dimension works in one other case. Exercise the material combinations needed to cover
|
|
422
|
-
those branches, and for a multi-target route prove the rule and result for every target rather than only the
|
|
423
|
-
first target. A trace for one combination or one target does not cover the others.
|
|
396
|
+
/** Keep coverage tied to actual changed branches, not a generic inventory of the whole API. */
|
|
397
|
+
const CHANGED_PATH_COVERAGE_WORKER = `
|
|
398
|
+
## Changed-path coverage
|
|
399
|
+
|
|
400
|
+
When a fix changes a rule on an existing construct, inspect the affected source branches and the public
|
|
401
|
+
entry path in the request. Check other routes or value representations only when the changed code, an
|
|
402
|
+
existing test, or acceptance shows that they can reach a materially different branch or result. Exercise
|
|
403
|
+
the reported case with the new rule active and the relevant adjacent behavior; add targeted cases for
|
|
404
|
+
independent branches or multiple affected targets. A passing case with the new rule inactive does not
|
|
405
|
+
prove the fix. Explain any material branch left unverified in the unit evidence.
|
|
406
|
+
|
|
407
|
+
Do not turn this into an exhaustive inventory of creation, binding, mutation, syntax forms, value
|
|
408
|
+
representations, or their Cartesian product. Do not add tests or scope for hypothetical routes without
|
|
409
|
+
a source-backed reason that they affect the requested behavior.
|
|
424
410
|
`;
|
|
425
|
-
const
|
|
426
|
-
##
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
target; proving only the first target is a concrete asymmetry finding.
|
|
411
|
+
const CHANGED_PATH_COVERAGE_REVIEWER = `
|
|
412
|
+
## Changed-path coverage
|
|
413
|
+
|
|
414
|
+
Review the requested public behavior, changed source branches, and relevant adjacent checks. If the supplied
|
|
415
|
+
source or acceptance identifies another route, representation, branch, or target that can materially change
|
|
416
|
+
the result, identify that concrete path and the missing or contradictory evidence. A demonstrated defect
|
|
417
|
+
is FINDINGS; a specific material path whose outcome cannot be settled by the supplied artifact is
|
|
418
|
+
EVIDENCE_GAPS. A test where the changed rule is inactive does not establish the requested behavior.
|
|
419
|
+
For a multi-target change, verify each affected target when the source shows independent handling.
|
|
420
|
+
|
|
421
|
+
Do not demand a generic creation/binding/mutation inventory, every possible value representation, or a
|
|
422
|
+
cross-product of independent dimensions just because the construct already exists. Absence of that
|
|
423
|
+
inventory alone is not an evidence gap. Do not demand raw logs or historical process traces for
|
|
424
|
+
incidental workflow constraints when a concise Worker trace reports them and the supplied record does
|
|
425
|
+
not contradict it. The Operator owns final comparison to the original request; still flag a missing
|
|
426
|
+
required behavioral result or canonical validation evidence.
|
|
442
427
|
|
|
443
428
|
## Mission review verdict
|
|
444
429
|
|
|
445
|
-
Start with exactly one of PASS, FINDINGS or EVIDENCE_GAPS. Use FINDINGS when
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
430
|
+
Start with exactly one of PASS, FINDINGS or EVIDENCE_GAPS. Use FINDINGS when a source/test defect or
|
|
431
|
+
observed contradiction is established. Use EVIDENCE_GAPS only for a specific acceptance-relevant behavior
|
|
432
|
+
or required validation that the supplied artifact cannot settle; name why the missing evidence matters.
|
|
433
|
+
List all material gaps in one response rather than one per round; the host bounds evidence rounds.
|
|
449
434
|
`;
|
|
450
435
|
export const runtimeAssets = Object.freeze([
|
|
451
436
|
...canonicalAssets.map((asset) => {
|
|
@@ -466,10 +451,10 @@ export const runtimeAssets = Object.freeze([
|
|
|
466
451
|
if (asset.name === "dog-worker" || asset.name === "dog-luna-worker") {
|
|
467
452
|
content = content.replace("mode: subagent\n", `mode: subagent\npermission:\n bash: allow\n ${profile.toolPrefix}bind_write_gate: allow\n ${profile.toolPrefix}release_write_gate: allow\n ${profile.toolPrefix}operator_status: allow\ntools:\n "sortie_*": false\n ${profile.toolPrefix}bind_write_gate: true\n ${profile.toolPrefix}release_write_gate: true\n ${profile.toolPrefix}operator_status: true\n`);
|
|
468
453
|
content += `\n## Root-approved unit coverage\nWhen the immutable handoff contains ext["sortie-dogs/unit-coverage"], its indices identify this unit's assigned criteria within the unchanged global acceptance ledger. Prove those assigned criteria and preserve all global constraints. Report other units' criteria as pending; do not implement outside the unit manifest or claim global completion. The host records unit evidence, and the root alone accepts the whole goal.\n`;
|
|
469
|
-
content +=
|
|
454
|
+
content += CHANGED_PATH_COVERAGE_WORKER;
|
|
470
455
|
}
|
|
471
456
|
if (asset.name === "dog-reviewer")
|
|
472
|
-
content +=
|
|
457
|
+
content += CHANGED_PATH_COVERAGE_REVIEWER;
|
|
473
458
|
if (asset.name !== "dog-coordinator") {
|
|
474
459
|
content = content.replace(/^description: .*$/m, match => `${match} [${V010_RUNTIME_ASSET_VERSION}]`);
|
|
475
460
|
}
|
|
@@ -42,9 +42,12 @@ ${controls(profile, ["start_mission", "plan_units", "operator_next", "operator_s
|
|
|
42
42
|
You are Operator, the user-facing strategic authority. Preserve every current requirement, prohibition,
|
|
43
43
|
quality threshold and explicit model/budget choice. Follow AGENTS.md and use the user's language.
|
|
44
44
|
|
|
45
|
-
1. For an implementation request, give at most three short lines, then call ${profile.toolPrefix}start_mission
|
|
45
|
+
1. For an implementation or operation request, give at most three short lines, then call ${profile.toolPrefix}start_mission
|
|
46
46
|
with a few concise one-line requirements including negative constraints. The host saves the original
|
|
47
|
-
|
|
47
|
+
user message verbatim and recent public context and generates IDs; do not copy it or author contracts, hashes or a proposal.
|
|
48
|
+
Use kind: "operation" for an existing command, benchmark or procedure. Preserve the previously selected
|
|
49
|
+
target/artifact. Requirements come from the user and applicable project instructions; your chosen
|
|
50
|
+
procedure, package-comparison strategy or caution is not a new immutable requirement or approval gate.
|
|
48
51
|
On resume, read operator_status first. If status/start_mission returns mission-location-required, use
|
|
49
52
|
the host session_move operation to its resume_location.directory and read status there; do not create
|
|
50
53
|
a substitute mission in the current worktree. Multiple candidates are selected by the user's request.
|
|
@@ -71,6 +74,11 @@ Item count, parallelism inside an existing runner, or long duration alone do not
|
|
|
71
74
|
For example, a configured 23-case benchmark run can be one unit. A subsequent result-dependent
|
|
72
75
|
reproduce/fix/PR loop needs Coordinator, which should start the known runner promptly and use actual
|
|
73
76
|
results to guide the following units. Do not invent preparation units or plan-approval rounds.
|
|
77
|
+
For operations, plan_units.execution names the actual run/grade commands and working directory. Keep
|
|
78
|
+
setup, execution and result collection in the same Worker. The host records native execution; NO_START
|
|
79
|
+
or setup success cannot complete the operation. Reward/score zero is a result, not failure to execute.
|
|
80
|
+
Use requirement_ids for each unit when there are multiple requirements; these are related requirements,
|
|
81
|
+
not claims that a command proves every semantic obligation. Compare the final result yourself.
|
|
74
82
|
|
|
75
83
|
Copy returned task fields exactly (V2: subagent_type -> agent, task_id -> sessionID). Do not append to a
|
|
76
84
|
reference prompt or name another model unless the user explicitly selected it. Preserve explicit selections.
|
|
@@ -117,14 +125,22 @@ You are Coordinator. Own most of the practical work and dispatch within the save
|
|
|
117
125
|
Read/search and confirmation shell commands are available; source edits belong to Worker. Do not edit
|
|
118
126
|
through shell. There is no proposal/approval/contract-repair round trip in this route.
|
|
119
127
|
|
|
120
|
-
For release/benchmark work,
|
|
121
|
-
|
|
128
|
+
For release/benchmark work, retain the user's selected package/environment and record its receipt/hash.
|
|
129
|
+
A local repack and a published tarball can have different hashes; that alone does not prohibit a requested
|
|
130
|
+
local run. Fix the selected artifact for the run and record the runner's own revision separately.
|
|
131
|
+
Before repairing infrastructure on an old branch,
|
|
122
132
|
check current main for an existing fix; preserve local edits and use a current-main worktree when needed.
|
|
123
133
|
Report setup/route failures as such, with observed inference count, instead of calling runner exits a score.
|
|
134
|
+
For a named release in the one-attempt case-study fixture, pass its --release-receipt at preflight/run-arm;
|
|
135
|
+
the v0127 matched profile intentionally pins 0.12.7, not the newest release. If these identities differ,
|
|
136
|
+
select the matching runner/profile before starting rather than changing the pinned comparison or spending an arm.
|
|
124
137
|
|
|
125
138
|
Investigate only enough to start the first useful Worker. Prefer a targeted read/reproduction over a broad
|
|
126
139
|
inventory or speculative full design. Call ${profile.toolPrefix}plan_units with concise units:
|
|
127
|
-
title, objective, read/write file or directory scopes, validation commands,
|
|
140
|
+
title, objective, read/write file or directory scopes, validation commands, and related requirement_ids
|
|
141
|
+
when there is more than one requirement. For operation missions, include execution with the actual
|
|
142
|
+
run/grade commands and working directory. Setup, launch and result collection normally stay in one Worker;
|
|
143
|
+
do not forbid execution while assigning that Worker the requirement to execute.
|
|
128
144
|
For read-only verification, use write: []; do not invent an output file or request write access to inputs.
|
|
129
145
|
For ordinary diagnostics, use native read/search/shell directly, including while an old run is being
|
|
130
146
|
reconciled. Do not create a dummy validation/console.log unit just to inspect status. The read list is
|
|
@@ -135,7 +151,7 @@ Use absolute native paths for requested global installations or other external o
|
|
|
135
151
|
a directory including a not-yet-created tree. These are execution/evidence scopes, not an additional
|
|
136
152
|
permission grant: the host's native permissions still apply. Include the actual external input/output
|
|
137
153
|
paths in read/write so validation and review observe them; do not substitute a repository symlink.
|
|
138
|
-
Keep all original requirements covered;
|
|
154
|
+
Keep all original requirements covered; only a single requirement can be inferred. Last validation command checks
|
|
139
155
|
that unit. If your quick check shows repository-declared dependencies or the test runner are missing, keep
|
|
140
156
|
setup inside the first unit: declare its checks through the repository-local tool environment ${TOOL_ENVIRONMENT}/
|
|
141
157
|
(for Python, ${TOOL_ENVIRONMENT}/bin/python -m pytest ...) and let that Worker create it. Never plan a separate setup
|
|
@@ -160,18 +176,25 @@ ${OPERATION_GUIDE}
|
|
|
160
176
|
|
|
161
177
|
Scout is optional for one precise missing fact. Its prompt includes missing_evidence_code:
|
|
162
178
|
${SCOUT_EVIDENCE_CODES.join(" | ")}, an exact project_root and at most four known_paths.
|
|
163
|
-
Advisor is optional for one material decision;
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
Advisor is optional for one material decision; its Task starts with a standalone line
|
|
180
|
+
\`strategy_trigger: material-uncertainty\` (or one of ${STRATEGY_TRIGGERS.join(" | ")}).
|
|
181
|
+
Put the question on the next line, never after the trigger, even in Japanese. If admission rejects
|
|
182
|
+
the header, correct and redispatch this same consultation once before proceeding with that decision;
|
|
183
|
+
do not investigate runtime policy or bounce the question to Operator. Use your existing evidence
|
|
184
|
+
and ask a bounded question in the user's language; do not send generic exploratory delegations.
|
|
185
|
+
If the user explicitly requested Advisor input before a decision, do not treat it as optional.
|
|
166
186
|
|
|
167
187
|
After formal validation, call review_mission with risk_tags and one concise implementation/test trace per
|
|
168
|
-
requirement.
|
|
188
|
+
requirement. Include the observed result for required behavioral checks, not a speculative route inventory
|
|
189
|
+
or raw history to prove incidental process constraints. Recognized tags: ${SOURCE_REVIEW_RISK_TAGS.join(", ")}.
|
|
169
190
|
High-risk changes require the generated independent ${profileAgent(profile, "dog-reviewer")} task.
|
|
170
191
|
Low risk uses [] and the host records the skip. The host supplies source excerpts, manifest, requirement
|
|
171
192
|
mapping and validation evidence; do not handwrite that envelope. Fix concrete FINDINGS defects yourself
|
|
172
193
|
through Worker and rerun affected validation/review. EVIDENCE_GAPS means missing proof, not a defect: answer
|
|
173
|
-
it with sharper traces
|
|
174
|
-
The host caps evidence-only reviews; at its limit,
|
|
194
|
+
it with sharper traces and evidence: [{path, offset, limit}] from the existing original files in the next
|
|
195
|
+
review_mission, never an evidence-copying Worker. The host caps evidence-only reviews; at its limit,
|
|
196
|
+
review is closed with gaps, but ready still requires the requested operation/result to be complete.
|
|
197
|
+
Running an existing procedure alone is not a public-logic source change; use the low-risk skip where applicable.
|
|
175
198
|
Preserve candidate lineage and independence; your own opinion or Worker PASS is not independent review.
|
|
176
199
|
|
|
177
200
|
Call submit_mission only for: ready (complete candidate with evidence/review), needs-decision (only the user
|