@tea-agent/loop-agent 0.42.0-next.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 (238) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +319 -1
  3. package/dist/application/dag/generate-task-dag.js +75 -19
  4. package/dist/application/dag/run-dag.js +41 -0
  5. package/dist/application/task-lifecycle/advance.js +24 -5
  6. package/dist/application/task-lifecycle/observe.js +171 -17
  7. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  8. package/dist/application/task-lifecycle/recommendations.js +13 -2
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +7 -0
  11. package/dist/cli/program.js +6 -1
  12. package/dist/commands/client-recovery.js +3 -0
  13. package/dist/commands/dag-follow-up.js +138 -0
  14. package/dist/commands/dag-rerun-task.js +2 -0
  15. package/dist/commands/init.js +27 -1
  16. package/dist/commands/task-advance.js +19 -0
  17. package/dist/executors/dag-pi-executor.js +3976 -76
  18. package/dist/executors/pi-executor.js +15 -4
  19. package/dist/executors/pi-read-budget-policy.js +239 -0
  20. package/dist/executors/shell-executor.js +978 -188
  21. package/dist/executors/shell-write-guard.js +7 -0
  22. package/dist/infrastructure/console/operation-store.js +226 -6
  23. package/dist/shared/dag-failure-category.js +12 -0
  24. package/dist/shared/openspec-spec.js +70 -4
  25. package/dist/shared/operator/capabilities.js +7 -0
  26. package/dist/task/config-types.js +105 -5
  27. package/dist/task/contract/adopt.js +4 -0
  28. package/dist/task/contract/import-revision.js +4 -0
  29. package/dist/task/contract/project.js +3 -0
  30. package/dist/task/contract/schema.js +2 -1
  31. package/dist/task/frontend-project-capability.js +203 -20
  32. package/dist/task/runtime.js +5 -2
  33. package/dist/task/source-prepare/build-draft.js +3 -3
  34. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  35. package/dist/task/source-prepare/prepare.js +126 -21
  36. package/dist/task/source-prepare/semantic-intake.js +6 -2
  37. package/dist/task/source-references.js +22 -1
  38. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  39. package/dist/worker/console/chat/model-resolver.js +7 -4
  40. package/dist/worker/console/chat/pi-runtime.js +258 -11
  41. package/dist/worker/console/chat/repo-browser.js +10 -3
  42. package/dist/worker/console/chat/routes.js +165 -71
  43. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  44. package/dist/worker/console/chat/session-catalog.js +2 -0
  45. package/dist/worker/console/chat/session-store.js +159 -70
  46. package/dist/worker/console/chat/shortcuts.js +19 -3
  47. package/dist/worker/console/chat/turn-process.js +111 -56
  48. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  49. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  50. package/dist/worker/console/index.js +3 -0
  51. package/dist/worker/console/operator-actions.js +169 -7
  52. package/dist/worker/console/pi-readiness.js +3 -2
  53. package/dist/worker/console/server.js +23 -1
  54. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  55. package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
  56. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  57. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  58. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  59. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  60. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  70. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  71. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  72. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  73. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  74. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  75. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  81. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  82. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  83. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  84. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  85. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  86. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  87. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  88. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  89. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  90. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  91. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  92. package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
  93. package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
  94. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  95. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  96. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  97. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  98. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  99. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  100. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  101. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  102. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  103. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  104. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  105. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  106. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  107. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  108. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  109. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  111. package/dist/worker/console/static/index.html +2 -2
  112. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  113. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  114. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  115. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  116. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  117. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  118. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  119. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  120. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  121. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  122. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  123. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  124. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  125. package/dist/worker/console/workspace-context.js +115 -1
  126. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  127. package/dist/worker/observe/routes.js +4 -0
  128. package/dist/worker/observe/static/dag-helpers.js +4 -2
  129. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  130. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  131. package/dist/worker/observe/static/kpi.js +2 -2
  132. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  133. package/dist/worker/observe/static/operator-chrome.js +37 -23
  134. package/dist/worker/observe/static/relations.js +2 -2
  135. package/dist/worker/observe/static/router.d.ts +12 -1
  136. package/dist/worker/observe/static/router.js +48 -6
  137. package/dist/worker/observe/static/run-processing.js +4 -2
  138. package/dist/worker/observe/static/shell-chrome.js +2 -2
  139. package/dist/worker/observe/static/state.js +5 -0
  140. package/dist/worker/observe/static/styles.css +95 -0
  141. package/dist/worker/observe/static/views/dag-inspector.js +14 -7
  142. package/dist/worker/observe/static/views/dag.js +10 -2
  143. package/dist/worker/observe/static/views/dags.js +3 -1
  144. package/dist/worker/observe/static/views/dashboard.js +14 -6
  145. package/dist/worker/observe/static/views/pool.js +7 -2
  146. package/dist/worker/observe/static/views/run.js +12 -3
  147. package/dist/worker/observe/static/views/session-timeline.js +69 -10
  148. package/dist/worker/observe/static/views/task.js +7 -2
  149. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  150. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  151. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  152. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  153. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  154. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  155. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  156. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  157. package/dist/workflows/dag/frontend-closeout.js +221 -0
  158. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  159. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  161. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  162. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  163. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  164. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  165. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  167. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  168. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  169. package/dist/workflows/dag/frontend-repair.js +1 -432
  170. package/dist/workflows/dag/frontend-review-context.js +261 -15
  171. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  172. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  173. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  174. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  175. package/dist/workflows/dag/frontend-shape.js +435 -0
  176. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  177. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  178. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  179. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  180. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  181. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  182. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  183. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  184. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  185. package/dist/workflows/dag/init-hybrid.js +1028 -562
  186. package/dist/workflows/dag/interrupt-request.js +7 -0
  187. package/dist/workflows/dag/node-execution.js +828 -4
  188. package/dist/workflows/dag/prompt.js +70 -3
  189. package/dist/workflows/dag/recovery-lease.js +170 -0
  190. package/dist/workflows/dag/report.js +37 -1
  191. package/dist/workflows/dag/rerun-feedback.js +315 -1
  192. package/dist/workflows/dag/rerun-plan.js +17 -6
  193. package/dist/workflows/dag/rerun-run.js +44 -6
  194. package/dist/workflows/dag/rerun-task.js +251 -13
  195. package/dist/workflows/dag/retry-policy.js +219 -104
  196. package/dist/workflows/dag/runner.js +580 -124
  197. package/dist/workflows/dag/scheduler.js +133 -20
  198. package/dist/workflows/dag/types.js +244 -16
  199. package/dist/workflows/dag/validate.js +28 -12
  200. package/docs/examples/README.md +5 -0
  201. package/docs/init-surface.manifest.json +30 -12
  202. package/docs/skills/vetted-skill-registry.md +4 -2
  203. package/docs/templates/README.md +2 -0
  204. package/docs/templates/agent-dag-report.schema.json +8 -2
  205. package/docs/templates/agent-dag.schema.json +1 -1
  206. package/docs/templates/backend-test-dag.json +23 -20
  207. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  208. package/docs/templates/frontend-implementation-dag.json +89 -0
  209. package/docs/templates/spec-registry.schema.json +45 -0
  210. package/harness.json +1 -1
  211. package/package.json +4 -3
  212. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  213. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  214. package/skills/frontend-contract/SKILL.md +23 -0
  215. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  216. package/skills/frontend-design-review/SKILL.md +22 -41
  217. package/skills/frontend-plan/SKILL.md +26 -0
  218. package/skills/frontend-plan/references/decision-contract.md +37 -0
  219. package/skills/frontend-plan/references/design-decisions.md +17 -0
  220. package/skills/frontend-review/SKILL.md +20 -15
  221. package/skills/frontend-review/references/review-findings.md +6 -7
  222. package/skills/frontend-scout/SKILL.md +25 -0
  223. package/skills/frontend-scout/references/design-evidence.md +16 -0
  224. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  225. package/skills/frontend-verification/SKILL.md +1 -1
  226. package/skills/loop-agent/references/command-reference.md +1 -0
  227. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  228. package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
  229. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
  230. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
  231. package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
  232. package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
  233. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
  234. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
  235. package/skills/frontend-implementation/SKILL.md +0 -52
  236. package/skills/frontend-implementation/references/code-standards.md +0 -33
  237. package/skills/frontend-implementation/references/design-spec.md +0 -56
  238. package/skills/frontend-implementation/references/node-contracts.md +0 -31
