@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.11

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 (213) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +114 -109
  3. package/README.md +2 -2
  4. package/dist/application/dag/generate-task-dag.js +6 -2
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/update/runtime-activity.js +1 -29
  8. package/dist/commands/dag-rerun.js +1 -1
  9. package/dist/executors/dag-pi-executor.js +69 -7
  10. package/dist/executors/pi-executor.js +4 -2
  11. package/dist/executors/pi-sdk-executor.js +46 -1
  12. package/dist/executors/pi-writer-tool-policy.js +57 -1
  13. package/dist/executors/shell-executor.js +107 -20
  14. package/dist/executors/workspace-write-snapshot.js +68 -0
  15. package/dist/governance/checks.js +1 -0
  16. package/dist/infrastructure/harness/atomic-write.js +23 -0
  17. package/dist/shared/dag-prompt-override.js +4 -5
  18. package/dist/shared/update/npm-client.js +33 -11
  19. package/dist/task/config-types.js +10 -0
  20. package/dist/task/contract/apply.js +11 -1
  21. package/dist/task/contract/constants.js +2 -0
  22. package/dist/task/contract/hash.js +3 -0
  23. package/dist/task/contract/observe.js +25 -4
  24. package/dist/task/contract/paths.js +2 -1
  25. package/dist/task/contract/project.js +15 -1
  26. package/dist/task/contract/schema.js +2 -0
  27. package/dist/task/contract/transaction.js +12 -0
  28. package/dist/task/source-prepare/completeness.js +188 -0
  29. package/dist/task/source-prepare/fragment-inventory.js +474 -0
  30. package/dist/task/source-prepare/index.js +3 -0
  31. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  32. package/dist/task/source-prepare/ledger-review.js +214 -0
  33. package/dist/task/source-prepare/ledger.js +545 -0
  34. package/dist/task/source-prepare/prepare.js +262 -1
  35. package/dist/task/source-prepare/semantic-intake.js +19 -3
  36. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  37. package/dist/task/source-prepare/types.js +22 -0
  38. package/dist/worker/cli.js +12 -26
  39. package/dist/worker/console/chat/chat-event-store.js +75 -5
  40. package/dist/worker/console/chat/context-panel.js +7 -0
  41. package/dist/worker/console/chat/deferred-turn.js +12 -0
  42. package/dist/worker/console/chat/pi-runtime.js +331 -18
  43. package/dist/worker/console/chat/provider-error.js +98 -0
  44. package/dist/worker/console/chat/routes.js +389 -56
  45. package/dist/worker/console/chat/session-stats.js +179 -0
  46. package/dist/worker/console/chat/session-store.js +93 -63
  47. package/dist/worker/console/chat/todos.js +115 -0
  48. package/dist/worker/console/chat/tool-preview.js +42 -0
  49. package/dist/worker/console/chat/turn-process.js +20 -55
  50. package/dist/worker/console/chat/user-questions.js +266 -0
  51. package/dist/worker/console/chat/workspace-landing.js +79 -0
  52. package/dist/worker/console/console-handoff.js +82 -0
  53. package/dist/worker/console/console-update-and-init.js +115 -0
  54. package/dist/worker/console/console-update-runtime.js +84 -0
  55. package/dist/worker/console/dag-execution-receipt.js +20 -2
  56. package/dist/worker/console/draft-store.js +49 -0
  57. package/dist/worker/console/native-directory-picker.js +13 -0
  58. package/dist/worker/console/operation-store.js +1 -1
  59. package/dist/worker/console/operator-actions.js +133 -7
  60. package/dist/worker/console/operator-surface-health.js +1 -0
  61. package/dist/worker/console/prd-intake-bridge.js +13 -0
  62. package/dist/worker/console/routes.js +312 -1
  63. package/dist/worker/console/security.js +4 -4
  64. package/dist/worker/console/server.js +583 -172
  65. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
  66. package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
  67. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
  68. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
  69. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
  70. package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
  71. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
  78. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
  79. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
  80. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
  81. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
  82. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
  83. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
  84. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
  85. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
  86. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
  89. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
  90. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
  91. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
  92. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
  93. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
  94. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
  95. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
  96. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
  97. package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
  98. package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
  99. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
  100. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
  101. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
  102. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
  103. package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
  104. package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
  105. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
  106. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
  107. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
  108. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
  109. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
  110. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
  111. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
  112. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
  113. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
  114. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
  115. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
  116. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
  117. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
  118. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
  119. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
  120. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
  121. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
  122. package/dist/worker/console/static/index.html +2 -2
  123. package/dist/worker/console/static-src/app/console-types.js +68 -4
  124. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  125. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  126. package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
  127. package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
  128. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  129. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  130. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  131. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  132. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  133. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  134. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  135. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  136. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  137. package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
  138. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  139. package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
  140. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  141. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  142. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  143. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  144. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  145. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  146. package/dist/worker/console/workspace-context.js +234 -0
  147. package/dist/worker/console/workspace-registry.js +214 -0
  148. package/dist/worker/observability/init-runtime-activity.js +26 -0
  149. package/dist/worker/observe/node-input.js +88 -2
  150. package/dist/worker/observe/routes.js +188 -0
  151. package/dist/worker/observe/server.js +4 -0
  152. package/dist/worker/observe/static/api.js +29 -9
  153. package/dist/worker/observe/static/app.js +18 -70
  154. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  155. package/dist/worker/observe/static/dag-node-purpose.js +203 -0
  156. package/dist/worker/observe/static/format.js +37 -0
  157. package/dist/worker/observe/static/index.html +6 -1
  158. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  159. package/dist/worker/observe/static/operator-chrome.css +145 -20
  160. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  161. package/dist/worker/observe/static/operator-chrome.js +330 -100
  162. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  163. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  164. package/dist/worker/observe/static/router.d.ts +46 -0
  165. package/dist/worker/observe/static/router.js +25 -1
  166. package/dist/worker/observe/static/state.d.ts +50 -0
  167. package/dist/worker/observe/static/state.js +174 -2
  168. package/dist/worker/observe/static/styles.css +357 -35
  169. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  170. package/dist/worker/observe/static/views/dag-inspector.js +533 -79
  171. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  172. package/dist/worker/observe/static/views/dag.js +6 -4
  173. package/dist/worker/observe/static/views/dashboard.js +6 -4
  174. package/dist/worker/observe/static/views/night.js +2 -2
  175. package/dist/worker/observe/static/views/pool.js +1 -0
  176. package/dist/worker/observe/static/views/run.js +3 -1
  177. package/dist/worker/observe/static/views/task.js +56 -1
  178. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +186 -8
  179. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  180. package/dist/workflows/dag/backend-test-pytest-collection.js +168 -16
  181. package/dist/workflows/dag/backend-test-result-contract.js +4 -0
  182. package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
  183. package/dist/workflows/dag/backend-test-writer-completeness.js +28 -17
  184. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  185. package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
  186. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  187. package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
  188. package/dist/workflows/dag/frontend-review-context.js +92 -10
  189. package/dist/workflows/dag/init-hybrid.js +239 -52
  190. package/dist/workflows/dag/interrupt-request.js +3 -3
  191. package/dist/workflows/dag/lifecycle.js +3 -3
  192. package/dist/workflows/dag/node-execution.js +45 -0
  193. package/dist/workflows/dag/types.js +62 -12
  194. package/docs/README.md +2 -0
  195. package/docs/architecture/evolution.md +45 -0
  196. package/docs/init-surface.manifest.json +1 -0
  197. package/docs/operations/local-development-environment.md +21 -0
  198. package/docs/templates/README.md +1 -1
  199. package/docs/templates/agent-dag.schema.json +18 -0
  200. package/docs/templates/backend-test-dag.json +267 -238
  201. package/docs/templates/frontend-implementation-contract.schema.json +681 -24
  202. package/harness.json +3 -3
  203. package/package.json +3 -1
  204. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  205. package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
  206. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
  207. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
  208. package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
  209. package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
  210. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
  211. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
  212. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  213. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -3,10 +3,12 @@ import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { dispatchOperatorAction } from "./operator-actions.js";
