@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +130 -3
  3. package/README.md +2 -2
  4. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  5. package/dist/adapters/context-transfer/pi-session.js +61 -0
  6. package/dist/application/dag/generate-task-dag.js +6 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +21 -5
  9. package/dist/cli/program.js +61 -4
  10. package/dist/commands/client-recovery.js +8 -36
  11. package/dist/commands/dag-artifact.js +284 -0
  12. package/dist/commands/dag-context.js +184 -0
  13. package/dist/commands/dag-rerun.js +206 -1
  14. package/dist/commands/init-model-catalog.js +22 -0
  15. package/dist/commands/task-advance.js +23 -1
  16. package/dist/commands/task-source-prepare.js +5 -0
  17. package/dist/commands/task-status.js +1 -0
  18. package/dist/executors/dag-pi-executor.js +39 -12
  19. package/dist/executors/pi-sdk-executor.js +36 -5
  20. package/dist/executors/shell-executor.js +64 -17
  21. package/dist/governance/checks.js +1 -0
  22. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  23. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  24. package/dist/infrastructure/console/context-export-store.js +160 -0
  25. package/dist/infrastructure/console/dir-lock.js +132 -0
  26. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  27. package/dist/infrastructure/harness/artifact-store.js +10 -1
  28. package/dist/infrastructure/harness/atomic-write.js +12 -2
  29. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  30. package/dist/shared/context-transfer.js +418 -0
  31. package/dist/shared/operator/capabilities.js +23 -1
  32. package/dist/shared/operator/safe-run-summary.js +1 -0
  33. package/dist/shared/path-safety.js +93 -0
  34. package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
  35. package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
  36. package/dist/shared/pi-context-pressure/env.js +58 -0
  37. package/dist/shared/pi-context-pressure/extension.js +100 -0
  38. package/dist/shared/pi-context-pressure/index.js +7 -0
  39. package/dist/shared/pi-context-pressure/overflow.js +252 -0
  40. package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
  41. package/dist/shared/pi-context-pressure/telemetry.js +51 -0
  42. package/dist/shared/preview.js +28 -4
  43. package/dist/task/config-types.js +2 -1
  44. package/dist/task/task-demand-routing.js +3 -0
  45. package/dist/worker/console/chat/assistant-content.js +7 -0
  46. package/dist/worker/console/chat/chat-event-store.js +84 -13
  47. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  48. package/dist/worker/console/chat/goals.js +300 -0
  49. package/dist/worker/console/chat/pi-runtime.js +285 -8
  50. package/dist/worker/console/chat/provider-error.js +2 -1
  51. package/dist/worker/console/chat/repo-browser.js +94 -12
  52. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  53. package/dist/worker/console/chat/routes.js +487 -30
  54. package/dist/worker/console/chat/session-catalog.js +48 -0
  55. package/dist/worker/console/chat/session-stats.js +96 -0
  56. package/dist/worker/console/chat/session-store.js +28 -19
  57. package/dist/worker/console/chat/shortcuts.js +7 -0
  58. package/dist/worker/console/chat/sift-bridge.js +1 -0
  59. package/dist/worker/console/chat/turn-process.js +32 -3
  60. package/dist/worker/console/chat/user-questions.js +1 -1
  61. package/dist/worker/console/console-update-runtime.js +1 -1
  62. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  63. package/dist/worker/console/dag-confirmation.js +1 -1
  64. package/dist/worker/console/dag-execution-receipt.js +21 -3
  65. package/dist/worker/console/doctor.js +1 -1
  66. package/dist/worker/console/draft-store.js +1 -1
  67. package/dist/worker/console/human-gate-token.js +1 -1
  68. package/dist/worker/console/index.js +3 -3
  69. package/dist/worker/console/interview/assessment.js +1 -1
  70. package/dist/worker/console/interview/session.js +1 -1
  71. package/dist/worker/console/operation-runner.js +45 -4
  72. package/dist/worker/console/operation-sse.js +1 -1
  73. package/dist/worker/console/operation-wait.js +1 -1
  74. package/dist/worker/console/operator-actions.js +124 -8
  75. package/dist/worker/console/operator-selection.js +6 -2
  76. package/dist/worker/console/operator-surface-health.js +1 -1
  77. package/dist/worker/console/operator-user-error.js +169 -0
  78. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  79. package/dist/worker/console/pi-plugins.js +179 -0
  80. package/dist/worker/console/routes.js +864 -13
  81. package/dist/worker/console/security.js +35 -0
  82. package/dist/worker/console/server.js +177 -44
  83. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
  84. package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
  85. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
  86. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
  87. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
  88. package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
  89. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
  90. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
  99. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
  100. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
  101. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
  102. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
  103. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
  104. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
  105. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
  111. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
  112. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
  113. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
  114. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
  115. package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
  116. package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
  117. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
  118. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
  119. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
  120. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
  121. package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
  122. package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
  123. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
  124. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
  125. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
  126. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
  127. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
  128. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
  129. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
  130. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
  131. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
  132. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
  133. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
  134. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
  135. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
  136. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
  137. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
  138. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
  139. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
  140. package/dist/worker/console/static/index.html +7 -2
  141. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  142. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  143. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  144. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  145. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
  146. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  147. package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
  148. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  149. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  150. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  151. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  152. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  153. package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
  154. package/dist/worker/console/workspace-context.js +56 -5
  155. package/dist/worker/console/workspace-registry.js +335 -62
  156. package/dist/worker/continuation/worker-continuation.js +278 -0
  157. package/dist/worker/observe/health.js +1 -1
  158. package/dist/worker/observe/node-transparency.js +572 -0
  159. package/dist/worker/observe/routes.js +51 -1
  160. package/dist/worker/observe/static/api.js +69 -5
  161. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  162. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  163. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  164. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  165. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  166. package/dist/worker/observe/static/operator-chrome.css +23 -4
  167. package/dist/worker/observe/static/operator-chrome.js +7 -2
  168. package/dist/worker/observe/static/state.js +3 -1
  169. package/dist/worker/observe/static/styles.css +1108 -135
  170. package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
  171. package/dist/worker/observe/static/views/dags.js +7 -5
  172. package/dist/worker/observe/static/views/session-timeline.js +210 -10
  173. package/dist/worker/outcomes/adapters.js +19 -0
  174. package/dist/worker/outcomes/types.js +1 -0
  175. package/dist/worker/pool/attempt-lease.js +97 -66
  176. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  177. package/dist/worker/pool/reconcile.js +46 -1
  178. package/dist/worker/pool/run-store.js +2 -0
  179. package/dist/worker/pool/state-projection.js +2 -0
  180. package/dist/worker/task-spec/workflow-routing.js +8 -3
  181. package/dist/workflows/dag/artifact-bindings.js +149 -0
  182. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  183. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
  184. package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
  185. package/dist/workflows/dag/backend-test-result-contract.js +4 -0
  186. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  187. package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
  188. package/dist/workflows/dag/budget-enforcement.js +10 -0
  189. package/dist/workflows/dag/context-receipt.js +305 -0
  190. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  191. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  192. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  193. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  194. package/dist/workflows/dag/init-hybrid.js +151 -4
  195. package/dist/workflows/dag/node-execution.js +112 -27
  196. package/dist/workflows/dag/path-safety.js +1 -0
  197. package/dist/workflows/dag/prompt.js +29 -4
  198. package/dist/workflows/dag/rerun-plan.js +555 -1
  199. package/dist/workflows/dag/rerun-run.js +601 -15
  200. package/dist/workflows/dag/runner.js +20 -1
  201. package/dist/workflows/dag/skill-snapshot.js +17 -0
  202. package/dist/workflows/dag/types.js +183 -7
  203. package/docs/architecture/runtime-boundaries.md +8 -7
  204. package/docs/architecture/worker-and-feature.md +1 -1
  205. package/docs/templates/agent-dag.schema.json +32 -0
  206. package/docs/templates/backend-test-dag.json +6 -5
  207. package/docs/templates/init-managed-agents.md +1 -1
  208. package/docs/templates/product-line/task.yaml +1 -1
  209. package/package.json +5 -2
  210. package/skills/loop-agent/references/command-reference.md +21 -1
  211. package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
  212. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
  213. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
  214. package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
  215. package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
  216. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
  217. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
  218. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -24,7 +24,7 @@ import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./r
