@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.3

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 (187) hide show
  1. package/CHANGELOG.md +9 -140
  2. package/dist/application/task-lifecycle/advance.js +4 -17
  3. package/dist/application/task-lifecycle/observe.js +29 -43
  4. package/dist/application/task-lifecycle/plan-transitions.js +4 -5
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/commands/client-recovery.js +55 -20
  7. package/dist/commands/new-task.js +5 -0
  8. package/dist/commands/status.js +57 -0
  9. package/dist/commands/task-contract.js +269 -0
  10. package/dist/executors/dag-pi-executor.js +230 -269
  11. package/dist/executors/pi-executor.js +0 -2
  12. package/dist/executors/pi-sdk-executor.js +39 -77
  13. package/dist/executors/shell-executor.js +2 -3
  14. package/dist/executors/shell-write-guard.js +32 -27
  15. package/dist/shared/pi-context-pressure/extension.js +16 -26
  16. package/dist/shared/pi-retry-settings.js +0 -40
  17. package/dist/shared/prompts.js +26 -0
  18. package/dist/task/config.js +2 -0
  19. package/dist/task/contract/validate-draft.js +106 -0
  20. package/dist/task/goal.js +3 -0
  21. package/dist/task/index.js +12 -0
  22. package/dist/task/lifecycle.js +1 -0
  23. package/dist/task/operator/capabilities.js +6 -0
  24. package/dist/task/operator/envelope.js +2 -0
  25. package/dist/task/operator/index.js +5 -0
  26. package/dist/task/operator/registry.js +2 -0
  27. package/dist/task/operator/types.js +1 -0
  28. package/dist/task/paths.js +1 -0
  29. package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
  30. package/dist/task/source-references.js +23 -48
  31. package/dist/task/source-state.js +1 -0
  32. package/dist/task/subagent-guidance.js +1 -0
  33. package/dist/worker/console/chat/assistant-content.js +2 -23
  34. package/dist/worker/console/chat/explore-tools.js +0 -13
  35. package/dist/worker/console/chat/pi-runtime.js +1182 -221
  36. package/dist/worker/console/chat/routes.js +71 -213
  37. package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
  38. package/dist/worker/console/chat/session-catalog.js +0 -32
  39. package/dist/worker/console/chat/session-store.js +6 -22
  40. package/dist/worker/console/chat/shortcuts.js +0 -6
  41. package/dist/worker/console/chat/sift-bridge.js +1 -0
  42. package/dist/worker/console/chat/tool-preview.js +0 -11
  43. package/dist/worker/console/chat/tools.js +0 -6
  44. package/dist/worker/console/console-update-and-init.js +19 -1
  45. package/dist/worker/console/console-update-runtime.js +20 -5
  46. package/dist/worker/console/index.js +20 -0
  47. package/dist/worker/console/operator-actions.js +1 -13
  48. package/dist/worker/console/routes.js +0 -8
  49. package/dist/worker/console/server.js +29 -11
  50. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
  51. package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
  52. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
  53. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
  54. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
  55. package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
  56. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
  66. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
  67. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
  68. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
  69. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
  70. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
  71. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
  77. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
  78. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
  79. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
  80. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
  81. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
  82. package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
  83. package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
  84. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
  85. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
  86. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
  87. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
  88. package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
  89. package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
  90. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
  91. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
  92. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
  93. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
  94. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
  95. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
  96. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
  97. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
  98. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
  99. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
  100. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
  101. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
  102. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
  103. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
  104. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
  105. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
  106. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
  107. package/dist/worker/console/static/index.html +2 -2
  108. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
  109. package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
  110. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
  111. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
  112. package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
  113. package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
  114. package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
  115. package/dist/worker/console/workspace-context.js +1 -21
  116. package/dist/worker/observe/static/router.js +1 -1
  117. package/dist/worker/observe/static/styles.css +4 -7
  118. package/dist/worker/observe/static/views/dag-inspector.js +19 -12
  119. package/dist/worker/observe/static/views/dag.js +2 -2
  120. package/dist/worker/scheduler/index.js +25 -0
  121. package/dist/workflows/dag/facts.js +4 -0
  122. package/dist/workflows/dag/frontend-closeout.js +1 -1
  123. package/dist/workflows/dag/frontend-design-policy.js +1 -2
  124. package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
  125. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  126. package/dist/workflows/dag/frontend-repair.js +76 -55
  127. package/dist/workflows/dag/frontend-review-context.js +1 -2
  128. package/dist/workflows/dag/frontend-review-scopes.js +12 -34
  129. package/dist/workflows/dag/frontend-shape-facts.js +2 -12
  130. package/dist/workflows/dag/frontend-verification-trace.js +42 -8
  131. package/dist/workflows/dag/index.js +6 -0
  132. package/dist/workflows/dag/init-hybrid.js +160 -97
  133. package/dist/workflows/dynamic/index.js +7 -0
  134. package/docs/architecture/runtime-boundaries.md +3 -3
  135. package/docs/templates/frontend-implementation-dag.json +8 -9
  136. package/package.json +3 -7
  137. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  138. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  139. package/skills/frontend-contract/references/contract-protocol.md +1 -3
  140. package/skills/frontend-design-review/SKILL.md +7 -12
  141. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  142. package/skills/frontend-plan/SKILL.md +0 -19
  143. package/skills/frontend-plan/references/decision-contract.md +19 -26
  144. package/skills/frontend-review/SKILL.md +18 -12
  145. package/skills/frontend-review/references/review-findings.md +43 -24
  146. package/dist/shared/pi-provider-recovery.js +0 -182
  147. package/dist/worker/console/chat/browser-automation.js +0 -363
  148. package/dist/worker/console/chat/browser-policy.js +0 -113
  149. package/dist/worker/console/chat/browser-routes.js +0 -162
  150. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
  151. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
  152. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
  153. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
  154. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
  155. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
  156. package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
  157. package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
  158. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
  159. package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
  160. package/dist/worker/console/chat/scm-routes.js +0 -217
  161. package/dist/worker/console/chat/scm-service.js +0 -283
  162. package/dist/worker/console/chat/scm-tools.js +0 -111
  163. package/dist/worker/console/chat/session-mode-view.js +0 -49
  164. package/dist/worker/console/chat/session-mode.js +0 -220
  165. package/dist/worker/console/chat/terminal-routes.js +0 -216
  166. package/dist/worker/console/chat/terminal-sessions.js +0 -348
  167. package/dist/worker/console/chat/terminal-tools.js +0 -211
  168. package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
  169. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
  170. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
  171. package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
  172. package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
  173. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
  174. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
  175. package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
  176. package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
  177. package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
  178. package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
  179. package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
  180. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
  181. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
  182. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
  183. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
  184. package/dist/worker/console/workspace-initialization.js +0 -209
  185. package/dist/workflows/dag/frontend-capacity.js +0 -9
  186. package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
  187. package/dist/workflows/dag/frontend-plan-recovery-policy.js +0 -18
