@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
@@ -1,17 +1,6 @@
1
1
  /**
2
2
  * Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
3
3
  */
4
- import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
5
- import { semanticActivityForTool, } from "./semantic-activity.js";
6
- function emptyGroup(key) {
7
- return {
8
- key,
9
- assistants: [],
10
- tools: [],
11
- processItems: [],
12
- live: false,
13
- };
14
- }
15
4
  /**
16
5
  * Restore chat bubbles + tool cards from durable session messages.
17
6
  * Tool rows often appear before the final assistant `message_end` message;
@@ -47,13 +36,11 @@ export function hydrateSessionThread(raw) {
47
36
  if (item.role === "assistant") {
48
37
  if (!turnId)
49
38
  turnId = `restored-${item.id}`;
50
- const content = parseAssistantContent(item.content);
51
39
  messages.push({
52
40
  id: item.id,
53
41
  role: "assistant",
54
42
  text: item.text,
55
43
  createdAt,
56
- ...(content && content.length > 0 ? { content } : {}),
57
44
  });
58
45
  flushPendingToAssistant(item.id);
59
46
  continue;
@@ -70,7 +57,6 @@ export function hydrateSessionThread(raw) {
70
57
  createdAt,
71
58
  finishedAt: createdAt,
72
59
  turnId,
73
- ...(item.operationId ? { operationId: item.operationId } : {}),
74
60
  };
75
61
  toolCalls.push(entry);
76
62
  pendingTools.push(entry);
@@ -78,125 +64,6 @@ export function hydrateSessionThread(raw) {
78
64
  }
79
65
  return { messages, toolCalls };
80
66
  }
81
- /** Stable tool order: server eventSeq first, createdAt fallback, id tiebreak. */
82
- function sortToolsStable(tools) {
83
- return [...tools].sort((a, b) => {
84
- if (a.eventSeq !== undefined &&
85
- b.eventSeq !== undefined &&
86
- a.eventSeq !== b.eventSeq) {
87
- return a.eventSeq - b.eventSeq;
88
- }
89
- if (a.createdAt !== b.createdAt)
90
- return a.createdAt - b.createdAt;
91
- return a.id < b.id ? -1 : 1;
92
- });
93
- }
94
- /**
95
- * Flush buffered consecutive tools into process items. Semantic operator
96
- * tools stay as individual activity events; plain tools group into a
97
- * `tool-group` ("并行调用 · N 项") when two or more land back to back.
98
- * Thinking/text/activity boundaries break the buffer upstream.
99
- */
100
- function flushToolBuffer(processItems, buffer) {
101
- if (buffer.length === 0)
102
- return;
103
- const sorted = sortToolsStable(buffer);
104
- let plainGroup = [];
105
- const flushPlain = () => {
106
- if (plainGroup.length === 1) {
107
- processItems.push({ kind: "tool", tool: plainGroup[0] });
108
- }
109
- else if (plainGroup.length > 1) {
110
- processItems.push({ kind: "tool-group", tools: plainGroup });
111
- }
112
- plainGroup = [];
113
- };
114
- for (const tool of sorted) {
115
- const activity = semanticActivityForTool(tool);
116
- if (activity) {
117
- flushPlain();
118
- processItems.push({ kind: "activity", activity });
119
- }
120
- else {
121
- plainGroup.push(tool);
122
- }
123
- }
124
- flushPlain();
125
- }
126
- function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
127
- let buffer = [];
128
- for (const block of blocks) {
129
- if (block.type === "thinking") {
130
- flushToolBuffer(processItems, buffer);
131
- buffer = [];
132
- processItems.push({ kind: "thinking", thinking: block.thinking });
133
- continue;
134
- }
135
- if (block.type === "text") {
136
- flushToolBuffer(processItems, buffer);
137
- buffer = [];
138
- if (block.text.trim()) {
139
- processItems.push({ kind: "text", text: block.text });
140
- }
141
- continue;
142
- }
143
- const tool = group.tools.find((entry) => entry.id === block.toolCallId);
144
- if (tool && !usedToolIds.has(tool.id)) {
145
- buffer.push(tool);
146
- usedToolIds.add(tool.id);
147
- }
148
- }
149
- flushToolBuffer(processItems, buffer);
150
- }
151
- /**
152
- * Insert tools attributed to this assistant stage but without a matching
153
- * content toolCall block (live SSE tool_call frames often arrive before the
154
- * assistant content does). They follow the stage's thinking/text instead of
155
- * sinking to the end of the whole turn.
156
- */
157
- function appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds) {
158
- const unmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) && tool.assistantMessageId === assistant.id);
159
- if (unmatched.length === 0)
160
- return;
161
- for (const tool of unmatched)
162
- usedToolIds.add(tool.id);
163
- flushToolBuffer(processItems, unmatched);
164
- }
165
- function fillProcessDetails(group) {
166
- const usedToolIds = new Set();
167
- const processItems = [];
168
- const prior = group.assistants.slice(0, -1);
169
- const finalAssistant = group.assistants.at(-1);
170
- for (const assistant of prior) {
171
- emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
172
- appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
173
- }
174
- const blocks = finalAssistant?.content ?? [];
175
- const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
176
- emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
177
- if (finalAssistant) {
178
- appendAssistantUnmatchedTools(group, finalAssistant, processItems, usedToolIds);
179
- }
180
- // Bounded fallback for legacy restored rows with no assistant attribution:
181
- // keep them at this turn's end in stable server order instead of randomly
182
- // interleaving them into earlier stages.
183
- const orphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
184
- if (orphans.length > 0)
185
- flushToolBuffer(processItems, orphans);
186
- group.processItems = processItems;
187
- if (!finalAssistant)
188
- return;
189
- if (blocks.length === 0) {
190
- group.answer = finalAssistant;
191
- return;
192
- }
193
- const answerText = joinAssistantText(split.answerBlocks);
194
- group.answer = {
195
- ...finalAssistant,
196
- text: answerText,
197
- content: split.answerBlocks,
198
- };
199
- }
200
67
  /**
201
68
  * Build user → process → answer groups for the message timeline.
202
69
  */