5
5
  import { openSseResponse, parseLastEventId, writeSseEvent, } from "./operation-sse.js";
6
+ import { probePiReadiness } from "./pi-readiness.js";
6
7
  import { buildOperatorSurfaceHealthV1 } from "./operator-surface-health.js";
7
8
  import { ROUTES } from "../observe/routes.js";
8
9
  import { evaluateMutationGate, injectConfirmationTokenScript, isMutationMethod, } from "./security.js";
9
10
  import { listDagRunsForRecovery } from "./resolve-dag-run-for-task.js";
11
+ import { pickNativeWorkspaceDirectory } from "./native-directory-picker.js";
10
12
  export function sendJson(res, status, body, extraHeaders) {
11
13
  const payload = JSON.stringify(body);
12
14
  res.writeHead(status, {
@@ -38,6 +40,10 @@ function contentTypeFor(pathname) {
38
40
  return "application/octet-stream";
39
41
  }
40
42
  async function readJsonBody(req, maxBytes = 10 * 1024 * 1024) {
43
+ // Body pre-read by the multi-workspace dispatcher (workspaceId resolution).
44
+ const preRead = req.__preReadBody;
45
+ if (preRead !== undefined)
46
+ return preRead;
41
47
  const chunks = [];
42
48
  let total = 0;
43
49
  for await (const chunk of req) {
@@ -67,6 +73,7 @@ export async function handleConsoleHealthRequest(_req, res, ctx) {
67
73
  piReadiness: ctx.operator?.getReadiness().state ?? "setup-required",
68
74
  inspectReady: ctx.inspect.ready,
69
75
  routes: ROUTES,
76
+ consoleInstanceId: ctx.consoleInstanceId,
70
77
  }));
71
78
  }
72
79
  catch {
@@ -77,6 +84,7 @@ export async function handleConsoleHealthRequest(_req, res, ctx) {
77
84
  repoFingerprint: "unavailable",
78
85
  packageName: "unknown",
79
86
  packageVersion: "0.0.0",
87
+ consoleInstanceId: ctx.consoleInstanceId,
80
88
  piReadiness: ctx.operator?.getReadiness().state ?? "setup-required",
81
89
  inspect: {
82
90
  ready: ctx.inspect.ready,
@@ -217,6 +225,7 @@ async function handleCreateOperation(req, res, ctx) {
217
225
  action,
218
226
  actionParams,
219
227
  clientRequestId,
228
+ source: "human-ui",
220
229
  });
221
230
  sendJson(res, result.status, result.body);
222
231
  }
@@ -242,7 +251,35 @@ async function handleGetOperation(_req, res, ctx, operationId) {
242
251
  });
243
252
  return;
244
253
  }
245
- sendJson(res, 200, { ok: true, operation: op });
254
+ sendJson(res, 200, {
255
+ ok: true,
256
+ workspaceId: ctx.workspace?.workspaceId,
257
+ operation: { ...op, workspaceId: ctx.workspace?.workspaceId },
258
+ });
259
+ }
260
+ /**
261
+ * GET /api/operator/v1/operations?workspaceId= — NEW per-workspace list route
262
+ * (multi-workspace foundation). Returns only the resolved Context's Operations
263
+ * and injects the owning workspaceId. operation-store.ts is unchanged.
264
+ */
265
+ async function handleListOperations(_req, res, ctx) {
266
+ if (!ctx.operator) {
267
+ sendJson(res, 503, {
268
+ ok: false,
269
+ error: {
270
+ code: "INTERNAL_ERROR",
271
+ message: "operator runtime unavailable",
272
+ },
273
+ });
274
+ return;
275
+ }
276
+ const operations = await ctx.operator.operations.list();
277
+ sendJson(res, 200, {
278
+ ok: true,
279
+ schemaVersion: 1,
280
+ workspaceId: ctx.workspace?.workspaceId,
281
+ ops: operations.map((op) => ({ ...op, workspaceId: ctx.workspace?.workspaceId })),
282
+ });
246
283
  }
