@tea-agent/loop-agent 0.39.3 → 0.40.0-beta.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 +133 -92
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -0
- package/dist/cli/program.js +23 -1
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/executors/dag-pi-executor.js +108 -19
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +49 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +42 -2
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +35 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +14 -0
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +10 -0
- package/dist/task/source-prepare/fragment-inventory.js +1 -14
- package/dist/worker/console/chat/chat-event-store.js +77 -7
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +94 -64
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +104 -3
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +128 -0
- package/dist/worker/console/routes.js +927 -8
- package/dist/worker/console/security.js +39 -4
- package/dist/worker/console/server.js +532 -187
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
- package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
- package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
- package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +260 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +94 -10
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +175 -1
- package/dist/worker/observe/static/styles.css +1039 -128
- package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
- package/dist/worker/observe/static/views/dag.js +2 -1
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +75 -10
- package/dist/worker/observe/static/views/task.js +1 -1
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1 -89
- package/dist/workflows/dag/init-hybrid.js +178 -47
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +272 -0
- package/dist/workflows/dag/rerun-run.js +390 -12
- package/dist/workflows/dag/skill-snapshot.js +1 -0
- package/dist/workflows/dag/types.js +203 -8
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/agent-dag.schema.json +50 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/harness.json +3 -3
- package/package.json +5 -1
- package/skills/loop-agent/references/command-reference.md +6 -0
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -364,7 +364,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
364
364
|
"page-action-btn",
|
|
365
365
|
"在操作台中止此运行 →",
|
|
366
366
|
);
|
|
367
|
-
interruptLink.href = `/#/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
|
|
367
|
+
interruptLink.href = `/#/operate/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
|
|
368
368
|
interruptLink.setAttribute("data-testid", "inspect-dag-interrupt-link");
|
|
369
369
|
interruptLink.title = "只跳转到操作台 Recovery,不会在观测页执行中止";
|
|
370
370
|
primaryEntries.push(["协作中止", interruptLink]);
|
|
@@ -482,6 +482,7 @@ export function startDagPolling(dagRunId, initial = true) {
|
|
|
482
482
|
const active = await renderDagDetail(dagRunId, isInitial);
|
|
483
483
|
if (generation !== uiState.pollingGeneration || !active) return;
|
|
484
484
|
const delay = detailPollDelay("running", isPageVisible());
|
|
485
|
+
if (delay == null) return;
|
|
485
486
|
uiState.dagPollTimer = setTimeout(() => {
|
|
486
487
|
uiState.dagPollTimer = null;
|
|
487
488
|
void poll(false);
|
|
@@ -84,7 +84,7 @@ let dashboardLoadingNode = null;
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* 「昨夜班次」条带:夜间结果在观测侧的唯一一级呈现(替代旧「夜间任务」tab)。
|
|
87
|
-
* 管理动作(晨审处置)始终落在操作侧 /#/night,保持 Inspect 只读不变式。
|
|
87
|
+
* 管理动作(晨审处置)始终落在操作侧 /#/operate/night,保持 Inspect 只读不变式。
|
|
88
88
|
*/
|
|
89
89
|
async function renderNightStrip() {
|
|
90
90
|
const strip = document.getElementById("dashboard-night");
|
|
@@ -118,14 +118,14 @@ async function renderNightStrip() {
|
|
|
118
118
|
|
|
119
119
|
const actions = el("div", "night-strip-actions");
|
|
120
120
|
const detail = el("a", "page-action-btn", "夜间详情");
|
|
121
|
-
detail.href = "
|
|
121
|
+
detail.href = "/#/inspect/night";
|
|
122
122
|
detail.addEventListener("click", (e) => {
|
|
123
123
|
e.preventDefault();
|
|
124
124
|
navigate("/night");
|
|
125
125
|
});
|
|
126
126
|
actions.appendChild(detail);
|
|
127
127
|
const manage = el("a", "page-action-btn night-strip-manage", "晨审处置 →");
|
|
128
|
-
manage.href = "/#/night";
|
|
128
|
+
manage.href = "/#/operate/night";
|
|
129
129
|
manage.title = "夜间计划的管理与晨审处置在操作台完成(Inspect 保持只读)";
|
|
130
130
|
actions.appendChild(manage);
|
|
131
131
|
strip.appendChild(actions);
|
|
@@ -738,10 +738,12 @@ export function startDashboardPolling(scrollTo) {
|
|
|
738
738
|
const poll = async (initialScrollTo) => {
|
|
739
739
|
await renderDashboard(initialScrollTo);
|
|
740
740
|
if (generation !== uiState.pollingGeneration) return;
|
|
741
|
+
const delay = dashboardPollDelay();
|
|
742
|
+
if (delay == null) return;
|
|
741
743
|
uiState.dashboardTimer = setTimeout(() => {
|
|
742
744
|
uiState.dashboardTimer = null;
|
|
743
745
|
void poll();
|
|
744
|
-
},
|
|
746
|
+
}, delay);
|
|
745
747
|
};
|
|
746
748
|
void poll(scrollTo);
|
|
747
749
|
}
|
|
@@ -46,8 +46,8 @@ export async function renderNightJobs(scheduleId) {
|
|
|
46
46
|
showView("night");
|
|
47
47
|
bindNightToolbar();
|
|
48
48
|
const crumbs = [
|
|
49
|
-
{ label: UI_TEXT.dashboard, href: "
|
|
50
|
-
{ label: UI_TEXT.nightJobs, href: "
|
|
49
|
+
{ label: UI_TEXT.dashboard, href: "/#/inspect" },
|
|
50
|
+
{ label: UI_TEXT.nightJobs, href: "/#/inspect/night" },
|
|
51
51
|
];
|
|
52
52
|
if (scheduleId) crumbs.push({ label: scheduleId });
|
|
53
53
|
setBreadcrumb(crumbs);
|
|
@@ -93,6 +93,7 @@ function startPoolAutoPolling() {
|
|
|
93
93
|
if (generation !== uiState.pollingGeneration) return;
|
|
94
94
|
if (!poolAutoRefreshEnabled()) return;
|
|
95
95
|
const delay = isPageVisible() ? POOL_POLL_MS : dashboardPollDelay();
|
|
96
|
+
if (delay == null) return;
|
|
96
97
|
uiState.poolTimer = setTimeout(() => {
|
|
97
98
|
uiState.poolTimer = null;
|
|
98
99
|
void poll();
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
} from "../relations.js";
|
|
51
51
|
import { copyText, renderRecommendedCommand } from "../copy.js";
|
|
52
52
|
import {
|
|
53
|
+
fetchWithWorkspaceScope,
|
|
53
54
|
isPageVisible,
|
|
54
55
|
detailPollDelay,
|
|
55
56
|
dashboardPollDelay,
|
|
@@ -126,7 +127,7 @@ export function renderInlineArtifactList(container, artifacts) {
|
|
|
126
127
|
list.appendChild(li);
|
|
127
128
|
void (async () => {
|
|
128
129
|
try {
|
|
129
|
-
const res = await
|
|
130
|
+
const res = await fetchWithWorkspaceScope(artifactUrl(artifactPath));
|
|
130
131
|
const text = await res.text();
|
|
131
132
|
if (!res.ok) {
|
|
132
133
|
let message = text;
|
|
@@ -444,6 +445,7 @@ export function startRunPolling(workerRunId, initial = true) {
|
|
|
444
445
|
const active = await renderRun(workerRunId, isInitial);
|
|
445
446
|
if (generation !== uiState.pollingGeneration || !active) return;
|
|
446
447
|
const delay = detailPollDelay("running", isPageVisible());
|
|
448
|
+
if (delay == null) return;
|
|
447
449
|
uiState.runPollTimer = setTimeout(() => {
|
|
448
450
|
uiState.runPollTimer = null;
|
|
449
451
|
void poll(false);
|
|
@@ -480,6 +480,35 @@ export function buildRichTimelineItems(events, options = {}) {
|
|
|
480
480
|
return items;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
const EXECUTION_RECORD_FILTERS = Object.freeze([
|
|
484
|
+
["all", "全部"],
|
|
485
|
+
["model", "模型消息"],
|
|
486
|
+
["tools", "工具与进程"],
|
|
487
|
+
]);
|
|
488
|
+
|
|
489
|
+
function normalizeExecutionRecordFilter(value) {
|
|
490
|
+
return value === "model" || value === "tools" ? value : "all";
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function filterExecutionRecordItems(items, filter) {
|
|
494
|
+
const normalized = normalizeExecutionRecordFilter(filter);
|
|
495
|
+
if (normalized === "all") return items;
|
|
496
|
+
return items.filter((item) => {
|
|
497
|
+
const protocolEvents = Array.isArray(item.protocolEvents)
|
|
498
|
+
? item.protocolEvents
|
|
499
|
+
: [item.event];
|
|
500
|
+
const toolProtocol =
|
|
501
|
+
item.kind === "protocol" &&
|
|
502
|
+
protocolEvents.some((event) => {
|
|
503
|
+
const role = event?.message?.role ?? event?.role;
|
|
504
|
+
return role === "tool" || role === "toolResult";
|
|
505
|
+
});
|
|
506
|
+
const modelFact =
|
|
507
|
+
item.kind === "turn" || (item.kind === "protocol" && !toolProtocol);
|
|
508
|
+
return normalized === "model" ? modelFact : !modelFact;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
483
512
|
function ensureOpenDetailsSet() {
|
|
484
513
|
if (!(uiState.sessionTimelineOpenDetails instanceof Set)) {
|
|
485
514
|
uiState.sessionTimelineOpenDetails = new Set();
|
|
@@ -723,7 +752,26 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
|
723
752
|
}
|
|
724
753
|
|
|
725
754
|
const showProtocol = Boolean(uiState.sessionTimelineShowProtocol);
|
|
755
|
+
const activeFilter = normalizeExecutionRecordFilter(
|
|
756
|
+
uiState.executionRecordFilter,
|
|
757
|
+
);
|
|
726
758
|
const toolbar = el("div", "process-timeline-toolbar");
|
|
759
|
+
const filterGroup = el("div", "process-timeline-filter-group");
|
|
760
|
+
filterGroup.setAttribute("role", "group");
|
|
761
|
+
filterGroup.setAttribute("aria-label", "筛选执行记录");
|
|
762
|
+
for (const [filter, label] of EXECUTION_RECORD_FILTERS) {
|
|
763
|
+
const button = document.createElement("button");
|
|
764
|
+
button.type = "button";
|
|
765
|
+
button.className = "process-timeline-toggle process-timeline-filter";
|
|
766
|
+
button.setAttribute("aria-pressed", String(activeFilter === filter));
|
|
767
|
+
button.textContent = label;
|
|
768
|
+
button.addEventListener("click", () => {
|
|
769
|
+
uiState.executionRecordFilter = filter;
|
|
770
|
+
rerenderSessionTimeline(content, nodeId, options);
|
|
771
|
+
});
|
|
772
|
+
filterGroup.appendChild(button);
|
|
773
|
+
}
|
|
774
|
+
toolbar.appendChild(filterGroup);
|
|
727
775
|
const protocolToggle = document.createElement("button");
|
|
728
776
|
protocolToggle.type = "button";
|
|
729
777
|
protocolToggle.className = "process-timeline-toggle";
|
|
@@ -733,20 +781,15 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
|
733
781
|
: "展开原始协议事件";
|
|
734
782
|
protocolToggle.addEventListener("click", () => {
|
|
735
783
|
uiState.sessionTimelineShowProtocol = !showProtocol;
|
|
736
|
-
|
|
737
|
-
const scrollTop = parent.scrollTop;
|
|
738
|
-
const scrollLeft = parent.scrollLeft;
|
|
739
|
-
while (parent.firstChild) parent.removeChild(parent.firstChild);
|
|
740
|
-
renderSessionTimeline(parent, nodeId, options);
|
|
741
|
-
parent.scrollTop = scrollTop;
|
|
742
|
-
parent.scrollLeft = scrollLeft;
|
|
784
|
+
rerenderSessionTimeline(content, nodeId, options);
|
|
743
785
|
});
|
|
744
786
|
toolbar.appendChild(protocolToggle);
|
|
745
787
|
content.appendChild(toolbar);
|
|
746
788
|
|
|
747
|
-
const timelineItems =
|
|
748
|
-
showProtocol,
|
|
749
|
-
|
|
789
|
+
const timelineItems = filterExecutionRecordItems(
|
|
790
|
+
buildRichTimelineItems(visible.events, { showProtocol }),
|
|
791
|
+
activeFilter,
|
|
792
|
+
);
|
|
750
793
|
const hasEventTimes = visible.events.some((event) =>
|
|
751
794
|
Boolean(sessionEventTimestamp(event)),
|
|
752
795
|
);
|
|
@@ -760,6 +803,13 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
|
760
803
|
);
|
|
761
804
|
}
|
|
762
805
|
|
|
806
|
+
if (timelineItems.length === 0) {
|
|
807
|
+
content.appendChild(
|
|
808
|
+
el("p", "empty", "当前筛选下没有可显示的执行记录。"),
|
|
809
|
+
);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
763
813
|
const ul = el("ul", "process-timeline process-timeline-rich");
|
|
764
814
|
for (let index = 0; index < timelineItems.length; index += 1) {
|
|
765
815
|
const item = timelineItems[index];
|
|
@@ -838,3 +888,18 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
|
838
888
|
content.scrollTop = timelineState.scrollTop;
|
|
839
889
|
}
|
|
840
890
|
}
|
|
891
|
+
|
|
892
|
+
function rerenderSessionTimeline(content, nodeId, options) {
|
|
893
|
+
const scrollTop = content.scrollTop;
|
|
894
|
+
const scrollLeft = content.scrollLeft;
|
|
895
|
+
const preserved = [...content.children].filter((child) =>
|
|
896
|
+
String(child.className ?? "")
|
|
897
|
+
.split(/\s+/)
|
|
898
|
+
.includes("dag-inspector-attempt-bar"),
|
|
899
|
+
);
|
|
900
|
+
while (content.firstChild) content.removeChild(content.firstChild);
|
|
901
|
+
for (const child of preserved) content.appendChild(child);
|
|
902
|
+
renderSessionTimeline(content, nodeId, options);
|
|
903
|
+
content.scrollTop = scrollTop;
|
|
904
|
+
content.scrollLeft = scrollLeft;
|
|
905
|
+
}
|
|
@@ -86,7 +86,7 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
86
86
|
showView("task");
|
|
87
87
|
setBreadcrumb([
|
|
88
88
|
{ label: UI_TEXT.dashboard, href: "#/" },
|
|
89
|
-
{ label: "资源池", href: "
|
|
89
|
+
{ label: "资源池", href: "/#/inspect/pool" },
|
|
90
90
|
{ label: displayLabel },
|
|
91
91
|
]);
|
|
92
92
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { acquireDirLock } from "../../infrastructure/console/dir-lock.js";
|
|
4
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
6
|
import { assertSafeTaskPoolId, getTaskPoolRoot, } from "./run-store.js";
|
|
6
7
|
import { TASK_POOL_STATE_ERROR_CODES, TaskPoolStateError } from "./types.js";
|
|
@@ -19,6 +20,20 @@ export function getAttemptLeasePath(repoRoot, featureId, taskId) {
|
|
|
19
20
|
}
|
|
20
21
|
return leasePath;
|
|
21
22
|
}
|
|
23
|
+
function getAttemptLeaseMutationLockPath(repoRoot, featureId, taskId) {
|
|
24
|
+
assertSafeTaskPoolId(featureId, "featureId");
|
|
25
|
+
assertSafeTaskPoolId(taskId, "taskId");
|
|
26
|
+
return path.join(getAttemptLeasesRoot(repoRoot), "_locks", `${featureId}__${taskId}.lock`);
|
|
27
|
+
}
|
|
28
|
+
async function withAttemptLeaseMutationLock(repoRoot, featureId, taskId, operation) {
|
|
29
|
+
const lock = await acquireDirLock(getAttemptLeaseMutationLockPath(repoRoot, featureId, taskId));
|
|
30
|
+
try {
|
|
31
|
+
return await operation();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
await lock.release();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
22
37
|
function isLeaseRecord(value) {
|
|
23
38
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
24
39
|
return false;
|
|
@@ -30,7 +45,11 @@ function isLeaseRecord(value) {
|
|
|
30
45
|
typeof record.batchRunId === "string" &&
|
|
31
46
|
typeof record.token === "string" &&
|
|
32
47
|
typeof record.acquiredAt === "string" &&
|
|
33
|
-
typeof record.renewedAt === "string"
|
|
48
|
+
typeof record.renewedAt === "string" &&
|
|
49
|
+
(record.revisionId === undefined || typeof record.revisionId === "string") &&
|
|
50
|
+
(record.controllerAnchor === undefined ||
|
|
51
|
+
typeof record.controllerAnchor === "string") &&
|
|
52
|
+
(record.claimId === undefined || typeof record.claimId === "string"));
|
|
34
53
|
}
|
|
35
54
|
export async function readAttemptLease(repoRoot, featureId, taskId) {
|
|
36
55
|
const leasePath = getAttemptLeasePath(repoRoot, featureId, taskId);
|
|
@@ -47,82 +66,94 @@ export async function readAttemptLease(repoRoot, featureId, taskId) {
|
|
|
47
66
|
}
|
|
48
67
|
/**
|
|
49
68
|
* Acquire Task-scoped attempt lease before beginWorkerAttempt.
|
|
50
|
-
* Same identity+
|
|
69
|
+
* Same identity+claim reuse is idempotent; a competing admission or active
|
|
70
|
+
* identity conflicts. The Task-scoped mutation lock closes cross-process
|
|
71
|
+
* read-before-rename and release-vs-successor races.
|
|
51
72
|
*/
|
|
52
73
|
export async function acquireAttemptLease(input) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (existing
|
|
57
|
-
existing.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
return withAttemptLeaseMutationLock(input.repoRoot, input.featureId, input.taskId, async () => {
|
|
75
|
+
const now = input.now ?? new Date();
|
|
76
|
+
const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
77
|
+
if (existing) {
|
|
78
|
+
if (existing.workerRunId === input.workerRunId &&
|
|
79
|
+
existing.batchRunId === input.batchRunId &&
|
|
80
|
+
existing.featureId === input.featureId &&
|
|
81
|
+
existing.taskId === input.taskId &&
|
|
82
|
+
existing.revisionId === input.revisionId &&
|
|
83
|
+
existing.controllerAnchor === input.controllerAnchor &&
|
|
84
|
+
existing.claimId === input.claimId) {
|
|
85
|
+
return { status: "idempotent-reuse", lease: existing };
|
|
86
|
+
}
|
|
87
|
+
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `attempt-lease-conflict for ${input.featureId}/${input.taskId}: held by ${existing.workerRunId}`);
|
|
61
88
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
? {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
const lease = {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
featureId: input.featureId,
|
|
92
|
+
taskId: input.taskId,
|
|
93
|
+
workerRunId: input.workerRunId,
|
|
94
|
+
batchRunId: input.batchRunId,
|
|
95
|
+
acquiredAt: now.toISOString(),
|
|
96
|
+
renewedAt: now.toISOString(),
|
|
97
|
+
token: `atk-${randomBytes(8).toString("hex")}`,
|
|
98
|
+
ownerProcess: { pid: process.pid },
|
|
99
|
+
...(input.revisionId ? { revisionId: input.revisionId } : {}),
|
|
100
|
+
...(input.controllerAnchor
|
|
101
|
+
? { controllerAnchor: input.controllerAnchor }
|
|
102
|
+
: {}),
|
|
103
|
+
...(input.claimId ? { claimId: input.claimId } : {}),
|
|
104
|
+
};
|
|
105
|
+
await mkdir(path.dirname(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId)), {
|
|
106
|
+
recursive: true,
|
|
107
|
+
});
|
|
108
|
+
await writeJsonAtomic(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId), lease, { repoRoot: input.repoRoot });
|
|
109
|
+
const confirmed = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
110
|
+
if (!confirmed) {
|
|
111
|
+
throw new Error(`failed to persist attempt lease for ${input.featureId}/${input.taskId}`);
|
|
112
|
+
}
|
|
113
|
+
if (confirmed.token !== lease.token) {
|
|
114
|
+
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `attempt-lease-conflict race for ${input.featureId}/${input.taskId}`);
|
|
115
|
+
}
|
|
116
|
+
return { status: "acquired", lease: confirmed };
|
|
81
117
|
});
|
|
82
|
-
await writeJsonAtomic(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId), lease, { repoRoot: input.repoRoot });
|
|
83
|
-
const confirmed = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
84
|
-
if (!confirmed) {
|
|
85
|
-
throw new Error(`failed to persist attempt lease for ${input.featureId}/${input.taskId}`);
|
|
86
|
-
}
|
|
87
|
-
if (confirmed.workerRunId !== input.workerRunId) {
|
|
88
|
-
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `attempt-lease-conflict race for ${input.featureId}/${input.taskId}`);
|
|
89
|
-
}
|
|
90
|
-
return { status: "acquired", lease: confirmed };
|
|
91
118
|
}
|
|
92
119
|
export async function renewAttemptLease(input) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
return withAttemptLeaseMutationLock(input.repoRoot, input.featureId, input.taskId, async () => {
|
|
121
|
+
const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
122
|
+
if (!existing) {
|
|
123
|
+
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot renew missing lease for ${input.featureId}/${input.taskId}`);
|
|
124
|
+
}
|
|
125
|
+
if (existing.token !== input.token || existing.workerRunId !== input.workerRunId) {
|
|
126
|
+
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot renew lease: CAS token/identity mismatch for ${input.featureId}/${input.taskId}`);
|
|
127
|
+
}
|
|
128
|
+
const renewed = {
|
|
129
|
+
...existing,
|
|
130
|
+
renewedAt: (input.now ?? new Date()).toISOString(),
|
|
131
|
+
};
|
|
132
|
+
await writeJsonAtomic(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId), renewed, { repoRoot: input.repoRoot });
|
|
133
|
+
return renewed;
|
|
134
|
+
});
|
|
106
135
|
}
|
|
107
136
|
/**
|
|
108
137
|
* Release active lease after terminal/recovery evidence. Moves to lease-history then deletes active locator.
|
|
109
138
|
*/
|
|
110
139
|
export async function releaseAttemptLease(input) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
await withAttemptLeaseMutationLock(input.repoRoot, input.featureId, input.taskId, async () => {
|
|
141
|
+
const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
142
|
+
if (!existing)
|
|
143
|
+
return;
|
|
144
|
+
if (existing.token !== input.token || existing.workerRunId !== input.workerRunId) {
|
|
145
|
+
throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot release lease: CAS token/identity mismatch for ${input.featureId}/${input.taskId}`);
|
|
146
|
+
}
|
|
147
|
+
const historyRoot = path.join(getAttemptLeasesRoot(input.repoRoot), "_history");
|
|
148
|
+
await mkdir(historyRoot, { recursive: true });
|
|
149
|
+
const historyPath = path.join(historyRoot, `${input.featureId}__${input.taskId}__${existing.workerRunId}.json`);
|
|
150
|
+
const activePath = getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId);
|
|
151
|
+
await writeJsonAtomic(historyPath, {
|
|
152
|
+
...existing,
|
|
153
|
+
releasedAt: (input.now ?? new Date()).toISOString(),
|
|
154
|
+
}, { repoRoot: input.repoRoot });
|
|
155
|
+
await unlink(activePath).catch(() => { });
|
|
156
|
+
});
|
|
126
157
|
}
|
|
127
158
|
export function decideAttemptLeaseReclaim(input) {
|
|
128
159
|
if (input.hasTerminalEvidence) {
|
|
@@ -19,6 +19,7 @@ export async function beginWorkerAttemptWithLease(input) {
|
|
|
19
19
|
...(input.controllerAnchor
|
|
20
20
|
? { controllerAnchor: input.controllerAnchor }
|
|
21
21
|
: {}),
|
|
22
|
+
...(input.leaseClaimId ? { claimId: input.leaseClaimId } : {}),
|
|
22
23
|
...(input.now ? { now: input.now } : {}),
|
|
23
24
|
});
|
|
24
25
|
const begun = await beginWorkerAttempt(input);
|
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { executeDagReconcileRun } from "../../workflows/dag/reconcile-run.js";
|
|
4
4
|
import { assessDagRunLiveness, assessDagRunRecoveryEligibility, locateDagRun, readDagRunState, } from "../../workflows/dag/lifecycle.js";
|
|
5
5
|
import { appendTaskPoolEvent, findRunByWorkerRunId, getTaskPoolRoot, readTaskPoolState, writeTaskPoolState, } from "./run-store.js";
|
|
6
|
+
import { readRunOwner } from "./run-owner-store.js";
|
|
7
|
+
import { readAttemptLease, releaseAttemptLease } from "./attempt-lease.js";
|
|
6
8
|
const RECOVERABLE_LIVENESS = new Set([
|
|
7
9
|
"active",
|
|
8
10
|
"node-quiet",
|
|
@@ -110,6 +112,17 @@ export async function reconcileTaskPoolAbandon(input) {
|
|
|
110
112
|
idempotent: false,
|
|
111
113
|
evidencePath: operatorRecovery.evidencePath,
|
|
112
114
|
});
|
|
115
|
+
const lease = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
|
|
116
|
+
if (lease?.workerRunId === input.workerRunId) {
|
|
117
|
+
await releaseAttemptLease({
|
|
118
|
+
repoRoot: input.repoRoot,
|
|
119
|
+
featureId: input.featureId,
|
|
120
|
+
taskId: input.taskId,
|
|
121
|
+
token: lease.token,
|
|
122
|
+
workerRunId: input.workerRunId,
|
|
123
|
+
now: input.now,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
113
126
|
return {
|
|
114
127
|
schemaVersion: 1,
|
|
115
128
|
status: "Failed",
|
|
@@ -201,7 +214,6 @@ export async function markTaskPoolFailed(input) {
|
|
|
201
214
|
};
|
|
202
215
|
}
|
|
203
216
|
async function validateWorkerDagAssociation(input) {
|
|
204
|
-
const run = await validateWorkerRunOwnership(input);
|
|
205
217
|
const dagLocated = await locateDagRun(input.repoRoot, input.dagRunId);
|
|
206
218
|
if (!dagLocated) {
|
|
207
219
|
throw new Error(`dag run not found: ${input.dagRunId}`);
|
|
@@ -210,6 +222,39 @@ async function validateWorkerDagAssociation(input) {
|
|
|
210
222
|
if (dagState.runId !== input.dagRunId) {
|
|
211
223
|
throw new Error(`dag run identity mismatch: expected ${input.dagRunId}, found ${dagState.runId}`);
|
|
212
224
|
}
|
|
225
|
+
let run = await findRunByWorkerRunId(input.repoRoot, input.workerRunId);
|
|
226
|
+
if (!run) {
|
|
227
|
+
const owner = await readRunOwner(input.repoRoot, input.workerRunId);
|
|
228
|
+
const association = dagState.workerAssociation;
|
|
229
|
+
const authority = dagState.continuation?.workerContinuationAuthority;
|
|
230
|
+
if (!owner ||
|
|
231
|
+
owner.featureId !== input.featureId ||
|
|
232
|
+
owner.taskId !== input.taskId ||
|
|
233
|
+
owner.workerRunId !== input.workerRunId ||
|
|
234
|
+
owner.revisionId === undefined ||
|
|
235
|
+
input.dagRunId !== input.workerRunId ||
|
|
236
|
+
association?.workerRunId !== input.workerRunId ||
|
|
237
|
+
association.featureId !== input.featureId ||
|
|
238
|
+
association.taskId !== input.taskId ||
|
|
239
|
+
authority?.childWorkerRunId !== input.workerRunId ||
|
|
240
|
+
authority.parentWorkerRunId === input.workerRunId ||
|
|
241
|
+
authority.revisionId !== owner.revisionId) {
|
|
242
|
+
throw new Error(`worker run not found: ${input.workerRunId}`);
|
|
243
|
+
}
|
|
244
|
+
run = {
|
|
245
|
+
schemaVersion: 1,
|
|
246
|
+
batchRunId: owner.batchRunId,
|
|
247
|
+
workerRunId: owner.workerRunId,
|
|
248
|
+
featureId: owner.featureId,
|
|
249
|
+
taskId: owner.taskId,
|
|
250
|
+
status: "run-error",
|
|
251
|
+
attemptKind: "artifact-continuation",
|
|
252
|
+
recordedAt: owner.registeredAt,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
else if (run.featureId !== input.featureId || run.taskId !== input.taskId) {
|
|
256
|
+
throw new Error(`worker run ownership mismatch: expected ${input.featureId}/${input.taskId}, found ${run.featureId}/${run.taskId}`);
|
|
257
|
+
}
|
|
213
258
|
const linked = await proveWorkerDagLink(input.repoRoot, run, input.workerRunId, input.dagRunId);
|
|
214
259
|
if (!linked) {
|
|
215
260
|
throw new Error(`worker run ${input.workerRunId} is not associated with dag run ${input.dagRunId}`);
|
|
@@ -350,6 +350,8 @@ function eventFromRun(run) {
|
|
|
350
350
|
taskId: run.taskId,
|
|
351
351
|
featureId: run.featureId,
|
|
352
352
|
status: run.status,
|
|
353
|
+
...(run.attemptKind ? { attemptKind: run.attemptKind } : {}),
|
|
354
|
+
...(run.continuation ? { continuation: run.continuation } : {}),
|
|
353
355
|
};
|
|
354
356
|
}
|
|
355
357
|
function isNotFound(error) {
|
|
@@ -47,6 +47,8 @@ export function stateFromRun(run) {
|
|
|
47
47
|
status: projectTaskPoolStatusFromRun(run),
|
|
48
48
|
updatedAt: run.recordedAt,
|
|
49
49
|
workerRunId: run.workerRunId,
|
|
50
|
+
...(run.attemptKind ? { attemptKind: run.attemptKind } : {}),
|
|
51
|
+
...(run.continuation ? { continuation: run.continuation } : {}),
|
|
50
52
|
...(run.runRecordPath ? { lastRunRecordPath: run.runRecordPath } : {}),
|
|
51
53
|
...(run.failure ? { failure: run.failure } : {}),
|
|
52
54
|
...(run.failureV2 ? { failureV2: run.failureV2 } : {}),
|