@tea-agent/loop-agent 0.41.1 → 0.42.0-next.10

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 (302) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +397 -5
  3. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  4. package/dist/adapters/context-transfer/pi-session.js +61 -0
  5. package/dist/application/dag/generate-task-dag.js +75 -19
  6. package/dist/application/dag/run-dag.js +41 -0
  7. package/dist/application/task-lifecycle/advance.js +24 -5
  8. package/dist/application/task-lifecycle/observe.js +171 -17
  9. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  10. package/dist/application/task-lifecycle/recommendations.js +13 -2
  11. package/dist/build-stamp.json +3 -3
  12. package/dist/cli/command-definitions.js +19 -0
  13. package/dist/cli/program.js +29 -2
  14. package/dist/commands/client-recovery.js +3 -0
  15. package/dist/commands/dag-artifact.js +284 -0
  16. package/dist/commands/dag-context.js +184 -0
  17. package/dist/commands/dag-follow-up.js +138 -0
  18. package/dist/commands/dag-rerun-task.js +2 -0
  19. package/dist/commands/dag-rerun.js +206 -1
  20. package/dist/commands/init.js +27 -1
  21. package/dist/commands/task-advance.js +19 -0
  22. package/dist/executors/dag-pi-executor.js +4013 -86
  23. package/dist/executors/pi-executor.js +15 -4
  24. package/dist/executors/pi-read-budget-policy.js +239 -0
  25. package/dist/executors/pi-sdk-executor.js +3 -0
  26. package/dist/executors/shell-executor.js +978 -188
  27. package/dist/executors/shell-write-guard.js +7 -0
  28. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  29. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  30. package/dist/infrastructure/console/context-export-store.js +160 -0
  31. package/dist/infrastructure/console/dir-lock.js +132 -0
  32. package/dist/infrastructure/console/operation-store.js +417 -0
  33. package/dist/infrastructure/harness/artifact-store.js +10 -1
  34. package/dist/infrastructure/harness/atomic-write.js +12 -2
  35. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  36. package/dist/shared/context-transfer.js +418 -0
  37. package/dist/shared/dag-failure-category.js +12 -0
  38. package/dist/shared/openspec-spec.js +70 -4
  39. package/dist/shared/operator/capabilities.js +21 -0
  40. package/dist/shared/operator/safe-run-summary.js +1 -0
  41. package/dist/shared/path-safety.js +93 -0
  42. package/dist/shared/preview.js +28 -4
  43. package/dist/task/config-types.js +107 -6
  44. package/dist/task/contract/adopt.js +4 -0
  45. package/dist/task/contract/import-revision.js +4 -0
  46. package/dist/task/contract/project.js +3 -0
  47. package/dist/task/contract/schema.js +2 -1
  48. package/dist/task/frontend-project-capability.js +203 -20
  49. package/dist/task/runtime.js +5 -2
  50. package/dist/task/source-prepare/build-draft.js +3 -3
  51. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  52. package/dist/task/source-prepare/prepare.js +126 -21
  53. package/dist/task/source-prepare/semantic-intake.js +6 -2
  54. package/dist/task/source-references.js +22 -1
  55. package/dist/task/task-demand-routing.js +3 -0
  56. package/dist/worker/console/chat/chat-event-store.js +2 -2
  57. package/dist/worker/console/chat/model-resolver.js +7 -4
  58. package/dist/worker/console/chat/pi-runtime.js +86 -5
  59. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  60. package/dist/worker/console/chat/routes.js +136 -53
  61. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  62. package/dist/worker/console/chat/session-catalog.js +2 -0
  63. package/dist/worker/console/chat/session-store.js +43 -1
  64. package/dist/worker/console/chat/shortcuts.js +18 -2
  65. package/dist/worker/console/chat/user-questions.js +1 -1
  66. package/dist/worker/console/console-update-runtime.js +1 -1
  67. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  68. package/dist/worker/console/dag-confirmation.js +1 -1
  69. package/dist/worker/console/dag-execution-receipt.js +1 -1
  70. package/dist/worker/console/doctor.js +1 -1
  71. package/dist/worker/console/draft-store.js +1 -1
  72. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  73. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  74. package/dist/worker/console/human-gate-token.js +1 -1
  75. package/dist/worker/console/index.js +6 -3
  76. package/dist/worker/console/interview/assessment.js +1 -1
  77. package/dist/worker/console/interview/session.js +1 -1
  78. package/dist/worker/console/operation-runner.js +45 -4
  79. package/dist/worker/console/operation-sse.js +1 -1
  80. package/dist/worker/console/operation-wait.js +1 -1
  81. package/dist/worker/console/operator-actions.js +273 -10
  82. package/dist/worker/console/operator-surface-health.js +1 -1
  83. package/dist/worker/console/operator-user-error.js +169 -0
  84. package/dist/worker/console/pi-plugins.js +57 -0
  85. package/dist/worker/console/pi-readiness.js +3 -2
  86. package/dist/worker/console/routes.js +637 -8
  87. package/dist/worker/console/security.js +35 -0
  88. package/dist/worker/console/server.js +29 -4
  89. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D7oU983t.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  90. package/dist/worker/console/static/assets/{arc-BrKSRdFS.js → arc-COptKq2S.js} +1 -1
  91. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CiXV7L56.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  92. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D_Hs_gIG.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  93. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DbM-Lqqh.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  94. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  95. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D1M8uqJw.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-4D3RFHT6.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CdwpY_n9.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BLeLZ9PG.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CeRNEMvc.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjkHP8Vy.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  101. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B54ZUQY-.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  102. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-4bKaMVmx.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Cyjfznu_.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CDN-zGFV.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  105. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  106. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  107. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-HxJmz411.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  108. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DtSt5K2w.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  109. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-VtkSpRmG.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  110. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bj4DT4du.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  111. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Bpuk6BmP.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  112. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BHYGv_yO.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  113. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DEx3WwnT.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  114. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cj0dS5e8.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  115. package/dist/worker/console/static/assets/{diagram-UB23O5K3-B97h8l21.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  116. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DMSFAhBd.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  117. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DhVOCbt6.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  118. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-GlakITWL.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  119. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-VxPpu9eQ.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  120. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  121. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  122. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  123. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXYuU4Uw.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  124. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C34erCbU.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  125. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DZCAiOU3.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  126. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C9gaTdUm.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  127. package/dist/worker/console/static/assets/{linear-CvI0Z17U.js → linear-D59YJ9kB.js} +1 -1
  128. package/dist/worker/console/static/assets/{mermaid.core-DMMSIx7j.js → mermaid.core-Bl13LpNa.js} +5 -5
  129. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BKKP0EO6.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  130. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-C3LiFbl5.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  131. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  132. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DNthMjzc.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  133. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-miFsr4Tv.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  134. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-yXUncmn6.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  135. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DLeWKLak.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  136. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CI3he1R3.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  137. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B7jfDkS5.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  138. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BmrZod7J.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  139. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  140. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CDOlHVSY.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  141. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  142. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cz-XX1gy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  143. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-caV4cI8C.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  144. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-dImPlnIO.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  145. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CtRYVJcn.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  146. package/dist/worker/console/static/index.html +7 -2
  147. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  148. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  149. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  150. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  151. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  152. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  153. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  154. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  155. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  156. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  157. package/dist/worker/console/static-src/shell/console-update-reload.js +52 -0
  158. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  159. package/dist/worker/console/workspace-context.js +142 -2
  160. package/dist/worker/console/workspace-registry.js +2 -2
  161. package/dist/worker/continuation/worker-continuation.js +278 -0
  162. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  163. package/dist/worker/observe/health.js +1 -1
  164. package/dist/worker/observe/node-transparency.js +572 -0
  165. package/dist/worker/observe/routes.js +55 -1
  166. package/dist/worker/observe/static/api.js +69 -5
  167. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  168. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  169. package/dist/worker/observe/static/dag-helpers.js +4 -2
  170. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  171. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  172. package/dist/worker/observe/static/kpi.js +2 -2
  173. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  174. package/dist/worker/observe/static/operator-chrome.js +37 -23
  175. package/dist/worker/observe/static/relations.js +2 -2
  176. package/dist/worker/observe/static/router.d.ts +12 -1
  177. package/dist/worker/observe/static/router.js +48 -6
  178. package/dist/worker/observe/static/run-processing.js +4 -2
  179. package/dist/worker/observe/static/shell-chrome.js +2 -2
  180. package/dist/worker/observe/static/state.js +8 -1
  181. package/dist/worker/observe/static/styles.css +1092 -86
  182. package/dist/worker/observe/static/views/dag-inspector.js +1228 -64
  183. package/dist/worker/observe/static/views/dag.js +10 -2
  184. package/dist/worker/observe/static/views/dags.js +3 -1
  185. package/dist/worker/observe/static/views/dashboard.js +14 -6
  186. package/dist/worker/observe/static/views/pool.js +7 -2
  187. package/dist/worker/observe/static/views/run.js +12 -3
  188. package/dist/worker/observe/static/views/session-timeline.js +142 -18
  189. package/dist/worker/observe/static/views/task.js +7 -2
  190. package/dist/worker/outcomes/adapters.js +19 -0
  191. package/dist/worker/outcomes/types.js +1 -0
  192. package/dist/worker/pool/attempt-lease.js +97 -66
  193. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  194. package/dist/worker/pool/reconcile.js +46 -1
  195. package/dist/worker/pool/run-store.js +2 -0
  196. package/dist/worker/pool/state-projection.js +2 -0
  197. package/dist/worker/task-spec/workflow-routing.js +8 -3
  198. package/dist/workflows/dag/artifact-bindings.js +149 -0
  199. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  200. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  201. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  202. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  203. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  204. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  205. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  206. package/dist/workflows/dag/budget-enforcement.js +10 -0
  207. package/dist/workflows/dag/context-receipt.js +305 -0
  208. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  209. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  210. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  211. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  212. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  213. package/dist/workflows/dag/frontend-closeout.js +221 -0
  214. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  215. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  216. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  217. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  218. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  219. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  220. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  221. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  222. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  223. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  224. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  225. package/dist/workflows/dag/frontend-repair.js +1 -432
  226. package/dist/workflows/dag/frontend-review-context.js +261 -15
  227. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  228. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  229. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  230. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  231. package/dist/workflows/dag/frontend-shape.js +435 -0
  232. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  233. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  234. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  235. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  236. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  237. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  238. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  239. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  240. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  241. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  242. package/dist/workflows/dag/init-hybrid.js +1174 -562
  243. package/dist/workflows/dag/interrupt-request.js +7 -0
  244. package/dist/workflows/dag/node-execution.js +940 -31
  245. package/dist/workflows/dag/path-safety.js +1 -0
  246. package/dist/workflows/dag/prompt.js +99 -7
  247. package/dist/workflows/dag/recovery-lease.js +170 -0
  248. package/dist/workflows/dag/report.js +37 -1
  249. package/dist/workflows/dag/rerun-feedback.js +315 -1
  250. package/dist/workflows/dag/rerun-plan.js +568 -3
  251. package/dist/workflows/dag/rerun-run.js +642 -18
  252. package/dist/workflows/dag/rerun-task.js +251 -13
  253. package/dist/workflows/dag/retry-policy.js +219 -104
  254. package/dist/workflows/dag/runner.js +596 -121
  255. package/dist/workflows/dag/scheduler.js +133 -20
  256. package/dist/workflows/dag/skill-snapshot.js +17 -0
  257. package/dist/workflows/dag/types.js +426 -22
  258. package/dist/workflows/dag/validate.js +28 -12
  259. package/docs/architecture/runtime-boundaries.md +6 -6
  260. package/docs/architecture/worker-and-feature.md +1 -1
  261. package/docs/examples/README.md +5 -0
  262. package/docs/init-surface.manifest.json +30 -12
  263. package/docs/skills/vetted-skill-registry.md +4 -2
  264. package/docs/templates/README.md +2 -0
  265. package/docs/templates/agent-dag-report.schema.json +8 -2
  266. package/docs/templates/agent-dag.schema.json +33 -1
  267. package/docs/templates/backend-test-dag.json +23 -20
  268. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  269. package/docs/templates/frontend-implementation-dag.json +89 -0
  270. package/docs/templates/product-line/task.yaml +1 -1
  271. package/docs/templates/spec-registry.schema.json +45 -0
  272. package/harness.json +1 -1
  273. package/package.json +6 -3
  274. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  275. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  276. package/skills/frontend-contract/SKILL.md +23 -0
  277. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  278. package/skills/frontend-design-review/SKILL.md +22 -41
  279. package/skills/frontend-plan/SKILL.md +26 -0
  280. package/skills/frontend-plan/references/decision-contract.md +37 -0
  281. package/skills/frontend-plan/references/design-decisions.md +17 -0
  282. package/skills/frontend-review/SKILL.md +20 -15
  283. package/skills/frontend-review/references/review-findings.md +6 -7
  284. package/skills/frontend-scout/SKILL.md +25 -0
  285. package/skills/frontend-scout/references/design-evidence.md +16 -0
  286. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  287. package/skills/frontend-verification/SKILL.md +1 -1
  288. package/skills/loop-agent/references/command-reference.md +7 -0
  289. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  290. package/dist/worker/console/operation-store.js +0 -169
  291. package/dist/worker/console/static/assets/channel-D1xajGpe.js +0 -1
  292. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +0 -1
  293. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +0 -1
  294. package/dist/worker/console/static/assets/index-BLzcAykx.js +0 -451
  295. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +0 -1
  296. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +0 -1
  297. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +0 -8
  298. package/skills/frontend-implementation/SKILL.md +0 -52
  299. package/skills/frontend-implementation/references/code-standards.md +0 -33
  300. package/skills/frontend-implementation/references/design-spec.md +0 -56
  301. package/skills/frontend-implementation/references/node-contracts.md +0 -31
  302. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -5,6 +5,9 @@ import { ContractMutationError, applyTaskContract, logicalRevisionForState, obse
5
5
  import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
6
6
  import { getTaskContractPaths } from "../contract/paths.js";
7
7
  import { loadTaskConfig } from "../runtime.js";
8
+ import { discoverFrontendProjectCapability, resolveFrontendSpecRootAliases, } from "../frontend-project-capability.js";
9
+ import { classifyTaskDemand } from "../task-demand-routing.js";
10
+ import { DEFAULT_FRONTEND_SPEC_ROOTS, extractDeclaredFrontendSpecRoots, } from "../../shared/openspec-spec.js";
8
11
  import { buildPrepareDraft } from "./build-draft.js";
9
12
  import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, appendLedgerGaps, } from "./completeness.js";
10
13
  import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
@@ -681,7 +684,7 @@ export async function prepareTaskSource(input) {
681
684
  facts = importedFacts ?? mergedSupplemental;
682
685
  }
683
686
  }