@@ -205,7 +72,12 @@ export function buildTurnProcessGroups(input) {
205
72
  const groups = [];
206
73
  let current = null;
207
74
  const startGroup = (key) => {
208
- const group = emptyGroup(key);
75
+ const group = {
76
+ key,
77
+ assistants: [],
78
+ tools: [],
79
+ live: false,
80
+ };
209
81
  groups.push(group);
210
82
  return group;
211
83
  };
@@ -215,20 +87,17 @@ export function buildTurnProcessGroups(input) {
215
87
  current.user = message;
216
88
  continue;
217
89
  }
218
- if (message.kind === "compaction") {
219
- // Compaction is a standalone timeline event. Starting a fresh group
220
- // prevents it from replacing the previous turn's assistant answer.
221
- current = startGroup(`compaction-${message.id}`);
222
- current.assistants.push(message);
223
- current = null;
224
- continue;
225
- }
226
90
  if (!current)
227
91
  current = startGroup(`assistant-${message.id}`);
228
92
  current.assistants.push(message);
229
93
  }
230
94
  if (groups.length === 0 && input.toolCalls.length > 0) {
231
- groups.push(emptyGroup("orphan-tools"));
95
+ groups.push({
96
+ key: "orphan-tools",
97
+ assistants: [],
98
+ tools: [],
99
+ live: false,
100
+ });
232
101
  }
233
102
  const assigned = new Set();
234
103
  const assistantToGroup = new Map();
@@ -243,6 +112,7 @@ export function buildTurnProcessGroups(input) {
243
112
  if (group) {
244
113
  group.tools.push(tool);
245
114
  assigned.add(tool.id);
115
+ continue;
246
116
  }
247
117
  }
248
118
  }
@@ -264,6 +134,7 @@ export function buildTurnProcessGroups(input) {
264
134
  if (restored) {
265
135
  restored.tools.push(tool);
266
136
  assigned.add(tool.id);
137
+ continue;
267
138
  }
268
139
  }
269
140
  }
@@ -280,80 +151,28 @@ export function buildTurnProcessGroups(input) {
280
151
  windowGroup.tools.push(tool);
281
152
  assigned.add(tool.id);
282
153
  }
283
- else {
284
- const lastGroup = groups.at(-1);
285
- if (lastGroup) {
286
- lastGroup.tools.push(tool);
287
- assigned.add(tool.id);
288
- }
154
+ else if (groups.length > 0) {
155
+ groups[groups.length - 1].tools.push(tool);
156
+ assigned.add(tool.id);
289
157
  }
290
158
  }
291
159
  for (const group of groups) {
292
- group.tools.sort((a, b) => {
293
- if (a.eventSeq !== undefined && b.eventSeq !== undefined) {
294
- return a.eventSeq - b.eventSeq;
295
- }
296
- return a.createdAt - b.createdAt;
297
- });
160
+ group.tools.sort((a, b) => a.createdAt - b.createdAt);
298
161
  group.live =
299
162
  input.streaming &&
300
163
  input.activeAssistantId !== null &&
301
164
  group.assistants.some((a) => a.id === input.activeAssistantId);
302
- fillProcessDetails(group);
303
165
  }