24
24
  import { ComposerDraftStore } from "./composer-draft-store.js";
25
25
  import { walkRepoFiles } from "./repo-walk.js";
26
26
  import { isSensitivePath, scrubSecrets } from "./explore-tools.js";
27
- import { listRepoDirectory, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
27
+ import { listRepoDirectory, matchRepoHtmlPreviewRoute, readRepoHtmlAsset, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
28
28
  import { projectRuntimeContext } from "./runtime-context.js";
29
29
  import { patchModelsConfig, readModelsConfig, readPackageInventory, } from "./pi-console-config.js";
30
30
  import { ChatInterviewAdapter, projectChatInterviewState, } from "./interview-adapter.js";
@@ -34,11 +34,76 @@ import { dispatchOperatorAction } from "../operator-actions.js";
34
34
  import { makeHumanGateCard, } from "./human-gate-card.js";
35
35
  import { ContractApplyReceiptStore } from "./contract-apply-receipt-store.js";
36
36
  import { projectTaskContext } from "./context-panel.js";
37
- import { deriveChatSessionTelemetry } from "./session-stats.js";
37
+ import { deriveChatSessionTelemetry, deriveChatTurnTelemetry } from "./session-stats.js";
38
38
  import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, } from "../human-gate-token.js";
39
39
  import { sendJson } from "../routes.js";
40
40
  import { openSseResponse, writeSseEvent } from "../operation-sse.js";
41
41
  import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
42
+ import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
43
+ import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
44
+ import { isTerminalOperationState } from "../../../infrastructure/console/operation-store.js";
45
+ /**
46
+ * Reattach the durable event-ring order to session-store rows. The session
47
+ * store remains the content authority; the ring only supplies chronology.
48
+ * This makes refresh hydration use the same turnId/seq ordering as live SSE.
49
+ */
50
+ export function projectStoredMessagesWithEventOrder(messages, events) {
51
+ let assistantCursor = 0;
52
+ const nextAssistantEvent = (message) => {
53
+ const expectedKind = message.kind === "provider-error" ? "error" : "message_end";
54
+ const messageAt = Date.parse(message.at);
55
+ for (let index = assistantCursor; index < events.length; index += 1) {
56
+ const event = events[index];
57
+ if (event.kind !== expectedKind)
58
+ continue;
59
+ const data = event.data;
60
+ const eventText = typeof data.text === "string"
61
+ ? data.text
62
+ : typeof data.message === "string"
63
+ ? data.message
64
+ : undefined;
65
+ const closeInTime = Number.isFinite(messageAt) && Number.isFinite(Date.parse(event.at))
66
+ ? Math.abs(Date.parse(event.at) - messageAt) <= 30_000
67
+ : true;
68
+ if (eventText === message.text && closeInTime) {
69
+ assistantCursor = index + 1;
70
+ return event;
71
+ }
72
+ }
73
+ // The bounded ring may no longer contain this old message. Leave it on the
74
+ // legacy timestamp fallback; never steal a newer event just to fill a seq.
75
+ return undefined;
76
+ };
77
+ return messages.map((message) => {
78
+ let start;
79
+ let finish;
80
+ if (message.role === "user") {
81
+ start = events.find((event) => {
82
+ if (event.kind !== "user_message")
83
+ return false;
84
+ const payload = event.data;
85
+ return payload.message?.id === message.id;
86
+ });
87
+ }
88
+ else if (message.role === "assistant") {
89
+ start = nextAssistantEvent(message);
90
+ }
91
+ else if (message.role === "tool" && message.toolCallId) {
92
+ start = events.find((event) => event.kind === "tool_call" &&
93
+ event.data.toolCallId ===
94
+ message.toolCallId);
95
+ finish = events.find((event) => event.kind === "tool_result" &&
96
+ event.data.toolCallId ===
97
+ message.toolCallId);
98
+ start ??= finish;
99
+ }
100
+ return {
101
+ ...message,
102
+ ...(start ? { eventSeq: start.seq, turnId: start.turnId } : {}),
103
+ ...(finish ? { finishedSeq: finish.seq } : {}),
104
+ };
105
+ });
106
+ }
42
107
  function turnSummary(turn) {
43
108
  return {
44
109
  turnId: turn.turnId,
@@ -514,6 +579,7 @@ workspaceId) {
514
579
  // a fallback for direct (non-dispatcher) handler tests.
515
580
  workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
516
581
  });
582
+ await deps.attachSession?.(handle.sessionId).catch(() => undefined);
517
583
  if (snapshot)
518
584
  deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
519
585
  kind: "compact",
@@ -595,6 +661,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
595
661
  targetSessionId: handle.sessionId,
596
662
  targetSessionFile: handle.sessionFile,
597
663
  });