@@ -13,10 +13,11 @@
13
13
  * Read routes (GET capabilities / sessions) are open to loopback.
14
14
  */
15
15
  import { randomUUID } from "node:crypto";
16
+ import { access } from "node:fs/promises";
16
17
  import { parseAssistantContent } from "./assistant-content.js";
17
18
  import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
18
19
  import { buildModelCallableToolSchemas } from "./tool-adapter.js";
19
- import { cleanChatTitle, acceptedOperationIdFromResult } from "./session-store.js";
20
+ import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
20
21
  import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
21
22
  import { projectDeferredTurnSummary, projectDeferredTurnSummaries, } from "./deferred-turn.js";
22
23
  import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
@@ -42,6 +43,12 @@ import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js
42
43
  import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
43
44
  import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
44
45
  import { isTerminalOperationState } from "../../../infrastructure/console/operation-store.js";
46
+ import { SddDataAlignmentError, } from "./sdd-data-alignment.js";
47
+ function projectBrowserChatSession(record) {
48
+ const { telemetrySessionId, ...projected } = record;
49
+ void telemetrySessionId;
50
+ return projected;
51
+ }
45
52
  /**
46
53
  * Reattach the durable event-ring order to session-store rows. The session
47
54
  * store remains the content authority; the ring only supplies chronology.
@@ -468,7 +475,7 @@ function sendRuntimeUnavailable(res, error) {
468
475
  * handled here — only reopen/init readiness failures.
469
476
  */
