@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
@@ -4,7 +4,7 @@ import { describeMutationGateFailure, isMutationGateFailureCode, } from "../oper
4
4
  /**
5
5
  * Multi-workspace registry state (2026-08-23, AC-001/AC-003).
6
6
  *
7
- * Consumes the process-level GET/POST/DELETE /api/operator/v1/workspaces
7
+ * Consumes the process-level GET/POST/PATCH/DELETE /api/operator/v1/workspaces
8
8
  * surface. POST/DELETE pass the Console mutation-gate confirmation token.
9
9
  * The selected workspaceId is remembered in localStorage and validated
10
10
  * against the visible list. A stale local preference falls back to the first
@@ -64,6 +64,11 @@ export function createWorkspacesController(params = {}) {
64
64
  const storage = params.storage;
65
65
  const token = params.confirmationTokenFn ?? confirmationToken;
66
66
  const listeners = new Set();
67
+ let appliedRevision = 0;
68
+ let refreshQueued = false;
69
+ let refreshPromise = null;
70
+ let eventStream;
71
+ let disposed = false;
67
72
  let state = {
68
73
  workspaces: [],
69
74
  loading: true,
@@ -79,44 +84,90 @@ export function createWorkspacesController(params = {}) {
79
84
  state = { ...state, ...patch };
80
85
  emit();
81
86
  };
82
- const refresh = async () => {
87
+ const refresh = () => {
88
+ refreshQueued = true;
89
+ if (refreshPromise)
90
+ return refreshPromise;
91
+ refreshPromise = (async () => {
92
+ while (refreshQueued && !disposed) {
93
+ refreshQueued = false;
94
+ try {
95
+ const res = await fetchImpl(`${origin}/api/operator/v1/workspaces`, {
96
+ credentials: "include",
97
+ });
98
+ if (!res.ok)
99
+ continue;
100
+ const body = (await res.json());
101
+ const revision = Number.isSafeInteger(body.revision)
102
+ ? Math.max(0, body.revision ?? 0)
103
+ : undefined;
104
+ // Legacy/injected responses without revision remain usable. A
105
+ // revisioned response can never roll the projection backwards.
106
+ if (revision !== undefined && revision < appliedRevision)
107
+ continue;
108
+ if (revision !== undefined)
109
+ appliedRevision = revision;
110
+ const list = body.workspaces ?? [];
111
+ const pick = pickDefaultWorkspaceId(list, state.workspaceId);
112
+ if (pick.status === "unknown-workspace") {
113
+ setState({
114
+ workspaces: list,
115
+ unknownWorkspaceId: pick.workspaceId,
116
+ });
117
+ continue;
118
+ }
119
+ const nextId = pick.workspaceId;
120
+ const previousId = state.workspaceId;
121
+ setState({
122
+ workspaces: list,
123
+ unknownWorkspaceId: null,
124
+ ...(nextId !== previousId ? { workspaceId: nextId } : {}),
125
+ });
126
+ if (nextId !== previousId)
127
+ rememberWorkspaceId(nextId, storage);
128
+ }
129
+ catch {
130
+ /* Registry unreachable: keep last known list. */
131
+ }
132
+ finally {
133
+ if (state.loading)
134
+ setState({ loading: false });
135
+ }
136
+ }
137
+ })().finally(() => {
138
+ refreshPromise = null;
139
+ // An event can arrive between the loop condition and finally.
140
+ if (refreshQueued && !disposed)
141
+ void refresh();
142
+ });
143
+ return refreshPromise;
144
+ };
145
+ const consumeRegistryEvent = (event) => {
83
146
  try {
84
- const res = await fetchImpl(`${origin}/api/operator/v1/workspaces`, {
85
- credentials: "include",
86
- });
87
- if (!res.ok)
88
- return;
89
- const body = (await res.json());
90
- const list = body.workspaces ?? [];
91
- // A stale local preference falls back to the first server-visible
92
- // Workspace. Explicit route ids are validated separately and remain
93
- // fail-closed.
94
- const pick = pickDefaultWorkspaceId(list, state.workspaceId);
95
- if (pick.status === "unknown-workspace") {
96
- setState({
97
- workspaces: list,
98
- unknownWorkspaceId: pick.workspaceId,
99
- });
100
- return;
147
+ const revision = Number(JSON.parse(event.data).revision);
148
+ if (Number.isSafeInteger(revision) && revision > appliedRevision) {
149
+ void refresh();
101
150
  }
102
- const nextId = pick.workspaceId;
103
- const previousId = state.workspaceId;
104
- setState({
105
- workspaces: list,
106
- unknownWorkspaceId: null,
107
- ...(nextId !== previousId ? { workspaceId: nextId } : {}),
108
- });
109
- if (nextId !== previousId)
110
- rememberWorkspaceId(nextId, storage);
111
151
  }
112
152
  catch {
113
- /* Registry unreachable: keep last known list. */
114
- }
115
- finally {
116
- if (state.loading)
117
- setState({ loading: false });
153
+ /* Malformed frames are ignored; reconnect baseline remains authoritative. */
118
154
  }
119
155
  };
156
+ const startEventStream = () => {
157
+ if (eventStream || disposed)
158
+ return;
159
+ const factory = params.eventSourceFactory ??
160
+ (typeof EventSource !== "undefined"
161
+ ? (url) => new EventSource(url, {
162
+ withCredentials: true,
163
+ })
164
+ : undefined);
165
+ if (!factory)
166
+ return;
167
+ eventStream = factory(`${origin}/api/operator/v1/workspaces/events`);
168
+ eventStream.addEventListener("workspace_registry_ready", consumeRegistryEvent);
169
+ eventStream.addEventListener("workspace_registry_changed", consumeRegistryEvent);
170
+ };
120
171
  const addWorkspace = async (directoryPath, name) => {
121
172
  setState({ error: null });
122
173
  try {
@@ -214,12 +265,66 @@ export function createWorkspacesController(params = {}) {
214
265
  return { ok: false, error: display };
215
266
  }
216
267
  };
268
+ const reorderWorkspace = async (workspaceId, beforeWorkspaceId) => {
269
+ const previous = state.workspaces;
270
+ const source = previous.find((workspace) => workspace.workspaceId === workspaceId);
271
+ const anchor = beforeWorkspaceId
272
+ ? previous.find((workspace) => workspace.workspaceId === beforeWorkspaceId)
273
+ : undefined;
274
+ if (!source || (beforeWorkspaceId && !anchor)) {
275
+ return { ok: false, error: "工作区顺序已变化,请稍后重试。" };
276
+ }
277
+ const optimistic = previous.filter((workspace) => workspace.workspaceId !== workspaceId);
278
+ const insertAt = beforeWorkspaceId
279
+ ? optimistic.findIndex((workspace) => workspace.workspaceId === beforeWorkspaceId)
280
+ : optimistic.length;
281
+ optimistic.splice(insertAt, 0, source);
282
+ if (optimistic.every((workspace, index) => workspace.workspaceId === previous[index]?.workspaceId)) {
283
+ return { ok: true };
284
+ }
285
+ setState({ workspaces: optimistic, error: null });
286
+ try {
287
+ const res = await fetchImpl(`${origin}/api/operator/v1/workspaces/${encodeURIComponent(workspaceId)}`, {
288
+ method: "PATCH",
289
+ credentials: "include",
290
+ headers: {
291
+ "Content-Type": "application/json",
292
+ "x-loop-console-confirmation": token(),
293
+ },
294
+ body: JSON.stringify({ beforeWorkspaceId }),
295
+ });
296
+ const body = (await res.json().catch(() => ({})));
297
+ if (!res.ok) {
298
+ const message = isMutationGateFailureCode(body.error?.code)
299
+ ? describeMutationGateFailure(body.error?.code ?? "")
300
+ : (body.error?.message ?? `调整 Workspace 顺序失败(HTTP ${res.status})`);
301
+ setState({ workspaces: previous, error: message });
302
+ void refresh();
303
+ return { ok: false, error: message };
304
+ }
305
+ return { ok: true };
306
+ }
307
+ catch (error) {
308
+ const message = `调整 Workspace 顺序失败:${error instanceof Error ? error.message : String(error)}`;
309
+ setState({ workspaces: previous, error: message });
310
+ void refresh();
311
+ return { ok: false, error: message };
312
+ }
313
+ };
217
314
  const selectWorkspace = (next) => {
218
315
  if (next === state.workspaceId && !state.unknownWorkspaceId)
219
316
  return;
220
317
  setState({ workspaceId: next, unknownWorkspaceId: null });
221
318
  rememberWorkspaceId(next, storage);
222
319
  };
320
+ const dispose = () => {
321
+ if (disposed)
322
+ return;
323
+ disposed = true;
324
+ eventStream?.close();
325
+ eventStream = undefined;
326
+ listeners.clear();
327
+ };
223
328
  void refresh();
224
329
  return {
225
330
  get workspaces() {
@@ -241,10 +346,19 @@ export function createWorkspacesController(params = {}) {
241
346
  pickWorkspaceDirectory,
242
347
  addWorkspace,
243
348
  removeWorkspace,
349
+ reorderWorkspace,
244
350
  selectWorkspace,
351
+ dispose,
245
352
  subscribe(listener) {
246
353
  listeners.add(listener);
247
- return () => listeners.delete(listener);
354
+ startEventStream();
355
+ return () => {
356
+ listeners.delete(listener);
357
+ if (listeners.size === 0) {
358
+ eventStream?.close();
359
+ eventStream = undefined;
360
+ }
361
+ };
248
362
  },
249
363
  };
250
364
  }
@@ -294,6 +408,8 @@ export function useWorkspaces(params) {
294
408
  pickWorkspaceDirectory: controller.pickWorkspaceDirectory,
295
409
  addWorkspace: controller.addWorkspace,
296
410
  removeWorkspace: controller.removeWorkspace,
411
+ reorderWorkspace: controller.reorderWorkspace,
297
412
  selectWorkspace: controller.selectWorkspace,
413
+ dispose: controller.dispose,
298
414
  }), [state, controller]);
299
415
  }