664
+ await deps.attachSession?.(handle.sessionId).catch(() => undefined);
598
665
  const copied = deps.events.forkContextRefs({
599
666
  sourceSessionId,
600
667
  targetSessionId: handle.sessionId,
@@ -746,6 +813,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
746
813
  eventEpoch: deps.events.epoch(sessionId),
747
814
  session: {
748
815
  ...record,
816
+ messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
749
817
  composerDraft,
750
818
  activeTurnId: activeTurn?.turnId,
751
819
  turnPhase: activeTurn
@@ -755,10 +823,15 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
755
823
  : "idle",
756
824
  pendingQuestion: pendingQuestion ?? null,
757
825
  todos: projectCurrentTodos(snapshot) ?? [],
826
+ goal: deps.goalRegistry?.get(sessionId) ?? null,
758
827
  },
759
828
  hasBash: true,
760
829
  sessionStats: telemetry.stats,
761
830
  usage: telemetry.usage,
831
+ turnTelemetry: deriveChatTurnTelemetry(snapshot),
832
+ contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
833
+ ? deps.runtime.getContextPressureTelemetry(sessionId)
834
+ : undefined,
762
835
  compactions: snapshot
763
836
  .filter((event) => event.kind === "compact" ||
764
837
  event.kind === "compaction_start" ||
@@ -767,6 +840,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
767
840
  eventId: event.eventId,
768
841
  kind: event.kind,
769
842
  at: event.at,
843
+ seq: event.seq,
844
+ turnId: event.turnId,
770
845
  data: event.data,
771
846
  })),
