@tea-agent/loop-agent 0.44.0-next.3 → 0.44.0-next.5

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 (102) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/dag-pi-executor.js +2 -2
  4. package/dist/executors/shell-write-guard.js +26 -3
  5. package/dist/shared/file-identity.js +23 -0
  6. package/dist/shared/git-workspace-status.js +480 -0
  7. package/dist/shared/path-safety.js +2 -2
  8. package/dist/task/config-types.js +2 -1
  9. package/dist/task/contract/schema.js +1 -0
  10. package/dist/worker/console/chat/chat-event-store.js +32 -0
  11. package/dist/worker/console/chat/context-insights.js +723 -0
  12. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  13. package/dist/worker/console/chat/pi-runtime.js +128 -12
  14. package/dist/worker/console/chat/provider-error.js +0 -12
  15. package/dist/worker/console/chat/repo-browser.js +3 -4
  16. package/dist/worker/console/chat/routes.js +209 -4
  17. package/dist/worker/console/chat/shortcuts.js +7 -0
  18. package/dist/worker/console/chat/turn-process.js +128 -7
  19. package/dist/worker/console/prd-intake-bridge.js +3 -0
  20. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-COQlLTI7.js → abnfDiagram-N423BO3Z-BHuhGM1e.js} +1 -1
  21. package/dist/worker/console/static/assets/{arc-zN3QkNaV.js → arc-CKh5E_sr.js} +1 -1
  22. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Pyoyj5f2.js → architectureDiagram-T3A2C74G-DizhiBEv.js} +1 -1
  23. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BNppA5WW.js → blockDiagram-VBNYF7ZC-3Neea-Z-.js} +1 -1
  24. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-lyxL8nTx.js → c4Diagram-5PPSVZJV-BCP_Qrgi.js} +1 -1
  25. package/dist/worker/console/static/assets/channel-DxgzA1GF.js +1 -0
  26. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-0hgjcdK4.js → chunk-2GRJ4B5K-C_wydftK.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D63N9d4i.js → chunk-2Q5K7J3B-jRPXZbzx.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CUw2AyB5.js → chunk-5RXB4S5H-BLQf2OYO.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-Q_kwZT30.js → chunk-5VM5RSS4-0qRq64rI.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-C7vMiKcw.js → chunk-6Q2QTUOP-ClMg8xmV.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-T9GbV5Hz.js → chunk-GF5L2VYU-Coop2FlL.js} +1 -1
  32. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D8nUSUJI.js → chunk-JWPE2WC7-CDdO-jPJ.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BCH2ewdp.js → chunk-KBJHAD2P-D8Gkppjl.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BTdDuDBN.js → chunk-RYQCIY6F-DA9HIySf.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Cqm2RcKF.js → chunk-XXDRQBXY-DZNDRX1k.js} +1 -1
  36. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +1 -0
  37. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +1 -0
  38. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-pjwfpVu4.js → cose-bilkent-JH36ORCC-CZ1zGyjq.js} +1 -1
  39. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BvalptKA.js → cynefin-VYW2F7L2-B8u1_FtM.js} +1 -1
  40. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BU4qJ6d7.js → cynefinDiagram-MW4NZA55-CPBB6lfo.js} +1 -1
  41. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DVTpq8J9.js → dagre-VZM6K2ZE-CG3Eov3E.js} +1 -1
  42. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B8s1W6Fc.js → diagram-7IWD3JNH-ycI-dOjh.js} +1 -1
  43. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DPrFk9yH.js → diagram-B4RE2ZJO-Bx_GyQ7Y.js} +1 -1
  44. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CnQIQsHC.js → diagram-LBJQPF4R-m0G6mP4d.js} +1 -1
  45. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-op0DEpCv.js → diagram-Q27KOJAE-DTDJkqcJ.js} +1 -1
  46. package/dist/worker/console/static/assets/{diagram-UB23O5K3-DXQamm4T.js → diagram-UB23O5K3-BCfFrG-p.js} +1 -1
  47. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-b1ad2C0r.js → ebnfDiagram-BXEA7PRR-BIkTNPpp.js} +1 -1
  48. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B4cD-ZFJ.js → erDiagram-JOGREHBK-DTcMWrd2.js} +1 -1
  49. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CrgGjlQ1.js → flowDiagram-UKHOOZJN-BqzDaNQ7.js} +1 -1
  50. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DovmXQNV.js → ganttDiagram-PKOTCBZU-BSl_vGFe.js} +1 -1
  51. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DpFyu2Jn.js → gitGraphDiagram-DS77QQ5N-DCA5HXM6.js} +1 -1
  52. package/dist/worker/console/static/assets/index-BAEDjTbD.css +1 -0
  53. package/dist/worker/console/static/assets/index-wKEJUaP5.js +482 -0
  54. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-D3MoQZIl.js → infoDiagram-6WML65LV-DvqT18ge.js} +1 -1
  55. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Ci6C1yjS.js → ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js} +1 -1
  56. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BWMo2lIP.js → journeyDiagram-NVQOT4AX-BpLQPI9C.js} +1 -1
  57. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BdthIAg3.js → kanban-definition-27J2QSJJ-CzIz_-Lt.js} +1 -1
  58. package/dist/worker/console/static/assets/{linear-DuFAlSOj.js → linear-CMQ8Jpp-.js} +1 -1
  59. package/dist/worker/console/static/assets/{mermaid.core-Dqz3Slj9.js → mermaid.core-BdAMrTf7.js} +5 -5
  60. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DNVTrD09.js → mindmap-definition-FAOFIHXS-hSwF6sOq.js} +1 -1
  61. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CgfZPfBh.js → pegDiagram-VL7TDLO6-DZdv509B.js} +1 -1
  62. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-ClM9P7yo.js → pieDiagram-7S7Q4E2Y-CmD4-b0K.js} +1 -1
  63. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DwnEak2J.js → quadrantDiagram-CIZ2JOQS-DtKxNxlS.js} +1 -1
  64. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-rK4hxt9P.js → railroadDiagram-AXF67PYL-D48iIoiS.js} +1 -1
  65. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Cjqa6vo3.js → requirementDiagram-LRYGKXZP-C3N0K-2p.js} +1 -1
  66. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-D3Fa250Z.js → sankeyDiagram-W5VNT64P-CACWR-Xs.js} +1 -1
  67. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-2PdrNajj.js → sequenceDiagram-SI44F4Z6-g_QgugvZ.js} +1 -1
  68. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BBcuGeZn.js → sizeCapture-X5ZJPWSS-DqLoRPGK.js} +1 -1
  69. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CSJX9X6s.js → stateDiagram-OKZ733FA-ES59VgMG.js} +1 -1
  70. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +1 -0
  71. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-pOUU1bic.js → swimlanes-SLNWSIFB-fjyLVNWo.js} +2 -2
  72. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +8 -0
  73. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CAeqfQn0.js → timeline-definition-Z64GVDOM-Cd5-biKv.js} +1 -1
  74. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DYPLOMVO.js → vennDiagram-T6HMQDX7-CZTu6mPf.js} +1 -1
  75. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dy2V0Cha.js → wardleyDiagram-T6FBY63Y-DR01jjbe.js} +1 -1
  76. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Dj9o7jrn.js → xychartDiagram-ELKLHX3M-iZcuGVGa.js} +1 -1
  77. package/dist/worker/console/static/index.html +2 -2
  78. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  79. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +97 -3
  80. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  81. package/dist/worker/console/static-src/operator-chat/process-label.js +11 -2
  82. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -1
  83. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  84. package/dist/worker/console/static-src/operator-chat/useChatThread.js +9 -0
  85. package/dist/worker/observe/node-context-insights.js +479 -0
  86. package/dist/workflows/dag/frontend-implementation-contract.js +3 -1
  87. package/dist/workflows/dag/frontend-plan-completeness.js +13 -3
  88. package/dist/workflows/dag/frontend-recovery-plan.js +10 -6
  89. package/dist/workflows/dag/frontend-typed-event-store.js +5 -0
  90. package/dist/workflows/dag/frontend-writer-admission.js +1 -0
  91. package/dist/workflows/dag/output-protocol.js +1 -0
  92. package/dist/workflows/dag/rerun-task.js +1 -0
  93. package/docs/README.md +1 -1
  94. package/docs/governance/harness-methodology-verification.md +11 -0
  95. package/package.json +1 -1
  96. package/dist/worker/console/static/assets/channel-CgLPkTw2.js +0 -1
  97. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuSD1Mhb.js +0 -1
  98. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuSD1Mhb.js +0 -1
  99. package/dist/worker/console/static/assets/index-DHBWEwz_.css +0 -1
  100. package/dist/worker/console/static/assets/index-JRfTUgP7.js +0 -478
  101. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cqskj0zt.js +0 -1
  102. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CM0ZIhNP.js +0 -8
