sortie-dogs 0.13.19 → 0.13.20
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 -7
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/work-quantity-observations.d.ts +4 -0
- package/dist/core/work-quantity-observations.js +78 -6
- package/dist/core/work-quantity.js +41 -5
- package/dist/plugin/profiled.js +41 -35
- package/dist/plugin/v2.js +96 -83
- 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.13.
|
|
23
|
-
· [Release notes](docs/release-v0.13.
|
|
22
|
+
**Current release: [v0.13.20](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.13.20)**
|
|
23
|
+
· [Release notes](docs/release-v0.13.20.md)
|
|
24
24
|
|
|
25
25
|
[Quick start](#quick-start) · [Workflow](#mission-workflow) · [Models](#default-models) ·
|
|
26
26
|
[Measured results](#measured-results) · [Configuration](#configuration) · [Documentation](#documentation)
|
|
@@ -57,7 +57,7 @@ Use an existing Codex CLI signed in with **ChatGPT**. On Windows, PowerShell **7
|
|
|
57
57
|
available; POSIX uses bash. Sortie does not install Codex, start login or use a metered API fallback.
|
|
58
58
|
|
|
59
59
|
```sh
|
|
60
|
-
npm install --save-dev sortie-dogs@0.13.
|
|
60
|
+
npm install --save-dev sortie-dogs@0.13.20
|
|
61
61
|
npx --no-install sortie-dogs codex init .
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -86,7 +86,7 @@ bridge. [Codex guide: SDK, permissions, progress, resume and manifest route](doc
|
|
|
86
86
|
Use **OpenCode V2**. In the target project:
|
|
87
87
|
|
|
88
88
|
```sh
|
|
89
|
-
npm install --save-dev sortie-dogs@0.13.
|
|
89
|
+
npm install --save-dev sortie-dogs@0.13.20
|
|
90
90
|
npx --no-install sortie-dogs init .
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -193,7 +193,7 @@ known expense. Official local evaluation and leaderboard acceptance are separate
|
|
|
193
193
|
**8/23 (34.8%)**, versus v0.12.25's 7/23; all seven prior resolutions retained, no empty patches or
|
|
194
194
|
official evaluation errors. [Fixed conditions, stopped-run accounting and costs](docs/benchmarks/swebench-lite-history.md#v0131-dev23-2026-09-30).
|
|
195
195
|
|
|
196
|
-
All scores belong to their fixed candidates, **not v0.13.
|
|
196
|
+
All scores belong to their fixed candidates, **not v0.13.20**. SWE-bench remains an optional
|
|
197
197
|
measurement, not a release gate. Different budgets, models and conditions are not controlled comparisons.
|
|
198
198
|
|
|
199
199
|
## Configuration
|
|
@@ -216,7 +216,7 @@ Install the desired package version and rerun **your host's** initializer:
|
|
|
216
216
|
|
|
217
217
|
An OpenCode config-local bridge may resolve a separate package; npm-global update alone does not
|
|
218
218
|
update it. [Global/update instructions](docs/configuration.md#global-availability).
|
|
219
|
-
The current Mission asset marker is `0.13.
|
|
219
|
+
The current Mission asset marker is `0.13.20-review-followup-v1`; the unchanged Codex skill marker is
|
|
220
220
|
`0.13.11-codex-skill-v2`. Installed markers alone do not prove a running host reloaded the new version.
|
|
221
221
|
|
|
222
222
|
There is no supported uninstall command. Remove the dependency and only known Sortie-owned paths;
|
|
@@ -232,7 +232,7 @@ follow the [manual removal guide](docs/uninstall.md), never delete the entire `.
|
|
|
232
232
|
[SWE-bench history](docs/benchmarks/swebench-lite-history.md) · [Historical local case study](docs/benchmark-reference.md).
|
|
233
233
|
- **Development:** [Testing](docs/testing.md) · [CLI testing](docs/cli-testing.md) ·
|
|
234
234
|
[Windows tests](docs/windows-tests.md) · [Release routine](docs/release-batch.md).
|
|
235
|
-
- **Releases:** [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) ·
|
|
235
|
+
- **Releases:** [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) ·
|
|
236
236
|
[v0.13.9](docs/release-v0.13.9.md) · [All GitHub releases](https://github.com/zufall-upon/Sortie-dogs/releases).
|
|
237
237
|
|
|
238
238
|
## Community
|
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.13.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.13.20-review-followup-v1";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
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.13.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.13.20-review-followup-v1";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.13.11-codex-skill-v2";
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import type { OperatorMission } from "./operator-mission.js";
|
|
3
3
|
import type { OperatorState } from "./operator-runtime.js";
|
|
4
4
|
import { type WorkQuantityEvent, type WorkQuantityState, type WorkQuantityProgress } from "./work-quantity.js";
|
|
5
|
+
export declare function forgetWorkSession(directory: string, actor: string): void;
|
|
6
|
+
export declare function forgetWorkDirectory(directory: string): void;
|
|
7
|
+
/** Module-global observations remain live until the last overlapping Location setup unloads. */
|
|
8
|
+
export declare function retainWorkDirectory(directory: string): () => void;
|
|
5
9
|
export declare function rememberWorkMission(directory: string, mission: OperatorMission): void;
|
|
6
10
|
export declare function rememberWorkRun(directory: string, run: OperatorState): void;
|
|
7
11
|
export declare function rememberedWorkRun(directory: string, root: string): OperatorState | undefined;
|
|
@@ -1,12 +1,74 @@
|
|
|
1
1
|
import { observeWorkQuantity, planWorkQuantity, projectWorkQuantity, syncWorkQuantity } from "./work-quantity.js";
|
|
2
2
|
const views = new Map();
|
|
3
3
|
const owners = new Map();
|
|
4
|
+
const instances = new Map();
|
|
5
|
+
// Terminal UI snapshots contain no Mission/run/events and cannot grow with history.
|
|
6
|
+
const terminals = new Map();
|
|
4
7
|
const identity = (directory, root) => `${directory.replaceAll("\\", "/").replace(/\/$/u, "")}\0${root}`;
|
|
8
|
+
function forgetRoot(directory, root) {
|
|
9
|
+
const id = identity(directory, root), prefix = identity(directory, "");
|
|
10
|
+
views.delete(id);
|
|
11
|
+
terminals.delete(id);
|
|
12
|
+
for (const [actor, owner] of owners)
|
|
13
|
+
if (actor.startsWith(prefix) && owner === root)
|
|
14
|
+
owners.delete(actor);
|
|
15
|
+
}
|
|
16
|
+
export function forgetWorkSession(directory, actor) {
|
|
17
|
+
const id = identity(directory, actor);
|
|
18
|
+
if (views.has(id) || terminals.has(id) || owners.get(id) === actor)
|
|
19
|
+
forgetRoot(directory, actor);
|
|
20
|
+
else {
|
|
21
|
+
const root = owners.get(id);
|
|
22
|
+
if (root)
|
|
23
|
+
views.get(identity(directory, root))?.deleted.add(actor);
|
|
24
|
+
owners.delete(id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function forgetWorkDirectory(directory) {
|
|
28
|
+
const prefix = identity(directory, "");
|
|
29
|
+
for (const cache of [views, owners, terminals])
|
|
30
|
+
for (const id of cache.keys())
|
|
31
|
+
if (id.startsWith(prefix))
|
|
32
|
+
cache.delete(id);
|
|
33
|
+
}
|
|
34
|
+
/** Module-global observations remain live until the last overlapping Location setup unloads. */
|
|
35
|
+
export function retainWorkDirectory(directory) {
|
|
36
|
+
const id = identity(directory, "");
|
|
37
|
+
instances.set(id, (instances.get(id) ?? 0) + 1);
|
|
38
|
+
let released = false;
|
|
39
|
+
return () => {
|
|
40
|
+
if (released)
|
|
41
|
+
return;
|
|
42
|
+
released = true;
|
|
43
|
+
const remaining = (instances.get(id) ?? 1) - 1;
|
|
44
|
+
if (remaining)
|
|
45
|
+
instances.set(id, remaining);
|
|
46
|
+
else {
|
|
47
|
+
instances.delete(id);
|
|
48
|
+
forgetWorkDirectory(directory);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function own(directory, root, actor, value) {
|
|
53
|
+
if (!value.deleted.has(actor))
|
|
54
|
+
owners.set(identity(directory, actor), root);
|
|
55
|
+
}
|
|
56
|
+
function finishView(directory, root, value) {
|
|
57
|
+
const mission = value.mission, run = value.run;
|
|
58
|
+
const progress = mission ? projectWorkQuantity(missionWorkState(directory, mission, run), mission.phase === "completed", mission.phase === "cancelled")
|
|
59
|
+
: run?.workQuantity ? projectWorkQuantity(syncWorkQuantity(applyPendingWork(directory, root, structuredClone(run.workQuantity)), {}, run), run.phase === "completed", run.phase === "cancelled") : undefined;
|
|
60
|
+
forgetRoot(directory, root);
|
|
61
|
+
if (progress)
|
|
62
|
+
terminals.set(identity(directory, root), { progress, runID: mission?.runID ?? run?.runID });
|
|
63
|
+
if (terminals.size > 128)
|
|
64
|
+
terminals.delete(terminals.keys().next().value);
|
|
65
|
+
}
|
|
5
66
|
function view(directory, root) {
|
|
6
67
|
const id = identity(directory, root);
|
|
68
|
+
terminals.delete(id);
|
|
7
69
|
let value = views.get(id);
|
|
8
70
|
if (!value) {
|
|
9
|
-
value = { sequence: 0, events: new Map() };
|
|
71
|
+
value = { sequence: 0, events: new Map(), deleted: new Set() };
|
|
10
72
|
views.set(id, value);
|
|
11
73
|
}
|
|
12
74
|
owners.set(id, root);
|
|
@@ -20,23 +82,30 @@ export function rememberWorkMission(directory, mission) {
|
|
|
20
82
|
value.sequence = Math.max(value.sequence, mission.workQuantity?.observationSequence ?? 0);
|
|
21
83
|
delete value.progress;
|
|
22
84
|
if (mission.coordinator)
|
|
23
|
-
|
|
85
|
+
own(directory, mission.root, mission.coordinator, value);
|
|
24
86
|
if (mission.review?.child)
|
|
25
|
-
|
|
87
|
+
own(directory, mission.root, mission.review.child, value);
|
|
26
88
|
for (const attempt of mission.attempts ?? [])
|
|
27
89
|
if (attempt.childSessionID)
|
|
28
|
-
|
|
90
|
+
own(directory, mission.root, attempt.childSessionID, value);
|
|
91
|
+
if (["completed", "cancelled"].includes(mission.phase))
|
|
92
|
+
finishView(directory, mission.root, value);
|
|
29
93
|
}
|
|
30
94
|
export function rememberWorkRun(directory, run) {
|
|
95
|
+
const terminal = terminals.get(identity(directory, run.rootSessionID));
|
|
96
|
+
if (terminal && terminal.runID === run.runID)
|
|
97
|
+
return;
|
|
31
98
|
const value = view(directory, run.rootSessionID);
|
|
32
99
|
value.run = run;
|
|
33
100
|
value.sequence = Math.max(value.sequence, run.workQuantity?.observationSequence ?? 0);
|
|
34
101
|
delete value.progress;
|
|
35
102
|
if (run.operatorSessionID)
|
|
36
|
-
|
|
103
|
+
own(directory, run.rootSessionID, run.operatorSessionID, value);
|
|
37
104
|
for (const unit of run.units)
|
|
38
105
|
if (unit.childSessionID)
|
|
39
|
-
|
|
106
|
+
own(directory, run.rootSessionID, unit.childSessionID, value);
|
|
107
|
+
if (!value.mission && ["completed", "cancelled"].includes(run.phase))
|
|
108
|
+
finishView(directory, run.rootSessionID, value);
|
|
40
109
|
}
|
|
41
110
|
export function rememberedWorkRun(directory, root) {
|
|
42
111
|
return views.get(identity(directory, root))?.run;
|
|
@@ -77,6 +146,9 @@ export function missionWorkState(directory, mission, run) {
|
|
|
77
146
|
return syncWorkQuantity(applyPendingWork(directory, mission.root, structuredClone(state)), mission, run);
|
|
78
147
|
}
|
|
79
148
|
export function cachedWorkProgress(directory, actor) {
|
|
149
|
+
const terminal = terminals.get(identity(directory, actor));
|
|
150
|
+
if (terminal)
|
|
151
|
+
return terminal.progress;
|
|
80
152
|
const root = owners.get(identity(directory, actor));
|
|
81
153
|
const value = root && views.get(identity(directory, root));
|
|
82
154
|
if (!value)
|
|
@@ -27,6 +27,7 @@ function pathMatches(actual, binding) {
|
|
|
27
27
|
const a = key(actual), b = key(binding).replace(/\/\*\*$/u, "");
|
|
28
28
|
return a === b || broad(binding) && ([".", "**"].includes(b) || a.startsWith(`${b}/`));
|
|
29
29
|
}
|
|
30
|
+
const pathsOverlap = (a, b) => pathMatches(a, b) || pathMatches(b, a);
|
|
30
31
|
function fraction(item) {
|
|
31
32
|
const o = item.observation;
|
|
32
33
|
if (item.removed)
|
|
@@ -71,6 +72,10 @@ function item(id, kind, label, requirements, paths = [], commands = [], units =
|
|
|
71
72
|
return { id, kind, label, requirementIDs: unique(requirements), weight: weight[kind], basis: "derived",
|
|
72
73
|
paths: unique(paths.map(key)), commands: unique(commands.map(commandKey)), units: unique(units), observation: {}, fraction: 0 };
|
|
73
74
|
}
|
|
75
|
+
/** Keep alias observations for later subdivisions without counting their quantity twice. */
|
|
76
|
+
function retainedAliases(state) {
|
|
77
|
+
return new Set(state.items.filter(entry => !entry.removed && entry.basis === "declared").flatMap(entry => entry.aliases ?? []));
|
|
78
|
+
}
|
|
74
79
|
/** Concrete work before scheduling/coalescence; broad scopes remain unexpanded estimates. */
|
|
75
80
|
export function planWorkQuantity(mission, rawUnits = [], run, previous, declared, directory) {
|
|
76
81
|
const pathKey = (path) => {
|
|
@@ -198,7 +203,7 @@ export function planWorkQuantity(mission, rawUnits = [], run, previous, declared
|
|
|
198
203
|
if (units.length)
|
|
199
204
|
declared.units = units;
|
|
200
205
|
for (const child of state.items.filter(entry => entry.parentID === declared.id && !entry.removed)) {
|
|
201
|
-
const matched = aliases.filter(entry => !child.paths.length && !child.commands.length || entry.paths.some(path => child.paths.
|
|
206
|
+
const matched = aliases.filter(entry => !child.paths.length && !child.commands.length || entry.paths.some(path => child.paths.some(binding => pathsOverlap(path, binding))) ||
|
|
202
207
|
entry.commands.some(command => child.commands.includes(command)));
|
|
203
208
|
const units = unique(matched.flatMap(entry => entry.units));
|
|
204
209
|
if (units.length)
|
|
@@ -282,8 +287,37 @@ function applyDeclaredWork(state, raw, generated, pathKey) {
|
|
|
282
287
|
child.units = parent.units;
|
|
283
288
|
child.paths = strings(point.paths).map(pathKey);
|
|
284
289
|
child.commands = strings(point.commands).map(commandKey);
|
|
285
|
-
if (!state.items.includes(child))
|
|
286
|
-
|
|
290
|
+
if (!state.items.includes(child)) {
|
|
291
|
+
const aliases = matches.filter(alias => child.paths.some(path => alias.paths.some(binding => pathsOverlap(path, binding))) ||
|
|
292
|
+
child.commands.some(command => alias.commands.includes(command)));
|
|
293
|
+
const observations = aliases.map(alias => {
|
|
294
|
+
const saved = state.items.find(entry => entry.id === alias.id).observation;
|
|
295
|
+
if (!alias.targetEstimate || child.paths.some(path => alias.paths.includes(path)))
|
|
296
|
+
return saved;
|
|
297
|
+
const projected = { ...saved };
|
|
298
|
+
// A broad alias's changed-target list identifies concrete edits;
|
|
299
|
+
// its scalar read flag does not identify which child was read.
|
|
300
|
+
delete projected.read;
|
|
301
|
+
if (saved.changed) {
|
|
302
|
+
projected.changedTargets = (saved.changedTargets ?? []).filter(path => child.paths.some(binding => pathMatches(path, binding)));
|
|
303
|
+
projected.changed = projected.changedTargets.length > 0;
|
|
304
|
+
}
|
|
305
|
+
return projected;
|
|
306
|
+
});
|
|
307
|
+
// Bound checkpoints inherit only matching aliases. Multiple aliases retain
|
|
308
|
+
// common observations conservatively; an unbound checkpoint covers its parent.
|
|
309
|
+
child.observation = !child.paths.length && !child.commands.length ? { ...parent.observation }
|
|
310
|
+
: Object.fromEntries(Object.entries(observations[0] ?? {}).filter(([field, value]) => observations.every(observation => JSON.stringify(observation[field]) === JSON.stringify(value))));
|
|
311
|
+
if (child.paths.some(broad) && aliases.length && aliases.every(alias => !alias.targetEstimate && alias.paths.length)) {
|
|
312
|
+
const targets = unique(aliases.flatMap(alias => alias.paths).filter(path => child.paths.some(binding => pathMatches(path, binding))));
|
|
313
|
+
child.targetEstimate = targets.length;
|
|
314
|
+
child.observation.changedTargets = unique(aliases.flatMap((alias, i) => observations[i].changed ? alias.paths : [])
|
|
315
|
+
.filter(path => targets.includes(path)));
|
|
316
|
+
child.observation.changed = child.observation.changedTargets.length > 0;
|
|
317
|
+
if (observations.some(observation => observation.read))
|
|
318
|
+
child.observation.read = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
287
321
|
child.parentID = parent.id;
|
|
288
322
|
child.weight = amount * quantities[index] / sum;
|
|
289
323
|
child.basis = "declared";
|
|
@@ -309,8 +343,9 @@ export function observeWorkQuantity(state, event) {
|
|
|
309
343
|
state.revisionReason = "native work revealed an additional target";
|
|
310
344
|
}
|
|
311
345
|
}
|
|
346
|
+
const aliases = retainedAliases(state);
|
|
312
347
|
for (const entry of state.items) {
|
|
313
|
-
if (entry.removed || entry.children?.length)
|
|
348
|
+
if (entry.removed && !aliases.has(entry.id) || entry.children?.length)
|
|
314
349
|
continue;
|
|
315
350
|
const pathMatch = paths.some(path => entry.paths.some(binding => pathMatches(path, binding)));
|
|
316
351
|
const unitMatch = event.unit !== undefined && entry.units.includes(event.unit);
|
|
@@ -373,8 +408,9 @@ export function syncWorkQuantity(state, mission, run) {
|
|
|
373
408
|
state.lastReview = reviewRef;
|
|
374
409
|
else
|
|
375
410
|
delete state.lastReview;
|
|
411
|
+
const aliases = retainedAliases(state);
|
|
376
412
|
for (const entry of state.items) {
|
|
377
|
-
if (entry.removed || entry.children?.length)
|
|
413
|
+
if (entry.removed && !aliases.has(entry.id) || entry.children?.length)
|
|
378
414
|
continue;
|
|
379
415
|
const units = current?.units.filter(unit => entry.units.includes(`${current.runID}/${unit.unit.id}`)) ?? [];
|
|
380
416
|
if (["artifact", "implementation", "validation", "requirement"].includes(entry.kind) && units.length && units.every(unit => unit.status === "succeeded")) {
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -16,7 +16,7 @@ import { normalizeExecutionScope, normalizeManifestScope, normalizeRelativePath
|
|
|
16
16
|
import { OperatorMissionRuntime, missionAcceptanceSummary, missionPacket, missionPlan, missionReviewAccepted, missionReviewIndependent, missionReviewScope, missionReviewTask, missionCommandOutcome, missionConversationContext, missionExecutionComplete, missionExecutionStatus, missionOperationSummary, missionValidationCommand, missionReviewTraces, missionReviewVerdict, missionSelfRecheckReport } from "../core/operator-mission.js";
|
|
17
17
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
18
18
|
import { formatWorkQuantity, planWorkQuantity } from "../core/work-quantity.js";
|
|
19
|
-
import { cachedWorkProgress, observeActorWork, workRootForActor } from "../core/work-quantity-observations.js";
|
|
19
|
+
import { cachedWorkProgress, forgetWorkSession, observeActorWork, workRootForActor } from "../core/work-quantity-observations.js";
|
|
20
20
|
import { serializeOperatorStatus } from "./operator-status-output.js";
|
|
21
21
|
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionDeliveryObservation, missionReviewBaseline, missionReviewSource, observedMissionValidation, observedMissionValidationSummary, missionReviewValidation, reviewerCorrectionValidation, reviewerCorrectionValidationFresh, validationDirectoryMatches } from "./mission-review.js";
|
|
22
22
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
@@ -551,7 +551,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
551
551
|
// not by resetting the goal budget. This checkpoint grants no dispatch itself.
|
|
552
552
|
if (mission && (!["completed", "cancelled"].includes(mission.phase) ||
|
|
553
553
|
(mission.phase === "cancelled" && run))) {
|
|
554
|
-
return JSON.stringify(missionPacket(mission, run));
|
|
554
|
+
return JSON.stringify(missionPacket(mission, run, false));
|
|
555
555
|
}
|
|
556
556
|
return await operators.continuationCheckpoint(root) ?? await proposals.continuationCheckpoint(root);
|
|
557
557
|
},
|
|
@@ -4489,42 +4489,48 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
4489
4489
|
: event.type.startsWith("session.") && typeof info?.id === "string" ? info.id : undefined;
|
|
4490
4490
|
if (!id)
|
|
4491
4491
|
return;
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
mappedPart
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
if (ownership.
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4492
|
+
try {
|
|
4493
|
+
if (event.type === "session.deleted" && canonicalAgent(profile, selected.get(id)) === "dog-coordinator") {
|
|
4494
|
+
await stop(id, "session-deleted");
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
if (!await rootFor(id))
|
|
4498
|
+
return;
|
|
4499
|
+
const mappedEvent = translate({ event }, false);
|
|
4500
|
+
const mappedPart = record(mappedEvent.event.properties?.part) ? mappedEvent.event.properties.part : undefined;
|
|
4501
|
+
if ((await identity(id)).role === "dog-coordinator" && mappedPart?.type === "text" && typeof mappedPart.text === "string") {
|
|
4502
|
+
mappedPart.text = forwardTerminalText(mappedPart.text);
|
|
4503
|
+
}
|
|
4504
|
+
await core.event?.(mappedEvent);
|
|
4505
|
+
if (event.type === "message.updated" && info)
|
|
4506
|
+
await renderCompletedReply(id, info);
|
|
4507
|
+
if (event.type === "message.part.updated" && part?.type === "tool" && part.tool === "task" &&
|
|
4508
|
+
typeof part.callID === "string" && record(part.state) && part.state.status === "error") {
|
|
4509
|
+
const ownership = taskOwners.get(part.callID);
|
|
4510
|
+
if (ownership !== undefined && ownership.actor === id) {
|
|
4511
|
+
if (ownership.mission) {
|
|
4512
|
+
await missions.update(ownership.root, state => { state.dispatchOpen = false; });
|
|
4513
|
+
taskOwners.delete(part.callID);
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
if (ownership.consultation) {
|
|
4517
|
+
taskOwners.delete(part.callID);
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
const settled = ownership.proposal
|
|
4521
|
+
? (await control.settleProposalBudget(ownership.root, (await proposals.required(ownership.root)).intent_id, part.callID, "failed"), true)
|
|
4522
|
+
: ownership.operator
|
|
4523
|
+
? (await operators.operatorRejected(ownership.root), true)
|
|
4524
|
+
: await control?.settleRejectedDispatch(ownership.root, part.callID) ?? false;
|
|
4525
|
+
if (settled)
|
|
4526
|
+
taskOwners.delete(part.callID);
|
|
4518
4527
|
}
|
|
4519
|
-
const settled = ownership.proposal
|
|
4520
|
-
? (await control.settleProposalBudget(ownership.root, (await proposals.required(ownership.root)).intent_id, part.callID, "failed"), true)
|
|
4521
|
-
: ownership.operator
|
|
4522
|
-
? (await operators.operatorRejected(ownership.root), true)
|
|
4523
|
-
: await control?.settleRejectedDispatch(ownership.root, part.callID) ?? false;
|
|
4524
|
-
if (settled)
|
|
4525
|
-
taskOwners.delete(part.callID);
|
|
4526
4528
|
}
|
|
4527
4529
|
}
|
|
4530
|
+
finally {
|
|
4531
|
+
if (event.type === "session.deleted")
|
|
4532
|
+
forgetWorkSession(input.directory, id);
|
|
4533
|
+
}
|
|
4528
4534
|
},
|
|
4529
4535
|
};
|
|
4530
4536
|
const before = hooks["tool.execute.before"];
|
package/dist/plugin/v2.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toolSchema, legacyToolArgs } from "./tool-schema.js";
|
|
2
2
|
import { SortieDogsV010Plugin } from "./profiled.js";
|
|
3
3
|
import { bindMissionProgress, missionProgressReader } from "./mission-progress.js";
|
|
4
|
-
import { cachedWorkProgress, workRootForActor } from "../core/work-quantity-observations.js";
|
|
4
|
+
import { cachedWorkProgress, forgetWorkSession, retainWorkDirectory, workRootForActor } from "../core/work-quantity-observations.js";
|
|
5
5
|
import { formatWorkQuantity } from "../core/work-quantity.js";
|
|
6
6
|
import { owningServiceMessageList, owningServiceSessionList, owningServiceSessionActive } from "./v2-session-history.js";
|
|
7
7
|
import { nativeContractReadSnapshot } from "./native-contract-read.js";
|
|
@@ -870,90 +870,103 @@ export function createSortieDogsV2Plugin(legacyFactory = SortieDogsV010Plugin) {
|
|
|
870
870
|
return {
|
|
871
871
|
id: "sortie-dogs.v010",
|
|
872
872
|
async setup(context) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
await
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const controller = new AbortController();
|
|
910
|
-
const eventOperations = new Map();
|
|
911
|
-
const handleEvent = async (event) => {
|
|
912
|
-
const data = record(event.data) ? event.data : {};
|
|
913
|
-
const id = string(data.sessionID);
|
|
914
|
-
if (event.type === "filesystem.changed" && typeof data.file === "string") {
|
|
915
|
-
await hooks.event?.({ event: { type: "file.edited", properties: { file: data.file } } });
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
if (id === undefined)
|
|
919
|
-
return;
|
|
920
|
-
if (String(event.type).startsWith("session.execution.") || event.type === "session.inbox.enqueued" || event.type === "session.idle")
|
|
921
|
-
await background.event(event);
|
|
922
|
-
if (event.type === "session.deleted") {
|
|
923
|
-
await context.storage?.remove?.(childSelectionKey(id));
|
|
924
|
-
liveState.delete(id);
|
|
925
|
-
await context.storage?.remove?.(liveStateKey(id));
|
|
926
|
-
// This native event is definite deletion, unlike a transient session.get failure.
|
|
927
|
-
await disposeCorrectionPermissions(context, id);
|
|
928
|
-
}
|
|
929
|
-
if (event.type === "session.execution.succeeded")
|
|
930
|
-
await finalize(id);
|
|
931
|
-
if (event.type === "session.created" || event.type === "session.deleted" || event.type === "session.idle") {
|
|
932
|
-
const info = event.type === "session.created" ? await context.session.get({ sessionID: id }).catch(() => ({ id })) : { id };
|
|
933
|
-
await hooks.event?.({ event: { type: String(event.type), properties: { sessionID: id, info } } });
|
|
934
|
-
for (const child of await hooks.reviewerCorrectionSessions?.(id) ?? [])
|
|
935
|
-
await syncReviewerCorrectionPermissions(context, hooks, child);
|
|
936
|
-
if (event.type !== "session.deleted")
|
|
937
|
-
await syncReviewerCorrectionPermissions(context, hooks, id);
|
|
873
|
+
const releaseWork = retainWorkDirectory(context.location.directory);
|
|
874
|
+
try {
|
|
875
|
+
let hooks;
|
|
876
|
+
const liveState = new Map();
|
|
877
|
+
const background = new NativeBackgroundLifecycle(context, async (dispatch, text) => {
|
|
878
|
+
if (dispatch.owner)
|
|
879
|
+
hooks.backgroundOwner?.(dispatch.callID, dispatch.owner);
|
|
880
|
+
const output = { status: dispatch.terminal === "succeeded" ? "completed" : dispatch.terminal === "interrupted" ? "cancelled" : "error",
|
|
881
|
+
output: text, metadata: { sessionID: dispatch.child, status: dispatch.terminal } };
|
|
882
|
+
await hooks["tool.execute.after"]?.({ tool: "task", sessionID: dispatch.parent, callID: dispatch.callID,
|
|
883
|
+
args: legacyToolInput("subagent", dispatch.input) }, output);
|
|
884
|
+
if (dispatch.child)
|
|
885
|
+
await syncReviewerCorrectionPermissions(context, hooks, dispatch.child);
|
|
886
|
+
// Inline Review already has a compact provenance receipt; retain the full diagnostic packet in its ordinary tool route.
|
|
887
|
+
try {
|
|
888
|
+
const packet = JSON.parse(output.output);
|
|
889
|
+
if (record(packet) && record(packet.completion_receipt))
|
|
890
|
+
return JSON.stringify(packet.completion_receipt);
|
|
891
|
+
}
|
|
892
|
+
catch { /* Ordinary native outputs are not necessarily JSON. */ }
|
|
893
|
+
return output.output;
|
|
894
|
+
}, (callID, restore) => hooks.backgroundOwner?.(callID, restore), (parent, callID, child, promptID) => hooks.backgroundResult?.(parent, callID, child, promptID) ?? Promise.resolve(undefined));
|
|
895
|
+
hooks = await legacyFactory({ directory: context.location.directory, client: legacyClient(context),
|
|
896
|
+
nativeBackground: background, returnReportTransport: "tool-result",
|
|
897
|
+
reviewerCorrectionPermissions: !!context.agent && !!context.storage && !!context.session.switchAgent }, context.options ?? {});
|
|
898
|
+
await registerV2Hooks(context, hooks, background, liveState);
|
|
899
|
+
// Recreate exact durable correction profiles at plugin setup, before resumed native Steps
|
|
900
|
+
// resolve their agent/tool snapshots. Storage.scan is part of Plugin 2.0.18's public domain.
|
|
901
|
+
if (context.storage?.scan) {
|
|
902
|
+
let after;
|
|
903
|
+
do {
|
|
904
|
+
const page = await context.storage.scan({ prefix: correctionStoragePrefix, ...(after ? { after } : {}) });
|
|
905
|
+
for (const entry of page.entries)
|
|
906
|
+
await syncReviewerCorrectionPermissions(context, hooks, entry.key.slice(correctionStoragePrefix.length));
|
|
907
|
+
after = page.next;
|
|
908
|
+
} while (after);
|
|
938
909
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
for await (const event of context.event.subscribe({ signal: controller.signal })) {
|
|
910
|
+
const finalize = createV2ReturnReportFinalizer(context, hooks);
|
|
911
|
+
const controller = new AbortController();
|
|
912
|
+
const eventOperations = new Map();
|
|
913
|
+
const handleEvent = async (event) => {
|
|
944
914
|
const data = record(event.data) ? event.data : {};
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
915
|
+
const id = string(data.sessionID);
|
|
916
|
+
if (event.type === "filesystem.changed" && typeof data.file === "string") {
|
|
917
|
+
await hooks.event?.({ event: { type: "file.edited", properties: { file: data.file } } });
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (id === undefined)
|
|
921
|
+
return;
|
|
922
|
+
try {
|
|
923
|
+
if (String(event.type).startsWith("session.execution.") || event.type === "session.inbox.enqueued" || event.type === "session.idle")
|
|
924
|
+
await background.event(event);
|
|
925
|
+
if (event.type === "session.deleted") {
|
|
926
|
+
await context.storage?.remove?.(childSelectionKey(id));
|
|
927
|
+
liveState.delete(id);
|
|
928
|
+
await context.storage?.remove?.(liveStateKey(id));
|
|
929
|
+
// This native event is definite deletion, unlike a transient session.get failure.
|
|
930
|
+
await disposeCorrectionPermissions(context, id);
|
|
931
|
+
}
|
|
932
|
+
if (event.type === "session.execution.succeeded")
|
|
933
|
+
await finalize(id);
|
|
934
|
+
if (event.type === "session.created" || event.type === "session.deleted" || event.type === "session.idle") {
|
|
935
|
+
const info = event.type === "session.created" ? await context.session.get({ sessionID: id }).catch(() => ({ id })) : { id };
|
|
936
|
+
await hooks.event?.({ event: { type: String(event.type), properties: { sessionID: id, info } } });
|
|
937
|
+
for (const child of await hooks.reviewerCorrectionSessions?.(id) ?? [])
|
|
938
|
+
await syncReviewerCorrectionPermissions(context, hooks, child);
|
|
939
|
+
if (event.type !== "session.deleted")
|
|
940
|
+
await syncReviewerCorrectionPermissions(context, hooks, id);
|
|
941
|
+
}
|
|
942
|
+
if (event.type === "session.compaction.ended")
|
|
943
|
+
await hooks.event?.({ event: { type: "session.compacted", properties: { sessionID: id } } });
|
|
944
|
+
}
|
|
945
|
+
finally {
|
|
946
|
+
if (event.type === "session.deleted")
|
|
947
|
+
forgetWorkSession(context.location.directory, id);
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
void (async () => {
|
|
951
|
+
for await (const event of context.event.subscribe({ signal: controller.signal })) {
|
|
952
|
+
const data = record(event.data) ? event.data : {};
|
|
953
|
+
const lane = string(data.sessionID) ?? "filesystem";
|
|
954
|
+
const operation = (eventOperations.get(lane) ?? Promise.resolve()).then(() => handleEvent(event)).catch(error => {
|
|
955
|
+
console.warn("[sortie-dogs-v010] V2 event handling failed", error instanceof Error ? error.message : "unknown");
|
|
956
|
+
}).finally(() => { if (eventOperations.get(lane) === operation)
|
|
957
|
+
eventOperations.delete(lane); });
|
|
958
|
+
eventOperations.set(lane, operation);
|
|
959
|
+
}
|
|
960
|
+
})().catch(error => {
|
|
961
|
+
if (!controller.signal.aborted)
|
|
962
|
+
console.warn("[sortie-dogs-v010] V2 event adapter stopped", error instanceof Error ? error.message : "unknown");
|
|
963
|
+
});
|
|
964
|
+
return () => { controller.abort(); releaseWork(); };
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
releaseWork();
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
957
970
|
},
|
|
958
971
|
};
|
|
959
972
|
}
|