772
847
  lastEventId: lastEvent?.eventId ?? null,
@@ -1070,9 +1145,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
1070
1145
  deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
1071
1146
  pendingQuestion: pendingQuestion ?? null,
1072
1147
  todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
1148
+ goal: deps.goalRegistry?.get(sessionId) ?? null,
1073
1149
  },
1074
1150
  });
1075
1151
  }
1152
+ function goalHttpStatus(code) {
1153
+ if (code === "GOAL_NOT_FOUND")
1154
+ return 404;
1155
+ if (code === "GOAL_INVALID_INPUT")
1156
+ return 400;
1157
+ return 409;
1158
+ }
1159
+ export async function handleChatGoal(req, res, deps, sessionId) {
1160
+ const registry = deps.goalRegistry;
1161
+ if (!registry) {
1162
+ sendJson(res, 503, {
1163
+ ok: false,
1164
+ error: { code: "GOAL_UNAVAILABLE", message: "Goal runtime is unavailable" },
1165
+ });
1166
+ return;
1167
+ }
1168
+ if (!(await deps.store.get(sessionId))) {
1169
+ sendJson(res, 404, {
1170
+ ok: false,
1171
+ error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
1172
+ });
1173
+ return;
1174
+ }
1175
+ if (req.method === "GET") {
1176
+ sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
1177
+ return;
1178
+ }
1179
+ const gate = gateMutation(req, deps);
1180
+ if (!gate.ok) {
1181
+ sendJson(res, gate.status, {
1182
+ ok: false,
1183
+ error: { code: gate.code, message: gate.message },
1184
+ });
1185
+ return;
1186
+ }
1187
+ let body = {};
1188
+ try {
1189
+ body = await readJsonBody(req);
1190
+ }
1191
+ catch (error) {
1192
+ sendJson(res, 400, {
1193
+ ok: false,
1194
+ error: {
1195
+ code: "GOAL_INVALID_INPUT",
1196
+ message: error instanceof Error ? error.message : String(error),
1197
+ },
1198
+ });
1199
+ return;
1200
+ }
1201
+ if (req.method === "POST") {
1202
+ const result = registry.create({
1203
+ sessionId,
1204
+ objective: body.objective,
1205
+ maxRounds: typeof body.maxRounds === "number" ? body.maxRounds : undefined,
1206
+ arm: true,
1207
+ });
1208
+ if (!result.ok) {
1209
+ sendJson(res, goalHttpStatus(result.code), {
1210
+ ok: false,
1211
+ error: { code: result.code, message: result.message },
1212
+ goal: result.goal,
1213
+ });
1214
+ return;
1215
+ }
1216
+ deps.goalRoundDriver?.wake(sessionId);
1217
+ sendJson(res, 201, { ok: true, goal: result.goal });
1218
+ return;
1219
+ }
1220
+ const current = registry.get(sessionId);
1221
+ const goalId = typeof body.goalId === "string" ? body.goalId : "";
1222
+ const revision = typeof body.revision === "number" ? body.revision : Number.NaN;
1223
+ if (!current || !goalId || !Number.isSafeInteger(revision)) {
1224
+ sendJson(res, current ? 400 : 404, {
1225
+ ok: false,
1226
+ error: {
1227
+ code: current ? "GOAL_INVALID_INPUT" : "GOAL_NOT_FOUND",
1228
+ message: current
1229
+ ? "goalId and integer revision are required"
1230
+ : "Goal not found",
1231
+ },
1232
+ });
1233
+ return;
1234
+ }
1235
+ if (req.method === "DELETE") {
1236
+ const result = registry.clear({
1237
+ sessionId,
1238
+ goalId,
1239
+ expectedRevision: revision,
1240
+ });
1241
+ if (!result.ok) {
1242
+ sendJson(res, goalHttpStatus(result.code), {
1243
+ ok: false,
1244
+ error: { code: result.code, message: result.message },
1245
+ goal: result.goal,
1246
+ });
1247
+ return;
1248
+ }
1249
+ deps.goalRoundDriver?.cancel(sessionId);
1250
+ sendJson(res, 200, { ok: true, goal: null });
1251
+ return;
1252
+ }
1253
+ if (req.method === "PATCH") {
1254
+ const action = typeof body.action === "string" ? body.action : "edit";
1255
+ let phase;
1256
+ let arm;
1257
+ if (action === "pause") {
1258
+ phase = "paused";
1259
+ arm = false;
1260
+ }
1261
+ else if (action === "resume") {
1262
+ phase = "active";
1263
+ arm = true;
1264
+ }
1265
+ else if (action === "edit") {
1266
+ phase = current.phase;
1267
+ arm = current.armed;
1268
+ }
1269
+ else {
1270
+ sendJson(res, 400, {
1271
+ ok: false,
1272
+ error: { code: "GOAL_INVALID_INPUT", message: "unsupported Goal action" },
1273
+ });
1274
+ return;
1275
+ }
1276
+ const result = registry.update({
1277
+ sessionId,
1278
+ goalId,
1279
+ expectedRevision: revision,
1280
+ phase,
1281
+ arm,
1282
+ ...(body.objective !== undefined ? { objective: body.objective } : {}),
1283
+ });
1284
+ if (!result.ok) {
1285
+ sendJson(res, goalHttpStatus(result.code), {
1286
+ ok: false,
1287
+ error: { code: result.code, message: result.message },
1288
+ goal: result.goal,
1289
+ });
1290
+ return;
1291
+ }
1292
+ if (action === "resume")
1293
+ deps.goalRoundDriver?.wake(sessionId);
1294
+ else if (action === "pause")
1295
+ deps.goalRoundDriver?.cancel(sessionId);
1296
+ sendJson(res, 200, { ok: true, goal: result.goal });
1297
+ return;
1298
+ }
1299
+ sendJson(res, 405, {
1300
+ ok: false,
1301
+ error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" },
1302
+ });
1303
+ }
1076
1304
  export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
