sortie-dogs 0.9.0 → 0.9.2
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 +1 -1
- package/dist/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/goal-bound.d.ts +2 -0
- package/dist/core/goal-bound.js +5 -1
- package/dist/core/worktree-parallel-dispatch.d.ts +2 -2
- package/dist/core/worktree-parallel-dispatch.js +3 -1
- package/dist/plugin/index.js +238 -87
- package/dist/plugin/run-metrics.d.ts +5 -2
- package/dist/plugin/run-metrics.js +116 -55
- package/dist/runtime-assets.d.ts +8 -8
- package/dist/runtime-assets.js +49 -90
- package/package.json +1 -1
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.2](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.2)
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
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.77-terminal-delivery-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.77-terminal-delivery-v1";
|
|
@@ -143,6 +143,8 @@ export type GoalFlightEvent = (GoalEventBase & {
|
|
|
143
143
|
readonly goal_id: string;
|
|
144
144
|
readonly unit_id: string;
|
|
145
145
|
readonly disposition: "succeeded" | "failed" | "cancelled";
|
|
146
|
+
/** Missing on pre-v0.9.1 ledgers and therefore interpreted as an acceptance result. */
|
|
147
|
+
readonly result_class?: "acceptance" | "process-defect" | "interrupted";
|
|
146
148
|
readonly progress_fingerprint: string | null;
|
|
147
149
|
readonly evidence: readonly GoalEvidence[];
|
|
148
150
|
readonly elapsed_ms: number | null;
|
package/dist/core/goal-bound.js
CHANGED
|
@@ -178,7 +178,11 @@ export function reduceGoalFlight(records) {
|
|
|
178
178
|
.filter((criterionID) => !state.satisfied_criteria.includes(criterionID));
|
|
179
179
|
const progress = newCriteria.length > 0;
|
|
180
180
|
requireState(event.progress_fingerprint === (progress ? goalFingerprint(event.evidence) : null), "evidence", "Progress fingerprint does not represent newly accepted evidence.");
|
|
181
|
-
const
|
|
181
|
+
const acceptanceFailure = event.disposition === "failed" &&
|
|
182
|
+
(event.result_class === undefined || event.result_class === "acceptance");
|
|
183
|
+
const nextNoProgress = progress ? 0 : acceptanceFailure
|
|
184
|
+
? state.no_progress_results + 1
|
|
185
|
+
: state.no_progress_results;
|
|
182
186
|
const elapsed = state.consumed_time_ms === null || event.elapsed_ms === null ? null : state.consumed_time_ms + event.elapsed_ms;
|
|
183
187
|
const cost = state.consumed_cost_usd === null || event.cost_usd === null ? null : state.consumed_cost_usd + event.cost_usd;
|
|
184
188
|
state = { ...state, consumed_units: state.consumed_units + 1, consumed_time_ms: elapsed, consumed_cost_usd: cost,
|
|
@@ -21,7 +21,7 @@ export type ParallelDispatchPrepareResult = {
|
|
|
21
21
|
readonly reason: "scope-overlap" | "dependency-ambiguous";
|
|
22
22
|
};
|
|
23
23
|
/** Runtime capacity and mapping limits that the pure admission policy cannot observe. */
|
|
24
|
-
export type FabricDispatchSolReason = LunaFabricSolReason | "unit-count-exceeds-capacity" | "concurrent-scope-overlap" | "contract-unmappable";
|
|
24
|
+
export type FabricDispatchSolReason = LunaFabricSolReason | "unit-count-exceeds-capacity" | "concurrent-scope-overlap" | "contract-unmappable" | "target-checked-out";
|
|
25
25
|
export type ParallelDispatchFabricPrepareResult = {
|
|
26
26
|
readonly status: "prepared";
|
|
27
27
|
readonly snapshot: ParallelDispatchSnapshot;
|
|
@@ -154,7 +154,7 @@ export declare class ParallelDispatchCoordinator {
|
|
|
154
154
|
private acquire;
|
|
155
155
|
private git;
|
|
156
156
|
private readRef;
|
|
157
|
-
|
|
157
|
+
targetCheckedOut(targetRef: string): Promise<boolean>;
|
|
158
158
|
private deleteFabricSourceRefs;
|
|
159
159
|
private ensureCandidateRef;
|
|
160
160
|
private withPrepareAuthority;
|
|
@@ -2705,7 +2705,9 @@ export class ParallelDispatchCoordinator {
|
|
|
2705
2705
|
await rm(temporary, { force: true }).catch(() => undefined);
|
|
2706
2706
|
}
|
|
2707
2707
|
}
|
|
2708
|
-
|
|
2708
|
+
// Heartbeats retain live ownership. A crashed CLI must not leave a lease whose expiry exceeds
|
|
2709
|
+
// the next CLI's acquisition budget; keep room for mutex acquisition and filesystem latency.
|
|
2710
|
+
async acquire(scope = STATE_SCOPE, ttlMs = Math.floor(LOCK_TIMEOUT_MS / 2)) {
|
|
2709
2711
|
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
2710
2712
|
while (true) {
|
|
2711
2713
|
try {
|
package/dist/plugin/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { TerminalRescueRuntime } from "../core/terminal-rescue-runtime.js";
|
|
|
36
36
|
import { OpenCodeTerminalRescueHost } from "./terminal-rescue-host.js";
|
|
37
37
|
import { AdaptiveRemediationRuntime, AdaptiveRunFlightLineage } from "../core/adaptive-remediation-runtime.js";
|
|
38
38
|
import { DEFAULT_ADAPTIVE_REMEDIATION_MODEL, GitAdaptiveRemediationHost, OpenCodeAdaptiveRemediationProvider } from "./adaptive-remediation-host.js";
|
|
39
|
-
import { collectRunMetrics, createSortieResult, insertRunMetrics, insertSortieResult, terminalRunOutcome } from "./run-metrics.js";
|
|
39
|
+
import { collectRunMetrics, createSortieResult, insertRunMetrics, insertSortieResult, replaceDoneTerminalStatus, replaceTerminalStatus, sanitizeTerminalReport, terminalRunOutcome } from "./run-metrics.js";
|
|
40
40
|
const INPUT_LIMITS = { config: 64 * 1024, manifest: 512 * 1024, handoff: 2 * 1024 * 1024, parallel: 512 * 1024 };
|
|
41
41
|
const INSPECTION_CACHE = { maximum: 256, ttlMilliseconds: 30 * 60 * 1000 };
|
|
42
42
|
const ACTIVE_SESSION_CACHE = { maximum: 256, ttlMilliseconds: 30 * 60 * 1000 };
|
|
@@ -619,6 +619,8 @@ const LABELLED_VALUE = /^[\t ]*(?:[-*][\t ]+)?[^\r\n:=]{1,64}[\t ]*[=:][\t ]*(.*
|
|
|
619
619
|
function roleTokenValues(text) {
|
|
620
620
|
const values = [];
|
|
621
621
|
for (const line of text.split(/\r?\n/u)) {
|
|
622
|
+
if (/^[\t ]*(?:[-*][\t ]+)?(?:delivery_intent|goal_[a-z0-9_]+)[\t ]*[=:]/iu.test(line))
|
|
623
|
+
continue;
|
|
622
624
|
const match = LABELLED_VALUE.exec(line);
|
|
623
625
|
if (match === null)
|
|
624
626
|
continue;
|
|
@@ -1516,13 +1518,56 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1516
1518
|
async function terminalGoalFromHostText(sessionID, text) {
|
|
1517
1519
|
const outcome = terminalRunOutcome(text);
|
|
1518
1520
|
if (outcome === undefined || !isCoordinatorSession(sessionID)) {
|
|
1519
|
-
return { outcome, goal: undefined, receipt: undefined };
|
|
1521
|
+
return { outcome, goal: undefined, receipt: undefined, delivery: "ready" };
|
|
1522
|
+
}
|
|
1523
|
+
let delivery = "ready";
|
|
1524
|
+
const coordinator = await getParallelCoordinator().catch(() => undefined);
|
|
1525
|
+
let parallel = await coordinator?.snapshot(sessionID).catch(() => undefined);
|
|
1526
|
+
if (parallel !== undefined && !parallel.archived) {
|
|
1527
|
+
await restoreChildLifecycles(sessionID, parallel).catch(() => undefined);
|
|
1528
|
+
const knownCalls = coordinatorTaskCalls.get(sessionID) ?? new Set();
|
|
1529
|
+
const running = parallel.tasks.filter(({ phase }) => phase === "running");
|
|
1530
|
+
if (running.length > 0 && running.every(({ call_id }) => call_id !== null && !knownCalls.has(call_id))) {
|
|
1531
|
+
const status = input.client?.session?.status;
|
|
1532
|
+
const response = status === undefined ? undefined : await status.call(input.client.session, {
|
|
1533
|
+
query: { directory: input.directory },
|
|
1534
|
+
}).catch(() => undefined);
|
|
1535
|
+
const payload = isRecord(response) && "data" in response ? response.data : response;
|
|
1536
|
+
const statuses = isRecord(payload) ? payload : undefined;
|
|
1537
|
+
const settled = statuses !== undefined && running.every(({ child_session_id }) => {
|
|
1538
|
+
const observed = child_session_id === null ? undefined : statuses[child_session_id];
|
|
1539
|
+
return isRecord(observed) && observed.type === "idle";
|
|
1540
|
+
});
|
|
1541
|
+
if (settled)
|
|
1542
|
+
parallel = await coordinator.reconcile(sessionID, knownCalls, parallel.run_id).catch(() => parallel);
|
|
1543
|
+
}
|
|
1544
|
+
if (parallel !== undefined && !parallel.archived)
|
|
1545
|
+
delivery = "running";
|
|
1520
1546
|
}
|
|
1547
|
+
if (parallel?.archived === true && parallel.terminal_reason !== "completed")
|
|
1548
|
+
delivery = "failed";
|
|
1521
1549
|
let goal = await currentGoal(sessionID).catch(() => undefined);
|
|
1522
1550
|
let receipt = goal?.receipt ?? undefined;
|
|
1523
|
-
|
|
1551
|
+
const proved = goal?.acceptance_contract !== null && goal?.acceptance_contract !== undefined &&
|
|
1552
|
+
goal.acceptance_contract.criteria.every(({ criterion_id }) => goal.satisfied_criteria.includes(criterion_id));
|
|
1553
|
+
if (delivery === "running") {
|
|
1554
|
+
return { outcome, goal, receipt, delivery };
|
|
1555
|
+
}
|
|
1556
|
+
if (receipt === undefined && delivery === "failed") {
|
|
1557
|
+
receipt = await terminalGoal(sessionID, "stopped", "stopped").catch(() => undefined);
|
|
1558
|
+
}
|
|
1559
|
+
else if (receipt === undefined && proved) {
|
|
1524
1560
|
receipt = await terminalGoal(sessionID, "completed", "succeeded").catch(() => undefined);
|
|
1525
1561
|
}
|
|
1562
|
+
else if (receipt === undefined && outcome === "DONE") {
|
|
1563
|
+
receipt = await terminalGoal(sessionID, "completed", "succeeded").catch(() => undefined);
|
|
1564
|
+
}
|
|
1565
|
+
else if (receipt === undefined && outcome === "INTERRUPTED") {
|
|
1566
|
+
receipt = await terminalGoal(sessionID, "stopped", "stopped").catch(() => undefined);
|
|
1567
|
+
}
|
|
1568
|
+
else if (receipt === undefined && outcome === "NEED_DECISION") {
|
|
1569
|
+
receipt = await terminalGoal(sessionID, "awaiting_user", "stopped").catch(() => undefined);
|
|
1570
|
+
}
|
|
1526
1571
|
else if (receipt === undefined && outcome === "BLOCKED") {
|
|
1527
1572
|
const reason = /(^|\n)TRUE_BLOCKER\s*:\s*user-decision\s*:/iu.test(text)
|
|
1528
1573
|
? "awaiting_user"
|
|
@@ -1533,95 +1578,169 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1533
1578
|
goal = await currentGoal(sessionID).catch(() => goal);
|
|
1534
1579
|
if (receipt !== undefined)
|
|
1535
1580
|
rootAcceptanceContinuity.delete(sessionID);
|
|
1536
|
-
return { outcome, goal, receipt };
|
|
1581
|
+
return { outcome, goal, receipt, delivery };
|
|
1537
1582
|
}
|
|
1538
|
-
function
|
|
1539
|
-
const entries = handoffEntries(prompt);
|
|
1540
|
-
const explicit = handoffValue(entries, ["delivery_mode"]);
|
|
1541
|
-
if (explicit === "planning-only" || explicit === "mvp-first" || explicit === "repair-first" || explicit === "controlled-change")
|
|
1542
|
-
return explicit;
|
|
1543
|
-
const intent = handoffValue(entries, ["delivery_intent"]);
|
|
1544
|
-
const declared = intent === "design" || intent === "registration" || intent === "repair" || intent === "controlled-change"
|
|
1545
|
-
? intent : "implementation";
|
|
1546
|
-
return selectGoalDelivery({ declared_intent: declared,
|
|
1547
|
-
requested_usable_path_established: handoffValue(entries, ["usable_path_established"]) === "true",
|
|
1548
|
-
irreversible_or_major_scope: handoffValue(entries, ["controlled_change"]) === "true" });
|
|
1549
|
-
}
|
|
1550
|
-
function declaredGoalContract(prompt) {
|
|
1583
|
+
function goalDeclarationContract(prompt) {
|
|
1551
1584
|
const lines = prompt.split(/\r?\n/u);
|
|
1552
1585
|
const criterionStarts = lines.flatMap((line, index) => /^\s*goal_criterion_id\s*:/u.test(line) ? [index] : []);
|
|
1553
|
-
if (criterionStarts.length
|
|
1554
|
-
|
|
1555
|
-
const contracts = criterionStarts.map((start, index) => declaredGoalContract(lines.slice(start, criterionStarts[index + 1]).join("\n") +
|
|
1556
|
-
(validation === undefined ? "" : `\nvalidation: ${validation}`)));
|
|
1557
|
-
if (contracts.some((contract) => contract === null))
|
|
1558
|
-
return null;
|
|
1559
|
-
const criteria = contracts.flatMap((contract) => contract.criteria);
|
|
1560
|
-
return new Set(criteria.map((criterion) => criterion.criterion_id)).size === criteria.length
|
|
1561
|
-
? { criteria } : null;
|
|
1586
|
+
if (criterionStarts.length === 0) {
|
|
1587
|
+
return { defects: [contractDefect("contract", "/goal_acceptance/criteria", "goal_criteria_missing")] };
|
|
1562
1588
|
}
|
|
1563
|
-
const
|
|
1589
|
+
const validation = handoffValue(handoffEntries(prompt), ["validation"]);
|
|
1564
1590
|
const required = ["goal_criterion_id", "goal_target", "goal_entrypoint", "goal_workload",
|
|
1565
1591
|
"goal_oracle_coverage", "goal_build_boundary", "goal_source", "goal_candidate", "goal_fixture",
|
|
1566
1592
|
"goal_proof_scope", "goal_expected_outcome"];
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1593
|
+
const criteria = [];
|
|
1594
|
+
const defects = [];
|
|
1595
|
+
for (const [criterionIndex, start] of criterionStarts.entries()) {
|
|
1596
|
+
const block = lines.slice(start, criterionStarts[criterionIndex + 1]).join("\n") +
|
|
1597
|
+
(validation === undefined ? "" : `\nvalidation: ${validation}`);
|
|
1598
|
+
const entries = handoffEntries(block);
|
|
1599
|
+
const values = Object.fromEntries(required.map((key) => [key, handoffValue(entries, [key])]));
|
|
1600
|
+
const pointer = (field) => `/goal_acceptance/criteria/${criterionIndex}/${field}`;
|
|
1601
|
+
for (const key of required) {
|
|
1602
|
+
if (key !== "goal_oracle_coverage" && values[key] === undefined) {
|
|
1603
|
+
defects.push(contractDefect("contract", pointer(key), "goal_field_missing"));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
const inlineOracle = values.goal_oracle_coverage;
|
|
1607
|
+
let oracleCoverage;
|
|
1608
|
+
try {
|
|
1609
|
+
const parsed = inlineOracle === undefined
|
|
1610
|
+
? taskAcceptanceCriteria(block, "goal_oracle_coverage")
|
|
1611
|
+
: JSON.parse(inlineOracle);
|
|
1612
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string" && value.length > 0 && value.length <= 512) &&
|
|
1613
|
+
new Set(parsed).size === parsed.length)
|
|
1614
|
+
oracleCoverage = parsed;
|
|
1615
|
+
}
|
|
1616
|
+
catch { /* concrete defect below */ }
|
|
1617
|
+
if (oracleCoverage === undefined) {
|
|
1618
|
+
defects.push(contractDefect("contract", pointer("goal_oracle_coverage"), inlineOracle === undefined ? "goal_field_missing_or_malformed" : "goal_oracle_coverage_invalid"));
|
|
1619
|
+
}
|
|
1620
|
+
const buildBoundary = values.goal_build_boundary;
|
|
1621
|
+
const proofScope = values.goal_proof_scope;
|
|
1622
|
+
const expectedOutcome = values.goal_expected_outcome;
|
|
1623
|
+
const sourceBinding = handoffValue(entries, ["goal_source_binding"]);
|
|
1624
|
+
const candidateBinding = handoffValue(entries, ["goal_candidate_binding"]);
|
|
1625
|
+
if (buildBoundary !== undefined && buildBoundary !== "included" && buildBoundary !== "excluded" && buildBoundary !== "not-applicable") {
|
|
1626
|
+
defects.push(contractDefect("contract", pointer("goal_build_boundary"), "goal_build_boundary_invalid"));
|
|
1627
|
+
}
|
|
1628
|
+
if (proofScope !== undefined && proofScope !== "requested-full" && proofScope !== "document-deliverable" && proofScope !== "expected-negative") {
|
|
1629
|
+
defects.push(contractDefect("contract", pointer("goal_proof_scope"), "goal_proof_scope_invalid"));
|
|
1630
|
+
}
|
|
1631
|
+
if (expectedOutcome !== undefined && expectedOutcome !== "pass" && expectedOutcome !== "fail") {
|
|
1632
|
+
defects.push(contractDefect("contract", pointer("goal_expected_outcome"), "goal_expected_outcome_invalid"));
|
|
1633
|
+
}
|
|
1634
|
+
if (sourceBinding !== undefined && sourceBinding !== "declared" && sourceBinding !== "current-protected") {
|
|
1635
|
+
defects.push(contractDefect("contract", pointer("goal_source_binding"), "goal_source_binding_invalid"));
|
|
1636
|
+
}
|
|
1637
|
+
if (candidateBinding !== undefined && candidateBinding !== "declared" && candidateBinding !== "current-protected") {
|
|
1638
|
+
defects.push(contractDefect("contract", pointer("goal_candidate_binding"), "goal_candidate_binding_invalid"));
|
|
1639
|
+
}
|
|
1640
|
+
const declaredValidation = handoffValue(entries, ["validation"]);
|
|
1641
|
+
const structuredCommands = declaredValidation?.startsWith("{") && declaredValidation.endsWith("}")
|
|
1642
|
+
? [...declaredValidation.matchAll(/(?:\{|,)\s*command\s*:\s*([^,}]+)/gu)] : [];
|
|
1643
|
+
const validationCommand = handoffValue(entries, ["goal_validation_command"]) ??
|
|
1644
|
+
(structuredCommands.length === 1 ? unquoteValue(structuredCommands[0][1].trim()) : undefined);
|
|
1645
|
+
if (validationCommand === undefined || normalizeCommand(validationCommand).length === 0) {
|
|
1646
|
+
defects.push(contractDefect("contract", pointer("goal_validation_command"), "goal_validation_command_missing"));
|
|
1647
|
+
}
|
|
1648
|
+
if (defects.some((defect) => defect.includes(`/criteria/${criterionIndex}/`)))
|
|
1649
|
+
continue;
|
|
1650
|
+
criteria.push({ criterion_id: values.goal_criterion_id, target: values.goal_target,
|
|
1651
|
+
entrypoint: values.goal_entrypoint, workload: values.goal_workload, oracle_coverage: oracleCoverage,
|
|
1652
|
+
build_boundary: buildBoundary,
|
|
1653
|
+
source: values.goal_source, candidate: values.goal_candidate,
|
|
1654
|
+
source_binding: (sourceBinding ?? "declared"),
|
|
1655
|
+
candidate_binding: (candidateBinding ?? "declared"),
|
|
1656
|
+
validation_command: normalizeCommand(validationCommand), fixture: values.goal_fixture,
|
|
1657
|
+
proof_scope: proofScope,
|
|
1658
|
+
expected_outcome: expectedOutcome });
|
|
1659
|
+
}
|
|
1660
|
+
const duplicateIDs = criteria.map(({ criterion_id }) => criterion_id)
|
|
1661
|
+
.filter((id, index, all) => all.indexOf(id) !== index);
|
|
1662
|
+
if (duplicateIDs.length > 0) {
|
|
1663
|
+
defects.push(contractDefect("contract", "/goal_acceptance/criteria", "goal_criterion_id_duplicate"));
|
|
1664
|
+
}
|
|
1665
|
+
return defects.length === 0 ? { contract: { criteria }, defects } : { defects };
|
|
1666
|
+
}
|
|
1667
|
+
function validateGoalDeclaration(prompt) {
|
|
1668
|
+
const entries = handoffEntries(prompt);
|
|
1669
|
+
const defects = [];
|
|
1670
|
+
const fingerprint = handoffValue(entries, ["goal_acceptance_fingerprint"]);
|
|
1671
|
+
if (fingerprint === undefined)
|
|
1672
|
+
defects.push(contractDefect("contract", "/goal_acceptance_fingerprint", "goal_fingerprint_missing"));
|
|
1673
|
+
else if (!/^sha256:[a-f0-9]{64}$/u.test(fingerprint)) {
|
|
1674
|
+
defects.push(contractDefect("contract", "/goal_acceptance_fingerprint", "goal_fingerprint_format"));
|
|
1577
1675
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
async function bindGoalDeclaration(sessionID, prompt
|
|
1676
|
+
const intent = handoffValue(entries, ["delivery_intent"]);
|
|
1677
|
+
const intents = ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
1678
|
+
if (intent === undefined)
|
|
1679
|
+
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_missing"));
|
|
1680
|
+
else if (!intents.includes(intent)) {
|
|
1681
|
+
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_invalid"));
|
|
1682
|
+
}
|
|
1683
|
+
const mode = handoffValue(entries, ["delivery_mode"]);
|
|
1684
|
+
const modes = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
1685
|
+
if (mode !== undefined && !modes.includes(mode)) {
|
|
1686
|
+
defects.push(contractDefect("contract", "/delivery_mode", "delivery_mode_invalid"));
|
|
1687
|
+
}
|
|
1688
|
+
const usable = handoffValue(entries, ["usable_path_established"]);
|
|
1689
|
+
const controlled = handoffValue(entries, ["controlled_change"]);
|
|
1690
|
+
for (const [field, value] of [["usable_path_established", usable], ["controlled_change", controlled]]) {
|
|
1691
|
+
if (value === undefined)
|
|
1692
|
+
defects.push(contractDefect("contract", `/${field}`, "goal_boolean_missing"));
|
|
1693
|
+
else if (value !== "true" && value !== "false")
|
|
1694
|
+
defects.push(contractDefect("contract", `/${field}`, "goal_boolean_invalid"));
|
|
1695
|
+
}
|
|
1696
|
+
const contract = goalDeclarationContract(prompt);
|
|
1697
|
+
defects.push(...contract.defects);
|
|
1698
|
+
if (defects.length > 0 || fingerprint === undefined || intent === undefined || contract.contract === undefined)
|
|
1699
|
+
return { defects };
|
|
1700
|
+
const delivery = mode ?? selectGoalDelivery({
|
|
1701
|
+
declared_intent: intent, requested_usable_path_established: usable === "true",
|
|
1702
|
+
irreversible_or_major_scope: controlled === "true",
|
|
1703
|
+
});
|
|
1704
|
+
return { declaration: { fingerprint, delivery, contract: contract.contract }, defects };
|
|
1705
|
+
}
|
|
1706
|
+
async function bindGoalDeclaration(sessionID, prompt) {
|
|
1609
1707
|
const ledger = await goalLedger(sessionID);
|
|
1610
1708
|
let state = (await ledger.readGoal()).state;
|
|
1611
1709
|
if (state.goal_id === null || state.origin_user_message_id === null)
|
|
1612
1710
|
return undefined;
|
|
1613
|
-
const authority = goalDeclarationAuthority.get(sessionID);
|
|
1614
|
-
if (authority === undefined || authority !== state.latest_user_message_id)
|
|
1615
|
-
return state;
|
|
1616
|
-
goalDeclarationAuthority.delete(sessionID);
|
|
1617
1711
|
const entries = handoffEntries(prompt);
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1712
|
+
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));
|
|
1713
|
+
// Accepted goals may retain their existing declaration on later units, but the first worker
|
|
1714
|
+
// handoff and every handoff containing declaration fields must be complete.
|
|
1715
|
+
if (!typedDeclarationPresent) {
|
|
1716
|
+
if (state.acceptance_contract === null && goalDeclarationAuthority.get(sessionID) === state.latest_user_message_id) {
|
|
1717
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", {
|
|
1718
|
+
defects: validateGoalDeclaration(prompt).defects,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
if (goalDeclarationAuthority.get(sessionID) === state.latest_user_message_id)
|
|
1722
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1620
1723
|
return state;
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1724
|
+
}
|
|
1725
|
+
const validated = validateGoalDeclaration(prompt);
|
|
1726
|
+
if (validated.declaration === undefined) {
|
|
1727
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: validated.defects });
|
|
1728
|
+
}
|
|
1729
|
+
const declaration = validated.declaration;
|
|
1730
|
+
const authority = goalDeclarationAuthority.get(sessionID);
|
|
1731
|
+
if (authority === undefined || authority !== state.latest_user_message_id) {
|
|
1732
|
+
if (declaration.fingerprint !== state.acceptance_fingerprint) {
|
|
1733
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: [
|
|
1734
|
+
contractDefect("contract", "/goal_acceptance_fingerprint", "goal_revision_unauthorized"),
|
|
1735
|
+
] });
|
|
1736
|
+
}
|
|
1737
|
+
return state;
|
|
1738
|
+
}
|
|
1739
|
+
if (declaration.fingerprint === state.acceptance_fingerprint &&
|
|
1740
|
+
goalFingerprint(declaration.contract) === goalFingerprint(state.acceptance_contract)) {
|
|
1741
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1624
1742
|
return state;
|
|
1743
|
+
}
|
|
1625
1744
|
const units = Number(handoffValue(entries, ["goal_budget_units"]));
|
|
1626
1745
|
const maxUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
|
|
1627
1746
|
? units : state.budget?.max_units ?? 32;
|
|
@@ -1631,21 +1750,25 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1631
1750
|
const costBudget = Number.isFinite(declaredCost) && declaredCost > 0 ? declaredCost : state.budget?.cost_usd ?? null;
|
|
1632
1751
|
state = await ledger.appendGoal({ kind: "goal.revised", at: new Date().toISOString(), goal_id: state.goal_id,
|
|
1633
1752
|
revision: state.revision + 1, scope_epoch: state.scope_epoch + 1,
|
|
1634
|
-
acceptance_fingerprint:
|
|
1753
|
+
acceptance_fingerprint: declaration.fingerprint, origin_user_message_id: state.latest_user_message_id,
|
|
1635
1754
|
session_id: sessionID, selected_agent: state.selected_agent ?? COORDINATOR_AGENT,
|
|
1636
|
-
delivery:
|
|
1755
|
+
delivery: declaration.delivery, budget: { max_units: maxUnits,
|
|
1637
1756
|
time_ms: timeBudget, cost_usd: costBudget,
|
|
1638
1757
|
source: Number.isSafeInteger(units) ? "accepted-plan" : state.budget?.source ?? "policy-default" },
|
|
1639
|
-
acceptance_contract:
|
|
1758
|
+
acceptance_contract: declaration.contract });
|
|
1759
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1640
1760
|
return state;
|
|
1641
1761
|
}
|
|
1642
|
-
async function reserveGoalDispatch(sessionID, callID, prompt
|
|
1762
|
+
async function reserveGoalDispatch(sessionID, callID, prompt) {
|
|
1643
1763
|
if (goalReservations.has(callID))
|
|
1644
1764
|
return;
|
|
1645
1765
|
const ledger = await goalLedger(sessionID);
|
|
1646
|
-
let state = await bindGoalDeclaration(sessionID, prompt
|
|
1766
|
+
let state = await bindGoalDeclaration(sessionID, prompt) ?? (await ledger.readGoal()).state;
|
|
1647
1767
|
if (state.goal_id === null)
|
|
1648
1768
|
return; // Legacy already-authorized roots may settle without inventing authority.
|
|
1769
|
+
if (state.phase === "terminal" || state.receipt !== null) {
|
|
1770
|
+
throw new Error("SORTIE_GOAL_CONTROL_DENIED: terminal");
|
|
1771
|
+
}
|
|
1649
1772
|
if (state.replan_required) {
|
|
1650
1773
|
if (state.replan_used) {
|
|
1651
1774
|
await terminalGoal(sessionID, "stop_no_progress", "stopped");
|
|
@@ -1735,10 +1858,18 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1735
1858
|
entry.execution.units.includes(reservation.unitID));
|
|
1736
1859
|
const newEvidence = acceptedEvidence.filter((entry) => entry.measurement.criterion_ids.some((criterionID) => !state.satisfied_criteria.includes(criterionID)));
|
|
1737
1860
|
const progress = newEvidence.length > 0;
|
|
1861
|
+
const metadata = isRecord(output.metadata) ? output.metadata : undefined;
|
|
1862
|
+
const interrupted = metadata?.status === "cancel" || metadata?.status === "cancelled" ||
|
|
1863
|
+
output.status === "cancel" || output.status === "cancelled";
|
|
1864
|
+
const hostBindingDefect = childSessionID !== undefined && [...(bindingDenials.get(reservation.root)?.values() ?? [])]
|
|
1865
|
+
.some((candidateDenials) => [...candidateDenials.values()].includes(childSessionID));
|
|
1866
|
+
const processDefect = childSessionID === undefined || hostBindingDefect;
|
|
1867
|
+
const resultClass = progress ? "acceptance" : interrupted ? "interrupted" : processDefect ? "process-defect" : "acceptance";
|
|
1738
1868
|
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
1739
1869
|
reservation_id: reservation.reservationID, receipt_id: goalFingerprint({ call_id: callID, output: outputText.slice(0, 2048) }),
|
|
1740
1870
|
goal_id: state.goal_id, unit_id: reservation.unitID,
|
|
1741
|
-
disposition: progress ? "succeeded" : "
|
|
1871
|
+
disposition: progress ? "succeeded" : interrupted ? "cancelled" : "failed", result_class: resultClass,
|
|
1872
|
+
progress_fingerprint: progress ? goalFingerprint(acceptedEvidence) : null,
|
|
1742
1873
|
evidence: acceptedEvidence, elapsed_ms: Math.max(0, Date.now() - reservation.started), cost_usd: null });
|
|
1743
1874
|
if (childSessionID !== undefined) {
|
|
1744
1875
|
for (const [executionCallID, execution] of hostGoalExecutions) {
|
|
@@ -3070,6 +3201,9 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
3070
3201
|
contract_fingerprint: structuralAdmission.contract_fingerprint, experience: experience.trace });
|
|
3071
3202
|
}
|
|
3072
3203
|
const coordinator = await getParallelCoordinator();
|
|
3204
|
+
if (await coordinator.targetCheckedOut(`refs/heads/${structuralAdmission.contract.provenance.target_branch}`)) {
|
|
3205
|
+
return JSON.stringify({ status: "sol-serial", reason: "target-checked-out", experience: experience.trace });
|
|
3206
|
+
}
|
|
3073
3207
|
const result = await coordinator.prepareFabric(contract, ownerRoot, executionPlanPath === undefined ? undefined : await readJson(resolve(executionPlanPath), INPUT_LIMITS.parallel));
|
|
3074
3208
|
if (result.status === "sol-serial")
|
|
3075
3209
|
return JSON.stringify({ ...result, experience: experience.trace });
|
|
@@ -5158,9 +5292,20 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5158
5292
|
const terminal = runOutcome === undefined || !isCoordinatorSession(textInput.sessionID)
|
|
5159
5293
|
? undefined
|
|
5160
5294
|
: await terminalGoalFromHostText(textInput.sessionID, textOutput.text);
|
|
5161
|
-
if (runOutcome === "DONE" && terminal
|
|
5162
|
-
terminal.goal
|
|
5163
|
-
textOutput.text = textOutput.text
|
|
5295
|
+
if (runOutcome === "DONE" && terminal !== undefined && (terminal.delivery === "running" ||
|
|
5296
|
+
(terminal.receipt === undefined && terminal.goal !== undefined && terminal.goal.acceptance_contract !== null))) {
|
|
5297
|
+
textOutput.text = replaceDoneTerminalStatus(textOutput.text, terminal.delivery === "running"
|
|
5298
|
+
? "status: IN_PROGRESS — durable delivery active; same sessionでjoinまたはstale reconcileが必要"
|
|
5299
|
+
: "status: IN_PROGRESS\ngoal_control: accepted criteria remain unproved");
|
|
5300
|
+
}
|
|
5301
|
+
if (runOutcome !== "DONE" && terminal?.delivery === "ready" && terminal.receipt?.status === "succeeded") {
|
|
5302
|
+
textOutput.text = replaceTerminalStatus(textOutput.text, "status: DONE");
|
|
5303
|
+
}
|
|
5304
|
+
if (runOutcome === "DONE" && terminal?.delivery === "failed") {
|
|
5305
|
+
textOutput.text = replaceTerminalStatus(textOutput.text, "status: INTERRUPTED — durable delivery failed");
|
|
5306
|
+
}
|
|
5307
|
+
if (runOutcome !== undefined && isCoordinatorSession(textInput.sessionID)) {
|
|
5308
|
+
textOutput.text = sanitizeTerminalReport(textOutput.text);
|
|
5164
5309
|
}
|
|
5165
5310
|
if ((isCoordinatorSession(textInput.sessionID) || await recoverCoordinatorRoot(textInput.sessionID)) &&
|
|
5166
5311
|
runOutcome !== undefined) {
|
|
@@ -6004,6 +6149,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6004
6149
|
fastLane.enableParallelDispatch(toolInput.sessionID, boundSnapshot.max_workers, counts.dispatched - currentContribution, counts.running - currentContribution, counts.total);
|
|
6005
6150
|
parallelWorkerAuthorized = true;
|
|
6006
6151
|
}
|
|
6152
|
+
if (toolInput.tool === "task" && taskRole !== undefined && IMPLEMENTATION_AGENTS.has(taskRole) &&
|
|
6153
|
+
isRecord(output.args)) {
|
|
6154
|
+
// Declaration admission precedes routing state and reservation. A concrete field denial can
|
|
6155
|
+
// therefore be repaired by a corrected Task call in this same coordinator turn.
|
|
6156
|
+
await bindGoalDeclaration(toolInput.sessionID, typeof output.args.prompt === "string" ? output.args.prompt : "");
|
|
6157
|
+
}
|
|
6007
6158
|
const resumedWorkerSessionID = fastLane.beforeTool(toolInput.sessionID, toolInput.tool, output.args, {
|
|
6008
6159
|
readonlyDiagnosisAuthorized: readonlyDiagnosis,
|
|
6009
6160
|
consultationFallbackAuthorized,
|
|
@@ -6012,7 +6163,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6012
6163
|
});
|
|
6013
6164
|
if (toolInput.tool === "task" && taskRole !== undefined && IMPLEMENTATION_AGENTS.has(taskRole) &&
|
|
6014
6165
|
isRecord(output.args)) {
|
|
6015
|
-
await reserveGoalDispatch(toolInput.sessionID, toolInput.callID, typeof output.args.prompt === "string" ? output.args.prompt : ""
|
|
6166
|
+
await reserveGoalDispatch(toolInput.sessionID, toolInput.callID, typeof output.args.prompt === "string" ? output.args.prompt : "");
|
|
6016
6167
|
}
|
|
6017
6168
|
if (validatedRootAcceptance !== undefined && reservedParallelDescriptor === undefined) {
|
|
6018
6169
|
rootAcceptanceContinuity.delete(toolInput.sessionID);
|
|
@@ -73,7 +73,7 @@ export interface SortieResult {
|
|
|
73
73
|
readonly accounting_phase: "pre-terminal";
|
|
74
74
|
readonly as_of: string;
|
|
75
75
|
readonly mission: {
|
|
76
|
-
readonly status: "COMPLETED" | "
|
|
76
|
+
readonly status: "COMPLETED" | "INTERRUPTED" | "EXTERNAL_BLOCKER" | "USER_DECISION";
|
|
77
77
|
readonly stop_reason: GoalTerminalReceipt["stop_reason"];
|
|
78
78
|
};
|
|
79
79
|
readonly speed: {
|
|
@@ -103,12 +103,15 @@ export interface SortieResult {
|
|
|
103
103
|
type SortieGoalSnapshot = Pick<GoalFlightState, "acceptance_contract" | "consumed_time_ms" | "satisfied_criteria">;
|
|
104
104
|
/** Builds a pure terminal snapshot from the durable goal receipt and already-observed host metrics. */
|
|
105
105
|
export declare function createSortieResult(receipt: GoalTerminalReceipt, goal: SortieGoalSnapshot, metrics: RunMetrics | undefined, asOf?: string): SortieResult;
|
|
106
|
-
export type RunTerminalOutcome = "DONE" | "BLOCKED" | "NEED_DECISION";
|
|
106
|
+
export type RunTerminalOutcome = "DONE" | "INTERRUPTED" | "BLOCKED" | "NEED_DECISION";
|
|
107
107
|
export declare function collectRunMetrics(client: RunMetricsClient | undefined, rootSessionID: string, directory?: string, now?: number, window?: RunMetricsWindow): Promise<RunMetrics | undefined>;
|
|
108
108
|
export declare function formatSortieResult(result: SortieResult): string;
|
|
109
109
|
export declare function formatRunMetrics(metrics: RunMetrics): string;
|
|
110
110
|
export declare function isDoneTerminalText(text: string): boolean;
|
|
111
111
|
export declare function terminalRunOutcome(text: string): RunTerminalOutcome | undefined;
|
|
112
|
+
export declare function replaceTerminalStatus(text: string, replacement: string): string;
|
|
113
|
+
export declare function replaceDoneTerminalStatus(text: string, replacement: string): string;
|
|
114
|
+
export declare function sanitizeTerminalReport(text: string): string;
|
|
112
115
|
export declare function insertRunMetrics(text: string, metrics: RunMetrics): string;
|
|
113
116
|
export declare function insertSortieResult(text: string, result: SortieResult): string;
|
|
114
117
|
export {};
|