684
- const built = buildPrepareDraft({
687
+ let built = buildPrepareDraft({
685
688
  taskId,
686
689
  existingTaskConfig,
687
690
  baseDraft,
@@ -689,6 +692,91 @@ export async function prepareTaskSource(input) {
689
692
  flags: input.flags,
690
693
  references,
691
694
  });
695
+ // Resolve an implicit frontend workflow before the first managed contract is
696
+ // projected. Explicit taskKind (including explicit standard) remains a hard
697
+ // contract; only a standard task with strong source/path/project evidence may
698
+ // be promoted here. This keeps task.json, contract.json, and the first DAG
699
+ // generation on the same workflow without a later hash-changing re-projection.
700
+ if (built.draft.taskKind === "standard" &&
701
+ !built.draft.taskKindExplicit) {
702
+ const routingRequirement = [
703
+ ...(sourceIntegrity?.documents ?? [])
704
+ .map((document) => document.content)
705
+ .filter((content) => Boolean(content?.trim())),
706
+ ...(input.intent.kind === "facts" && input.intent.text
707
+ ? [input.intent.text]
708
+ : []),
709
+ built.draft.requirement.objective,
710
+ ...built.draft.requirement.scope,
711
+ ...built.draft.requirement.acceptanceCriteria.map((item) => item.text),
712
+ ].join("\n");
713
+ let frontendProject;
714
+ try {
715
+ frontendProject = await discoverFrontendProjectCapability(repoRoot);
716
+ }
717
+ catch {
718
+ // Capability discovery is advisory for routing. A failed probe must not
719
+ // turn an otherwise valid standard contract preparation into an error.
720
+ }
721
+ const classification = classifyTaskDemand({
722
+ title: built.draft.title,
723
+ requirementMarkdown: routingRequirement,
724
+ allowedPaths: built.draft.constraints.allowedPaths,
725
+ frontendProject: frontendProject
726
+ ? {
727
+ framework: frontendProject.framework,
728
+ evidencePaths: frontendProject.evidencePaths,
729
+ }
730
+ : undefined,
731
+ });
732
+ if (classification.kind === "frontend") {
733
+ built = buildPrepareDraft({
734
+ taskId,
735
+ existingTaskConfig,
736
+ baseDraft,
737
+ facts,
738
+ flags: {
739
+ ...input.flags,
740
+ taskKind: "frontend-implementation",
741
+ },
742
+ references,
743
+ });
744
+ }
745
+ }
746
+ // A user can explicitly name a non-default normative directory in the task
747
+ // prompt (for example “规范目录 `design-system`”). Freeze that root into
748
+ // the managed task config before the DAG exists; normal DAG selection consumes
749
+ // the already-resolved task kind instead of mutating task.json later.
750
+ if (built.draft.taskKind === "frontend-implementation") {
751
+ // An imported PRD is materialized under sourceIntegrity rather than copied
752
+ // into intent.text. Use both inputs so a `--prd` invocation can declare a
753
+ // task-owned spec root just like supplemental free-form text can.
754
+ const sourceText = [
755
+ ...(sourceIntegrity?.documents ?? []).flatMap((document) => document.content ? [document.content] : []),
756
+ ...(input.intent.kind === "facts" && input.intent.text
757
+ ? [input.intent.text]
758
+ : []),
759
+ ].join("\n");
760
+ const inferredRoots = extractDeclaredFrontendSpecRoots(sourceText);
761
+ if (inferredRoots.length > 0) {
762
+ const current = existingTaskConfig?.frontendOpenspec;
763
+ const configuredRoots = current?.specRoots ?? [...DEFAULT_FRONTEND_SPEC_ROOTS];
764
+ // Include roots declared by this source before resolving physical-target
765
+ // aliases. A newly declared root may itself be a symlink (for example
766
+ // `project-specs -> .../ai_doc`), so resolving aliases against only the
767
+ // previous config cannot map `ai_doc/rules.md` back to project-specs.
768
+ const provisionalRoots = [
769
+ ...new Set([...configuredRoots, ...inferredRoots]),
770
+ ].sort();
771
+ const aliases = await resolveFrontendSpecRootAliases(repoRoot, provisionalRoots);
772
+ const aliasMap = new Map(aliases.map(({ alias, logicalRoot }) => [alias, logicalRoot]));
773
+ built.draft.frontendOpenspec = {
774
+ policy: current?.policy ?? "cited",
775
+ specRoots: [...new Set([...configuredRoots, ...inferredRoots.map((root) => aliasMap.get(root) ?? root)])].sort(),
776
+ requiredReadPaths: current?.requiredReadPaths ?? [],
777
+ };
778
+ }
779
+ }
692
780
  const { gaps, risks } = listPrepareGaps({
693
781
  draft: built.draft,
694
782
  state,
@@ -708,29 +796,46 @@ export async function prepareTaskSource(input) {
708
796
  // -------------------------------------------------------------------
709
797
  let ledger;
710
798
  let ledgerSummary;
711
- if (hasImportedPrd &&
712
- sourceIntegrity &&
713
- sourceIntegrity.parseableDocuments.some((doc) => doc.role === "requirement" || doc.role === "acceptance")) {
799
+ const hasAuthoritativeImportedDocs = Boolean(hasImportedPrd &&
800
+ sourceIntegrity?.parseableDocuments.some((doc) => doc.role === "requirement" || doc.role === "acceptance"));
801
+ // Frontend tasks created from text still need the same source-fidelity
802
+ // contract as imported-PRD tasks. In that case the projected requirement
803
+ // document is the canonical, task-owned source that the DAG can bind to;
804
+ // without it init-hybrid would silently emit a v1 binding and the first
805
+ // frontend-contract node would fail before making a model call.
806
+ if (built.draft.taskKind === "frontend-implementation" &&
807
+ (hasAuthoritativeImportedDocs ||
808
+ (input.intent.kind === "facts" && Boolean(input.intent.text)))) {
714
809
  try {
715
- const authoritativeDocs = sourceIntegrity.parseableDocuments.filter((doc) => (doc.role === "requirement" || doc.role === "acceptance") &&
810
+ const authoritativeDocs = sourceIntegrity?.parseableDocuments.filter((doc) => (doc.role === "requirement" || doc.role === "acceptance") &&
716
811
  typeof doc.content === "string" &&
717
- doc.content.trim().length > 0);
718
- const sourceDocuments = authoritativeDocs.map((doc) => {
719
- // Canonical source identity 单一边界(AC-001 / AC-FIDELITY-REPAIR-001):
720
- // ledger sourceDocuments[].path 与 DAG 侧 serializeDagTaskSourcePath /
721
- // resolveDagTaskSourcePath 一致(repo-root relative POSIX),路径转换只
722
- // 发生在该边界,不再二次转换进入 digest 计算。
723
- const absolute = path.resolve(contractPaths.sourceDir, doc.materializedPath);
724
- return {
725
- path: path
726
- .relative(repoRoot, absolute)
727
- .split(path.sep)
728
- .join("/"),
729
- content: doc.content,
730
- };
731
- });
812
+ doc.content.trim().length > 0) ?? [];
813
+ const requirementProjection = projectRequirementMarkdown(built.draft);
814
+ const sourceDocuments = authoritativeDocs.length > 0
815
+ ? authoritativeDocs.map((doc) => {
816
+ // Canonical source identity 单一边界(AC-001 / AC-FIDELITY-REPAIR-001):
817
+ // ledger sourceDocuments[].path 与 DAG 侧 serializeDagTaskSourcePath /
818
+ // resolveDagTaskSourcePath 一致(repo-root relative POSIX),路径转换只
819
+ // 发生在该边界,不再二次转换进入 digest 计算。
820
+ const absolute = path.resolve(contractPaths.sourceDir, doc.materializedPath);
821
+ return {
822
+ path: path
823
+ .relative(repoRoot, absolute)
824
+ .split(path.sep)
825
+ .join("/"),
826
+ content: doc.content,
827
+ };
828
+ })
829
+ : [
830
+ {
831
+ path: path
832
+ .relative(repoRoot, contractPaths.requirementPath)
833
+ .split(path.sep)
834
+ .join("/"),
835
+ content: requirementProjection,
836
+ },
837
+ ];
732
838
  if (sourceDocuments.length > 0) {
733
- const requirementProjection = projectRequirementMarkdown(built.draft);
734
839
  ledger = await buildRequirementLedger({
735
840
  taskId,
736
841
  requirementSha256: sha256Utf8(requirementProjection),
@@ -457,8 +457,12 @@ async function buildPrompt(input) {
457
457
  "Do NOT invent product behavior that is not supported by the documents.",
458
458
  "You MAY recommend an optional engineering block when the repo layout hint supports it:",
459
459
  "- allowedPaths / forbiddenPaths as minimal precise globs (e.g. source/packages/**/*.test.ts)",
460
- "- verifyCommands as real package scripts (e.g. pnpm -C source test)",
461
- "- taskKind when clearly implied (backend-test / frontend-test / standard / …)",
460
+ "- verifyCommands as real package scripts (e.g. pnpm -C source test); when acceptance criteria name a specific test file to run (e.g. 定向 vitest(test/.../xxx.test.ts)), emit the corresponding targeted command as its own entry (e.g. npx vitest run test/.../xxx.test.ts) instead of only the full-suite script — targeted commands are what a frontend implementation plan maps to behavior verification targets",
461
+ "- taskKind when clearly implied, choosing from {standard / frontend-implementation / frontend-test / backend-test / knowledge-sync}:",
462
+ " - frontend-implementation: the deliverable is a frontend component/page/UI feature (even when it ships with its own tests)",
463
+ " - frontend-test: the deliverable is a browser/UI automation test suite for EXISTING functionality (no implementation work)",
464
+ " - backend-test: pytest/backend test suite; standard: general repo implementation",
465
+ " A task that IMPLEMENTS a component and adds tests for it is frontend-implementation, never frontend-test.",
462
466
  "Prefer smallest correct write boundary; do not invent product paths that are not in the layout hint.",
463
467
  "Do NOT rewrite or suggest editing the original PRD files.",
464
468
  "Prefer explicit text; use provenance derived for rephrasing; use inferred only when clearly implied — if you must invent ACs, leave acceptanceCriteria empty so the system can stop for clarification.",
@@ -22,6 +22,18 @@ function slugifyName(value) {
22
22
  .replace(/-{2,}/g, "-");
23
23
  return slug || "requirement";
24
24
  }
25
+ /** Stable identity of a manifest document for change detection. Timestamps and
26
+ * byte sizes are excluded: identical content re-imported later must not appear
27
+ * "changed" (the canonical hash includes manifest bytes). */
28
+ function documentIdentity(doc) {
29
+ return `${doc.role}|${doc.name}|${doc.materializedPath}|${doc.sha256}`;
30
+ }
31
+ function sameDocumentSet(left, right) {
32
+ if (left.length !== right.length)
33
+ return false;
34
+ const rightIds = new Set(right.map(documentIdentity));
35
+ return left.every((doc) => rightIds.has(documentIdentity(doc)));
36
+ }
25
37
  function ensureUniqueReferenceName(existing, baseName, extension) {
26
38
  const used = new Set(existing);
27
39
  const candidate = `${baseName}${extension}`;
@@ -149,10 +161,19 @@ export async function importPrdDocument(input) {
149
161
  const documents = [...(existingManifest?.documents ?? [])].filter((entry) => entry.materializedPath !== materializedRelativePath);
150
162
  documents.push(document);
151
163
  documents.sort((left, right) => left.materializedPath.localeCompare(right.materializedPath));
164
+ // Keep `updatedAt` stable across re-imports of the same content: the
165
+ // canonical contract hash includes the manifest bytes, so a fresh
166
+ // timestamp on every identical re-import would make the observed
167
+ // canonical hash drift and fail task verification. Only bump it when the
168
+ // document set actually changed (a new/changed materialized file).
169
+ const documentSetChanged = !existingManifest ||
170
+ !sameDocumentSet(existingManifest.documents, documents);
152
171
  const manifest = {
153
172
  schemaVersion: 1,
154
173
  taskId: input.taskId,
155
- updatedAt: importedAt,
174
+ updatedAt: documentSetChanged
175
+ ? importedAt
176
+ : (existingManifest?.updatedAt ?? importedAt),
156
177
  documents,
157
178
  };
158
179
  await writeSourceManifest(paths.sourceDir, manifest);
@@ -76,6 +76,7 @@ const SPECIAL_TEMPLATE_BY_TASK_KIND = {
76
76
  "frontend-implementation": "frontend-implementation",
77
77
  "frontend-test": "frontend-test-dag",
78
78
  "backend-test": "backend-test-dag",
79
+ "backend-test-analysis": "backend-test-analysis-dag",
79
80
  "knowledge-sync": "knowledge-sync-dag",
80
81
  "knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
81
82
  };
@@ -83,6 +84,7 @@ const TASK_KIND_ROUTING_REASON = {
83
84
  "frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
84
85
  "frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
85
86
  "backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
87
+ "backend-test-analysis": 'taskKind "backend-test-analysis" selects the read-only backend test analysis DAG template',
86
88
  "knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
87
89
  "knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
88
90
  };
@@ -90,6 +92,7 @@ const SPECIAL_REQUESTED_TEMPLATES = new Set([
90
92
  "frontend-implementation",
91
93
  "frontend-test-dag",
92
94
  "backend-test-dag",
95
+ "backend-test-analysis-dag",
93
96
  "knowledge-sync-dag",
94
97
  "knowledge-graph-bootstrap-dag",
95
98
  ]);
@@ -19,8 +19,8 @@
19
19
  import { createHash } from "node:crypto";
20
20
  import { existsSync, readFileSync, rmSync } from "node:fs";
21
21
  import path from "node:path";
22
- import { ensureSecureDir, writeSecureJsonSync, } from "../app-data.js";
23
- import { isTerminalOperationState, } from "../operation-store.js";
22
+ import { ensureSecureDir, writeSecureJsonSync, } from "../../../infrastructure/console/app-data.js";
23
+ import { isTerminalOperationState, } from "../../../infrastructure/console/operation-store.js";
24
24
  import { scrubSecrets } from "./explore-tools.js";
25
25
  const OPERATION_PREVIEW_MAX_CHARS = 1200;
26
26
  function boundedRedactedText(value, maxChars = OPERATION_PREVIEW_MAX_CHARS) {
@@ -125,11 +125,14 @@ function resolveTierDescriptor(selection, available) {
125
125
  });
126
126
  return {
127
127
  provider: selection.provider,
128
- resolved: Boolean(entry),
128
+ resolved: Boolean(entry && entry.hasCredentials !== false),
129
129
  };
130
130
  }
131
131
  const entry = findProviderForModelId(selection.modelId, available);
132
- return { provider: entry?.provider, resolved: Boolean(entry) };
132
+ return {
133
+ provider: entry?.provider,
134
+ resolved: Boolean(entry && entry.hasCredentials !== false),
135
+ };
133
136
  }
134
137
  /**
135
138
  * Resolve harness executors.pi tiers (LOW/MED/HIGH) to provider descriptors.
@@ -196,7 +199,7 @@ export async function resolveDefaultChatModel(repoRoot, available, options) {
196
199
  const entry = findProviderForModelId(selection.modelId, available, {
197
200
  preferredProvider: selection.provider,
198
201
  });
199
- if (!entry)
202
+ if (!entry || entry.hasCredentials === false)
200
203
  return undefined;
201
204
  return {
202
205
  provider: selection.provider,
@@ -205,7 +208,7 @@ export async function resolveDefaultChatModel(repoRoot, available, options) {
205
208
  };
206
209
  }
207
210
  const entry = findProviderForModelId(selection.modelId, available);
208
- if (!entry)
211
+ if (!entry || entry.hasCredentials === false)
209
212
  return undefined;
210
213
  return {
211
214
  provider: entry.provider,
@@ -26,7 +26,7 @@
26
26
  * `createDefaultPiSdkBindings()`.
27
27
  */
28
28
  import { createHash, randomBytes } from "node:crypto";
29
- import { access, stat } from "node:fs/promises";
29
+ import { access, mkdir, rm, stat, writeFile } from "node:fs/promises";
30
30
  import path from "node:path";
31
31
  import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
32
32
  import { parseProviderError, } from "./provider-error.js";
@@ -43,6 +43,7 @@ import { filterActiveInterviewTools } from "../interview/tools.js";
43
43
  import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
44
44
  import { buildTodoContinuationContext, } from "./todos.js";
45
45
  import { buildGoalContinuationContext, } from "./goals.js";
46
+ import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
46
47
  /**
47
48
  * Base system prompt fragment every General Operator Chat session receives.
48
49
  *
@@ -298,6 +299,24 @@ export function createGoalCompactionContextExtension(options) {
298
299
  },
299
300
  };
300
301
  }
302
+ export function createWizardLocalHeadersExtension(options) {
303
+ return {
304
+ name: "operator-chat-wizard-local-headers",
305
+ hidden: true,
306
+ factory: (api) => {
307
+ api.on("before_provider_headers", async (rawEvent, context) => {
308
+ if (!context || !rawEvent.headers || typeof rawEvent.headers !== "object")
309
+ return;
310
+ await injectWizardLocalHeaders({
311
+ headers: rawEvent.headers,
312
+ context,
313
+ sessionId: options.sessionId,
314
+ service: options.service,
315
+ });
316
+ });
317
+ },
318
+ };
319
+ }
301
320
  /**
302
321
  * Build Pi custom ToolDefinition objects for every whitelisted operator action.
303
322
  * Each tool's execute() routes into the operator action dispatcher (Gate 3).
@@ -1061,10 +1080,17 @@ export class ConsolePiRuntime {
1061
1080
  registry: this.options.goalRegistry,
1062
1081
  })
1063
1082
  : undefined;
1083
+ const wizardLocalHeadersExtension = sessionId && this.options.sddDataAlignment
1084
+ ? createWizardLocalHeadersExtension({
1085
+ sessionId,
1086
+ service: this.options.sddDataAlignment,
1087
+ })
1088
+ : undefined;
1064
1089
  const extraFactories = [
1065
1090
  todoContextExtension,
1066
1091
  goalContextExtension,
1067
1092
  pressureExtension,
1093
+ wizardLocalHeadersExtension,
1068
1094
  ].filter((item) => Boolean(item));
1069
1095
  return {
1070
1096
  ...base,
@@ -1305,6 +1331,15 @@ export class ConsolePiRuntime {
1305
1331
  if (!sessionFile) {
1306
1332
  throw new Error(`chat session shell missing sessionFile for ${sessionId}`);
1307
1333
  }
1334
+ // Pi's SessionManager.create deliberately postpones writing a JSONL until
1335
+ // an assistant response. Materialize a valid header now, otherwise Console
1336
+ // could persist a path that disappears on a restart before first reply.
1337
+ await this.bindings.materializeSessionShell({
1338
+ sessionManager,
1339
+ sessionId,
1340
+ sessionFile,
1341
+ cwd: this.options.cwd,
1342
+ });
1308
1343
  this.sessionManagers.set(sessionId, sessionManager);
1309
1344
  this.thinkingSelections.set(sessionId, "auto");
1310
1345
  // Detached single-flight materialization; dependent routes await the same promise.
@@ -2312,6 +2347,16 @@ export class ConsolePiRuntime {
2312
2347
  });
2313
2348
  }
2314
2349
  catch (error) {
2350
+ const code = error && typeof error === "object" && "code" in error
2351
+ ? String(error.code ?? "")
2352
+ : "";
2353
+ if (code === "SESSION_FILE_MISSING" || code === "ENOENT") {
2354
+ return {
2355
+ ok: false,
2356
+ code: "NO_SESSION_FILE",
2357
+ message: `Pi session file missing or not openable: ${init.sessionFile}`,
2358
+ };
2359
+ }
2315
2360
  return {
2316
2361
  ok: false,
2317
2362
  code: "REOPEN_FAILED",
@@ -3147,6 +3192,36 @@ export function createDefaultPiSdkBindings() {
3147
3192
  id: opts.sessionId,
3148
3193
  });
3149
3194
  },
3195
+ materializeSessionShell: async (opts) => {
3196
+ const sdk = (await loadSdk());
3197
+ const manager = opts.sessionManager;
3198
+ if (typeof manager.setSessionFile !== "function") {
3199
+ throw new Error("Pi SessionManager cannot materialize a session file");
3200
+ }
3201
+ let created = false;
3202
+ try {
3203
+ await mkdir(path.dirname(opts.sessionFile), { recursive: true });
3204
+ // Match Pi's documented session header. Rebinding through the public
3205
+ // SessionManager API marks it flushed, so Pi appends the first user
3206
+ // entry instead of trying to create the already-present file later.
3207
+ await writeFile(opts.sessionFile, `${JSON.stringify({
3208
+ type: "session",
3209
+ version: typeof sdk.CURRENT_SESSION_VERSION === "number"
3210
+ ? sdk.CURRENT_SESSION_VERSION
3211
+ : 3,
3212
+ id: opts.sessionId,
3213
+ timestamp: new Date().toISOString(),
3214
+ cwd: opts.cwd,
3215
+ })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
3216
+ created = true;
3217
+ manager.setSessionFile(opts.sessionFile);
3218
+ }
3219
+ catch (error) {
3220
+ if (created)
3221
+ await rm(opts.sessionFile, { force: true }).catch(() => undefined);
3222
+ throw error;
3223
+ }
3224
+ },
3150
3225
  createInMemorySessionManager: async (opts) => {
3151
3226
  const sdk = (await loadSdk());
3152
3227
  return sdk.SessionManager.inMemory(opts.cwd);
@@ -3163,13 +3238,19 @@ export function createDefaultPiSdkBindings() {
3163
3238
  // wrong conversation under the durable Console sessionId. Fail closed
3164
3239
  // before open when the persisted file is absent.
3165
3240
  await access(opts.sessionFile);
3166
- return sdk.SessionManager.open(opts.sessionFile);
3167
3241
  }
3168
- catch {
3169
- // Missing/corrupt session file — caller fails closed rather than
3170
- // silently creating a fresh session and losing prior context.
3242
+ catch (error) {
3243
+ const code = error && typeof error === "object" && "code" in error
3244
+ ? String(error.code ?? "")
3245
+ : "";
3246
+ if (code === "ENOENT") {
3247
+ throw Object.assign(new Error(`SESSION_FILE_MISSING: ${opts.sessionFile}`), { code: "SESSION_FILE_MISSING", cause: error });
3248
+ }
3249
+ // File exists but is unreadable (permissions / I/O) — caller maps
3250
+ // undefined to 503 rather than silently creating a fresh session.
3171
3251
  return undefined;
3172
3252
  }
3253
+ return sdk.SessionManager.open(opts.sessionFile);
3173
3254
  },
3174
3255
  resolveModel: (opts) => {
3175
3256
  const rt = opts.modelRuntime;
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { createHash } from "node:crypto";
9
9
  import { existsSync, lstatSync } from "node:fs";
10
- import { readJsonIfExists, writeSecureJson, } from "../app-data.js";
10
+ import { readJsonIfExists, writeSecureJson, } from "../../../infrastructure/console/app-data.js";
11
11
  export const RESOURCE_PREFERENCES_SCHEMA_VERSION = 1;
12
12
  export class ResourcePreferencesError extends Error {
13
13
  code;