1077
1305
  const gate = gateMutation(req, deps);
1078
1306
  if (!gate.ok) {
@@ -1121,27 +1349,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
1121
1349
  }
1122
1350
  }
1123
1351
  export async function handleListChatSessions(_req, res, deps) {
1124
- const sessions = await deps.store.list({ includeArchived: true });
1352
+ const sessions = await listChatSessionSummaries(deps);
1125
1353
  sendJson(res, 200, {
1126
1354
  ok: true,
1127
1355
  schemaVersion: 2,
1128
- sessions: sessions.map((session) => {
1129
- const active = deps.events.getActiveTurn(session.sessionId);
1130
- return {
1131
- ...session,
1132
- activeTurnId: active?.turnId,
1133
- turnPhase: active?.abortRequestedAt
1134
- ? "stopping"
1135
- : active
1136
- ? "running"
1137
- : "idle",
1138
- deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
1139
- deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
1140
- };
1141
- }),
1356
+ sessions,
1142
1357
  hasBash: true,
1143
1358
  });
1144
1359
  }
1360
+ /**
1361
+ * Build sidebar-only summaries from one Workspace store without opening a
1362
+ * runtime. Message bodies, Pi session paths and tool audit rows belong to the
1363
+ * detail endpoint; returning them here makes the global multi-Workspace index
1364
+ * grow with the entire chat history.
1365
+ */
1366
+ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
1367
+ const sessions = await deps.store.list({ includeArchived: true });
1368
+ return sessions.map((session) => {
1369
+ const active = deps.events?.getActiveTurn(session.sessionId);
1370
+ const workspaceId = ownerWorkspaceId ?? session.workspaceId;
1371
+ return {
1372
+ sessionId: session.sessionId,
1373
+ ...(workspaceId ? { workspaceId } : {}),
1374
+ state: session.state,
1375
+ createdAt: session.createdAt,
1376
+ updatedAt: session.updatedAt,
1377
+ ...(session.title !== undefined ? { title: session.title } : {}),
1378
+ ...(session.displayTitle !== undefined
1379
+ ? { displayTitle: session.displayTitle }
1380
+ : {}),
1381
+ ...(session.modelProvider !== undefined
1382
+ ? { modelProvider: session.modelProvider }
1383
+ : {}),
1384
+ ...(session.modelId !== undefined ? { modelId: session.modelId } : {}),
1385
+ ...(session.thinkingLevel !== undefined
1386
+ ? { thinkingLevel: session.thinkingLevel }
1387
+ : {}),
1388
+ activeTurnId: active?.turnId,
1389
+ turnPhase: active?.abortRequestedAt
1390
+ ? "stopping"
1391
+ : active
1392
+ ? "running"
1393
+ : "idle",
1394
+ deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
1395
+ deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
1396
+ };
1397
+ });
1398
+ }
1145
1399
  export async function handlePatchChatSession(req, res, deps, sessionId) {
1146
1400
  const gate = gateMutation(req, deps);
1147
1401
  if (!gate.ok) {
@@ -1644,6 +1898,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1644
1898
  });
1645
1899
  return;
1646
1900
  }
1901
+ try {
1902
+ trustedPayload?.onTurnCreated?.(turn.turnId);
1903
+ }
1904
+ catch (error) {
1905
+ const message = error instanceof Error ? error.message : String(error);
1906
+ deps.events.setTurnState(sessionId, turn.turnId, "failed", {
1907
+ code: "GOAL_ADMISSION_STALE",
1908
+ message,
1909
+ });
1910
+ sendJson(res, 409, {
1911
+ ok: false,
1912
+ accepted: false,
1913
+ error: { code: "GOAL_ADMISSION_STALE", message },
1914
+ });
1915
+ return;
1916
+ }
1647
1917
  const controller = new AbortController();
