@tea-agent/loop-agent 0.39.2 → 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.
Files changed (215) hide show
  1. package/CHANGELOG.md +133 -92
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +12 -0
  6. package/dist/cli/program.js +23 -1
  7. package/dist/commands/dag-artifact.js +284 -0
  8. package/dist/commands/dag-context.js +184 -0
  9. package/dist/commands/dag-rerun.js +206 -1
  10. package/dist/executors/dag-pi-executor.js +108 -19
  11. package/dist/executors/pi-executor.js +4 -2
  12. package/dist/executors/pi-sdk-executor.js +49 -1
  13. package/dist/executors/pi-writer-tool-policy.js +57 -1
  14. package/dist/executors/shell-executor.js +42 -2
  15. package/dist/executors/workspace-write-snapshot.js +68 -0
  16. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  17. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  18. package/dist/infrastructure/console/context-export-store.js +160 -0
  19. package/dist/infrastructure/console/dir-lock.js +132 -0
  20. package/dist/infrastructure/harness/artifact-store.js +10 -1
  21. package/dist/infrastructure/harness/atomic-write.js +35 -2
  22. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  23. package/dist/shared/context-transfer.js +418 -0
  24. package/dist/shared/operator/capabilities.js +14 -0
  25. package/dist/shared/operator/safe-run-summary.js +1 -0
  26. package/dist/shared/path-safety.js +93 -0
  27. package/dist/shared/preview.js +28 -4
  28. package/dist/task/config-types.js +10 -0
  29. package/dist/worker/console/chat/chat-event-store.js +77 -7
  30. package/dist/worker/console/chat/context-panel.js +7 -0
  31. package/dist/worker/console/chat/deferred-turn.js +12 -0
  32. package/dist/worker/console/chat/pi-runtime.js +331 -18
  33. package/dist/worker/console/chat/provider-error.js +98 -0
  34. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  35. package/dist/worker/console/chat/routes.js +389 -56
  36. package/dist/worker/console/chat/session-stats.js +179 -0
  37. package/dist/worker/console/chat/session-store.js +94 -64
  38. package/dist/worker/console/chat/todos.js +115 -0
  39. package/dist/worker/console/chat/tool-preview.js +42 -0
  40. package/dist/worker/console/chat/turn-process.js +20 -55
  41. package/dist/worker/console/chat/user-questions.js +266 -0
  42. package/dist/worker/console/chat/workspace-landing.js +79 -0
  43. package/dist/worker/console/console-update-runtime.js +1 -1
  44. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  45. package/dist/worker/console/dag-confirmation.js +1 -1
  46. package/dist/worker/console/dag-execution-receipt.js +1 -1
  47. package/dist/worker/console/doctor.js +1 -1
  48. package/dist/worker/console/draft-store.js +1 -1
  49. package/dist/worker/console/human-gate-token.js +1 -1
  50. package/dist/worker/console/index.js +3 -3
  51. package/dist/worker/console/interview/assessment.js +1 -1
  52. package/dist/worker/console/interview/session.js +1 -1
  53. package/dist/worker/console/native-directory-picker.js +13 -0
  54. package/dist/worker/console/operation-runner.js +45 -4
  55. package/dist/worker/console/operation-sse.js +1 -1
  56. package/dist/worker/console/operation-wait.js +1 -1
  57. package/dist/worker/console/operator-actions.js +104 -3
  58. package/dist/worker/console/operator-surface-health.js +1 -1
  59. package/dist/worker/console/operator-user-error.js +128 -0
  60. package/dist/worker/console/routes.js +927 -8
  61. package/dist/worker/console/security.js +39 -4
  62. package/dist/worker/console/server.js +532 -187
  63. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
  64. package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
  65. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
  66. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
  67. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
  68. package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
  69. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
  78. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
  79. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
  80. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
  81. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
  83. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
  84. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
  89. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
  90. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
  91. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
  92. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
  93. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
  94. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
  95. package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
  96. package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
  97. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
  98. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
  99. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
  100. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
  101. package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
  102. package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
  103. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
  104. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
  105. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
  106. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
  107. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
  108. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
  109. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
  110. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
  111. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
  112. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
  113. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
  114. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
  115. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
  116. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
  117. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
  118. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
  119. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
  120. package/dist/worker/console/static/index.html +7 -2
  121. package/dist/worker/console/static-src/app/console-types.js +29 -4
  122. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  123. package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
  124. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  125. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  126. package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
  127. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  128. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  129. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  130. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  131. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  132. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  133. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  134. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  135. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  136. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  137. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  138. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  139. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  140. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  141. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  142. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  143. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  144. package/dist/worker/console/workspace-context.js +260 -0
  145. package/dist/worker/console/workspace-registry.js +214 -0
  146. package/dist/worker/continuation/worker-continuation.js +278 -0
  147. package/dist/worker/observe/health.js +1 -1
  148. package/dist/worker/observe/node-transparency.js +572 -0
  149. package/dist/worker/observe/routes.js +51 -1
  150. package/dist/worker/observe/server.js +4 -0
  151. package/dist/worker/observe/static/api.js +94 -10
  152. package/dist/worker/observe/static/app.js +18 -70
  153. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  154. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  155. package/dist/worker/observe/static/index.html +1 -1
  156. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  157. package/dist/worker/observe/static/operator-chrome.css +145 -20
  158. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  159. package/dist/worker/observe/static/operator-chrome.js +330 -100
  160. package/dist/worker/observe/static/router.d.ts +46 -0
  161. package/dist/worker/observe/static/router.js +25 -1
  162. package/dist/worker/observe/static/state.d.ts +50 -0
  163. package/dist/worker/observe/static/state.js +175 -1
  164. package/dist/worker/observe/static/styles.css +1039 -128
  165. package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
  166. package/dist/worker/observe/static/views/dag.js +2 -1
  167. package/dist/worker/observe/static/views/dashboard.js +6 -4
  168. package/dist/worker/observe/static/views/night.js +2 -2
  169. package/dist/worker/observe/static/views/pool.js +1 -0
  170. package/dist/worker/observe/static/views/run.js +3 -1
  171. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  172. package/dist/worker/observe/static/views/task.js +1 -1
  173. package/dist/worker/pool/attempt-lease.js +97 -66
  174. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  175. package/dist/worker/pool/reconcile.js +46 -1
  176. package/dist/worker/pool/run-store.js +2 -0
  177. package/dist/worker/pool/state-projection.js +2 -0
  178. package/dist/workflows/dag/artifact-bindings.js +149 -0
  179. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  180. package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
  181. package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
  182. package/dist/workflows/dag/context-receipt.js +305 -0
  183. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  184. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  185. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  186. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  187. package/dist/workflows/dag/init-hybrid.js +178 -47
  188. package/dist/workflows/dag/interrupt-request.js +3 -3
  189. package/dist/workflows/dag/lifecycle.js +3 -3
  190. package/dist/workflows/dag/node-execution.js +112 -27
  191. package/dist/workflows/dag/path-safety.js +1 -0
  192. package/dist/workflows/dag/prompt.js +29 -4
  193. package/dist/workflows/dag/rerun-plan.js +272 -0
  194. package/dist/workflows/dag/rerun-run.js +390 -12
  195. package/dist/workflows/dag/skill-snapshot.js +1 -0
  196. package/dist/workflows/dag/types.js +203 -8
  197. package/docs/README.md +1 -0
  198. package/docs/architecture/runtime-boundaries.md +6 -6
  199. package/docs/operations/local-development-environment.md +21 -0
  200. package/docs/templates/agent-dag.schema.json +50 -0
  201. package/docs/templates/backend-test-dag.json +265 -237
  202. package/harness.json +3 -3
  203. package/package.json +5 -1
  204. package/skills/loop-agent/references/command-reference.md +6 -0
  205. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
  206. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
  207. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
  208. package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
  209. package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
  210. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
  211. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
  212. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  213. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  214. /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
  215. /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 = "#/night";
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
- }, dashboardPollDelay());
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: "#/night" },
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 fetch(artifactUrl(artifactPath));
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
- const parent = content;
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 = buildRichTimelineItems(visible.events, {
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: "#/pool" },
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+token reuse is idempotent; different active identity → conflict.
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
- const now = input.now ?? new Date();
54
- const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
55
- if (existing) {
56
- if (existing.workerRunId === input.workerRunId &&
57
- existing.batchRunId === input.batchRunId &&
58
- existing.featureId === input.featureId &&
59
- existing.taskId === input.taskId) {
60
- return { status: "idempotent-reuse", lease: existing };
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
- throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `attempt-lease-conflict for ${input.featureId}/${input.taskId}: held by ${existing.workerRunId}`);
63
- }
64
- const lease = {
65
- schemaVersion: 1,
66
- featureId: input.featureId,
67
- taskId: input.taskId,
68
- workerRunId: input.workerRunId,
69
- batchRunId: input.batchRunId,
70
- acquiredAt: now.toISOString(),
71
- renewedAt: now.toISOString(),
72
- token: `atk-${randomBytes(8).toString("hex")}`,
73
- ownerProcess: { pid: process.pid },
74
- ...(input.revisionId ? { revisionId: input.revisionId } : {}),
75
- ...(input.controllerAnchor
76
- ? { controllerAnchor: input.controllerAnchor }
77
- : {}),
78
- };
79
- await mkdir(path.dirname(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId)), {
80
- recursive: true,
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
- const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
94
- if (!existing) {
95
- throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot renew missing lease for ${input.featureId}/${input.taskId}`);
96
- }
97
- if (existing.token !== input.token || existing.workerRunId !== input.workerRunId) {
98
- throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot renew lease: CAS token/identity mismatch for ${input.featureId}/${input.taskId}`);
99
- }
100
- const renewed = {
101
- ...existing,
102
- renewedAt: (input.now ?? new Date()).toISOString(),
103
- };
104
- await writeJsonAtomic(getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId), renewed, { repoRoot: input.repoRoot });
105
- return renewed;
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
- const existing = await readAttemptLease(input.repoRoot, input.featureId, input.taskId);
112
- if (!existing)
113
- return;
114
- if (existing.token !== input.token || existing.workerRunId !== input.workerRunId) {
115
- throw new TaskPoolStateError(TASK_POOL_STATE_ERROR_CODES.CONFLICT, `cannot release lease: CAS token/identity mismatch for ${input.featureId}/${input.taskId}`);
116
- }
117
- const historyRoot = path.join(getAttemptLeasesRoot(input.repoRoot), "_history");
118
- await mkdir(historyRoot, { recursive: true });
119
- const historyPath = path.join(historyRoot, `${input.featureId}__${input.taskId}__${existing.workerRunId}.json`);
120
- const activePath = getAttemptLeasePath(input.repoRoot, input.featureId, input.taskId);
121
- await writeJsonAtomic(historyPath, {
122
- ...existing,
123
- releasedAt: (input.now ?? new Date()).toISOString(),
124
- }, { repoRoot: input.repoRoot });
125
- await unlink(activePath).catch(() => { });
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 } : {}),