@@ -37,9 +37,6 @@ import { UserQuestionRegistry } from "./chat/user-questions.js";
37
37
  import { TodoListRegistry } from "./chat/todos.js";
38
38
  import { GoalRegistry } from "./chat/goals.js";
39
39
  import { GoalRoundDriver } from "./chat/goal-round-driver.js";
40
- import { TerminalSessionRegistry } from "./chat/terminal-sessions.js";
41
- import { ScmService } from "./chat/scm-service.js";
42
- import { BrowserAutomationService } from "./chat/browser-automation.js";
43
40
  import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
44
41
  import { createOperationEventStore, } from "./operation-sse.js";
45
42
  import { OperationStore } from "../../infrastructure/console/operation-store.js";
@@ -53,7 +50,6 @@ import { probePiReadiness, } from "./pi-readiness.js";
53
50
  import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
54
51
  import { NightAuxTicker } from "./night-aux-ticker.js";
55
52
  import { createObserveRouteContext, } from "../observe/routes.js";
56
- import { WorkspaceInitializationService } from "./workspace-initialization.js";
57
53
  import { WorkspaceRegistry } from "./workspace-registry.js";
58
54
  import { resolveConsoleDefaultTimeoutMs } from "./server.js";
59
55
  /**
@@ -244,7 +240,6 @@ export async function createWorkspaceContext(record, deps) {
244
240
  readiness: readinessCache,
245
241
  getReadiness,
246
242
  refreshReadiness,
247
- workspaceInitialization: new WorkspaceInitializationService({ workspaceId: record.workspaceId, name: record.name, repoRoot, client, operations, events, activeChatTurnCount: () => chatTurnExecutions.size() }),
248
243
  observeBaseUrl: deps.observeBaseUrl,
249
244
  fetchImpl: deps.fetchImpl,
250
245
  getConsoleOrigin: deps.getConsoleOrigin,
@@ -259,9 +254,7 @@ export async function createWorkspaceContext(record, deps) {
259
254
  }
260
255
  },
261
256
  });
262
- const aimaxAuth = new AimaxAuthReader();
263
- await aimaxAuth.getToken();
264
- const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
257
+ const sddDataAlignment = new SddDataAlignmentService(chatStore, new AimaxAuthReader(), deps.fetchImpl);
265
258
  const chatEventEpoch = randomBytes(12).toString("hex");
266
259
  const chatEvents = createChatEventStore({
267
260
  appData,
@@ -292,13 +285,6 @@ export async function createWorkspaceContext(record, deps) {
292
285
  operations,
293
286
  operationEvents: events,
294
287
  });
295
- // Persistent PTY terminal sessions shared by the model tool (terminal) and
296
- // the Console UI terminal panel (HTTP API), keyed per chat session owner.
297
- const terminalRegistry = new TerminalSessionRegistry();
298
- // Workspace-scoped git source control shared by the model tool (scm) and
299
- // the Console UI SCM panel (HTTP API).
300
- const scmService = new ScmService(repoRoot);
301
- const browserAutomation = new BrowserAutomationService();
302
288
  const chatRuntime = new ConsolePiRuntime({
303
289
  cwd: repoRoot,
304
290
  sessionDir: appData.chatSessions,
@@ -308,9 +294,6 @@ export async function createWorkspaceContext(record, deps) {
308
294
  todoRegistry,
309
295
  goalRegistry,
310
296
  sddDataAlignment,
311
- terminalRegistry,
312
- scmService,
313
- browserAutomation,
314
297
  });
315
298
  let chatRouteDeps;
316
299
  const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
@@ -325,9 +308,6 @@ export async function createWorkspaceContext(record, deps) {
325
308
  sddDataAlignment,
326
309
  goalRoundDriver,
327
310
  operationLinker: chatOperationLinker,
328
- terminals: terminalRegistry,
329
- scm: scmService,
330
- browserAutomation,
331
311
  appData,
332
312
  bootToken: deps.bootToken,
333
313
  getConsoleOrigin: deps.getConsoleOrigin,
@@ -204,7 +204,7 @@ export function parseHashRouteWithFilters(hashValue) {
204
204
  if (base.view === "dag") {
205
205
  const params = new URLSearchParams(raw.includes("?") ? raw.slice(raw.indexOf("?") + 1) : "");
206
206
  const nodeId = params.get("node");
207
- if (nodeId) return { ...base, inspector: { nodeId, tab: params.get("tab") || null, executionKey: /^(attempt|pass)-[1-9]\d*$/.test(params.get("attempt") || "") ? params.get("attempt") : null } };
207
+ if (nodeId) return { ...base, inspector: { nodeId, tab: params.get("tab") || "output", executionKey: /^(attempt|pass)-[1-9]\d*$/.test(params.get("attempt") || "") ? params.get("attempt") : null } };
208
208
  }
209
209
  if (base.view === "dags") {
210
210
  const history = parseDagHistoryQuery(raw);
@@ -1281,9 +1281,6 @@ body.is-resizing-dag-graph {
1281
1281
  user-select: none;
1282
1282
  }
1283
1283
  .dag-inspector-header {
1284
- /* Keep the attempt menu above the content's sticky timeline toolbar. */
1285
- position: relative;
1286
- z-index: 6;
1287
1284
  display: grid;