470
477
  function mapReopenSessionFailure(reopen) {
471
- if (reopen.code === "NO_SESSION_FILE") {
478
+ if (reopen.code === "NO_SESSION_FILE" || reopen.code === "SESSION_FILE_MISSING") {
472
479
  return {
473
480
  status: 404,
474
481
  code: "PI_SESSION_NOT_FOUND",
@@ -488,6 +495,19 @@ function mapReopenSessionFailure(reopen) {
488
495
  message: reopen.message,
489
496
  };
490
497
  }
498
+ /** Drop an orphan catalog record whose promised Pi JSONL is gone. */
499
+ async function healMissingSessionFile(deps, sessionId, reopen) {
500
+ if (reopen && reopen.code !== "NO_SESSION_FILE" && reopen.code !== "SESSION_FILE_MISSING") {
501
+ return;
502
+ }
503
+ if (typeof deps.store.remove !== "function")
504
+ return;
505
+ await deps.store.remove(sessionId).catch(() => undefined);
506
+ }
507
+ async function mapFailedReopen(deps, sessionId, reopen) {
508
+ await healMissingSessionFile(deps, sessionId, reopen);
509
+ return mapReopenSessionFailure(reopen);
510
+ }
491
511
  export async function handleChatCapabilities(_req, res, deps) {
492
512
  const toolSchemas = buildModelCallableToolSchemas();
493
513
  sendJson(res, 200, {
@@ -555,9 +575,15 @@ workspaceId) {
555
575
  });
556
576
  return;
557
577
  }
578
+ let createdSessionId;
579
+ let persisted = false;
558
580
  try {
559
581
  const snapshot = snapshotInput === undefined ? undefined : projectCompactSnapshot(snapshotInput);
560
- // Fast path: durable shell + Console record before full AgentSession ready.
582
+ // A Console record promises that its Pi JSONL can be reopened after a
583
+ // Console restart. Do not make that promise until Pi has finished
584
+ // materializing the session: the old fast path wrote a record for a
585
+ // future JSONL path, leaving an unrecoverable orphan when init failed or
586
+ // the process restarted in that window.
561
587
  const handle = await deps.runtime.createSession({
562
588
  ...(provider && modelId ? { model: { provider, modelId } } : {}),
563
589
  ...(snapshot
@@ -577,56 +603,51 @@ workspaceId) {
577
603
  });
578
604
  return;
579
605
  }
606
+ createdSessionId = handle.sessionId;
607
+ await deps.runtime.ensureSessionReady(handle.sessionId);
608
+ const selection = deps.runtime.getRuntimeSelection(handle.sessionId);
580
609
  const session = await deps.store.create({
581
610
  sessionId: handle.sessionId,
582
611
  sessionFile: handle.sessionFile,
583
- modelProvider: handle.model?.provider,
584
- modelId: handle.model?.modelId,
612
+ modelProvider: selection.model?.provider ?? handle.model?.provider,
613
+ modelId: selection.model?.modelId ?? handle.model?.modelId,
585
614
  // AC-R7-002: explicit parameter from the dispatcher — the runtime
586
615
  // store.create wrapper is gone; body workspaceId (if any) only acts as
587
616
  // a fallback for direct (non-dispatcher) handler tests.
588
617
  workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
589
618
  });
619
+ persisted = true;
590
620
  await deps.attachSession?.(handle.sessionId).catch(() => undefined);
591
621
  if (snapshot)
592
622
  deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
593
623
  kind: "compact",
594
624
  data: snapshot,
595
625
  });
596
- // Best-effort: after materialization, patch resolved default model onto the
597
- // durable record. Failures stay on the init fence (PI_SESSION_INIT_FAILED).
598
- if (typeof deps.runtime.ensureSessionReady === "function") {
599
- void deps.runtime
600
- .ensureSessionReady(handle.sessionId)
601
- .then(async () => {
602
- if (typeof deps.runtime.getRuntimeSelection !== "function")
603
- return;
604
- const selection = deps.runtime.getRuntimeSelection(handle.sessionId);
605
- if (!selection?.model)
606
- return;
607
- await deps.store.setRuntimeSelection(handle.sessionId, {
608
- modelProvider: selection.model.provider,
609
- modelId: selection.model.modelId,
610
- });
611
- })
612
- .catch(() => undefined);
613
- }
614
626
  sendJson(res, 201, {
615
627
  ok: true,
616
628
  sessionId: handle.sessionId,
617
629
  sessionFile: handle.sessionFile,
618
630
  model: handle.model,
619
- session,
631
+ session: { ...session, isBlank: isBlankChatSession(session) },
620
632
  activeTools: handle.activeTools,
621
633
  hasBash: true,
622
- ...(handle.initializing ? { initializing: true } : {}),
623
634
  });
624
635
  }
625
636
  catch (error) {
637
+ // Before store.create succeeds, this handle has no durable owner. Dispose
638
+ // it so a failed create cannot survive in-memory until the next restart.
639
+ if (createdSessionId && !persisted)
640
+ deps.runtime.dispose(createdSessionId);
626
641
  const message = error instanceof Error ? error.message : String(error);
627
- sendJson(res, 500, {
642
+ const code = error && typeof error === "object" && "code" in error
643
+ ? String(error.code ?? "")
644
+ : "";
645
+ sendJson(res, code === "PI_SESSION_INIT_FAILED" ? 503 : 500, {
628
646
  ok: false,
629
- error: { code: "CHAT_SESSION_CREATE_FAILED", message },
647
+ error: {
648
+ code: code === "PI_SESSION_INIT_FAILED" ? code : "CHAT_SESSION_CREATE_FAILED",
649
+ message,
650
+ },
630
651
  });
631
652
  }
632
653
  }
@@ -731,10 +752,7 @@ export async function handleBranchChatSession(req, res, deps, sessionId) {
731
752
  sessionFile: record.sessionFile,
732
753
  });
733
754
  if (!reopened.ok) {
734
- const mapped = mapReopenSessionFailure({
735
- code: reopened.code,
736
- message: reopened.message,
737
- });
755
+ const mapped = await mapFailedReopen(deps, sessionId, reopened);
738
756
  sendJson(res, mapped.status, {
739
757
  ok: false,
740
758
  error: { code: mapped.code, message: mapped.message },
@@ -809,7 +827,31 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
809
827
  });
810
828
  return;
811
829
  }
830
+ if (record.sessionFile) {
831
+ try {
832
+ await access(record.sessionFile);
833
+ }
834
+ catch (error) {
835
+ const code = error && typeof error === "object" && "code" in error
836
+ ? String(error.code ?? "")
837
+ : "";
838
+ if (code === "ENOENT") {
839
+ await healMissingSessionFile(deps, sessionId, {
840
+ code: "NO_SESSION_FILE",
841
+ });
842
+ sendJson(res, 404, {
843
+ ok: false,
844
+ error: {
845
+ code: "PI_SESSION_NOT_FOUND",
846
+ message: `Pi session file missing or not openable: ${record.sessionFile}`,
847
+ },
848
+ });
849
+ return;
850
+ }
851
+ }
852
+ }
812
853
  await deps.operationLinker.recoverSession(sessionId);
854
+ const messagePage = await deps.store.listMessages(sessionId, { limit: 50 });
813
855
  const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
814
856
  const snapshot = deps.events.snapshot(sessionId);
815
857
  const telemetry = deriveChatSessionTelemetry(snapshot);
@@ -820,8 +862,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
820
862
  ok: true,
821
863
  eventEpoch: deps.events.epoch(sessionId),
822
864
  session: {
823
- ...record,
824
- messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
865
+ ...projectBrowserChatSession(record),
866
+ messages: projectStoredMessagesWithEventOrder(messagePage.messages, snapshot),
825
867
  composerDraft,
826
868
  activeTurnId: activeTurn?.turnId,
827
869
  turnPhase: activeTurn
@@ -833,6 +875,10 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
833
875
  todos: projectCurrentTodos(snapshot) ?? [],
834
876
  goal: deps.goalRegistry?.get(sessionId) ?? null,
835
877
  },
878
+ history: {
879
+ hasMore: messagePage.hasMore,
880
+ ...(messagePage.nextCursor ? { nextCursor: messagePage.nextCursor } : {}),
881
+ },
836
882
  hasBash: true,
837
883
  sessionStats: telemetry.stats,
838
884
  usage: telemetry.usage,
@@ -1065,20 +1111,10 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
1065
1111
  }
