sortie-dogs 0.9.0 → 0.9.1
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/plugin/index.js +194 -83
- package/dist/plugin/run-metrics.d.ts +5 -2
- package/dist/plugin/run-metrics.js +60 -25
- package/dist/runtime-assets.d.ts +8 -8
- package/dist/runtime-assets.js +48 -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.1](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.1)
|
|
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.76-goal-control-report-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.76-goal-control-report-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,
|
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;
|
|
@@ -1520,9 +1522,20 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1520
1522
|
}
|
|
1521
1523
|
let goal = await currentGoal(sessionID).catch(() => undefined);
|
|
1522
1524
|
let receipt = goal?.receipt ?? undefined;
|
|
1523
|
-
|
|
1525
|
+
const proved = goal?.acceptance_contract !== null && goal?.acceptance_contract !== undefined &&
|
|
1526
|
+
goal.acceptance_contract.criteria.every(({ criterion_id }) => goal.satisfied_criteria.includes(criterion_id));
|
|
1527
|
+
if (receipt === undefined && proved) {
|
|
1528
|
+
receipt = await terminalGoal(sessionID, "completed", "succeeded").catch(() => undefined);
|
|
1529
|
+
}
|
|
1530
|
+
else if (receipt === undefined && outcome === "DONE") {
|
|
1524
1531
|
receipt = await terminalGoal(sessionID, "completed", "succeeded").catch(() => undefined);
|
|
1525
1532
|
}
|
|
1533
|
+
else if (receipt === undefined && outcome === "INTERRUPTED") {
|
|
1534
|
+
receipt = await terminalGoal(sessionID, "stopped", "stopped").catch(() => undefined);
|
|
1535
|
+
}
|
|
1536
|
+
else if (receipt === undefined && outcome === "NEED_DECISION") {
|
|
1537
|
+
receipt = await terminalGoal(sessionID, "awaiting_user", "stopped").catch(() => undefined);
|
|
1538
|
+
}
|
|
1526
1539
|
else if (receipt === undefined && outcome === "BLOCKED") {
|
|
1527
1540
|
const reason = /(^|\n)TRUE_BLOCKER\s*:\s*user-decision\s*:/iu.test(text)
|
|
1528
1541
|
? "awaiting_user"
|
|
@@ -1535,93 +1548,167 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1535
1548
|
rootAcceptanceContinuity.delete(sessionID);
|
|
1536
1549
|
return { outcome, goal, receipt };
|
|
1537
1550
|
}
|
|
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) {
|
|
1551
|
+
function goalDeclarationContract(prompt) {
|
|
1551
1552
|
const lines = prompt.split(/\r?\n/u);
|
|
1552
1553
|
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;
|
|
1554
|
+
if (criterionStarts.length === 0) {
|
|
1555
|
+
return { defects: [contractDefect("contract", "/goal_acceptance/criteria", "goal_criteria_missing")] };
|
|
1562
1556
|
}
|
|
1563
|
-
const
|
|
1557
|
+
const validation = handoffValue(handoffEntries(prompt), ["validation"]);
|
|
1564
1558
|
const required = ["goal_criterion_id", "goal_target", "goal_entrypoint", "goal_workload",
|
|
1565
1559
|
"goal_oracle_coverage", "goal_build_boundary", "goal_source", "goal_candidate", "goal_fixture",
|
|
1566
1560
|
"goal_proof_scope", "goal_expected_outcome"];
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1561
|
+
const criteria = [];
|
|
1562
|
+
const defects = [];
|
|
1563
|
+
for (const [criterionIndex, start] of criterionStarts.entries()) {
|
|
1564
|
+
const block = lines.slice(start, criterionStarts[criterionIndex + 1]).join("\n") +
|
|
1565
|
+
(validation === undefined ? "" : `\nvalidation: ${validation}`);
|
|
1566
|
+
const entries = handoffEntries(block);
|
|
1567
|
+
const values = Object.fromEntries(required.map((key) => [key, handoffValue(entries, [key])]));
|
|
1568
|
+
const pointer = (field) => `/goal_acceptance/criteria/${criterionIndex}/${field}`;
|
|
1569
|
+
for (const key of required) {
|
|
1570
|
+
if (key !== "goal_oracle_coverage" && values[key] === undefined) {
|
|
1571
|
+
defects.push(contractDefect("contract", pointer(key), "goal_field_missing"));
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const inlineOracle = values.goal_oracle_coverage;
|
|
1575
|
+
let oracleCoverage;
|
|
1576
|
+
try {
|
|
1577
|
+
const parsed = inlineOracle === undefined
|
|
1578
|
+
? taskAcceptanceCriteria(block, "goal_oracle_coverage")
|
|
1579
|
+
: JSON.parse(inlineOracle);
|
|
1580
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string" && value.length > 0 && value.length <= 512) &&
|
|
1581
|
+
new Set(parsed).size === parsed.length)
|
|
1582
|
+
oracleCoverage = parsed;
|
|
1583
|
+
}
|
|
1584
|
+
catch { /* concrete defect below */ }
|
|
1585
|
+
if (oracleCoverage === undefined) {
|
|
1586
|
+
defects.push(contractDefect("contract", pointer("goal_oracle_coverage"), inlineOracle === undefined ? "goal_field_missing_or_malformed" : "goal_oracle_coverage_invalid"));
|
|
1587
|
+
}
|
|
1588
|
+
const buildBoundary = values.goal_build_boundary;
|
|
1589
|
+
const proofScope = values.goal_proof_scope;
|
|
1590
|
+
const expectedOutcome = values.goal_expected_outcome;
|
|
1591
|
+
const sourceBinding = handoffValue(entries, ["goal_source_binding"]);
|
|
1592
|
+
const candidateBinding = handoffValue(entries, ["goal_candidate_binding"]);
|
|
1593
|
+
if (buildBoundary !== undefined && buildBoundary !== "included" && buildBoundary !== "excluded" && buildBoundary !== "not-applicable") {
|
|
1594
|
+
defects.push(contractDefect("contract", pointer("goal_build_boundary"), "goal_build_boundary_invalid"));
|
|
1595
|
+
}
|
|
1596
|
+
if (proofScope !== undefined && proofScope !== "requested-full" && proofScope !== "document-deliverable" && proofScope !== "expected-negative") {
|
|
1597
|
+
defects.push(contractDefect("contract", pointer("goal_proof_scope"), "goal_proof_scope_invalid"));
|
|
1598
|
+
}
|
|
1599
|
+
if (expectedOutcome !== undefined && expectedOutcome !== "pass" && expectedOutcome !== "fail") {
|
|
1600
|
+
defects.push(contractDefect("contract", pointer("goal_expected_outcome"), "goal_expected_outcome_invalid"));
|
|
1601
|
+
}
|
|
1602
|
+
if (sourceBinding !== undefined && sourceBinding !== "declared" && sourceBinding !== "current-protected") {
|
|
1603
|
+
defects.push(contractDefect("contract", pointer("goal_source_binding"), "goal_source_binding_invalid"));
|
|
1604
|
+
}
|
|
1605
|
+
if (candidateBinding !== undefined && candidateBinding !== "declared" && candidateBinding !== "current-protected") {
|
|
1606
|
+
defects.push(contractDefect("contract", pointer("goal_candidate_binding"), "goal_candidate_binding_invalid"));
|
|
1607
|
+
}
|
|
1608
|
+
const declaredValidation = handoffValue(entries, ["validation"]);
|
|
1609
|
+
const structuredCommands = declaredValidation?.startsWith("{") && declaredValidation.endsWith("}")
|
|
1610
|
+
? [...declaredValidation.matchAll(/(?:\{|,)\s*command\s*:\s*([^,}]+)/gu)] : [];
|
|
1611
|
+
const validationCommand = handoffValue(entries, ["goal_validation_command"]) ??
|
|
1612
|
+
(structuredCommands.length === 1 ? unquoteValue(structuredCommands[0][1].trim()) : undefined);
|
|
1613
|
+
if (validationCommand === undefined || normalizeCommand(validationCommand).length === 0) {
|
|
1614
|
+
defects.push(contractDefect("contract", pointer("goal_validation_command"), "goal_validation_command_missing"));
|
|
1615
|
+
}
|
|
1616
|
+
if (defects.some((defect) => defect.includes(`/criteria/${criterionIndex}/`)))
|
|
1617
|
+
continue;
|
|
1618
|
+
criteria.push({ criterion_id: values.goal_criterion_id, target: values.goal_target,
|
|
1619
|
+
entrypoint: values.goal_entrypoint, workload: values.goal_workload, oracle_coverage: oracleCoverage,
|
|
1620
|
+
build_boundary: buildBoundary,
|
|
1621
|
+
source: values.goal_source, candidate: values.goal_candidate,
|
|
1622
|
+
source_binding: (sourceBinding ?? "declared"),
|
|
1623
|
+
candidate_binding: (candidateBinding ?? "declared"),
|
|
1624
|
+
validation_command: normalizeCommand(validationCommand), fixture: values.goal_fixture,
|
|
1625
|
+
proof_scope: proofScope,
|
|
1626
|
+
expected_outcome: expectedOutcome });
|
|
1627
|
+
}
|
|
1628
|
+
const duplicateIDs = criteria.map(({ criterion_id }) => criterion_id)
|
|
1629
|
+
.filter((id, index, all) => all.indexOf(id) !== index);
|
|
1630
|
+
if (duplicateIDs.length > 0) {
|
|
1631
|
+
defects.push(contractDefect("contract", "/goal_acceptance/criteria", "goal_criterion_id_duplicate"));
|
|
1632
|
+
}
|
|
1633
|
+
return defects.length === 0 ? { contract: { criteria }, defects } : { defects };
|
|
1634
|
+
}
|
|
1635
|
+
function validateGoalDeclaration(prompt) {
|
|
1636
|
+
const entries = handoffEntries(prompt);
|
|
1637
|
+
const defects = [];
|
|
1638
|
+
const fingerprint = handoffValue(entries, ["goal_acceptance_fingerprint"]);
|
|
1639
|
+
if (fingerprint === undefined)
|
|
1640
|
+
defects.push(contractDefect("contract", "/goal_acceptance_fingerprint", "goal_fingerprint_missing"));
|
|
1641
|
+
else if (!/^sha256:[a-f0-9]{64}$/u.test(fingerprint)) {
|
|
1642
|
+
defects.push(contractDefect("contract", "/goal_acceptance_fingerprint", "goal_fingerprint_format"));
|
|
1577
1643
|
}
|
|
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
|
|
1644
|
+
const intent = handoffValue(entries, ["delivery_intent"]);
|
|
1645
|
+
const intents = ["design", "registration", "implementation", "repair", "controlled-change"];
|
|
1646
|
+
if (intent === undefined)
|
|
1647
|
+
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_missing"));
|
|
1648
|
+
else if (!intents.includes(intent)) {
|
|
1649
|
+
defects.push(contractDefect("contract", "/delivery_intent", "delivery_intent_invalid"));
|
|
1650
|
+
}
|
|
1651
|
+
const mode = handoffValue(entries, ["delivery_mode"]);
|
|
1652
|
+
const modes = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
|
|
1653
|
+
if (mode !== undefined && !modes.includes(mode)) {
|
|
1654
|
+
defects.push(contractDefect("contract", "/delivery_mode", "delivery_mode_invalid"));
|
|
1655
|
+
}
|
|
1656
|
+
const usable = handoffValue(entries, ["usable_path_established"]);
|
|
1657
|
+
const controlled = handoffValue(entries, ["controlled_change"]);
|
|
1658
|
+
for (const [field, value] of [["usable_path_established", usable], ["controlled_change", controlled]]) {
|
|
1659
|
+
if (value === undefined)
|
|
1660
|
+
defects.push(contractDefect("contract", `/${field}`, "goal_boolean_missing"));
|
|
1661
|
+
else if (value !== "true" && value !== "false")
|
|
1662
|
+
defects.push(contractDefect("contract", `/${field}`, "goal_boolean_invalid"));
|
|
1663
|
+
}
|
|
1664
|
+
const contract = goalDeclarationContract(prompt);
|
|
1665
|
+
defects.push(...contract.defects);
|
|
1666
|
+
if (defects.length > 0 || fingerprint === undefined || intent === undefined || contract.contract === undefined)
|
|
1667
|
+
return { defects };
|
|
1668
|
+
const delivery = mode ?? selectGoalDelivery({
|
|
1669
|
+
declared_intent: intent, requested_usable_path_established: usable === "true",
|
|
1670
|
+
irreversible_or_major_scope: controlled === "true",
|
|
1671
|
+
});
|
|
1672
|
+
return { declaration: { fingerprint, delivery, contract: contract.contract }, defects };
|
|
1673
|
+
}
|
|
1674
|
+
async function bindGoalDeclaration(sessionID, prompt) {
|
|
1609
1675
|
const ledger = await goalLedger(sessionID);
|
|
1610
1676
|
let state = (await ledger.readGoal()).state;
|
|
1611
1677
|
if (state.goal_id === null || state.origin_user_message_id === null)
|
|
1612
1678
|
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
1679
|
const entries = handoffEntries(prompt);
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1680
|
+
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));
|
|
1681
|
+
// Accepted goals may retain their existing declaration on later units, but the first worker
|
|
1682
|
+
// handoff and every handoff containing declaration fields must be complete.
|
|
1683
|
+
if (!typedDeclarationPresent) {
|
|
1684
|
+
if (state.acceptance_contract === null && goalDeclarationAuthority.get(sessionID) === state.latest_user_message_id) {
|
|
1685
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", {
|
|
1686
|
+
defects: validateGoalDeclaration(prompt).defects,
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
if (goalDeclarationAuthority.get(sessionID) === state.latest_user_message_id)
|
|
1690
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1691
|
+
return state;
|
|
1692
|
+
}
|
|
1693
|
+
const validated = validateGoalDeclaration(prompt);
|
|
1694
|
+
if (validated.declaration === undefined) {
|
|
1695
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: validated.defects });
|
|
1696
|
+
}
|
|
1697
|
+
const declaration = validated.declaration;
|
|
1698
|
+
const authority = goalDeclarationAuthority.get(sessionID);
|
|
1699
|
+
if (authority === undefined || authority !== state.latest_user_message_id) {
|
|
1700
|
+
if (declaration.fingerprint !== state.acceptance_fingerprint) {
|
|
1701
|
+
throw new HandoffDeniedError("contract-invalid", "<goal-declaration>", { defects: [
|
|
1702
|
+
contractDefect("contract", "/goal_acceptance_fingerprint", "goal_revision_unauthorized"),
|
|
1703
|
+
] });
|
|
1704
|
+
}
|
|
1620
1705
|
return state;
|
|
1621
|
-
|
|
1622
|
-
if (
|
|
1623
|
-
|
|
1706
|
+
}
|
|
1707
|
+
if (declaration.fingerprint === state.acceptance_fingerprint &&
|
|
1708
|
+
goalFingerprint(declaration.contract) === goalFingerprint(state.acceptance_contract)) {
|
|
1709
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1624
1710
|
return state;
|
|
1711
|
+
}
|
|
1625
1712
|
const units = Number(handoffValue(entries, ["goal_budget_units"]));
|
|
1626
1713
|
const maxUnits = Number.isSafeInteger(units) && units >= state.consumed_units && units > 0
|
|
1627
1714
|
? units : state.budget?.max_units ?? 32;
|
|
@@ -1631,21 +1718,25 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1631
1718
|
const costBudget = Number.isFinite(declaredCost) && declaredCost > 0 ? declaredCost : state.budget?.cost_usd ?? null;
|
|
1632
1719
|
state = await ledger.appendGoal({ kind: "goal.revised", at: new Date().toISOString(), goal_id: state.goal_id,
|
|
1633
1720
|
revision: state.revision + 1, scope_epoch: state.scope_epoch + 1,
|
|
1634
|
-
acceptance_fingerprint:
|
|
1721
|
+
acceptance_fingerprint: declaration.fingerprint, origin_user_message_id: state.latest_user_message_id,
|
|
1635
1722
|
session_id: sessionID, selected_agent: state.selected_agent ?? COORDINATOR_AGENT,
|
|
1636
|
-
delivery:
|
|
1723
|
+
delivery: declaration.delivery, budget: { max_units: maxUnits,
|
|
1637
1724
|
time_ms: timeBudget, cost_usd: costBudget,
|
|
1638
1725
|
source: Number.isSafeInteger(units) ? "accepted-plan" : state.budget?.source ?? "policy-default" },
|
|
1639
|
-
acceptance_contract:
|
|
1726
|
+
acceptance_contract: declaration.contract });
|
|
1727
|
+
goalDeclarationAuthority.delete(sessionID);
|
|
1640
1728
|
return state;
|
|
1641
1729
|
}
|
|
1642
|
-
async function reserveGoalDispatch(sessionID, callID, prompt
|
|
1730
|
+
async function reserveGoalDispatch(sessionID, callID, prompt) {
|
|
1643
1731
|
if (goalReservations.has(callID))
|
|
1644
1732
|
return;
|
|
1645
1733
|
const ledger = await goalLedger(sessionID);
|
|
1646
|
-
let state = await bindGoalDeclaration(sessionID, prompt
|
|
1734
|
+
let state = await bindGoalDeclaration(sessionID, prompt) ?? (await ledger.readGoal()).state;
|
|
1647
1735
|
if (state.goal_id === null)
|
|
1648
1736
|
return; // Legacy already-authorized roots may settle without inventing authority.
|
|
1737
|
+
if (state.phase === "terminal" || state.receipt !== null) {
|
|
1738
|
+
throw new Error("SORTIE_GOAL_CONTROL_DENIED: terminal");
|
|
1739
|
+
}
|
|
1649
1740
|
if (state.replan_required) {
|
|
1650
1741
|
if (state.replan_used) {
|
|
1651
1742
|
await terminalGoal(sessionID, "stop_no_progress", "stopped");
|
|
@@ -1735,10 +1826,18 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
1735
1826
|
entry.execution.units.includes(reservation.unitID));
|
|
1736
1827
|
const newEvidence = acceptedEvidence.filter((entry) => entry.measurement.criterion_ids.some((criterionID) => !state.satisfied_criteria.includes(criterionID)));
|
|
1737
1828
|
const progress = newEvidence.length > 0;
|
|
1829
|
+
const metadata = isRecord(output.metadata) ? output.metadata : undefined;
|
|
1830
|
+
const interrupted = metadata?.status === "cancel" || metadata?.status === "cancelled" ||
|
|
1831
|
+
output.status === "cancel" || output.status === "cancelled";
|
|
1832
|
+
const hostBindingDefect = childSessionID !== undefined && [...(bindingDenials.get(reservation.root)?.values() ?? [])]
|
|
1833
|
+
.some((candidateDenials) => [...candidateDenials.values()].includes(childSessionID));
|
|
1834
|
+
const processDefect = childSessionID === undefined || hostBindingDefect;
|
|
1835
|
+
const resultClass = progress ? "acceptance" : interrupted ? "interrupted" : processDefect ? "process-defect" : "acceptance";
|
|
1738
1836
|
await ledger.appendGoal({ kind: "unit.settled", at: new Date().toISOString(),
|
|
1739
1837
|
reservation_id: reservation.reservationID, receipt_id: goalFingerprint({ call_id: callID, output: outputText.slice(0, 2048) }),
|
|
1740
1838
|
goal_id: state.goal_id, unit_id: reservation.unitID,
|
|
1741
|
-
disposition: progress ? "succeeded" : "
|
|
1839
|
+
disposition: progress ? "succeeded" : interrupted ? "cancelled" : "failed", result_class: resultClass,
|
|
1840
|
+
progress_fingerprint: progress ? goalFingerprint(acceptedEvidence) : null,
|
|
1742
1841
|
evidence: acceptedEvidence, elapsed_ms: Math.max(0, Date.now() - reservation.started), cost_usd: null });
|
|
1743
1842
|
if (childSessionID !== undefined) {
|
|
1744
1843
|
for (const [executionCallID, execution] of hostGoalExecutions) {
|
|
@@ -5160,7 +5259,13 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
5160
5259
|
: await terminalGoalFromHostText(textInput.sessionID, textOutput.text);
|
|
5161
5260
|
if (runOutcome === "DONE" && terminal?.goal !== undefined &&
|
|
5162
5261
|
terminal.goal.acceptance_contract !== null && terminal.receipt === undefined) {
|
|
5163
|
-
textOutput.text = textOutput.text
|
|
5262
|
+
textOutput.text = replaceDoneTerminalStatus(textOutput.text, "status: IN_PROGRESS\ngoal_control: accepted criteria remain unproved");
|
|
5263
|
+
}
|
|
5264
|
+
if (runOutcome !== "DONE" && terminal?.receipt?.status === "succeeded") {
|
|
5265
|
+
textOutput.text = replaceTerminalStatus(textOutput.text, "status: DONE");
|
|
5266
|
+
}
|
|
5267
|
+
if (runOutcome !== undefined && isCoordinatorSession(textInput.sessionID)) {
|
|
5268
|
+
textOutput.text = sanitizeTerminalReport(textOutput.text);
|
|
5164
5269
|
}
|
|
5165
5270
|
if ((isCoordinatorSession(textInput.sessionID) || await recoverCoordinatorRoot(textInput.sessionID)) &&
|
|
5166
5271
|
runOutcome !== undefined) {
|
|
@@ -6004,6 +6109,12 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6004
6109
|
fastLane.enableParallelDispatch(toolInput.sessionID, boundSnapshot.max_workers, counts.dispatched - currentContribution, counts.running - currentContribution, counts.total);
|
|
6005
6110
|
parallelWorkerAuthorized = true;
|
|
6006
6111
|
}
|
|
6112
|
+
if (toolInput.tool === "task" && taskRole !== undefined && IMPLEMENTATION_AGENTS.has(taskRole) &&
|
|
6113
|
+
isRecord(output.args)) {
|
|
6114
|
+
// Declaration admission precedes routing state and reservation. A concrete field denial can
|
|
6115
|
+
// therefore be repaired by a corrected Task call in this same coordinator turn.
|
|
6116
|
+
await bindGoalDeclaration(toolInput.sessionID, typeof output.args.prompt === "string" ? output.args.prompt : "");
|
|
6117
|
+
}
|
|
6007
6118
|
const resumedWorkerSessionID = fastLane.beforeTool(toolInput.sessionID, toolInput.tool, output.args, {
|
|
6008
6119
|
readonlyDiagnosisAuthorized: readonlyDiagnosis,
|
|
6009
6120
|
consultationFallbackAuthorized,
|
|
@@ -6012,7 +6123,7 @@ export const SortieDogsPlugin = async (input, options) => {
|
|
|
6012
6123
|
});
|
|
6013
6124
|
if (toolInput.tool === "task" && taskRole !== undefined && IMPLEMENTATION_AGENTS.has(taskRole) &&
|
|
6014
6125
|
isRecord(output.args)) {
|
|
6015
|
-
await reserveGoalDispatch(toolInput.sessionID, toolInput.callID, typeof output.args.prompt === "string" ? output.args.prompt : ""
|
|
6126
|
+
await reserveGoalDispatch(toolInput.sessionID, toolInput.callID, typeof output.args.prompt === "string" ? output.args.prompt : "");
|
|
6016
6127
|
}
|
|
6017
6128
|
if (validatedRootAcceptance !== undefined && reservedParallelDescriptor === undefined) {
|
|
6018
6129
|
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 {};
|
|
@@ -18,14 +18,20 @@ export function createSortieResult(receipt, goal, metrics, asOf = receipt.ended_
|
|
|
18
18
|
const hostMetric = (value) => value === undefined
|
|
19
19
|
? unavailable(metrics === undefined ? "host-metrics-unavailable" : "incomplete-host-coverage")
|
|
20
20
|
: available(value, "host-reported");
|
|
21
|
-
const
|
|
21
|
+
const missionStatus = receipt.status === "succeeded"
|
|
22
|
+
? "COMPLETED"
|
|
23
|
+
: receipt.stop_reason === "awaiting_user"
|
|
24
|
+
? "USER_DECISION"
|
|
25
|
+
: receipt.stop_reason === "external_dependency" || receipt.stop_reason === "persistence_unavailable"
|
|
26
|
+
? "EXTERNAL_BLOCKER"
|
|
27
|
+
: "INTERRUPTED";
|
|
22
28
|
return {
|
|
23
29
|
schema_version: "0.1",
|
|
24
30
|
result_id: [receipt.goal_id, receipt.terminal_revision],
|
|
25
31
|
accounting_phase: "pre-terminal",
|
|
26
32
|
as_of: asOf,
|
|
27
33
|
mission: {
|
|
28
|
-
status:
|
|
34
|
+
status: missionStatus,
|
|
29
35
|
stop_reason: receipt.stop_reason,
|
|
30
36
|
},
|
|
31
37
|
speed: {
|
|
@@ -105,11 +111,11 @@ function assistantMessages(value) {
|
|
|
105
111
|
});
|
|
106
112
|
}
|
|
107
113
|
function conclusionStatusAlias(line) {
|
|
108
|
-
const match = /^(
|
|
114
|
+
const match = /^(✅|⚠️|⛔|❓)[ \t]+conclusion:\s*status:\s*(DONE|INTERRUPTED|BLOCKED|NEED_DECISION)\b/iu.exec(line);
|
|
109
115
|
const outcome = match?.[2]?.toUpperCase();
|
|
110
|
-
if (outcome !== "DONE" && outcome !== "BLOCKED" && outcome !== "NEED_DECISION")
|
|
116
|
+
if (outcome !== "DONE" && outcome !== "INTERRUPTED" && outcome !== "BLOCKED" && outcome !== "NEED_DECISION")
|
|
111
117
|
return undefined;
|
|
112
|
-
const expectedIcon = outcome === "DONE" ? "✅" : outcome === "BLOCKED" ? "⛔" : "❓";
|
|
118
|
+
const expectedIcon = outcome === "DONE" ? "✅" : outcome === "INTERRUPTED" ? "⚠️" : outcome === "BLOCKED" ? "⛔" : "❓";
|
|
113
119
|
return match?.[1] === expectedIcon ? outcome : undefined;
|
|
114
120
|
}
|
|
115
121
|
export async function collectRunMetrics(client, rootSessionID, directory, now = Date.now(), window) {
|
|
@@ -287,18 +293,22 @@ function duration(milliseconds) {
|
|
|
287
293
|
return `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`;
|
|
288
294
|
}
|
|
289
295
|
function metricText(metric, render) {
|
|
290
|
-
return metric.availability === "available" ? render(metric.value) :
|
|
296
|
+
return metric.availability === "available" ? render(metric.value) : "計測不可";
|
|
291
297
|
}
|
|
292
298
|
export function formatSortieResult(result) {
|
|
293
299
|
const criteria = metricText(result.proof.criteria, (entries) => {
|
|
294
300
|
const passing = entries.filter(({ status }) => status === "PASS").length;
|
|
295
|
-
return `${passing}/${entries.length}
|
|
301
|
+
return `${passing}/${entries.length}`;
|
|
296
302
|
});
|
|
303
|
+
const achievement = result.mission.status === "COMPLETED" ? "完了"
|
|
304
|
+
: result.mission.status === "INTERRUPTED" ? "中断(未完了)"
|
|
305
|
+
: result.mission.status === "EXTERNAL_BLOCKER" ? "外部要因で未完了"
|
|
306
|
+
: "ユーザー判断待ち(未完了)";
|
|
297
307
|
return [
|
|
298
308
|
"**Sortie Result**",
|
|
299
|
-
`**Speed:**
|
|
300
|
-
`**Cost:** ${metricText(result.cost.total_tokens, (value) => `${value.toLocaleString("
|
|
301
|
-
|
|
309
|
+
`**Speed:** 全体 ${metricText(result.speed.goal_wall_ms, duration)} · worker ${metricText(result.speed.worker_execution_ms, duration)}`,
|
|
310
|
+
`**Cost:** ${metricText(result.cost.total_tokens, (value) => `${value.toLocaleString("ja-JP")}トークン`)} · ${metricText(result.cost.cost_usd, (value) => `$${value.toFixed(4)}`)}`,
|
|
311
|
+
`**達成:** ${achievement} · acceptance ${criteria}`,
|
|
302
312
|
].join("\n");
|
|
303
313
|
}
|
|
304
314
|
export function formatRunMetrics(metrics) {
|
|
@@ -337,15 +347,16 @@ function terminalCheckpoint(text) {
|
|
|
337
347
|
return undefined;
|
|
338
348
|
const checkpoint = (() => {
|
|
339
349
|
const { index, line } = first;
|
|
340
|
-
const normalized = /^status:\s*(DONE|BLOCKED|NEED_DECISION)\b/iu.exec(line)?.[1]?.toUpperCase();
|
|
341
|
-
const explicit = normalized === "DONE" || normalized === "BLOCKED" || normalized === "NEED_DECISION"
|
|
350
|
+
const normalized = /^status:\s*(DONE|INTERRUPTED|BLOCKED|NEED_DECISION)\b/iu.exec(line)?.[1]?.toUpperCase();
|
|
351
|
+
const explicit = normalized === "DONE" || normalized === "INTERRUPTED" || normalized === "BLOCKED" || normalized === "NEED_DECISION"
|
|
342
352
|
? normalized
|
|
343
353
|
: undefined;
|
|
344
354
|
const outcome = explicit ?? conclusionStatusAlias(line) ??
|
|
345
355
|
(/^✅[ \t]+\*\*DONE\*\*/u.test(line) ? "DONE" :
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
356
|
+
/^⚠️[ \t]+\*\*INTERRUPTED\*\*/u.test(line) ? "INTERRUPTED" :
|
|
357
|
+
/^⛔[ \t]+\*\*BLOCKED\*\*/u.test(line) ? "BLOCKED" :
|
|
358
|
+
/^❓[ \t]+\*\*NEED_DECISION\*\*/u.test(line) ? "NEED_DECISION" : undefined);
|
|
359
|
+
return outcome === "DONE" || outcome === "INTERRUPTED" || outcome === "BLOCKED" || outcome === "NEED_DECISION"
|
|
349
360
|
? { index, outcome }
|
|
350
361
|
: undefined;
|
|
351
362
|
})();
|
|
@@ -365,25 +376,49 @@ export function terminalRunOutcome(text) {
|
|
|
365
376
|
? "BLOCKED"
|
|
366
377
|
: undefined;
|
|
367
378
|
}
|
|
368
|
-
export function
|
|
379
|
+
export function replaceTerminalStatus(text, replacement) {
|
|
369
380
|
const checkpoint = terminalCheckpoint(text);
|
|
370
|
-
if (checkpoint
|
|
371
|
-
return text;
|
|
372
|
-
if (topLevelLines(text).some(({ index, line }) => index > checkpoint.index && /^\*\*Run:\*\*/u.test(line)))
|
|
381
|
+
if (checkpoint === undefined)
|
|
373
382
|
return text;
|
|
374
383
|
const newline = text.includes("\r\n") ? "\r\n" : "\n";
|
|
375
384
|
const lines = text.split(/\r?\n/u);
|
|
385
|
+
lines.splice(checkpoint.index, 1, ...replacement.split(/\r?\n/u));
|
|
386
|
+
return lines.join(newline);
|
|
387
|
+
}
|
|
388
|
+
export function replaceDoneTerminalStatus(text, replacement) {
|
|
389
|
+
return terminalCheckpoint(text)?.outcome === "DONE" ? replaceTerminalStatus(text, replacement) : text;
|
|
390
|
+
}
|
|
391
|
+
export function sanitizeTerminalReport(text) {
|
|
392
|
+
const newline = text.includes("\r\n") ? "\r\n" : "\n";
|
|
393
|
+
const internal = /\b(?:evidence_refs?|manifest|raw|raw_status|reason_code|goal_control|TRUE_BLOCKER)\s*:|\bEvidence\b/iu;
|
|
394
|
+
return text.replace(/^[ \t]*(`{3,}|~{3,})[^\r\n]*\r?\n([\s\S]*?)^[ \t]*\1[ \t]*$/gimu, (block, _fence, body) => internal.test(body) ? "" : block)
|
|
395
|
+
.replace(/<details\b[^>]*>[\s\S]*?<\/details>/giu, "")
|
|
396
|
+
.split(/\r?\n/u)
|
|
397
|
+
.filter((line) => !/^\s*(?:(?:#{1,6}\s*)?\**Evidence\**\s*:|(?:TRUE_BLOCKER|goal_control|evidence_refs?|reason_code|raw|raw_status|manifest)\s*:)/iu.test(line))
|
|
398
|
+
.join(newline)
|
|
399
|
+
.trimEnd();
|
|
400
|
+
}
|
|
401
|
+
export function insertRunMetrics(text, metrics) {
|
|
402
|
+
const visible = sanitizeTerminalReport(text);
|
|
403
|
+
const checkpoint = terminalCheckpoint(visible);
|
|
404
|
+
if (checkpoint?.outcome !== "DONE")
|
|
405
|
+
return visible;
|
|
406
|
+
if (topLevelLines(visible).some(({ index, line }) => index > checkpoint.index && /^\*\*Run:\*\*/u.test(line)))
|
|
407
|
+
return visible;
|
|
408
|
+
const newline = visible.includes("\r\n") ? "\r\n" : "\n";
|
|
409
|
+
const lines = visible.split(/\r?\n/u);
|
|
376
410
|
lines.splice(checkpoint.index + 1, 0, "", formatRunMetrics(metrics));
|
|
377
411
|
return lines.join(newline);
|
|
378
412
|
}
|
|
379
413
|
export function insertSortieResult(text, result) {
|
|
380
|
-
const
|
|
414
|
+
const visible = sanitizeTerminalReport(text);
|
|
415
|
+
const checkpoint = terminalCheckpoint(visible);
|
|
381
416
|
if (checkpoint === undefined)
|
|
382
|
-
return
|
|
383
|
-
if (topLevelLines(
|
|
384
|
-
return
|
|
385
|
-
const newline =
|
|
386
|
-
const lines =
|
|
417
|
+
return visible;
|
|
418
|
+
if (topLevelLines(visible).some(({ index, line }) => index > checkpoint.index && /^\*\*Sortie Result\*\*/u.test(line)))
|
|
419
|
+
return visible;
|
|
420
|
+
const newline = visible.includes("\r\n") ? "\r\n" : "\n";
|
|
421
|
+
const lines = visible.split(/\r?\n/u);
|
|
387
422
|
lines.splice(checkpoint.index + 1, 0, "", formatSortieResult(result));
|
|
388
423
|
return lines.join(newline);
|
|
389
424
|
}
|