sortie-dogs 0.9.7 → 0.9.8
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 +80 -60
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/goal-bound.d.ts +1 -1
- package/dist/core/goal-declaration-format.d.ts +2 -0
- package/dist/core/goal-declaration-format.js +52 -3
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.js +137 -19
- package/dist/plugin/run-metrics.js +51 -17
- package/dist/plugin/sortie-debrief.d.ts +4 -0
- package/dist/plugin/sortie-debrief.js +53 -20
- package/dist/runtime-assets.d.ts +39 -16
- package/dist/runtime-assets.js +30 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Sortie-dogs
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Add a bounded, cost-aware execution loop to OpenCode without taking OpenCode over.**
|
|
4
4
|
|
|
5
|
-
Sortie-dogs is an
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Sortie-dogs is an opt-in overlay, not a replacement. It stays passive until you
|
|
6
|
+
invoke `/sortie` or select `dog-coordinator`. Standard OpenCode agents and
|
|
7
|
+
unrelated sessions remain available and unchanged.
|
|
8
8
|
|
|
9
|
-
> **Project status: Beta.**
|
|
10
|
-
> configuration and runtime assets may still change
|
|
9
|
+
> **Project status: Beta.** v0.9.x is under active stabilization. Runtime
|
|
10
|
+
> behavior, configuration, and runtime assets may still change before 1.0.
|
|
11
11
|
|
|
12
12
|
[](https://www.npmjs.com/package/sortie-dogs)
|
|
13
13
|
[](LICENSE)
|
|
@@ -15,47 +15,63 @@ orchestration.
|
|
|
15
15
|
|
|
16
16
|

|
|
17
17
|
|
|
18
|
-
Sortie-dogs
|
|
19
|
-
|
|
20
|
-
validation, and evidence-backed completion—while preserving standard OpenCode
|
|
21
|
-
agents and settings.
|
|
18
|
+
Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
|
|
19
|
+
bounded implementation, canonical validation, and evidence-backed completion.
|
|
22
20
|
|
|
23
21
|
Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
24
22
|
|
|
25
23
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
|
|
26
24
|
|
|
27
|
-
Release: [v0.9.
|
|
28
|
-
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
Release: [v0.9.8](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.8)
|
|
26
|
+
|
|
27
|
+
## Why Sortie-dogs?
|
|
28
|
+
|
|
29
|
+
### Invisible until invited
|
|
30
|
+
|
|
31
|
+
Use normal OpenCode normally. Sortie activates only for `/sortie` or
|
|
32
|
+
`dog-coordinator`; it does not disable or replace OpenCode's standard agents.
|
|
33
|
+
|
|
34
|
+
### Spend strong models only where they matter
|
|
35
|
+
|
|
36
|
+
Lower-cost models handle bounded retrieval and parallel volume work. Stronger
|
|
37
|
+
models are reserved for implementation, escalation, and independent review.
|
|
38
|
+
|
|
39
|
+
### Return with proof
|
|
40
|
+
|
|
41
|
+
Writes stay scoped, and completion requires validation evidence. Every completed
|
|
42
|
+
run can return a concise Speed / Cost / Proof debrief.
|
|
43
|
+
|
|
44
|
+
## Designed to coexist with OpenCode
|
|
45
|
+
|
|
46
|
+
Sortie-dogs adds a workflow to your existing setup rather than replacing it.
|
|
47
|
+
|
|
48
|
+
- It does not disable OpenCode's native agents or replace standard roles such as
|
|
49
|
+
`build`, `plan`, `explore`, or `general`.
|
|
50
|
+
- Ordinary sessions are not automatically converted into Sortie workflows.
|
|
51
|
+
- Project-local initialization is the recommended setup and does not change
|
|
52
|
+
user settings.
|
|
53
|
+
- Global runtime availability and cross-project reflection require separate,
|
|
54
|
+
explicit opt-in. Reflection is disabled by default.
|
|
55
|
+
- Unknown or user-owned runtime files are preserved rather than overwritten.
|
|
56
|
+
- Manual removal targets only known Sortie-owned runtime assets.
|
|
57
|
+
|
|
58
|
+
Use OpenCode normally. Invoke the pack only when you want it.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
Install the public npm package in the project and generate the project-local
|
|
63
|
+
OpenCode runtime files:
|
|
33
64
|
|
|
34
65
|
```sh
|
|
35
66
|
npm install --save-dev sortie-dogs
|
|
36
|
-
npx sortie-dogs init .
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
sortie-dogs init --global
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
This installs the canonical runtime assets in OpenCode's global configuration,
|
|
48
|
-
so `dog-coordinator` can be selected from other projects without project-local
|
|
49
|
-
initialization. Global initialization and project-local initialization are
|
|
50
|
-
separate: `sortie-dogs init .` still writes runtime files only into that
|
|
51
|
-
project. Project-local configuration and the plugin bridge below remain
|
|
52
|
-
available when a project needs its own settings or dependency.
|
|
53
|
-
|
|
54
|
-
Installing the runtime assets does not load the plugin, and without the plugin
|
|
55
|
-
every role runs on whichever model the caller happened to use. Add the package
|
|
56
|
-
to the `plugin` array of the OpenCode configuration the agents run under —
|
|
57
|
-
`~/.config/opencode/opencode.json` for the global assets, or the project's
|
|
58
|
-
`.opencode/opencode.json`:
|
|
67
|
+
npx sortie-dogs init .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This is the recommended setup. Runtime assets stay project-local.
|
|
71
|
+
|
|
72
|
+
Installing the runtime assets does not load the plugin, and without the plugin
|
|
73
|
+
every role runs on whichever model the caller happened to use. Add the package
|
|
74
|
+
to the `plugin` array in the project's `.opencode/opencode.json`:
|
|
59
75
|
|
|
60
76
|
```json
|
|
61
77
|
{
|
|
@@ -218,26 +234,7 @@ global file for durable global settings.
|
|
|
218
234
|
unit, with a maximum of three records per run; routine bugs and external
|
|
219
235
|
failures are never journaled.
|
|
220
236
|
|
|
221
|
-
##
|
|
222
|
-
|
|
223
|
-
- **Focused when invited, invisible otherwise.** Activate it with `/sortie` or
|
|
224
|
-
select `dog-coordinator`; ordinary OpenCode sessions remain unchanged.
|
|
225
|
-
- **Evidence gathering without arbitrary dispatch ceilings.** A bounded scout resolves
|
|
226
|
-
each concrete manifest, validation, or ownership gap whenever it appears; unchanged
|
|
227
|
-
requests are not repeated.
|
|
228
|
-
- **Writes stay inside the assignment.** Exact source or operation manifests
|
|
229
|
-
gate edits and handoffs.
|
|
230
|
-
- **Autonomous implementation routing.** An accepted scope with at least two safe independent
|
|
231
|
-
units defaults to Luna fabric without opt-in. An explicit serial/no-parallel request wins;
|
|
232
|
-
other work uses sequential workers. The separate parallel contract remains explicit.
|
|
233
|
-
- **Runtime overlap protection.** Active equal or ancestor write scopes are rejected
|
|
234
|
-
before mutation, and full validation waits until every parallel unit joins.
|
|
235
|
-
- **Evidence before completion.** Canonical validation, risk-based review, and
|
|
236
|
-
terminal evidence gate coordinator-owned completion and commits.
|
|
237
|
-
- **Long work can recover.** Restart recovery and bounded compaction continue
|
|
238
|
-
from retained handoff context rather than silently starting over.
|
|
239
|
-
|
|
240
|
-
## Example run
|
|
237
|
+
## Example run
|
|
241
238
|
|
|
242
239
|
An illustrative low-risk run stays bounded and reports its gates:
|
|
243
240
|
|
|
@@ -400,7 +397,30 @@ coordinator. `dog-reviewer` independently checks high-risk candidates after
|
|
|
400
397
|
canonical validation. Neither role implements, stages, commits, or acts as a
|
|
401
398
|
user-facing worker.
|
|
402
399
|
|
|
403
|
-
##
|
|
400
|
+
## Optional global availability
|
|
401
|
+
|
|
402
|
+
If you intentionally want the Sortie roles available across projects, install
|
|
403
|
+
the CLI and runtime assets globally:
|
|
404
|
+
|
|
405
|
+
```sh
|
|
406
|
+
npm install --global sortie-dogs
|
|
407
|
+
sortie-dogs init --global
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
This writes canonical Sortie runtime assets to OpenCode's global configuration;
|
|
411
|
+
it does not make project-local initialization global. Load the plugin from the
|
|
412
|
+
global `~/.config/opencode/opencode.json` when using these assets:
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"plugin": ["sortie-dogs"]
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Project-local configuration and plugin loading remain available when a project
|
|
421
|
+
needs its own settings or dependency.
|
|
422
|
+
|
|
423
|
+
## Updates and migration
|
|
404
424
|
|
|
405
425
|
After replacing the dependency with a newer release asset, run:
|
|
406
426
|
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export declare const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export declare const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
|
|
6
6
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Version of the installable runtime assets. Kept in its own module so the plugin can compare an
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
|
-
export const RUNTIME_ASSET_VERSION = "0.3.
|
|
5
|
+
export const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
|
|
@@ -243,7 +243,7 @@ export interface GoalFlightState {
|
|
|
243
243
|
}
|
|
244
244
|
export declare class GoalBoundError extends Error {
|
|
245
245
|
readonly code: "invalid" | "transition" | "budget" | "ticket" | "evidence";
|
|
246
|
-
constructor(code: "
|
|
246
|
+
constructor(code: GoalBoundError["code"], message: string);
|
|
247
247
|
}
|
|
248
248
|
export declare function goalFingerprint(value: unknown): string;
|
|
249
249
|
export declare function selectGoalDelivery(input: {
|
|
@@ -2,3 +2,5 @@
|
|
|
2
2
|
export declare const GOAL_DELIVERY_INTENTS: readonly ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
3
3
|
export declare const GOAL_DELIVERY_MODES: readonly ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
4
4
|
export declare const GOAL_DECLARATION_FORMAT: string;
|
|
5
|
+
/** Normalize a shared declaration without guessing acceptance, coverage, delivery, or validation. */
|
|
6
|
+
export declare function expandGoalDeclaration(value: unknown): string;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { goalFingerprint } from "./goal-bound.js";
|
|
1
2
|
/** Shared planner guidance and admission enums; no inferred acceptance or dispatch authority. */
|
|
2
3
|
export const GOAL_DELIVERY_INTENTS = ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
3
4
|
export const GOAL_DELIVERY_MODES = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
4
|
-
export const GOAL_DECLARATION_FORMAT = `
|
|
5
|
+
export const GOAL_DECLARATION_FORMAT = `Declare a goal once, then reference it with goal_declaration_path in the Task prompt.
|
|
6
|
+
The referenced JSON may contain shared defaults and a criteria array; the host expands them privately.
|
|
7
|
+
An inline ext["sortie-dogs/goal-declaration"] in the registered handoff is also supported.
|
|
8
|
+
Existing accepted goals need no repeated declaration; budget-only revisions retain their criteria.
|
|
9
|
+
Legacy flat goal_* fields remain supported. No need to rewrite unrelated files or repeat common fields for each criterion.
|
|
5
10
|
delivery_intent must be exactly one of: ${GOAL_DELIVERY_INTENTS.join(" | ")}. Never use prose or a boolean.
|
|
6
11
|
delivery_mode is a separate optional enum: ${GOAL_DELIVERY_MODES.join(" | ")}.
|
|
7
12
|
Use flat key: value lines. Each criterion begins with its own goal_criterion_id: line.
|
|
8
|
-
|
|
9
|
-
YAML object/list wrappers, inline { goal_criterion_id: ... }, or JSON objects for these blocks.
|
|
13
|
+
For legacy flat input each criterion has a goal_criterion_id line. Prefer shared JSON defaults for multiple criteria.
|
|
10
14
|
Replace every placeholder with the accepted task's actual value; preserve its acceptance and validation:
|
|
11
15
|
goal_acceptance_fingerprint: <sha256: followed by exactly 64 lowercase hexadecimal characters>
|
|
12
16
|
delivery_intent: implementation
|
|
@@ -31,3 +35,48 @@ goal_validation_command: <exact operation manifest validation command>
|
|
|
31
35
|
The example's implementation/mvp-first selections apply only to an implementation without its usable path;
|
|
32
36
|
select other enum values only from the accepted request. A declaration rejection launches no child.
|
|
33
37
|
Repair the Task prompt's named fields, not unrelated files; retry only with the corrected declaration.`;
|
|
38
|
+
/** Normalize a shared declaration without guessing acceptance, coverage, delivery, or validation. */
|
|
39
|
+
export function expandGoalDeclaration(value) {
|
|
40
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
41
|
+
throw new Error("goal declaration must be an object");
|
|
42
|
+
const object = value;
|
|
43
|
+
if (!Array.isArray(object.criteria) || object.criteria.length === 0)
|
|
44
|
+
throw new Error("goal declaration requires criteria");
|
|
45
|
+
const defaults = object.defaults !== null && typeof object.defaults === "object" && !Array.isArray(object.defaults)
|
|
46
|
+
? object.defaults : {};
|
|
47
|
+
const fields = ["criterion_id", "target", "entrypoint", "workload", "oracle_coverage", "build_boundary", "source",
|
|
48
|
+
"candidate", "source_binding", "candidate_binding", "fixture", "proof_scope", "expected_outcome", "validation_command"];
|
|
49
|
+
const scalar = (value) => {
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
if (typeof value === "string" && !/[\r\n]/u.test(value))
|
|
53
|
+
return value;
|
|
54
|
+
if (typeof value === "boolean" || typeof value === "number")
|
|
55
|
+
return String(value);
|
|
56
|
+
throw new Error("goal declaration field must be a single-line scalar or array");
|
|
57
|
+
};
|
|
58
|
+
const normalizedFields = (values) => Object.fromEntries(fields.flatMap(field => {
|
|
59
|
+
const found = values[`goal_${field}`] ?? values[field];
|
|
60
|
+
return found === undefined ? [] : [[`goal_${field}`, found]];
|
|
61
|
+
}));
|
|
62
|
+
const criteria = object.criteria.map(raw => {
|
|
63
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
64
|
+
throw new Error("goal criterion must be an object");
|
|
65
|
+
return { ...normalizedFields(defaults), ...normalizedFields(raw) };
|
|
66
|
+
});
|
|
67
|
+
const identity = { criteria, delivery_intent: object.delivery_intent, delivery_mode: object.delivery_mode,
|
|
68
|
+
usable_path_established: object.usable_path_established, controlled_change: object.controlled_change };
|
|
69
|
+
const lines = [`goal_acceptance_fingerprint: ${scalar(object.goal_acceptance_fingerprint ?? goalFingerprint(identity))}`];
|
|
70
|
+
for (const field of ["delivery_intent", "delivery_mode", "usable_path_established", "controlled_change",
|
|
71
|
+
"goal_budget_units", "goal_budget_time_ms", "goal_budget_cost_usd"]) {
|
|
72
|
+
if (object[field] !== undefined)
|
|
73
|
+
lines.push(`${field}: ${scalar(object[field])}`);
|
|
74
|
+
}
|
|
75
|
+
for (const criterion of criteria) {
|
|
76
|
+
criterion.goal_criterion_id ??= `criterion-${goalFingerprint(criterion).slice(7, 31)}`;
|
|
77
|
+
for (const field of fields)
|
|
78
|
+
if (criterion[`goal_${field}`] !== undefined)
|
|
79
|
+
lines.push(`goal_${field}: ${scalar(criterion[`goal_${field}`])}`);
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface OpenCodeHooks {
|
|
|
65
65
|
/** Continuation observes the coordinator's completed final text to honour its fallback markers. */
|
|
66
66
|
"experimental.text.complete"?: (input: {
|
|
67
67
|
sessionID: string;
|
|
68
|
+
messageID?: string;
|
|
69
|
+
partID?: string;
|
|
68
70
|
}, output: {
|
|
69
71
|
text: string;
|
|
70
72
|
}) => Promise<void>;
|
package/dist/plugin/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
10
10
|
import { lstat, mkdir, open, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
11
11
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
12
12
|
import { RUNTIME_ASSET_VERSION } from "../asset-version.js";
|
|
13
|
-
import { GOAL_DECLARATION_FORMAT, GOAL_DELIVERY_INTENTS, GOAL_DELIVERY_MODES } from "../core/goal-declaration-format.js";
|
|
13
|
+
import { GOAL_DECLARATION_FORMAT, GOAL_DELIVERY_INTENTS, GOAL_DELIVERY_MODES, expandGoalDeclaration } from "../core/goal-declaration-format.js";
|
|
14
14
|
import { ACCEPTANCE_CONTINUITY_AUTHORITY, ACCEPTANCE_CONTINUITY_EXTENSION, ACCEPTANCE_CONTINUITY_SCHEMA_VERSION, acceptanceContinuityFingerprint, inspectAcceptanceContinuity, normalizeAcceptanceCriteria, } from "../core/acceptance-continuity.js";
|
|
15
15
|
import { resolveGlobalConfigRoot } from "../core/initialize.js";
|
|
16
16
|
import { admitLunaFabric } from "../core/luna-fabric-contract.js";
|
|
@@ -1699,6 +1699,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1699
1699
|
rootAcceptanceContinuity.delete(sessionID);
|
|
1700
1700
|
return { outcome, goal, receipt, delivery, records: snapshot?.records };
|
|
1701
1701
|
}
|
|
1702
|
+
const ROOT_INTERRUPTION = /(^|\n)TRUE_INTERRUPTION\s*:\s*(?:user|internal)\s*:/iu;
|
|
1703
|
+
async function preserveActiveGoalContinuation(sessionID, text, messageID) {
|
|
1704
|
+
if (!isCoordinatorSession(sessionID) || terminalRunOutcome(text) !== "INTERRUPTED" ||
|
|
1705
|
+
ROOT_INTERRUPTION.test(text) || hasCoordinatorInterruption(sessionID, messageID)) {
|
|
1706
|
+
return text;
|
|
1707
|
+
}
|
|
1708
|
+
const goal = await currentGoal(sessionID).catch(() => undefined);
|
|
1709
|
+
if (goal === undefined || goal.goal_id === null || goal.receipt !== null)
|
|
1710
|
+
return text;
|
|
1711
|
+
return replaceTerminalStatus(text, "status: IN_PROGRESS — local/process/step continuation remains active in the same session");
|
|
1712
|
+
}
|
|
1702
1713
|
function goalDeclarationContract(prompt) {
|
|
1703
1714
|
const lines = prompt.split(/\r?\n/u);
|
|
1704
1715
|
const criterionStarts = lines.flatMap((line, index) => /^\s*goal_criterion_id\s*:/u.test(line) ? [index] : []);
|
|
@@ -1822,15 +1833,52 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1822
1833
|
});
|
|
1823
1834
|
return { declaration: { fingerprint, delivery, contract: contract.contract }, defects };
|
|
1824
1835
|
}
|
|
1836
|
+
function resolveGoalDeclaration(state, prompt) {
|
|
1837
|
+
const keys = [...handoffEntries(prompt).keys()].filter(key => /^(?:goal_|delivery_intent$|delivery_mode$|usable_path_established$|controlled_change$)/u.test(key));
|
|
1838
|
+
const budgetOnly = keys.every(key => ["goal_budget_units", "goal_budget_time_ms", "goal_budget_cost_usd",
|
|
1839
|
+
"goal_acceptance_fingerprint"].includes(key));
|
|
1840
|
+
const fingerprint = handoffValue(handoffEntries(prompt), ["goal_acceptance_fingerprint"]);
|
|
1841
|
+
if (budgetOnly && state.acceptance_contract !== null && state.acceptance_fingerprint !== null && state.delivery !== null &&
|
|
1842
|
+
(fingerprint === undefined || fingerprint === state.acceptance_fingerprint)) {
|
|
1843
|
+
return { declaration: { fingerprint: state.acceptance_fingerprint, delivery: state.delivery,
|
|
1844
|
+
contract: state.acceptance_contract }, defects: [], inherited: true };
|
|
1845
|
+
}
|
|
1846
|
+
return { ...validateGoalDeclaration(prompt), inherited: false };
|
|
1847
|
+
}
|
|
1848
|
+
async function resolveGoalPrompt(prompt) {
|
|
1849
|
+
const entries = handoffEntries(prompt);
|
|
1850
|
+
if (entries.has("goal_criterion_id"))
|
|
1851
|
+
return prompt;
|
|
1852
|
+
const reference = handoffValue(entries, ["goal_declaration_path"]);
|
|
1853
|
+
let definition;
|
|
1854
|
+
if (reference !== undefined)
|
|
1855
|
+
definition = await readJson(resolve(input.directory, reference), INPUT_LIMITS.handoff);
|
|
1856
|
+
else {
|
|
1857
|
+
const handoffPath = handoffValue(entries, ["handoff_path", "handoffpath"]);
|
|
1858
|
+
if (handoffPath !== undefined) {
|
|
1859
|
+
const handoff = await readJson(resolve(input.directory, handoffPath), INPUT_LIMITS.handoff);
|
|
1860
|
+
if (isRecord(handoff) && isRecord(handoff.ext))
|
|
1861
|
+
definition = handoff.ext["sortie-dogs/goal-declaration"];
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
if (definition === undefined)
|
|
1865
|
+
return prompt;
|
|
1866
|
+
const expanded = expandGoalDeclaration(definition).split("\n").filter(line => {
|
|
1867
|
+
const field = line.slice(0, line.indexOf(":"));
|
|
1868
|
+
return !entries.has(field);
|
|
1869
|
+
});
|
|
1870
|
+
return `${prompt}\n${expanded.join("\n")}`;
|
|
1871
|
+
}
|
|
1825
1872
|
async function bindGoalDeclaration(sessionID, prompt) {
|
|
1873
|
+
prompt = await resolveGoalPrompt(prompt);
|
|
1826
1874
|
const ledger = await goalLedger(sessionID);
|
|
1827
1875
|
let state = (await ledger.readGoal()).state;
|
|
1828
1876
|
if (state.goal_id === null || state.origin_user_message_id === null)
|
|
1829
1877
|
return undefined;
|
|
1830
1878
|
const entries = handoffEntries(prompt);
|
|
1831
1879
|
const typedDeclarationPresent = prompt.split(/\r?\n/u).some((line) => /^\s*(?:goal_[a-z0-9_]+|delivery_intent|delivery_mode|usable_path_established|controlled_change)\s*:/iu.test(line));
|
|
1832
|
-
//
|
|
1833
|
-
//
|
|
1880
|
+
// A budget-only amendment retains the accepted goal contract, just like a later unit with
|
|
1881
|
+
// no declaration fields. Explicit acceptance changes still use the existing full declaration.
|
|
1834
1882
|
if (!typedDeclarationPresent) {
|
|
1835
1883
|
if (state.acceptance_contract === null && goalDeclarationAuthority.get(sessionID) === state.latest_user_message_id) {
|
|
1836
1884
|
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", {
|
|
@@ -1841,7 +1889,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1841
1889
|
goalDeclarationAuthority.delete(sessionID);
|
|
1842
1890
|
return state;
|
|
1843
1891
|
}
|
|
1844
|
-
const validated =
|
|
1892
|
+
const validated = resolveGoalDeclaration(state, prompt);
|
|
1845
1893
|
if (validated.declaration === undefined) {
|
|
1846
1894
|
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: validated.defects });
|
|
1847
1895
|
}
|
|
@@ -1883,6 +1931,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1883
1931
|
async function reserveGoalDispatch(sessionID, callID, prompt) {
|
|
1884
1932
|
if (goalReservations.has(callID))
|
|
1885
1933
|
return;
|
|
1934
|
+
await recoverCompletedGoalReservations(sessionID);
|
|
1886
1935
|
const ledger = await goalLedger(sessionID);
|
|
1887
1936
|
let state = await bindGoalDeclaration(sessionID, prompt) ?? (await ledger.readGoal()).state;
|
|
1888
1937
|
if (state.goal_id === null)
|
|
@@ -2064,6 +2113,26 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
2064
2113
|
}), { issueTicket: issueGoalTicket });
|
|
2065
2114
|
const completedCoordinatorMessages = new Set();
|
|
2066
2115
|
const completedCoordinatorParts = new Set();
|
|
2116
|
+
const interruptedCoordinatorMessages = new Map();
|
|
2117
|
+
function rememberCoordinatorInterruption(sessionID, info) {
|
|
2118
|
+
const error = isRecord(info.error) ? info.error : undefined;
|
|
2119
|
+
if (info.role !== "assistant" || info.agent !== COORDINATOR_AGENT || typeof info.id !== "string" ||
|
|
2120
|
+
error?.name !== "MessageAbortedError")
|
|
2121
|
+
return;
|
|
2122
|
+
const messages = interruptedCoordinatorMessages.get(sessionID) ?? new Set();
|
|
2123
|
+
messages.add(info.id);
|
|
2124
|
+
interruptedCoordinatorMessages.delete(sessionID);
|
|
2125
|
+
interruptedCoordinatorMessages.set(sessionID, messages);
|
|
2126
|
+
while (messages.size > ACTIVE_SESSION_CACHE.maximum)
|
|
2127
|
+
messages.delete(messages.values().next().value);
|
|
2128
|
+
while (interruptedCoordinatorMessages.size > ACTIVE_SESSION_CACHE.maximum) {
|
|
2129
|
+
interruptedCoordinatorMessages.delete(interruptedCoordinatorMessages.keys().next().value);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function hasCoordinatorInterruption(sessionID, messageID) {
|
|
2133
|
+
const messages = interruptedCoordinatorMessages.get(sessionID);
|
|
2134
|
+
return messages !== undefined && (messageID === undefined || messages.has(messageID));
|
|
2135
|
+
}
|
|
2067
2136
|
function freshSessionFallback(reason, action) {
|
|
2068
2137
|
return { status: "user-action-required", reason, action, retry_same_session: false };
|
|
2069
2138
|
}
|
|
@@ -4734,6 +4803,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4734
4803
|
return true;
|
|
4735
4804
|
}
|
|
4736
4805
|
function expireSession(sessionID) {
|
|
4806
|
+
for (const key of inspectionOperations.keys())
|
|
4807
|
+
if (key.startsWith(`${sessionID}\u0000`))
|
|
4808
|
+
inspectionOperations.delete(key);
|
|
4737
4809
|
activeSessions.delete(sessionID);
|
|
4738
4810
|
sessionOperationMetrics.delete(sessionID);
|
|
4739
4811
|
abandonSessionLease(sessionID);
|
|
@@ -4755,6 +4827,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4755
4827
|
clearSessionLinks(sessionID);
|
|
4756
4828
|
}
|
|
4757
4829
|
function evictSession(sessionID) {
|
|
4830
|
+
for (const key of inspectionOperations.keys())
|
|
4831
|
+
if (key.startsWith(`${sessionID}\u0000`))
|
|
4832
|
+
inspectionOperations.delete(key);
|
|
4758
4833
|
goalValidationDefects.delete(sessionID);
|
|
4759
4834
|
activeSessions.delete(sessionID);
|
|
4760
4835
|
sessionOperationMetrics.delete(sessionID);
|
|
@@ -4771,6 +4846,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4771
4846
|
assetVersionPins.delete(sessionID);
|
|
4772
4847
|
coordinatorPrompts.delete(sessionID);
|
|
4773
4848
|
explicitCoordinatorModels.delete(sessionID);
|
|
4849
|
+
interruptedCoordinatorMessages.delete(sessionID);
|
|
4774
4850
|
bindingDenials.delete(sessionID);
|
|
4775
4851
|
sessionTaskIDs.delete(sessionID);
|
|
4776
4852
|
recoverableWorkerChildren.delete(sessionID);
|
|
@@ -4780,17 +4856,17 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
4780
4856
|
parallelArtifactOperations.delete(sessionID);
|
|
4781
4857
|
clearSessionLinks(sessionID);
|
|
4782
4858
|
}
|
|
4783
|
-
async function inspectSuccessfulRead(
|
|
4784
|
-
if (
|
|
4859
|
+
async function inspectSuccessfulRead(toolInput) {
|
|
4860
|
+
if (toolInput.tool.toLowerCase() !== "read" || toolInput.sessionID === undefined)
|
|
4785
4861
|
return;
|
|
4786
|
-
if (activeSessionStatus(
|
|
4862
|
+
if (activeSessionStatus(toolInput.sessionID) !== "active" || !isRecord(toolInput.args))
|
|
4787
4863
|
return;
|
|
4788
|
-
const path =
|
|
4864
|
+
const path = toolInput.args.filePath;
|
|
4789
4865
|
if (typeof path !== "string" || path.length === 0)
|
|
4790
4866
|
return;
|
|
4791
|
-
const absolutePath = resolve(path);
|
|
4792
|
-
const key = `${
|
|
4793
|
-
const operation = inspect(
|
|
4867
|
+
const absolutePath = isAbsolute(path) ? resolve(path) : resolve(input.worktree ?? input.directory, path);
|
|
4868
|
+
const key = `${toolInput.sessionID}\u0000${absolutePath}`;
|
|
4869
|
+
const operation = inspectionOperations.get(key) ?? inspect(absolutePath, toolInput.sessionID).then(() => undefined);
|
|
4794
4870
|
inspectionOperations.set(key, operation);
|
|
4795
4871
|
try {
|
|
4796
4872
|
await operation;
|
|
@@ -5219,10 +5295,33 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5219
5295
|
description: "Report handoff and operation manifest contract defects before dispatch. Read-only: it never inspects, binds, or authorizes.",
|
|
5220
5296
|
args: {
|
|
5221
5297
|
handoff_path: defineTool.schema.string(),
|
|
5298
|
+
task_prompt: optionalString(),
|
|
5222
5299
|
},
|
|
5223
|
-
async execute(args) {
|
|
5300
|
+
async execute(args, context) {
|
|
5224
5301
|
try {
|
|
5225
5302
|
await inspect(args.handoff_path, undefined, { report: true });
|
|
5303
|
+
if (typeof args.task_prompt === "string") {
|
|
5304
|
+
const state = await currentGoal(context.sessionID);
|
|
5305
|
+
const effectivePrompt = await resolveGoalPrompt(args.task_prompt);
|
|
5306
|
+
const resolved = resolveGoalDeclaration(state, effectivePrompt);
|
|
5307
|
+
if (resolved.defects.length > 0)
|
|
5308
|
+
return JSON.stringify({ status: "defective",
|
|
5309
|
+
defects: resolved.defects.slice(0, CONTRACT_DEFECTS.limit),
|
|
5310
|
+
remedy: "Correct the Task declaration before dispatch. Budget-only changes may omit the already accepted goal fields." });
|
|
5311
|
+
const units = Number(handoffValue(handoffEntries(effectivePrompt), ["goal_budget_units"]));
|
|
5312
|
+
const proposed = Number.isSafeInteger(units) && units > 0 && units >= state.consumed_units
|
|
5313
|
+
? units : state.budget?.max_units ?? null;
|
|
5314
|
+
const approvalAvailable = goalDeclarationAuthority.get(context.sessionID) === state.latest_user_message_id &&
|
|
5315
|
+
state.latest_user_message_id !== null;
|
|
5316
|
+
return JSON.stringify({ status: "ok", defects: [], goal: {
|
|
5317
|
+
goal_id: state.goal_id, revision: state.revision, inherited: resolved.inherited,
|
|
5318
|
+
max_units: state.budget?.max_units ?? null, proposed_max_units: proposed,
|
|
5319
|
+
consumed_units: state.consumed_units, reserved_units: state.outstanding_reservations.length,
|
|
5320
|
+
remaining_units: state.budget === null ? null : Math.max(0, state.budget.max_units - state.consumed_units - state.outstanding_reservations.length),
|
|
5321
|
+
proposed_remaining_units: proposed === null ? null : Math.max(0, proposed - state.consumed_units - state.outstanding_reservations.length),
|
|
5322
|
+
budget_revision_requires_approval: proposed !== state.budget?.max_units && !approvalAvailable,
|
|
5323
|
+
} });
|
|
5324
|
+
}
|
|
5226
5325
|
return JSON.stringify({ status: "ok", defects: [] });
|
|
5227
5326
|
}
|
|
5228
5327
|
catch (error) {
|
|
@@ -5420,12 +5519,14 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5420
5519
|
"experimental.text.complete": async (textInput, textOutput) => {
|
|
5421
5520
|
if (pendingRealGoalTurns.has(textInput.sessionID))
|
|
5422
5521
|
await recoverPendingRealGoalTurn(textInput.sessionID);
|
|
5522
|
+
const coordinatorReport = isCoordinatorSession(textInput.sessionID) || await recoverCoordinatorRoot(textInput.sessionID);
|
|
5423
5523
|
if (fastLane.manualCompactionForbidden(textInput.sessionID)) {
|
|
5424
5524
|
textOutput.text = textOutput.text
|
|
5425
5525
|
.replaceAll(ROLLOVER_MARKER, "")
|
|
5426
5526
|
.replaceAll(CONTINUATION_MARKER, "")
|
|
5427
5527
|
.trimEnd();
|
|
5428
5528
|
}
|
|
5529
|
+
textOutput.text = await preserveActiveGoalContinuation(textInput.sessionID, textOutput.text, textInput.messageID);
|
|
5429
5530
|
const runOutcome = terminalRunOutcome(textOutput.text);
|
|
5430
5531
|
const terminal = runOutcome === undefined || !isCoordinatorSession(textInput.sessionID)
|
|
5431
5532
|
? undefined
|
|
@@ -5442,7 +5543,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5442
5543
|
if (runOutcome === "DONE" && terminal?.delivery === "failed") {
|
|
5443
5544
|
textOutput.text = replaceTerminalStatus(textOutput.text, "status: INTERRUPTED — durable delivery failed");
|
|
5444
5545
|
}
|
|
5445
|
-
if (runOutcome !== undefined
|
|
5546
|
+
if (coordinatorReport && (runOutcome !== undefined || /<summary\b[^>]*>\s*Evidence\b/iu.test(textOutput.text))) {
|
|
5446
5547
|
textOutput.text = sanitizeTerminalReport(textOutput.text);
|
|
5447
5548
|
}
|
|
5448
5549
|
if ((isCoordinatorSession(textInput.sessionID) || await recoverCoordinatorRoot(textInput.sessionID)) &&
|
|
@@ -5561,6 +5662,8 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5561
5662
|
}
|
|
5562
5663
|
const coordinatorOrigin = parentID === undefined && requestedCoordinator;
|
|
5563
5664
|
if (coordinatorOrigin) {
|
|
5665
|
+
if (!synthetic)
|
|
5666
|
+
interruptedCoordinatorMessages.delete(chatInput.sessionID);
|
|
5564
5667
|
if (synthetic) {
|
|
5565
5668
|
if (messageID === undefined)
|
|
5566
5669
|
throw new Error("SORTIE_GOAL_CONTROL_DENIED: receiving-message-id-required");
|
|
@@ -5987,6 +6090,18 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5987
6090
|
}
|
|
5988
6091
|
},
|
|
5989
6092
|
"tool.execute.before": async (toolInput, output) => {
|
|
6093
|
+
// Publish the inspection at Read admission, before a concurrently scheduled bind can
|
|
6094
|
+
// observe an empty cache. Bind joins this same host validation rather than asking the
|
|
6095
|
+
// coordinator to launch another worker round merely for tool scheduling.
|
|
6096
|
+
if (toolInput.tool.toLowerCase() === "read" && activeSessionStatus(toolInput.sessionID) === "active" &&
|
|
6097
|
+
isRecord(output.args) && typeof output.args.filePath === "string") {
|
|
6098
|
+
const path = output.args.filePath;
|
|
6099
|
+
const absolutePath = isAbsolute(path) ? resolve(path) : resolve(input.worktree ?? input.directory, path);
|
|
6100
|
+
const key = `${toolInput.sessionID}\u0000${absolutePath}`;
|
|
6101
|
+
const operation = inspect(absolutePath, toolInput.sessionID).then(() => undefined);
|
|
6102
|
+
inspectionOperations.set(key, operation);
|
|
6103
|
+
void operation.catch(() => undefined); // The after hook/bind consumes the actual outcome.
|
|
6104
|
+
}
|
|
5990
6105
|
if (childLifecycles.get(toolInput.sessionID)?.stopping && toolInput.tool !== "sortie_release_write_gate") {
|
|
5991
6106
|
throw new Error("child-cancellation-in-progress");
|
|
5992
6107
|
}
|
|
@@ -6161,12 +6276,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6161
6276
|
const resumeDeltaPresent = resumeDeltas.length === 1 && hasResumeContractShape(contractPrompt);
|
|
6162
6277
|
const contractRedefinitions = [
|
|
6163
6278
|
...taskValues(contractPrompt, [
|
|
6164
|
-
"
|
|
6279
|
+
"project_root", "projectroot", "source_manifest", "sourcemanifest",
|
|
6165
6280
|
"acceptance", "validation", "validation_history", "validation_attempts", "scout",
|
|
6166
6281
|
"known_facts", "known_paths", "relevant_constraints", "preserve",
|
|
6167
6282
|
"parallel_group", "parallel_unit", "parallel_units",
|
|
6168
6283
|
]),
|
|
6169
|
-
...
|
|
6284
|
+
...taskValues(contractPrompt, ["role"]).filter(role => !TASK_ROLES.has(role)),
|
|
6170
6285
|
];
|
|
6171
6286
|
if (modes.length !== 0 && !resume) {
|
|
6172
6287
|
throw new HandoffDeniedError("contract-invalid", "<worker-dispatch>", {
|
|
@@ -6516,6 +6631,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6516
6631
|
if (eventSessionID === undefined)
|
|
6517
6632
|
return;
|
|
6518
6633
|
if (event.type === "message.updated" && info !== undefined) {
|
|
6634
|
+
rememberCoordinatorInterruption(eventSessionID, info);
|
|
6519
6635
|
await acceptPersistedRealGoalEvent(eventSessionID, info);
|
|
6520
6636
|
}
|
|
6521
6637
|
if (pendingRealGoalTurns.has(eventSessionID) &&
|
|
@@ -6549,8 +6665,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6549
6665
|
while (completedCoordinatorMessages.size > ACTIVE_SESSION_CACHE.maximum) {
|
|
6550
6666
|
completedCoordinatorMessages.delete(completedCoordinatorMessages.values().next().value);
|
|
6551
6667
|
}
|
|
6552
|
-
await
|
|
6553
|
-
await
|
|
6668
|
+
const effectiveText = await preserveActiveGoalContinuation(eventSessionID, text, eventPart.messageID);
|
|
6669
|
+
await terminalGoalFromHostText(eventSessionID, effectiveText);
|
|
6670
|
+
await completeContinuationText(eventSessionID, effectiveText, false);
|
|
6554
6671
|
return;
|
|
6555
6672
|
}
|
|
6556
6673
|
completedCoordinatorParts.delete(eventPart.id);
|
|
@@ -6583,8 +6700,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6583
6700
|
if (text === undefined)
|
|
6584
6701
|
completedCoordinatorMessages.delete(info.id);
|
|
6585
6702
|
else {
|
|
6586
|
-
await
|
|
6587
|
-
await
|
|
6703
|
+
const effectiveText = await preserveActiveGoalContinuation(eventSessionID, text, info.id);
|
|
6704
|
+
await terminalGoalFromHostText(eventSessionID, effectiveText);
|
|
6705
|
+
await completeContinuationText(eventSessionID, effectiveText);
|
|
6588
6706
|
}
|
|
6589
6707
|
}
|
|
6590
6708
|
catch {
|
|
@@ -334,6 +334,8 @@ function duration(milliseconds) {
|
|
|
334
334
|
if (seconds < 60)
|
|
335
335
|
return `${seconds}s`;
|
|
336
336
|
const minutes = Math.floor(seconds / 60);
|
|
337
|
+
if (minutes >= 60)
|
|
338
|
+
return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, "0")}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
337
339
|
return `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
338
340
|
}
|
|
339
341
|
function metricText(metric, render) {
|
|
@@ -348,15 +350,16 @@ export function formatSortieResult(result) {
|
|
|
348
350
|
: result.mission.status === "INTERRUPTED" ? "中断(未完了)"
|
|
349
351
|
: result.mission.status === "EXTERNAL_BLOCKER" ? "外部要因で未完了"
|
|
350
352
|
: "ユーザー判断待ち(未完了)";
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
`**⚡
|
|
354
|
-
`**🪙 使用量:**
|
|
353
|
+
const color = result.mission.status === "COMPLETED" ? "🟢" : result.mission.status === "EXTERNAL_BLOCKER" ? "🔴" : "🟡";
|
|
354
|
+
const body = [
|
|
355
|
+
`**⚡ 任務経過(待機含む):** **${metricText(result.speed.goal_wall_ms, duration)}**`,
|
|
356
|
+
`**🪙 使用量:** **${metricText(result.cost.total_tokens, (value) => `${value.toLocaleString("ja-JP")} tokens`)}** · host推定額 ${metricText(result.cost.cost_usd, (value) => `$${value.toFixed(4)}`)}(実課金換算なし)`,
|
|
355
357
|
...renderDebrief(result.debrief),
|
|
356
|
-
`**🛡 達成:** ${achievement} · 達成条件
|
|
358
|
+
`**🛡 達成:** ${color} **${achievement}** · 達成条件 **${criteria}**`,
|
|
357
359
|
"*最終応答生成前の計測*",
|
|
358
360
|
...renderCareer(result.career),
|
|
359
361
|
].join("\n\n");
|
|
362
|
+
return `<details>\n<summary><strong>🐾 SORTIE DOGS — 帰還報告|${color} ${achievement}</strong></summary>\n\n${body}\n\n</details>`;
|
|
360
363
|
}
|
|
361
364
|
export function formatRunMetrics(metrics) {
|
|
362
365
|
const elapsed = metrics.durationMilliseconds === undefined ? "duration unavailable" : `${duration(metrics.durationMilliseconds)} wall-clock`;
|
|
@@ -370,7 +373,16 @@ export function formatRunMetrics(metrics) {
|
|
|
370
373
|
function topLevelLines(text) {
|
|
371
374
|
const lines = [];
|
|
372
375
|
let fence;
|
|
376
|
+
let disclosureDepth = 0;
|
|
373
377
|
for (const [index, line] of text.split(/\r?\n/u).entries()) {
|
|
378
|
+
if (fence === undefined) {
|
|
379
|
+
const opens = (line.match(/<details\b[^>]*>/giu) ?? []).length;
|
|
380
|
+
const closes = (line.match(/<\/details>/giu) ?? []).length;
|
|
381
|
+
const inside = disclosureDepth > 0 || opens > 0;
|
|
382
|
+
disclosureDepth = Math.max(0, disclosureDepth + opens - closes);
|
|
383
|
+
if (inside)
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
374
386
|
if (fence === undefined) {
|
|
375
387
|
const opener = /^[ \t]*(`{3,}|~{3,})/u.exec(line)?.[1];
|
|
376
388
|
if (opener === undefined) {
|
|
@@ -398,13 +410,11 @@ function terminalCheckpoint(text) {
|
|
|
398
410
|
const explicit = normalized === "DONE" || normalized === "INTERRUPTED" || normalized === "BLOCKED" || normalized === "NEED_DECISION"
|
|
399
411
|
? normalized
|
|
400
412
|
: undefined;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
/^⛔[ \t]+\*\*BLOCKED\*\*/u.test(line) ? "BLOCKED" :
|
|
405
|
-
/^❓[ \t]+\*\*NEED_DECISION\*\*/u.test(line) ? "NEED_DECISION" : undefined);
|
|
413
|
+
// The status carries meaning; a decorative icon must not suppress terminal rendering.
|
|
414
|
+
const decorated = /^(?:\p{Extended_Pictographic}\uFE0F?[ \t]+)?\*\*(DONE|INTERRUPTED|BLOCKED|NEED_DECISION)\*\*(?=[ \t]|$)/u.exec(line)?.[1];
|
|
415
|
+
const outcome = explicit ?? conclusionStatusAlias(line) ?? decorated;
|
|
406
416
|
return outcome === "DONE" || outcome === "INTERRUPTED" || outcome === "BLOCKED" || outcome === "NEED_DECISION"
|
|
407
|
-
? { index, outcome }
|
|
417
|
+
? { index, outcome: outcome }
|
|
408
418
|
: undefined;
|
|
409
419
|
})();
|
|
410
420
|
return checkpoint;
|
|
@@ -437,12 +447,20 @@ export function replaceDoneTerminalStatus(text, replacement) {
|
|
|
437
447
|
}
|
|
438
448
|
export function sanitizeTerminalReport(text) {
|
|
439
449
|
const newline = text.includes("\r\n") ? "\r\n" : "\n";
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
const preserved = [];
|
|
451
|
+
const keep = (block) => `\0SORTIE_PROSE_${preserved.push(block) - 1}\0`;
|
|
452
|
+
return text.replace(/<details\b[^>]*>[\s\S]*?<\/details>/giu, block => {
|
|
453
|
+
const summary = /<summary\b[^>]*>([\s\S]*?)<\/summary>/iu.exec(block)?.[1]?.replace(/<[^>]*>/gu, "").trim() ?? "";
|
|
454
|
+
return /^(?:Evidence\b|🐾\s*SORTIE DOGS\s*[—-]\s*帰還報告)/iu.test(summary) ? "" : keep(block);
|
|
455
|
+
})
|
|
456
|
+
.replace(/^[ \t]*(`{3,}|~{3,})[^\r\n]*\r?\n[\s\S]*?^[ \t]*\1[ \t]*$/gimu, (block, _fence, offset, source) => {
|
|
457
|
+
const preceding = source.slice(0, offset).trimEnd().split(/\r?\n\s*\r?\n/u).at(-1) ?? "";
|
|
458
|
+
return /^[ \t]*(?:#{1,6}[ \t]*)?\**Evidence\**[ \t]*:/imu.test(preceding) ? "" : keep(block);
|
|
459
|
+
})
|
|
443
460
|
.split(/\r?\n/u)
|
|
444
461
|
.filter((line) => !/^\s*(?:(?:#{1,6}\s*)?\**Evidence\**\s*:|(?:TRUE_BLOCKER|goal_control|evidence_refs?|reason_code|raw|raw_status|manifest)\s*:)/iu.test(line))
|
|
445
462
|
.join(newline)
|
|
463
|
+
.replace(/\0SORTIE_PROSE_(\d+)\0/gu, (_token, index) => preserved[Number(index)])
|
|
446
464
|
.trimEnd();
|
|
447
465
|
}
|
|
448
466
|
export function insertRunMetrics(text, metrics) {
|
|
@@ -465,8 +483,24 @@ export function insertSortieResult(text, result) {
|
|
|
465
483
|
// A title in model text is not trusted evidence. Replace existing cards, including legacy cards.
|
|
466
484
|
const newline = visible.includes("\r\n") ? "\r\n" : "\n";
|
|
467
485
|
const lines = visible.split(/\r?\n/u);
|
|
468
|
-
const cardLines = new Set(
|
|
469
|
-
|
|
486
|
+
const cardLines = new Set();
|
|
487
|
+
let legacyCard = false;
|
|
488
|
+
let previousIndex = checkpoint.index;
|
|
489
|
+
for (const { index, line } of topLevelLines(visible)) {
|
|
490
|
+
if (index <= checkpoint.index)
|
|
491
|
+
continue;
|
|
492
|
+
if (index > previousIndex + 1)
|
|
493
|
+
legacyCard = false;
|
|
494
|
+
previousIndex = index;
|
|
495
|
+
if (/^\*\*(?:Sortie Result|🐾 SORTIE DOGS — 帰還報告|📜 PACK RECORD)/u.test(line))
|
|
496
|
+
legacyCard = true;
|
|
497
|
+
if (legacyCard && (line.trim() === "" ||
|
|
498
|
+
/^(?:\*\*(?:Sortie Result|🐾 SORTIE DOGS — 帰還報告|📜 PACK RECORD|↳\*\*|(?:Speed|Cost|達成|⚡ 時間|🪙 使用量|🐕 出撃隊|モデル別token内訳|実行重複率|🛡 達成|確認|🏅 今回の戦績|戦績|初回完遂|累積使用量|累積モデル|累積時間|累積実行重複率|保存範囲|🎖 隊の称号):)|\*最終応答生成前の計測\*)/u.test(line))) {
|
|
499
|
+
cardLines.add(index);
|
|
500
|
+
}
|
|
501
|
+
else
|
|
502
|
+
legacyCard = false;
|
|
503
|
+
}
|
|
470
504
|
const cleaned = lines.filter((_, index) => !cardLines.has(index));
|
|
471
505
|
while (cleaned[checkpoint.index + 1] === "")
|
|
472
506
|
cleaned.splice(checkpoint.index + 1, 1);
|
|
@@ -475,7 +509,7 @@ export function insertSortieResult(text, result) {
|
|
|
475
509
|
card = formatSortieResult(result);
|
|
476
510
|
}
|
|
477
511
|
catch {
|
|
478
|
-
card = "
|
|
512
|
+
card = "<details>\n<summary><strong>🐾 SORTIE DOGS — 帰還報告</strong></summary>\n\n**確認:** 表示集計を取得できません。任務結果は先頭の状態を参照。\n\n</details>";
|
|
479
513
|
}
|
|
480
514
|
cleaned.splice(checkpoint.index + 1, 0, "", card, "");
|
|
481
515
|
return cleaned.join(newline).trimEnd();
|
|
@@ -17,11 +17,13 @@ export interface DebriefSession {
|
|
|
17
17
|
readonly reviews: Array<{
|
|
18
18
|
at: number;
|
|
19
19
|
status: "PASS" | "FAIL" | "WAIVED";
|
|
20
|
+
source?: "reviewer" | "controller";
|
|
20
21
|
}>;
|
|
21
22
|
readonly tasks: string[];
|
|
22
23
|
complete: boolean;
|
|
23
24
|
timingComplete: boolean;
|
|
24
25
|
failed: boolean;
|
|
26
|
+
lastPossibleMutation?: number;
|
|
25
27
|
}
|
|
26
28
|
export interface DebriefObservation {
|
|
27
29
|
readonly complete: boolean;
|
|
@@ -40,6 +42,8 @@ export interface Debrief {
|
|
|
40
42
|
}[] | null;
|
|
41
43
|
readonly validation: "PASS" | "FAIL" | "未確認";
|
|
42
44
|
readonly review: "PASS" | "FAIL" | "WAIVED" | "未確認";
|
|
45
|
+
readonly reviewSource?: "reviewer" | "controller";
|
|
46
|
+
readonly notes?: readonly string[];
|
|
43
47
|
readonly traits: readonly ("連携作戦" | "修正から復帰" | "一発完遂")[];
|
|
44
48
|
readonly firstPassEligible?: boolean;
|
|
45
49
|
readonly overlap?: {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { DEDICATED_WORKER_ROLES, LUNA_FABRIC_WORKER_ROLE } from "./model-routing.js";
|
|
3
|
+
import { normalizeCommand } from "./gate.js";
|
|
3
4
|
const object = (value) => value !== null && typeof value === "object" ? value : undefined;
|
|
4
5
|
const timeValue = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
5
|
-
const fingerprint = (value) => createHash("sha256").update(value).digest("hex");
|
|
6
|
+
const fingerprint = (value) => createHash("sha256").update(normalizeCommand(value)).digest("hex");
|
|
6
7
|
const workerRoles = new Set([...DEDICATED_WORKER_ROLES, LUNA_FABRIC_WORKER_ROLE]);
|
|
7
8
|
function unionDuration(spans) {
|
|
8
9
|
let end = -1, total = 0;
|
|
@@ -34,14 +35,11 @@ export function observeDebriefSession(id, root, messages, window) {
|
|
|
34
35
|
session.timingComplete = false;
|
|
35
36
|
continue;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
session.timingComplete = false;
|
|
41
|
-
}
|
|
38
|
+
// Match the token collector's completed-message window. A message can start just before
|
|
39
|
+
// goal.accepted; clip its timing rather than invalidating every independent metric.
|
|
40
|
+
if (window !== undefined && (span.end < window.start || span.end > window.end))
|
|
42
41
|
continue;
|
|
43
|
-
}
|
|
44
|
-
session.spans.push(span);
|
|
42
|
+
session.spans.push(window === undefined ? span : { start: Math.max(span.start, window.start), end: span.end });
|
|
45
43
|
if (info.error !== undefined)
|
|
46
44
|
session.failed = true;
|
|
47
45
|
for (const raw of Array.isArray(message.parts) ? message.parts : []) {
|
|
@@ -89,8 +87,20 @@ export function observeDebriefSession(id, root, messages, window) {
|
|
|
89
87
|
else
|
|
90
88
|
session.complete = false;
|
|
91
89
|
}
|
|
90
|
+
if (args?.subagent_type === "dog-reviewer" && typeof state.output === "string") {
|
|
91
|
+
const body = /<task_result>([\s\S]*?)<\/task_result>/u.exec(state.output)?.[1] ?? state.output;
|
|
92
|
+
const lines = body.replace(/^[ \t]*(`{3,}|~{3,})[^\r\n]*\r?\n[\s\S]*?^[ \t]*\1[ \t]*$/gmu, "")
|
|
93
|
+
.trim().split(/\r?\n/u);
|
|
94
|
+
const verdicts = [lines[0], lines.at(-1)].map(line => line?.trim().match(/^(?:\*\*)?(?:(?:verdict|結論|判定)\s*:\s*)?(PASS|FAIL|MUST_FIX)(?:\*\*)?$/iu)?.[1]?.toUpperCase());
|
|
95
|
+
const verdict = verdicts.some(value => value === "FAIL" || value === "MUST_FIX") || /^FINDING\b/iu.test(lines[0] ?? "")
|
|
96
|
+
? "FAIL" : verdicts.includes("PASS") ? "PASS" : undefined;
|
|
97
|
+
if (verdict !== undefined)
|
|
98
|
+
session.reviews.push({ at: interval.end,
|
|
99
|
+
status: verdict === "PASS" ? "PASS" : "FAIL", source: "reviewer" });
|
|
100
|
+
}
|
|
92
101
|
}
|
|
93
102
|
if (["edit", "write", "apply_patch"].includes(tool)) {
|
|
103
|
+
session.lastPossibleMutation = Math.max(session.lastPossibleMutation ?? 0, interval.end);
|
|
94
104
|
const metadata = object(state.metadata);
|
|
95
105
|
const changed = typeof metadata?.diff === "string" && /^[+-](?![+-])/mu.test(metadata.diff) ||
|
|
96
106
|
Array.isArray(metadata?.files) && metadata.files.some((file) => {
|
|
@@ -138,15 +148,14 @@ export function observeDebriefSession(id, root, messages, window) {
|
|
|
138
148
|
return session;
|
|
139
149
|
}
|
|
140
150
|
export function buildDebrief(receipt, contract, observation, records) {
|
|
141
|
-
|
|
142
|
-
if (observation === undefined)
|
|
143
|
-
return empty;
|
|
151
|
+
observation ??= { complete: false, sessions: [], window: undefined };
|
|
144
152
|
const sessions = observation.sessions;
|
|
145
153
|
const complete = observation.complete && sessions.every((session) => session.complete);
|
|
146
154
|
const timingComplete = observation.complete && sessions.every((session) => session.timingComplete);
|
|
147
|
-
const children = sessions.filter((session) => !session.root &&
|
|
155
|
+
const children = sessions.filter((session) => !session.root &&
|
|
156
|
+
(session.spans.length > 0 || Object.keys(session.models).length > 0));
|
|
148
157
|
const counts = new Map(), totals = new Map();
|
|
149
|
-
let usageComplete =
|
|
158
|
+
let usageComplete = observation.complete;
|
|
150
159
|
for (const session of sessions) {
|
|
151
160
|
for (const [model, tokens] of Object.entries(session.models)) {
|
|
152
161
|
if (tokens === null || model === "未分類")
|
|
@@ -163,11 +172,25 @@ export function buildDebrief(receipt, contract, observation, records) {
|
|
|
163
172
|
const total = [...totals.values()].reduce((sum, tokens) => sum + tokens, 0);
|
|
164
173
|
const commands = new Set(contract?.criteria.flatMap((criterion) => criterion.validation_command === undefined ? [] : [fingerprint(criterion.validation_command)]));
|
|
165
174
|
const checks = sessions.flatMap((session) => session.checks.filter((check) => commands.has(check.command))).sort((a, b) => a.end - b.end);
|
|
175
|
+
// Retain canonical proof even when an unrelated host tool lacks timing/metadata.
|
|
176
|
+
for (const { event } of records ?? []) {
|
|
177
|
+
if (event.kind !== "unit.settled" || event.goal_id !== receipt.goal_id)
|
|
178
|
+
continue;
|
|
179
|
+
for (const evidence of event.evidence ?? []) {
|
|
180
|
+
const command = fingerprint(evidence.execution.command.join(" "));
|
|
181
|
+
const start = Date.parse(evidence.execution.started_at), end = Date.parse(evidence.execution.ended_at);
|
|
182
|
+
if (receipt.evidence_refs.includes(evidence.evidence_id) && commands.has(command) &&
|
|
183
|
+
Number.isFinite(start) && Number.isFinite(end))
|
|
184
|
+
checks.push({ start, end, command,
|
|
185
|
+
passed: evidence.execution.exit_code === 0 && evidence.execution.outcome === "pass" });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
checks.sort((a, b) => a.end - b.end);
|
|
166
189
|
const latest = new Map();
|
|
167
190
|
for (const check of checks)
|
|
168
191
|
latest.set(check.command, check);
|
|
169
192
|
const validation = [...latest.values()].some((check) => !check.passed) ? "FAIL"
|
|
170
|
-
:
|
|
193
|
+
: commands.size > 0 && latest.size === commands.size ? "PASS" : "未確認";
|
|
171
194
|
const reviews = sessions.flatMap((session) => session.reviews).sort((a, b) => a.at - b.at);
|
|
172
195
|
const traits = [];
|
|
173
196
|
const spans = children.flatMap((session) => session.spans.filter((span) => span.end > span.start)
|
|
@@ -218,9 +241,15 @@ export function buildDebrief(receipt, contract, observation, records) {
|
|
|
218
241
|
(event.kind === "validation.admission" && event.decision === "DENY") || (event.kind === "validation.settled" && event.outcome !== "passed")) &&
|
|
219
242
|
sessions.every((session) => !session.failed && new Set(session.checks.map((check) => check.command)).size === session.checks.length))
|
|
220
243
|
traits.push("一発完遂");
|
|
221
|
-
|
|
244
|
+
const review = reviews.filter((entry) => !sessions.some((session) => (session.lastPossibleMutation ?? 0) > entry.at ||
|
|
245
|
+
session.mutations.some((edit) => edit.end > entry.at))).at(-1);
|
|
246
|
+
const packComplete = observation.complete && sessions.every(session => session.root || session.timingComplete || Object.keys(session.models).length > 0);
|
|
247
|
+
return { pack: packComplete ? [...counts].sort(([a], [b]) => a.localeCompare(b)).map(([model, count]) => ({ model, count })) : null,
|
|
222
248
|
mix: usageComplete && total > 0 ? [...totals].sort(([a], [b]) => a.localeCompare(b)).map(([model, tokens]) => ({ model, tokens, percent: tokens / total * 100 })) : null,
|
|
223
|
-
validation, review:
|
|
249
|
+
validation, review: review?.status ?? "未確認", reviewSource: review?.source ?? "controller", traits, firstPassEligible,
|
|
250
|
+
notes: [...(!observation.complete ? ["host履歴の取得が一部不足"] : []),
|
|
251
|
+
...(!usageComplete ? ["モデルIDまたはusageの記録が不足"] : []),
|
|
252
|
+
...(!timingComplete ? ["稼働区間の時刻が不足(token集計とは独立)"] : [])],
|
|
224
253
|
...(timingComplete ? { overlap: { workerMilliseconds: children.reduce((sum, child) => sum + unionDuration(child.spans), 0),
|
|
225
254
|
wallMilliseconds: unionDuration(spans) } } : {}) };
|
|
226
255
|
}
|
|
@@ -239,13 +268,17 @@ export function renderDebrief(debrief) {
|
|
|
239
268
|
const filled = Math.max(0, Math.min(10, Math.round(percent / 10)));
|
|
240
269
|
return "█".repeat(filled) + "░".repeat(10 - filled);
|
|
241
270
|
};
|
|
271
|
+
const status = (value) => value === "PASS" ? "🟢 **PASS**" : value === "FAIL" ? "🔴 **FAIL**"
|
|
272
|
+
: value === "WAIVED" ? "免除" : "未記録";
|
|
242
273
|
return [
|
|
243
|
-
`**🐕 出撃隊:** ${pack === null ? "
|
|
244
|
-
`**モデル別token内訳:** ${mix === null ? "
|
|
274
|
+
`**🐕 出撃隊:** ${pack === null ? "履歴未取得" : pack.length === 0 ? "出撃なし" : packVisible.map((entry) => `${label(entry.model)} **×${entry.count}**`).join(" · ")}`,
|
|
275
|
+
`**モデル別token内訳:** ${mix === null ? "usage未取得" : ""}`,
|
|
245
276
|
...visible.map((entry) => `**↳** ${label(entry.model)} \`${bars(entry.percent)}\` ${entry.percent.toFixed(1)}%`),
|
|
246
277
|
`**実行重複率:** ${debrief?.overlap !== undefined && debrief.overlap.wallMilliseconds > 0
|
|
247
|
-
?
|
|
248
|
-
|
|
278
|
+
? `**${(debrief.overlap.workerMilliseconds / debrief.overlap.wallMilliseconds).toFixed(2)}×**(worker区間・速度倍率ではありません)`
|
|
279
|
+
: pack?.length === 0 ? "対象なし(出撃なし)" : "稼働区間の記録不足"}`,
|
|
280
|
+
`**確認:** 対象検証 ${status(debrief?.validation ?? "未確認")} · 直近Review ${status(debrief?.review ?? "未確認")}${debrief?.reviewSource === "reviewer" ? "(reviewer報告)" : ""}`,
|
|
281
|
+
...(debrief?.notes?.length ? [`**計測範囲:** ${debrief.notes.join(" · ")}`] : []),
|
|
249
282
|
...(debrief?.traits.length ? [`**🏅 今回の戦績:** ${debrief.traits.join(" · ")}`] : []),
|
|
250
283
|
];
|
|
251
284
|
}
|
package/dist/runtime-assets.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface RuntimeAsset {
|
|
|
7
7
|
}
|
|
8
8
|
export declare const runtimeAssets: readonly [{
|
|
9
9
|
readonly name: "dog-coordinator";
|
|
10
|
-
readonly version: "0.3.
|
|
10
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
11
11
|
readonly installPath: "agent/dog-coordinator.md";
|
|
12
12
|
readonly content: `---
|
|
13
13
|
description: Canonical MkII coordinator packaged by Sortie-dogs
|
|
@@ -575,7 +575,8 @@ END_ONE_WORKER_EXECUTION_CLOSURE_FIXTURE
|
|
|
575
575
|
|
|
576
576
|
Use a same-task resume only when the runtime denial explicitly returns resume_session=true for that
|
|
577
577
|
exact child. A completed Task without that signal requires a fresh worker and full handoff. For an
|
|
578
|
-
authorized same-task resume, retain the prior effective digest.
|
|
578
|
+
authorized same-task resume, retain the prior effective digest. Optional role: blocker-resolution
|
|
579
|
+
names the recovery action without replacing scope or acceptance. Send the same task_id and a
|
|
579
580
|
resume_delta containing stale_paths, new_findings, the previous command exit/fingerprint, and
|
|
580
581
|
next_action. Do not resend unchanged acceptance, role, validation, facts, constraints, manifests,
|
|
581
582
|
or file content; the preserved values plus this delta form the effective digest.
|
|
@@ -854,9 +855,19 @@ acceptance-continuity parent_fingerprint. SORTIE_ACCEPTANCE_CONTINUITY_STATE is
|
|
|
854
855
|
projection of the latest gate-accepted sequential unit; its next_sequential_parent_fingerprint is the
|
|
855
856
|
only projected value for the next unit's parent when present.
|
|
856
857
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
858
|
+
A goal may be declared once in JSON and referenced by goal_declaration_path in Task, or embedded as
|
|
859
|
+
ext["sortie-dogs/goal-declaration"] in its registered handoff. Shared defaults apply to each item in
|
|
860
|
+
criteria, with explicit criterion overrides. The host supplies stable IDs and a fingerprint if omitted.
|
|
861
|
+
Do not expand this definition into worker instructions; the host resolves it privately. Acceptance,
|
|
862
|
+
oracle coverage, delivery choice, and validation remain explicit. Legacy flat input remains supported:
|
|
863
|
+
declare goal_acceptance_fingerprint, delivery_intent, delivery_mode when explicitly selected,
|
|
864
|
+
usable_path_established, controlled_change, and goal_budget_units when first accepting a goal or changing
|
|
865
|
+
its acceptance contract. A same-goal continuation with only an approved budget change may send just
|
|
866
|
+
goal_budget_units (or the changed time/cost budget) beside the ordinary Task handoff: the host inherits
|
|
867
|
+
the accepted goal declaration. Do not reconstruct or repeat that declaration merely because the user
|
|
868
|
+
resumed the session. Optional task_prompt on sortie_check_contract previews declaration inheritance
|
|
869
|
+
and current/proposed capacity before dispatch without consuming approval or reserving a unit.
|
|
870
|
+
For each new or revised terminal criterion also declare goal_criterion_id, goal_target,
|
|
860
871
|
goal_entrypoint, goal_workload, goal_oracle_coverage, goal_build_boundary, goal_fixture, goal_proof_scope,
|
|
861
872
|
goal_expected_outcome, and the exact goal_validation_command from the operation manifest. Use
|
|
862
873
|
goal_source_binding: current-protected and goal_candidate_binding: current-protected when implementation
|
|
@@ -1399,23 +1410,33 @@ COMMIT_SCOPE_FIXTURE
|
|
|
1399
1410
|
mismatch: commit rejected
|
|
1400
1411
|
END_COMMIT_SCOPE_FIXTURE
|
|
1401
1412
|
|
|
1413
|
+
When the user asks what you are doing, why, or requests an explanation, answer the actual question
|
|
1414
|
+
with sufficient context, completed work, remaining work, and rationale. The terminal format below
|
|
1415
|
+
applies only to a task's terminal report, not to ordinary questions. Do not replace a substantive
|
|
1416
|
+
explanation with a status/Validation/Next template. Preserve explanatory paragraphs, code examples,
|
|
1417
|
+
and explanatory disclosure sections; a status annotation must not erase the user's answer.
|
|
1418
|
+
|
|
1402
1419
|
At each checkpoint and terminal return, preserve concise proof internally. The user-facing terminal
|
|
1403
1420
|
return MUST begin with its conclusion: no plan, progress, assessment, Evidence heading, or preamble.
|
|
1404
1421
|
Use exactly one of DONE, INTERRUPTED, BLOCKED, or NEED_DECISION with one status emoji and a short
|
|
1405
1422
|
Japanese conclusion. Then render Japanese \u5909\u66F4\u70B9, \u78BA\u8A8D\u7D50\u679C, and \u6B21 paragraphs without bullets or decorative
|
|
1406
|
-
emoji. The plugin injects measured Speed, Cost, and \u9054\u6210 paragraphs in a Japanese mission debrief card,
|
|
1423
|
+
emoji. The plugin injects measured Speed, Cost, and \u9054\u6210 paragraphs in a collapsed Japanese mission debrief card,
|
|
1407
1424
|
with one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
|
|
1408
1425
|
Its Markdown token bars and PACK RECORD summarize retained project goals, with coverage and team titles;
|
|
1409
1426
|
they never imply lifetime history, XP, levels, unmeasured savings, or a leaderboard rank.
|
|
1410
1427
|
Never write the card, its metrics, or its badges yourself. Do not estimate or fabricate them.
|
|
1411
1428
|
Use \u4EFB\u52D9\u5B8C\u4E86 for DONE, \u4E2D\u65AD\u5E30\u9084\uFF08\u672A\u5B8C\u4E86\uFF09 for INTERRUPTED, \u5916\u90E8\u8981\u56E0\u3067\u5F85\u6A5F\uFF08\u672A\u5B8C\u4E86\uFF09 for BLOCKED,
|
|
1412
1429
|
and \u6307\u793A\u5F85\u3061\uFF08\u672A\u5B8C\u4E86\uFF09 for NEED_DECISION; preserve the machine status token and first-line checkpoint.
|
|
1413
|
-
Never render a user-facing Evidence heading
|
|
1430
|
+
Never render a user-facing Evidence heading or Evidence details block, evidence reference, internal reason code,
|
|
1414
1431
|
ledger key, or raw status. Keep ordered command/exit/fingerprint history, manifests, evidence refs,
|
|
1415
1432
|
review proof, and terminal receipt append-only in their internal typed ledger and host logs. A concise
|
|
1416
1433
|
\u78BA\u8A8D\u7D50\u679C may summarize PASS/FAIL without exposing those internal identifiers.
|
|
1417
1434
|
An undeclared write or mutation must be reported as rejected, not performed. A locally repairable process or evidence defect is never a
|
|
1418
1435
|
user question: repair it and continue in the same turn.
|
|
1436
|
+
An INTERRUPTED root conclusion requires one machine line: \`TRUE_INTERRUPTION: user: <condition>\`
|
|
1437
|
+
for an explicit user stop, or \`TRUE_INTERRUPTION: internal: <condition>\` for a true internal
|
|
1438
|
+
interruption. Never emit that line for a local process defect, step boundary, continuation request, or
|
|
1439
|
+
recoverable limit; keep those on the same-session autonomous continuation path.
|
|
1419
1440
|
|
|
1420
1441
|
TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
1421
1442
|
DONE: all accepted criteria proved complete; unmet or interrupted work forbidden
|
|
@@ -1426,13 +1447,15 @@ TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
|
1426
1447
|
status_icons: DONE=\u2705 | INTERRUPTED=\u26A0\uFE0F | BLOCKED=\u26D4 | NEED_DECISION=\u2753
|
|
1427
1448
|
quality_gate_fail: validation evidence + autonomous non-adoption decision -> DONE; release remains unperformed
|
|
1428
1449
|
process_defect: gate | routing | handoff | local tool defect -> autonomous repair; never terminal BLOCKED
|
|
1450
|
+
interruption_marker: TRUE_INTERRUPTION: user: <condition> | TRUE_INTERRUPTION: internal: <condition>
|
|
1451
|
+
continuation_not_interruption: local process defect | step boundary | continuation request | recoverable limit
|
|
1429
1452
|
END_TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
1430
1453
|
|
|
1431
1454
|
RUNTIME_ASSET_VERSION_SYNC_FIXTURE
|
|
1432
|
-
runtime_version: 0.3.
|
|
1455
|
+
runtime_version: 0.3.84-dispatch-recovery-v1
|
|
1433
1456
|
shared_marker: src/asset-version.ts
|
|
1434
|
-
packaged_expectation: test/plugin-loader.test.ts uses 0.3.
|
|
1435
|
-
initialize_expectation: test/initialize.test.ts uses 0.3.
|
|
1457
|
+
packaged_expectation: test/plugin-loader.test.ts uses 0.3.84-dispatch-recovery-v1
|
|
1458
|
+
initialize_expectation: test/initialize.test.ts uses 0.3.84-dispatch-recovery-v1
|
|
1436
1459
|
rule: runtime asset versions, shared marker, packaged expectation, and initialize expectation change together
|
|
1437
1460
|
END_RUNTIME_ASSET_VERSION_SYNC_FIXTURE
|
|
1438
1461
|
|
|
@@ -1455,32 +1478,32 @@ END_INTERNAL_TERMINAL_PROOF_FIXTURE
|
|
|
1455
1478
|
`;
|
|
1456
1479
|
}, {
|
|
1457
1480
|
readonly name: "dog-worker";
|
|
1458
|
-
readonly version: "0.3.
|
|
1481
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1459
1482
|
readonly installPath: "agent/dog-worker.md";
|
|
1460
1483
|
readonly content: string;
|
|
1461
1484
|
}, {
|
|
1462
1485
|
readonly name: "dog-luna-worker";
|
|
1463
|
-
readonly version: "0.3.
|
|
1486
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1464
1487
|
readonly installPath: "agent/dog-luna-worker.md";
|
|
1465
1488
|
readonly content: string;
|
|
1466
1489
|
}, {
|
|
1467
1490
|
readonly name: "dog-scout";
|
|
1468
|
-
readonly version: "0.3.
|
|
1491
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1469
1492
|
readonly installPath: "agent/dog-scout.md";
|
|
1470
1493
|
readonly content: "---\ndescription: Bounded evidence scout for dog-coordinator\nmode: subagent\nsteps: 8\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n---\n# dog-scout\n\nAccept one concrete missing_evidence_code: manifest, validation, or owner-risk. Accept only an\nexplicit absolute project_root and a known_paths list of at most four paths from dog-coordinator.\nResolve only that evidence key from those paths under project_root; never resolve a path against the\nsession directory. Use Read only, with at most 120 lines and no more than one read per supplied path.\nDo not resolve a second key, explore, invoke another tool, retry, edit, stage, commit, or become user-facing.\n\nWhen project_root is missing, or a supplied path does not resolve under it, or a resolved path is\nunreadable, report that dispatch defect as the facts for the requested key and name the exact paths.\nDo not retry, guess another root, or answer from an unread path.\n\nReturn exactly one concise JSON object of at most 800 characters with exactly these keys:\nmissing_evidence_code, facts, evidence_paths, risks. Use no Markdown, code fence, commentary, or raw log. Return it only\nto dog-coordinator. Write the facts and risks prose in the language the dispatch uses for its own\nprose; keep the keys, paths, commands, and identifiers verbatim.\n";
|
|
1471
1494
|
}, {
|
|
1472
1495
|
readonly name: "dog-reviewer";
|
|
1473
|
-
readonly version: "0.3.
|
|
1496
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1474
1497
|
readonly installPath: "agent/dog-reviewer.md";
|
|
1475
1498
|
readonly content: "---\ndescription: Independent source reviewer for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-reviewer\n\nAccept only one bounded SourceReview request from dog-coordinator after canonical\nvalidation for one high-risk candidate. Review only the supplied acceptance criteria, exact\nmanifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly\nmaps to at least one changedLogicSummary entry and assess that changed logic against the mapped\nacceptance item. Missing or incomplete coverage is a concrete finding, never PASS.\nRequire one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and\nreject a missing index or unequal mapping count before assessing the changed logic.\nDo not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch\nanother agent.\nTreat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.\nDo not infer that a branch or exemption is absent from source because a prose summary omits it.\nIf the supplied excerpts do not establish a claim, report an evidence gap and request the exact\nbranch/helper excerpt in the next artifact; do not prescribe a source fix for an unproven defect.\n\nReturn one concise PASS or concrete-finding response only to dog-coordinator before the\ncoordinator commit. Write every finding, evidence, and required-fix sentence in the language the\nsupplied artifact uses for its own prose, one statement per line, and keep verdict values,\nidentifiers, paths, and commands verbatim. Do not implement, remediate, resolve blockers, edit,\nstage, commit, or become user-facing. Remain host-routed: do not require or identify a provider, vendor, model, variant,\nor transport.\n";
|
|
1476
1499
|
}, {
|
|
1477
1500
|
readonly name: "dog-advisor";
|
|
1478
|
-
readonly version: "0.3.
|
|
1501
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1479
1502
|
readonly installPath: "agent/dog-advisor.md";
|
|
1480
1503
|
readonly content: "---\ndescription: Focused technical advisor for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-advisor\n\nAccept only one bounded Strategy request from dog-coordinator for one candidate and one focused\nquestion. Use only the supplied acceptance criteria, exact manifest, constraints, and concise\nevidence. Do not request raw logs or full source files, expand scope, or dispatch another agent.\nTreat those supplied fields as the complete bounded Strategy artifact; use only that artifact and invoke no tools.\nReject every SourceReview request and return the rejection only to dog-coordinator; SourceReview is\ndog-reviewer-only work.\n\nReturn concise options and one recommendation only to dog-coordinator. Write every option,\nrecommendation, and consideration in the language the supplied request uses for its own prose, one\nstatement per line, and keep identifiers, paths, and commands verbatim. Do not perform\nSourceReview, implement, remediate, resolve blockers, edit, stage, commit, or become user-facing.\nImplementation remains dog-worker work. Remain host-routed: do not require or identify a\nprovider, vendor, model, variant, or transport.\n";
|
|
1481
1504
|
}, {
|
|
1482
1505
|
readonly name: "sortie";
|
|
1483
|
-
readonly version: "0.3.
|
|
1506
|
+
readonly version: "0.3.84-dispatch-recovery-v1";
|
|
1484
1507
|
readonly installPath: "command/sortie.md";
|
|
1485
1508
|
readonly content: "---\ndescription: Start the canonical Sortie-dogs MkII workflow\nagent: dog-coordinator\n---\nRequest: $ARGUMENTS\n\n1. If $ARGUMENTS is empty, request task context and stop; give project init guidance first.\n2. Do not preflight installed runtime assets. The plugin reports version skew without adding model\n turns; proceed from task evidence and project instructions.\n3. On restart or re-entry, reconstruct context from project-local durable artifacts and the\n latest bounded handoff or checkpoint. Preserve both manifests and ordered validation history;\n resume the same task through dog-coordinator with only the required delta.\n4. Otherwise transfer request and project context to dog-coordinator. Frontmatter is the single coordinator\n transfer; never route a worker to the user.\n";
|
|
1486
1509
|
}];
|
package/dist/runtime-assets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GOAL_DECLARATION_FORMAT } from "./core/goal-declaration-format.js";
|
|
2
|
-
const ASSET_VERSION = "0.3.
|
|
2
|
+
const ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
|
|
3
3
|
// Kept local so source-mode CLI execution does not load the plugin graph.
|
|
4
4
|
const BACKLOG_DRAIN_CAPABILITY = "sortie_enable_backlog_drain";
|
|
5
5
|
const PARALLEL_PREPARE_CAPABILITY = "sortie_prepare_parallel_dispatch";
|
|
@@ -782,7 +782,8 @@ END_ONE_WORKER_EXECUTION_CLOSURE_FIXTURE
|
|
|
782
782
|
|
|
783
783
|
Use a same-task resume only when the runtime denial explicitly returns resume_session=true for that
|
|
784
784
|
exact child. A completed Task without that signal requires a fresh worker and full handoff. For an
|
|
785
|
-
authorized same-task resume, retain the prior effective digest.
|
|
785
|
+
authorized same-task resume, retain the prior effective digest. Optional role: blocker-resolution
|
|
786
|
+
names the recovery action without replacing scope or acceptance. Send the same task_id and a
|
|
786
787
|
resume_delta containing stale_paths, new_findings, the previous command exit/fingerprint, and
|
|
787
788
|
next_action. Do not resend unchanged acceptance, role, validation, facts, constraints, manifests,
|
|
788
789
|
or file content; the preserved values plus this delta form the effective digest.
|
|
@@ -1061,9 +1062,19 @@ acceptance-continuity parent_fingerprint. SORTIE_ACCEPTANCE_CONTINUITY_STATE is
|
|
|
1061
1062
|
projection of the latest gate-accepted sequential unit; its next_sequential_parent_fingerprint is the
|
|
1062
1063
|
only projected value for the next unit's parent when present.
|
|
1063
1064
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1065
|
+
A goal may be declared once in JSON and referenced by goal_declaration_path in Task, or embedded as
|
|
1066
|
+
ext["sortie-dogs/goal-declaration"] in its registered handoff. Shared defaults apply to each item in
|
|
1067
|
+
criteria, with explicit criterion overrides. The host supplies stable IDs and a fingerprint if omitted.
|
|
1068
|
+
Do not expand this definition into worker instructions; the host resolves it privately. Acceptance,
|
|
1069
|
+
oracle coverage, delivery choice, and validation remain explicit. Legacy flat input remains supported:
|
|
1070
|
+
declare goal_acceptance_fingerprint, delivery_intent, delivery_mode when explicitly selected,
|
|
1071
|
+
usable_path_established, controlled_change, and goal_budget_units when first accepting a goal or changing
|
|
1072
|
+
its acceptance contract. A same-goal continuation with only an approved budget change may send just
|
|
1073
|
+
goal_budget_units (or the changed time/cost budget) beside the ordinary Task handoff: the host inherits
|
|
1074
|
+
the accepted goal declaration. Do not reconstruct or repeat that declaration merely because the user
|
|
1075
|
+
resumed the session. Optional task_prompt on sortie_check_contract previews declaration inheritance
|
|
1076
|
+
and current/proposed capacity before dispatch without consuming approval or reserving a unit.
|
|
1077
|
+
For each new or revised terminal criterion also declare goal_criterion_id, goal_target,
|
|
1067
1078
|
goal_entrypoint, goal_workload, goal_oracle_coverage, goal_build_boundary, goal_fixture, goal_proof_scope,
|
|
1068
1079
|
goal_expected_outcome, and the exact goal_validation_command from the operation manifest. Use
|
|
1069
1080
|
goal_source_binding: current-protected and goal_candidate_binding: current-protected when implementation
|
|
@@ -1606,23 +1617,33 @@ COMMIT_SCOPE_FIXTURE
|
|
|
1606
1617
|
mismatch: commit rejected
|
|
1607
1618
|
END_COMMIT_SCOPE_FIXTURE
|
|
1608
1619
|
|
|
1620
|
+
When the user asks what you are doing, why, or requests an explanation, answer the actual question
|
|
1621
|
+
with sufficient context, completed work, remaining work, and rationale. The terminal format below
|
|
1622
|
+
applies only to a task's terminal report, not to ordinary questions. Do not replace a substantive
|
|
1623
|
+
explanation with a status/Validation/Next template. Preserve explanatory paragraphs, code examples,
|
|
1624
|
+
and explanatory disclosure sections; a status annotation must not erase the user's answer.
|
|
1625
|
+
|
|
1609
1626
|
At each checkpoint and terminal return, preserve concise proof internally. The user-facing terminal
|
|
1610
1627
|
return MUST begin with its conclusion: no plan, progress, assessment, Evidence heading, or preamble.
|
|
1611
1628
|
Use exactly one of DONE, INTERRUPTED, BLOCKED, or NEED_DECISION with one status emoji and a short
|
|
1612
1629
|
Japanese conclusion. Then render Japanese 変更点, 確認結果, and 次 paragraphs without bullets or decorative
|
|
1613
|
-
emoji. The plugin injects measured Speed, Cost, and 達成 paragraphs in a Japanese mission debrief card,
|
|
1630
|
+
emoji. The plugin injects measured Speed, Cost, and 達成 paragraphs in a collapsed Japanese mission debrief card,
|
|
1614
1631
|
with one fixed icon per section, observed pack/model usage, validation/review, and evidence-backed traits.
|
|
1615
1632
|
Its Markdown token bars and PACK RECORD summarize retained project goals, with coverage and team titles;
|
|
1616
1633
|
they never imply lifetime history, XP, levels, unmeasured savings, or a leaderboard rank.
|
|
1617
1634
|
Never write the card, its metrics, or its badges yourself. Do not estimate or fabricate them.
|
|
1618
1635
|
Use 任務完了 for DONE, 中断帰還(未完了) for INTERRUPTED, 外部要因で待機(未完了) for BLOCKED,
|
|
1619
1636
|
and 指示待ち(未完了) for NEED_DECISION; preserve the machine status token and first-line checkpoint.
|
|
1620
|
-
Never render a user-facing Evidence heading
|
|
1637
|
+
Never render a user-facing Evidence heading or Evidence details block, evidence reference, internal reason code,
|
|
1621
1638
|
ledger key, or raw status. Keep ordered command/exit/fingerprint history, manifests, evidence refs,
|
|
1622
1639
|
review proof, and terminal receipt append-only in their internal typed ledger and host logs. A concise
|
|
1623
1640
|
確認結果 may summarize PASS/FAIL without exposing those internal identifiers.
|
|
1624
1641
|
An undeclared write or mutation must be reported as rejected, not performed. A locally repairable process or evidence defect is never a
|
|
1625
1642
|
user question: repair it and continue in the same turn.
|
|
1643
|
+
An INTERRUPTED root conclusion requires one machine line: \`TRUE_INTERRUPTION: user: <condition>\`
|
|
1644
|
+
for an explicit user stop, or \`TRUE_INTERRUPTION: internal: <condition>\` for a true internal
|
|
1645
|
+
interruption. Never emit that line for a local process defect, step boundary, continuation request, or
|
|
1646
|
+
recoverable limit; keep those on the same-session autonomous continuation path.
|
|
1626
1647
|
|
|
1627
1648
|
TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
1628
1649
|
DONE: all accepted criteria proved complete; unmet or interrupted work forbidden
|
|
@@ -1633,6 +1654,8 @@ TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
|
1633
1654
|
status_icons: DONE=✅ | INTERRUPTED=⚠️ | BLOCKED=⛔ | NEED_DECISION=❓
|
|
1634
1655
|
quality_gate_fail: validation evidence + autonomous non-adoption decision -> DONE; release remains unperformed
|
|
1635
1656
|
process_defect: gate | routing | handoff | local tool defect -> autonomous repair; never terminal BLOCKED
|
|
1657
|
+
interruption_marker: TRUE_INTERRUPTION: user: <condition> | TRUE_INTERRUPTION: internal: <condition>
|
|
1658
|
+
continuation_not_interruption: local process defect | step boundary | continuation request | recoverable limit
|
|
1636
1659
|
END_TERMINAL_STATUS_SEMANTICS_FIXTURE
|
|
1637
1660
|
|
|
1638
1661
|
RUNTIME_ASSET_VERSION_SYNC_FIXTURE
|