@claudexor/control-api 3.1.1 → 3.2.0
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/dist/authorization.d.ts +3 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +15 -0
- package/dist/authorization.js.map +1 -0
- package/dist/binary-response.d.ts +5 -0
- package/dist/binary-response.d.ts.map +1 -0
- package/dist/binary-response.js +31 -0
- package/dist/binary-response.js.map +1 -0
- package/dist/budget-valuation.d.ts +23 -0
- package/dist/budget-valuation.d.ts.map +1 -0
- package/dist/budget-valuation.js +90 -0
- package/dist/budget-valuation.js.map +1 -0
- package/dist/catalog-query.d.ts +310 -0
- package/dist/catalog-query.d.ts.map +1 -0
- package/dist/catalog-query.js +36 -0
- package/dist/catalog-query.js.map +1 -0
- package/dist/daemon-server.d.ts +33 -79
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +175 -281
- package/dist/daemon-server.js.map +1 -1
- package/dist/decision-accept-risk.d.ts +36 -0
- package/dist/decision-accept-risk.d.ts.map +1 -0
- package/dist/decision-accept-risk.js +79 -0
- package/dist/decision-accept-risk.js.map +1 -0
- package/dist/decision-rerun.d.ts +2 -1
- package/dist/decision-rerun.d.ts.map +1 -1
- package/dist/decision-rerun.js +79 -48
- package/dist/decision-rerun.js.map +1 -1
- package/dist/delegation-control.d.ts +23 -0
- package/dist/delegation-control.d.ts.map +1 -0
- package/dist/delegation-control.js +81 -0
- package/dist/delegation-control.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/maintenance-routes.d.ts +2 -2
- package/dist/maintenance-routes.d.ts.map +1 -1
- package/dist/maintenance-routes.js +11 -10
- package/dist/maintenance-routes.js.map +1 -1
- package/dist/operation-catalog.d.ts +9 -9
- package/dist/operation-catalog.d.ts.map +1 -1
- package/dist/operation-catalog.js +22 -79
- package/dist/operation-catalog.js.map +1 -1
- package/dist/operation-draft.d.ts +3 -0
- package/dist/operation-draft.d.ts.map +1 -0
- package/dist/operation-draft.js +2 -0
- package/dist/operation-draft.js.map +1 -0
- package/dist/operation-parameters.d.ts.map +1 -1
- package/dist/operation-summaries.d.ts +3 -0
- package/dist/operation-summaries.d.ts.map +1 -0
- package/dist/operation-summaries.js +75 -0
- package/dist/operation-summaries.js.map +1 -0
- package/dist/primary-output.d.ts +3 -3
- package/dist/primary-output.d.ts.map +1 -1
- package/dist/primary-output.js +70 -19
- package/dist/primary-output.js.map +1 -1
- package/dist/problem-response.d.ts.map +1 -1
- package/dist/problem-response.js +14 -9
- package/dist/problem-response.js.map +1 -1
- package/dist/project-routes.d.ts +21 -13
- package/dist/project-routes.d.ts.map +1 -1
- package/dist/project-routes.js +80 -34
- package/dist/project-routes.js.map +1 -1
- package/dist/remote-operation-descriptors.d.ts +36 -0
- package/dist/remote-operation-descriptors.d.ts.map +1 -0
- package/dist/remote-operation-descriptors.js +35 -0
- package/dist/remote-operation-descriptors.js.map +1 -0
- package/dist/resource-routes.d.ts +4 -3
- package/dist/resource-routes.d.ts.map +1 -1
- package/dist/resource-routes.js +63 -39
- package/dist/resource-routes.js.map +1 -1
- package/dist/route-stages.d.ts +15 -0
- package/dist/route-stages.d.ts.map +1 -0
- package/dist/route-stages.js +24 -0
- package/dist/route-stages.js.map +1 -0
- package/dist/run-facts-projection.d.ts +35 -0
- package/dist/run-facts-projection.d.ts.map +1 -0
- package/dist/run-facts-projection.js +133 -0
- package/dist/run-facts-projection.js.map +1 -0
- package/dist/run-list-fingerprint.d.ts +7 -0
- package/dist/run-list-fingerprint.d.ts.map +1 -0
- package/dist/run-list-fingerprint.js +68 -0
- package/dist/run-list-fingerprint.js.map +1 -0
- package/dist/run-record.d.ts +53 -0
- package/dist/run-record.d.ts.map +1 -0
- package/dist/run-record.js +61 -0
- package/dist/run-record.js.map +1 -0
- package/dist/run-retry-routes.d.ts +3 -2
- package/dist/run-retry-routes.d.ts.map +1 -1
- package/dist/run-retry-routes.js +129 -48
- package/dist/run-retry-routes.js.map +1 -1
- package/dist/run-start.d.ts +13 -1
- package/dist/run-start.d.ts.map +1 -1
- package/dist/run-start.js +90 -18
- package/dist/run-start.js.map +1 -1
- package/dist/run-timeline.d.ts.map +1 -1
- package/dist/run-timeline.js +17 -9
- package/dist/run-timeline.js.map +1 -1
- package/dist/thread-lifecycle-routes.d.ts +1 -1
- package/dist/thread-lifecycle-routes.d.ts.map +1 -1
- package/dist/thread-lifecycle-routes.js +3 -4
- package/dist/thread-lifecycle-routes.js.map +1 -1
- package/dist/thread-mutation.d.ts +6 -0
- package/dist/thread-mutation.d.ts.map +1 -1
- package/dist/thread-mutation.js +23 -0
- package/dist/thread-mutation.js.map +1 -1
- package/dist/thread-projection.d.ts +6 -1
- package/dist/thread-projection.d.ts.map +1 -1
- package/dist/thread-projection.js +28 -1
- package/dist/thread-projection.js.map +1 -1
- package/dist/thread-recovery.d.ts +54 -0
- package/dist/thread-recovery.d.ts.map +1 -0
- package/dist/thread-recovery.js +71 -0
- package/dist/thread-recovery.js.map +1 -0
- package/dist/thread-turn-routes.d.ts +22 -8
- package/dist/thread-turn-routes.d.ts.map +1 -1
- package/dist/thread-turn-routes.js +73 -49
- package/dist/thread-turn-routes.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isTerminalLifecycle, RunFactsInvalidError, validateRunFactsReceipt, requiredActionsFor, } from "@claudexor/schema";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { safeArtifactPath, safeArtifactRoot } from "./artifact-paths.js";
|
|
6
|
+
/** Bind a canonical receipt to its daemon record without asserting a racy active lifecycle. */
|
|
7
|
+
export function expectedRunFacts(record) {
|
|
8
|
+
return {
|
|
9
|
+
runId: record.runId ?? record.id,
|
|
10
|
+
...(record.taskId ? { taskId: record.taskId } : {}),
|
|
11
|
+
...(isTerminalLifecycle(record.state)
|
|
12
|
+
? { lifecycle: record.state }
|
|
13
|
+
: {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Single terminal-facts accessor: a cross-field-valid RunFacts receipt wins;
|
|
18
|
+
* old/in-flight runs with no receipt retain legacy projections. A receipt that
|
|
19
|
+
* is present but invalid fails loudly and can never reopen legacy apply truth.
|
|
20
|
+
*/
|
|
21
|
+
export function effectiveTerminalFacts(runDir, legacyOutcomeFacts, legacyDecision = null, expected = {}) {
|
|
22
|
+
const runFacts = readRunFacts(runDir, expected);
|
|
23
|
+
const outcomeFacts = runFacts?.outcome ?? legacyOutcomeFacts;
|
|
24
|
+
return {
|
|
25
|
+
runFacts,
|
|
26
|
+
outcomeFacts,
|
|
27
|
+
decision: legacyDecision && outcomeFacts ? { ...legacyDecision, facts: outcomeFacts } : legacyDecision,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Project terminal compatibility fields from one canonical receipt read.
|
|
32
|
+
* Missing receipts use legacy derivations for in-flight and pre-RunFacts runs.
|
|
33
|
+
* Present-but-invalid receipts fail loudly.
|
|
34
|
+
*/
|
|
35
|
+
export function projectRunFactsForDetail(runDir, legacyOutcomeFacts, hasValidOperatorDecision, legacyApplyEligibility, expected = {}) {
|
|
36
|
+
const { runFacts, outcomeFacts } = effectiveTerminalFacts(runDir, legacyOutcomeFacts, null, expected);
|
|
37
|
+
if (runFacts) {
|
|
38
|
+
// Delivery state and operator decisions are the two legitimate
|
|
39
|
+
// post-terminal overlays. Re-derive only their mutable compatibility
|
|
40
|
+
// fields while keeping the receipt's terminal outcome authoritative.
|
|
41
|
+
const hasMutableOverlay = hasValidOperatorDecision || artifactExists(runDir, "final/delivery_state.yaml");
|
|
42
|
+
return {
|
|
43
|
+
runFacts,
|
|
44
|
+
outcomeFacts,
|
|
45
|
+
applyEligibility: hasMutableOverlay ? legacyApplyEligibility() : runFacts.apply.eligibility,
|
|
46
|
+
requiredActions: hasMutableOverlay
|
|
47
|
+
? requiredActionsFor(runFacts.outcome, hasValidOperatorDecision)
|
|
48
|
+
: runFacts.required_actions,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
runFacts: null,
|
|
53
|
+
outcomeFacts: legacyOutcomeFacts,
|
|
54
|
+
applyEligibility: legacyApplyEligibility(),
|
|
55
|
+
requiredActions: requiredActionsFor(legacyOutcomeFacts, hasValidOperatorDecision),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** RunFacts is already redacted; avoid a second presentation-redaction pass. */
|
|
59
|
+
function readRunFacts(runDir, expected) {
|
|
60
|
+
if (!runDir)
|
|
61
|
+
return null;
|
|
62
|
+
const root = safeArtifactRoot(runDir);
|
|
63
|
+
if (!root) {
|
|
64
|
+
try {
|
|
65
|
+
lstatSync(runDir);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code === "ENOENT")
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
throw invalidRunFacts();
|
|
72
|
+
}
|
|
73
|
+
const finalDir = join(root, "final");
|
|
74
|
+
let finalStat;
|
|
75
|
+
try {
|
|
76
|
+
finalStat = lstatSync(finalDir);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === "ENOENT")
|
|
80
|
+
return null;
|
|
81
|
+
throw invalidRunFacts();
|
|
82
|
+
}
|
|
83
|
+
if (finalStat.isSymbolicLink() || !finalStat.isDirectory()) {
|
|
84
|
+
throw invalidRunFacts();
|
|
85
|
+
}
|
|
86
|
+
const path = join(finalDir, "run_facts.yaml");
|
|
87
|
+
let stat;
|
|
88
|
+
try {
|
|
89
|
+
stat = lstatSync(path);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error.code === "ENOENT")
|
|
93
|
+
return null;
|
|
94
|
+
throw invalidRunFacts();
|
|
95
|
+
}
|
|
96
|
+
if (stat.isSymbolicLink() || stat.isDirectory() || !stat.isFile()) {
|
|
97
|
+
throw invalidRunFacts();
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
// Shape + cross-axis invariants + identity binding live in the ONE shared
|
|
101
|
+
// pure owner (@claudexor/schema); only YAML/file reading and the HTTP
|
|
102
|
+
// problem mapping below are control-api's own.
|
|
103
|
+
return validateRunFactsReceipt(parseYaml(readFileSync(path, "utf8")), expected);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw invalidRunFacts();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Transport mapping of the shared refusal: the HTTP status and typed unblock
|
|
110
|
+
* action are control-api's own; message/code/retryable/evidenceRefs come from
|
|
111
|
+
* the ONE shared class in @claudexor/schema — never duplicated here. */
|
|
112
|
+
class RunFactsInvalidHttpError extends RunFactsInvalidError {
|
|
113
|
+
status = 500;
|
|
114
|
+
requiredActions = ["inspect_run_artifacts"];
|
|
115
|
+
}
|
|
116
|
+
function invalidRunFacts() {
|
|
117
|
+
return new RunFactsInvalidHttpError();
|
|
118
|
+
}
|
|
119
|
+
function artifactExists(runDir, relativePath) {
|
|
120
|
+
try {
|
|
121
|
+
if (!runDir)
|
|
122
|
+
return false;
|
|
123
|
+
const path = safeArtifactPath(runDir, relativePath);
|
|
124
|
+
if (!path)
|
|
125
|
+
return false;
|
|
126
|
+
const stat = lstatSync(path);
|
|
127
|
+
return !stat.isSymbolicLink() && !stat.isDirectory();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=run-facts-projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-facts-projection.js","sourceRoot":"","sources":["../src/run-facts-projection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,GAOnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAmBzE,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAAC,MAKhC;IACC,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE;QAChC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC;YACnC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAqC,EAAE;YAC7D,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAA0B,EAC1B,kBAA0C,EAC1C,cAAc,GAA0B,IAAI,EAC5C,QAAQ,GAAqB,EAAE;IAE/B,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,QAAQ,EAAE,OAAO,IAAI,kBAAkB,CAAC;IAC7D,OAAO;QACL,QAAQ;QACR,YAAY;QACZ,QAAQ,EACN,cAAc,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,cAAc;KAC/F,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAA0B,EAC1B,kBAA0C,EAC1C,wBAAiC,EACjC,sBAAqD,EACrD,QAAQ,GAAqB,EAAE;IAE/B,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,sBAAsB,CACvD,MAAM,EACN,kBAAkB,EAClB,IAAI,EACJ,QAAQ,CACT,CAAC;IACF,IAAI,QAAQ,EAAE,CAAC;QACb,+DAA+D;QAC/D,qEAAqE;QACrE,qEAAqE;QACrE,MAAM,iBAAiB,GACrB,wBAAwB,IAAI,cAAc,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;QAClF,OAAO;YACL,QAAQ;YACR,YAAY;YACZ,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW;YAC3F,eAAe,EAAE,iBAAiB;gBAChC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;gBAChE,CAAC,CAAC,QAAQ,CAAC,gBAAgB;SAC9B,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,kBAAkB;QAChC,gBAAgB,EAAE,sBAAsB,EAAE;QAC1C,eAAe,EAAE,kBAAkB,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;KAClF,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,SAAS,YAAY,CAAC,MAA0B,EAAE,QAA0B;IAC1E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC;YACH,SAAS,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;QACtE,CAAC;QACD,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,SAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACpE,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3D,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,IAAkC,CAAC;IACvC,IAAI,CAAC;QACH,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACpE,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAClE,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,CAAC;QACH,0EAA0E;QAC1E,sEAAsE;QACtE,+CAA+C;QAC/C,OAAO,uBAAuB,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,eAAe,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;wEAEwE;AACxE,MAAM,wBAAyB,SAAQ,oBAAoB;IAChD,MAAM,GAAG,GAAG,CAAC;IACb,eAAe,GAAG,CAAC,uBAAuB,CAAC,CAAC;CACtD;AAED,SAAS,eAAe;IACtB,OAAO,IAAI,wBAAwB,EAAE,CAAC;AACxC,CAAC;AAED,SAAS,cAAc,CAAC,MAA0B,EAAE,YAAoB;IACtE,IAAI,CAAC;QACH,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DaemonRunRecord } from "./run-record.js";
|
|
2
|
+
export declare function runListFingerprintProbeCountForTests(): number;
|
|
3
|
+
export declare function resetRunListFingerprintProbeCountForTests(): void;
|
|
4
|
+
/** Fingerprint only the mutable delivery overlay for settled runs; all other
|
|
5
|
+
* terminal artifacts are immutable. Active runs retain the complete set. */
|
|
6
|
+
export declare function summaryFingerprint(rec: DaemonRunRecord): string;
|
|
7
|
+
//# sourceMappingURL=run-list-fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-list-fingerprint.d.ts","sourceRoot":"","sources":["../src/run-list-fingerprint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMvD,wBAAgB,oCAAoC,IAAI,MAAM,CAE7D;AAED,wBAAgB,yCAAyC,IAAI,IAAI,CAEhE;AAED;4EAC4E;AAC5E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAyC/D"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { sha256 } from "@claudexor/util";
|
|
3
|
+
import { safeArtifactPath } from "./artifact-paths.js";
|
|
4
|
+
import { TERMINAL_STATES } from "./sse-shared.js";
|
|
5
|
+
/** Number of artifact-path probes performed by the run-list fingerprinter. */
|
|
6
|
+
let fingerprintProbeCount = 0;
|
|
7
|
+
export function runListFingerprintProbeCountForTests() {
|
|
8
|
+
return fingerprintProbeCount;
|
|
9
|
+
}
|
|
10
|
+
export function resetRunListFingerprintProbeCountForTests() {
|
|
11
|
+
fingerprintProbeCount = 0;
|
|
12
|
+
}
|
|
13
|
+
/** Fingerprint only the mutable delivery overlay for settled runs; all other
|
|
14
|
+
* terminal artifacts are immutable. Active runs retain the complete set. */
|
|
15
|
+
export function summaryFingerprint(rec) {
|
|
16
|
+
const fileIdentity = (rel) => {
|
|
17
|
+
fingerprintProbeCount++;
|
|
18
|
+
if (!rec.runDir)
|
|
19
|
+
return "0";
|
|
20
|
+
const path = safeArtifactPath(rec.runDir, rel);
|
|
21
|
+
if (!path)
|
|
22
|
+
return "0";
|
|
23
|
+
try {
|
|
24
|
+
const stat = statSync(path, { bigint: true });
|
|
25
|
+
return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "0";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const identity = [rec.state, paramsFingerprint(rec), rec.finishedAt ?? "", rec.error ?? ""];
|
|
32
|
+
if (TERMINAL_STATES.has(rec.state)) {
|
|
33
|
+
// Delivery is the only ordinary mutable terminal overlay. Retention is the
|
|
34
|
+
// other sanctioned transition: it atomically replaces the whole run tree
|
|
35
|
+
// with tombstone.yaml, so that marker must invalidate a previously cached
|
|
36
|
+
// ready summary without re-statting every immutable terminal artifact.
|
|
37
|
+
return [
|
|
38
|
+
...identity,
|
|
39
|
+
fileIdentity("final/delivery_state.yaml"),
|
|
40
|
+
fileIdentity("tombstone.yaml"),
|
|
41
|
+
].join("|");
|
|
42
|
+
}
|
|
43
|
+
return [
|
|
44
|
+
...identity,
|
|
45
|
+
fileIdentity("events.jsonl"),
|
|
46
|
+
fileIdentity("arbitration/decision.yaml"),
|
|
47
|
+
fileIdentity("final/telemetry.yaml"),
|
|
48
|
+
fileIdentity("final/run_facts.yaml"),
|
|
49
|
+
fileIdentity("final/failure.yaml"),
|
|
50
|
+
fileIdentity("final/summary.md"),
|
|
51
|
+
fileIdentity("final/answer.md"),
|
|
52
|
+
fileIdentity("final/plan.md"),
|
|
53
|
+
fileIdentity("final/explore.md"),
|
|
54
|
+
fileIdentity("final/report.md"),
|
|
55
|
+
fileIdentity("final/patch.diff"),
|
|
56
|
+
fileIdentity("final/work_product.yaml"),
|
|
57
|
+
fileIdentity("final/delivery_state.yaml"),
|
|
58
|
+
].join("|");
|
|
59
|
+
}
|
|
60
|
+
function paramsFingerprint(rec) {
|
|
61
|
+
try {
|
|
62
|
+
return sha256(JSON.stringify(rec.params ?? null));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "unserializable-params";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=run-list-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-list-fingerprint.js","sourceRoot":"","sources":["../src/run-list-fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,8EAA8E;AAC9E,IAAI,qBAAqB,GAAG,CAAC,CAAC;AAE9B,MAAM,UAAU,oCAAoC;IAClD,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,yCAAyC;IACvD,qBAAqB,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;4EAC4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,GAAoB;IACrD,MAAM,YAAY,GAAG,CAAC,GAAW,EAAU,EAAE;QAC3C,qBAAqB,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC;QAC5B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI;YAAE,OAAO,GAAG,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5F,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,uEAAuE;QACvE,OAAO;YACL,GAAG,QAAQ;YACX,YAAY,CAAC,2BAA2B,CAAC;YACzC,YAAY,CAAC,gBAAgB,CAAC;SAC/B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACd,CAAC;IACD,OAAO;QACL,GAAG,QAAQ;QACX,YAAY,CAAC,cAAc,CAAC;QAC5B,YAAY,CAAC,2BAA2B,CAAC;QACzC,YAAY,CAAC,sBAAsB,CAAC;QACpC,YAAY,CAAC,sBAAsB,CAAC;QACpC,YAAY,CAAC,oBAAoB,CAAC;QAClC,YAAY,CAAC,kBAAkB,CAAC;QAChC,YAAY,CAAC,iBAAiB,CAAC;QAC/B,YAAY,CAAC,eAAe,CAAC;QAC7B,YAAY,CAAC,kBAAkB,CAAC;QAChC,YAAY,CAAC,iBAAiB,CAAC;QAC/B,YAAY,CAAC,kBAAkB,CAAC;QAChC,YAAY,CAAC,yBAAyB,CAAC;QACvC,YAAY,CAAC,2BAA2B,CAAC;KAC1C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAoB;IAC7C,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,uBAAuB,CAAC;IACjC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface DaemonRunRecord {
|
|
2
|
+
id: string;
|
|
3
|
+
state: string;
|
|
4
|
+
runId?: string;
|
|
5
|
+
taskId?: string;
|
|
6
|
+
runDir?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
errorCode?: string;
|
|
9
|
+
errorStatus?: number;
|
|
10
|
+
errorRetryable?: boolean;
|
|
11
|
+
errorRequiredActions?: string[];
|
|
12
|
+
errorContext?: Record<string, unknown>;
|
|
13
|
+
params?: unknown;
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
finishedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DaemonFacadeClient {
|
|
19
|
+
enqueue(params: unknown, options?: {
|
|
20
|
+
idempotencyKey?: string;
|
|
21
|
+
clientId?: string;
|
|
22
|
+
idempotencyRequest?: unknown;
|
|
23
|
+
operation?: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
id: string;
|
|
26
|
+
state: string;
|
|
27
|
+
reused?: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
findAccepted?(params: unknown, options: {
|
|
30
|
+
idempotencyKey: string;
|
|
31
|
+
clientId?: string;
|
|
32
|
+
operation?: string;
|
|
33
|
+
idempotencyRequest?: unknown;
|
|
34
|
+
}): Promise<DaemonRunRecord | null>;
|
|
35
|
+
status(id: string): Promise<DaemonRunRecord>;
|
|
36
|
+
list(): Promise<DaemonRunRecord[]>;
|
|
37
|
+
cancel(id: string): Promise<unknown>;
|
|
38
|
+
fenceDelegationParent?(runId: string): Promise<unknown>;
|
|
39
|
+
}
|
|
40
|
+
export interface ControlOperatorDecisionRecord {
|
|
41
|
+
action: "accept_risk" | "override_needs_human";
|
|
42
|
+
findingIds: string[];
|
|
43
|
+
acceptedRisks: string[];
|
|
44
|
+
patchSha256: string;
|
|
45
|
+
decidedAt: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function paramsRecord(record: DaemonRunRecord): Record<string, unknown>;
|
|
48
|
+
/** Bounded direct Delegate children for reload projections. Exact persisted
|
|
49
|
+
* lineage only; ordinary parentRunId is intentionally ignored. */
|
|
50
|
+
export declare function directDelegatedChildrenFromRecords(parentRunId: string, runs: readonly DaemonRunRecord[], limit?: number): DaemonRunRecord[];
|
|
51
|
+
/** Persisted Delegate-only descendant graph; ordinary parentRunId is ignored. */
|
|
52
|
+
export declare function delegatedDescendantsFromRecords(parentRunId: string, runs: readonly DaemonRunRecord[]): DaemonRunRecord[];
|
|
53
|
+
//# sourceMappingURL=run-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-record.d.ts","sourceRoot":"","sources":["../src/run-record.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CACL,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5D,YAAY,CAAC,CACX,MAAM,EAAE,OAAO,EACf,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;KAC9B,GACA,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC7C,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,qBAAqB,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,aAAa,GAAG,sBAAsB,CAAC;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI7E;AAED;kEACkE;AAClE,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,SAAS,eAAe,EAAE,EAChC,KAAK,SAAyB,GAC7B,eAAe,EAAE,CAwBnB;AAED,iFAAiF;AACjF,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,SAAS,eAAe,EAAE,GAC/B,eAAe,EAAE,CAuBnB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MAX_DELEGATED_CHILDREN } from "@claudexor/schema";
|
|
2
|
+
export function paramsRecord(record) {
|
|
3
|
+
return record.params && typeof record.params === "object" && !Array.isArray(record.params)
|
|
4
|
+
? record.params
|
|
5
|
+
: {};
|
|
6
|
+
}
|
|
7
|
+
/** Bounded direct Delegate children for reload projections. Exact persisted
|
|
8
|
+
* lineage only; ordinary parentRunId is intentionally ignored. */
|
|
9
|
+
export function directDelegatedChildrenFromRecords(parentRunId, runs, limit = MAX_DELEGATED_CHILDREN) {
|
|
10
|
+
const boundedLimit = Math.max(0, limit);
|
|
11
|
+
if (boundedLimit === 0)
|
|
12
|
+
return [];
|
|
13
|
+
const ordered = runs
|
|
14
|
+
.filter((run) => paramsRecord(run)["delegatedFromRunId"] === parentRunId &&
|
|
15
|
+
(run.runId ?? run.id) !== parentRunId)
|
|
16
|
+
.sort((a, b) => {
|
|
17
|
+
const created = (a.createdAt ?? "\uffff").localeCompare(b.createdAt ?? "\uffff");
|
|
18
|
+
const runId = (a.runId ?? a.id).localeCompare(b.runId ?? b.id);
|
|
19
|
+
return created || runId || a.id.localeCompare(b.id);
|
|
20
|
+
});
|
|
21
|
+
const out = [];
|
|
22
|
+
const seen = new Set([parentRunId]);
|
|
23
|
+
for (const run of ordered) {
|
|
24
|
+
const runId = run.runId ?? run.id;
|
|
25
|
+
if (seen.has(runId))
|
|
26
|
+
continue;
|
|
27
|
+
seen.add(runId);
|
|
28
|
+
out.push(run);
|
|
29
|
+
if (out.length >= boundedLimit)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** Persisted Delegate-only descendant graph; ordinary parentRunId is ignored. */
|
|
35
|
+
export function delegatedDescendantsFromRecords(parentRunId, runs) {
|
|
36
|
+
const children = new Map();
|
|
37
|
+
for (const run of runs) {
|
|
38
|
+
const parent = paramsRecord(run)["delegatedFromRunId"];
|
|
39
|
+
if (typeof parent !== "string")
|
|
40
|
+
continue;
|
|
41
|
+
const rows = children.get(parent) ?? [];
|
|
42
|
+
rows.push(run);
|
|
43
|
+
children.set(parent, rows);
|
|
44
|
+
}
|
|
45
|
+
const out = [];
|
|
46
|
+
const seen = new Set([parentRunId]);
|
|
47
|
+
const queue = [parentRunId];
|
|
48
|
+
while (queue.length > 0) {
|
|
49
|
+
const parent = queue.shift();
|
|
50
|
+
for (const child of children.get(parent) ?? []) {
|
|
51
|
+
const childRunId = child.runId ?? child.id;
|
|
52
|
+
if (seen.has(childRunId))
|
|
53
|
+
continue;
|
|
54
|
+
seen.add(childRunId);
|
|
55
|
+
out.push(child);
|
|
56
|
+
queue.push(childRunId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=run-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-record.js","sourceRoot":"","sources":["../src/run-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type { DaemonControlApiOptions, DaemonFacadeClient, DaemonRunRecord } from "./daemon-server.js";
|
|
3
|
-
type RetryServices = Pick<NonNullable<DaemonControlApiOptions["services"]>, "createThreadTurn" | "setTurnEnqueueError" | "threadDetail" | "validateResources" | "preflightRunRequirements">;
|
|
3
|
+
type RetryServices = Pick<NonNullable<DaemonControlApiOptions["services"]>, "createThreadTurn" | "findThreadTurnByIdempotency" | "setTurnEnqueueError" | "threadDetail" | "validateResources" | "preflightRunRequirements">;
|
|
4
4
|
export interface RunRetryRouteContext {
|
|
5
5
|
daemon: DaemonFacadeClient;
|
|
6
6
|
services?: RetryServices;
|
|
7
7
|
findRun(id: string): Promise<DaemonRunRecord | null>;
|
|
8
8
|
waitForRunStart(jobId: string): Promise<DaemonRunRecord>;
|
|
9
|
+
serializeThreadMutation<T>(threadId: string, work: () => Promise<T>): Promise<T>;
|
|
9
10
|
json(response: ServerResponse, status: number, body: unknown): void;
|
|
10
|
-
requestError(response: ServerResponse, error: unknown): void;
|
|
11
|
+
requestError(response: ServerResponse, error: unknown, fallbackStatus?: 400 | 500): void;
|
|
11
12
|
}
|
|
12
13
|
export declare function handleRunRetryRoute(ctx: RunRetryRouteContext, method: string, path: string, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
13
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-retry-routes.d.ts","sourceRoot":"","sources":["../src/run-retry-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAWjE,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EAChB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-retry-routes.d.ts","sourceRoot":"","sources":["../src/run-retry-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAWjE,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAM5B,KAAK,aAAa,GAAG,IAAI,CACvB,WAAW,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,EAC9C,kBAAkB,GAClB,6BAA6B,GAC7B,qBAAqB,GACrB,cAAc,GACd,mBAAmB,GACnB,0BAA0B,CAC7B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACrD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACzD,uBAAuB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjF,IAAI,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACpE,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;CAC1F;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,OAAO,CAAC,CAYlB"}
|
package/dist/run-retry-routes.js
CHANGED
|
@@ -2,7 +2,8 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { ControlRunAgainDraft, ControlRunRetryResponse, ControlRunStartRequest, TaskContract, } from "@claudexor/schema";
|
|
5
|
-
import { recordTurnEnqueueFailure } from "./thread-turn-routes.js";
|
|
5
|
+
import { recordTurnEnqueueFailure, turnEnqueueProblemResponse } from "./thread-turn-routes.js";
|
|
6
|
+
import { resolveThreadRecoveryTurn } from "./thread-recovery.js";
|
|
6
7
|
import { TERMINAL_STATES } from "./sse-shared.js";
|
|
7
8
|
import * as runStart from "./run-start.js";
|
|
8
9
|
export async function handleRunRetryRoute(ctx, method, path, req, res) {
|
|
@@ -22,14 +23,25 @@ async function exactRetry(ctx, id, req, res) {
|
|
|
22
23
|
const source = await ctx.findRun(id);
|
|
23
24
|
if (!source)
|
|
24
25
|
return ctx.json(res, 404, { error: "no such run" });
|
|
26
|
+
const sourceParams = paramsRecord(source);
|
|
27
|
+
if (typeof sourceParams["delegatedFromRunId"] === "string") {
|
|
28
|
+
return ctx.json(res, 409, {
|
|
29
|
+
error: "Exact Retry is unavailable for a Delegate child after its parent authority ends; use Run Again to create an ordinary editable run",
|
|
30
|
+
code: "delegated_child_retry_unavailable",
|
|
31
|
+
retryable: false,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
25
34
|
if (source.state === "queued" || source.state === "running") {
|
|
26
35
|
return ctx.json(res, 409, { error: `run is still ${source.state}` });
|
|
27
36
|
}
|
|
28
37
|
let idempotencyKey;
|
|
29
38
|
let params;
|
|
39
|
+
let sourceTurnProvenance = {};
|
|
30
40
|
try {
|
|
31
41
|
idempotencyKey = runStart.requiredIdempotencyKey(req);
|
|
32
|
-
const
|
|
42
|
+
const replay = await sourceReplayInput(ctx, source);
|
|
43
|
+
sourceTurnProvenance = replay.turn;
|
|
44
|
+
const parsed = ControlRunStartRequest.parse(replay.params);
|
|
33
45
|
const { turnId: _turnId, retryOf: _retryOf, ...original } = parsed;
|
|
34
46
|
// QA-035: Exact Retry replays the IMMUTABLE original request. The stored
|
|
35
47
|
// params omit any model/effort the caller left to settings, so re-reading
|
|
@@ -48,64 +60,112 @@ async function exactRetry(ctx, id, req, res) {
|
|
|
48
60
|
parentRunId: source.runId ?? source.id,
|
|
49
61
|
retryOf: source.runId ?? source.id,
|
|
50
62
|
});
|
|
51
|
-
await ctx.services?.validateResources?.(params.attachments ?? []);
|
|
52
|
-
await ctx.services?.preflightRunRequirements?.(params);
|
|
53
63
|
}
|
|
54
64
|
catch (error) {
|
|
55
65
|
return ctx.requestError(res, error);
|
|
56
66
|
}
|
|
57
67
|
const sourceRunId = source.runId ?? source.id;
|
|
58
68
|
const threadId = typeof params.threadId === "string" ? params.threadId : null;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
planRunId: params.planRunId ?? null,
|
|
65
|
-
attachments: params.attachments,
|
|
66
|
-
idempotency: {
|
|
67
|
-
key: idempotencyKey,
|
|
68
|
-
client: "control-api",
|
|
69
|
-
request: { retryOf: sourceRunId },
|
|
70
|
-
},
|
|
71
|
-
}));
|
|
72
|
-
retryTurnId = turn.id;
|
|
73
|
-
}
|
|
74
|
-
const request = { ...params, ...(retryTurnId ? { turnId: retryTurnId } : {}) };
|
|
75
|
-
let job;
|
|
69
|
+
const idempotencyRequest = { retryOf: sourceRunId };
|
|
70
|
+
// A durable daemon command is the canonical replay owner. Probe it before
|
|
71
|
+
// any mutable resource/Git/harness preflight so a later environment change
|
|
72
|
+
// cannot replace an already-accepted handle with a new admission error.
|
|
73
|
+
let preflightStarted = false;
|
|
76
74
|
try {
|
|
77
|
-
|
|
75
|
+
const prior = await runStart.findAcceptedAroundPreflight(() => ctx.daemon.findAccepted?.(params, {
|
|
78
76
|
idempotencyKey,
|
|
79
77
|
clientId: "control-api",
|
|
80
78
|
operation: "run.retry",
|
|
81
|
-
idempotencyRequest
|
|
79
|
+
idempotencyRequest,
|
|
80
|
+
}) ?? Promise.resolve(null), async () => {
|
|
81
|
+
preflightStarted = true;
|
|
82
|
+
await ctx.services?.validateResources?.(params.attachments ?? []);
|
|
83
|
+
await ctx.services?.preflightRunRequirements?.(params);
|
|
82
84
|
});
|
|
85
|
+
if (prior) {
|
|
86
|
+
const priorParams = paramsRecord(prior);
|
|
87
|
+
const priorTurnId = typeof priorParams["turnId"] === "string" ? priorParams["turnId"] : undefined;
|
|
88
|
+
return await respondToExactRetryJob(ctx, res, sourceRunId, prior, threadId, priorTurnId);
|
|
89
|
+
}
|
|
83
90
|
}
|
|
84
91
|
catch (error) {
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
return ctx.requestError(res, error, preflightStarted ? undefined : 500);
|
|
93
|
+
}
|
|
94
|
+
const retry = async () => {
|
|
95
|
+
let retryTurnId;
|
|
96
|
+
if (threadId) {
|
|
97
|
+
const turn = await resolveThreadRecoveryTurn(ctx.daemon, ctx.services, source, threadId, params.prompt, {
|
|
98
|
+
kind: "followup",
|
|
99
|
+
parentRunId: sourceRunId,
|
|
100
|
+
planRunId: params.planRunId ?? null,
|
|
101
|
+
planHash: sourceTurnProvenance.planHash ?? null,
|
|
102
|
+
planOverridden: sourceTurnProvenance.planOverridden === true,
|
|
103
|
+
attachments: params.attachments,
|
|
104
|
+
}, {
|
|
105
|
+
key: idempotencyKey,
|
|
106
|
+
client: "control-api",
|
|
107
|
+
request: idempotencyRequest,
|
|
108
|
+
}, (turnId) => ctx.daemon.findAccepted?.({ ...params, turnId }, {
|
|
109
|
+
idempotencyKey,
|
|
110
|
+
clientId: "control-api",
|
|
111
|
+
operation: "run.retry",
|
|
112
|
+
idempotencyRequest,
|
|
113
|
+
}) ?? Promise.resolve(null));
|
|
114
|
+
retryTurnId = turn.id;
|
|
115
|
+
}
|
|
116
|
+
const request = { ...params, ...(retryTurnId ? { turnId: retryTurnId } : {}) };
|
|
117
|
+
let job;
|
|
118
|
+
try {
|
|
119
|
+
job = await ctx.daemon.enqueue(request, {
|
|
120
|
+
idempotencyKey,
|
|
121
|
+
clientId: "control-api",
|
|
122
|
+
operation: "run.retry",
|
|
123
|
+
idempotencyRequest,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const problem = recordTurnEnqueueFailure(ctx.services?.setTurnEnqueueError, retryTurnId, error);
|
|
128
|
+
const status = error && typeof error === "object" && "status" in error
|
|
129
|
+
? Number(error.status)
|
|
130
|
+
: 500;
|
|
131
|
+
return ctx.json(res, status, turnEnqueueProblemResponse(problem, {
|
|
132
|
+
...(threadId ? { threadId } : {}),
|
|
133
|
+
...(retryTurnId ? { turnId: retryTurnId } : {}),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
return respondToExactRetryJob(ctx, res, sourceRunId, job, threadId, retryTurnId);
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
return await (threadId ? ctx.serializeThreadMutation(threadId, retry) : retry());
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return ctx.requestError(res, error, 500);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function respondToExactRetryJob(ctx, res, sourceRunId, job, threadId, retryTurnId) {
|
|
146
|
+
let accepted;
|
|
147
|
+
try {
|
|
148
|
+
accepted = await ctx.waitForRunStart(job.id);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return ctx.json(res, 500, {
|
|
152
|
+
error: `retry job ${job.id} was accepted but its start could not be observed: ${error instanceof Error ? error.message : String(error)}`,
|
|
153
|
+
jobId: job.id,
|
|
154
|
+
retryOf: sourceRunId,
|
|
155
|
+
...(threadId ? { threadId } : {}),
|
|
156
|
+
...(retryTurnId ? { turnId: retryTurnId } : {}),
|
|
157
|
+
});
|
|
87
158
|
}
|
|
88
|
-
const accepted = await ctx.waitForRunStart(job.id);
|
|
89
159
|
if (!accepted.runId && TERMINAL_STATES.has(accepted.state)) {
|
|
90
160
|
// The replayed job died BEFORE it bound a run (e.g. the trust gate's typed
|
|
91
161
|
// 403). A 202 here would hand the caller a durable handle for a run that
|
|
92
|
-
// will never exist, and `claudexor retry` would exit 0 on
|
|
93
|
-
|
|
94
|
-
// as its typed failure; Exact Retry was the one surface that did not.
|
|
95
|
-
//
|
|
96
|
-
// The status mapping deliberately follows POST /runs
|
|
97
|
-
// (`runStart.unboundRunStartResponse`): the persisted `errorStatus`
|
|
98
|
-
// verbatim, otherwise 500. The sibling turn surface
|
|
99
|
-
// (`preStartRefusalStatus` in thread-turn-routes.ts) additionally maps a
|
|
100
|
-
// status-less `trust_full_access_required` to 403, so a trust refusal
|
|
101
|
-
// recorded WITHOUT an errorStatus is mapped differently by the two
|
|
102
|
-
// surfaces. Unifying them is out of scope here.
|
|
103
|
-
const { status, body } = runStart.unboundRunStartResponse(accepted, true);
|
|
104
|
-
return ctx.json(res, status, {
|
|
105
|
-
...body,
|
|
162
|
+
// will never exist, and `claudexor retry` would exit 0 on it.
|
|
163
|
+
const { status, body } = runStart.unboundRunStartResponse(accepted, true, {
|
|
106
164
|
retryOf: sourceRunId,
|
|
165
|
+
...(threadId ? { threadId } : {}),
|
|
107
166
|
...(retryTurnId ? { turnId: retryTurnId } : {}),
|
|
108
167
|
});
|
|
168
|
+
return ctx.json(res, status, body);
|
|
109
169
|
}
|
|
110
170
|
ctx.json(res, accepted.runId ? 200 : 202, ControlRunRetryResponse.parse({
|
|
111
171
|
retryOf: sourceRunId,
|
|
@@ -120,13 +180,13 @@ async function runAgain(ctx, id, res) {
|
|
|
120
180
|
if (!source)
|
|
121
181
|
return ctx.json(res, 404, { error: "no such run" });
|
|
122
182
|
try {
|
|
123
|
-
const parsed = ControlRunStartRequest.parse(await
|
|
183
|
+
const parsed = ControlRunStartRequest.parse((await sourceReplayInput(ctx, source)).params);
|
|
124
184
|
// Strip EVERY server-owned binding, with disclosure: the draft is an
|
|
125
185
|
// editable POST /runs request, and POST /runs 400s threadId/planRef (they
|
|
126
186
|
// belong to the turn pipeline) — surviving here would make the draft
|
|
127
187
|
// unpostable, and a replayed planRef would smuggle the frozen-plan
|
|
128
188
|
// reference past the boundary (INV-081). Same set as decision-rerun.
|
|
129
|
-
const { turnId, retryOf, planRunId, planRef, threadId, ...request } = parsed;
|
|
189
|
+
const { turnId, retryOf, planRunId, planRef, threadId, parentRunId, delegatedFromRunId, ...request } = parsed;
|
|
130
190
|
const differences = [
|
|
131
191
|
...(turnId
|
|
132
192
|
? [{ field: "turnId", change: "omitted", reason: "server-owned turn binding" }]
|
|
@@ -149,6 +209,18 @@ async function runAgain(ctx, id, res) {
|
|
|
149
209
|
...(threadId
|
|
150
210
|
? [{ field: "threadId", change: "omitted", reason: "server-owned thread binding" }]
|
|
151
211
|
: []),
|
|
212
|
+
...(parentRunId
|
|
213
|
+
? [{ field: "parentRunId", change: "omitted", reason: "new ordinary run" }]
|
|
214
|
+
: []),
|
|
215
|
+
...(delegatedFromRunId
|
|
216
|
+
? [
|
|
217
|
+
{
|
|
218
|
+
field: "delegatedFromRunId",
|
|
219
|
+
change: "omitted",
|
|
220
|
+
reason: "Delegate parent authority is not replayable",
|
|
221
|
+
},
|
|
222
|
+
]
|
|
223
|
+
: []),
|
|
152
224
|
];
|
|
153
225
|
ctx.json(res, 200, ControlRunAgainDraft.parse({ sourceRunId: source.runId ?? source.id, request, differences }));
|
|
154
226
|
}
|
|
@@ -156,6 +228,11 @@ async function runAgain(ctx, id, res) {
|
|
|
156
228
|
ctx.requestError(res, error);
|
|
157
229
|
}
|
|
158
230
|
}
|
|
231
|
+
function paramsRecord(rec) {
|
|
232
|
+
return rec.params && typeof rec.params === "object" && !Array.isArray(rec.params)
|
|
233
|
+
? rec.params
|
|
234
|
+
: {};
|
|
235
|
+
}
|
|
159
236
|
/** QA-035: read the model/effort the engine froze into the source run's
|
|
160
237
|
* TaskContract. Exact Retry injects these as the immutable route so a settings
|
|
161
238
|
* change between runs cannot silently re-resolve the model or drop the effort.
|
|
@@ -178,28 +255,32 @@ function readFrozenRouting(source) {
|
|
|
178
255
|
: undefined;
|
|
179
256
|
return { ...(models ? { models } : {}), ...(efforts ? { efforts } : {}) };
|
|
180
257
|
}
|
|
181
|
-
async function
|
|
258
|
+
async function sourceReplayInput(ctx, source) {
|
|
182
259
|
const params = source.params && typeof source.params === "object" && !Array.isArray(source.params)
|
|
183
260
|
? { ...source.params }
|
|
184
261
|
: {};
|
|
185
|
-
if (params["attachments"] !== undefined)
|
|
186
|
-
return params;
|
|
187
262
|
const threadId = typeof params["threadId"] === "string" ? params["threadId"] : null;
|
|
188
263
|
const turnId = typeof params["turnId"] === "string" ? params["turnId"] : null;
|
|
189
264
|
if (!threadId || !turnId || !ctx.services?.threadDetail)
|
|
190
|
-
return params;
|
|
265
|
+
return { params, turn: {} };
|
|
191
266
|
const detail = await ctx.services.threadDetail(threadId);
|
|
192
267
|
const turn = detail.turns.find((candidate) => candidate &&
|
|
193
268
|
typeof candidate === "object" &&
|
|
194
269
|
!Array.isArray(candidate) &&
|
|
195
270
|
candidate.id === turnId);
|
|
196
|
-
if (Array.isArray(turn?.attachments)) {
|
|
271
|
+
if (params["attachments"] === undefined && Array.isArray(turn?.attachments)) {
|
|
197
272
|
params["attachments"] = turn.attachments.map((attachment) => ({
|
|
198
273
|
resourceId: attachment && typeof attachment === "object"
|
|
199
274
|
? attachment.resource_id
|
|
200
275
|
: undefined,
|
|
201
276
|
}));
|
|
202
277
|
}
|
|
203
|
-
return
|
|
278
|
+
return {
|
|
279
|
+
params,
|
|
280
|
+
turn: {
|
|
281
|
+
planHash: typeof turn?.plan_hash === "string" ? turn.plan_hash : null,
|
|
282
|
+
planOverridden: turn?.plan_readiness_overridden === true,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
204
285
|
}
|
|
205
286
|
//# sourceMappingURL=run-retry-routes.js.map
|