sortie-dogs 0.12.6 → 0.12.7
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/cli/main.js +8 -0
- package/dist/core/initialize.d.ts +9 -0
- package/dist/core/initialize.js +28 -0
- package/dist/core/operator-mission.d.ts +12 -1
- package/dist/core/operator-mission.js +19 -2
- package/dist/core/operator-runtime.d.ts +3 -1
- package/dist/core/operator-runtime.js +24 -0
- package/dist/plugin/index.js +11 -2
- package/dist/plugin/mission-review.js +3 -1
- package/dist/plugin/profiled.js +19 -6
- package/dist/plugin/runtime-bridge.d.ts +1 -0
- package/dist/runtime-assets-v010.js +9 -2
- package/dist/runtime-mission-assets.d.ts +2 -0
- package/dist/runtime-mission-assets.js +21 -6
- 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.7-review-convergence-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.7-review-convergence-v1";
|
package/dist/cli/main.js
CHANGED
|
@@ -258,6 +258,14 @@ export async function run(argv) {
|
|
|
258
258
|
if (initialized.preservedLegacyPaths.length > 0) {
|
|
259
259
|
process.stdout.write(`Preserved legacy runtime files: ${initialized.preservedLegacyPaths.join(", ")}.\n`);
|
|
260
260
|
}
|
|
261
|
+
const override = profile === "v010"
|
|
262
|
+
? await initializer.coordinatorModelOverride(global ? target : resolve(initArgs[0] ?? process.cwd()), global).catch(() => undefined)
|
|
263
|
+
: undefined;
|
|
264
|
+
if (override) {
|
|
265
|
+
process.stderr.write(`Warning: ${override.path} routes dogs-coordinator to ${override.model}. ` +
|
|
266
|
+
`Sortie-dogs expects ${initializer.V010_COORDINATOR_MODEL}; a gpt-6-luna-fast Coordinator was measured to repeat plans and reviews until timeout. ` +
|
|
267
|
+
"Remove the override unless it is intentional.\n");
|
|
268
|
+
}
|
|
261
269
|
return 0;
|
|
262
270
|
}
|
|
263
271
|
catch (error) {
|
|
@@ -11,6 +11,15 @@ export declare class ProjectInitializationError extends Error {
|
|
|
11
11
|
readonly code: ProjectInitializationErrorCode;
|
|
12
12
|
constructor(code: ProjectInitializationErrorCode, message: string, options?: ErrorOptions);
|
|
13
13
|
}
|
|
14
|
+
export declare const V010_COORDINATOR_MODEL = "openai/gpt-6-sol#xhigh";
|
|
15
|
+
/**
|
|
16
|
+
* Report a user config that routes the v0.10 Coordinator away from its packaged model. Measured Luna
|
|
17
|
+
* Coordinators repeated plans and review dispatches until timeout, so init surfaces it without editing it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function coordinatorModelOverride(root: string, global: boolean): Promise<{
|
|
20
|
+
path: string;
|
|
21
|
+
model: string;
|
|
22
|
+
} | undefined>;
|
|
14
23
|
/** Resolves the OpenCode global configuration directory without platform-specific paths. */
|
|
15
24
|
export declare function resolveGlobalConfigRoot(env?: NodeJS.ProcessEnv, home?: string): Promise<string>;
|
|
16
25
|
export declare function initializeProject(projectRoot?: string, profile?: RuntimeProfileId): Promise<InitializeProjectResult>;
|
package/dist/core/initialize.js
CHANGED
|
@@ -536,6 +536,34 @@ async function initializeRoot(requestedRoot, layout, installAssets = runtimeAsse
|
|
|
536
536
|
preservedLegacyPaths,
|
|
537
537
|
};
|
|
538
538
|
}
|
|
539
|
+
export const V010_COORDINATOR_MODEL = "openai/gpt-6-sol#xhigh";
|
|
540
|
+
/**
|
|
541
|
+
* Report a user config that routes the v0.10 Coordinator away from its packaged model. Measured Luna
|
|
542
|
+
* Coordinators repeated plans and review dispatches until timeout, so init surfaces it without editing it.
|
|
543
|
+
*/
|
|
544
|
+
export async function coordinatorModelOverride(root, global) {
|
|
545
|
+
const prefix = global ? "" : `${OPEN_CODE_DIRECTORY}/`;
|
|
546
|
+
for (const relativePath of [`${prefix}opencode.jsonc`, `${prefix}opencode.json`]) {
|
|
547
|
+
let content;
|
|
548
|
+
try {
|
|
549
|
+
content = await readFile(resolve(root, relativePath), "utf8");
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
if (error.code === "ENOENT")
|
|
553
|
+
continue;
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
const config = parse(content, [], { allowTrailingComma: true });
|
|
557
|
+
for (const key of ["agents", "agent"]) {
|
|
558
|
+
const agents = config?.[key];
|
|
559
|
+
const coordinator = agents !== null && typeof agents === "object" ? agents["dogs-coordinator"] : undefined;
|
|
560
|
+
const model = coordinator !== null && typeof coordinator === "object" ? coordinator.model : undefined;
|
|
561
|
+
if (typeof model === "string" && model !== V010_COORDINATOR_MODEL)
|
|
562
|
+
return { path: resolve(root, relativePath), model };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
539
567
|
/** Resolves the OpenCode global configuration directory without platform-specific paths. */
|
|
540
568
|
export async function resolveGlobalConfigRoot(env = process.env, home = homedir()) {
|
|
541
569
|
if (env.OPENCODE_CONFIG_DIR)
|
|
@@ -40,11 +40,22 @@ export interface OperatorMission {
|
|
|
40
40
|
risk: string[];
|
|
41
41
|
source: string;
|
|
42
42
|
task: OperatorTask | null;
|
|
43
|
-
verdict: "pending" | "PASS" | "findings" | "skipped-low-risk";
|
|
43
|
+
verdict: "pending" | "PASS" | "findings" | "evidence-gaps" | "skipped-low-risk";
|
|
44
44
|
result?: string;
|
|
45
45
|
child?: string;
|
|
46
|
+
/** Reviews on this mission that found only missing evidence; bounded by MISSION_EVIDENCE_GAP_REVIEW_LIMIT. */
|
|
47
|
+
evidenceGapReviews?: number;
|
|
46
48
|
};
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Evidence-only findings do not establish a defect. Each extra round costs a full Reviewer pass and often a
|
|
52
|
+
* Worker unit, so after this many evidence-only reviews the candidate may be submitted with the gaps listed.
|
|
53
|
+
*/
|
|
54
|
+
export declare const MISSION_EVIDENCE_GAP_REVIEW_LIMIT = 2;
|
|
55
|
+
/** Classify an independent Reviewer's first line. Anything else is a finding. */
|
|
56
|
+
export declare function missionReviewVerdict(text: string): "PASS" | "evidence-gaps" | "findings";
|
|
57
|
+
/** Whether the recorded review permits submission and acceptance of the current candidate. */
|
|
58
|
+
export declare function missionReviewAccepted(review: NonNullable<OperatorMission["review"]>): boolean;
|
|
48
59
|
/** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
|
|
49
60
|
export declare class OperatorMissionRuntime {
|
|
50
61
|
readonly projectRoot: string;
|
|
@@ -18,6 +18,20 @@ export function missionValidationCommand(command) {
|
|
|
18
18
|
const expression = `exec(${JSON.stringify(heredoc[4])})`;
|
|
19
19
|
return `${heredoc[1]} -c '${expression.replaceAll("'", "'\\''")}'`;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Evidence-only findings do not establish a defect. Each extra round costs a full Reviewer pass and often a
|
|
23
|
+
* Worker unit, so after this many evidence-only reviews the candidate may be submitted with the gaps listed.
|
|
24
|
+
*/
|
|
25
|
+
export const MISSION_EVIDENCE_GAP_REVIEW_LIMIT = 2;
|
|
26
|
+
/** Classify an independent Reviewer's first line. Anything else is a finding. */
|
|
27
|
+
export function missionReviewVerdict(text) {
|
|
28
|
+
return /^\s*PASS(?:\s|$)/u.test(text) ? "PASS" : /^\s*EVIDENCE_GAPS(?:\s|$)/u.test(text) ? "evidence-gaps" : "findings";
|
|
29
|
+
}
|
|
30
|
+
/** Whether the recorded review permits submission and acceptance of the current candidate. */
|
|
31
|
+
export function missionReviewAccepted(review) {
|
|
32
|
+
return review.verdict === "PASS" || review.verdict === "skipped-low-risk" ||
|
|
33
|
+
(review.verdict === "evidence-gaps" && (review.evidenceGapReviews ?? 0) >= MISSION_EVIDENCE_GAP_REVIEW_LIMIT);
|
|
34
|
+
}
|
|
21
35
|
/** Durable user intent and dispatch ownership. Execution/evidence still belong to the v0.10 engine. */
|
|
22
36
|
export class OperatorMissionRuntime {
|
|
23
37
|
projectRoot;
|
|
@@ -294,12 +308,15 @@ export function missionPacket(mission, run) {
|
|
|
294
308
|
submission: mission.submission, progress: mission.progress,
|
|
295
309
|
review: mission.review ? { risk_tags: mission.review.risk, verdict: mission.review.verdict,
|
|
296
310
|
source_fingerprint: mission.review.source, reviewer_session_id: mission.review.child ?? null,
|
|
297
|
-
result: mission.review.result ?? null
|
|
311
|
+
result: mission.review.result ?? null, evidence_gap_reviews: mission.review.evidenceGapReviews ?? 0,
|
|
312
|
+
evidence_gap_review_limit: MISSION_EVIDENCE_GAP_REVIEW_LIMIT, accepted: missionReviewAccepted(mission.review) } : null,
|
|
298
313
|
...(run ? { run_id: run.runID, status: run.phase, decision: run.decision,
|
|
299
314
|
units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
|
|
300
315
|
child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence })) } : {}),
|
|
301
316
|
next_action: run?.phase === "awaiting-decision" ? "Coordinator: correct the cause and call plan_units with the remaining work and all requirements; budget is cumulative."
|
|
302
|
-
: run?.phase === "awaiting-acceptance" ?
|
|
317
|
+
: run?.phase === "awaiting-acceptance" ? (mission.review?.verdict === "evidence-gaps" && !missionReviewAccepted(mission.review)
|
|
318
|
+
? "Coordinator: the Reviewer found only missing evidence. Supply it through review_mission traces (or at most one evidence-only unit) and re-review once; do not re-implement. At the evidence-gap limit, submit_mission ready with the gaps listed."
|
|
319
|
+
: "Coordinator: obtain required independent review, then submit_mission. Operator: compare all requirements with source/evidence before complete_mission.")
|
|
303
320
|
: "Coordinator: continue the next useful unit within original requirements. Return only a completion candidate, user-only decision, or scope/budget extension." };
|
|
304
321
|
}
|
|
305
322
|
/** Models forward a short capability, never recopy the host's evidence hashes and source packet. */
|
|
@@ -109,7 +109,7 @@ interface UnitState {
|
|
|
109
109
|
readonly task: OperatorTask;
|
|
110
110
|
readonly handoffPath: string;
|
|
111
111
|
readonly manifestPath: string;
|
|
112
|
-
|
|
112
|
+
hashes: readonly string[];
|
|
113
113
|
status: "pending" | "running" | "succeeded" | "failed" | "cancelled";
|
|
114
114
|
callID: string | null;
|
|
115
115
|
childSessionID: string | null;
|
|
@@ -332,6 +332,8 @@ export declare class OperatorRuntime {
|
|
|
332
332
|
packet(state: OperatorState): unknown;
|
|
333
333
|
continuationCheckpoint(root: string): Promise<string | undefined>;
|
|
334
334
|
private verifyControls;
|
|
335
|
+
/** Follow only the host's authorized parent-link repair; keep the durable control pin in sync. */
|
|
336
|
+
acknowledgeHostHandoffRepair(root: string, taskID: string, path: string, original: string, repaired: string): Promise<void>;
|
|
335
337
|
private gitPaths;
|
|
336
338
|
private pathAuthorized;
|
|
337
339
|
/**
|
|
@@ -1893,6 +1893,30 @@ export class OperatorRuntime {
|
|
|
1893
1893
|
if (contents.some((value, index) => hash(value) !== unit.hashes[index]))
|
|
1894
1894
|
throw new Error("operator-contract-changed");
|
|
1895
1895
|
}
|
|
1896
|
+
/** Follow only the host's authorized parent-link repair; keep the durable control pin in sync. */
|
|
1897
|
+
acknowledgeHostHandoffRepair(root, taskID, path, original, repaired) {
|
|
1898
|
+
return this.serial(root, async () => {
|
|
1899
|
+
const state = await this.read(root);
|
|
1900
|
+
const unit = state?.units.find(candidate => candidate.status === "running" && candidate.handoffPath === path &&
|
|
1901
|
+
/^task_id: (.+)$/m.exec(candidate.task.prompt)?.[1] === taskID);
|
|
1902
|
+
if (!unit)
|
|
1903
|
+
return; // The same host repair also serves non-Operator handoffs.
|
|
1904
|
+
if (hash(original) !== unit.hashes[0] || await readFile(path, "utf8") !== repaired) {
|
|
1905
|
+
throw new Error("operator-host-handoff-repair-unpinned");
|
|
1906
|
+
}
|
|
1907
|
+
const before = JSON.parse(original), after = JSON.parse(repaired);
|
|
1908
|
+
const oldLink = inspectAcceptanceContinuity(before).ledger;
|
|
1909
|
+
const newLink = inspectAcceptanceContinuity(after).ledger;
|
|
1910
|
+
if (!oldLink || !newLink || oldLink.task_id !== taskID || oldLink.parent_fingerprint !== "none" ||
|
|
1911
|
+
newLink.task_id !== taskID || newLink.fingerprint !== oldLink.fingerprint ||
|
|
1912
|
+
newLink.parent_fingerprint === "none" ||
|
|
1913
|
+
JSON.stringify({ ...before, ext: { ...before.ext, [ACCEPTANCE_CONTINUITY_EXTENSION]: { ...oldLink, parent_fingerprint: newLink.parent_fingerprint } } }, null, 2) + "\n" !== repaired) {
|
|
1914
|
+
throw new Error("operator-host-handoff-repair-invalid");
|
|
1915
|
+
}
|
|
1916
|
+
unit.hashes = [hash(repaired), ...unit.hashes.slice(1)];
|
|
1917
|
+
await this.save(state);
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1896
1920
|
gitPaths(output) {
|
|
1897
1921
|
return output.split("\0").filter(Boolean).map(path => normalizeRelativePath(path.replaceAll("\\", "/")));
|
|
1898
1922
|
}
|
package/dist/plugin/index.js
CHANGED
|
@@ -6953,7 +6953,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6953
6953
|
// Persist it so the worker's Read and later recovery see the same contract.
|
|
6954
6954
|
if (reservedParallelDescriptor === undefined && ledger.parent_fingerprint === "none" &&
|
|
6955
6955
|
(exactCarryForward || strictAppend)) {
|
|
6956
|
-
const
|
|
6956
|
+
const source = await readFile(handoffPaths[0], "utf8");
|
|
6957
|
+
const handoff = JSON.parse(source);
|
|
6957
6958
|
const validated = validateHandoffSchema(handoff);
|
|
6958
6959
|
const current = validated.ok ? inspectAcceptanceContinuity(validated.value).ledger : undefined;
|
|
6959
6960
|
if (current === undefined || JSON.stringify(current) !== JSON.stringify(ledger)) {
|
|
@@ -6963,7 +6964,15 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6963
6964
|
}
|
|
6964
6965
|
ledger = { ...ledger, parent_fingerprint: previous.fingerprint };
|
|
6965
6966
|
handoff.ext[ACCEPTANCE_CONTINUITY_EXTENSION] = ledger;
|
|
6966
|
-
|
|
6967
|
+
const repaired = `${JSON.stringify(handoff, null, 2)}\n`;
|
|
6968
|
+
await writeFile(handoffPaths[0], repaired);
|
|
6969
|
+
try {
|
|
6970
|
+
await input.runtimeBridge?.onHostHandoffRepaired?.(toolInput.sessionID, ledger.task_id, handoffPaths[0], source, repaired);
|
|
6971
|
+
}
|
|
6972
|
+
catch (error) {
|
|
6973
|
+
await writeFile(handoffPaths[0], source);
|
|
6974
|
+
throw error;
|
|
6975
|
+
}
|
|
6967
6976
|
await inspect(handoffPaths[0], undefined, { report: true });
|
|
6968
6977
|
}
|
|
6969
6978
|
if (ledger.parent_fingerprint !== previous.fingerprint || (!exactCarryForward && !strictAppend)) {
|
|
@@ -3,10 +3,12 @@ import { execFile } from "node:child_process";
|
|
|
3
3
|
import { lstat, readFile, readlink } from "node:fs/promises";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { TOOL_ENVIRONMENT } from "../runtime-mission-assets.js";
|
|
6
7
|
const exec = promisify(execFile);
|
|
7
8
|
/** Pin all scoped tracked/untracked source bytes, including deletions; display a bounded excerpt only. */
|
|
8
9
|
export async function missionReviewSource(directory, run) {
|
|
9
|
-
|
|
10
|
+
// The shared dependency environment is local tooling, never reviewed or pinned source.
|
|
11
|
+
const scopes = [...new Set(run.units.flatMap(unit => unit.unit.write)), `:(exclude)${TOOL_ENVIRONMENT}`];
|
|
10
12
|
const git = async (args) => (await exec("git", args, { cwd: directory, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
11
13
|
const names = await git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...scopes]);
|
|
12
14
|
const untracked = new Set((await git(["ls-files", "-z", "--others", "--exclude-standard", "--", ...scopes])).split("\0").filter(Boolean));
|
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 { extractWritePaths, normalizeCommand } from "./gate.js";
|
|
14
14
|
import { normalizeRelativePath } from "../core/path.js";
|
|
15
|
-
import { OperatorMissionRuntime, missionPacket, missionPlan, missionReviewTask, missionReviewTraces } from "../core/operator-mission.js";
|
|
15
|
+
import { MISSION_EVIDENCE_GAP_REVIEW_LIMIT, OperatorMissionRuntime, missionPacket, missionPlan, missionReviewAccepted, missionReviewTask, missionReviewTraces, missionReviewVerdict } from "../core/operator-mission.js";
|
|
16
16
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
17
17
|
import { missionReviewSource } from "./mission-review.js";
|
|
18
18
|
import { SOURCE_REVIEW_RISK_TAGS } from "../core/consultation.js";
|
|
@@ -284,6 +284,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
284
284
|
});
|
|
285
285
|
const runtimeBridge = {
|
|
286
286
|
profile, assetVersion,
|
|
287
|
+
onHostHandoffRepaired: (root, taskID, path, original, repaired) => operators.acknowledgeHostHandoffRepair(root, taskID, path, original, repaired),
|
|
287
288
|
continuationCheckpoint: async (root) => {
|
|
288
289
|
const mission = await missions.read(root);
|
|
289
290
|
if (mission && !["completed", "cancelled"].includes(mission.phase))
|
|
@@ -1193,7 +1194,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1193
1194
|
const task = risk.length === 0 ? null : { subagent_type: profileAgent(profile, "dog-reviewer"),
|
|
1194
1195
|
description: `🔎 ${run.units[0].unit.title}`, prompt: [
|
|
1195
1196
|
`candidate_id: ${mission.id}`, `review_phase: ${phase}`, "canonical_validation_exit: 0", `risk_tags: [${risk.join(", ")}]`,
|
|
1196
|
-
"Review this candidate independently. Use the language of the requirements/traces. Invoke no tools. First line: exactly PASS or
|
|
1197
|
+
"Review this candidate independently. Use the language of the requirements/traces. Invoke no tools. First line: exactly PASS, FINDINGS or EVIDENCE_GAPS.",
|
|
1198
|
+
"Use EVIDENCE_GAPS only when no finding establishes a source/test defect and every finding is proof the supplied artifact cannot settle, including process history later established on the base. Any concrete defect uses FINDINGS.",
|
|
1197
1199
|
`acceptance: ${JSON.stringify(run.acceptance)}`, `changedLogicSummary: ${JSON.stringify(traces)}`,
|
|
1198
1200
|
...run.acceptance.map((_, i) => `acceptance[${i}] -> changedLogicSummary[${i}]`),
|
|
1199
1201
|
`manifest: ${JSON.stringify(run.units.map(unit => unit.unit))}`, `sourceFingerprint: ${source.fingerprint}`,
|
|
@@ -1202,13 +1204,14 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1202
1204
|
].join("\n") };
|
|
1203
1205
|
const reviewed = await missions.update(root, item => {
|
|
1204
1206
|
item.review = { runID: run.runID, risk: risk, source: source.fingerprint,
|
|
1205
|
-
task, verdict: task ? "pending" : "skipped-low-risk", ...(mission.review?.child ? { child: mission.review.child } : {})
|
|
1207
|
+
task, verdict: task ? "pending" : "skipped-low-risk", ...(mission.review?.child ? { child: mission.review.child } : {}),
|
|
1208
|
+
...(mission.review?.evidenceGapReviews ? { evidenceGapReviews: mission.review.evidenceGapReviews } : {}) };
|
|
1206
1209
|
});
|
|
1207
1210
|
return JSON.stringify(task ? { status: "review-required", task: missionReviewTask(reviewed) } : { status: "skipped-low-risk" });
|
|
1208
1211
|
} };
|
|
1209
1212
|
async function assertMissionReview(root, mission) {
|
|
1210
1213
|
const run = await operators.required(root), review = mission.review;
|
|
1211
|
-
if (!review || review.runID !== run.runID || !
|
|
1214
|
+
if (!review || review.runID !== run.runID || !missionReviewAccepted(review) ||
|
|
1212
1215
|
review.source !== (await missionReviewSource(input.directory, run)).fingerprint)
|
|
1213
1216
|
throw new Error("mission-review-required-or-stale");
|
|
1214
1217
|
}
|
|
@@ -1736,13 +1739,23 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1736
1739
|
const history = child ? await messages(child) : [];
|
|
1737
1740
|
const last = [...history].reverse().find(message => (record(message.info) ? message.info.role : message.role) === "assistant");
|
|
1738
1741
|
const text = Array.isArray(last?.parts) ? last.parts.filter(record).filter(part => part.type === "text").map(part => part.text).join("\n") : String(raw);
|
|
1739
|
-
|
|
1742
|
+
const independent = who?.role === "dog-reviewer" && who.parent === ownership.actor;
|
|
1743
|
+
const reviewed = await missions.update(ownership.root, mission => {
|
|
1740
1744
|
if (mission.review) {
|
|
1741
1745
|
mission.review.result = text.slice(0, 16_000);
|
|
1742
|
-
|
|
1746
|
+
const verdict = missionReviewVerdict(text);
|
|
1747
|
+
mission.review.verdict = independent ? verdict : "findings";
|
|
1748
|
+
if (mission.review.verdict === "evidence-gaps")
|
|
1749
|
+
mission.review.evidenceGapReviews = (mission.review.evidenceGapReviews ?? 0) + 1;
|
|
1743
1750
|
mission.review.child = child;
|
|
1744
1751
|
}
|
|
1745
1752
|
});
|
|
1753
|
+
if (reviewed.review?.verdict === "evidence-gaps" && typeof output.output === "string") {
|
|
1754
|
+
const count = reviewed.review.evidenceGapReviews ?? 0;
|
|
1755
|
+
output.output += missionReviewAccepted(reviewed.review)
|
|
1756
|
+
? `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT} reached the limit. No defect was found; submit_mission ready is permitted. List the remaining evidence gaps in the summary.`
|
|
1757
|
+
: `\n\nHOST: evidence-gap review ${count}/${MISSION_EVIDENCE_GAP_REVIEW_LIMIT}. No defect was found; supply the requested evidence through review_mission traces (or at most one evidence-only unit) and re-review. Do not re-implement.`;
|
|
1758
|
+
}
|
|
1746
1759
|
}
|
|
1747
1760
|
taskOwners.delete(request.callID);
|
|
1748
1761
|
return;
|
|
@@ -27,6 +27,7 @@ export interface RuntimeBridge {
|
|
|
27
27
|
continuationCheckpoint?(rootSessionID: string): Promise<string | undefined>;
|
|
28
28
|
requiresExplicitAcceptance?(rootSessionID: string): Promise<boolean>;
|
|
29
29
|
ownsCanonicalValidation?(rootSessionID: string, unitID: string, childSessionID: string, command: string): Promise<boolean>;
|
|
30
|
+
onHostHandoffRepaired?(rootSessionID: string, taskID: string, handoffPath: string, original: string, repaired: string): Promise<void>;
|
|
30
31
|
allowsInvestigativeShell?(sessionID: string): Promise<boolean>;
|
|
31
32
|
onSerialSettlement?(settlement: SerialDispatchSettlement): Promise<void>;
|
|
32
33
|
onRootTerminal?(rootSessionID: string, receipt: GoalTerminalReceipt): Promise<void>;
|
|
@@ -432,13 +432,20 @@ routes separately from mutation routes; an enumeration justified only by the req
|
|
|
432
432
|
covers mutation routes while leaving creation routes unlisted, is incomplete. Require the value representations
|
|
433
433
|
that reach the rule to be listed too, and require each trace to exercise the form with the new rule active; a
|
|
434
434
|
trace whose case leaves the new rule inactive evidences only the pre-existing behavior. A missing enumeration, an
|
|
435
|
-
excluded form without a reason, or an enumerated form without a trace is
|
|
436
|
-
|
|
435
|
+
excluded form without a reason, or an enumerated form without a trace is never PASS. When the supplied
|
|
436
|
+
excerpts cannot settle the form and no defect is established, it is an evidence gap, not a FINDINGS defect.
|
|
437
437
|
|
|
438
438
|
Reject a matrix that lists independent syntax or dispatch dimensions but traces them only in isolation. Require
|
|
439
439
|
the material combinations that can select different branches, including cardinality with optional-clause
|
|
440
440
|
presence, scope, and value representation where applicable. For a multi-target route, require evidence for every
|
|
441
441
|
target; proving only the first target is a concrete asymmetry finding.
|
|
442
|
+
|
|
443
|
+
## Mission review verdict
|
|
444
|
+
|
|
445
|
+
Start with exactly one of PASS, FINDINGS or EVIDENCE_GAPS. Use FINDINGS when any finding establishes a
|
|
446
|
+
source/test defect or an observed behavior contradicting an acceptance item. Use EVIDENCE_GAPS when every finding
|
|
447
|
+
is proof the artifact cannot settle: a missing excerpt, trace, enumeration, or process history later established
|
|
448
|
+
on the base. List all gaps you see in one response rather than one per round; the host bounds evidence rounds.
|
|
442
449
|
`;
|
|
443
450
|
export const runtimeAssets = Object.freeze([
|
|
444
451
|
...canonicalAssets.map((asset) => {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type RuntimeProfile } from "./core/runtime-profile.ts";
|
|
2
|
+
/** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
|
|
3
|
+
export declare const TOOL_ENVIRONMENT = ".sortie-env";
|
|
2
4
|
export declare function missionOperatorContent(profile: RuntimeProfile, version: string): string;
|
|
3
5
|
export declare function missionCoordinatorContent(profile: RuntimeProfile, version: string): string;
|
|
4
6
|
export declare function missionWorkerContent(profile: RuntimeProfile): string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { profileAgent } from "./core/runtime-profile.js";
|
|
2
2
|
import { STRATEGY_TRIGGERS, SOURCE_REVIEW_RISK_TAGS } from "./core/consultation.js";
|
|
3
3
|
import { SCOUT_EVIDENCE_CODES } from "./core/scout-contract.js";
|
|
4
|
+
/** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
|
|
5
|
+
export const TOOL_ENVIRONMENT = ".sortie-env";
|
|
4
6
|
function controls(profile, names) {
|
|
5
7
|
return names.map(name => ` ${profile.toolPrefix}${name}: true`).join("\n");
|
|
6
8
|
}
|
|
@@ -36,8 +38,9 @@ quality threshold and explicit model/budget choice. Follow AGENTS.md and use the
|
|
|
36
38
|
unit boundaries, Worker/Scout/Advisor/independent Reviewer calls, write-scope extensions and corrections
|
|
37
39
|
within the original request and cumulative budget. Do not investigate or approve each unit at the root.
|
|
38
40
|
3. Compare the returned completion candidate against the original request, real source and observed evidence.
|
|
39
|
-
If incomplete, resume the SAME Coordinator with concrete feedback. If complete and required review passed
|
|
40
|
-
|
|
41
|
+
If incomplete, resume the SAME Coordinator with concrete feedback. If complete and required review passed
|
|
42
|
+
(or the host accepted it at the evidence-gap limit, with the gaps reported), call
|
|
43
|
+
${profile.toolPrefix}complete_mission. Only its succeeded receipt authorizes DONE.
|
|
41
44
|
|
|
42
45
|
Fast-lane exception: simple, low-risk, single-unit work with known files and validation may use
|
|
43
46
|
${profile.toolPrefix}plan_units directly after start_mission, then dispatch its exact Worker task.
|
|
@@ -91,7 +94,10 @@ Investigate only enough to start the first useful Worker. Prefer a targeted read
|
|
|
91
94
|
inventory or speculative full design. Call ${profile.toolPrefix}plan_units with concise units:
|
|
92
95
|
title, objective, read/write file or directory scopes, validation commands, optionally requirement_ids.
|
|
93
96
|
Keep all original requirements covered; omitted requirement_ids means all. Last validation command proves
|
|
94
|
-
that unit.
|
|
97
|
+
that unit. If your quick check shows repository-declared dependencies or the test runner are missing, keep
|
|
98
|
+
setup inside the first unit: declare its checks through the repository-local tool environment ${TOOL_ENVIRONMENT}/
|
|
99
|
+
(for Python, ${TOOL_ENVIRONMENT}/bin/python -m pytest ...) and let that Worker create it. Never plan a separate setup
|
|
100
|
+
unit or put ${TOOL_ENVIRONMENT}/ in a write scope. Host generates IDs, proof mapping, handoff, manifest and Task references. Dispatch the returned
|
|
95
101
|
${profileAgent(profile, "dog-worker")} task verbatim, in foreground. V2 maps subagent_type to agent and
|
|
96
102
|
task_id to sessionID. Do not insert model overrides unless the user explicitly selected them.
|
|
97
103
|
|
|
@@ -114,8 +120,10 @@ After formal validation, call review_mission with risk_tags and one concise impl
|
|
|
114
120
|
requirement. Recognized tags: ${SOURCE_REVIEW_RISK_TAGS.join(", ")}.
|
|
115
121
|
High-risk changes require the generated independent ${profileAgent(profile, "dog-reviewer")} task.
|
|
116
122
|
Low risk uses [] and the host records the skip. The host supplies source excerpts, manifest, requirement
|
|
117
|
-
mapping and validation evidence; do not handwrite that envelope. Fix concrete
|
|
118
|
-
Worker and rerun affected validation/review.
|
|
123
|
+
mapping and validation evidence; do not handwrite that envelope. Fix concrete FINDINGS defects yourself
|
|
124
|
+
through Worker and rerun affected validation/review. EVIDENCE_GAPS means missing proof, not a defect: answer
|
|
125
|
+
it with sharper traces in the next review_mission, or at most one evidence-only unit, never a re-implementation.
|
|
126
|
+
The host caps evidence-only reviews; at its limit, submit ready and list the remaining gaps.
|
|
119
127
|
Preserve candidate lineage and independence; your own opinion or Worker PASS is not independent review.
|
|
120
128
|
|
|
121
129
|
Call submit_mission only for: ready (complete candidate with evidence/review), needs-decision (only the user
|
|
@@ -140,7 +148,8 @@ The binding remains valid throughout this Task until return or a control/source
|
|
|
140
148
|
|
|
141
149
|
Read/search and read-only investigation commands are unrestricted. Use targeted reproduction/diagnosis
|
|
142
150
|
without registering every exploratory command. All writes, generated/transient files and cleanup stay
|
|
143
|
-
inside the unit's file/directory scopes. Do not write outside them through
|
|
151
|
+
inside the unit's file/directory scopes, except the tool environment below. Do not write outside them through
|
|
152
|
+
scripts or tools. If scope must
|
|
144
153
|
expand or a formal check must change, return the exact paths/command and reason to Coordinator; it can
|
|
145
154
|
approve an in-request extension immediately. Do not ask the user or delegate to another agent.
|
|
146
155
|
|
|
@@ -150,6 +159,12 @@ and separate shell calls; the host records actual command, source and exit. Diag
|
|
|
150
159
|
formal acceptance evidence. Do not repeat a failed command without a concrete source/setup correction or
|
|
151
160
|
repeat passed checks on unchanged source. Add meaningful tests only when needed by the change/request.
|
|
152
161
|
|
|
162
|
+
Missing repository-declared dependencies or test runner are setup, not a result. Make one bounded,
|
|
163
|
+
repository-documented setup attempt in the repository-local tool environment ${TOOL_ENVIRONMENT}/ (for Python:
|
|
164
|
+
python -m venv ${TOOL_ENVIRONMENT}, then install the declared dependencies with its pip), then run the checks.
|
|
165
|
+
Reuse an existing ${TOOL_ENVIRONMENT}/ and never delete it; it is local tooling, not a change, and needs no write
|
|
166
|
+
scope. Return to Coordinator for setup only when it is externally blocked or a formal command must change.
|
|
167
|
+
|
|
153
168
|
Return changed paths and concise criterion-level implementation/test/input evidence with the real check
|
|
154
169
|
results. Respect negative constraints and API success/error compatibility. A broad suite PASS does not
|
|
155
170
|
alone prove every requirement; leave unproven items explicit. Never claim other units or the whole mission
|