1066
1112
  const item = claim.deferredTurn;
1067
1113
  try {
1068
- await deps.runtime.steer(sessionId, item.text, item.images);
1069
- const userMessage = await deps.store.appendMessage(sessionId, {
1070
- role: "user",
1071
- text: item.text,
1072
- });
1073
- deps.events.append(sessionId, active.turnId, {
1074
- kind: "user_message",
1075
- data: {
1076
- clientRequestId: item.clientRequestId,
1077
- message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
1078
- },
1079
- });
1080
- await deps.store.clearDeferredTurnForRequest(sessionId, item.clientRequestId);
1081
- await emitDeferredTurnUpdate(deps, sessionId, active.turnId, undefined, item.revision + 2);
1114
+ await deps.runtime.steer(sessionId, item.clientRequestId, item.text, item.images);
1115
+ // session.steer() only acknowledges queueing. Keep the durable item in
1116
+ // dispatching until Pi emits the corresponding user message_start.
1117
+ await emitDeferredTurnUpdate(deps, sessionId, active.turnId, item, item.revision);
1082
1118
  sendJson(res, 200, {
1083
1119
  ok: true,
1084
1120
  accepted: true,
@@ -1102,6 +1138,21 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
1102
1138
  });
1103
1139
  }
1104
1140
  }