1648
1918
  const execution = deps.turnExecutions?.register({
1649
1919
  sessionId,
@@ -1693,6 +1963,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1693
1963
  if (placeholder)
1694
1964
  emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
1695
1965
  },
1966
+ persistUserMessage: trustedPayload?.persistUserMessage,
1696
1967
  onOperationAccepted: (accepted) => deps.operationLinker.link({
1697
1968
  sessionId,
1698
1969
  turnId: turn.turnId,
@@ -1755,16 +2026,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1755
2026
  }
1756
2027
  }
1757
2028
  finally {
2029
+ if (terminalState !== "settled") {
2030
+ pauseGoalForSafety(deps, sessionId, turn.turnId);
2031
+ deps.goalRoundDriver?.cancel(sessionId);
2032
+ }
1758
2033
  execution?.resolveCompletion({
1759
2034
  ok: terminalState === "settled",
1760
2035
  state: terminalState,
1761
2036
  error: terminalError,
1762
2037
  });
1763
2038
  deps.turnExecutions?.clear(sessionId, turn.turnId);
1764
- void dispatchDeferredTurn(deps, sessionId);
2039
+ void dispatchDeferredTurn(deps, sessionId).finally(() => {
2040
+ deps.goalRoundDriver?.wake(sessionId);
2041
+ });
1765
2042
  }
1766
2043
  })();
1767
2044
  }
2045
+ function buildGoalRoundPrompt(goal) {
2046
+ const remaining = Math.max(0, goal.maxRounds - goal.round);
2047
+ return [
2048
+ buildGoalContinuationContext(goal),
2049
+ `This is autonomous Goal round ${goal.round + 1} of at most ${goal.maxRounds} (${remaining} rounds remain before this one).`,
2050
+ "Continue from the current session state. Prefer the user's queued or newly supplied instructions when present. Use todo_write for multi-step work.",
2051
+ "Before ending this round, call get_goal. If the objective is fully achieved, call update_goal(status=complete). If no safe progress remains, call update_goal(status=blocked) with the concrete blocker. Otherwise leave it active for the next bounded round.",
2052
+ ].filter(Boolean).join("\n\n");
2053
+ }
2054
+ function pauseGoalForSafety(deps, sessionId, turnId) {
2055
+ const current = deps.goalRegistry?.get(sessionId);
2056
+ if (!current || current.phase !== "active") {
2057
+ deps.goalRegistry?.disarm(sessionId);
2058
+ return;
2059
+ }
2060
+ deps.goalRegistry?.update({
2061
+ sessionId,
2062
+ goalId: current.goalId,
2063
+ expectedRevision: current.revision,
2064
+ phase: "paused",
2065
+ arm: false,
2066
+ turnId,
2067
+ });
2068
+ }
2069
+ export function linkedOperationIdsForGoal(events) {
2070
+ const linked = new Set();
2071
+ for (const event of events) {
2072
+ const operationId = event.data && typeof event.data === "object"
2073
+ ? event.data.operationId
2074
+ : undefined;
2075
+ if (typeof operationId !== "string" || !operationId)
2076
+ continue;
2077
+ if (event.kind === "operation-ref" || event.kind === "operation-status")
2078
+ linked.add(operationId);
2079
+ if (event.kind === "operation-unlinked")
2080
+ linked.delete(operationId);
2081
+ }
2082
+ return [...linked];
2083
+ }
2084
+ /** One admission attempt used by the process-local GoalRoundDriver. */
2085
+ export async function driveGoalRound(deps, sessionId) {
2086
+ const registry = deps.goalRegistry;
2087
+ if (!registry || !registry.isArmed(sessionId))
2088
+ return "idle";
2089
+ const goal = registry.get(sessionId);
2090
+ if (!goal || goal.phase !== "active" || !goal.armed)
2091
+ return "idle";
2092
+ if (goal.round >= goal.maxRounds) {
2093
+ registry.update({
2094
+ sessionId,
2095
+ goalId: goal.goalId,
2096
+ expectedRevision: goal.revision,
2097
+ phase: "paused",
2098
+ arm: false,
2099
+ });
2100
+ return "idle";
2101
+ }
2102
+ const record = await deps.store.get(sessionId);
2103
+ if (!record) {
2104
+ registry.disarm(sessionId);
2105
+ return "idle";
2106
+ }
2107
+ const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
2108
+ const operationStore = deps.runtime.actionContext?.operations;
2109
+ const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
2110
+ const operations = operationStore
2111
+ ? await Promise.all(linkedOperationIds.map((operationId) => operationStore.get(operationId)))
2112
+ : [];
2113
+ const admission = classifyGoalRoundAdmission({
2114
+ phase: goal.phase,
2115
+ armed: goal.armed,
2116
+ round: goal.round,
2117
+ maxRounds: goal.maxRounds,
2118
+ hasQueuedUserTurn: (record.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"),
2119
+ hasActiveTurn: Boolean(deps.events.getActiveTurn(sessionId)),
2120
+ runtimeBusy: Object.values(runtimeFacts).some((value) => value === true),
2121
+ operationBusy: operations.some((operation) => Boolean(operation) && !isTerminalOperationState(operation.state)),
2122
+ });
2123
+ if (admission.action === "wait")
2124
+ return "wait";
2125
+ if (admission.action === "idle")
2126
+ return "idle";
2127
+ const expectedRound = goal.round + 1;
2128
+ const clientRequestId = `goal:${goal.goalId}:${expectedRound}`;
2129
+ const started = await startTrustedChatTurn(deps, sessionId, {
2130
+ clientRequestId,
2131
+ text: buildGoalRoundPrompt(goal),
2132
+ images: [],
2133
+ persistUserMessage: false,
2134
+ onTurnCreated: (turnId) => {
2135
+ const advanced = registry.beginRound({
2136
+ sessionId,
2137
+ goalId: goal.goalId,
2138
+ expectedRevision: goal.revision,
2139
+ turnId,
2140
+ });
2141
+ if (!advanced.ok) {
2142
+ registry.disarm(sessionId);
2143
+ throw new Error(advanced.message);
2144
+ }
2145
+ },
2146
+ });
2147
+ const accepted = started.body.accepted === true;
2148
+ const turnId = started.body.turn && typeof started.body.turn === "object"
2149
+ ? started.body.turn.turnId
2150
+ : undefined;
2151
+ if (!accepted || !turnId) {
2152
+ const code = started.body.error && typeof started.body.error === "object"
2153
+ ? String(started.body.error.code ?? "")
2154
+ : "";
2155
+ if (code === "TURN_ACTIVE")
2156
+ return "wait";
2157
+ registry.disarm(sessionId);
2158
+ return "idle";
2159
+ }
2160
+ return "started";
2161
+ }
1768
2162
  async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
1769
2163
  const record = await deps.store.get(sessionId);
1770
2164
  if (!record)
@@ -1985,6 +2379,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
1985
2379
  });
