sortie-dogs 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/initialize.js +4 -3
- package/dist/core/mission-purpose.d.ts +42 -0
- package/dist/core/mission-purpose.js +56 -0
- package/dist/core/operator-mission.d.ts +4 -0
- package/dist/core/operator-mission.js +8 -0
- package/dist/plugin/declared-artifacts.d.ts +1 -0
- package/dist/plugin/declared-artifacts.js +16 -2
- package/dist/plugin/mission-review.js +9 -2
- package/dist/plugin/profiled.js +18 -1
- package/dist/runtime-mission-assets.d.ts +2 -0
- package/dist/runtime-mission-assets.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,8 @@ evidence-backed result, not more agents for their own sake.
|
|
|
19
19
|
[](https://www.npmjs.com/package/sortie-dogs)
|
|
20
20
|
[](LICENSE)
|
|
21
21
|
|
|
22
|
-
**Current release: [v0.15.
|
|
23
|
-
· [Release notes](docs/release-v0.15.
|
|
22
|
+
**Current release: [v0.15.3](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.15.3)**
|
|
23
|
+
· [Release notes](docs/release-v0.15.3.md)
|
|
24
24
|
· [Execution vision](docs/v0.14.0-execution-vision.md)
|
|
25
25
|
|
|
26
26
|
[When to use](#when-to-use-sortie-dogs) · [Quick start](#quick-start) · [Workflow](#mission-workflow) · [Models](#default-models) ·
|
|
@@ -78,7 +78,7 @@ Use an existing Codex CLI signed in with **ChatGPT**. On Windows, PowerShell **7
|
|
|
78
78
|
available; POSIX uses bash. Sortie does not install Codex, start login or use a metered API fallback.
|
|
79
79
|
|
|
80
80
|
```sh
|
|
81
|
-
npm install --save-dev sortie-dogs@0.15.
|
|
81
|
+
npm install --save-dev sortie-dogs@0.15.3
|
|
82
82
|
npx --no-install sortie-dogs codex init .
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -107,7 +107,7 @@ bridge. [Codex guide: SDK, permissions, progress, resume and manifest route](doc
|
|
|
107
107
|
Use **OpenCode V2**. In the target project:
|
|
108
108
|
|
|
109
109
|
```sh
|
|
110
|
-
npm install --save-dev sortie-dogs@0.15.
|
|
110
|
+
npm install --save-dev sortie-dogs@0.15.3
|
|
111
111
|
npx --no-install sortie-dogs init .
|
|
112
112
|
```
|
|
113
113
|
|
|
@@ -127,7 +127,7 @@ v0.10 is installed. [OpenCode setup and configuration](docs/configuration.md) ·
|
|
|
127
127
|
|
|
128
128
|
### Project Memory (OpenCode v010, opt-in)
|
|
129
129
|
|
|
130
|
-
With v0.15.
|
|
130
|
+
With v0.15.3 installed, run `sortie-dogs memory on .` once and reload
|
|
131
131
|
the project plugin. Ordinary public turns then become pending sources; the existing
|
|
132
132
|
Operator records semantic additions, corrections, withdrawals and decisions without
|
|
133
133
|
another save request or a dedicated model call per turn. New sessions recall the
|
|
@@ -284,7 +284,7 @@ Install the desired package version and rerun **your host's** initializer:
|
|
|
284
284
|
|
|
285
285
|
An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
|
|
286
286
|
update it. [Global/update instructions](docs/configuration.md#global-availability).
|
|
287
|
-
The current Mission asset marker is `0.15.
|
|
287
|
+
The current Mission asset marker is `0.15.3-external-review-links-v1`; the Codex skill marker is
|
|
288
288
|
`0.14.6-codex-skill-v1`. Installed markers alone do not prove a running host reloaded the new version.
|
|
289
289
|
|
|
290
290
|
There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
|
|
@@ -300,6 +300,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
|
|
|
300
300
|
[SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
|
|
301
301
|
- **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
|
|
302
302
|
[Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
|
|
303
|
+
- **Current release:** [v0.15.3](docs/release-v0.15.3.md).
|
|
303
304
|
- **Releases:** [v0.15.2](docs/release-v0.15.2.md) · [v0.15.1](docs/v0.15.1-migration.md) · [v0.15.0](docs/release-v0.15.0.md) · [v0.14.9](docs/release-v0.14.9.md) · [v0.14.8](docs/release-v0.14.8.md) · [v0.14.7](docs/release-v0.14.7.md) · [v0.14.6](docs/release-v0.14.6.md) · [v0.14.5](docs/release-v0.14.5.md) · [v0.14.4](docs/release-v0.14.4.md) · [v0.14.3](docs/release-v0.14.3.md) · [v0.14.2](docs/release-v0.14.2.md) · [v0.14.1](docs/release-v0.14.1.md) · [v0.14.0](docs/release-v0.14.0.md) · [v0.13.22](docs/release-v0.13.22.md) · [v0.13.21](docs/release-v0.13.21.md) · [v0.13.20](docs/release-v0.13.20.md) · [v0.13.19](docs/release-v0.13.19.md) · [v0.13.18](docs/release-v0.13.18.md) · [v0.13.17](docs/release-v0.13.17.md) · [v0.13.16](docs/release-v0.13.16.md) · [v0.13.15](docs/release-v0.13.15.md) · [v0.13.14](docs/release-v0.13.14.md) · [v0.13.13](docs/release-v0.13.13.md) · [v0.13.12](docs/release-v0.13.12.md) · [v0.13.11](docs/release-v0.13.11.md) · [v0.13.10](docs/release-v0.13.10.md) ·
|
|
304
305
|
[v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
|
|
305
306
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.16.0-purpose-aligned-v2";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.16.0-purpose-aligned-v2";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
package/dist/core/initialize.js
CHANGED
|
@@ -136,11 +136,12 @@ function classifyVersionTransition(installedValue, currentValue, v010Restore = f
|
|
|
136
136
|
const adjacentPreOneLine = installed.major === 0 && current.major === 0 &&
|
|
137
137
|
current.minor === installed.minor + 1;
|
|
138
138
|
// v0.11 used a different runtime. The v010 asset migration restores the 0.10
|
|
139
|
-
// preview into the 0.12–0.
|
|
139
|
+
// preview into the 0.12–0.16 Mission line without selecting a v0.11 execution path.
|
|
140
140
|
const restoredPreview = v010Restore && installed.major === 0 && installed.minor === 10 &&
|
|
141
|
-
current.major === 0 && [12, 13, 14, 15].includes(current.minor);
|
|
141
|
+
current.major === 0 && [12, 13, 14, 15, 16].includes(current.minor);
|
|
142
142
|
const retainedMission = v010Restore && installed.major === 0 &&
|
|
143
|
-
((installed.minor === 12 && current.minor === 14) || ([12, 13].includes(installed.minor) && current.minor === 15)
|
|
143
|
+
((installed.minor === 12 && current.minor === 14) || ([12, 13].includes(installed.minor) && current.minor === 15) ||
|
|
144
|
+
([12, 13, 14].includes(installed.minor) && current.minor === 16)) &&
|
|
144
145
|
current.major === 0;
|
|
145
146
|
return sameLine || adjacentPreOneLine || restoredPreview || retainedMission ? "compatible-update" : "incompatible";
|
|
146
147
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OperatorMission } from "./operator-mission.js";
|
|
2
|
+
export declare const PURPOSE_DECISIONS: readonly ["CONTINUE", "REUSE", "CHANGE_METHOD", "STOP_METHOD", "NEEDS_DECISION"];
|
|
3
|
+
export interface PurposeDecisionInput {
|
|
4
|
+
decision: typeof PURPOSE_DECISIONS[number];
|
|
5
|
+
requirement_ids: string[];
|
|
6
|
+
observed_outcome: string;
|
|
7
|
+
unmet_work: string[];
|
|
8
|
+
reason: string;
|
|
9
|
+
evidence_refs: string[];
|
|
10
|
+
affected_method?: string;
|
|
11
|
+
next_action: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PurposeDecision extends PurposeDecisionInput {
|
|
14
|
+
id: string;
|
|
15
|
+
mission_id: string;
|
|
16
|
+
run_id: string | null;
|
|
17
|
+
requirements_fingerprint: string;
|
|
18
|
+
recorded_at: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function purposeRequirementsFingerprint(mission: OperatorMission): string;
|
|
21
|
+
/** A bounded strategy observation, never an evidence receipt or an execution control. */
|
|
22
|
+
export declare function validatePurposeDecision(mission: OperatorMission, raw: unknown): asserts raw is PurposeDecisionInput;
|
|
23
|
+
export declare function appendPurposeDecision(mission: OperatorMission, raw: unknown): void;
|
|
24
|
+
export declare function purposeDecisionView(mission: OperatorMission): {
|
|
25
|
+
current: PurposeDecision[];
|
|
26
|
+
historical: PurposeDecision[];
|
|
27
|
+
application_status: string;
|
|
28
|
+
interpretation: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const purposeDecisionSchema: {
|
|
31
|
+
type: string;
|
|
32
|
+
"x-sortie-optional": boolean;
|
|
33
|
+
additionalProperties: boolean;
|
|
34
|
+
description: string;
|
|
35
|
+
required: string[];
|
|
36
|
+
properties: {
|
|
37
|
+
decision: {
|
|
38
|
+
type: string;
|
|
39
|
+
enum: ("CONTINUE" | "REUSE" | "CHANGE_METHOD" | "STOP_METHOD" | "NEEDS_DECISION")[];
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
export const PURPOSE_DECISIONS = ["CONTINUE", "REUSE", "CHANGE_METHOD", "STOP_METHOD", "NEEDS_DECISION"];
|
|
3
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
const line = (value) => typeof value === "string" && !!value.trim() && value.length <= 2000;
|
|
5
|
+
const lines = (value, min = 0) => Array.isArray(value) && value.length >= min && value.length <= 64 && value.every(line);
|
|
6
|
+
const keys = ["decision", "requirement_ids", "observed_outcome", "unmet_work", "reason", "evidence_refs", "affected_method", "next_action"];
|
|
7
|
+
function valid(value) {
|
|
8
|
+
return object(value) && PURPOSE_DECISIONS.includes(value.decision) &&
|
|
9
|
+
lines(value.requirement_ids, 1) && line(value.observed_outcome) && lines(value.unmet_work) && line(value.reason) &&
|
|
10
|
+
lines(value.evidence_refs) && (value.affected_method === undefined || line(value.affected_method)) && line(value.next_action) &&
|
|
11
|
+
(!["CHANGE_METHOD", "STOP_METHOD"].includes(String(value.decision)) || line(value.affected_method));
|
|
12
|
+
}
|
|
13
|
+
export function purposeRequirementsFingerprint(mission) {
|
|
14
|
+
return createHash("sha256").update(JSON.stringify(mission.requirements)).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
/** A bounded strategy observation, never an evidence receipt or an execution control. */
|
|
17
|
+
export function validatePurposeDecision(mission, raw) {
|
|
18
|
+
if (!valid(raw) || Object.keys(raw).some(key => !keys.includes(key)) ||
|
|
19
|
+
raw.requirement_ids.some(id => !mission.requirements.some(requirement => requirement.id === id))) {
|
|
20
|
+
throw new Error("mission-purpose-decision-invalid");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function appendPurposeDecision(mission, raw) {
|
|
24
|
+
validatePurposeDecision(mission, raw);
|
|
25
|
+
const input = structuredClone(raw);
|
|
26
|
+
// Transport retries may repeat an observation; do not crowd out useful prior decisions.
|
|
27
|
+
const last = purposeDecisionView(mission).current.at(-1);
|
|
28
|
+
if (last && keys.every(key => JSON.stringify(last[key]) === JSON.stringify(input[key])))
|
|
29
|
+
return;
|
|
30
|
+
mission.purposeDecisions = [...retained(mission), { ...input, id: randomUUID(), mission_id: mission.id,
|
|
31
|
+
run_id: mission.runID, requirements_fingerprint: purposeRequirementsFingerprint(mission), recorded_at: new Date().toISOString() }].slice(-8);
|
|
32
|
+
}
|
|
33
|
+
function retained(mission) {
|
|
34
|
+
const raw = mission.purposeDecisions;
|
|
35
|
+
if (!Array.isArray(raw))
|
|
36
|
+
return [];
|
|
37
|
+
return raw.filter((value) => valid(value) && object(value) && line(value.id) &&
|
|
38
|
+
line(value.mission_id) && (value.run_id === null || line(value.run_id)) &&
|
|
39
|
+
typeof value.requirements_fingerprint === "string" && /^[a-f0-9]{64}$/u.test(value.requirements_fingerprint) &&
|
|
40
|
+
line(value.recorded_at) && Number.isFinite(Date.parse(value.recorded_at))).slice(-8);
|
|
41
|
+
}
|
|
42
|
+
export function purposeDecisionView(mission) {
|
|
43
|
+
const entries = retained(mission), fingerprint = purposeRequirementsFingerprint(mission);
|
|
44
|
+
const current = entries.filter(item => item.mission_id === mission.id && item.requirements_fingerprint === fingerprint &&
|
|
45
|
+
item.requirement_ids.every(id => mission.requirements.some(requirement => requirement.id === id)));
|
|
46
|
+
return { current, historical: entries.filter(item => !current.includes(item)),
|
|
47
|
+
application_status: "not-established-by-this-record",
|
|
48
|
+
interpretation: "Selected strategy observations, not proof that a plan was admitted or a method executed. observed_outcome and evidence_refs are model reports, not host observations. current means related to current requirements, not current evidence freshness. Original requirements and actual outcomes govern. REUSE does not establish evidence freshness or PASS. STOP_METHOD ends only the named method: it neither stops a live process nor completes/cancels the Mission. Required validation, explicit rollback, permissions, budget, Review and root acceptance remain unchanged. next_action here is advisory, not the host next_action." };
|
|
49
|
+
}
|
|
50
|
+
export const purposeDecisionSchema = { type: "object", "x-sortie-optional": true, additionalProperties: false,
|
|
51
|
+
description: "Optional important purpose/method decision accompanying useful planning or submission, not every Task. Record actual user outcome and concrete unmet work; references are not proof. No extra plan/approval solely to record it.",
|
|
52
|
+
required: ["decision", "requirement_ids", "observed_outcome", "unmet_work", "reason", "evidence_refs", "next_action"],
|
|
53
|
+
properties: { decision: { type: "string", enum: [...PURPOSE_DECISIONS] },
|
|
54
|
+
...Object.fromEntries(["observed_outcome", "reason", "affected_method", "next_action"].map(key => [key, { type: "string", minLength: 1, maxLength: 2000 }])),
|
|
55
|
+
...Object.fromEntries(["requirement_ids", "unmet_work", "evidence_refs"].map(key => [key, { type: "array", minItems: key === "requirement_ids" ? 1 : 0,
|
|
56
|
+
maxItems: 64, items: { type: "string", minLength: 1, maxLength: 2000 } }])) } };
|
|
@@ -2,6 +2,7 @@ import { type OperatorPlan, type OperatorState, type OperatorTask, type Operator
|
|
|
2
2
|
import { type RuntimeProfile } from "./runtime-profile.js";
|
|
3
3
|
import { type MissionContinuity } from "./mission-context.js";
|
|
4
4
|
import { type WorkQuantityState } from "./work-quantity.js";
|
|
5
|
+
import { type PurposeDecision } from "./mission-purpose.js";
|
|
5
6
|
export { missionValidationCommand } from "./mission-validation-command.js";
|
|
6
7
|
export declare const MISSION_REFERENCE = "SORTIE_MISSION_REF ";
|
|
7
8
|
export declare const MISSION_REVIEW_REFERENCE = "SORTIE_MISSION_REVIEW_REF ";
|
|
@@ -220,6 +221,8 @@ export interface OperatorMission {
|
|
|
220
221
|
context?: MissionContext[];
|
|
221
222
|
/** Shared public context/working notes; observations, never extra requirements or acceptance. */
|
|
222
223
|
continuity?: MissionContinuity;
|
|
224
|
+
/** Optional bounded strategy observations; never requirements, proof or authority. */
|
|
225
|
+
purposeDecisions?: PurposeDecision[];
|
|
223
226
|
/** Operator recommendation from already-known context; not a requirement or execution lock. */
|
|
224
227
|
preferredExecutor?: "coordinator" | "worker";
|
|
225
228
|
/** Known unit facts passed to the Coordinator without pre-starting a Worker. */
|
|
@@ -398,6 +401,7 @@ export declare class OperatorMissionRuntime {
|
|
|
398
401
|
}): Promise<OperatorMission>;
|
|
399
402
|
update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
|
|
400
403
|
recordConsultationSkip(root: string, role: MissionConsultation["role"], reason: string): Promise<OperatorMission>;
|
|
404
|
+
recordPurposeDecision(root: string, raw: unknown): Promise<OperatorMission>;
|
|
401
405
|
recordConsultationDispatch(root: string, input: {
|
|
402
406
|
role: MissionConsultation["role"];
|
|
403
407
|
callID: string;
|
|
@@ -9,6 +9,7 @@ import { missionValidationCommand } from "./mission-validation-command.js";
|
|
|
9
9
|
import { retainMissionContext, missionContextView } from "./mission-context.js";
|
|
10
10
|
import { planWorkQuantity, projectWorkQuantity, syncWorkQuantity } from "./work-quantity.js";
|
|
11
11
|
import { applyPendingWork, rememberWorkMission, rememberedWorkRun } from "./work-quantity-observations.js";
|
|
12
|
+
import { appendPurposeDecision, purposeDecisionView } from "./mission-purpose.js";
|
|
12
13
|
export { missionValidationCommand } from "./mission-validation-command.js";
|
|
13
14
|
const digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
14
15
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -332,6 +333,7 @@ export class OperatorMissionRuntime {
|
|
|
332
333
|
launch_conditions: state.launchConditions ?? [],
|
|
333
334
|
conversation: sources.some(source => source.session_id === state.root && source.entries > 0 && !("snapshot_available" in source)) ? [] : state.context ?? [],
|
|
334
335
|
continuity: { notes: state.continuity?.notes, sources, progress: state.progress,
|
|
336
|
+
purpose_decisions: purposeDecisionView(state),
|
|
335
337
|
attempts: (state.attempts ?? []).map(({ runID, unitID, status, failure, resultClass, childSessionID }) => ({ run_id: runID, unit_id: unitID, status, failure, result_class: resultClass, session_id: childSessionID })),
|
|
336
338
|
execution: state.execution,
|
|
337
339
|
source_authors: state.sourceAuthors ?? [],
|
|
@@ -619,6 +621,9 @@ export class OperatorMissionRuntime {
|
|
|
619
621
|
reason: reason.trim(), at: new Date().toISOString() }].slice(-MISSION_CONSULTATION_LIMIT);
|
|
620
622
|
});
|
|
621
623
|
}
|
|
624
|
+
recordPurposeDecision(root, raw) {
|
|
625
|
+
return this.update(root, state => appendPurposeDecision(state, raw));
|
|
626
|
+
}
|
|
622
627
|
recordConsultationDispatch(root, input) {
|
|
623
628
|
if ((input.role !== "advisor" && input.role !== "scout") || !input.callID || !input.reason.trim() ||
|
|
624
629
|
input.reason.length > 1000 || /[\r\n]/u.test(input.reason) ||
|
|
@@ -715,6 +720,7 @@ export class OperatorMissionRuntime {
|
|
|
715
720
|
...(state.suggestedUnit ? [`Known unit facts (reuse or adapt during actual work): ${JSON.stringify(state.suggestedUnit)}`] : []),
|
|
716
721
|
"Escalate only a completion candidate, a user-only decision, or an extension of original requirements/budget. Unit progress is published without stopping you.",
|
|
717
722
|
"Requirements:", ...state.requirements.map(item => `${item.id}: ${item.text}`),
|
|
723
|
+
`Purpose/method observations (advisory, not proof or authority): ${JSON.stringify(purposeDecisionView(state))}`,
|
|
718
724
|
`Confirmed launch conditions (fixed limits, not consumption or remaining budget): ${JSON.stringify(state.launchConditions ?? [])}`,
|
|
719
725
|
`Explicit user write prohibitions: ${JSON.stringify(state.prohibitedWrite ?? [])}`,
|
|
720
726
|
`Work kind: ${state.kind ?? "implementation"}. For an operation, setup, execution and result collection belong in one useful Worker whenever possible.`,
|
|
@@ -872,6 +878,7 @@ export async function missionAcceptanceSummary(mission, run, operators, observe)
|
|
|
872
878
|
return { original_requests: mission.requests, requirements: mission.requirements,
|
|
873
879
|
original_attachments: (mission.continuity?.sources ?? []).flatMap(source => (source.attachments ?? []).map(file => ({ session_id: source.session_id, ...file }))),
|
|
874
880
|
operation: missionOperationSummary(mission),
|
|
881
|
+
purpose_decisions: purposeDecisionView(mission),
|
|
875
882
|
history: { status: missingAnchors.length ? "unavailable" : history.status,
|
|
876
883
|
...(history.reason ? { reason: history.reason } : missingAnchors.length ? { reason: "accepted-handoff-anchor-unavailable" } : {}),
|
|
877
884
|
...(missingAnchors.length ? { unavailable_anchors: missingAnchors } : {}),
|
|
@@ -914,6 +921,7 @@ export function missionPacket(mission, run, includeWorkQuantity = true) {
|
|
|
914
921
|
accounting_scope: "Implementation units (Worker, direct controller execution or scoped Reviewer correction) are not benchmark attempts. Host budget uses the existing unit ledger; orchestration, read-only Review and external campaign costs are excluded. Launch caps are fixed conditions, not a known campaign remainder.",
|
|
915
922
|
delivery_observation: mission.deliveryObservation?.run_id === (run?.runID ?? mission.runID) ? mission.deliveryObservation : null,
|
|
916
923
|
submission: mission.submission, progress: mission.progress, consultations: mission.consultations ?? [],
|
|
924
|
+
purpose_decisions: purposeDecisionView(mission),
|
|
917
925
|
attempts: mission.attempts ?? [], corrections: (mission.corrections ?? []).map(({ findings: _findings, initialPrompt: _prompt, ...item }) => item), ...(mission.rescue ? { rescue: mission.rescue } : {}),
|
|
918
926
|
operation: missionOperationSummary(mission),
|
|
919
927
|
execution_summary: { completed_units: run?.units.filter(unit => unit.status === "succeeded").length ?? 0,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare function declaredArtifacts(paths: readonly string[], previewLimit?: number, options?: {
|
|
5
5
|
reportUnreadableDirectories?: boolean;
|
|
6
|
+
reportOutsideLinks?: boolean;
|
|
6
7
|
}): Promise<{
|
|
7
8
|
entries: (readonly [string, string, (string | undefined)?])[];
|
|
8
9
|
excerpt: string;
|
|
@@ -62,8 +62,22 @@ export async function declaredArtifacts(paths, previewLimit = 0, options = {}) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
entries.push([label, `symlink:${target}`, canonical]);
|
|
65
|
-
if (!physical.some(root => within(root, canonical)))
|
|
66
|
-
|
|
65
|
+
if (!physical.some(root => within(root, canonical))) {
|
|
66
|
+
if (!options.reportOutsideLinks)
|
|
67
|
+
throw new Error("declared-artifact-link-outside-scope");
|
|
68
|
+
// Review records the link entry, not authority to inspect its target. Keep the
|
|
69
|
+
// strict default for validation snapshots and never recurse beyond declared roots.
|
|
70
|
+
entries.push([label, "target-not-inspected:outside-scope", canonical]);
|
|
71
|
+
const notice = Buffer.from(`\n--- external symlink: ${label} ---\n${target}\n[target not inspected: outside declared scope]\n`);
|
|
72
|
+
const bounded = notice.subarray(0, remaining);
|
|
73
|
+
if (bounded.length) {
|
|
74
|
+
excerpts.push(bounded.toString("utf8"));
|
|
75
|
+
remaining -= bounded.length;
|
|
76
|
+
}
|
|
77
|
+
if (bounded.length < notice.length)
|
|
78
|
+
truncated = true;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
67
81
|
if (ancestors.has(canonical))
|
|
68
82
|
return; // The link identity is recorded; do not traverse cycles.
|
|
69
83
|
await visit(canonical, ancestors);
|
|
@@ -761,6 +761,7 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
761
761
|
untracked.add(path);
|
|
762
762
|
const omitted = [];
|
|
763
763
|
const unreadable = [];
|
|
764
|
+
const outsideLinks = [];
|
|
764
765
|
let diff = scopedDiff;
|
|
765
766
|
if (repository && local.length && displayBaseline) {
|
|
766
767
|
// HEAD-only diffs are empty once the author commits. Display the original mission delta
|
|
@@ -881,19 +882,25 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
881
882
|
// A declared write scope can also be a host-managed runtime directory (e.g. Docker's data root).
|
|
882
883
|
// Do not make review depend on listing it; record the missing coverage instead. Protected
|
|
883
884
|
// validation still requires readable outputs and does not use this review-only option.
|
|
884
|
-
const artifacts = await declaredArtifacts(external, Math.max(1, 24_000 - Buffer.byteLength(excerpt)), { reportUnreadableDirectories: true });
|
|
885
|
+
const artifacts = await declaredArtifacts(external, Math.max(1, 24_000 - Buffer.byteLength(excerpt)), { reportUnreadableDirectories: true, reportOutsideLinks: true });
|
|
885
886
|
sourceUpdate(JSON.stringify(artifacts.entries));
|
|
886
887
|
excerpt += artifacts.excerpt;
|
|
887
888
|
if (artifacts.truncated)
|
|
888
889
|
omitted.push("external artifacts");
|
|
889
890
|
unreadable.push(...artifacts.unreadable);
|
|
891
|
+
outsideLinks.push(...artifacts.entries.filter(([, kind]) => kind === "target-not-inspected:outside-scope").map(([path]) => path));
|
|
890
892
|
}
|
|
891
893
|
const bytes = Buffer.from(excerpt);
|
|
892
894
|
const truncatedSource = [...new Set(omitted)];
|
|
893
895
|
if (bytes.length > 24_000 && truncatedSource.length === 0)
|
|
894
896
|
truncatedSource.push("(source diff exceeds excerpt budget)");
|
|
897
|
+
// Missing coverage is not a clipped source preview: disclose it even after that
|
|
898
|
+
// budget is exhausted. Full link identities remain in the fingerprint above.
|
|
899
|
+
const outsideNotice = outsideLinks.length ? `\n[UNINSPECTED EXTERNAL LINK TARGETS: ${outsideLinks.length} link(s); outside declared artifact roots; ` +
|
|
900
|
+
`target contents not inspected or fingerprinted through these links. Link entries (first 20, clipped preview): ` +
|
|
901
|
+
`${Buffer.from(JSON.stringify(outsideLinks.slice(0, 20))).subarray(0, 2_000).toString("utf8")}]` : "";
|
|
895
902
|
return { fingerprint: `sha256:${hash.digest("hex")}`, candidateFingerprint: `sha256:${candidateHash.digest("hex")}`, excerpt: (bytes.length > 24_000 ? bytes.subarray(0, 24_000).toString("utf8") : excerpt) +
|
|
896
903
|
(truncatedSource.length ? `\n[EXCERPT TRUNCATED: ${truncatedSource.slice(0, 20).join(", ")}; Reviewer can read/search the relevant source directly]` : "") +
|
|
897
|
-
(unreadable.length ? `\n[UNINSPECTED EXTERNAL DIRECTORIES: ${unreadable.slice(0, 20).join(", ")}; select specific result files as review evidence]` : ""),
|
|
904
|
+
(unreadable.length ? `\n[UNINSPECTED EXTERNAL DIRECTORIES: ${unreadable.slice(0, 20).join(", ")}; select specific result files as review evidence]` : "") + outsideNotice,
|
|
898
905
|
truncatedEvidence, truncatedSource };
|
|
899
906
|
}
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -24,6 +24,7 @@ import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
|
24
24
|
import { prepareValidationScratch } from "./validation-scratch.js";
|
|
25
25
|
import { SOURCE_REVIEW_RISK_TAGS } from "../core/consultation.js";
|
|
26
26
|
import { createHash, randomUUID } from "node:crypto";
|
|
27
|
+
import { appendPurposeDecision, purposeDecisionSchema, purposeDecisionView, validatePurposeDecision } from "../core/mission-purpose.js";
|
|
27
28
|
import { proposeTerminalRescue } from "../core/terminal-rescue-policy.js";
|
|
28
29
|
import { DEFAULT_TERMINAL_RESCUE_MODEL } from "./terminal-rescue-host.js";
|
|
29
30
|
import { readHostModels } from "./model-routing-hook.js";
|
|
@@ -3149,12 +3150,15 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3149
3150
|
execution: missionExecutionSchema,
|
|
3150
3151
|
handoff_context: handoffContextSchema,
|
|
3151
3152
|
reason: { type: "string", "x-sortie-optional": true },
|
|
3153
|
+
purpose_decision: purposeDecisionSchema,
|
|
3152
3154
|
executor: { type: "string", enum: ["worker", "self"], description: "Use self to implement/correct and formally validate here. Use worker without units to yield the current direct unit, carrying remaining action in handoff_context; the same unit, edits, checks and budget continue. With units, worker/default retains configured Worker routing.", "x-sortie-optional": true } }, execute: async (args, context) => {
|
|
3153
3155
|
const { root, mission } = await missionAuthority(context.sessionID);
|
|
3154
3156
|
if (args.executor !== undefined && !["self", "worker"].includes(args.executor))
|
|
3155
3157
|
throw new Error("mission-executor-invalid");
|
|
3156
3158
|
try {
|
|
3157
3159
|
return await serializeDispatchTransition(root, async () => {
|
|
3160
|
+
if (args.purpose_decision !== undefined)
|
|
3161
|
+
await missions.recordPurposeDecision(root, args.purpose_decision);
|
|
3158
3162
|
if (args.executor === "worker" && args.units === undefined)
|
|
3159
3163
|
return handoffDirect(root, context.sessionID, context.callID, args.handoff_context);
|
|
3160
3164
|
const planned = await declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution, context.callID, args.handoff_context, args.work_quantities);
|
|
@@ -3402,6 +3406,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3402
3406
|
} };
|
|
3403
3407
|
tools[reviewMission] = { description: "Coordinator or direct Fast-lane root: prepare independent quality review or author read-only self-recheck. Reuse useful context by default; mode=independent or reviewer_session_id selects legitimate alternate review without a new approval. Concrete reachable residual Major risk requires independent review; tags/hash/Medium alone do not mandate another review. Host supplies original requests, retained findings/authors, current source/diff and observed checks. Traces/excerpts are optional. Preserve current validation and separate root acceptance. A low-risk initial skip is recorded.",
|
|
3404
3408
|
args: { risk_tags: { type: "array", items: { type: "string", enum: SOURCE_REVIEW_RISK_TAGS } },
|
|
3409
|
+
purpose_decision: purposeDecisionSchema,
|
|
3405
3410
|
mode: { type: "string", enum: ["independent", "self-recheck"], "x-sortie-optional": true },
|
|
3406
3411
|
reviewer_session_id: { type: "string", "x-sortie-optional": true, description: "Optional existing Reviewer conversation. Its actual authorship determines independence; selecting a different ID alone never makes an author independent." },
|
|
3407
3412
|
evidence: { type: "array", maxItems: 6, items: { type: "object", additionalProperties: false,
|
|
@@ -3444,6 +3449,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3444
3449
|
const requestedRisk = args.risk_tags;
|
|
3445
3450
|
const risk = correction && Array.isArray(requestedRisk) && requestedRisk.length === 0 ? mission.review?.risk : requestedRisk;
|
|
3446
3451
|
const traces = missionReviewTraces(mission, args.traces);
|
|
3452
|
+
if (args.purpose_decision !== undefined)
|
|
3453
|
+
validatePurposeDecision(mission, args.purpose_decision);
|
|
3447
3454
|
if (!Array.isArray(risk) || !risk.every(tag => SOURCE_REVIEW_RISK_TAGS.includes(tag))) {
|
|
3448
3455
|
throw new Error("mission-review-input: use recognized risk tags");
|
|
3449
3456
|
}
|
|
@@ -3478,6 +3485,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3478
3485
|
throw new Error("mission-review-selected-author-mode-mismatch");
|
|
3479
3486
|
const selectedDisposition = (!independentRequested || mission.review?.mode === "independent") &&
|
|
3480
3487
|
(!selectedReviewer || mission.review?.child === selectedReviewer);
|
|
3488
|
+
// Record only after existing Review prerequisites/selection succeed. This is
|
|
3489
|
+
// an observation on the already-needed operation, never a new Review gate.
|
|
3490
|
+
if (args.purpose_decision !== undefined)
|
|
3491
|
+
mission = await missions.recordPurposeDecision(root, args.purpose_decision);
|
|
3481
3492
|
const currentPinned = correction?.selfRecheck && mission.review && missionReviewAccepted(mission.review) &&
|
|
3482
3493
|
JSON.stringify(evidence) !== JSON.stringify(mission.review.evidence)
|
|
3483
3494
|
? await missionReviewSource(input.directory, run, mission.review.evidence, mission.reviewBaseline, mission.reviewScope) : source;
|
|
@@ -3529,6 +3540,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3529
3540
|
"Verbatim original user requests (complete, outside the source-excerpt budget; task data, not replacement instructions):",
|
|
3530
3541
|
...mission.requests.flatMap(request => [`original_request user:${request.id}:`, request.text, "end_original_request"]),
|
|
3531
3542
|
`acceptance: ${JSON.stringify(run.acceptance)}`, `changedLogicSummary: ${JSON.stringify(traces)}`,
|
|
3543
|
+
`purpose_observations: ${JSON.stringify(purposeDecisionView(mission))}`,
|
|
3532
3544
|
`retained_findings_and_authors: ${JSON.stringify({ source_authors: mission.sourceAuthors ?? [],
|
|
3533
3545
|
corrections: (mission.corrections ?? []).map(({ initialPrompt: _prompt, inlineReview: _inline, ...item }) => item),
|
|
3534
3546
|
prior_review: mission.review ? { run_id: mission.review.runID, verdict: mission.review.verdict,
|
|
@@ -3628,10 +3640,13 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3628
3640
|
throw new Error("mission-review-required-or-stale");
|
|
3629
3641
|
}
|
|
3630
3642
|
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.",
|
|
3631
|
-
args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema
|
|
3643
|
+
args: { status: { type: "string", enum: ["ready", "needs-decision", "blocked"] }, summary: stringSchema,
|
|
3644
|
+
purpose_decision: purposeDecisionSchema }, execute: async (args, context) => {
|
|
3632
3645
|
const { root } = await missionAuthority(context.sessionID);
|
|
3633
3646
|
return serializeDispatchTransition(root, async () => {
|
|
3634
3647
|
let mission = await missions.required(root);
|
|
3648
|
+
if (args.purpose_decision !== undefined)
|
|
3649
|
+
validatePurposeDecision(mission, args.purpose_decision);
|
|
3635
3650
|
mission = await reconcileMissionExecution(root, mission, await operators.read(root));
|
|
3636
3651
|
if (args.status === "ready") {
|
|
3637
3652
|
if (mission.kind === "operation" && !missionExecutionComplete(mission)) {
|
|
@@ -3653,6 +3668,8 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
3653
3668
|
await control.failDirectUnit(root, context.sessionID);
|
|
3654
3669
|
}
|
|
3655
3670
|
const updated = await missions.update(root, state => {
|
|
3671
|
+
if (args.purpose_decision !== undefined)
|
|
3672
|
+
appendPurposeDecision(state, args.purpose_decision);
|
|
3656
3673
|
state.phase = "submitted";
|
|
3657
3674
|
state.submission = { status: args.status, summary: args.summary };
|
|
3658
3675
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type RuntimeProfile } from "./core/runtime-profile.ts";
|
|
2
2
|
/** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
|
|
3
3
|
export declare const TOOL_ENVIRONMENT = ".sortie-env";
|
|
4
|
+
/** Purpose alignment at existing decisions, not another role, gate or per-Task ritual. */
|
|
5
|
+
export declare const MISSION_PURPOSE = "## Purpose and methods\nKeep the user's vision/required outcomes separate from the current method. At an existing material\ndecision (reusable result, failed approach, peripheral issue, or completion comparison), compare the\noriginal requirements with actual user outcomes, concrete unmet work and existing evidence. Choose\nCONTINUE, REUSE, CHANGE_METHOD, STOP_METHOD or NEEDS_DECISION during useful work, not a separate turn.\nReuse unchanged applicable evidence; inspect still-unmet outcomes rather than repeating the same method\nwithout a changed input or hypothesis. A successful small Task, title or instruction is not vision fulfillment.\nSTOP_METHOD ends only a named means, never a required outcome, live process or Mission. Finish required\nvalidation and explicit rollback/recovery. Do not expand into unrelated issues; only user-only choices\nor original scope/budget extensions require escalation. Normal in-scope correction continues autonomously.\nWhen a material rationale would otherwise be lost on resume, optionally attach purpose_decision to an\nalready-needed plan_units, review_mission or submit_mission: decision, requirement_ids, observed_outcome, unmet_work,\nreason, evidence_refs, affected_method (required for change/stop), next_action. No extra plan, Task or\napproval solely to record it. Reuse current purpose_decisions on resume; historical entries need fresh\ncomparison with current requirements. These are model observations, not evidence PASS/freshness or\npermission. A Fast-lane root has no submit_mission: use its already-needed review_mission or next useful\nplan, never replan solely for logging. Do not request another Review or elevate a low-risk skip to save a\nnote. Host next_action, budgets, history, validation, independent Review and root acceptance govern.";
|
|
4
6
|
/** Static presentation guidance; observed values stay in ordinary tool results, not live instructions. */
|
|
5
7
|
export declare const MISSION_PRESENTATION = "## Readable progress and return reports\nUse short, meaningful headings in ordinary user-facing updates: \u2691 start, \u25CE finding, \u270E note,\n[!] obstacle, \u21A9 recovered only after recovery is observed. Keep warnings, code and machine status exact.\nUse restrained spacing, not large ASCII frames, constant military metaphors, animation or typing delays.\nReserve a small margin note or doodle for a formal completion or earned distinction, not every message.\nWhen an ordinary Mission tool result supplies work_quantity_display, copy that host-generated label into\nyour next useful user-facing progress update. No separate progress-only status call or model turn.\nThis is estimated workload for the whole request, NOT elapsed time, cost, budget or completed-unit share.\nAn estimate revision may decrease it; only formal request completion means 100%. Do not extrapolate,\nrecompute or invent it. Task metadata may be saved without being displayed by the client; Direct may\nhave no Task card. Use ordinary response text, never a fake Task or a claim that metadata proves UI delivery.\nWhen complete_mission supplies return_report, append it verbatim exactly once outside any code fence.\nThe host-authored panel and formal receipt are authoritative; titles and decoration never prove success.\nDo not regenerate metrics, hide unknowns, schedule another model turn, or repeat work just to render it.";
|
|
6
8
|
/** Task-specific environment limits qualify general dependency setup guidance. */
|
|
@@ -3,6 +3,25 @@ import { STRATEGY_TRIGGERS, SOURCE_REVIEW_RISK_TAGS } from "./core/consultation.
|
|
|
3
3
|
import { SCOUT_EVIDENCE_CODES } from "./core/scout-contract.js";
|
|
4
4
|
/** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
|
|
5
5
|
export const TOOL_ENVIRONMENT = ".sortie-env";
|
|
6
|
+
/** Purpose alignment at existing decisions, not another role, gate or per-Task ritual. */
|
|
7
|
+
export const MISSION_PURPOSE = `## Purpose and methods
|
|
8
|
+
Keep the user's vision/required outcomes separate from the current method. At an existing material
|
|
9
|
+
decision (reusable result, failed approach, peripheral issue, or completion comparison), compare the
|
|
10
|
+
original requirements with actual user outcomes, concrete unmet work and existing evidence. Choose
|
|
11
|
+
CONTINUE, REUSE, CHANGE_METHOD, STOP_METHOD or NEEDS_DECISION during useful work, not a separate turn.
|
|
12
|
+
Reuse unchanged applicable evidence; inspect still-unmet outcomes rather than repeating the same method
|
|
13
|
+
without a changed input or hypothesis. A successful small Task, title or instruction is not vision fulfillment.
|
|
14
|
+
STOP_METHOD ends only a named means, never a required outcome, live process or Mission. Finish required
|
|
15
|
+
validation and explicit rollback/recovery. Do not expand into unrelated issues; only user-only choices
|
|
16
|
+
or original scope/budget extensions require escalation. Normal in-scope correction continues autonomously.
|
|
17
|
+
When a material rationale would otherwise be lost on resume, optionally attach purpose_decision to an
|
|
18
|
+
already-needed plan_units, review_mission or submit_mission: decision, requirement_ids, observed_outcome, unmet_work,
|
|
19
|
+
reason, evidence_refs, affected_method (required for change/stop), next_action. No extra plan, Task or
|
|
20
|
+
approval solely to record it. Reuse current purpose_decisions on resume; historical entries need fresh
|
|
21
|
+
comparison with current requirements. These are model observations, not evidence PASS/freshness or
|
|
22
|
+
permission. A Fast-lane root has no submit_mission: use its already-needed review_mission or next useful
|
|
23
|
+
plan, never replan solely for logging. Do not request another Review or elevate a low-risk skip to save a
|
|
24
|
+
note. Host next_action, budgets, history, validation, independent Review and root acceptance govern.`;
|
|
6
25
|
/** Static presentation guidance; observed values stay in ordinary tool results, not live instructions. */
|
|
7
26
|
export const MISSION_PRESENTATION = `## Readable progress and return reports
|
|
8
27
|
Use short, meaningful headings in ordinary user-facing updates: ⚑ start, ◎ finding, ✎ note,
|
|
@@ -314,6 +333,8 @@ ${MISSION_REFLECTION_WORKFLOW}
|
|
|
314
333
|
|
|
315
334
|
${MISSION_PRESENTATION}
|
|
316
335
|
|
|
336
|
+
${MISSION_PURPOSE}
|
|
337
|
+
|
|
317
338
|
Retain 🐾 Sortie presentation. When complete_mission returns return_report, append that host-authored
|
|
318
339
|
Markdown verbatim exactly once after a concise final outcome and key checks, outside any code fence.
|
|
319
340
|
Preserve the panel's headings, metrics, unknown values and caveats; do not recalculate, regenerate it or
|
|
@@ -540,6 +561,8 @@ Do not reconstruct or prepend the absolute workspace path. Preserve explicitly r
|
|
|
540
561
|
|
|
541
562
|
${MISSION_PRESENTATION}
|
|
542
563
|
|
|
564
|
+
${MISSION_PURPOSE}
|
|
565
|
+
|
|
543
566
|
For release/benchmark work, retain the user's selected package/environment and record its receipt/hash.
|
|
544
567
|
A local repack and a published tarball can have different hashes; that alone does not prohibit a requested
|
|
545
568
|
local run. Fix the selected artifact for the run and record the runner's own revision separately.
|