sortie-dogs 0.12.24 → 0.12.25
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 +1 -1
- package/dist/core/operator-mission.js +6 -5
- package/dist/plugin/profiled.js +18 -7
- package/dist/plugin/validation-scratch.d.ts +5 -0
- package/dist/plugin/validation-scratch.js +51 -0
- package/dist/runtime-mission-assets.js +18 -0
- 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.25-mission-continuity-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.25-mission-continuity-v1";
|
|
@@ -174,7 +174,7 @@ export declare class OperatorMissionRuntime {
|
|
|
174
174
|
constructor(projectRoot: string, profile: RuntimeProfile);
|
|
175
175
|
private file;
|
|
176
176
|
private load;
|
|
177
|
-
/** Recover
|
|
177
|
+
/** Recover non-replacement continuations only; an explicit replacement must link the current cancelled run. */
|
|
178
178
|
private loadMission;
|
|
179
179
|
/** Find exactly one archived cancelled mission that owned this run; ambiguity never grants recovery. */
|
|
180
180
|
archivedRun(root: string, runID: string): Promise<OperatorMission | undefined>;
|
|
@@ -113,10 +113,10 @@ export class OperatorMissionRuntime {
|
|
|
113
113
|
throw error;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
/** Recover
|
|
116
|
+
/** Recover non-replacement continuations only; an explicit replacement must link the current cancelled run. */
|
|
117
117
|
async loadMission(root) {
|
|
118
118
|
const state = await this.load(this.file(root));
|
|
119
|
-
if (!state || state.supersededRunID !== undefined || state.runID !== null ||
|
|
119
|
+
if (!state || state.supersededRunID !== undefined || state.runID !== null || state.requirementsReplaced ||
|
|
120
120
|
!["open", "running", "submitted", "cancelled"].includes(state.phase) || state.requests.length === 0)
|
|
121
121
|
return state;
|
|
122
122
|
const directory = join(this.projectRoot, this.profile.stateDirectory, "missions");
|
|
@@ -241,8 +241,9 @@ export class OperatorMissionRuntime {
|
|
|
241
241
|
}
|
|
242
242
|
if (previous)
|
|
243
243
|
await this.save(this.file(root, `.${previous.id}`), previous);
|
|
244
|
-
//
|
|
245
|
-
//
|
|
244
|
+
// Explicit replacement links the latest cancelled run even when a user narrows the
|
|
245
|
+
// request in the same turn. Ordinary same-turn continuation still retains acceptance.
|
|
246
|
+
// A cancelled standalone/legacy run has no mission-owned runID; only replacement links it.
|
|
246
247
|
const predecessor = previous?.runID ?? previous?.supersededRunID ??
|
|
247
248
|
(replaceRequirements ? options.cancelledRunID : undefined);
|
|
248
249
|
const state = { version: "0.12", id: `mission-${randomUUID()}`, root, requests: [request],
|
|
@@ -252,7 +253,7 @@ export class OperatorMissionRuntime {
|
|
|
252
253
|
...(replaceRequirements ? { requirementsReplaced: true } : {}),
|
|
253
254
|
coordinator: null, callID: null, dispatchOpen: false, runID: null, plans: 0, progress: [], submission: null,
|
|
254
255
|
...((previous === undefined || previous.phase === "cancelled") &&
|
|
255
|
-
(previous?.runID === null || previous === undefined || request.id !== previous.requests[0]?.id) && predecessor
|
|
256
|
+
(replaceRequirements || previous?.runID === null || previous === undefined || request.id !== previous.requests[0]?.id) && predecessor
|
|
256
257
|
? { supersededRunID: predecessor } : {}) };
|
|
257
258
|
await this.save(this.file(root), state);
|
|
258
259
|
return state;
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -17,6 +17,7 @@ import { MISSION_EVIDENCE_GAP_REVIEW_LIMIT, OperatorMissionRuntime, missionPacke
|
|
|
17
17
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
18
18
|
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionReviewBaseline, missionReviewSource } from "./mission-review.js";
|
|
19
19
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
20
|
+
import { prepareValidationScratch } from "./validation-scratch.js";
|
|
20
21
|
import { SOURCE_REVIEW_RISK_TAGS } from "../core/consultation.js";
|
|
21
22
|
import { createHash, randomUUID } from "node:crypto";
|
|
22
23
|
import { proposeTerminalRescue } from "../core/terminal-rescue-policy.js";
|
|
@@ -580,10 +581,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
580
581
|
} : {}) };
|
|
581
582
|
}
|
|
582
583
|
function sourceReconciliationRequired(mission, previous) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
584
|
+
if (previous?.phase !== "cancelled" || ["completed", "cancelled"].includes(mission.phase) || mission.runID !== null)
|
|
585
|
+
return false;
|
|
586
|
+
if (mission.supersededRunID !== undefined)
|
|
587
|
+
return mission.supersededRunID !== previous.runID;
|
|
588
|
+
return mission.requirementsReplaced === true || previous.sourceRefs[0] !== `user:${mission.requests[0]?.id}` ||
|
|
589
|
+
!previous.sourceRefs.every(ref => mission.requests.some(request => ref === `user:${request.id}`));
|
|
587
590
|
}
|
|
588
591
|
async function missionAuthority(id) {
|
|
589
592
|
const root = await rootFor(id);
|
|
@@ -674,7 +677,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
674
677
|
if (sourceReconciliationRequired(mission, run))
|
|
675
678
|
return { ...packet, status: "mission-source-reconciliation-required",
|
|
676
679
|
next_action: mission.dispatchOpen ? "The Coordinator Task is still active. Do not redispatch; wait for its native completion and reconcile via operator_status."
|
|
677
|
-
: `The
|
|
680
|
+
: `The mission is not linked to the current cancelled run. Do not dispatch or repeat plan_units. ` +
|
|
678
681
|
`If these saved requirements reflect the user's changed or narrowed scope, Operator: call ${profile.toolPrefix}start_mission ` +
|
|
679
682
|
`with intent=replace and the exact requirements array shown here. The host repairs this mission in place, retains spend, ` +
|
|
680
683
|
`and verifies old children before preparing a Worker. Otherwise obtain the user's scope decision.` };
|
|
@@ -1696,7 +1699,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1696
1699
|
return JSON.stringify({ ...missionDispatchPacket(mission, previous),
|
|
1697
1700
|
next_action: `Coordinator: do not repeat plan_units. Call ${submitMission} with status=blocked and report the saved ` +
|
|
1698
1701
|
`requirements to Operator. Operator can relink this mission in place via ${startMission} intent=replace ` +
|
|
1699
|
-
`when they match the user's changed scope; no Worker can start before that decision.` });
|
|
1702
|
+
`with those exact requirements when they match the user's changed scope; no Worker can start before that decision.` });
|
|
1700
1703
|
mission = await retainCancelledMissionAcceptance(root, mission, previous);
|
|
1701
1704
|
let operation = mission.execution;
|
|
1702
1705
|
// Models can serialize an optional operation field as an empty object for a normal edit.
|
|
@@ -1780,7 +1783,15 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1780
1783
|
item.execution = operation;
|
|
1781
1784
|
}
|
|
1782
1785
|
});
|
|
1783
|
-
|
|
1786
|
+
const next = await operators.next(root, actor);
|
|
1787
|
+
if (!record(next) || !record(next.task))
|
|
1788
|
+
return JSON.stringify(next);
|
|
1789
|
+
const setup = await prepareValidationScratch(input.directory, plan.units.flatMap(unit => unit.validation));
|
|
1790
|
+
return JSON.stringify(setup.prepared_directories.length || setup.unprepared_directories.length
|
|
1791
|
+
? { ...next, validation_setup: setup, ...(setup.unprepared_directories.length ? {
|
|
1792
|
+
next_action: "Before dispatch, correct or prepare the listed in-project TMPDIR directories. Keep this plan and its budget; no new approval or validation run is needed."
|
|
1793
|
+
} : {}) }
|
|
1794
|
+
: next);
|
|
1784
1795
|
}
|
|
1785
1796
|
tools[planUnits] = { description: "Coordinator (or single-unit Fast-lane Operator): declare useful units, then dispatch the returned Worker immediately. Host generates IDs, handoff, manifest and proof mapping. Keep every original requirement covered. Use write: [] for read-only verification; use dir/** for directory outputs including not-yet-created trees. Native absolute paths support global installs and external outputs under host permissions; include their actual paths in read/write for evidence. Final validation command in each unit proves that unit; read-only diagnostic commands need no registration. Recalling with reason replaces settled work within unchanged requirements and cumulative budget; include required scope extensions here. Rejected budget, contract or control-storage preparation preserves the existing run so you can correct the plan directly.",
|
|
1786
1797
|
args: { units: { type: "array", minItems: 1, maxItems: 32, items: { type: "object", additionalProperties: false,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Prepare only an explicitly declared, in-project TMPDIR before a Worker starts validation. */
|
|
2
|
+
export declare function prepareValidationScratch(directory: string, commands: readonly string[]): Promise<{
|
|
3
|
+
prepared_directories: string[];
|
|
4
|
+
unprepared_directories: string[];
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { lstat, mkdir } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, resolve, win32 } from "node:path";
|
|
3
|
+
import { createProjectPaths } from "./gate.js";
|
|
4
|
+
import { RUNTIME_PROFILES } from "../core/runtime-profile.js";
|
|
5
|
+
const controlDirectories = new Set([".git", ".opencode", ...Object.values(RUNTIME_PROFILES).map(profile => profile.stateDirectory)]);
|
|
6
|
+
/** Prepare only an explicitly declared, in-project TMPDIR before a Worker starts validation. */
|
|
7
|
+
export async function prepareValidationScratch(directory, commands) {
|
|
8
|
+
const prepared_directories = [], unprepared_directories = [];
|
|
9
|
+
if (!commands.some(command => /(?:^|\s)TMPDIR=/u.test(command)))
|
|
10
|
+
return { prepared_directories, unprepared_directories };
|
|
11
|
+
const project = await createProjectPaths(directory);
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const command of commands) {
|
|
14
|
+
for (const match of command.matchAll(/(?:^|\s)TMPDIR=(?:"([^"]+)"|'([^']+)'|([^\s"']+))/gu)) {
|
|
15
|
+
const declared = match[1] ?? match[2] ?? match[3];
|
|
16
|
+
// WSL's default drive mount and a native Windows project refer to the same directory.
|
|
17
|
+
// Other mounts are not inferred from command text.
|
|
18
|
+
const wsl = process.platform === "win32" ? /^\/mnt\/([a-z])\/(.+)$/iu.exec(declared) : null;
|
|
19
|
+
const target = wsl ? win32.resolve(`${wsl[1].toUpperCase()}:\\`, wsl[2].replaceAll("/", "\\"))
|
|
20
|
+
: isAbsolute(declared) ? resolve(declared) : null;
|
|
21
|
+
if (!target)
|
|
22
|
+
continue;
|
|
23
|
+
const path = relative(project.root, target).replaceAll("\\", "/");
|
|
24
|
+
if (!path || path === ".." || path.startsWith("../") || seen.has(path))
|
|
25
|
+
continue;
|
|
26
|
+
seen.add(path);
|
|
27
|
+
if (controlDirectories.has(path.split("/")[0]) || !await project.contains(target).catch(() => false)) {
|
|
28
|
+
unprepared_directories.push(path);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const stat = await lstat(target).catch(error => {
|
|
33
|
+
if (error.code === "ENOENT")
|
|
34
|
+
return null;
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
37
|
+
if (stat?.isDirectory())
|
|
38
|
+
continue;
|
|
39
|
+
if (stat)
|
|
40
|
+
throw new Error("path is not a directory");
|
|
41
|
+
await mkdir(target, { recursive: true });
|
|
42
|
+
prepared_directories.push(path);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Existing Worker dispatch remains available; the Coordinator sees the missing setup.
|
|
46
|
+
unprepared_directories.push(path);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { prepared_directories, unprepared_directories };
|
|
51
|
+
}
|
|
@@ -173,6 +173,13 @@ Do not reconstruct or prepend the absolute workspace path. Preserve explicitly r
|
|
|
173
173
|
For release/benchmark work, retain the user's selected package/environment and record its receipt/hash.
|
|
174
174
|
A local repack and a published tarball can have different hashes; that alone does not prohibit a requested
|
|
175
175
|
local run. Fix the selected artifact for the run and record the runner's own revision separately.
|
|
176
|
+
Distinguish an outer benchmark attempt from the Worker dispatches inside it. If the user asked for no
|
|
177
|
+
automatic retries, do not start another Worker or replacement run for an unchanged failed task. Report
|
|
178
|
+
the host's actual attempt history and model observations, not only the outer runner's retry counter.
|
|
179
|
+
If a requested branch base is called 'main' but the snapshot has only 'master', inspect the intended
|
|
180
|
+
base commit. When 'master' names that same commit and the ref spelling itself is not required, use
|
|
181
|
+
the existing ref as start_ref in a lifecycle plan and disclose the substitution. Do not ask for
|
|
182
|
+
approval solely over a name; a genuinely different or unknown base needs a decision.
|
|
176
183
|
Before repairing infrastructure on an old branch,
|
|
177
184
|
check current main for an existing fix; preserve local edits and use a current-main worktree when needed.
|
|
178
185
|
Report setup/route failures as such, with observed inference count, instead of calling runner exits a score.
|
|
@@ -215,6 +222,11 @@ ${profileAgent(profile, "dog-worker")} task verbatim, in foreground. V2 maps sub
|
|
|
215
222
|
task_id to sessionID. Do not insert model overrides unless the user explicitly selected them.
|
|
216
223
|
|
|
217
224
|
After each Worker returns, use its actual report and host evidence. Continue pending units with operator_next.
|
|
225
|
+
If a Worker returns process-defect with no formal validation evidence, inspect the specific missing
|
|
226
|
+
execution/proof route before any new dispatch. A command run through a custom container tool is not
|
|
227
|
+
native shell validation merely because its own output says PASS. If that route is unchanged, report
|
|
228
|
+
the blocker to Operator instead of creating another run or Worker with the same defect. Correct a
|
|
229
|
+
recoverable route within the current request and budget; this is not a new approval requirement.
|
|
218
230
|
If plan_units returns mission-source-reconciliation-required, do not retry the same plan. The
|
|
219
231
|
root-only Operator must reconcile the prior cancelled run. Submit status=blocked with the saved
|
|
220
232
|
requirements and exact host diagnostic, then return; do not declare a user-only decision when
|
|
@@ -304,6 +316,12 @@ Use ${profile.toolPrefix}operator_status when the task needs native runtime iden
|
|
|
304
316
|
remaining budget. It is a read-only observation available to Worker; do not request a separate unit or
|
|
305
317
|
Coordinator transcription just to obtain it. It does not grant plan, dispatch or completion authority.
|
|
306
318
|
|
|
319
|
+
If the requested new branch is from 'main' but this source snapshot has only the checked-out
|
|
320
|
+
default 'master' at the intended base commit, use that existing ref as the branch base when the
|
|
321
|
+
ref spelling itself is not required. Report the substitution, not a user-only decision. If the
|
|
322
|
+
base commit differs or cannot be identified, report the ambiguity. Do not bypass a host Git
|
|
323
|
+
lifecycle, rewrite history or alter an existing branch.
|
|
324
|
+
|
|
307
325
|
Read/search and read-only investigation commands are unrestricted. Use targeted reproduction/diagnosis
|
|
308
326
|
without registering every exploratory command. All writes, generated/transient files and cleanup stay
|
|
309
327
|
inside the unit's file/directory scopes, except the tool environment below. Do not write outside them through
|