1986
2380
  return;
1987
2381
  }
2382
+ pauseGoalForSafety(deps, sessionId, turnId);
2383
+ deps.goalRoundDriver?.cancel(sessionId);
1988
2384
  if (isTerminalTurnState(turn.state)) {
1989
2385
  sendJson(res, 200, {
1990
2386
  ok: true,
@@ -2155,6 +2551,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
2155
2551
  }
2156
2552
  await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
2157
2553
  deps.todoRegistry?.clear(sessionId);
2554
+ deps.goalRegistry?.clearRuntime(sessionId);
2555
+ deps.goalRoundDriver?.cancel(sessionId);
2158
2556
  deps.runtime.dispose(sessionId);
2159
2557
  deps.operationLinker.clearSession(sessionId);
2160
2558
  deps.events.clear(sessionId);
@@ -2170,6 +2568,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
2170
2568
  });
2171
2569
  return;
2172
2570
  }
2571
+ await deps.detachSession?.(sessionId).catch(() => undefined);
2173
2572
  sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
2174
2573
  }
2175
2574
  export async function handleGetTaskContext(_res, res, deps, sessionId) {
@@ -2229,6 +2628,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
2229
2628
  });
2230
2629
  }
2231
2630
  }
2631
+ async function handleRepoHtmlPreview(res, deps, rawPath) {
2632
+ try {
2633
+ const asset = await readRepoHtmlAsset(deps.appData.repoRoot, rawPath);
2634
+ const origin = deps.getConsoleOrigin();
2635
+ res.writeHead(200, {
2636
+ "Content-Type": asset.contentType,
2637
+ "Content-Length": asset.body.byteLength,
2638
+ "Cache-Control": "no-store",
2639
+ "X-Content-Type-Options": "nosniff",
2640
+ "Referrer-Policy": "no-referrer",
2641
+ // The iframe also has an empty sandbox attribute. This response-level
2642
+ // sandbox keeps direct/popup loads isolated, while explicit local-origin
2643
+ // style/image/font/media sources let relative project assets render.
2644
+ "Content-Security-Policy": [
2645
+ "sandbox",
2646
+ "default-src 'none'",
2647
+ "script-src 'none'",
2648
+ "object-src 'none'",
2649
+ "frame-src 'none'",
2650
+ "form-action 'none'",
2651
+ "base-uri 'none'",
2652
+ "connect-src 'none'",
2653
+ `style-src 'unsafe-inline' ${origin}`,
2654
+ `img-src data: blob: ${origin}`,
2655
+ `font-src data: ${origin}`,
2656
+ `media-src blob: ${origin}`,
2657
+ ].join("; "),
2658
+ });
2659
+ res.end(asset.body);
2660
+ }
2661
+ catch (error) {
2662
+ const failure = error instanceof RepoBrowserError
2663
+ ? error
2664
+ : new RepoBrowserError(500, "READ_FAILED", error instanceof Error ? error.message : String(error));
2665
+ sendJson(res, failure.status, {
2666
+ ok: false,
2667
+ error: { code: failure.code, message: failure.message },
2668
+ });
2669
+ }
2670
+ }
2232
2671
  async function handlePiConfig(req, res, deps, resource) {
2233
2672
  try {
2234
2673
  if (resource === "models") {
@@ -2974,7 +3413,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
2974
3413
  taskId,
2975
3414
  confirmationId,
2976
3415
  expiresAt: confirmation.expiresAt,
2977
- inspectHref: `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3416
+ inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
2978
3417
  humanGateToken: confirmation.humanGateToken,
2979
3418
  displayOnlyChallenges: [
2980
3419
  "source",
@@ -3000,7 +3439,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
3000
3439
  receiptId: receipt.receiptId,
3001
3440
  expiresAt: receipt.expiresAt,
3002
3441
  humanGateToken: receipt.humanGateToken,
3003
- inspectHref: `/inspect/#/`,
3442
+ inspectHref: `/#/inspect`,
3004
3443
  }));