247
284
  async function handleOperationEvents(req, res, ctx, operationId) {
248
285
  if (!ctx.operator) {
@@ -450,6 +487,10 @@ export async function handleConsoleOperatorRequest(req, res, ctx) {
450
487
  await handleCreateOperation(req, res, ctx);
451
488
  return true;
452
489
  }
490
+ if (method === "GET" && pathname === "/api/operator/v1/operations") {
491
+ await handleListOperations(req, res, ctx);
492
+ return true;
493
+ }
453
494
  if (method === "GET" && pathname === "/api/operator/v1/night-aux-ticker") {
454
495
  await handleNightAuxTickerGet(req, res, ctx);
455
496
  return true;
@@ -514,3 +555,273 @@ export async function handleConsoleRequest(req, res, ctx) {
514
555
  }
515
556
  return false;
516
557
  }
558
+ export function createWorkspaceReadinessCache() {
559
+ const entries = new Map();
560
+ return {
561
+ get: (repoRoot) => entries.get(repoRoot),
562
+ set: (repoRoot, entry) => {
563
+ entries.set(repoRoot, entry);
564
+ },
565
+ };
566
+ }
567
+ const defaultWorkspaceReadinessCache = createWorkspaceReadinessCache();
568
+ const READY_STATE_TTL_MS = 60_000;
569
+ const NON_READY_STATE_TTL_MS = 15_000;
570
+ function isCacheFresh(entry, now) {
571
+ const ttl = entry.state === "ready" ? READY_STATE_TTL_MS : NON_READY_STATE_TTL_MS;
572
+ return now - entry.probedAt < ttl;
573
+ }
574
+ /** Readiness placeholder for a never-probed workspace (AC-006): the list is
575
+ * still returned immediately with a pending marker, refreshed by the
576
+ * background probe. */
577
+ function pendingReadinessSummary(inspectReady) {
578
+ return {
579
+ state: "pending",
580
+ summary: `pi=pending; inspect=${inspectReady ? "ready" : "unavailable"}`,
581
+ };
582
+ }
583
+ /**
584
+ * Process-level Workspace Registry HTTP surface (2026-08-22 multi-workspace
585
+ * foundation):
586
+ * GET /api/operator/v1/workspaces — visible list
587
+ * POST /api/operator/v1/workspaces/pick — host native folder chooser
588
+ * POST /api/operator/v1/workspaces — body { "name": "<label>",
589
+ * "directoryPath": "<dir>" }
590
+ * DELETE /api/operator/v1/workspaces/:workspaceId — soft remove (restore on re-add)
591
+ *
592
+ * POST/DELETE are mutations and pass the Console mutation gate. GET carries no
593
+ * workspace selection parameter.
594
+ *
595
+ * AC-006 (2026-08-23): GET is NON-BLOCKING — registry + process-level
596
+ * readiness cache answer immediately (first sight may be a pending
597
+ * placeholder); probes refresh the cache in the background for subsequent
598
+ * requests. A hanging probe can never delay the 200 list.
599
+ */
600
+ export async function handleWorkspaceRoutes(req, res, registry, deps, url, pathname, method, readinessDeps) {
601
+ if (method === "GET" && pathname === "/api/operator/v1/workspaces") {
602
+ const records = await registry.listVisible();
603
+ const inspectReady = readinessDeps.inspectReady;
604
+ const readinessCache = readinessDeps.readinessCache ?? defaultWorkspaceReadinessCache;
605
+ const workspaces = records.map((record) => {
606
+ const cached = readinessCache.get(record.repoRoot);
607
+ return {
608
+ workspaceId: record.workspaceId,
609
+ name: record.name,
610
+ displayPath: record.displayPath,
611
+ repoFingerprint: record.repoFingerprint,
612
+ createdAt: record.createdAt,
613
+ lastAccessedAt: record.lastAccessedAt,
614
+ readiness: cached
615
+ ? {
616
+ state: cached.state,
617
+ summary: `pi=${cached.state}; inspect=${inspectReady ? "ready" : "unavailable"}`,
618
+ }
619
+ : pendingReadinessSummary(inspectReady),
620
+ };
621
+ });
622
+ // Respond FIRST, then refresh readiness in the background (AC-006).
623
+ sendJson(res, 200, {
624
+ ok: true,
625
+ schemaVersion: 1,
626
+ workspaces,
627
+ });
628
+ void Promise.all(records.map(async (record) => {
629
+ const cached = readinessCache.get(record.repoRoot);
630
+ if (cached && isCacheFresh(cached, Date.now()))
631
+ return;
632
+ let state;
633
+ try {
634
+ const report = await probePiReadiness({
635
+ override: deps.piReadinessProbe,
636
+ repoRoot: record.repoRoot,
637
+ });
638
+ state = report.state;
639
+ }
640
+ catch {
641
+ state = "setup-required";
642
+ }
643
+ readinessCache.set(record.repoRoot, {
644
+ state,
645
+ probedAt: Date.now(),
646
+ });
647
+ })).catch(() => undefined);
648
+ return;
649
+ }
650
+ if (method === "POST" && pathname === "/api/operator/v1/workspaces/pick") {
651
+ const failure = evaluateMutationGate(req, {
652
+ cookieName: deps.bootToken.cookieName,
653
+ bootToken: deps.bootToken.value,
654
+ confirmationToken: deps.bootToken.confirmationToken,
655
+ consoleOrigin: deps.getConsoleOrigin(),
656
+ allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
657
+ });
658
+ if (failure) {
659
+ sendJson(res, failure.status, {
660
+ ok: false,
661
+ error: { code: failure.code, message: failure.message },
662
+ });
663
+ return;
664
+ }
665
+ if (deps.allowNonLoopbackAccess) {
666
+ sendJson(res, 409, {
667
+ ok: false,
668
+ error: {
669
+ code: "NATIVE_PICKER_UNAVAILABLE",
670
+ message: "原生目录选择仅在本机回环地址启动的 Console 中可用。",
671
+ },
672
+ });
673
+ return;
674
+ }
675
+ const abortController = new AbortController();
676
+ const abort = () => abortController.abort();
677
+ req.once("aborted", abort);
678
+ res.once("close", abort);
679
+ try {
680
+ const directoryPath = await (deps.pickDirectory ?? pickNativeWorkspaceDirectory)(abortController.signal);
681
+ if (!res.writableEnded && !abortController.signal.aborted) {
682
+ sendJson(res, 200, { ok: true, directoryPath });
683
+ }
684
+ }
685
+ catch (error) {
686
+ if (!res.writableEnded && !abortController.signal.aborted) {
687
+ sendJson(res, 500, {
688
+ ok: false,
689
+ error: {
690
+ code: "DIRECTORY_PICKER_FAILED",
691
+ message: error instanceof Error ? error.message : String(error),
692
+ },
693
+ });
694
+ }
695
+ }
696
+ finally {
697
+ req.off("aborted", abort);
698
+ res.off("close", abort);
699
+ }
700
+ return;
701
+ }
702
+ if (method === "POST" && pathname === "/api/operator/v1/workspaces") {
703
+ const failure = evaluateMutationGate(req, {
704
+ cookieName: deps.bootToken.cookieName,
705
+ bootToken: deps.bootToken.value,
706
+ confirmationToken: deps.bootToken.confirmationToken,
707
+ consoleOrigin: deps.getConsoleOrigin(),
708
+ allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
709
+ });
710
+ if (failure) {
711
+ sendJson(res, failure.status, {
712
+ ok: false,
713
+ error: { code: failure.code, message: failure.message },
714
+ });
715
+ return;
716
+ }
717
+ let body;
718
+ try {
719
+ body = (await readJsonBody(req));
720
+ }
721
+ catch (error) {
722
+ sendJson(res, 400, {
723
+ ok: false,
724
+ error: {
725
+ code: "INVALID_INPUT",
726
+ message: error instanceof Error ? error.message : String(error),
727
+ },
728
+ });
729
+ return;
730
+ }
731
+ const directoryPath = typeof body.directoryPath === "string"
732
+ ? body.directoryPath
733
+ : typeof body.path === "string"
734
+ ? body.path
735
+ : "";
736
+ if (!directoryPath.trim()) {
737
+ sendJson(res, 400, {
738
+ ok: false,
739
+ error: {
740
+ code: "INVALID_INPUT",
741
+ message: "directoryPath (string) is required",
742
+ },
743
+ });
744
+ return;
745
+ }
746
+ if (body.name !== undefined &&
747
+ (typeof body.name !== "string" || !body.name.trim())) {
748
+ sendJson(res, 400, {
749
+ ok: false,
750
+ error: {
751
+ code: "INVALID_INPUT",
752
+ message: "name must be a non-empty string when provided",
753
+ },
754
+ });
755
+ return;
756
+ }
757
+ const name = typeof body.name === "string" ? body.name : undefined;
758
+ const result = await registry.add(directoryPath, name ? { name } : {});
759
+ if (!result.ok) {
760
+ const status = result.code === "DUPLICATE"
761
+ ? 409
762
+ : result.code === "INVALID_PATH"
763
+ ? 400
764
+ : 400;
765
+ sendJson(res, status, {
766
+ ok: false,
767
+ error: {
768
+ code: result.code,
769
+ message: result.message,
770
+ },
771
+ });
772
+ return;
773
+ }
774
+ const record = result.record;
775
+ sendJson(res, 201, {
776
+ ok: true,
777
+ workspace: {
778
+ workspaceId: record.workspaceId,
779
+ name: record.name,
780
+ displayPath: record.displayPath,
781
+ repoFingerprint: record.repoFingerprint,
782
+ createdAt: record.createdAt,
783
+ lastAccessedAt: record.lastAccessedAt,
784
+ readiness: {
785
+ state: "setup-required",
786
+ summary: `pi=unknown; inspect=${readinessDeps.inspectReady ? "ready" : "unavailable"}`,
787
+ },
788
+ },
789
+ });
790
+ return;
791
+ }
792
+ const removeMatch = pathname.match(/^\/api\/operator\/v1\/workspaces\/([^/]+)$/);
793
+ if (method === "DELETE" && removeMatch) {
794
+ const failure = evaluateMutationGate(req, {
795
+ cookieName: deps.bootToken.cookieName,
796
+ bootToken: deps.bootToken.value,
797
+ confirmationToken: deps.bootToken.confirmationToken,
798
+ consoleOrigin: deps.getConsoleOrigin(),
799
+ allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
800
+ });
801
+ if (failure) {
802
+ sendJson(res, failure.status, {
803
+ ok: false,
804
+ error: { code: failure.code, message: failure.message },
805
+ });
806
+ return;
807
+ }
808
+ const workspaceId = decodeURIComponent(removeMatch[1]);
809
+ const removed = await registry.remove(workspaceId);
810
+ if (!removed) {
811
+ sendJson(res, 404, {
812
+ ok: false,
813
+ error: {
814
+ code: "WORKSPACE_NOT_FOUND",
815
+ message: `unknown workspace: ${workspaceId}`,
816
+ },
817
+ });
818
+ return;
819
+ }
820
+ sendJson(res, 200, { ok: true, workspaceId, removed: true });
821
+ return;
822
+ }
823
+ sendJson(res, 404, {
824
+ ok: false,
825
+ error: { code: "NOT_FOUND", message: `unknown workspace route: ${pathname}` },
826
+ });
827
+ }
@@ -119,12 +119,12 @@ function safeEqualToken(a, b) {
119
119
  function loopbackOriginMatches(origin, consoleOrigin) {
120
120
  if (origin === consoleOrigin)
121
121
  return true;
122
- const parseLoopback = (value) => {
122
+ const parseLoopbackPort = (value) => {
123
123
  const match = /^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.exec(value);
124
- return match ? `${match[1].toLowerCase()}${match[2] ?? ""}` : null;
124
+ return match ? (match[2] ?? ":80") : null;
125
125
  };
126
- const originKey = parseLoopback(origin);
127
- const consoleKey = parseLoopback(consoleOrigin);
126
+ const originKey = parseLoopbackPort(origin);
127
+ const consoleKey = parseLoopbackPort(consoleOrigin);
128
128
  return originKey !== null && consoleKey !== null && originKey === consoleKey;
129
129
  }
130
130
  export function evaluateMutationGate(req, ctx) {