@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0

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 (192) hide show
  1. package/AGENTS.md +2 -6
  2. package/CHANGELOG.md +71 -144
  3. package/README.md +1 -1
  4. package/bin/loop-agent.js +1 -37
  5. package/dist/application/task-lifecycle/advance.js +0 -1
  6. package/dist/application/task-lifecycle/observe.js +0 -15
  7. package/dist/application/task-lifecycle/plan-transitions.js +0 -15
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +3 -8
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/run-dag-progress.js +0 -14
  13. package/dist/commands/task-advance.js +3 -33
  14. package/dist/executors/dag-pi-executor.js +30 -47
  15. package/dist/executors/pi-sdk-executor.js +0 -34
  16. package/dist/executors/shell-executor.js +95 -6
  17. package/dist/shared/operator/capabilities.js +13 -200
  18. package/dist/shared/package-metadata.js +0 -42
  19. package/dist/task/contract/project.js +0 -4
  20. package/dist/task/source-prepare/completeness.js +0 -53
  21. package/dist/task/source-prepare/parse-intent.js +12 -82
  22. package/dist/task/source-prepare/prepare.js +5 -102
  23. package/dist/task/source-prepare/semantic-intake.js +23 -144
  24. package/dist/worker/console/chat/artifact-card.js +1 -8
  25. package/dist/worker/console/chat/chat-event-store.js +0 -152
  26. package/dist/worker/console/chat/human-gate-card.js +1 -9
  27. package/dist/worker/console/chat/operation-card.js +2 -71
  28. package/dist/worker/console/chat/pi-runtime.js +62 -111
  29. package/dist/worker/console/chat/routes.js +10 -43
  30. package/dist/worker/console/chat/session-store.js +1 -9
  31. package/dist/worker/console/chat/shortcuts.js +7 -9
  32. package/dist/worker/console/chat/turn-process.js +23 -204
  33. package/dist/worker/console/chat/workspace-landing.js +1 -2
  34. package/dist/worker/console/operation-runner.js +6 -155
  35. package/dist/worker/console/operator-actions.js +14 -391
  36. package/dist/worker/console/operator-user-error.js +0 -4
  37. package/dist/worker/console/recovery-cta.js +3 -50
  38. package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
  39. package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +9 -13
  42. package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
  45. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
  46. package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
  47. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  48. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
  49. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
  50. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
  52. package/dist/worker/loop-agent/loop-agent-client.js +3 -17
  53. package/dist/worker/observability/read-model.js +0 -20
  54. package/dist/worker/observe/spec-evidence.js +8 -3
  55. package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
  56. package/dist/worker/observe/static/operator-chrome.js +3 -10
  57. package/dist/worker/observe/static/views/dag-inspector.js +71 -6
  58. package/dist/worker/observe/static/views/dag.js +0 -12
  59. package/dist/worker/preflight.js +1 -2
  60. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
  61. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  62. package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
  63. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  64. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  65. package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
  66. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  67. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  68. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
  69. package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
  70. package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
  71. package/dist/workflows/dag/frontend-repair.js +18 -219
  72. package/dist/workflows/dag/frontend-verification-trace.js +32 -47
  73. package/dist/workflows/dag/init-hybrid.js +82 -165
  74. package/dist/workflows/dag/node-execution.js +0 -89
  75. package/dist/workflows/dag/recovery-recommendation.js +0 -58
  76. package/dist/workflows/dag/runner.js +15 -333
  77. package/dist/workflows/dag/scheduler.js +3 -257
  78. package/dist/workflows/dag/types.js +9 -130
  79. package/docs/README.md +3 -3
  80. package/docs/architecture/evolution.md +67 -102
  81. package/docs/templates/backend-test-dag.json +50 -32
  82. package/docs/templates/init-managed-agents.md +2 -5
  83. package/harness.json +2 -2
  84. package/package.json +3 -9
  85. package/skills/loop-agent/SKILL.md +0 -1
  86. package/skills/loop-agent/references/command-reference.md +0 -4
  87. package/dist/build-stamp.json +0 -6
  88. package/dist/commands/dag-request-interrupt.js +0 -20
  89. package/dist/worker/console/chat/assistant-content.js +0 -121
  90. package/dist/worker/console/chat/semantic-activity.js +0 -477
  91. package/dist/worker/console/operation-run-facts.js +0 -190
  92. package/dist/worker/console/operation-wait.js +0 -355
  93. package/dist/worker/console/recovery-error-copy.js +0 -198
  94. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
  95. package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
  96. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
  97. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
  98. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
  99. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  100. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
  101. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
  102. package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
  103. package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
  104. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
  105. package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
  106. package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
  107. package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
  108. package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
  109. package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
  110. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  111. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  112. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
  113. package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
  114. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
  115. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
  116. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
  117. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
  118. package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
  119. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
  120. package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
  121. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
  122. package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
  123. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
  124. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
  125. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
  126. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
  127. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
  128. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
  129. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  130. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  131. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
  132. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
  133. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
  134. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
  135. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
  136. package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
  137. package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
  138. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
  139. package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
  140. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
  141. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
  142. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
  143. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
  144. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
  145. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
  146. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
  147. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
  148. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
  149. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
  150. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
  151. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  152. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
  153. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
  154. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
  155. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
  156. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
  157. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
  158. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  159. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  160. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  161. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  162. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  163. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  164. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  165. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  166. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  167. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  168. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  169. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  170. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  171. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  172. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  173. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  174. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  175. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  176. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  177. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  178. package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
  179. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  180. package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
  181. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
  182. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
  183. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  184. package/dist/worker/observability/interrupt-eligibility.js +0 -264
  185. package/dist/workflows/dag/contract-output-registry.js +0 -14
  186. package/dist/workflows/dag/contract-validator-registrations.js +0 -8
  187. package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
  188. package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
  189. package/dist/workflows/dag/frontend-recovery-run.js +0 -539
  190. package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
  191. package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
  192. package/dist/workflows/dag/interrupt-request.js +0 -559