@@ -0,0 +1,383 @@
1
+ function finiteStep(data) {
2
+ return typeof data.step === "number" && Number.isSafeInteger(data.step) && data.step >= 0
3
+ ? data.step
4
+ : undefined;
5
+ }
6
+ function locationFor(event, step = finiteStep(event.data)) {
7
+ return step === undefined
8
+ ? { kind: "turn", turnId: event.turnId }
9
+ : { kind: "step", turnId: event.turnId, step };
10
+ }
11
+ function status(value) {
12
+ return value === "scheduled" ||
13
+ value === "started" ||
14
+ value === "cancelled" ||
15
+ value === "running" ||
16
+ value === "completed" ||
17
+ value === "failed" ||
18
+ value === "interrupted"
19
+ ? value
20
+ : undefined;
21
+ }
22
+ function positiveInteger(value) {
23
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0
24
+ ? value
25
+ : undefined;
26
+ }
27
+ function nonNegativeNumber(value) {
28
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
29
+ ? value
30
+ : undefined;
31
+ }
32
+ function nonEmptyString(value) {
33
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
34
+ }
35
+ function unknownRetryNode(event, base) {
36
+ return {
37
+ ...base,
38
+ key: `unknown:${event.eventId}`,
39
+ kind: "unknown",
40
+ location: locationFor(event),
41
+ data: {
42
+ eventType: "model_retry",
43
+ preview: "Incomplete model retry lifecycle event",
44
+ },
45
+ };
46
+ }
47
+ function retryStateFromEvent(data) {
48
+ if (data.state === "scheduled")
49
+ return "scheduled";
50
+ return data.success === false || data.state === "failed"
51
+ ? "cancelled"
52
+ : "started";
53
+ }
54
+ function isRetryAttempt(value) {
55
+ if (!value || typeof value !== "object" || Array.isArray(value))
56
+ return false;
57
+ const attempt = value;
58
+ return (positiveInteger(attempt.attempt) !== undefined &&
59
+ positiveInteger(attempt.maxAttempts) !== undefined &&
60
+ nonNegativeNumber(attempt.delayMs) !== undefined &&
61
+ nonEmptyString(attempt.errorMessage) !== undefined &&
62
+ (attempt.state === "scheduled" ||
63
+ attempt.state === "started" ||
64
+ attempt.state === "cancelled") &&
65
+ typeof attempt.seq === "number" &&
66
+ typeof attempt.at === "number");
67
+ }
68
+ /** Read the current complete attempt from a projected retry-chain node. */
69
+ export function modelRetryCurrent(node) {
70
+ if (node.kind !== "retry")
71
+ return undefined;
72
+ return isRetryAttempt(node.data.current) ? node.data.current : undefined;
73
+ }
74
+ function modelRetryAttempts(node) {
75
+ return Array.isArray(node.data.attempts)
76
+ ? node.data.attempts.filter(isRetryAttempt)
77
+ : [];
78
+ }
79
+ function retryUpdate(node) {
80
+ const value = node.data.retryUpdate;
81
+ if (!value || typeof value !== "object" || Array.isArray(value))
82
+ return undefined;
83
+ const candidate = value;
84
+ if (positiveInteger(candidate.attempt) === undefined ||
85
+ (candidate.state !== "started" && candidate.state !== "cancelled"))
86
+ return undefined;
87
+ return candidate;
88
+ }
89
+ /** Convert one already-redacted durable event into a stable UI node. */
90
+ export function conversationNodeFromEvent(event) {
91
+ const at = Date.parse(event.at);
92
+ const base = {
93
+ anchorSeq: event.seq,
94
+ updatedSeq: event.seq,
95
+ at: Number.isFinite(at) ? at : 0,
96
+ data: event.data,
97
+ };
98
+ const step = finiteStep(event.data);
99
+ switch (event.kind) {
100
+ case "step_start":
101
+ case "step_end": {
102
+ if (step === undefined)
103
+ return undefined;
104
+ const outcome = status(event.data.outcome);
105
+ return {
106
+ ...base,
107
+ key: `step:${event.turnId}:${step}`,
108
+ kind: "step",
109
+ location: { kind: "step", turnId: event.turnId, step },
110
+ status: event.kind === "step_start" ? "running" : (outcome ?? "completed"),
111
+ };
112
+ }
113
+ case "model_retry": {
114
+ const attempt = positiveInteger(event.data.attempt);
115
+ if (attempt === undefined)
116
+ return unknownRetryNode(event, base);
117
+ const retryState = retryStateFromEvent(event.data);
118
+ const key = `retry:${event.turnId}:${step ?? 0}`;
119
+ if (retryState !== "scheduled") {
120
+ return {
121
+ ...base,
122
+ key,
123
+ kind: "retry",
124
+ location: locationFor(event),
125
+ status: retryState,
126
+ data: {
127
+ retryUpdate: {
128
+ attempt,
129
+ state: retryState,
130
+ seq: event.seq,
131
+ at: base.at,
132
+ },
133
+ },
134
+ };
135
+ }
136
+ const maxAttempts = positiveInteger(event.data.maxAttempts);
137
+ const delayMs = nonNegativeNumber(event.data.delayMs);
138
+ const errorMessage = nonEmptyString(event.data.errorMessage);
139
+ if (maxAttempts === undefined || delayMs === undefined || !errorMessage) {
140
+ return unknownRetryNode(event, base);
141
+ }
142
+ const current = {
143
+ attempt,
144
+ maxAttempts,
145
+ delayMs,
146
+ errorMessage,
147
+ state: "scheduled",
148
+ seq: event.seq,
149
+ at: base.at,
150
+ };
151
+ return {
152
+ ...base,
153
+ key,
154
+ kind: "retry",
155
+ location: locationFor(event),
156
+ status: "scheduled",
157
+ data: { attempts: [current], current },
158
+ };
159
+ }
160
+ case "turn_error":
161
+ return {
162
+ ...base,
163
+ key: `turn-error:${event.turnId}`,
164
+ kind: "turn-error",
165
+ location: locationFor(event),
166
+ status: "failed",
167
+ };
168
+ case "turn_max_tokens":
169
+ return {
170
+ ...base,
171
+ key: `max-tokens:${event.turnId}:${step ?? 0}`,
172
+ kind: "max-tokens",
173
+ location: locationFor(event),
174
+ status: "completed",
175
+ };
176
+ case "request_context": {
177
+ const requestId = typeof event.data.requestId === "string" && event.data.requestId
178
+ ? event.data.requestId
179
+ : event.eventId;
180
+ return {
181
+ ...base,
182
+ key: `request:${requestId}`,
183
+ kind: "request",
184
+ location: locationFor(event),
185
+ status: "completed",
186
+ };
187
+ }
188
+ case "command": {
189
+ const commandId = typeof event.data.commandId === "string" && event.data.commandId
190
+ ? event.data.commandId
191
+ : event.eventId;
192
+ return {
193
+ ...base,
194
+ key: `command:${commandId}`,
195
+ kind: "command",
196
+ location: { kind: "turn", turnId: event.turnId },
197
+ status: status(event.data.outcome) ?? "completed",
198
+ };
199
+ }
200
+ case "unknown":
201
+ return {
202
+ ...base,
203
+ key: `unknown:${event.eventId}`,
204
+ kind: "unknown",
205
+ location: locationFor(event),
206
+ };
207
+ }
208
+ }
209
+ export function conversationNodesFromEvents(events) {
210
+ let nodes = [];
211
+ for (const event of events) {
212
+ const node = conversationNodeFromEvent(event);
213
+ if (node)
214
+ nodes = upsertConversationNode(nodes, node);
215
+ }
216
+ return nodes;
217
+ }
218
+ /** Keyed upsert that preserves the first structural anchor and location. */
219
+ export function upsertConversationNode(nodes, next) {
220
+ if (next.kind === "retry")
221
+ return upsertRetryNode(nodes, next);
222
+ const merged = upsertBase(nodes, next);
223
+ if (next.kind === "step" && next.location.kind === "step") {
224
+ const { step: nextStep, turnId } = next.location;
225
+ if (next.status === "running") {
226
+ return transitionScheduledRetries(merged, (node) => node.location.kind === "step" &&
227
+ node.location.turnId === turnId &&
228
+ node.location.step < nextStep, "started", next.updatedSeq);
229
+ }
230
+ return transitionScheduledRetries(merged, (node) => node.location.kind === "step" &&
231
+ node.location.turnId === turnId &&
232
+ node.location.step === nextStep &&
233
+ node.anchorSeq < next.updatedSeq, "cancelled", next.updatedSeq);
234
+ }
235
+ if (next.kind === "turn-error") {
236
+ return transitionScheduledRetries(merged, (node) => node.location.kind !== "session" &&
237
+ next.location.kind !== "session" &&
238
+ node.location.turnId === next.location.turnId, "cancelled", next.updatedSeq);
239
+ }
240
+ return merged;
241
+ }
242
+ function upsertBase(nodes, next) {
243
+ const index = nodes.findIndex((node) => node.key === next.key);
244
+ if (index < 0) {
245
+ return [...nodes, next].sort((left, right) => left.anchorSeq - right.anchorSeq || left.key.localeCompare(right.key));
246
+ }
247
+ const previous = nodes[index];
248
+ if (next.updatedSeq < previous.updatedSeq)
249
+ return [...nodes];
250
+ const merged = {
251
+ ...previous,
252
+ ...next,
253
+ anchorSeq: previous.anchorSeq,
254
+ at: previous.at,
255
+ location: previous.location,
256
+ data: { ...previous.data, ...next.data },
257
+ };
258
+ const result = [...nodes];
259
+ result[index] = merged;
260
+ return result;
261
+ }
262
+ function retryTurnId(node) {
263
+ return node.location.kind === "session" ? undefined : node.location.turnId;
264
+ }
265
+ function retryCandidateIndex(nodes, next) {
266
+ const exact = nodes.findIndex((node) => node.key === next.key);
267
+ if (exact >= 0)
268
+ return exact;
269
+ const update = retryUpdate(next);
270
+ const nextCurrent = modelRetryCurrent(next);
271
+ const attempt = update?.attempt ?? nextCurrent?.attempt;
272
+ const turnId = retryTurnId(next);
273
+ if (attempt === undefined || !turnId)
274
+ return -1;
275
+ for (let index = nodes.length - 1; index >= 0; index -= 1) {
276
+ const candidate = nodes[index];
277
+ const current = modelRetryCurrent(candidate);
278
+ if (candidate.kind !== "retry" || retryTurnId(candidate) !== turnId || !current)
279
+ continue;
280
+ if (current.attempt === attempt)
281
+ return index;
282
+ if (nextCurrent?.state === "scheduled" && current.attempt + 1 === attempt)
283
+ return index;
284
+ }
285
+ return -1;
286
+ }
287
+ function upsertRetryNode(nodes, next) {
288
+ const index = retryCandidateIndex(nodes, next);
289
+ const nextCurrent = modelRetryCurrent(next);
290
+ const update = retryUpdate(next);
291
+ if (index < 0) {
292
+ if (nextCurrent)
293
+ return upsertBase(nodes, next);
294
+ return upsertBase(nodes, {
295
+ ...next,
296
+ key: `unknown:${next.key}:${next.updatedSeq}`,
297
+ kind: "unknown",
298
+ status: undefined,
299
+ data: {
300
+ eventType: "model_retry",
301
+ preview: "Orphan model retry lifecycle update",
302
+ },
303
+ });
304
+ }
305
+ const previous = nodes[index];
306
+ if (next.updatedSeq < previous.updatedSeq)
307
+ return [...nodes];
308
+ let attempts = modelRetryAttempts(previous);
309
+ if (nextCurrent) {
310
+ attempts = attempts.map((attempt) => attempt.state === "scheduled" && attempt.attempt < nextCurrent.attempt
311
+ ? { ...attempt, state: "started" }
312
+ : attempt);
313
+ const existing = attempts.findIndex((attempt) => attempt.attempt === nextCurrent.attempt);
314
+ if (existing >= 0)
315
+ attempts[existing] = { ...attempts[existing], ...nextCurrent };
316
+ else
317
+ attempts = [...attempts, nextCurrent];
318
+ }
319
+ else if (update) {
320
+ attempts = attempts.map((attempt) => attempt.attempt === update.attempt
321
+ ? { ...attempt, state: update.state }
322
+ : attempt);
323
+ }
324
+ const current = attempts.at(-1);
325
+ if (!current)
326
+ return [...nodes];
327
+ const result = [...nodes];
328
+ result[index] = {
329
+ ...previous,
330
+ updatedSeq: next.updatedSeq,
331
+ status: current.state,
332
+ data: { attempts, current },
333
+ };
334
+ return result;
335
+ }
336
+ function transitionScheduledRetries(nodes, predicate, state, updatedSeq) {
337
+ return nodes.map((node) => {
338
+ const current = modelRetryCurrent(node);
339
+ if (node.kind !== "retry" || current?.state !== "scheduled" || !predicate(node)) {
340
+ return node;
341
+ }
342
+ const attempts = modelRetryAttempts(node).map((attempt) => attempt.attempt === current.attempt
343
+ ? { ...attempt, state }
344
+ : attempt);
345
+ const nextCurrent = attempts.at(-1);
346
+ return {
347
+ ...node,
348
+ updatedSeq: Math.max(node.updatedSeq, updatedSeq),
349
+ status: state,
350
+ data: { attempts, current: nextCurrent },
351
+ };
352
+ });
353
+ }
354
+ /** Resolve explicit parent links only. Cycles and excessive chains fail flat. */
355
+ export function toolNestingDepths(tools, maxDepth = 32) {
356
+ const byId = new Map(tools.map((tool) => [tool.id, tool]));
357
+ const depths = new Map();
358
+ const cyclic = new Set();
359
+ const resolve = (id, path) => {
360
+ const cached = depths.get(id);
361
+ if (cached !== undefined)
362
+ return cached;
363
+ const cycleAt = path.indexOf(id);
364
+ if (cycleAt >= 0) {
365
+ for (const key of path.slice(cycleAt))
366
+ cyclic.add(key);
367
+ return 0;
368
+ }
369
+ if (path.length >= maxDepth)
370
+ return 0;
371
+ const tool = byId.get(id);
372
+ const parent = tool?.parentToolCallId;
373
+ if (!parent || !byId.has(parent) || parent === id)
374
+ return 0;
375
+ const depth = Math.min(maxDepth, resolve(parent, [...path, id]) + 1);
376
+ return cyclic.has(id) ? 0 : depth;
377
+ };
378
+ for (const tool of tools)
379
+ depths.set(tool.id, resolve(tool.id, []));
380
+ for (const id of cyclic)
381
+ depths.set(id, 0);
382
+ return depths;
383
+ }
@@ -297,6 +297,35 @@ export function createWizardLocalHeadersExtension(options) {
297
297
  export { THINKING_LEVELS, RuntimeSnapshotUnavailableError, projectRuntimeSnapshot, };
298
298
  export { createDefaultPiSdkBindings } from "./pi-runtime/sdk-bindings.js";
299
299
  export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
300
+ export function buildRequestContextEvent(input) {
301
+ const systemPrompt = input.session.state?.systemPrompt ?? input.session.systemPrompt ?? "";
302
+ const activeTools = input.session.getActiveToolNames()
303
+ .filter((name) => typeof name === "string" && name.trim())
304
+ .slice(0, 256);
305
+ const provider = input.session.model?.provider;
306
+ const modelId = input.session.model?.modelId ?? input.session.model?.id;
307
+ const sources = ["system", "conversation"];
308
+ if (activeTools.length > 0)
309
+ sources.push("tools");
310
+ if (input.imageCount > 0)
311
+ sources.push("images");
312
+ return {
313
+ type: "request_context",
314
+ sessionId: input.sessionId,
315
+ requestId: `${input.turnId}:request:1`,
316
+ step: 1,
317
+ ...(provider && modelId ? { model: { provider, modelId } } : {}),
318
+ ...(input.session.thinkingLevel
319
+ ? { thinkingLevel: input.session.thinkingLevel }
320
+ : {}),
321
+ systemPromptHash: createHash("sha256").update(systemPrompt).digest("hex"),
322
+ systemPromptChars: systemPrompt.length,
323
+ activeTools,
324
+ inputChars: input.text.length,
325
+ imageCount: input.imageCount,
326
+ sources,
327
+ };
328
+ }
300
329
  function readSessionManagerId(manager) {
301
330
  if (!manager || typeof manager !== "object")
302
331
  return undefined;
@@ -1977,6 +2006,14 @@ export class ConsolePiRuntime {
1977
2006
  };
1978
2007
  emitContextUsage();
1979
2008
  await this.runPrePromptCompaction(sessionId, session, text, options?.images?.length ?? 0, onEvent);
2009
+ onEvent(buildRequestContextEvent({
2010
+ sessionId,
2011
+ turnId: options?.turnId ?? `${sessionId}:active`,
2012
+ session,
2013
+ text,
2014
+ imageCount: options?.images?.length ?? 0,
2015
+ }));
2016
+ let step = 0;
1980
2017
  let stepStartedAt;
1981
2018
  let firstTokenAt;
1982
2019
  let pendingProviderError;
@@ -1985,12 +2022,24 @@ export class ConsolePiRuntime {
1985
2022
  const emitPendingProviderError = () => {
1986
2023
  if (!pendingProviderError)
1987
2024
  return;
1988
- onEvent(pendingProviderError);
2025
+ const error = pendingProviderError;
2026
+ onEvent(error);
2027
+ onEvent({
2028
+ type: "turn_error",
2029
+ sessionId,
2030
+ step: Math.max(1, step),
2031
+ ...(error.providerError.code === undefined
2032
+ ? {}
2033
+ : { code: String(error.providerError.code) }),
2034
+ message: error.message,
2035
+ });
1989
2036
  pendingProviderError = undefined;
1990
2037
  providerErrorEmitted = true;
1991
2038
  };
1992
2039
  unsub = session.subscribe((event) => {
1993
2040
  const observedAt = Date.now();
2041
+ if (event.type === "turn_start")
2042
+ step += 1;
1994
2043
  if (event.type === "message_start" && isUserSdkMessage(event.message)) {
1995
2044
  if (!initialUserMessageStarted) {
1996
2045
  // The first user message_start belongs to session.prompt(text), not
@@ -2021,7 +2070,7 @@ export class ConsolePiRuntime {
2021
2070
  const compactionId = event.type === "compaction_start" || event.type === "compaction_end"
2022
2071
  ? activeCompaction?.compactionId ?? newCompactionId(sessionId)
2023
2072
  : undefined;
2024
- const mapped = mapSdkEvent(sessionId, event, compactionId);
2073
+ const mapped = mapSdkEvent(sessionId, event, compactionId, Math.max(1, step));
2025
2074
  if (event.type === "compaction_end")
2026
2075
  activeCompaction = undefined;
2027
2076
  if (mapped?.type === "error") {
@@ -2046,7 +2095,7 @@ export class ConsolePiRuntime {
2046
2095
  if (mapped?.type === "agent_settled") {
2047
2096
  emitPendingProviderError();
2048
2097
  }
2049
- if (mapped?.type === "agent_start") {
2098
+ if (mapped?.type === "step_start" || mapped?.type === "agent_start") {
2050
2099
  stepStartedAt = observedAt;
2051
2100
  firstTokenAt = undefined;
2052
2101
  }
@@ -2074,6 +2123,13 @@ export class ConsolePiRuntime {
2074
2123
  : { outputTokens: usage.outputTokens }),
2075
2124
  };
2076
2125
  onEvent({ ...mapped, ...(timing ? { timing } : {}) });
2126
+ if (mapped.stopReason === "length") {
2127
+ onEvent({
2128
+ type: "turn_max_tokens",
2129
+ sessionId,
2130
+ step: mapped.step ?? Math.max(1, step),
2131
+ });
2132
+ }
2077
2133
  if (usage)
2078
2134
  onEvent({ type: "usage", sessionId, usage });
2079
2135
  emitContextUsage();
@@ -2331,7 +2387,7 @@ export class ConsolePiRuntime {
2331
2387
  this.dbConnections.dispose();
2332
2388
  }
2333
2389
  }
2334
- function mapAssistantMessageEvent(type, sessionId, message) {
2390
+ function mapAssistantMessageEvent(type, sessionId, message, step) {
2335
2391
  const record = message !== null && typeof message === "object"
2336
2392
  ? message
2337
2393
  : undefined;
@@ -2355,6 +2411,10 @@ function mapAssistantMessageEvent(type, sessionId, message) {
2355
2411
  sessionId,
2356
2412
  text: extractAssistantText(message),
2357
2413
  ...(content.length > 0 ? { content } : {}),
2414
+ step,
2415
+ ...(type === "message_end" && typeof record?.stopReason === "string"
2416
+ ? { stopReason: record.stopReason }
2417
+ : {}),
2358
2418
  };
2359
2419
  }
2360
2420
  function isUserSdkMessage(message) {
@@ -2362,10 +2422,12 @@ function isUserSdkMessage(message) {
2362
2422
  typeof message === "object" &&
2363
2423
  message.role === "user");
2364
2424
  }
2365
- function mapSdkEvent(sessionId, event, compactionId) {
2425
+ export function mapSdkEvent(sessionId, event, compactionId, step = 1) {
2366
2426
  switch (event.type) {
2367
2427
  case "agent_start":
2368
2428
  return { type: "agent_start", sessionId };
2429
+ case "turn_start":
2430
+ return { type: "step_start", sessionId, step };
2369
2431
  case "agent_end":
2370
2432
  return {
2371
2433
  type: "agent_end",
@@ -2375,15 +2437,50 @@ function mapSdkEvent(sessionId, event, compactionId) {
2375
2437
  case "message_update":
2376
2438
  if (!isAssistantSdkMessage(event.message))
2377
2439
  return undefined;
2378
- return mapAssistantMessageEvent("message_update", sessionId, event.message);
2440
+ return mapAssistantMessageEvent("message_update", sessionId, event.message, step);
2379
2441
  case "message_end":
2380
2442
  if (!isAssistantSdkMessage(event.message))
2381
2443
  return undefined;
2382
- return mapAssistantMessageEvent("message_end", sessionId, event.message);
2383
- case "turn_end":
2384
- // Usage is emitted from message_end (see prompt subscribe). turn_end
2385
- // carries the same final assistant message and would double-count.
2386
- return undefined;
2444
+ return mapAssistantMessageEvent("message_end", sessionId, event.message, step);
2445
+ case "turn_end": {
2446
+ const record = event.message && typeof event.message === "object"
2447
+ ? event.message
2448
+ : undefined;
2449
+ const reason = record?.stopReason;
2450
+ return {
2451
+ type: "step_end",
2452
+ sessionId,
2453
+ step,
2454
+ outcome: reason === "error"
2455
+ ? "failed"
2456
+ : reason === "aborted"
2457
+ ? "interrupted"
2458
+ : "completed",
2459
+ };
2460
+ }
2461
+ case "auto_retry_start":
2462
+ return {
2463
+ type: "model_retry",
2464
+ sessionId,
2465
+ step,
2466
+ attempt: event.attempt,
2467
+ maxAttempts: event.maxAttempts,
2468
+ delayMs: event.delayMs,
2469
+ errorMessage: redactRuntimeText(event.errorMessage, 600),
2470
+ state: "scheduled",
2471
+ };
2472
+ case "auto_retry_end":
2473
+ return {
2474
+ type: "model_retry",
2475
+ sessionId,
2476
+ step,
2477
+ attempt: event.attempt,
2478
+ state: event.success ? "completed" : "failed",
2479
+ success: event.success,
2480
+ ...(event.finalError
2481
+ ? { errorMessage: redactRuntimeText(event.finalError, 600) }
2482
+ : {}),
2483
+ };
2387
2484
  case "tool_execution_start":
2388
2485
  return {
2389
2486
  type: "tool_call",
@@ -2391,6 +2488,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
2391
2488
  toolCallId: event.toolCallId,
2392
2489
  toolName: event.toolName,
2393
2490
  args: event.args,
2491
+ step,
2492
+ ...(event.parentToolCallId
2493
+ ? { parentToolCallId: event.parentToolCallId }
2494
+ : {}),
2394
2495
  };
2395
2496
  case "tool_execution_end":
2396
2497
  return {
@@ -2400,6 +2501,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
2400
2501
  toolName: event.toolName,
2401
2502
  result: event.result,
2402
2503
  isError: event.isError,
2504
+ step,
2505
+ ...(event.parentToolCallId
2506
+ ? { parentToolCallId: event.parentToolCallId }
2507
+ : {}),
2403
2508
  };
2404
2509
  case "agent_settled":
2405
2510
  return { type: "agent_settled", sessionId };
@@ -2446,8 +2551,19 @@ function mapSdkEvent(sessionId, event, compactionId) {
2446
2551
  : {}),
2447
2552
  };
2448
2553
  }
2449
- default:
2554
+ case "message_start":
2555
+ case "queue_update":
2450
2556
  return undefined;
2557
+ default:
2558
+ {
2559
+ const unknownEvent = event;
2560
+ return {
2561
+ type: "unknown",
2562
+ sessionId,
2563
+ eventType: String(unknownEvent.type ?? "unknown"),
2564
+ value: unknownEvent,
2565
+ };
2566
+ }
2451
2567
  }
2452
2568
  }
2453
2569
  function newCompactionId(sessionId) {
@@ -85,15 +85,3 @@ export function parseProviderError(value) {
85
85
  category: classify({ message, raw, code }),
86
86
  };
87
87
  }
88
- export function providerErrorTitle(category) {
89
- switch (category) {
90
- case "authentication":
91
- return "认证失败";
92
- case "context-overflow":
93
- return "上下文过大";
94
- case "rate-limit":
95
- return "请求过于频繁";
96
- default:
97
- return "Provider 请求失败";
98
- }
99
- }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { lstat, open, readdir } from "node:fs/promises";
4
+ import { sameFileIdentity } from "../../../shared/file-identity.js";
4
5
  import path from "node:path";
5
6
  import { guardReadPath, isSensitivePath, scrubSecrets, } from "./explore-tools.js";
6
7
  const TREE_LIMIT = 500;
@@ -143,8 +144,7 @@ export async function readRepoPreview(repoRoot, rawPath) {
143
144
  throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
144
145
  const current = await lstat(rechecked.abs);
145
146
  if (current.isSymbolicLink() ||
146
- current.dev !== info.dev ||
147
- current.ino !== info.ino)
147
+ !sameFileIdentity(info, current))
148
148
  throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
149
149
  size = info.size;
150
150
  const readSize = Math.min(size, TEXT_PREVIEW_LIMIT);
@@ -204,8 +204,7 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
204
204
  throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
205
205
  const current = await lstat(rechecked.abs);
206
206
  if (current.isSymbolicLink() ||
207
- current.dev !== info.dev ||
208
- current.ino !== info.ino)
207
+ !sameFileIdentity(info, current))
209
208
  throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
210
209
  return {
211
210
  body: await handle.readFile(),