@@ -12,6 +12,7 @@ export const OPERATE_TAB_IDS = [
12
12
  "night",
13
13
  "models",
14
14
  "settings",
15
+ "pi-plugins",
15
16
  ];
16
17
  export const LAST_OPERATE_TAB_STORAGE_KEY = "loop-console.operator-shell.last-operate-tab.v1";
17
18
  export const LAST_INSPECT_HASH_STORAGE_KEY = "loop-console.operator-shell.last-inspect-hash.v1";
@@ -44,7 +45,8 @@ function isOperateTab(value) {
44
45
  value === "recovery" ||
45
46
  value === "night" ||
46
47
  value === "models" ||
47
- value === "settings");
48
+ value === "settings" ||
49
+ value === "pi-plugins");
48
50
  }
49
51
  function readParam(query, key) {
50
52
  const value = query.get(key)?.trim();
@@ -21,7 +21,7 @@ import { randomBytes } from "node:crypto";
21
21
  import { existsSync } from "node:fs";
22
22
  import path from "node:path";
23
23
  import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
24
- import { openConsoleAppData } from "./app-data.js";
24
+ import { openConsoleAppData, } from "../../infrastructure/console/app-data.js";
25
25
  import { DagConfirmationStore } from "./dag-confirmation.js";
26
26
  import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
27
27
  import { DraftStore } from "./draft-store.js";
@@ -33,8 +33,14 @@ import { createChatTurnExecutionRegistry } from "./chat/turn-execution-registry.
33
33
  import { ConsolePiRuntime } from "./chat/pi-runtime.js";
34
34
  import { UserQuestionRegistry } from "./chat/user-questions.js";
35
35
  import { TodoListRegistry } from "./chat/todos.js";
36
+ import { GoalRegistry } from "./chat/goals.js";
37
+ import { GoalRoundDriver } from "./chat/goal-round-driver.js";
38
+ import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
36
39
  import { createOperationEventStore, } from "./operation-sse.js";
37
- import { OperationStore } from "./operation-store.js";
40
+ import { OperationStore } from "../../infrastructure/console/operation-store.js";
41
+ import { reconcileContextExports } from "../../infrastructure/console/context-export-store.js";
42
+ import { reconcileArtifactRevisions } from "../../infrastructure/console/artifact-revision-store.js";
43
+ import { recordContextTransferEvent } from "./context-transfer-diagnostics.js";
38
44
  import { probePiReadiness, } from "./pi-readiness.js";
39
45
  import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
40
46
  import { NightAuxTicker } from "./night-aux-ticker.js";
@@ -77,6 +83,29 @@ export async function createWorkspaceContext(record, deps) {
77
83
  const assessments = new AssessmentStore(appData);
78
84
  if (!deps.skipReconcile) {
79
85
  await operations.reconcileOnBoot();
86
+ const [expiredExports, expiredRevisions] = await Promise.all([
87
+ reconcileContextExports({ appData }),
88
+ reconcileArtifactRevisions({ appData }),
89
+ ]);
90
+ // Boot-time lifecycle evidence for what reconcile reclaimed. Only
91
+ // 32-hex ids are real export/revision identities; uploads/ and lock/
92
+ // entries are blob housekeeping and get no event.
93
+ await Promise.all([
94
+ ...expiredExports.removed
95
+ .filter((exportId) => /^[a-f0-9]{32}$/.test(exportId))
96
+ .map((exportId) => recordContextTransferEvent({
97
+ appData,
98
+ event: "context_export.expired",
99
+ fields: { exportId },
100
+ }).catch(() => undefined)),
101
+ ...expiredRevisions
102
+ .filter((revisionId) => /^[a-f0-9]{32}$/.test(revisionId))
103
+ .map((revisionId) => recordContextTransferEvent({
104
+ appData,
105
+ event: "artifact_revision.expired",
106
+ fields: { revisionId },
107
+ }).catch(() => undefined)),
108
+ ]);
80
109
  }
81
110
  const getReadiness = () => readinessCache;
82
111
  const refreshReadiness = async () => {
@@ -107,7 +136,16 @@ export async function createWorkspaceContext(record, deps) {
107
136
  fetchImpl: deps.fetchImpl,
108
137
  getConsoleOrigin: deps.getConsoleOrigin,
109
138
  };
110
- const chatStore = new ChatSessionStore(appData);
139
+ const chatStore = new ChatSessionStore(appData, {
140
+ onRecordChange: (sessionId, session) => {
141
+ if (session) {
142
+ deps.chatSessionCatalog?.upsert(session, record.workspaceId);
143
+ }
144
+ else {
145
+ deps.chatSessionCatalog?.remove(sessionId);
146
+ }
147
+ },
148
+ });
111
149
  const chatEventEpoch = randomBytes(12).toString("hex");
112
150
  const chatEvents = createChatEventStore({
113
151
  appData,
@@ -116,6 +154,7 @@ export async function createWorkspaceContext(record, deps) {
116
154
  const chatTurnExecutions = createChatTurnExecutionRegistry();
117
155
  const questionRegistry = new UserQuestionRegistry(path.join(appData.chats, "user-questions"));
118
156
  const todoRegistry = new TodoListRegistry();
157
+ const goalRegistry = new GoalRegistry(chatEvents);
119
158
  questionRegistry.subscribe((change) => {
120
159
  const question = change.question;
121
160
  chatEvents.append(question.sessionId, question.turnId ?? `${question.sessionId}:question`, {
@@ -144,21 +183,27 @@ export async function createWorkspaceContext(record, deps) {
144
183
  actionContext,
145
184
  questionRegistry,
146
185
  todoRegistry,
186
+ goalRegistry,
147
187
  });
148
- const chatRouteDeps = {
188
+ let chatRouteDeps;
189
+ const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
190
+ chatRouteDeps = {
149
191
  runtime: chatRuntime,
150
192
  store: chatStore,
151
193
  events: chatEvents,
152
194
  turnExecutions: chatTurnExecutions,
153
195
  questionRegistry,
154
196
  todoRegistry,
197
+ goalRegistry,
198
+ goalRoundDriver,
155
199
  operationLinker: chatOperationLinker,
156
200
  appData,
157
201
  bootToken: deps.bootToken,
158
202
  getConsoleOrigin: deps.getConsoleOrigin,
159
203
  allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
204
+ attachSession: (sessionId) => registry.attachSession(record.workspaceId, sessionId),
205
+ detachSession: (sessionId) => registry.detachSession(record.workspaceId, sessionId),
160
206
  };
161
- const { recoverDeferredTurns } = await import("./chat/routes.js");
162
207
  const nightAuxTicker = new NightAuxTicker({ repoRoot, client });
163
208
  const observeRouteContext = createObserveRouteContext(repoRoot, {
164
209
  staticDir: deps.inspectStaticDir,
@@ -191,6 +236,12 @@ export async function createWorkspaceContext(record, deps) {
191
236
  if (disposed)
192
237
  return;
193
238
  disposed = true;
239
+ try {
240
+ goalRoundDriver.dispose();
241
+ }
242
+ catch {
243
+ // best-effort
244
+ }
194
245
  try {
195
246
  chatOperationLinker.dispose();
196
247
  }