304
166
  return groups;
305
167
  }
306
- export function processGroupSummary(tools, options) {
168
+ export function processGroupSummary(tools) {
307
169
  const count = tools.length;
308
- const thinkingCount = options?.thinkingCount ?? 0;
309
- const textCount = options?.textCount ?? 0;
310
- const activityCount = options?.activityCount ?? 0;
311
- const stepCount = thinkingCount + textCount + activityCount + count;
312
- const errorCount = tools.filter((t) => t.status === "error").length +
313
- (options?.activityErrorCount ?? 0);
314
- const runningCount = tools.filter((t) => t.status === "running").length +
315
- (options?.activityRunningCount ?? 0);
316
- const parts = [`处理详情 · ${stepCount} 步`];
317
- if (count > 0)
318
- parts.push(`${count} 工具`);
319
- if (activityCount > 0)
320
- parts.push(`${activityCount} 进展`);
170
+ const errorCount = tools.filter((t) => t.status === "error").length;
171
+ const runningCount = tools.filter((t) => t.status === "running").length;
172
+ const parts = [`执行过程 · ${count} 工具`];
321
173
  if (errorCount > 0)
322
174
  parts.push(`${errorCount} 出错`);
323
175
  else if (runningCount > 0)
324
176
  parts.push("运行中");
325
- return { count, errorCount, runningCount, stepCount, label: parts.join(" · ") };
326
- }
327
- /** Summary over rendered process items (tool-group tools count individually). */
328
- export function summarizeProcessItems(items) {
329
- const tools = [];
330
- let thinkingCount = 0;
331
- let textCount = 0;
332
- let activityCount = 0;
333
- let activityErrorCount = 0;
334
- let activityRunningCount = 0;
335
- for (const item of items) {
336
- if (item.kind === "thinking")
337
- thinkingCount += 1;
338
- else if (item.kind === "text")
339
- textCount += 1;
340
- else if (item.kind === "activity") {
341
- activityCount += 1;
342
- if (item.activity.kind === "error")
343
- activityErrorCount += 1;
344
- else if (item.activity.kind === "progress")
345
- activityRunningCount += 1;
346
- }
347
- else if (item.kind === "tool")
348
- tools.push(item.tool);
349
- else
350
- tools.push(...item.tools);
351
- }
352
- return processGroupSummary(tools, {
353
- thinkingCount,
354
- textCount,
355
- activityCount,
356
- activityErrorCount,
357
- activityRunningCount,
358
- });
177
+ return { count, errorCount, runningCount, label: parts.join(" · ") };
359
178
  }
@@ -27,7 +27,6 @@ function readWorkspaceParams(query) {
27
27
  ...(read("dagRunId") ? { dagRunId: read("dagRunId") } : {}),
28
28
  ...(read("fromNodeId") ? { fromNodeId: read("fromNodeId") } : {}),
29
29
  ...(read("featureId") ? { featureId: read("featureId") } : {}),
30
- ...(read("intent") ? { intent: read("intent") } : {}),
31
30
  };
32
31
  }
33
32
  /** Parse `#/tasks?taskId=…` hash routes (Observe-style virtual paths). */
@@ -79,7 +78,7 @@ export function resolveDefaultLanding(input) {
79
78
  return (workspaceFromLocation(input.location) ??
80
79
  configuredLanding(input.storage?.getItem(DEFAULT_LANDING_STORAGE_KEY)) ??
81
80
  configuredLanding(input.env) ??
82
- "chat");
81
+ "tasks");
83
82
  }
84
83
  /** Hash path segment for Console virtual routing (no leading `#`). */