@@ -2,7 +2,6 @@ import { artifactFromEventPayload, mergeArtifactCardState, } from "../../chat/ar
2
2
  import { humanGateCardFromEventPayload, mergeHumanGateCardState, } from "../../chat/human-gate-card.js";
3
3
  import { mergeOperationCardState, operationFromEventPayload, } from "../../chat/operation-card.js";
4
4
  import { requestId } from "./format.js";
5
- import { parseAssistantContent } from "../../chat/assistant-content.js";
6
5
  /** Shared handler for a single parsed Chat SSE event (M1 reconnect).
7
6
  *
8
7
  * Used by BOTH the inline prompt POST SSE and the background reconnect
@@ -25,30 +24,25 @@ export function createChatSseEventApplier(deps) {
25
24
  // global error; the snapshot replay frames that follow are still applied
26
25
  // as ordinary blocks (deduped by eventId, replay text skipped).
27
26
  const reason = typeof data.reason === "string" ? data.reason : "cursor_expired";
28
- const reconcileInfo = {
27
+ onReconcile?.({
29
28
  reason,
30
29
  ...(typeof data.minSeq === "number" ? { minSeq: data.minSeq } : {}),
31
30
  ...(typeof data.epoch === "string" ? { epoch: data.epoch } : {}),
32
31
  ...(typeof data.latestTurnId === "string"
33
32
  ? { latestTurnId: data.latestTurnId }
34
33
  : {}),
35
- };
36
- if (typeof data.latestEventId === "string" || data.latestEventId === null) {
37
- reconcileInfo.latestEventId = data.latestEventId;
38
- }
39
- onReconcile?.(reconcileInfo);
34
+ ...(typeof data.latestEventId === "string"
35
+ ? { latestEventId: data.latestEventId }
36
+ : data.latestEventId === null
37
+ ? { latestEventId: null }
38
+ : {}),
39
+ });
40
40
  return;
41
41
  }
42
42
  const payload = data.data && typeof data.data === "object"
43
43
  ? data.data
44
44
  : data;
45
45
  const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
46
- const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
47
- ? data.seq
48
- : undefined;
49
- const eventAt = typeof data.at === "string" && Number.isFinite(Date.parse(data.at))
50
- ? Date.parse(data.at)
51
- : Date.now();
52
46
  if (turnId &&
53
47
  (eventName === "agent_start" || eventName === "message_update") &&
54
48
  refs.settledTurnIdsRef.current.has(turnId)) {
@@ -113,26 +107,14 @@ export function createChatSseEventApplier(deps) {
113
107
  }
114
108
  if (eventName === "human-gate-card") {
115
109
  const card = humanGateCardFromEventPayload(payload);
116
- if (card) {
117
- setHumanGateCards((current) => mergeHumanGateCardState(current, {
118
- ...card,
119
- ...(eventSeq === undefined ? {} : { eventSeq }),
120
- ...(turnId ? { turnId } : {}),
121
- activityRef: `gate:${card.cardId}`,
122
- }));
123
- }
110
+ if (card)
111
+ setHumanGateCards((current) => mergeHumanGateCardState(current, card));
124
112
  return;
125
113
  }
126
114
  if (eventName === "artifact-ref") {
127
115
  const artifact = artifactFromEventPayload(payload);
128
- if (artifact) {
129
- setArtifactCards((current) => mergeArtifactCardState(current, {
130
- ...artifact,
131
- ...(eventSeq === undefined ? {} : { eventSeq }),
132
- ...(turnId ? { turnId } : {}),
133
- activityRef: `artifact:${artifact.artifactId}`,
134
- }));
135
- }
116
+ if (artifact)
117
+ setArtifactCards((current) => mergeArtifactCardState(current, artifact));
136
118
  return;
137
119
  }
138
120
  if (eventName === "agent_start") {
@@ -189,18 +171,7 @@ export function createChatSseEventApplier(deps) {
189
171
  if (eventName === "operation-ref" || eventName === "operation-status") {
190
172
  const operation = operationFromEventPayload(payload);
191
173
  if (operation) {
192
- const toolCallId = typeof payload.toolCallId === "string"
193
- ? payload.toolCallId
194
- : undefined;
195
- setOperationCards((current) => mergeOperationCardState(current, {
196
- ...operation,
197
- ...(eventSeq === undefined
198
- ? {}
199
- : { eventSeq, updatedSeq: eventSeq }),
200
- ...(turnId ? { turnId } : {}),
201
- ...(toolCallId ? { toolCallId } : {}),
202
- activityRef: `operation:${operation.operationId}`,
203
- }));
174
+ setOperationCards((current) => mergeOperationCardState(current, operation));
204
175
  }
205
176
  return;
206
177
  }
@@ -222,7 +193,6 @@ export function createChatSseEventApplier(deps) {
222
193
  const cacheRead = sample.cacheReadTokens ?? 0;
223
194
  const cacheWrite = sample.cacheWriteTokens ?? 0;
224
195
  const total = sample.totalTokens ?? input + output + cacheRead + cacheWrite;
225
- const cu = sample.contextUsage;
226
196
  return {
227
197
  inputTokens: current.inputTokens + input,
228
198
  outputTokens: current.outputTokens + output,
@@ -232,18 +202,6 @@ export function createChatSseEventApplier(deps) {
232
202
  cost: sample.cost === undefined
233
203
  ? current.cost
234
204
  : (current.cost ?? 0) + sample.cost,
235
- // Context usage is a live snapshot, not an accumulator: replace it.
236
- ...(cu && typeof cu === "object"
237
- ? {
238
- contextUsage: {
239
- tokens: typeof cu.tokens === "number" ? cu.tokens : null,
240
- contextWindow: typeof cu.contextWindow === "number"
241
- ? cu.contextWindow
242
- : 0,
243
- percent: typeof cu.percent === "number" ? cu.percent : null,
244
- },
245
- }
246
- : {}),
247
205
  };
248
206
  });
249
207
  return;
@@ -255,46 +213,24 @@ export function createChatSseEventApplier(deps) {
255
213
  // a duplicate bubble.
256
214
  return;
257
215
  }
258
- const sealedIds = refs.sealedAssistantIdsRef.current;
259
- if (eventName === "message_update" &&
260
- sealedIds.has(resolvedAssistantId)) {
261
- const nextId = requestId("a");
262
- if (turnId)
263
- refs.turnAssistantIdsRef.current.set(turnId, nextId);
264
- resolvedAssistantId = nextId;
265
- setStreamingAssistantId(nextId);
266
- }
267
216
  const text = typeof payload.text === "string" ? payload.text : "";
268
- const content = parseAssistantContent(payload.content);
269
- const assistantIdForEvent = resolvedAssistantId;
270
217
  setMessages((m) => {
271
- const exists = m.some((msg) => msg.id === assistantIdForEvent);
218
+ const exists = m.some((msg) => msg.id === resolvedAssistantId);
272
219
  if (exists) {
273
- return m.map((msg) => {
274
- if (msg.id !== assistantIdForEvent)
275
- return msg;
276
- return {
277
- ...msg,
278
- text,
279
- ...(content ? { content } : {}),
280
- };
281
- });
220
+ return m.map((msg) => msg.id === resolvedAssistantId ? { ...msg, text } : msg);
282
221
  }
283
222
  // Reconnect path: the assistant message may not exist yet
284
223
  // (e.g. only tool_call events arrived). Append a placeholder.
285
224
  return [
286
225
  ...m,
287
226
  {
288
- id: assistantIdForEvent,
227
+ id: resolvedAssistantId,
289
228
  role: "assistant",
290
229
  text,
291
- createdAt: eventAt,
292
- ...(content ? { content } : {}),
230
+ createdAt: Date.now(),
293
231
  },
294
232
  ];
295
233
  });
296
- if (eventName === "message_end")
297
- sealedIds.add(assistantIdForEvent);
298
234
  }
299
235
  else if (eventName === "tool_call") {
300
236
  const id = typeof payload.toolCallId === "string"
@@ -302,20 +238,18 @@ export function createChatSseEventApplier(deps) {
302
238
  : requestId("t");
303
239
  const toolName = typeof payload.toolName === "string" ? payload.toolName : "tool";
304
240
  const argsPreview = typeof payload.argsPreview === "string" ? payload.argsPreview : "{}";
241
+ const now = Date.now();
305
242
  setToolCalls((prev) => {
306
243
  if (prev.some((t) => t.id === id)) {
307
- return prev.map((t) => {
308
- if (t.id !== id)
309
- return t;
310
- return {
244
+ return prev.map((t) => t.id === id
245
+ ? {
311
246
  ...t,
312
247
  args: argsPreview,
313
248
  status: "running",
314
249
  turnId: turnId ?? t.turnId,
315
250
  assistantMessageId: resolvedAssistantId,
316
- ...(eventSeq === undefined ? {} : { eventSeq }),
317
- };
318
- });
251
+ }
252
+ : t);
319
253
  }
320
254
  return [
321
255
  ...prev,
@@ -324,8 +258,7 @@ export function createChatSseEventApplier(deps) {
324
258
  toolName,
325
259
  args: argsPreview,
326
260
  status: "running",
327
- createdAt: eventAt,
328
- ...(eventSeq === undefined ? {} : { eventSeq }),
261
+ createdAt: now,
329
262
  turnId,
330
263
  assistantMessageId: resolvedAssistantId,
331
264
  },
@@ -341,34 +274,21 @@ export function createChatSseEventApplier(deps) {
341
274
  const resultText = typeof payload.resultPreview === "string"
342
275
  ? payload.resultPreview
343
276
  : "{}";
344
- // Plan A (2026-08-16): the server carries the accepted-operation link on
345
- // the frame; persist it on the tool entry so the semantic event resolves
346
- // an operation:<id> ref (mirrors the hydrated path backfill).
347
- const resultOperationId = typeof payload.operationId === "string" && payload.operationId
348
- ? payload.operationId
349
- : undefined;
277
+ const now = Date.now();
350
278
  setToolCalls((prev) => {
351
279
  const exists = prev.find((t) => t.id === id);
352
280
  if (exists) {
353
- return prev.map((t) => {
354
- if (t.id !== id)
355
- return t;
356
- return {
281
+ return prev.map((t) => t.id === id
282
+ ? {
357
283
  ...t,
358
284
  result: resultText,
359
285
  resultText,
360
286
  status: isError ? "error" : "ok",
361
- finishedAt: eventAt,
362
- ...(eventSeq === undefined ? {} : { finishedSeq: eventSeq }),
287
+ finishedAt: now,
363
288
  turnId: turnId ?? t.turnId,
364
289
  assistantMessageId: t.assistantMessageId ?? resolvedAssistantId,
365
- ...(resultOperationId
366
- ? { operationId: resultOperationId }
367
- : t.operationId
368
- ? { operationId: t.operationId }
369
- : {}),
370
- };
371
- });
290
+ }
291
+ : t);
372
292
  }
373
293
  return [
374
294
  ...prev,
@@ -378,14 +298,10 @@ export function createChatSseEventApplier(deps) {
378
298
  result: resultText,
379
299
  resultText,
380
300
  status: isError ? "error" : "ok",
381
- createdAt: eventAt,
382
- finishedAt: eventAt,
383
- ...(eventSeq === undefined
384
- ? {}
385
- : { eventSeq, finishedSeq: eventSeq }),
301
+ createdAt: now,
302
+ finishedAt: now,
386
303
  turnId,
387
304
  assistantMessageId: resolvedAssistantId,
388
- ...(resultOperationId ? { operationId: resultOperationId } : {}),
389
305
  },
390
306
  ];
391
307
  });
@@ -12,7 +12,6 @@ export function useChatRefs() {
12
12
  const settledTurnIdsRef = useRef(new Set());
13
13
  const seenEventIdsRef = useRef(new Set());
14
14
  const turnAssistantIdsRef = useRef(new Map());
15
- const sealedAssistantIdsRef = useRef(new Set());
16
15
  const reconnectAbortRef = useRef(null);
17
16
  const activeTurnIdRef = useRef(null);
18
17
  const submitInFlightRef = useRef(false);
@@ -41,7 +40,6 @@ export function useChatRefs() {
41
40
  settledTurnIdsRef,
42
41
  seenEventIdsRef,
43
42
  turnAssistantIdsRef,
44
- sealedAssistantIdsRef,
45
43
  reconnectAbortRef,
46
44
  activeTurnIdRef,
47
45
  submitInFlightRef,
@@ -68,7 +66,6 @@ export function useChatRefs() {
68
66
  settledTurnIdsRef,
69
67
  seenEventIdsRef,
70
68
  turnAssistantIdsRef,
71
- sealedAssistantIdsRef,
72
69
  reconnectAbortRef,
73
70
  activeTurnIdRef,
74
71
  submitInFlightRef,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Mutual-exclusion state for large spatial overlays in Operator Chat.
3
- * At most one of: sidebar drawer, process, runtime context, tools, activity.
3
+ * At most one of: sidebar drawer, process, runtime context, tools.
4
4
  * Small composer menus (model/thinking) are out of scope.
5
5
  */