3005
3444
  }
3006
3445
  function actionResultBody(result) {
@@ -3089,7 +3528,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3089
3528
  humanGateToken,
3090
3529
  contractRevision: binding.revision,
3091
3530
  contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
3092
- inspectHref: `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3531
+ inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
3093
3532
  });
3094
3533
  appendHumanGateCard(deps, sessionId, card);
3095
3534
  sendJson(res, 201, { ok: true, card });
@@ -3143,8 +3582,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3143
3582
  contractRevision: receipt.revision,
3144
3583
  operationId,
3145
3584
  inspectHref: operationId
3146
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3147
- : `/inspect/?taskId=${encodeURIComponent(receipt.taskId)}#/`,
3585
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3586
+ : `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
3148
3587
  });
3149
3588
  appendHumanGateCard(deps, sessionId, card);
3150
3589
  sendJson(res, 202, {
@@ -3190,8 +3629,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3190
3629
  contractRevision: receipt.revision,
3191
3630
  operationId,
3192
3631
  inspectHref: operationId
3193
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3194
- : `/inspect/?taskId=${encodeURIComponent(receipt.taskId)}#/`,
3632
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3633
+ : `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
3195
3634
  });
3196
3635
  appendHumanGateCard(deps, sessionId, card);
3197
3636
  if (operationId)
@@ -3292,8 +3731,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
3292
3731
  confirmationId,
3293
3732
  expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
3294
3733
  inspectHref: operationId
3295
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3296
- : `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3734
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3735
+ : `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
3297
3736
  ...(confirmation?.humanGateToken
3298
3737
  ? { humanGateToken: confirmation.humanGateToken }
3299
3738
  : {}),
@@ -3387,8 +3826,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
3387
3826
  expiresAt: receipt.expiresAt,
3388
3827
  operationId,
3389
3828
  inspectHref: operationId
3390
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3391
- : `/inspect/#/`,
3829
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3830
+ : `/#/inspect`,
3392
3831
  });
3393
3832
  appendHumanGateCard(deps, sessionId, card);
3394
3833
  if (operationId) {
@@ -3512,7 +3951,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
3512
3951
  }
3513
3952
  const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
3514
3953
  const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
3515
- const customInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
3954
+ const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
3955
+ const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
3516
3956
  const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
3517
3957
  const compactionId = `${sessionId}:compaction:${randomUUID()}`;
3518
3958
  deps.events.append(sessionId, turnId, {
@@ -3649,6 +4089,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
3649
4089
  await handleChatCapabilities(req, res, deps);
3650
4090
  return true;
3651
4091
  }
4092
+ const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
4093
+ if (method === "GET" && htmlPreviewRoute) {
4094
+ if (!htmlPreviewRoute.rawPath)
4095
+ sendJson(res, 400, {
4096
+ ok: false,
4097
+ error: { code: "INVALID_PATH", message: "path is required" },
4098
+ });
4099
+ else
4100
+ await handleRepoHtmlPreview(res, deps, htmlPreviewRoute.rawPath);
4101
+ return true;
4102
+ }
3652
4103
  if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
3653
4104
  await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
3654
4105
  return true;
@@ -3780,6 +4231,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
3780
4231
  await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
3781
4232
  return true;
3782
4233
  }
4234
+ const sessionGoal = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/goal$/);
4235
+ if (sessionGoal &&
4236
+ (method === "GET" || method === "POST" || method === "PATCH" || method === "DELETE")) {
4237
+ await handleChatGoal(req, res, deps, decodeURIComponent(sessionGoal[1]));
4238
+ return true;
4239
+ }
3783
4240
  const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
3784
4241
  if (method === "PUT" && deferredTurn) {
3785
4242
  await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));