1288
1285
  grid-template-columns: minmax(0, 1fr) auto;
1289
1286
  gap: var(--space-3);
@@ -5404,10 +5401,10 @@ button.copy-command:focus-visible {
5404
5401
  .risk-dag-items { display: grid; gap: 6px; padding-top: 8px; }
5405
5402
  /* The hosted Console also has .panel styles; keep the reader shell flush. */
5406
5403
  #dag-inspector { padding: 0; gap: 0; }
5407
- .dag-inspector-header .dag-inspector-attempt-bar { flex-wrap: wrap; gap: 8px; background: transparent; }
5408
- .dag-inspector-header .dag-inspector-attempt-select { min-width: 112px; flex: 0 0 126px; }
5409
- .dag-inspector-header .dag-inspector-attempt-preview { flex: 1 1 100px; flex-wrap: wrap; padding: 0; }
5410
- .dag-inspector-header .dag-inspector-attempt-category { display: inline; white-space: normal; overflow-wrap: anywhere; }
5404
+ .dag-inspector-header .dag-inspector-attempt-bar { flex-wrap: nowrap; background: transparent; }
5405
+ .dag-inspector-header .dag-inspector-attempt-select { min-width: 0; flex: 0 1 126px; }
5406
+ .dag-inspector-header .dag-inspector-attempt-preview { flex: 1 1 auto; flex-wrap: nowrap; padding: 0; }
5407
+ .dag-inspector-header .dag-inspector-attempt-category,
5411
5408
  .dag-inspector-header .dag-inspector-attempt-duration { display: none; }
5412
5409
  @container (max-width: 560px) {
5413
5410
  .process-timeline-toolbar { display: grid; grid-template-columns: minmax(80px, 1fr) 32px 32px 32px; gap: 4px; }
@@ -518,9 +518,6 @@ function renderDagInspectorForCurrent() {
518
518
 
519
519
  function resetInspectorNodeSelection(dagRunId, nodeId) {
520
520
  uiState.selectedDagNodeId = nodeId;
521
- // Resolve the default after loading this node, rather than inheriting
522
- // another node's tab. Explicit links and later tab clicks still win.
523
- uiState.dagInspectorTab = null;
524
521
  uiState.dagInspectorOpen = true;
525
522
  // Each node opens in live-follow mode by default. The explicit floating
526
523
  // control can pause it without losing the reader's current position.
@@ -3340,7 +3337,6 @@ function inspectorTabDefinitions(node = inspectorSelection.node) {
3340
3337
  const options = node ? attemptSelectorOptions(node) : [];
3341
3338
  const hasPassOnlyHistory = options[0]?.key.startsWith("pass-");
3342
3339
  const tabDefs = DAG_INSPECTOR_READ_ONLY_TABS.filter(([tab]) => {
3343
- if (tab === "lineage") return Boolean(inspectorSelection.run?.continuation?.parentRunId);
3344
3340
  if (tab === "context") return !["shell", "static"].includes(node?.executor) && !hasPassOnlyHistory;
3345
3341
  if (tab === "timeline") return node?.executor !== "static";
3346
3342
  return true;
@@ -3357,9 +3353,6 @@ function activeInspectorTab() {
3357
3353
  const available = inspectorTabDefinitions().map(([tab]) => tab);
3358
3354
  const requested = uiState.dagInspectorTab;
3359
3355
  if (available.includes(requested)) return requested;
3360
- if (requested == null && ["running", "started"].includes(String(inspectorSelection.node?.status ?? "").toLowerCase())) {
3361
- return available.includes("timeline") ? "timeline" : "overview";
3362
- }
3363
3356
  if (requested === "compare" && available.includes("timeline")) return "timeline";
3364
3357
  return ["compare", "context", "timeline"].includes(requested) ? "overview" : "output";
3365
3358
  }
@@ -3590,13 +3583,21 @@ function buildAttemptBar(dagRunId, node) {
3590
3583
 
3591
3584
  const bar = el("div", "dag-inspector-attempt-bar");
3592
3585
  bar.appendChild(el("label", "dag-inspector-attempt-label", "执行分次"));
3593
- const choose = (nextKey) => {
3586
+ const choose = (nextKey, source = "select") => {
3594
3587
  uiState.dagInspectorAttempt = { dagRunId, nodeId: node.nodeId, key: String(nextKey) };
3595
3588
  inspectorNodeSelections.set(`${dagRunId}:${node.nodeId}`, uiState.dagInspectorAttempt);
3596
3589
  renderDagInspectorForCurrent();
3597
3590
  const nextBar = document.querySelector(".dag-inspector-header .dag-inspector-attempt-bar");
3598
- nextBar?.querySelector(".custom-select-trigger")?.focus({ preventScroll: true });
3591
+ const control = nextBar?.querySelector(`[data-attempt-action="${source}"]`);
3592
+ (control && !control.disabled ? control : nextBar?.querySelector(".custom-select-trigger"))?.focus({ preventScroll: true });
3599
3593
  };
3594
+ const selectedIndex = options.findIndex((option) => option.key === selectedKey);
3595
+ const previous = iconButton("上一次尝试", "ri-arrow-left-s-line");
3596
+ previous.type = "button";
3597
+ previous.dataset.attemptAction = "previous";
3598
+ previous.disabled = selectedIndex <= 0;
3599
+ previous.addEventListener("click", () => choose(options[selectedIndex - 1].key, "previous"));
3600
+ bar.appendChild(previous);
3600
3601
  const select = createCustomSelect({
3601
3602
  ariaLabel: "选择执行分次",
3602
3603
  className: "dag-inspector-attempt-select",
@@ -3611,6 +3612,12 @@ function buildAttemptBar(dagRunId, node) {
3611
3612
  // `.value` after assign + change (StubNode handler contract).
3612
3613
  select.value = selectedKey;
3613
3614
  bar.appendChild(select);
3615
+ const next = iconButton("下一次尝试", "ri-arrow-right-s-line");
3616
+ next.type = "button";
3617
+ next.dataset.attemptAction = "next";
3618
+ next.disabled = selectedIndex >= options.length - 1;
3619
+ next.addEventListener("click", () => choose(options[selectedIndex + 1].key, "next"));
3620
+ bar.appendChild(next);
3614
3621
 
3615
3622
  const selected =
3616
3623
  options.find((option) => option.key === selectedKey) ??
@@ -3622,16 +3629,16 @@ function buildAttemptBar(dagRunId, node) {
3622
3629
  if (status) {
3623
3630
  preview.appendChild(el("span", `badge badge-${status.cls}`, status.label));
3624
3631
  }
3625
- if (status?.cls === "failed") {
3632
+ if (status?.cls === "failed" && data.failureCategory) {
3626
3633
  // 仅失败尝试显示原因;部分 runtime 会在成功尝试保留
3627
3634
  // failureCategory="success",不能将其误映射为「其他失败原因」。
3628
3635
  // 未知失败分类仍 fail-closed,raw token 仅保留在 title 诊断中。
3629
3636
  const reason = el(
3630
3637
  "span",
3631
3638
  "dag-inspector-attempt-category",
3632
- data.failureCategory ? `原因 ${attemptFailureLabel(data.failureCategory)}` : "未记录失败原因",
3639
+ `原因 ${attemptFailureLabel(data.failureCategory)}`,
3633
3640
  );
3634
- if (data.failureCategory) reason.setAttribute("title", `failureCategory ${data.failureCategory}`);
3641
+ reason.setAttribute("title", `failureCategory ${data.failureCategory}`);
3635
3642
  preview.appendChild(reason);
3636
3643
  }
3637
3644
  if (data.durationMs !== undefined) {
@@ -263,7 +263,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
263
263
  uiState.sessionEvents = [];
264
264
  uiState.sessionEventIdentity = null;
265
265
  uiState.dagInspectorOpen = false;
266
- uiState.dagInspectorTab = null;
266
+ uiState.dagInspectorTab = "output";
267
267
  uiState.dagInspectorAttempt = null;
268
268
  uiState.dagGraphViewportState = null;
269
269
  uiState.dagTimelineViewportState = null;
@@ -343,7 +343,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
343
343
  if (routeState?.nodeId && routeState.open !== false && nodes.some((node) => node.nodeId === routeState.nodeId)) {
344
344
  uiState.selectedDagNodeId = routeState.nodeId;
345
345
  uiState.dagInspectorOpen = true;
346
- uiState.dagInspectorTab = ["overview", "input", "context", "timeline", "compare", "output", "lineage", "spec-evidence", "prompt-restart"].includes(routeState.tab) ? routeState.tab : null;
346
+ uiState.dagInspectorTab = ["overview", "input", "context", "timeline", "compare", "output", "lineage", "spec-evidence", "prompt-restart"].includes(routeState.tab) ? routeState.tab : "output";
347
347
  uiState.dagInspectorAttempt = { dagRunId, nodeId: routeState.nodeId, key: routeState.executionKey };
348
348
  uiState.sessionEventIdentity = makeSessionEventIdentity(dagRunId, routeState.nodeId);
349
349
  }
@@ -0,0 +1,25 @@
1
+ export * from "./types.js";
2
+ export * from "./paths.js";
3
+ export * from "./lock.js";
4
+ export * from "./trigger.js";
5
+ export * from "./store.js";
6
+ export * from "./lifecycle.js";
7
+ export * from "./doctor.js";
8
+ export * from "./traceability.js";
9
+ export * from "./reservation.js";
10
+ export * from "./workspace-adapter.js";
11
+ export * from "./admission.js";
12
+ export * from "./lease.js";
13
+ export * from "./evidence.js";
14
+ export * from "./night-git-finalizer.js";
15
+ export * from "./dispatcher.js";
16
+ export * from "./scheduler-loop.js";
17
+ export * from "./night-harvest.js";
18
+ export * from "./morning-window.js";
19
+ export * from "./recovery.js";
20
+ export * from "./retry.js";
21
+ export * from "./prepared-attempt-recovery.js";
22
+ export * from "./auto-followup.js";
23
+ export * from "./clock.js";
24
+ export * from "./clock-install.js";
25
+ export { registerSchedulerCommands } from "./cli.js";
@@ -0,0 +1,4 @@
1
+ export * from "./decision-evidence.js";
2
+ export * from "./failure-category.js";
3
+ export * from "./recovery-recommendation.js";
4
+ export * from "../../executors/shell-write-guard.js";
@@ -114,7 +114,7 @@ export function buildFrontendTestHandoff(input) {
114
114
  * required verification evidence. Anything else downgrades to pending.
115
115
  */
116
116
  export function deriveFrontendIntegrationFacts(input) {
117
- const requiredEvidenceSatisfied = input.verificationTargets.some((target) => target.mode === "behavior") &&
117
+ const requiredEvidenceSatisfied = input.verificationTargets.length > 0 &&
118
118
  input.verificationTargets.every((target) => target.status === "ok");
119
119
  if (input.realApiEvidence.present &&
120
120
  input.realApiEvidence.passed &&
@@ -1,12 +1,11 @@
1
1
  import { deterministicSha256, } from "./frontend-implementation-contract.js";
2
2
  import { pathMatchesPattern } from "../../shared/git-progress.js";
3
- import { FRONTEND_MAX_IMPLEMENTATION_FILES } from "./frontend-capacity.js";
4
3
  const SHA256_HEX = /^[a-f0-9]{64}$/;
5
4
  const NPM_PACKAGE_NAME = /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
6
5
  /** Max concrete implementation files a single writeSet may carry before the
7
6
  * plan is asked to split. Kept at 15: beyond this, a single implement node's
8
7
  * read/edit loop inflates provider context toward overflow. */
9
- const MAX_WRITE_SET_FILES = FRONTEND_MAX_IMPLEMENTATION_FILES;
8
+ const MAX_WRITE_SET_FILES = 15;
10
9
  function normalizeRelativePath(value) {
11
10
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
12
11
  }
@@ -345,11 +345,11 @@ export async function deriveFrontendVerifyCommandDirectoryFromRun(runDir) {
345
345
  }
346
346
  /**
347
347
  * A behavior verification target must bind a test file: the trace gate
348
- * binds results by frozen command and executed test file, without requiring
349
- * generated target ids in test titles. Enforced at the plan boundary and
348
+ * proves behavior targets by finding the target id in a real
349
+ * describe/it/test title inside `file`. Enforced at the plan boundary and
350
350
  * at materialization (path shape only — the writer may create the file).
351
351
  *
352
- * Only code files count: fixtures and
352
+ * Only code files count (the gate reads titles from source): fixtures and
353
353
  * data files such as `test/fixtures/data.json` are rejected even under a
354
354
  * test directory, as are helper/support/setup files (`cypress/fixtures/*`,
355
355
  * `tests/support/*`, `setup.*`) that never carry describe/it/test titles.
@@ -2377,7 +2377,7 @@ export async function analyzeFrontendImplementationContract(input) {
2377
2377
  record.commandLabel = entry.label;
2378
2378
  const file = asString(record.file);
2379
2379
  if (entry.mode === "behavior" && file && !isFrontendTestFilePath(file)) {
2380
- fail("retryable-invalid", `invalid-output: verification-target-phase-mismatch: verification target "${vtId}" binds behavior command "${entry.label}" (${entry.commandId}) to non-test file "${file}"; behavior targets must bind a test file (__tests__/, tests?/, e2e/, cypress/, *.test.*, *.spec.*, *.cy.*) so verification can bind runner evidence to the declared test file`, candidateJsonSha256);
2380
+ fail("retryable-invalid", `invalid-output: verification-target-phase-mismatch: verification target "${vtId}" binds behavior command "${entry.label}" (${entry.commandId}) to non-test file "${file}"; behavior targets must bind a test file (__tests__/, tests?/, e2e/, cypress/, *.test.*, *.spec.*, *.cy.*) so the trace gate can match the target id in a real describe/it/test title`, candidateJsonSha256);
2381
2381
  }
2382
2382
  }
2383
2383
  pushAction("resolve-verification-command-ids");
@@ -2509,10 +2509,6 @@ function splitFrontendContractReviewGroup(canonical, fields) {
2509
2509
  if (!Object.hasOwn(canonical, field))
2510
2510
  continue;
2511
2511
  const fieldValue = canonical[field];
2512
- // JSON omits undefined object properties; the index must describe the
2513
- // persisted contract, not phantom keys retained by normalization.
2514
- if (fieldValue === undefined)
2515
- continue;
2516
2512
  if (!Array.isArray(fieldValue)) {
2517
2513
  const candidate = { ...data, [field]: fieldValue };
2518
2514
  if (Object.keys(data).length > 0 &&
@@ -702,10 +702,10 @@ export async function runFrontendPrewriteGate(input) {
702
702
  failureCode: "mock-strategy-outside-allowed",
703
703
  });
704
704
  }
705
- // Behavior verification needs bound Mock evidence. Build-only implementation
706
- // does not claim tested Mock behavior and may defer those commands.
705
+ // Mock command binding (main): a non-not-needed strategy can only be proven
706
+ // by the DAG's frozen Mock verification commands. Block before any write is
707
+ // authorized when none were materialized at generation time.
707
708
  if (mockStrategy !== "not-needed" &&
708
- analysis.canonical.verificationTargets.some((target) => target.mode === "behavior") &&
709
709
  (input.config.mockCommandLabels?.length ?? 0) === 0) {
710
710
  return finalizePrewrite(input, {
711
711
  ...basePending,
@@ -24,50 +24,6 @@ export const frontendCommandCategorySchema = z.enum([
24
24
  "unit-test",
25
25
  "trace",
26
26
  ]);
27
- /**
28
- * Ordered string-blob rules. Order is the contract: the first matching rule wins,
29
- * so rules that share keywords keep their original precedence. `nodeIdIncludes`
30
- * and `blobIncludes` are alternatives within one rule (logical OR).
31
- */
32
- const BLOB_RULES = [
33
- {
34
- result: "contract",
35
- nodeIdIncludes: ["contract"],
36
- blobIncludes: ["contract mismatch", "source binding"],
37
- },
38
- { result: "trace", nodeIdIncludes: ["trace"], blobIncludes: ["trace:"] },
39
- { result: "review", nodeIdIncludes: ["review"] },
40
- {
41
- result: "unit-test",
42
- nodeIdIncludes: ["behavior"],
43
- blobIncludes: ["vitest", "jest", "npm test"],
44
- },
45
- {
46
- result: "typecheck",
47
- blobIncludes: ["typecheck", "tsc"],
48
- blobPatterns: [/error ts\d+/i],
49
- },
50
- { result: "lint", blobIncludes: ["eslint", "lint"] },
51
- // "vite build"/"next build" are subsumed by "build".
52
- { result: "build", blobIncludes: ["build"] },
53
- {
54
- result: "component-test",
55
- blobIncludes: ["testing-library", "component", "render("],
56
- },
57
- {
58
- result: "dependency",
59
- blobIncludes: ["package.json", "unapproved dependency", "npm install"],
60
- },
61
- {
62
- result: "credential",
63
- blobIncludes: ["credential", "api key", "secret"],
64
- },
65
- { result: "deploy", blobIncludes: ["deploy", "production release"] },
66
- {
67
- result: "spec-unclear",
68
- blobIncludes: ["spec unclear", "requirement ambiguous", "needs human"],
69
- },
70
- ];
71
27
  export function classifyFrontendFailure(input) {
72
28
  // AC-2: structured evidence takes priority over string-blob heuristics.
73
29
  if (input.failureCategory === "write-guard") {
@@ -83,21 +39,86 @@ export function classifyFrontendFailure(input) {
83
39
  return "trace";
84
40
  }
85
41
  if (input.commandCategory) {
86
- // "trace" already returned above, so the remaining categories map 1:1.
87
- return input.commandCategory;
42
+ switch (input.commandCategory) {
43
+ case "typecheck":
44
+ return "typecheck";
45
+ case "build":
46
+ return "build";
47
+ case "lint":
48
+ return "lint";
49
+ case "component-test":
50
+ return "component-test";
51
+ case "unit-test":
52
+ return "unit-test";
53
+ }
88
54
  }
89
- // Fallback: ordered string-blob heuristics. Only the shell executor's
90
- // structured category proves a write-boundary failure, so blob text never
91
- // overrides the structured checks above.
55
+ // Fallback: string-blob heuristic, preserved in its original branch order so
56
+ // legacy assertions (error TS2304 → typecheck, write-guard → path, etc.) stay green.
92
57
  const blob = `${input.nodeId}\n${input.failureCategory ?? ""}\n${input.stdout ?? ""}\n${input.stderr ?? ""}`.toLowerCase();
93
- for (const rule of BLOB_RULES) {
94
- if (rule.nodeIdIncludes?.some((keyword) => input.nodeId.includes(keyword)) ||
95
- rule.blobIncludes?.some((keyword) => blob.includes(keyword)) ||
96
- rule.blobPatterns?.some((pattern) => pattern.test(blob))) {
97
- return rule.result;
98
- }
58
+ if (input.nodeId.includes("contract") ||
59
+ blob.includes("contract mismatch") ||
60
+ blob.includes("source binding")) {
61
+ return "contract";
62
+ }
63
+ // Shell verification may contain test-runner logs from unrelated tests. Only
64
+ // the shell executor's structured category proves a write-boundary failure.
65
+ if (input.failureCategory === "write-guard") {
66
+ return "path";
67
+ }
68
+ if (input.nodeId.includes("trace") || blob.includes("trace:")) {
69
+ return "trace";
70
+ }
71
+ if (input.nodeId.includes("review")) {
72
+ return "review";
73
+ }
74
+ // Classify the failed verification command before inspecting its output.
75
+ // Full test output includes unrelated passing-test titles such as
76
+ // "secret-shaped" and "write-guard failure".
77
+ if (blob.includes("vitest") ||
78
+ blob.includes("jest") ||
79
+ blob.includes("npm test") ||
80
+ input.nodeId.includes("behavior")) {
81
+ return "unit-test";
82
+ }
83
+ if (blob.includes("typecheck") ||
84
+ blob.includes("tsc") ||
85
+ /error ts\d+/i.test(blob)) {
86
+ return "typecheck";
87
+ }
88
+ if (blob.includes("eslint") || blob.includes("lint")) {
89
+ return "lint";
90
+ }
91
+ if (blob.includes("build") ||
92
+ blob.includes("vite build") ||
93
+ blob.includes("next build")) {
94
+ return "build";
95
+ }
96
+ if (blob.includes("testing-library") ||
97
+ blob.includes("component") ||
98
+ blob.includes("render(")) {
99
+ return "component-test";
100
+ }
101
+ if (blob.includes("package.json") ||
102
+ blob.includes("unapproved dependency") ||
103
+ blob.includes("npm install")) {
104
+ return "dependency";
105
+ }
106
+ if (blob.includes("credential") ||
107
+ blob.includes("api key") ||
108
+ blob.includes("secret")) {
109
+ return "credential";
110
+ }
111
+ if (blob.includes("deploy") || blob.includes("production release")) {
112
+ return "deploy";
113
+ }
114
+ if (blob.includes("spec unclear") ||
115
+ blob.includes("requirement ambiguous") ||
116
+ blob.includes("needs human")) {
117
+ return "spec-unclear";
99
118
  }
100
119
  if (input.failureCategory === "invalid-output")
101
120
  return "contract";
121
+ if (input.failureCategory === "write-guard")
122
+ return "path";
102
123
  return "unknown";
103
124
  }
@@ -246,8 +246,7 @@ function assertReviewEvidence(input) {
246
246
  const trace = input.verificationTrace;
247
247
  const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
248
248
  const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
249
- const mockEvidenceBound = contract.data.verificationTargets.every((target) => target.mode === "static") ||
250
- contract.data.mockApi.strategy === "not-needed" ||
249
+ const mockEvidenceBound = contract.data.mockApi.strategy === "not-needed" ||
251
250
  (typeof trace?.mockCommandNodeId === "string" && trace.mockCommandNodeId.length > 0 &&
252
251
  Array.isArray(trace?.mockCommandLabels) && trace.mockCommandLabels.length > 0 &&
253
252
  Array.isArray(trace?.mockCommandTexts) && trace.mockCommandTexts.length === trace.mockCommandLabels.length &&
@@ -4,11 +4,6 @@ import path from "node:path";
4
4
  import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
5
5
  import { readCommittedEvents } from "./frontend-typed-event-store.js";
6
6
  import { stageTypedEventFact, adoptTypedEventFact } from "./frontend-typed-event-transaction.js";
7
- const invalidScope = (message) => Object.assign(new Error(`FRONTEND_REVIEW_SCOPE_INVALID: ${message}`), { code: "FRONTEND_REVIEW_SCOPE_INVALID" });
8
- const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9
- const isArtifactReference = (value) => isRecord(value)
10
- && typeof value.path === "string" && value.path.length > 0
11
- && typeof value.sha256 === "string" && /^[a-f0-9]{64}$/.test(value.sha256);
12
7
  const hash = (text) => createHash("sha256").update(text).digest("hex");
13
8
  /** The section index is navigation; reconstruct coverage against the canonical source before using it. */
14
9
  export async function loadFrontendReviewScopes(runDir, phase, targetBytes) {
@@ -17,7 +12,7 @@ export async function loadFrontendReviewScopes(runDir, phase, targetBytes) {
17
12
  async function read(relative, expected) {
18
13
  const absolute = path.resolve(root, relative);
19
14
  if (!absolute.startsWith(root + path.sep) || (await lstat(absolute)).isSymbolicLink() || !(await realpath(absolute)).startsWith(root + path.sep))
20
- throw invalidScope("artifact escaped run");
15
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: artifact escaped run");
21
16
  const text = await readFile(absolute, "utf8");
22
17
  const sha256 = hash(text);
23
18
  if (expected && sha256 !== expected)
@@ -35,62 +30,45 @@ export async function loadFrontendReviewScopes(runDir, phase, targetBytes) {
35
30
  throw error;
36
31
  }
37
32
  const index = JSON.parse(indexArtifact.text);
38
- if (!isRecord(index) || !isArtifactReference(index.contract) || !Array.isArray(index.sections))
39
- throw invalidScope("malformed contract index");
40
33
  const canonicalArtifact = await read(index.contract.path, index.contract.sha256);
41
34
  const canonical = JSON.parse(canonicalArtifact.text);
42
- if (!isRecord(canonical))
43
- throw invalidScope("canonical contract must be an object");
44
35
  const covered = new Map();
45
36
  const sections = [];
46
37
  const ids = new Set();
47
38
  for (const entry of index.sections) {
48
- if (!isRecord(entry) || !isArtifactReference(entry) || !Array.isArray(entry.slices))
49
- throw invalidScope("malformed section index entry");
50
39
  if (ids.has(entry.path))
51
- throw invalidScope("duplicate section identity");
40
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: duplicate section identity");
52
41
  ids.add(entry.path);
53
42
  const section = await read(entry.path, entry.sha256);
54
43
  const payload = JSON.parse(section.text);
55
- if (!isRecord(payload) || !Array.isArray(payload.slices) || !isRecord(payload.data))
56
- throw invalidScope(`malformed section: ${entry.path}`);
57
44
  if (payload.contractSha256 !== canonicalArtifact.sha256 || sha256OfCanonicalJson(payload.slices) !== sha256OfCanonicalJson(entry.slices))
58
- throw invalidScope("unbound section");
45
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: unbound section");
59
46
  for (const slice of payload.slices) {
60
- if (!isRecord(slice) || typeof slice.field !== "string" || !Object.hasOwn(canonical, slice.field) || !Object.hasOwn(payload.data, slice.field))
61
- throw invalidScope(`missing or invalid field reference in ${entry.path}: ${String(slice?.field)}`);
62
47
  const value = canonical[slice.field];
48
+ const range = Array.isArray(value) ? value.slice(slice.start, slice.end) : value;
49
+ if (sha256OfCanonicalJson(range) !== sha256OfCanonicalJson(payload.data[slice.field]))
50
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: section differs from canonical obligation");
63
51
  const positions = covered.get(slice.field) ?? new Set();
64
52
  if (Array.isArray(value)) {
65
53
  if (!Number.isInteger(slice.start) || !Number.isInteger(slice.end) || slice.start < 0 || slice.end < slice.start || slice.end > value.length || slice.total !== value.length)
66
- throw invalidScope("invalid array coverage");
67
- if ((value.length === 0 && covered.has(slice.field)) || (value.length > 0 && slice.start === slice.end))
68
- throw invalidScope(`empty or duplicate array coverage: ${slice.field}`);
54
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: invalid array coverage");
69
55
  for (let i = slice.start; i < slice.end; i++) {
70
56
  if (positions.has(i))
71
- throw invalidScope("overlapping coverage");
57
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: overlapping coverage");
72
58
  positions.add(i);
73
59
  }
74
60
  }
75
61
  else {
76
- if (["start", "end", "total"].some(key => Object.hasOwn(slice, key)))
77
- throw invalidScope(`scalar field has array range: ${slice.field}`);
78
62
  if (positions.size)
79
- throw invalidScope("duplicate field");
63
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: duplicate field");
80
64
  positions.add(0);
81
65
  }
82
- const range = Array.isArray(value) ? value.slice(slice.start, slice.end) : value;
83
- if (sha256OfCanonicalJson(range) !== sha256OfCanonicalJson(payload.data[slice.field]))
84
- throw invalidScope(`section differs from canonical obligation: ${slice.field}`);
85
66
  covered.set(slice.field, positions);
86
67
  }
87
- const sectionFields = new Set(payload.slices.map(slice => slice.field));
88
- if (Object.keys(payload.data).some(field => !sectionFields.has(field)))
89
- throw invalidScope(`unindexed section data: ${entry.path}`);
90
68
  sections.push(section);
91
69
  }
92
70
  if (Object.keys(canonical).some(field => !covered.has(field) || covered.get(field).size !== (Array.isArray(canonical[field]) ? canonical[field].length : 1)))
93
- throw invalidScope("canonical field coverage incomplete");
71
+ throw Error("FRONTEND_REVIEW_SCOPE_INVALID: canonical field coverage incomplete");
94
72
  const scopes = Buffer.byteLength(canonicalArtifact.text) <= targetBytes ? [canonicalArtifact] : sections;
95
73
  if (phase === "review") {
96
74
  const context = await read("contracts/frontend-review-context.json");
@@ -121,11 +99,11 @@ export function createFrontendReviewScopeProtocol(input) {
121
99
  committedFacts: () => readCommittedEvents(input.getStore(), input.attemptId),
122
100
  assertComplete() { const missing = input.inventory?.scopes.filter(s => !completedScopeIds().has(s.id)) ?? []; if (missing.length)
123
101
  throw Object.assign(Error(`FRONTEND_REVIEW_SCOPE_INCOMPLETE: ${missing.map(s => s.id).join(", ")}`), { code: "FRONTEND_REVIEW_SCOPE_INCOMPLETE" }); },
124
- customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1, enum: input.inventory.scopes.map(scope => scope.id) } }, required: ["id"], additionalProperties: false },
102
+ customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1 } }, required: ["id"], additionalProperties: false },
125
103
  async execute(callId, params) {
126
104
  const scope = input.inventory.scopes.find(s => s.id === params.id);
127
105
  if (!scope || !active.has(params.id))
128
- return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed; use an exact activeScopeIds entry, not the node name", activeScopeIds: [...active], completedScopeIds: [...completedScopeIds()] });
106
+ return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed" });
129
107
  await input.inventory.validate();
130
108
  const store = input.getStore();
131
109
  const requestId = `${input.attemptId}:${callId}:${randomUUID()}`;
@@ -1,5 +1,4 @@
1
1
  import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
2
- import { FRONTEND_MAX_IMPLEMENTATION_FILES, frontendConcreteDeliveryFiles } from "./frontend-capacity.js";
3
2
  import { buildFrontendShapeTransitionCapsule, deriveFrontendRuntimeShapeFacts, evaluateFrontendShapeEscalation, parseFrontendShapeTransitionCapsule, } from "./frontend-shape.js";
4
3
  import { createTypedEventStore, readCommittedEvents, } from "./frontend-typed-event-store.js";
5
4
  import { adoptTypedEventFact, stageTypedEventFact, } from "./frontend-typed-event-transaction.js";
@@ -223,13 +222,7 @@ function extractRuntimeShapeEvidence(input) {
223
222
  !sourceText.includes(path) &&
224
223
  /(?:\.[A-Za-z0-9]+|\*\*)$/.test(path));
225
224
  const routes = canonicalStrings(text.match(/\/(?:[A-Za-z0-9_:[\]-]+\/?)+/g) ?? []);
226
- // The frozen delivery inventory is available before Plan. Do not spend
227
- // coverage/UX sessions building a contract the same capacity gate will reject.
228
- // allowedPaths and text mentions are permissions/evidence, never this count.
229
- const skeleton = input.tasksById.get("frontend-plan-pi")?.structuredContractOutput?.skeleton;
230
- const deliveryFiles = frontendConcreteDeliveryFiles(skeleton?.targets?.files);
231
- const capacitySplit = deliveryFiles.length > FRONTEND_MAX_IMPLEMENTATION_FILES;
232
- const splitRequired = capacitySplit || /["']splitRequired["']\s*:\s*true|\bsplit-required\s+shape\b/i.test(text);
225
+ const splitRequired = /["']splitRequired["']\s*:\s*true|\bsplit-required\s+shape\b/i.test(text);
233
226
  return {
234
227
  sourceNodeId: input.sourceNodeId,
235
228
  sourceNodeDigest: computeFrontendShapeNodeDigest(node),
@@ -239,10 +232,7 @@ function extractRuntimeShapeEvidence(input) {
239
232
  targetPaths,
240
233
  routes,
241
234
  splitRequired,
242
- evidenceRefs: [
243
- ...(capacitySplit ? ["dag:frontend-plan-pi:structuredContractOutput.skeleton.targets.files"] : []),
244
- `node:${input.sourceNodeId}:stdout`,
245
- ],
235
+ evidenceRefs: [`node:${input.sourceNodeId}:stdout`],
246
236
  };
247
237
  }
248
238
  /** Commit a typed, structured runtime fact immediately after a canonical source