6
6
  export function createSpatialOverlayState(active = "none") {
@@ -33,6 +33,5 @@ export function spatialOverlayFlags(state) {
33
33
  processOpen: state.active === "process",
34
34
  runtimeContextOpen: state.active === "runtime-context",
35
35
  toolsOpen: state.active === "tools",
36
- activityOpen: state.active === "activity",
37
36
  };
38
37
  }
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
- import { parseAssistantContent } from "../../chat/assistant-content.js";
3
2
  import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
4
3
  import { hydrateSessionThread } from "../../chat/turn-process.js";
5
4
  import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
@@ -58,7 +57,7 @@ export async function createSessionAndPrimeTitleWatcher(input) {
58
57
  /** Session lifecycle: list / activate / create / delete / rename-archive /
59
58
  * compact, plus browser-refresh recovery from the durable session store. */
60
59
  export function useChatSessions(params) {
61
- const { origin, refs, setSession, thread, composer, browser, setError, setNotice, onCompactionChange, setProcessOpen, setAutoScroll, } = params;
60
+ const { origin, refs, setSession, thread, composer, browser, setError, setProcessOpen, setAutoScroll, } = params;
62
61
  const { applyActivatedSession, resetForNewSession, setMessages } = thread;
63
62
  const { setInput, setPendingImages } = composer;
64
63
  const { setPreviews, setActivePreviewPath } = browser;
@@ -90,7 +89,6 @@ export function useChatSessions(params) {
90
89
  refs.lastEventIdRef.current = null;
91
90
  refs.seenEventIdsRef.current = new Set();
92
91
  refs.turnAssistantIdsRef.current = new Map();
93
- refs.sealedAssistantIdsRef.current = new Set();
94
92
  refs.settledTurnIdsRef.current = new Set();
95
93
  refs.activeTurnIdRef.current = null;
96
94
  resetForNewSession();
@@ -146,6 +144,8 @@ export function useChatSessions(params) {
146
144
  }
147
145
  }, [origin, refs, resetActiveProjection, setSession]);
148
146
  const deleteSession = useCallback(async (sessionId) => {
147
+ if (!window.confirm("确定删除该会话?删除后不可恢复。"))
148
+ return;
149
149
  try {
150
150
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`, {
151
151
  method: "DELETE",
@@ -156,7 +156,7 @@ export function useChatSessions(params) {
156
156
  });
157
157
  if (!res.ok) {
158
158
  setError("删除会话失败");
159
- return false;
159
+ return;
160
160
  }
161
161
  titleWatcherRef.current?.cancel(sessionId);
162
162
  if (refs.sessionRef.current?.sessionId === sessionId) {
@@ -167,11 +167,9 @@ export function useChatSessions(params) {
167
167
  window.localStorage.removeItem(ACTIVE_CHAT_SESSION_STORAGE_KEY);
168
168
  }
169
169
  await refreshSessionList();
170
- return true;
171
170
  }
172
171
  catch (error) {
173
172
  setError(error instanceof Error ? error.message : String(error));
174
- return false;
175
173
  }
176
174
  }, [
177
175
  origin,
@@ -200,7 +198,6 @@ export function useChatSessions(params) {
200
198
  refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
201
199
  refs.seenEventIdsRef.current = new Set();
202
200
  refs.turnAssistantIdsRef.current = new Map();
203
- refs.sealedAssistantIdsRef.current = new Set();
204
201
  refs.settledTurnIdsRef.current = new Set();
205
202
  refs.lastEventIdRef.current = null;
206
203
  setSession(next);
@@ -298,23 +295,18 @@ export function useChatSessions(params) {
298
295
  refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
299
296
  refs.seenEventIdsRef.current = new Set();
300
297
  refs.turnAssistantIdsRef.current = new Map();
301
- refs.sealedAssistantIdsRef.current = new Set();
302
298
  refs.settledTurnIdsRef.current = new Set();
303
299
  refs.lastEventIdRef.current = null;
304
300
  setSession(restored);
305
301
  setInput(body.session.composerDraft ?? "");
306
302
  setMessages((body.session.messages ?? [])
307
303
  .filter((message) => message.role === "user" || message.role === "assistant")
308
- .map((message) => {
309
- const content = parseAssistantContent(message.content);
310
- return {
311
- id: message.id,
312
- role: message.role,
313
- text: message.text,
314
- createdAt: Date.parse(message.at) || Date.now(),
315
- ...(content && content.length > 0 ? { content } : {}),
316
- };
317
- }));
304
+ .map((message) => ({
305
+ id: message.id,
306
+ role: message.role,
307
+ text: message.text,
308
+ createdAt: Date.parse(message.at) || Date.now(),
309
+ })));
318
310
  }
319
311
  catch {
320
312
  // Recovery is best-effort; a new session remains available.
@@ -372,27 +364,15 @@ export function useChatSessions(params) {
372
364
  createSessionAndPrimeTitleWatcherForHook,
373
365
  ]);
374
366
  // Phase-1: fork / in-session branch / mainline switch UI deferred; API routes remain.
375
- /** Single compact helper for toolbar + `/compact <instructions>` (AC-1). */
376
- const compactSession = useCallback(async (instructions) => {
367
+ const compactSession = useCallback(async () => {
377
368
  const current = refs.sessionRef.current;
378
369
  if (!current || refs.streamingRef.current)
379
370
  return;
380
- const trimmed = typeof instructions === "string" ? instructions.trim() : "";
381
- onCompactionChange?.(true);
382
371
  try {
383
- const headers = {
384
- "x-loop-console-confirmation": confirmationToken(),
385
- };
386
- if (trimmed) {
387
- headers["content-type"] = "application/json";
388
- }
389
372
  const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(current.sessionId)}/compact`, {
390
373
  method: "POST",
391
374
  credentials: "include",
392
- headers,
393
- ...(trimmed
394
- ? { body: JSON.stringify({ instructions: trimmed }) }
395
- : {}),
375
+ headers: { "x-loop-console-confirmation": confirmationToken() },
396
376
  });
397
377
  const body = (await response.json().catch(() => ({})));
398
378
  if (!response.ok) {
@@ -400,45 +380,11 @@ export function useChatSessions(params) {
400
380
  return;
401
381
  }
402
382
  setError(null);
403
- // pi-web parity: reload the compacted session, then leave a distinct
404
- // compaction summary card in the timeline plus a transient toast.
405
- const summary = body.snapshot?.summary?.trim();
406
- const tokensBefore = body.snapshot?.tokensBefore;
407
- const summaryText = summary || "历史上下文已精简,关键决策与当前任务已保留。";
408
- await activateSession(current.sessionId);
409
- setMessages((messages) => [
410
- ...messages,
411
- {
412
- id: `compaction-${Date.now()}`,
413
- role: "assistant",
414
- kind: "compaction",
415
- text: summaryText,
416
- createdAt: Date.now(),
417
- ...(tokensBefore
418
- ? { compaction: { tokensBefore } }
419
- : {}),
420
- },
421
- ]);
422
- setNotice({
423
- id: Date.now(),
424
- message: `已压缩上下文:${summaryText.slice(0, 72)}${summaryText.length > 72 ? "…" : ""}`,
425
- });
426
383
  }
427
384
  catch (error) {
428
385
  setError(error instanceof Error ? error.message : "上下文压缩失败");
429
386
  }
430
- finally {
431
- onCompactionChange?.(false);
432
- }
433
- }, [
434
- origin,
435
- refs,
436
- setError,
437
- setNotice,
438
- setMessages,
439
- onCompactionChange,
440
- activateSession,
441
- ]);
387
+ }, [origin, refs, setError]);
442
388
  const updateSession = async (sessionId, patch) => {
443
389
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`, {
444
390
  method: "PATCH",
@@ -1,5 +1,4 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
- import { parseAssistantContent } from "../../chat/assistant-content.js";
3
2
  import { buildTurnProcessGroups } from "../../chat/turn-process.js";
4
3
  import { createChatSseEventApplier, } from "./chat-sse-events.js";
5
4
  import { taskContextUnchanged } from "./format.js";
@@ -219,19 +218,14 @@ export function useChatThread(params) {
219
218
  refs.lastEventIdRef.current = body.lastEventId ?? null;
220
219
  refs.seenEventIdsRef.current = new Set();
221
220
  refs.turnAssistantIdsRef.current = new Map();
222
- refs.sealedAssistantIdsRef.current = new Set();
223
221
  setMessages((body.session.messages ?? [])
224
222
  .filter((message) => message.role === "user" || message.role === "assistant")
225
- .map((message) => {
226
- const content = parseAssistantContent(message.content);
227
- return {
228
- id: message.id,
229
- role: message.role,
230
- text: message.text,
231
- createdAt: Date.parse(message.at) || Date.now(),
232
- ...(content && content.length > 0 ? { content } : {}),
233
- };
234
- }));
223
+ .map((message) => ({
224
+ id: message.id,
225
+ role: message.role,
226
+ text: message.text,
227
+ createdAt: Date.parse(message.at) || Date.now(),
228
+ })));
235
229
  setStreamingAssistantId(null);
236
230
  return true;
237
231
  }
@@ -1,52 +1,27 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
2
2
  import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
3
3
  import { confirmationToken } from "./format.js";
4
- import { clampShortcutIndex } from "./slash-palette-nav.js";
5
4
  import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
6
5
  /** Composer state: input text, @file palette, image attachments, /shortcuts,
7
6
  * IME composition guards, auto-grow and server-side draft sync. */
8
7
  export function useComposer(params) {
9
8
  const { origin, sessionId, refs, setError } = params;
10
- const [input, setInputState] = useState("");
9
+ const [input, setInput] = useState("");
11
10
  const [fileOptions, setFileOptions] = useState([]);
12
11
  const [pendingImages, setPendingImages] = useState([]);
13
12
  const [shortcutOpen, setShortcutOpen] = useState(false);
14
- /** Any composer text that starts with `/` should reveal the palette,
15
- * including draft restore. Escape can still close it without clearing. */
16
- const setInput = useCallback((value) => {
17
- if (typeof value === "function") {
18
- setInputState((prev) => {
19
- const next = value(prev);
20
- setShortcutOpen(next.startsWith("/"));
21
- return next;
22
- });
23
- return;
24
- }
25
- setInputState(value);
26
- setShortcutOpen(value.startsWith("/"));
27
- }, []);
28
13
  const [shortcutIndex, setShortcutIndex] = useState(0);
29
14
  const [dynamicCommands, setDynamicCommands] = useState([]);
30
15
  const [dynamicCommandsLoading, setDynamicCommandsLoading] = useState(false);
31
16
  const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
32
17
  const [commandsEpoch, setCommandsEpoch] = useState(0);
33
18
  const textareaRef = useRef(null);
34
- /** Active session generation for /commands; bumped immediately on switch (AC-2). */
35
- const commandsSessionRef = useRef(sessionId);
36
- const commandsGenerationRef = useRef(0);
19
+ const commandsSessionRef = useRef(undefined);
37
20
  const handleInputChange = useCallback((value) => {
38
21
  setInput(value);
39
22
  setShortcutOpen(value.startsWith("/"));
40
23
  setShortcutIndex(0);
41
24
  }, []);
42
- // Immediate invalidation on A→B even when B palette never opens (AC-2).
43
- useLayoutEffect(() => {
44
- commandsSessionRef.current = sessionId;
45
- commandsGenerationRef.current += 1;
46
- setDynamicCommands([]);
47
- setDynamicCommandsError(null);
48
- setDynamicCommandsLoading(false);
49
- }, [sessionId]);
50
25
  const loadDynamicCommands = useCallback(async () => {
51
26
  if (!sessionId) {
52
27
  setDynamicCommands([]);
@@ -55,15 +30,12 @@ export function useComposer(params) {
55
30
  return;
56
31
  }
57
32
  const target = sessionId;
58
- const generation = commandsGenerationRef.current;
59
33
  commandsSessionRef.current = target;
60
34
  setDynamicCommandsLoading(true);
61
35
  setDynamicCommandsError(null);
62
- const isCurrent = () => commandsSessionRef.current === target &&
63
- commandsGenerationRef.current === generation;
64
36
  try {
65
37
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(target)}/commands`, { credentials: "include" });
66
- if (!isCurrent())
38
+ if (commandsSessionRef.current !== target)
67
39
  return;
