sortie-dogs 0.9.6 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/plugin/continuation.js +3 -2
- package/dist/plugin/index.js +77 -3
- package/package.json +59 -59
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
|
24
24
|
|
|
25
25
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
|
|
26
26
|
|
|
27
|
-
Release: [v0.9.
|
|
27
|
+
Release: [v0.9.7](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.7)
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
|
@@ -417,7 +417,12 @@ older runtime files, and records the installed version in
|
|
|
417
417
|
untouched and initialization stops safely. User-owned configuration—including
|
|
418
418
|
`.opencode/sortie-dogs.json`—and standard OpenCode files are preserved.
|
|
419
419
|
|
|
420
|
-
##
|
|
420
|
+
## Maintainer releases
|
|
421
|
+
|
|
422
|
+
The [release batch guide](docs/release-batch.md) covers fixed-tarball CLI verification,
|
|
423
|
+
global application, resumable GitHub publication, and manual npm publication checks.
|
|
424
|
+
|
|
425
|
+
## Safe manual removal
|
|
421
426
|
|
|
422
427
|
There is no supported Sortie-dogs uninstall command. Remove the npm dependency
|
|
423
428
|
separately, then follow the [safe manual removal guide](docs/uninstall.md) to
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* identity fails closed into "no automatic continuation" rather than into a guess.
|
|
13
13
|
*/
|
|
14
14
|
import { openCodeModel } from "./model-routing-hook.js";
|
|
15
|
+
import { terminalRunOutcome } from "./run-metrics.js";
|
|
15
16
|
/** Plugin tool name the coordinator asset names as the direct continuation capability. */
|
|
16
17
|
export const CONTINUATION_CAPABILITY = "sortie_compact_and_continue";
|
|
17
18
|
/** Fallback marker, used only when the direct capability is unavailable. */
|
|
@@ -445,8 +446,8 @@ export function createContinuationHooks(client, directory, policySource, timings
|
|
|
445
446
|
return firstCheckpoint(text)?.status;
|
|
446
447
|
}
|
|
447
448
|
function terminalCheckpoint(text) {
|
|
448
|
-
|
|
449
|
-
return
|
|
449
|
+
// Use the same terminal vocabulary as the receipt/debrief path, including INTERRUPTED.
|
|
450
|
+
return terminalRunOutcome(text) !== undefined;
|
|
450
451
|
}
|
|
451
452
|
function trueBlockerReport(text) {
|
|
452
453
|
const checkpoint = firstCheckpoint(text);
|
package/dist/plugin/index.js
CHANGED
|
@@ -1130,6 +1130,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1130
1130
|
const goalLedgerFiles = new Map();
|
|
1131
1131
|
const goalLedgerDirectories = new Set();
|
|
1132
1132
|
const goalReservations = new Map();
|
|
1133
|
+
const goalReservationRecoveries = new Map();
|
|
1133
1134
|
const hostGoalExecutions = new Map();
|
|
1134
1135
|
const goalValidationDefects = new Set();
|
|
1135
1136
|
const goalDeclarationAuthority = new Map();
|
|
@@ -1435,7 +1436,69 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1435
1436
|
startedAt: observedStartedAt, endedAt, exitCode: exitCode ?? null,
|
|
1436
1437
|
...(outcome === undefined ? {} : { outcome }), ...(immutableRef === undefined ? {} : { immutableRef }), fresh });
|
|
1437
1438
|
}
|
|
1439
|
+
async function recoverCompletedGoalReservations(sessionID) {
|
|
1440
|
+
const root = goalRoot(sessionID);
|
|
1441
|
+
const active = goalReservationRecoveries.get(root);
|
|
1442
|
+
if (active !== undefined)
|
|
1443
|
+
return active;
|
|
1444
|
+
const recovery = (async () => {
|
|
1445
|
+
const ledger = await goalLedger(sessionID);
|
|
1446
|
+
const state = (await ledger.readGoal()).state;
|
|
1447
|
+
const pending = state.outstanding_reservations.filter(reservation => ![...goalReservations.values()].some(live => live.reservationID === reservation.reservation_id));
|
|
1448
|
+
if (pending.length === 0 || state.goal_id === null || input.client?.session?.messages === undefined)
|
|
1449
|
+
return;
|
|
1450
|
+
const messages = input.client.session.messages;
|
|
1451
|
+
const response = await messages.call(input.client.session, { path: { id: root },
|
|
1452
|
+
query: { directory: input.directory, limit: 1000 } }).catch(() => undefined);
|
|
1453
|
+
const data = isRecord(response) ? response.data : undefined;
|
|
1454
|
+
if (!Array.isArray(data))
|
|
1455
|
+
return;
|
|
1456
|
+
for (const reservation of pending) {
|
|
1457
|
+
const matches = [];
|
|
1458
|
+
for (const message of data.slice(-1000)) {
|
|
1459
|
+
if (!isRecord(message) || !isRecord(message.info) || message.info.role !== "assistant" ||
|
|
1460
|
+
message.info.sessionID !== root || !Array.isArray(message.parts))
|
|
1461
|
+
continue;
|
|
1462
|
+
for (const part of message.parts) {
|
|
1463
|
+
if (!isRecord(part) || part.type !== "tool" || part.tool !== "task" || typeof part.callID !== "string" ||
|
|
1464
|
+
!isRecord(part.state) || !["completed", "error"].includes(String(part.state.status)) ||
|
|
1465
|
+
!isRecord(part.state.input) || typeof part.state.input.prompt !== "string" ||
|
|
1466
|
+
!["dog-worker", "dog-luna-worker"].includes(String(part.state.input.subagent_type)))
|
|
1467
|
+
continue;
|
|
1468
|
+
const unitID = handoffValue(handoffEntries(part.state.input.prompt), ["task_id"]) ?? part.callID;
|
|
1469
|
+
if (unitID !== reservation.unit_id || goalFingerprint({ goal_id: state.goal_id, unit_id: unitID,
|
|
1470
|
+
call_id: part.callID }) !== reservation.reservation_id)
|
|
1471
|
+
continue;
|
|
1472
|
+
const time = isRecord(part.state.time) ? part.state.time : undefined;
|
|
1473
|
+
const elapsed = typeof time?.start === "number" && typeof time.end === "number" &&
|
|
1474
|
+
Number.isFinite(time.start) && Number.isFinite(time.end) && time.end >= time.start ? time.end - time.start : null;
|
|
1475
|
+
matches.push({ callID: part.callID, status: String(part.state.status), elapsed });
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
if (matches.length !== 1)
|
|
1479
|
+
continue;
|
|
1480
|
+
const match = matches[0];
|
|
1481
|
+
// Reconcile lifecycle accounting only. Lost in-memory validation bindings cannot be
|
|
1482
|
+
// reconstructed from worker prose and must not manufacture acceptance evidence.
|
|
1483
|
+
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
1484
|
+
reservation_id: reservation.reservation_id,
|
|
1485
|
+
receipt_id: goalFingerprint({ recovered_host_task: match.callID, reservation: reservation.reservation_id }),
|
|
1486
|
+
goal_id: state.goal_id, unit_id: reservation.unit_id,
|
|
1487
|
+
disposition: match.status === "completed" ? "succeeded" : "cancelled", result_class: "process-defect",
|
|
1488
|
+
progress_fingerprint: null, evidence: [], elapsed_ms: match.elapsed, cost_usd: null });
|
|
1489
|
+
appLogInfo("goal.reservation-recovered", root, { unitID: reservation.unit_id, hostStatus: match.status });
|
|
1490
|
+
}
|
|
1491
|
+
})();
|
|
1492
|
+
goalReservationRecoveries.set(root, recovery);
|
|
1493
|
+
try {
|
|
1494
|
+
await recovery;
|
|
1495
|
+
}
|
|
1496
|
+
finally {
|
|
1497
|
+
goalReservationRecoveries.delete(root);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1438
1500
|
async function acceptRealGoalTurn(sessionID, messageID, selectedAgent, parts) {
|
|
1501
|
+
await recoverCompletedGoalReservations(sessionID);
|
|
1439
1502
|
const ledger = await goalLedger(sessionID);
|
|
1440
1503
|
const state = (await ledger.readGoal()).state;
|
|
1441
1504
|
if (state.latest_user_message_id === messageID)
|
|
@@ -1824,6 +1887,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1824
1887
|
let state = await bindGoalDeclaration(sessionID, prompt) ?? (await ledger.readGoal()).state;
|
|
1825
1888
|
if (state.goal_id === null)
|
|
1826
1889
|
return; // Legacy already-authorized roots may settle without inventing authority.
|
|
1890
|
+
const budgetDenied = (dimension) => new Error("SORTIE_GOAL_CONTROL_DENIED: stop_budget\n" + JSON.stringify({
|
|
1891
|
+
goal_id: state.goal_id, revision: state.revision, exhausted_dimension: dimension,
|
|
1892
|
+
budget: state.budget, consumed_units: state.consumed_units,
|
|
1893
|
+
reserved_units: state.outstanding_reservations.length,
|
|
1894
|
+
remaining_units: state.budget === null ? null : Math.max(0, state.budget.max_units - state.consumed_units - state.outstanding_reservations.length),
|
|
1895
|
+
consumed_time_ms: state.consumed_time_ms, consumed_cost_usd: state.consumed_cost_usd,
|
|
1896
|
+
validation_consumed: state.validation_budget.consumed, validation_limit: state.validation_budget.limit,
|
|
1897
|
+
remedy: "goal_budget_units is the cumulative limit across this goal's revisions, not an added allowance. " +
|
|
1898
|
+
"Use these host counters when requesting an explicit budget revision. Renaming task_id or contracts does not create a new goal. " +
|
|
1899
|
+
"If the user holds or stops, report status: INTERRUPTED and do not redispatch."
|
|
1900
|
+
}));
|
|
1827
1901
|
if (state.phase === "terminal" || state.receipt !== null) {
|
|
1828
1902
|
throw new Error("SORTIE_GOAL_CONTROL_DENIED: terminal");
|
|
1829
1903
|
}
|
|
@@ -1837,17 +1911,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1837
1911
|
}
|
|
1838
1912
|
if (state.budget !== null && state.consumed_units + state.outstanding_reservations.length >= state.budget.max_units) {
|
|
1839
1913
|
await terminalGoal(sessionID, "stop_budget", "stopped");
|
|
1840
|
-
throw
|
|
1914
|
+
throw budgetDenied("units");
|
|
1841
1915
|
}
|
|
1842
1916
|
if (state.budget !== null && state.budget.time_ms !== null &&
|
|
1843
1917
|
(state.consumed_time_ms === null || state.consumed_time_ms >= state.budget.time_ms)) {
|
|
1844
1918
|
await terminalGoal(sessionID, "stop_budget", "stopped");
|
|
1845
|
-
throw
|
|
1919
|
+
throw budgetDenied("time_ms");
|
|
1846
1920
|
}
|
|
1847
1921
|
if (state.budget !== null && state.budget.cost_usd !== null &&
|
|
1848
1922
|
(state.consumed_cost_usd === null || state.consumed_cost_usd >= state.budget.cost_usd)) {
|
|
1849
1923
|
await terminalGoal(sessionID, "stop_budget", "stopped");
|
|
1850
|
-
throw
|
|
1924
|
+
throw budgetDenied("cost_usd");
|
|
1851
1925
|
}
|
|
1852
1926
|
if (state.goal_id === null)
|
|
1853
1927
|
return;
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "sortie-dogs",
|
|
3
|
-
"version": "0.9.
|
|
1
|
+
{
|
|
2
|
+
"name": "sortie-dogs",
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"opencode",
|
|
7
|
-
"opencode-plugin",
|
|
8
|
-
"ai-orchestration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"opencode",
|
|
7
|
+
"opencode-plugin",
|
|
8
|
+
"ai-orchestration",
|
|
9
9
|
"multi-agent",
|
|
10
10
|
"agent-harness",
|
|
11
11
|
"harness-engineering",
|
|
12
12
|
"coding-agent",
|
|
13
13
|
"agentic-coding"
|
|
14
|
-
],
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/zufall-upon/Sortie-dogs.git"
|
|
19
|
-
},
|
|
20
|
-
"bugs": {
|
|
21
|
-
"url": "https://github.com/zufall-upon/Sortie-dogs/issues"
|
|
22
|
-
},
|
|
23
|
-
"homepage": "https://github.com/zufall-upon/Sortie-dogs#readme",
|
|
24
|
-
"publishConfig": {
|
|
25
|
-
"access": "public"
|
|
26
|
-
},
|
|
27
|
-
"type": "module",
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=22.6.0"
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"dist"
|
|
33
|
-
],
|
|
34
|
-
"exports": {
|
|
35
|
-
".": {
|
|
36
|
-
"types": "./dist/index.d.ts",
|
|
37
|
-
"import": "./dist/index.js"
|
|
38
|
-
},
|
|
39
|
-
"./plugin": {
|
|
40
|
-
"types": "./dist/plugin/opencode.d.ts",
|
|
41
|
-
"import": "./dist/plugin/opencode.js"
|
|
42
|
-
},
|
|
43
|
-
"./server": {
|
|
44
|
-
"types": "./dist/plugin/opencode.d.ts",
|
|
45
|
-
"import": "./dist/plugin/opencode.js"
|
|
46
|
-
},
|
|
47
|
-
"./assets": {
|
|
48
|
-
"types": "./dist/runtime-assets.d.ts",
|
|
49
|
-
"import": "./dist/runtime-assets.js"
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
"bin": {
|
|
53
|
-
"sortie-dogs": "dist/cli/main.js"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/zufall-upon/Sortie-dogs.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/zufall-upon/Sortie-dogs/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/zufall-upon/Sortie-dogs#readme",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.6.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./plugin": {
|
|
40
|
+
"types": "./dist/plugin/opencode.d.ts",
|
|
41
|
+
"import": "./dist/plugin/opencode.js"
|
|
42
|
+
},
|
|
43
|
+
"./server": {
|
|
44
|
+
"types": "./dist/plugin/opencode.d.ts",
|
|
45
|
+
"import": "./dist/plugin/opencode.js"
|
|
46
|
+
},
|
|
47
|
+
"./assets": {
|
|
48
|
+
"types": "./dist/runtime-assets.d.ts",
|
|
49
|
+
"import": "./dist/runtime-assets.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"sortie-dogs": "dist/cli/main.js"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"prebuild": "node --input-type=module --eval \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });\"",
|
|
57
57
|
"build": "tsc -p tsconfig.json",
|
|
58
|
-
"postbuild": "node --input-type=module --eval \"import fs from 'node:fs'; const path = 'dist/cli/main.js'; const source = fs.readFileSync(path, 'utf8'); if (!source.startsWith('#!')) fs.writeFileSync(path, '#!/usr/bin/env node\\n' + source); if (process.platform !== 'win32') fs.chmodSync(path, 0o755);\"",
|
|
59
|
-
"prepack": "npm run build",
|
|
58
|
+
"postbuild": "node --input-type=module --eval \"import fs from 'node:fs'; const path = 'dist/cli/main.js'; const source = fs.readFileSync(path, 'utf8'); if (!source.startsWith('#!')) fs.writeFileSync(path, '#!/usr/bin/env node\\n' + source); if (process.platform !== 'win32') fs.chmodSync(path, 0o755);\"",
|
|
59
|
+
"prepack": "npm run build",
|
|
60
60
|
"pretest": "npm run build",
|
|
61
61
|
"test": "node --experimental-strip-types --import ./test/setup.ts --test \"test/plugin.test.ts\" \"test/continuation.test.ts\" \"test/fast-lane.test.ts\"",
|
|
62
62
|
"pretest:dispatch": "npm run build",
|
|
@@ -65,13 +65,13 @@
|
|
|
65
65
|
"test:integration": "node --experimental-strip-types --import ./test/setup.ts --test --test-timeout=1200000 --test-reporter=spec \"test/integration/worktree-parallel-dispatch.test.ts\"",
|
|
66
66
|
"pretest:full": "npm run build",
|
|
67
67
|
"test:full": "node --experimental-strip-types test/helpers/full-test-runner.ts"
|
|
68
|
-
},
|
|
69
|
-
"dependencies": {
|
|
70
|
-
"ajv": "^8.17.1",
|
|
71
|
-
"ajv-formats": "^3.0.1"
|
|
72
|
-
},
|
|
73
|
-
"devDependencies": {
|
|
74
|
-
"@types/node": "^22.0.0",
|
|
75
|
-
"typescript": "^5.9.0"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"ajv": "^8.17.1",
|
|
71
|
+
"ajv-formats": "^3.0.1"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/node": "^22.0.0",
|
|
75
|
+
"typescript": "^5.9.0"
|
|
76
|
+
}
|
|
77
|
+
}
|