85
84
  export function buildConsoleHashPath(workspace, params = {}) {
@@ -1,6 +1,5 @@
1
1
  import { clipPreview, } from "./operation-store.js";
2
2
  import { stateEvent } from "./operation-sse.js";
3
- import { extractTrustedRunFacts } from "./operation-run-facts.js";
4
3
  /**
5
4
  * Execute a queued operation via LoopAgentClient (or inject).
6
5
  * Timeout without proven exit → needs-reconcile (never Cancel).
@@ -75,30 +74,6 @@ export async function runOperation(operationId, deps) {
75
74
  message: chunk,
76
75
  });
77
76
  },
78
- onHeartbeat: (info) => {
79
- // P1 (2026-08-13): project sibling CLI heartbeats into canonical
80
- // operation events so operationWait/operationEventSummary can consume
81
- // them. The projection is best-effort: a failed append must never
82
- // terminate the sibling CLI execution (AC-002). The injected test
83
- // runCommand path has no LoopAgentClient callback guard, so the
84
- // runner protects itself here.
85
- try {
86
- deps.events.append(operationId, {
87
- at: info.at,
88
- kind: "heartbeat",
89
- message: `heartbeat elapsedMs=${info.elapsedMs}`,
90
- data: {
91
- elapsedMs: info.elapsedMs,
92
- action: op.action,
93
- ...(op.taskId ? { taskId: op.taskId } : {}),
94
- ...(op.dagRunId ? { dagRunId: op.dagRunId } : {}),
95
- },
96
- });
97
- }
98
- catch {
99
- // Heartbeat is derived telemetry; ignore projection failures.
100
- }
101
- },
102
77
  });
103
78
  await spawnUpdate;
104
79
  finished = await finalizeFromWorkerResult(operationId, result, deps);
@@ -122,38 +97,6 @@ export async function runOperation(operationId, deps) {
122
97
  }
123
98
  return finished;
124
99
  }
125
- function taskAdvanceBusinessBlock(operation, envelope) {
126
- if (!operation || !envelope)
127
- return undefined;
128
- if (operation.action !== "taskAdvance" && operation.action !== "dagRunTask") {
129
- return undefined;
130
- }
131
- const result = envelope.result && typeof envelope.result === "object"
132
- ? envelope.result
133
- : undefined;
134
- const lifecycleState = typeof result?.lifecycleState === "string"
135
- ? result.lifecycleState
136
- : undefined;
137
- const blockers = Array.isArray(result?.blockers)
138
- ? result.blockers
139
- : [];
140
- const expectedGateStop = lifecycleState === "awaiting-write-set-approval" && blockers.length === 0;
141
- if (expectedGateStop)
142
- return undefined;
143
- if (lifecycleState !== "intake-incomplete" &&
144
- blockers.length === 0 &&
145
- envelope.outcome !== "blocked") {
146
- return undefined;
147
- }
148
- const firstBlocker = blockers[0];
149
- const code = typeof firstBlocker?.code === "string"
150
- ? firstBlocker.code
151
- : "TASK_ADVANCE_BLOCKED";
152
- const message = typeof firstBlocker?.message === "string"
153
- ? firstBlocker.message
154
- : `task advance stopped before runnable DAG completion (lifecycleState=${lifecycleState ?? "unknown"})`;
155
- return { code, message };
156
- }
157
100
  async function finalizeFromWorkerResult(operationId, result, deps) {
158
101
  const previewStdout = clipPreview(result.stdout ?? "");
159
102
  const previewStderr = clipPreview(result.stderr ?? "");
@@ -163,8 +106,6 @@ async function finalizeFromWorkerResult(operationId, result, deps) {
163
106
  resultPath: result.artifacts.resultPath,
164
107
  dir: result.artifacts.dir,
165
108
  };
166
- // Trusted structured run facts only (AC-003): never scanned from free text.
167
- const runFacts = extractTrustedRunFacts(result);
168
109
  if (result.timedOut) {
169
110
  // Supervisory timeout without classified CLI timeout → needs-reconcile
170
111
  // Only mark timed-out when we have a machine timeout result after exit.
@@ -180,35 +121,12 @@ async function finalizeFromWorkerResult(operationId, result, deps) {
180
121
  ? "CLI timed out with process exit"
181
122
  : "timeout reached without proven process exit; needs reconcile (not cancelled)",
182
123
  result: coerceEnvelope(result),
183
- ...(runFacts
184
- ? {
185
- dagRunId: runFacts.runId,
186
- ...(runFacts.runSummary ? { runSummary: runFacts.runSummary } : {}),
187
- }
188
- : {}),
189
124
  });
190
125
  stateEvent(deps.events, updated, state, updated.errorMessage);
191
- if (runFacts) {
192
- appendSafeResultEvent(deps, operationId, state, {
193
- ok: false,
194
- exitCode: result.exitCode,
195
- }, runFacts);
196
- }
197
- else {
198
- deps.events.append(operationId, {
199
- at: new Date().toISOString(),
200
- kind: "result",
201
- state,
202
- data: { ok: false, exitCode: result.exitCode },
203
- });
204
- }
205
126
  return updated;
206
127
  }
207
128
  const envelope = coerceEnvelope(result);
208
- const operation = await deps.store.get(operationId);
209
- const businessBlock = taskAdvanceBusinessBlock(operation, envelope);
210
- const processOk = envelope?.ok === true || (result.ok && result.exitCode === 0);
211
- const ok = processOk && !businessBlock;
129
+ const ok = envelope?.ok === true || (result.ok && result.exitCode === 0);
212
130
  const state = ok ? "succeeded" : "failed";
213
131
  const updated = await deps.store.update(operationId, {
214
132
  state,
@@ -220,43 +138,21 @@ async function finalizeFromWorkerResult(operationId, result, deps) {
220
138
  stdout: previewStdout,
221
139
  stderr: previewStderr,
222
140
  }),
223
- errorCode: ok
224
- ? undefined
225
- : (businessBlock?.code ?? envelope?.error?.code ?? "INTERNAL_ERROR"),
141
+ errorCode: ok ? undefined : (envelope?.error?.code ?? "INTERNAL_ERROR"),
226
142
  errorMessage: ok
227
143
  ? undefined
228
- : (businessBlock?.message ??
229
- envelope?.error?.message ??
144
+ : (envelope?.error?.message ??
230
145
  (previewStderr.trim() ||
231
146
  `command failed with exit ${result.exitCode}`)),
232
- // Atomic final patch: state/result + trusted dagRunId + safe runSummary
233
- // in a single per-op write-locked update (AC-004). The runSummary is a
234
- // projection and never launders the envelope/exit-decided state.
235
- ...(runFacts
236
- ? {
237
- dagRunId: runFacts.runId,
238
- ...(runFacts.runSummary ? { runSummary: runFacts.runSummary } : {}),
239
- }
240
- : {}),
241
147
  });
242
- stateEvent(deps.events, updated, state, updated.errorMessage);
243
- appendSafeResultEvent(deps, operationId, state, { ok, exitCode: result.exitCode }, runFacts);
244
- if (ok && operation?.action === "dagRequestInterrupt") {
245
- await projectInterruptRequestOntoTarget(operation, result.json, deps);
246
- }
247
- return updated;
248
- }
249
- /** Result event carries the same safe summary persisted on the record. */
250
- function appendSafeResultEvent(deps, operationId, state, base, runFacts) {
148
+ stateEvent(deps.events, updated, state);
251
149
  deps.events.append(operationId, {
252
150
  at: new Date().toISOString(),
253
151
  kind: "result",
254
152
  state,
255
- data: {
256
- ...base,
257
- ...(runFacts?.runSummary ? { runSummary: runFacts.runSummary } : {}),
258
- },
153
+ data: { ok, exitCode: result.exitCode },
259
154
  });
155
+ return updated;
260
156
  }
261
157
  function coerceEnvelope(result) {
262
158
  const json = result.json;
@@ -306,48 +202,3 @@ export function scheduleOperation(operationId, deps) {
306
202
  });
307
203
  });