68
40
  if (!res.ok) {
69
41
  setDynamicCommandsError("动态命令加载失败");
@@ -89,27 +61,26 @@ export function useComposer(params) {
89
61
  source,
90
62
  });
91
63
  }
92
- if (!isCurrent())
64
+ if (commandsSessionRef.current !== target)
93
65
  return;
94
66
  setDynamicCommands(items);
95
- // Compact server fail-soft / partial-source warning; local Operator/Web still merge.
96
- const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
97
- setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
98
67
  }
99
68
  catch {
100
- if (!isCurrent())
69
+ if (commandsSessionRef.current !== target)
101
70
  return;
102
71
  setDynamicCommandsError("动态命令加载失败");
103
72
  setDynamicCommands([]);
104
73
  }
105
74
  finally {
106
- if (isCurrent()) {
75
+ if (commandsSessionRef.current === target) {
107
76
  setDynamicCommandsLoading(false);
108
77
  }
109
78
  }
110
79
  }, [origin, sessionId]);
111
80
  // Lazy-load dynamic commands when slash palette opens or session changes.
112
81
  useEffect(() => {
82
+ setDynamicCommands([]);
83
+ setDynamicCommandsError(null);
113
84
  if (!sessionId)
114
85
  return;
115
86
  if (!shortcutOpen && commandsEpoch === 0)
@@ -201,14 +172,6 @@ export function useComposer(params) {
201
172
  const filteredShortcuts = useMemo(() => input.startsWith("/")
202
173
  ? filterSlashCommands(mergedCommands, input)
203
174
  : [], [input, mergedCommands]);
204
- // Keep the active option index inside the current candidate range (UI-12
205
- // AC-011): dynamic loads completing/failing empty, retries, session
206
- // switches, late responses and search shrink all funnel through
207
- // filteredShortcuts.length. The clamped index always matches a rendered
208
- // `oc-shortcut-option-${globalIndex}` id (same source of truth).
209
- useEffect(() => {
210
- setShortcutIndex((index) => clampShortcutIndex(index, filteredShortcuts.length));
211
- }, [filteredShortcuts.length]);
212
175
  const onCompositionStart = useCallback(() => {
213
176
  refs.isComposingRef.current = true;
214
177
  }, [refs]);
@@ -17,13 +17,9 @@ export function resolveWorkspaceLayoutMode(widthPx) {
17
17
  return "compact";
18
18
  return "narrow";
19
19
  }
20
- /**
21
- * Tool-call history always uses the original right overlay panel.
22
- * Reflowing it as a comfortable-mode third column moves ChatMain and also
23
- * invalidates Activity Rail's fixed ChatMain anchor.
24
- */
25
- export function processUsesOverlay(_mode) {
26
- return true;
20
+ /** Whether process should render as a right overlay (no main reflow). */
21
+ export function processUsesOverlay(mode) {
22
+ return mode === "compact" || mode === "narrow";
27
23
  }
28
24
  /** Whether the permanent full sidebar is allowed. */
29
25
  export function fullSidebarAllowed(mode) {