1141
+ async function persistAcceptedSteering(deps, sessionId, turnId, userMessage, clientRequestId) {
1142
+ deps.events.append(sessionId, turnId, {
1143
+ kind: "user_message",
1144
+ data: {
1145
+ clientRequestId,
1146
+ steering: true,
1147
+ message: {
1148
+ id: userMessage.id,
1149
+ text: userMessage.text,
1150
+ at: userMessage.at,
1151
+ },
1152
+ },
1153
+ });
1154
+ await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
1155
+ }
1105
1156
  /** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
1106
1157
  * get_state equivalent): exposes the active turn + the latest turn's durable
1107
1158
  * outcome WITHOUT the full message payload, so the UI can poll it on an
@@ -1401,6 +1452,7 @@ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
1401
1452
  : "idle",
1402
1453
  deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
1403
1454
  deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
1455
+ isBlank: isBlankChatSession(session) && !active,
1404
1456
  };
1405
1457
  });
1406
1458
  }
@@ -1431,7 +1483,7 @@ export async function handlePatchChatSession(req, res, deps, sessionId) {
1431
1483
  });
1432
1484
  return;
1433
1485
  }
1434
- sendJson(res, 200, { ok: true, session: record });
1486
+ sendJson(res, 200, { ok: true, session: projectBrowserChatSession(record) });
1435
1487
  }
1436
1488
  catch (error) {
1437
1489
  sendJson(res, 404, {
@@ -1481,10 +1533,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
1481
1533
  : {}),
1482
1534
  });
1483
1535
  if (!result.ok) {
1484
- const mapped = mapReopenSessionFailure({
1485
- code: result.code,
1486
- message: result.message,
1487
- });
1536
+ const mapped = await mapFailedReopen(deps, sessionId, result);
1488
1537
  sendJson(res, mapped.status, {
1489
1538
  ok: false,
1490
1539
  error: { code: mapped.code, message: mapped.message },
@@ -1570,10 +1619,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
1570
1619
  : {}),
1571
1620
  });
1572
1621
  if (!reopen.ok) {
1573
- const mapped = mapReopenSessionFailure({
1574
- code: reopen.code,
1575
- message: reopen.message,
1576
- });
1622
+ const mapped = await mapFailedReopen(deps, sessionId, reopen);
1577
1623
  sendJson(res, mapped.status, {
1578
1624
  ok: false,
1579
1625
  error: { code: mapped.code, message: mapped.message },
@@ -1661,6 +1707,9 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
1661
1707
  if (!res.writableEnded)
1662
1708
  writeChatSse(res, persisted);
1663
1709
  },
1710
+ onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
1711
+ await persistAcceptedSteering(deps, sessionId, turnId, userMessage, accepted.clientRequestId);
1712
+ },
1664
1713
  onOperationAccepted: (accepted) => deps.operationLinker.link({
1665
1714
  sessionId,
1666
1715
  turnId,
@@ -1839,10 +1888,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1839
1888
  : {}),
1840
1889
  });
1841
1890
  if (!reopen.ok) {
1842
- const mapped = mapReopenSessionFailure({
1843
- code: reopen.code,
1844
- message: reopen.message,
1845
- });
1891
+ const mapped = await mapFailedReopen(deps, sessionId, reopen);
1846
1892
  sendJson(res, mapped.status, {
1847
1893
  ok: false,
1848
1894
  accepted: false,
@@ -1971,6 +2017,9 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1971
2017
  if (placeholder)
1972
2018
  emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
1973
2019
  },
2020
+ onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
2021
+ await persistAcceptedSteering(deps, sessionId, turn.turnId, userMessage, accepted.clientRequestId);
2022
+ },
1974
2023
  persistUserMessage: trustedPayload?.persistUserMessage,
1975
2024
  onOperationAccepted: (accepted) => deps.operationLinker.link({
1976
2025
  sessionId,
@@ -2646,13 +2695,13 @@ async function handleRepoHtmlPreview(res, deps, rawPath) {
2646
2695
  "Cache-Control": "no-store",
2647
2696
  "X-Content-Type-Options": "nosniff",
2648
2697
  "Referrer-Policy": "no-referrer",
2649
- // The iframe also has an empty sandbox attribute. This response-level
2650
- // sandbox keeps direct/popup loads isolated, while explicit local-origin
2651
- // style/image/font/media sources let relative project assets render.
2698
+ // Project scripts may drive tabs and other local interactions, but the
2699
+ // opaque sandbox still withholds same-origin access, forms, popups and
2700
+ // top-level navigation. Network requests remain disabled below.
2652
2701
  "Content-Security-Policy": [
2653
- "sandbox",
2702
+ "sandbox allow-scripts",
2654
2703
  "default-src 'none'",
2655
- "script-src 'none'",
2704
+ `script-src 'unsafe-inline' ${origin}`,
2656
2705
  "object-src 'none'",
2657
2706
  "frame-src 'none'",
2658
2707
  "form-action 'none'",
@@ -2765,6 +2814,9 @@ export async function handleChatCommands(res, deps, sessionId) {
2765
2814
  res.end(JSON.stringify({
2766
2815
  ok: true,
2767
2816
  commands,
2817
+ capabilities: {
2818
+ sddBingo: Boolean(await deps.sddDataAlignment?.isEligible()),
2819
+ },
2768
2820
  ...(warning ? { warning } : {}),
2769
2821
  }));
2770
2822
  }
@@ -2775,10 +2827,46 @@ export async function handleChatCommands(res, deps, sessionId) {
2775
2827
  res.end(JSON.stringify({
2776
2828
  ok: true,
2777
2829
  commands: [],
2830
+ capabilities: { sddBingo: false },
2778
2831
  warning: error instanceof Error ? error.message : "commands unavailable",
2779
2832
  }));
2780
2833
  }
2781
2834
  }
2835
+ async function handleSddBingo(req, res, deps, sessionId) {
2836
+ const gate = gateMutation(req, deps);
2837
+ if (!gate.ok) {
2838
+ sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
2839
+ return;
2840
+ }
2841
+ if (!deps.sddDataAlignment) {
2842
+ sendJson(res, 403, {
2843
+ ok: false,
2844
+ error: { code: "SDD_NOT_ELIGIBLE", message: "当前用户不可使用 SDD 数据对齐" },
2845
+ });
2846
+ return;
2847
+ }
2848
+ try {
2849
+ const body = await readJsonBody(req);
2850
+ if (Object.keys(body).some((key) => key !== "skillName")) {
2851
+ throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
2852
+ }
2853
+ await deps.sddDataAlignment.report(sessionId, body.skillName);
2854
+ sendJson(res, 200, { ok: true });
2855
+ }
2856
+ catch (error) {
2857
+ if (error instanceof SddDataAlignmentError) {
2858
+ sendJson(res, error.status, {
2859
+ ok: false,
2860
+ error: { code: error.code, message: error.message },
2861
+ });
2862
+ return;
2863
+ }
2864
+ sendJson(res, 502, {
2865
+ ok: false,
2866
+ error: { code: "SDD_UPSTREAM_FAILED", message: "SDD 数据对齐上报失败" },
2867
+ });
2868
+ }
2869
+ }
2782
2870
  export async function handleRuntimeContext(res, deps, sessionId) {
2783
2871
  const ensured = await ensureActiveSession(deps, sessionId);
2784
2872
  if (!ensured.ok) {
@@ -2910,7 +2998,11 @@ export async function handleModels(req, res, deps, sessionId) {
2910
2998
  // folded into a 409 MODEL_SWITCH_FAILED (the frontend refresh path
2911
2999
  // re-reads the snapshot to converge on the applied selection).
2912
3000
  const snapshot = await deps.runtime.getRuntimeSnapshot(sessionId);
2913
- sendJson(res, 200, { ok: true, session: updated, snapshot });
3001
+ sendJson(res, 200, {
3002
+ ok: true,
3003
+ session: projectBrowserChatSession(updated),
3004
+ snapshot,
3005
+ });
2914
3006
  }
2915
3007
  catch (error) {
2916
3008
  if (error instanceof RuntimeSnapshotUnavailableError) {
@@ -2988,7 +3080,7 @@ export async function ensureActiveSession(deps, sessionId) {
2988
3080
  : {}),
2989
3081
  });
2990
3082
  if (!reopened.ok) {
2991
- return { ok: false, ...mapReopenSessionFailure(reopened) };
3083
+ return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
2992
3084
  }
2993
3085
  return { ok: true };
2994
3086
  }
@@ -3935,10 +4027,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
3935
4027
  : {}),
3936
4028
  });
3937
4029
  if (!reopened.ok) {
3938
- const mapped = mapReopenSessionFailure({
3939
- code: reopened.code,
3940
- message: reopened.message,
3941
- });
4030
+ const mapped = await mapFailedReopen(deps, sessionId, reopened);
3942
4031
  sendJson(res, mapped.status, {
3943
4032
  ok: false,
3944
4033
  error: { code: mapped.code, message: mapped.message },
@@ -4177,6 +4266,11 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
4177
4266
  await handleSkillAutoInvocation(req, res, deps, decodeURIComponent(skillAutoInvocation[1]), decodeURIComponent(skillAutoInvocation[2]));
4178
4267
  return true;
4179
4268
  }
4269
+ const sddBingo = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/sdd-bingo$/);
4270
+ if (method === "POST" && sddBingo) {
4271
+ await handleSddBingo(req, res, deps, decodeURIComponent(sddBingo[1]));
4272
+ return true;
4273
+ }
4180
4274
  const m4Route = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/(draft|files|runtime-context|runtime-snapshot|models|model|reload|commands)$/);
4181
4275
  if (m4Route) {
4182
4276
  const id = decodeURIComponent(m4Route[1]);
@@ -0,0 +1,222 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ export const SDD_DEFAULT_SKILL_NAME = "SDD-implementation-test-review";
6
+ export const SDD_TELEMETRY_URL = "https://wizard-ai.paic.com.cn/code_pilot/api/skill-usage-log";
7
+ export const SDD_TELEMETRY_SIGN = "52aad0c09e6b46818a11ee702d6fe0ba";
8
+ export const WIZARD_LOCAL_DEFAULT_USER_AGENT = "Builder/OpenCode ai-sdk/provider-utils/4.0.21 runtime/bun/1.3.11";
9
+ function sameAuthFileVersion(left, right) {
10
+ if (!left.exists || !right.exists)
11
+ return left.exists === right.exists;
12
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size &&
13
+ left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
14
+ }
15
+ /** Reads ~/.aima-go/auth.json with metadata-based parse caching and fail-closed semantics. */
16
+ export class AimaxAuthReader {
17
+ authFile;
18
+ cached;
19
+ constructor(authFile = path.join(os.homedir(), ".aima-go", "auth.json")) {
20
+ this.authFile = authFile;
21
+ }
22
+ async getToken() {
23
+ let version;
24
+ try {
25
+ const info = await stat(this.authFile, { bigint: true });
26
+ version = { exists: true, dev: info.dev, ino: info.ino, size: info.size,
27
+ mtimeNs: info.mtimeNs, ctimeNs: info.ctimeNs };
28
+ }
29
+ catch {
30
+ this.cached = { version: { exists: false } };
31
+ return undefined;
32
+ }
33
+ if (this.cached && sameAuthFileVersion(this.cached.version, version))
34
+ return this.cached.token;
35
+ let token;
36
+ try {
37
+ const parsed = JSON.parse(await readFile(this.authFile, "utf8"));
38
+ const candidate = parsed["aimax-auth-token"];
39
+ if (typeof candidate === "string" && candidate.trim())
40
+ token = candidate;
41
+ }
42
+ catch {
43
+ // Malformed or unreadable files are intentionally ineligible.
44
+ }
45
+ this.cached = { version, ...(token ? { token } : {}) };
46
+ return token;
47
+ }
48
+ async isEligible() {
49
+ return Boolean(await this.getToken());
50
+ }
51
+ }
52
+ const OPENCODE_RANDOM_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
53
+ const OPENCODE_TIME_MASK = (1n << 48n) - 1n;
54
+ let lastOpenCodeMillisecond = -1;
55
+ let openCodeMillisecondCounter = 0;
56
+ /** Generate the OpenCode-compatible `ses_` identifier without an OpenCode dependency. */
57
+ export function createOpenCodeSessionId(options = {}) {
58
+ const now = Math.floor((options.now ?? Date.now)());
59
+ if (now === lastOpenCodeMillisecond)
60
+ openCodeMillisecondCounter = (openCodeMillisecondCounter + 1) & 0xfff;
61
+ else {
62
+ lastOpenCodeMillisecond = now;
63
+ openCodeMillisecondCounter = 0;
64
+ }
65
+ const ordered = BigInt(now) * 0x1000n + BigInt(openCodeMillisecondCounter);
66
+ const timePart = ((~ordered) & OPENCODE_TIME_MASK).toString(16).padStart(12, "0");
67
+ const bytes = (options.random ?? randomBytes)(14);
68
+ let randomPart = "";
69
+ for (const byte of bytes)
70
+ randomPart += OPENCODE_RANDOM_ALPHABET[byte % OPENCODE_RANDOM_ALPHABET.length];
71
+ return `ses_${timePart}${randomPart}`;
72
+ }
73
+ export class SddDataAlignmentError extends Error {
74
+ code;
75
+ status;
76
+ constructor(code, message, status) {
77
+ super(message);
78
+ this.code = code;
79
+ this.status = status;
80
+ this.name = "SddDataAlignmentError";
81
+ }
82
+ }
83
+ export function normalizeSddSkillName(value) {
84
+ if (value !== undefined && typeof value !== "string") {
85
+ throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "skillName 必须是字符串", 400);
86
+ }
87
+ const raw = value ?? "";
88
+ const normalized = raw.trim() || SDD_DEFAULT_SKILL_NAME;
89
+ if ([...normalized].length > 256 || /[\u0000-\u001f\u007f]/u.test(raw)) {
90
+ throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "skillName 不能超过 256 个字符或包含控制字符", 400);
91
+ }
92
+ return normalized;
93
+ }
94
+ export class SddDataAlignmentService {
95
+ store;
96
+ auth;
97
+ fetchImpl;
98
+ constructor(store, auth = new AimaxAuthReader(), fetchImpl = fetch) {
99
+ this.store = store;
100
+ this.auth = auth;
101
+ this.fetchImpl = fetchImpl;
102
+ }
103
+ async isEligible() {
104
+ return this.auth.isEligible();
105
+ }
106
+ async ensureTelemetrySessionId(sessionId) {
107
+ const value = await this.store.ensureTelemetrySessionId(sessionId, createOpenCodeSessionId);
108
+ if (!value)
109
+ throw new SddDataAlignmentError("SDD_SESSION_NOT_FOUND", "当前对话不存在", 404);
110
+ return value;
111
+ }
112
+ async report(sessionId, rawSkillName) {
113
+ const token = await this.auth.getToken();
114
+ if (!token) {
115
+ throw new SddDataAlignmentError("SDD_NOT_ELIGIBLE", "当前用户不可使用 SDD 数据对齐", 403);
116
+ }
117
+ const skillName = normalizeSddSkillName(rawSkillName);
118
+ const telemetrySessionId = await this.ensureTelemetrySessionId(sessionId);
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), 30_000);
121
+ let response;
122
+ try {
123
+ response = await this.fetchImpl(SDD_TELEMETRY_URL, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ "ws-auth": token,
128
+ "ws-sign": SDD_TELEMETRY_SIGN,
129
+ Connection: "keep-alive",
130
+ "User-Agent": "opencode/1.3.6",
131
+ Accept: "*/*",
132
+ Host: "wizard-ai.paic.com.cn",
133
+ "Accept-Encoding": "gzip, deflate, br, zstd",
134
+ },
135
+ body: JSON.stringify({ sessionId: telemetrySessionId, builderId: "opencode", skillName }),
136
+ signal: controller.signal,
137
+ });
138
+ }
139
+ catch {
140
+ if (controller.signal.aborted) {
141
+ throw new SddDataAlignmentError("SDD_UPSTREAM_TIMEOUT", "SDD 数据对齐上报超时", 504);
142
+ }
143
+ throw new SddDataAlignmentError("SDD_UPSTREAM_FAILED", "SDD 数据对齐上报失败", 502);
144
+ }
145
+ finally {
146
+ clearTimeout(timeout);
147
+ }
148
+ const body = await readBoundedTelemetryJson(response);
149
+ if (!response.ok || body?.ret !== 0 || body.succ !== true) {
150
+ throw new SddDataAlignmentError("SDD_UPSTREAM_FAILED", "SDD 数据对齐上报失败", 502);
151
+ }
152
+ }
153
+ }
154
+ async function readBoundedTelemetryJson(response) {
155
+ const reader = response.body?.getReader();
156
+ if (!reader)
157
+ return undefined;
158
+ const chunks = [];
159
+ let total = 0;
160
+ try {
161
+ for (;;) {
162
+ const item = await reader.read();
163
+ if (item.done)
164
+ break;
165
+ total += item.value.byteLength;
166
+ if (total > 64 * 1024) {
167
+ await reader.cancel();
168
+ return undefined;
169
+ }
170
+ chunks.push(item.value);
171
+ }
172
+ const merged = new Uint8Array(total);
173
+ let offset = 0;
174
+ for (const chunk of chunks) {
175
+ merged.set(chunk, offset);
176
+ offset += chunk.byteLength;
177
+ }
178
+ return JSON.parse(new TextDecoder().decode(merged));
179
+ }
180
+ catch {
181
+ return undefined;
182
+ }
183
+ finally {
184
+ reader.releaseLock();
185
+ }
186
+ }
187
+ function deleteHeaderCaseInsensitive(headers, name) {
188
+ for (const key of Object.keys(headers))
189
+ if (key.toLowerCase() === name.toLowerCase())
190
+ delete headers[key];
191
+ }
192
+ function configuredHeader(headers, name) {
193
+ const key = Object.keys(headers ?? {}).find((entry) => entry.toLowerCase() === name.toLowerCase());
194
+ return key ? headers?.[key] : undefined;
195
+ }
196
+ function setStaticHeader(headers, configured, name, defaultValue) {
197
+ deleteHeaderCaseInsensitive(headers, name);
198
+ headers[name] = configuredHeader(configured, name) ?? defaultValue;
199
+ }
200
+ export async function injectWizardLocalHeaders(options) {
201
+ const model = options.context.model;
202
+ if (model?.provider !== "wizard-local" || !model.id)
203
+ return;
204
+ if (!(await options.service.isEligible()))
205
+ return;
206
+ const modelRegistry = options.context.modelRegistry;
207
+ if (!modelRegistry?.getApiKeyAndHeaders)
208
+ return;
209
+ // Keep the method call attached to ModelRegistry. Its implementation reads
210
+ // the private runtime through `this`; extracting the function would make the
211
+ // SDK path fail closed and silently drop all custom headers.
212
+ const auth = await modelRegistry.getApiKeyAndHeaders(model);
213
+ if (!auth.ok || typeof auth.apiKey !== "string" || !auth.apiKey)
214
+ return;
215
+ setStaticHeader(options.headers, auth.headers, "client-code", "OpenCode");
216
+ setStaticHeader(options.headers, auth.headers, "User-Agent", WIZARD_LOCAL_DEFAULT_USER_AGENT);
217
+ for (const name of ["x-api-key", "X-Session-Id", "X-Model-Id"])
218
+ deleteHeaderCaseInsensitive(options.headers, name);
219
+ options.headers["x-api-key"] = auth.apiKey;
220
+ options.headers["X-Session-Id"] = await options.service.ensureTelemetrySessionId(options.sessionId);
221
+ options.headers["X-Model-Id"] = model.id;
222
+ }
@@ -1,3 +1,4 @@
1
+ import { isBlankChatSession } from "./session-store.js";
1
2
  import { projectDeferredTurnSummaries, projectDeferredTurnSummary, } from "./deferred-turn.js";
2
3
  /**
3
4
  * Process-wide Session baseline, mirroring DSH's independent session.list.
@@ -33,6 +34,7 @@ export class ChatSessionCatalog {
33
34
  : {}),
34
35
  deferredTurn: projectDeferredTurnSummary(record.deferredTurns?.[0]),
35
36
  deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
37
+ isBlank: isBlankChatSession(record),
36
38
  ...(workspaceId ? { workspaceId } : {}),
37
39
  });
38
40
  }