308
204
  }
309
- async function projectInterruptRequestOntoTarget(operation, json, deps) {
310
- const params = operation.actionParams ?? {};
311
- const targetOperationId = typeof params.targetOperationId === "string"
312
- ? params.targetOperationId.trim()
313
- : "";
314
- const runId = typeof params.runId === "string" ? params.runId.trim() : "";
315
- if (!targetOperationId)
316
- return;
317
- const rec = json && typeof json === "object" ? json : {};
318
- const request = rec.request && typeof rec.request === "object"
319
- ? rec.request
320
- : rec;
321
- const status = (typeof rec.interruptStatus === "string" && rec.interruptStatus) ||
322
- (typeof request.status === "string" && request.status) ||
323
- "requested";
324
- if (status !== "requested" &&
325
- status !== "acknowledged" &&
326
- status !== "settled" &&
327
- status !== "needs-reconcile") {
328
- return;
329
- }
330
- const target = await deps.store.get(targetOperationId);
331
- if (!target)
332
- return;
333
- await deps.store.update(targetOperationId, {
334
- ...(runId && !target.dagRunId ? { dagRunId: runId } : {}),
335
- interrupt: {
336
- status,
337
- ...(typeof request.requestId === "string"
338
- ? { requestId: request.requestId }
339
- : {}),
340
- ...(typeof request.reasonCode === "string"
341
- ? { reasonCode: request.reasonCode }
342
- : {}),
343
- ...(typeof request.requestedAt === "string"
344
- ? { requestedAt: request.requestedAt }
345
- : {}),
346
- },
347
- });
348
- deps.events.append(targetOperationId, {
349
- kind: "state",
350
- message: `interrupt ${status}`,
351
- data: { interruptStatus: status, ...(runId ? { runId } : {}) },
352
- });
353
- }