@tea-agent/loop-agent 0.42.0-next.9 → 0.42.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/CHANGELOG.md +111 -45
- package/dist/application/dag/run-dag.js +8 -2
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +26 -8
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +2584 -600
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +135 -55
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +173 -94
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +208 -66
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +27 -7
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +21 -0
- package/dist/worker/console/chat/tools.js +13 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -5
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +431 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +51 -4
- package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +64 -22
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +194 -243
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +103 -17
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +21 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +146 -16
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +46 -7
- package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +70 -35
- package/dist/workflows/dag/node-execution.js +225 -226
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +96 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +246 -19
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +68 -4
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/harness.json +1 -1
- package/package.json +8 -6
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +22 -2
- package/skills/frontend-plan/references/decision-contract.md +114 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -136,6 +136,10 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
136
136
|
const consumedArtifactIds = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
|
|
137
137
|
for (const depId of task.depends_on) {
|
|
138
138
|
const record = upstream[depId];
|
|
139
|
+
if (task.id === "frontend-scout-pi" && depId === "frontend-contract-pi" && record?.status === "FINISHED") {
|
|
140
|
+
sections.push(`## Upstream output: ${depId}\nCommitted Contract obligations are supplied in frontend_scout_input. Do not reconstruct them from prose or reread the source.`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
139
143
|
const stdout = record?.stdout?.trim() ? record.stdout : "";
|
|
140
144
|
const assistantText = record?.assistantText?.trim()
|
|
141
145
|
? record.assistantText
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { hostname as localHostname } from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
6
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
7
|
import { DAG_RUNS_DIR } from "./lifecycle.js";
|
|
6
8
|
function recoveryLeasePath(cwd, parentRunId) {
|
|
@@ -23,6 +25,11 @@ function parseLease(raw, parentRunId) {
|
|
|
23
25
|
record.kind !== "standalone-task-rerun") ||
|
|
24
26
|
typeof record.leaseToken !== "string" ||
|
|
25
27
|
record.leaseToken.length === 0 ||
|
|
28
|
+
typeof record.ownerPid !== "number" ||
|
|
29
|
+
!Number.isInteger(record.ownerPid) ||
|
|
30
|
+
record.ownerPid <= 0 ||
|
|
31
|
+
typeof record.ownerHostname !== "string" ||
|
|
32
|
+
record.ownerHostname.length === 0 ||
|
|
26
33
|
typeof record.acquiredAt !== "string" ||
|
|
27
34
|
typeof record.executionRef !== "undefined" &&
|
|
28
35
|
typeof record.executionRef !== "string") {
|
|
@@ -30,6 +37,60 @@ function parseLease(raw, parentRunId) {
|
|
|
30
37
|
}
|
|
31
38
|
return record;
|
|
32
39
|
}
|
|
40
|
+
const LEASE_MUTATION_LOCK_STALE_MS = 60_000;
|
|
41
|
+
const LEASE_MUTATION_LOCK_RETRIES = 200;
|
|
42
|
+
async function withLeaseMutationLock(leasePath, operation) {
|
|
43
|
+
const lockPath = `${leasePath}.mutation-lock`;
|
|
44
|
+
for (let attempt = 0; attempt < LEASE_MUTATION_LOCK_RETRIES; attempt += 1) {
|
|
45
|
+
try {
|
|
46
|
+
await mkdir(lockPath);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const lockStat = await stat(lockPath);
|
|
54
|
+
if (Date.now() - lockStat.mtimeMs >= LEASE_MUTATION_LOCK_STALE_MS) {
|
|
55
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (statError) {
|
|
60
|
+
if (statError &&
|
|
61
|
+
typeof statError === "object" &&
|
|
62
|
+
"code" in statError &&
|
|
63
|
+
statError.code === "ENOENT") {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
throw statError;
|
|
67
|
+
}
|
|
68
|
+
await delay(10);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return await operation();
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error("dag recovery lease mutation lock is busy");
|
|
79
|
+
}
|
|
80
|
+
function defaultIsProcessAlive(pid) {
|
|
81
|
+
try {
|
|
82
|
+
process.kill(pid, 0);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function leaseOwnerIsAlive(input) {
|
|
90
|
+
if (input.lease.ownerHostname !== input.hostname)
|
|
91
|
+
return true;
|
|
92
|
+
return input.isProcessAlive(input.lease.ownerPid);
|
|
93
|
+
}
|
|
33
94
|
export async function acquireDagRecoveryLease(input) {
|
|
34
95
|
const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
|
|
35
96
|
const lease = {
|
|
@@ -38,13 +99,15 @@ export async function acquireDagRecoveryLease(input) {
|
|
|
38
99
|
requestId: input.requestId,
|
|
39
100
|
kind: input.kind,
|
|
40
101
|
leaseToken: randomBytes(16).toString("hex"),
|
|
102
|
+
ownerPid: process.pid,
|
|
103
|
+
ownerHostname: localHostname(),
|
|
41
104
|
acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
42
105
|
};
|
|
43
106
|
await mkdir(path.dirname(leasePath), { recursive: true });
|
|
44
107
|
try {
|
|
45
108
|
// `wx` is the cross-process compare-and-create primitive. The lease is
|
|
46
|
-
//
|
|
47
|
-
//
|
|
109
|
+
// parent-scoped for the full recovery lineage. Ownership may only be
|
|
110
|
+
// replaced by claimDagRecoveryLease after the recorded owner is proven dead.
|
|
48
111
|
await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
|
|
49
112
|
encoding: "utf8",
|
|
50
113
|
flag: "wx",
|
|
@@ -59,22 +122,49 @@ export async function acquireDagRecoveryLease(input) {
|
|
|
59
122
|
return { kind: "held", leasePath, lease: existing };
|
|
60
123
|
}
|
|
61
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Reclaim a lease only after its recorded same-host owner is proven dead.
|
|
127
|
+
* The mutation lock serializes reclaim with execution binding, so a live owner
|
|
128
|
+
* can never race a stale-owner takeover and launch a second recovery.
|
|
129
|
+
*/
|
|
130
|
+
export async function claimDagRecoveryLease(input) {
|
|
131
|
+
const hostname = input.hostname ?? localHostname();
|
|
132
|
+
const isProcessAlive = input.isProcessAlive ?? defaultIsProcessAlive;
|
|
133
|
+
return withLeaseMutationLock(input.leasePath, async () => {
|
|
134
|
+
const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
|
|
135
|
+
if (existing.leaseToken !== input.lease.leaseToken ||
|
|
136
|
+
leaseOwnerIsAlive({ lease: existing, hostname, isProcessAlive })) {
|
|
137
|
+
return { kind: "held", leasePath: input.leasePath, lease: existing };
|
|
138
|
+
}
|
|
139
|
+
const claimed = {
|
|
140
|
+
...existing,
|
|
141
|
+
leaseToken: randomBytes(16).toString("hex"),
|
|
142
|
+
ownerPid: process.pid,
|
|
143
|
+
ownerHostname: hostname,
|
|
144
|
+
acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
await writeJsonAtomic(input.leasePath, claimed);
|
|
147
|
+
return { kind: "acquired", leasePath: input.leasePath, lease: claimed };
|
|
148
|
+
});
|
|
149
|
+
}
|
|
62
150
|
/** Persist the operation/run identity before the caller is allowed to execute it. */
|
|
63
151
|
export async function bindDagRecoveryLeaseExecution(input) {
|
|
64
152
|
if (!input.executionRef.trim()) {
|
|
65
153
|
throw new Error("dag recovery lease executionRef is required");
|
|
66
154
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
155
|
+
return withLeaseMutationLock(input.leasePath, async () => {
|
|
156
|
+
const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
|
|
157
|
+
if (existing.leaseToken !== input.lease.leaseToken) {
|
|
158
|
+
throw new Error("dag recovery lease ownership drift");
|
|
159
|
+
}
|
|
160
|
+
if (existing.executionRef && existing.executionRef !== input.executionRef) {
|
|
161
|
+
throw new Error("dag recovery lease execution identity drift");
|
|
162
|
+
}
|
|
163
|
+
const bound = {
|
|
164
|
+
...existing,
|
|
165
|
+
executionRef: input.executionRef,
|
|
166
|
+
};
|
|
167
|
+
await writeJsonAtomic(input.leasePath, bound);
|
|
168
|
+
return bound;
|
|
169
|
+
});
|
|
80
170
|
}
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
|
|
5
|
+
import { workspaceDriftedPaths, } from "./workspace-checkpoint.js";
|
|
5
6
|
import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
|
|
6
7
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
7
8
|
import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
|
|
@@ -45,6 +46,7 @@ const highRiskContinuationAuthorityV1Schema = z
|
|
|
45
46
|
tokens: z.number().int().nonnegative().nullable(),
|
|
46
47
|
wallTimeMs: z.number().nonnegative(),
|
|
47
48
|
executorCalls: z.number().int().nonnegative(),
|
|
49
|
+
providerRequests: z.number().int().nonnegative().optional(),
|
|
48
50
|
repairPasses: z.number().int().nonnegative(),
|
|
49
51
|
peakContextChars: z.number().int().nonnegative(),
|
|
50
52
|
missingTokenNodeIds: z.array(z.string()),
|
|
@@ -1110,6 +1112,46 @@ function deriveSuggestedAction(input) {
|
|
|
1110
1112
|
}
|
|
1111
1113
|
return "manual";
|
|
1112
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Gitignore-style-lite matcher for readSet/writeSet path patterns in scoped
|
|
1117
|
+
* workspace-drift evaluation: literal paths match exactly; `**` spans path
|
|
1118
|
+
* segments (a trailing `/**` also matches when nothing follows), `*` stays
|
|
1119
|
+
* within one segment, `?` is one non-separator char.
|
|
1120
|
+
*/
|
|
1121
|
+
function pathMatchesWorkspacePattern(pathValue, pattern) {
|
|
1122
|
+
const normalizedPath = pathValue.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1123
|
+
const trimmed = pattern.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1124
|
+
if (!trimmed || trimmed === "/")
|
|
1125
|
+
return false;
|
|
1126
|
+
if (trimmed === normalizedPath)
|
|
1127
|
+
return true;
|
|
1128
|
+
let source = "";
|
|
1129
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
1130
|
+
const char = trimmed[index];
|
|
1131
|
+
if (char === "*") {
|
|
1132
|
+
if (trimmed[index + 1] === "*") {
|
|
1133
|
+
if (trimmed[index + 2] === "/") {
|
|
1134
|
+
source += "(?:.*/)?";
|
|
1135
|
+
index += 2;
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
source += ".*";
|
|
1139
|
+
index += 1;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
source += "[^/]*";
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
else if (char === "?") {
|
|
1147
|
+
source += "[^/]";
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
source += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return new RegExp(`^${source}$`).test(normalizedPath);
|
|
1154
|
+
}
|
|
1113
1155
|
function deriveRisk(reasonCodes, resetCount) {
|
|
1114
1156
|
if (reasonCodes.includes("restart-subgraph-contains-writer") ||
|
|
1115
1157
|
reasonCodes.includes("restart-subgraph-contains-unsafe-shell") ||
|
|
@@ -1213,7 +1255,14 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
1213
1255
|
blockedReasons.push("parent-lifecycle-ineligible");
|
|
1214
1256
|
}
|
|
1215
1257
|
else if (input.parentState.status !== "failed" &&
|
|
1216
|
-
input.parentState.status !== "partial_failed"
|
|
1258
|
+
input.parentState.status !== "partial_failed" &&
|
|
1259
|
+
// A superseded parent is a terminal operator judgment about the run as
|
|
1260
|
+
// a whole, not about its deterministic read-only compute: its FINISHED
|
|
1261
|
+
// upstream nodes are still valid fact donors for the imported set, and
|
|
1262
|
+
// every unsafe case (writers in the reset subgraph, decision gates,
|
|
1263
|
+
// stale checkpoints, unresolved ERROR nodes outside the closure) keeps
|
|
1264
|
+
// its own dedicated block below.
|
|
1265
|
+
input.parentState.status !== "superseded") {
|
|
1217
1266
|
reasonCodes.push("parent-lifecycle-ineligible");
|
|
1218
1267
|
blockedReasons.push("parent-lifecycle-ineligible");
|
|
1219
1268
|
}
|
|
@@ -1241,8 +1290,41 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
1241
1290
|
else if (input.currentWorkspace?.fingerprint &&
|
|
1242
1291
|
input.parentTerminalWorkspace.fingerprint !==
|
|
1243
1292
|
input.currentWorkspace.fingerprint) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1293
|
+
// Whole-fingerprint drift is refined to content-level paths: the rerun
|
|
1294
|
+
// re-derives (readSet) and rewrites (writeSet) the reset subgraph's own
|
|
1295
|
+
// paths, so drift confined to those paths is recomputed by the rerun
|
|
1296
|
+
// itself and must not force a full standalone rerun. Drift anywhere
|
|
1297
|
+
// else — especially inputs of imported fact-reuse nodes — keeps the
|
|
1298
|
+
// strict block. Indeterminate checkpoints fail closed.
|
|
1299
|
+
const driftedPaths = workspaceDriftedPaths(input.parentTerminalWorkspace, input.currentWorkspace);
|
|
1300
|
+
const importedReadPaths = importedNodeIds.flatMap((nodeId) => {
|
|
1301
|
+
const task = tasksByIdForPlan.get(nodeId);
|
|
1302
|
+
return task?.readSet ?? [];
|
|
1303
|
+
});
|
|
1304
|
+
// An omitted or empty readSet is unrestricted, not proof that an
|
|
1305
|
+
// imported node read nothing. Localize drift only when every reused
|
|
1306
|
+
// node has an explicit reading boundary.
|
|
1307
|
+
const importedReadSetsKnown = importedNodeIds.every((nodeId) => (tasksByIdForPlan.get(nodeId)?.readSet?.length ?? 0) > 0);
|
|
1308
|
+
const scopedDrift = importedReadSetsKnown &&
|
|
1309
|
+
driftedPaths !== undefined &&
|
|
1310
|
+
driftedPaths.length > 0 &&
|
|
1311
|
+
driftedPaths.every((driftPath) => !importedReadPaths.some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern)) &&
|
|
1312
|
+
resetNodeIds.some((nodeId) => {
|
|
1313
|
+
const task = tasksByIdForPlan.get(nodeId);
|
|
1314
|
+
if (!task)
|
|
1315
|
+
return false;
|
|
1316
|
+
return [
|
|
1317
|
+
...(task.writeSet ?? []),
|
|
1318
|
+
...(task.readSet ?? []),
|
|
1319
|
+
].some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern));
|
|
1320
|
+
}));
|
|
1321
|
+
if (scopedDrift) {
|
|
1322
|
+
reasonCodes.push("workspace-drift-scoped");
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
reasonCodes.push("workspace-drift");
|
|
1326
|
+
blockedReasons.push("workspace-drift");
|
|
1327
|
+
}
|
|
1246
1328
|
}
|
|
1247
1329
|
else if (!input.currentWorkspace?.fingerprint) {
|
|
1248
1330
|
reasonCodes.push("workspace-checkpoint-missing");
|
|
@@ -1293,8 +1375,18 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
1293
1375
|
}
|
|
1294
1376
|
for (const nodeId of importedNodeIds) {
|
|
1295
1377
|
const record = input.parentState.nodes[nodeId];
|
|
1296
|
-
if (!record)
|
|
1378
|
+
if (!record) {
|
|
1379
|
+
reasonCodes.push("parent-facts-invalid");
|
|
1380
|
+
blockedReasons.push("parent-facts-invalid");
|
|
1381
|
+
blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
|
|
1297
1382
|
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (record.status === "PENDING" || record.status === "RUNNING") {
|
|
1385
|
+
reasonCodes.push("parent-facts-invalid");
|
|
1386
|
+
blockedReasons.push("parent-facts-invalid");
|
|
1387
|
+
blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1298
1390
|
if (record.status === "ERROR") {
|
|
1299
1391
|
reasonCodes.push("parent-facts-invalid");
|
|
1300
1392
|
blockedReasons.push("parent-facts-invalid");
|
|
@@ -88,11 +88,18 @@ const operatorRequestEnvelopeSchema = z
|
|
|
88
88
|
})
|
|
89
89
|
.strict();
|
|
90
90
|
async function readExistingOperatorRequest(cwd, requestId) {
|
|
91
|
+
const requestPath = operatorRequestPath(cwd, requestId);
|
|
91
92
|
try {
|
|
92
|
-
const raw = JSON.parse(await readFile(
|
|
93
|
+
const raw = JSON.parse(await readFile(requestPath, "utf-8"));
|
|
93
94
|
const parsed = operatorRequestEnvelopeSchema.safeParse(raw);
|
|
94
|
-
if (!parsed.success)
|
|
95
|
-
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope is invalid or belongs to a different request kind");
|
|
97
|
+
}
|
|
98
|
+
if (parsed.data.requestId !== requestId ||
|
|
99
|
+
parsed.data.parentRunId !== parsed.data.result.plan.parentRunId ||
|
|
100
|
+
parsed.data.newRunId !== parsed.data.result.newRunId) {
|
|
101
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope binding mismatch");
|
|
102
|
+
}
|
|
96
103
|
const result = parsed.data.result;
|
|
97
104
|
return {
|
|
98
105
|
...result,
|
|
@@ -100,10 +107,18 @@ async function readExistingOperatorRequest(cwd, requestId) {
|
|
|
100
107
|
idempotentReplay: true,
|
|
101
108
|
};
|
|
102
109
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error &&
|
|
112
|
+
typeof error === "object" &&
|
|
113
|
+
"code" in error &&
|
|
114
|
+
error.code === "ENOENT") {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (error instanceof Error && error.message.startsWith("REQUEST_ID_REUSE_CONFLICT:")) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`REQUEST_ID_REUSE_CONFLICT: invalid persisted operator request envelope: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
121
|
}
|
|
106
|
-
return undefined;
|
|
107
122
|
}
|
|
108
123
|
/**
|
|
109
124
|
* Idempotent replay of a previously persisted operator request, if any.
|
|
@@ -769,6 +784,13 @@ async function stageContinuationRun(input) {
|
|
|
769
784
|
if (!parentRecord) {
|
|
770
785
|
throw new Error(`parent node record missing for imported node: ${nodeId}`);
|
|
771
786
|
}
|
|
787
|
+
if (parentRecord.status !== "FINISHED" &&
|
|
788
|
+
!(parentRecord.status === "SKIPPED" &&
|
|
789
|
+
(parentRecord.skippedReason?.includes("condition") ||
|
|
790
|
+
parentRecord.skippedReason?.includes("runIf") ||
|
|
791
|
+
parentRecord.skippedReason?.includes("run-if")))) {
|
|
792
|
+
throw new Error(`parent node is not a settled fact donor for imported node: ${nodeId}`);
|
|
793
|
+
}
|
|
772
794
|
const { manifestNode, importedRecord } = await importNodeFacts({
|
|
773
795
|
parentRunDir: input.parentRunDir,
|
|
774
796
|
newRunDir: runDir,
|