@tea-agent/loop-agent 0.44.0-next.4 → 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 (77) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/executors/shell-write-guard.js +26 -3
  4. package/dist/shared/file-identity.js +23 -0
  5. package/dist/shared/git-workspace-status.js +480 -0
  6. package/dist/shared/path-safety.js +2 -2
  7. package/dist/worker/console/chat/context-insights.js +723 -0
  8. package/dist/worker/console/chat/repo-browser.js +3 -4
  9. package/dist/worker/console/chat/routes.js +73 -1
  10. package/dist/worker/console/chat/shortcuts.js +7 -0
  11. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_lmJjOm.js → abnfDiagram-N423BO3Z-BHuhGM1e.js} +1 -1
  12. package/dist/worker/console/static/assets/{arc-pymM4Qeh.js → arc-CKh5E_sr.js} +1 -1
  13. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw9EPAhi.js → architectureDiagram-T3A2C74G-DizhiBEv.js} +1 -1
  14. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C7_oXJp2.js → blockDiagram-VBNYF7ZC-3Neea-Z-.js} +1 -1
  15. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-LnLWDbh8.js → c4Diagram-5PPSVZJV-BCP_Qrgi.js} +1 -1
  16. package/dist/worker/console/static/assets/channel-DxgzA1GF.js +1 -0
  17. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BBiu3sCW.js → chunk-2GRJ4B5K-C_wydftK.js} +1 -1
  18. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DiXqDQml.js → chunk-2Q5K7J3B-jRPXZbzx.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-lmNq9nYk.js → chunk-5RXB4S5H-BLQf2OYO.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-boBu74dU.js → chunk-5VM5RSS4-0qRq64rI.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CjxqoJLQ.js → chunk-6Q2QTUOP-ClMg8xmV.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DL0vQ9MG.js → chunk-GF5L2VYU-Coop2FlL.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DGPqE-O0.js → chunk-JWPE2WC7-CDdO-jPJ.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-v4BbT_r2.js → chunk-KBJHAD2P-D8Gkppjl.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Ci0ynvyL.js → chunk-RYQCIY6F-DA9HIySf.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CGsbJoTV.js → chunk-XXDRQBXY-DZNDRX1k.js} +1 -1
  27. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +1 -0
  28. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +1 -0
  29. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CzoRDEI7.js → cose-bilkent-JH36ORCC-CZ1zGyjq.js} +1 -1
  30. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BOwFwbX4.js → cynefin-VYW2F7L2-B8u1_FtM.js} +1 -1
  31. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2Cfg451l.js → cynefinDiagram-MW4NZA55-CPBB6lfo.js} +1 -1
  32. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DbT0D6Y5.js → dagre-VZM6K2ZE-CG3Eov3E.js} +1 -1
  33. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dfd_JoMs.js → diagram-7IWD3JNH-ycI-dOjh.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DaGm82kh.js → diagram-B4RE2ZJO-Bx_GyQ7Y.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DMoI8Ibc.js → diagram-LBJQPF4R-m0G6mP4d.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtIFA3aY.js → diagram-Q27KOJAE-DTDJkqcJ.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-UB23O5K3-D_2X1DTZ.js → diagram-UB23O5K3-BCfFrG-p.js} +1 -1
  38. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-yFW5ZYlZ.js → ebnfDiagram-BXEA7PRR-BIkTNPpp.js} +1 -1
  39. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DBXh5Svj.js → erDiagram-JOGREHBK-DTcMWrd2.js} +1 -1
  40. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C4ycm8KM.js → flowDiagram-UKHOOZJN-BqzDaNQ7.js} +1 -1
  41. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-qr-_Pekr.js → ganttDiagram-PKOTCBZU-BSl_vGFe.js} +1 -1
  42. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DzOSLv_A.js → gitGraphDiagram-DS77QQ5N-DCA5HXM6.js} +1 -1
  43. package/dist/worker/console/static/assets/{index-ZtyB6Ol-.css → index-BAEDjTbD.css} +1 -1
  44. package/dist/worker/console/static/assets/{index-PFjB6g6l.js → index-wKEJUaP5.js} +106 -106
  45. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C7Hl8D7l.js → infoDiagram-6WML65LV-DvqT18ge.js} +1 -1
  46. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-fBxVlr6t.js → ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js} +1 -1
  47. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CoiJEVCl.js → journeyDiagram-NVQOT4AX-BpLQPI9C.js} +1 -1
  48. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-TJXpxNuV.js → kanban-definition-27J2QSJJ-CzIz_-Lt.js} +1 -1
  49. package/dist/worker/console/static/assets/{linear-CjQWz1pG.js → linear-CMQ8Jpp-.js} +1 -1
  50. package/dist/worker/console/static/assets/{mermaid.core-DY9_apIE.js → mermaid.core-BdAMrTf7.js} +5 -5
  51. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-HB0Id8gM.js → mindmap-definition-FAOFIHXS-hSwF6sOq.js} +1 -1
  52. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-0OSWgr6v.js → pegDiagram-VL7TDLO6-DZdv509B.js} +1 -1
  53. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Cei1GGC2.js → pieDiagram-7S7Q4E2Y-CmD4-b0K.js} +1 -1
  54. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CDy0Adfu.js → quadrantDiagram-CIZ2JOQS-DtKxNxlS.js} +1 -1
  55. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwqzUWwN.js → railroadDiagram-AXF67PYL-D48iIoiS.js} +1 -1
  56. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bif2IBRL.js → requirementDiagram-LRYGKXZP-C3N0K-2p.js} +1 -1
  57. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CYIoNMLg.js → sankeyDiagram-W5VNT64P-CACWR-Xs.js} +1 -1
  58. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BDS7Y0aQ.js → sequenceDiagram-SI44F4Z6-g_QgugvZ.js} +1 -1
  59. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-lZic-_Bp.js → sizeCapture-X5ZJPWSS-DqLoRPGK.js} +1 -1
  60. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-WCVlCqSd.js → stateDiagram-OKZ733FA-ES59VgMG.js} +1 -1
  61. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +1 -0
  62. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-pGccxTw_.js → swimlanes-SLNWSIFB-fjyLVNWo.js} +2 -2
  63. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +8 -0
  64. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-D29RlrQA.js → timeline-definition-Z64GVDOM-Cd5-biKv.js} +1 -1
  65. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-01fEM1NU.js → vennDiagram-T6HMQDX7-CZTu6mPf.js} +1 -1
  66. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C4GeB6dq.js → wardleyDiagram-T6FBY63Y-DR01jjbe.js} +1 -1
  67. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BXHrXHps.js → xychartDiagram-ELKLHX3M-iZcuGVGa.js} +1 -1
  68. package/dist/worker/console/static/index.html +2 -2
  69. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  70. package/dist/worker/observe/node-context-insights.js +479 -0
  71. package/docs/README.md +1 -1
  72. package/package.json +1 -1
  73. package/dist/worker/console/static/assets/channel-Ym13M3Lb.js +0 -1
  74. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeFR1wGo.js +0 -1
  75. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeFR1wGo.js +0 -1
  76. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-cj5UUB6m.js +0 -1
  77. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-C_Qp6bnh.js +0 -8
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Read-only context insights projection for a DAG node attempt — the
3
+ * loop-agent counterpart of the dsh-context dashboard, built exclusively from
4
+ * recorded run facts:
5
+ *
6
+ * - context receipt (`<node>/context-receipts/attempt-N.json`) → composition
7
+ * - session events (`<node>/session-events.jsonl`) → per-request usage
8
+ * trend, tool activity, file footprint, timing
9
+ * - node record (`<node>.json`) → skill injection footprint, headline totals
10
+ *
11
+ * Everything is derived deterministically from persisted facts; nothing is
12
+ * estimated beyond what the recorded bytes already contain. All projections are
13
+ * read-only, bounded, and redaction-preserving (paths already pass through
14
+ * redactSecrets in node-transparency before reaching here for receipt data;
15
+ * file-activity paths are redacted in this module).
16
+ */
17
+ import { readFile } from "node:fs/promises";
18
+ import path from "node:path";
19
+ import { redactSecrets } from "../../shared/preview.js";
20
+ import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
21
+ import { resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
22
+ export const NODE_CONTEXT_INSIGHTS_SCHEMA_VERSION = 1;
23
+ /** Upper bound on session-event lines scanned per attempt (defensive). */
24
+ const SESSION_EVENT_LINE_LIMIT = 50_000;
25
+ function isRecord(value) {
26
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ function asInt(value) {
29
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
30
+ ? value
31
+ : null;
32
+ }
33
+ function asString(value) {
34
+ return typeof value === "string" && value.length > 0 ? value : null;
35
+ }
36
+ /** Minimal per-line JSON parse (session events are JSONL). */
37
+ function parseSessionEventLine(line) {
38
+ try {
39
+ const parsed = JSON.parse(line);
40
+ return isRecord(parsed) ? parsed : null;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function messageOf(event) {
47
+ const message = event.message;
48
+ return isRecord(message) ? message : null;
49
+ }
50
+ function contentBlocksOf(message) {
51
+ const content = message?.content;
52
+ return Array.isArray(content) ? content : [];
53
+ }
54
+ function usageOf(message) {
55
+ const usage = message?.usage;
56
+ return isRecord(usage) ? usage : null;
57
+ }
58
+ function toolNameOfArgs(args) {
59
+ for (const key of ["path", "file", "file_path", "filePath"]) {
60
+ const value = args[key];
61
+ if (typeof value === "string" && value.length > 0)
62
+ return value;
63
+ }
64
+ return null;
65
+ }
66
+ function searchTargetsOfArgs(args) {
67
+ const targets = [];
68
+ const push = (value) => {
69
+ if (typeof value === "string" && value.length > 0)
70
+ targets.push(value);
71
+ };
72
+ push(args.path);
73
+ push(args.directory);
74
+ push(args.dir);
75
+ const pattern = asString(args.pattern) ?? asString(args.query);
76
+ if (pattern)
77
+ targets.push(`pattern:${pattern.slice(0, 120)}`);
78
+ return targets;
79
+ }
80
+ /** Extract referenced file paths from a bash command string (best effort). */
81
+ function filesFromCommand(command) {
82
+ const files = [];
83
+ const patterns = [
84
+ /(?:^|\s)(?:cat|head|tail|less|more)\s+([^\s;&|]+)/g,
85
+ /(?:^|\s)(?:rg|grep|find|fd|ls)\s+.*?([^\s;&|]*\.[A-Za-z0-9]{1,8})/g,
86
+ /(?:^|\s)(?:sed|awk|sort|uniq|wc)\s+.*?([^\s;&|]*\.[A-Za-z0-9]{1,8})/g,
87
+ ];
88
+ for (const pattern of patterns) {
89
+ let match;
90
+ while ((match = pattern.exec(command)) !== null) {
91
+ const candidate = match[1];
92
+ if (candidate && candidate.length > 1)
93
+ files.push(candidate);
94
+ }
95
+ }
96
+ return files;
97
+ }
98
+ function classifyFileOp(toolName) {
99
+ const name = toolName.toLowerCase();
100
+ if (["read", "read_file", "readfile", "view"].includes(name))
101
+ return "read";
102
+ if (["write", "write_file", "writefile", "create"].includes(name))
103
+ return "write";
104
+ if (["edit", "str_replace", "str_replace_editor", "apply_patch"].includes(name))
105
+ return "edit";
106
+ if (["grep", "rg", "find", "fd", "search", "glob", "list", "ls"].includes(name))
107
+ return "search";
108
+ return null;
109
+ }
110
+ function normalizeRepoPath(raw, repoRoot) {
111
+ let value = raw;
112
+ const resolvedRoot = path.resolve(repoRoot);
113
+ const rootVariants = [
114
+ resolvedRoot,
115
+ resolvedRoot.replace(/\\/g, "/"),
116
+ resolvedRoot.replace(/\\/g, "/").toLowerCase(),
117
+ ];
118
+ for (const variant of rootVariants) {
119
+ if (value.toLowerCase().startsWith(`${variant.toLowerCase()}\\`) ||
120
+ value.toLowerCase().startsWith(`${variant.toLowerCase()}/`)) {
121
+ value = value.slice(variant.length + 1);
122
+ break;
123
+ }
124
+ if (value.toLowerCase() === variant.toLowerCase()) {
125
+ value = ".";
126
+ break;
127
+ }
128
+ }
129
+ return value.replace(/\\/g, "/");
130
+ }
131
+ function accumulateFileActivity(fileActivity, key, op) {
132
+ const existing = fileActivity.get(key) ?? {
133
+ path: key,
134
+ reads: 0,
135
+ writes: 0,
136
+ edits: 0,
137
+ searches: 0,
138
+ };
139
+ if (op === "read")
140
+ existing.reads += 1;
141
+ else if (op === "write")
142
+ existing.writes += 1;
143
+ else if (op === "edit")
144
+ existing.edits += 1;
145
+ else
146
+ existing.searches += 1;
147
+ fileActivity.set(key, existing);
148
+ }
149
+ /**
150
+ * Pure projection over parsed session events. Exported for unit tests.
151
+ */
152
+ export function projectSessionInsights(events, repoRoot) {
153
+ let requestCount = 0;
154
+ let toolCalls = 0;
155
+ let toolErrors = 0;
156
+ let humanInputs = 0;
157
+ let thinkingSteps = 0;
158
+ let inputTokens = 0;
159
+ let outputTokens = 0;
160
+ let cacheReadTokens = 0;
161
+ let cacheWriteTokens = 0;
162
+ let cacheSamples = 0;
163
+ let cacheHits = 0;
164
+ const requests = [];
165
+ const toolCounters = new Map();
166
+ const fileActivity = new Map();
167
+ // Tool calls issued in the current in-flight model request (reset per
168
+ // assistant message_end) so each request row attributes its own calls.
169
+ let pendingToolCalls = 0;
170
+ let pendingThinking = false;
171
+ for (const event of events) {
172
+ if (!event)
173
+ continue;
174
+ const type = asString(event.type);
175
+ if (type === "tool_execution_start") {
176
+ toolCalls += 1;
177
+ pendingToolCalls += 1;
178
+ const toolName = asString(event.toolName) ?? "unknown";
179
+ const row = toolCounters.get(toolName) ?? {
180
+ toolName,
181
+ calls: 0,
182
+ errors: 0,
183
+ };
184
+ row.calls += 1;
185
+ toolCounters.set(toolName, row);
186
+ const args = isRecord(event.args) ? event.args : {};
187
+ const op = classifyFileOp(toolName);
188
+ if (op === "read" || op === "write" || op === "edit") {
189
+ const target = toolNameOfArgs(args);
190
+ if (target) {
191
+ accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(target), repoRoot), op);
192
+ }
193
+ }
194
+ else if (op === "search") {
195
+ for (const target of searchTargetsOfArgs(args)) {
196
+ accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(target), repoRoot), "search");
197
+ }
198
+ }
199
+ else if (toolName.toLowerCase() === "bash") {
200
+ const command = asString(args.command) ?? "";
201
+ for (const file of filesFromCommand(command)) {
202
+ accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(file), repoRoot), "search");
203
+ }
204
+ }
205
+ continue;
206
+ }
207
+ if (type === "tool_execution_end") {
208
+ if (event.isError === true)
209
+ toolErrors += 1;
210
+ continue;
211
+ }
212
+ const message = messageOf(event);
213
+ if (!message)
214
+ continue;
215
+ const role = asString(message.role);
216
+ if (type === "message_start" && role === "assistant") {
217
+ // A new model request begins.
218
+ requestCount += 1;
219
+ pendingToolCalls = 0;
220
+ pendingThinking = false;
221
+ continue;
222
+ }
223
+ if (type === "message_end" && role === "assistant") {
224
+ for (const block of contentBlocksOf(message)) {
225
+ if (isRecord(block) && asString(block.type) === "thinking") {
226
+ thinkingSteps += 1;
227
+ pendingThinking = true;
228
+ }
229
+ }
230
+ const usage = usageOf(message);
231
+ const input = asInt(usage?.input);
232
+ const output = asInt(usage?.output);
233
+ const cacheRead = asInt(usage?.cacheRead);
234
+ const cacheWrite = asInt(usage?.cacheWrite);
235
+ if (usage && (input ?? 0) + (cacheRead ?? 0) > 0) {
236
+ cacheSamples += 1;
237
+ if ((cacheRead ?? 0) > 0)
238
+ cacheHits += 1;
239
+ }
240
+ if (input != null)
241
+ inputTokens += input;
242
+ if (output != null)
243
+ outputTokens += output;
244
+ if (cacheRead != null)
245
+ cacheReadTokens += cacheRead;
246
+ if (cacheWrite != null)
247
+ cacheWriteTokens += cacheWrite;
248
+ requests.push({
249
+ request: requestCount,
250
+ recordedAt: asString(event.recordedAt),
251
+ model: asString(message.model),
252
+ provider: asString(message.provider),
253
+ usage: usage == null
254
+ ? null
255
+ : {
256
+ input,
257
+ output,
258
+ cacheRead,
259
+ cacheWrite,
260
+ totalTokens: asInt(usage.totalTokens),
261
+ },
262
+ stopReason: asString(message.stopReason),
263
+ toolCalls: pendingToolCalls,
264
+ thinking: pendingThinking,
265
+ });
266
+ pendingToolCalls = 0;
267
+ pendingThinking = false;
268
+ continue;
269
+ }
270
+ if (type === "message_end" && role === "user") {
271
+ humanInputs += 1;
272
+ }
273
+ }
274
+ const toolActivity = [...toolCounters.values()].sort((a, b) => b.calls - a.calls || a.toolName.localeCompare(b.toolName));
275
+ const fileActivityRows = [...fileActivity.values()].sort((a, b) => b.reads + b.writes + b.edits + b.searches -
276
+ (a.reads + a.writes + a.edits + a.searches) ||
277
+ a.path.localeCompare(b.path));
278
+ return {
279
+ requests,
280
+ toolActivity,
281
+ fileActivity: fileActivityRows,
282
+ stats: {
283
+ requests: requestCount,
284
+ toolCalls,
285
+ toolErrors,
286
+ humanInputs,
287
+ thinkingSteps,
288
+ cacheHitRate: cacheSamples > 0 ? cacheHits / cacheSamples : null,
289
+ inputTokens,
290
+ outputTokens,
291
+ cacheReadTokens,
292
+ cacheWriteTokens,
293
+ },
294
+ };
295
+ }
296
+ export async function loadDagNodeContextInsights(repoRoot, dagRunId, nodeId, attempt) {
297
+ if (!isSafeObservabilityIdentifier(dagRunId)) {
298
+ return {
299
+ status: 400,
300
+ body: { error: "Invalid DAG run identifier", reason: "invalid-id" },
301
+ };
302
+ }
303
+ if (!isSafeObservabilityIdentifier(nodeId)) {
304
+ return {
305
+ status: 400,
306
+ body: { error: "Invalid node identifier", reason: "invalid-id" },
307
+ };
308
+ }
309
+ const resolvedRun = resolveDagRunJson(repoRoot, dagRunId);
310
+ if (!resolvedRun.ok) {
311
+ return {
312
+ status: resolvedRun.reason === "invalid-id" ? 400 : 404,
313
+ body: { error: "DAG run not found", reason: resolvedRun.reason },
314
+ };
315
+ }
316
+ // Node record (<nodeId>.json) carries attempt metadata + headline totals.
317
+ const recordResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
318
+ `${nodeId}.json`,
319
+ ]);
320
+ let nodeRecord = null;
321
+ if (recordResolved.ok) {
322
+ try {
323
+ const raw = JSON.parse(await readFile(recordResolved.result.absolutePath, "utf8"));
324
+ if (isRecord(raw))
325
+ nodeRecord = raw;
326
+ }
327
+ catch {
328
+ nodeRecord = null;
329
+ }
330
+ }
331
+ const selectedAttempt = normalizeAttemptNumber(attempt, nodeRecord);
332
+ // Context receipt composition.
333
+ const receiptResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
334
+ nodeId,
335
+ "context-receipts",
336
+ `attempt-${selectedAttempt}.json`,
337
+ ]);
338
+ let composition = null;
339
+ if (receiptResolved.ok) {
340
+ try {
341
+ const raw = JSON.parse(await readFile(receiptResolved.result.absolutePath, "utf8"));
342
+ if (isRecord(raw) && Array.isArray(raw.orderedComponents)) {
343
+ const byKind = new Map();
344
+ for (const entry of raw.orderedComponents) {
345
+ if (!isRecord(entry))
346
+ continue;
347
+ const kind = asString(entry.kind);
348
+ const bytes = asInt(entry.bytes) ?? 0;
349
+ if (!kind)
350
+ continue;
351
+ const slice = byKind.get(kind) ?? { kind, items: 0, bytes: 0 };
352
+ slice.items += 1;
353
+ slice.bytes += bytes;
354
+ byKind.set(kind, slice);
355
+ }
356
+ composition = [...byKind.values()].sort((a, b) => b.bytes - a.bytes);
357
+ }
358
+ }
359
+ catch {
360
+ composition = null;
361
+ }
362
+ }
363
+ // Session events for the node (single file across attempts in v1 format).
364
+ const sessionResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
365
+ nodeId,
366
+ "session-events.jsonl",
367
+ ]);
368
+ let sessionProjection = null;
369
+ if (sessionResolved.ok) {
370
+ try {
371
+ const raw = await readFile(sessionResolved.result.absolutePath, "utf8");
372
+ const lines = raw.split("\n").slice(0, SESSION_EVENT_LINE_LIMIT);
373
+ const events = lines
374
+ .map((line) => line.trim())
375
+ .filter(Boolean)
376
+ .map(parseSessionEventLine);
377
+ sessionProjection = projectSessionInsights(events, repoRoot);
378
+ }
379
+ catch {
380
+ sessionProjection = null;
381
+ }
382
+ }
383
+ const attemptRecord = findAttemptRecord(nodeRecord, selectedAttempt);
384
+ const timing = timingFromRecords(nodeRecord, attemptRecord);
385
+ const skills = skillInjectionsOf(nodeRecord);
386
+ const body = {
387
+ schemaVersion: NODE_CONTEXT_INSIGHTS_SCHEMA_VERSION,
388
+ dagRunId,
389
+ nodeId,
390
+ attempt: selectedAttempt,
391
+ generatedFrom: {
392
+ sessionEvents: sessionProjection != null,
393
+ contextReceipt: composition != null,
394
+ nodeRecord: nodeRecord != null,
395
+ },
396
+ stats: {
397
+ requests: sessionProjection?.stats.requests ?? 0,
398
+ toolCalls: sessionProjection?.stats.toolCalls ?? 0,
399
+ toolErrors: sessionProjection?.stats.toolErrors ?? 0,
400
+ humanInputs: sessionProjection?.stats.humanInputs ?? 0,
401
+ thinkingSteps: sessionProjection?.stats.thinkingSteps ?? 0,
402
+ tokensUsed: asInt(attemptRecord?.tokensUsed) ?? asInt(nodeRecord?.tokensUsed),
403
+ parsedEvents: asInt(attemptRecord?.parsedEvents) ?? asInt(nodeRecord?.parsedEvents),
404
+ writeToolCallCount: asInt(attemptRecord?.writeToolCallCount) ??
405
+ asInt(nodeRecord?.writeToolCallCount),
406
+ cacheHitRate: sessionProjection?.stats.cacheHitRate ?? null,
407
+ inputTokens: sessionProjection?.stats.inputTokens ?? null,
408
+ outputTokens: sessionProjection?.stats.outputTokens ?? null,
409
+ cacheReadTokens: sessionProjection?.stats.cacheReadTokens ?? null,
410
+ cacheWriteTokens: sessionProjection?.stats.cacheWriteTokens ?? null,
411
+ },
412
+ timing,
413
+ composition,
414
+ skills,
415
+ toolActivity: sessionProjection?.toolActivity ?? [],
416
+ fileActivity: sessionProjection?.fileActivity ?? [],
417
+ usageTrend: sessionProjection?.requests ?? [],
418
+ };
419
+ return { status: 200, body };
420
+ }
421
+ function normalizeAttemptNumber(rawAttempt, nodeRecord) {
422
+ if (Number.isSafeInteger(rawAttempt) && (rawAttempt ?? 0) > 0) {
423
+ return rawAttempt;
424
+ }
425
+ if (typeof nodeRecord?.currentAttempt === "number" &&
426
+ Number.isSafeInteger(nodeRecord.currentAttempt) &&
427
+ nodeRecord.currentAttempt > 0) {
428
+ return nodeRecord.currentAttempt;
429
+ }
430
+ return 1;
431
+ }
432
+ function findAttemptRecord(nodeRecord, attempt) {
433
+ if (!nodeRecord)
434
+ return null;
435
+ const attempts = nodeRecord.attempts;
436
+ if (!Array.isArray(attempts))
437
+ return null;
438
+ for (const entry of attempts) {
439
+ if (isRecord(entry) && entry.attempt === attempt)
440
+ return entry;
441
+ }
442
+ return null;
443
+ }
444
+ function timingFromRecords(nodeRecord, attemptRecord) {
445
+ const attemptTiming = isRecord(attemptRecord?.timing)
446
+ ? attemptRecord.timing
447
+ : null;
448
+ const nodeTiming = isRecord(nodeRecord?.timing)
449
+ ? nodeRecord.timing
450
+ : null;
451
+ return {
452
+ startedAt: asString(attemptRecord?.startedAt) ?? asString(nodeRecord?.startedAt),
453
+ finishedAt: asString(attemptRecord?.finishedAt) ?? asString(nodeRecord?.finishedAt),
454
+ durationMs: asInt(attemptRecord?.durationMs) ?? asInt(nodeRecord?.durationMs),
455
+ executorDurationMs: asInt(attemptTiming?.executorDurationMs) ??
456
+ asInt(nodeTiming?.executorDurationMs),
457
+ };
458
+ }
459
+ function skillInjectionsOf(nodeRecord) {
460
+ const raw = nodeRecord?.resolvedSkills;
461
+ if (!Array.isArray(raw))
462
+ return [];
463
+ const skills = [];
464
+ for (const entry of raw) {
465
+ if (!isRecord(entry))
466
+ continue;
467
+ const name = asString(entry.name);
468
+ if (!name)
469
+ continue;
470
+ skills.push({
471
+ name,
472
+ resolution: asString(entry.resolution) ?? "unknown",
473
+ bytes: asInt(entry.bytes),
474
+ truncated: entry.truncated === true,
475
+ injectionMode: asString(entry.injectionMode),
476
+ });
477
+ }
478
+ return skills;
479
+ }
package/docs/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `docs/` 是 loop-agent 源仓的治理文档根目录。新读者不需要先浏览所有文件,先按任务选择入口,再由目录 README 或 `docs/document-catalog.json` 判断文档身份。
4
4
 
5
- **维护日:2026-08-16** — 对齐 `@tea-agent/loop-agent@0.35.3`。版本事实以根 `CHANGELOG.md` 与 `package.json` 为准;能力短摘要见 `reports/current-capability-summary.md`。
5
+ **维护日:2026-09-16** — 对齐 `@tea-agent/loop-agent@0.41.1-beta.0`。版本事实以根 `CHANGELOG.md` 与 `package.json` 为准;能力短摘要见 `reports/current-capability-summary.md`。
6
6
 
7
7
  ## 我现在要做什么?
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.44.0-next.4",
3
+ "version": "0.44.0-next.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-DY9_apIE.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DL0vQ9MG.js";import{_ as i}from"./mermaid.core-DY9_apIE.js";import"./chunk-5VM5RSS4-boBu74dU.js";import"./chunk-XXDRQBXY-CGsbJoTV.js";import"./chunk-KBJHAD2P-v4BbT_r2.js";import"./chunk-2GRJ4B5K-BBiu3sCW.js";import"./index-PFjB6g6l.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DL0vQ9MG.js";import{_ as i}from"./mermaid.core-DY9_apIE.js";import"./chunk-5VM5RSS4-boBu74dU.js";import"./chunk-XXDRQBXY-CGsbJoTV.js";import"./chunk-KBJHAD2P-v4BbT_r2.js";import"./chunk-2GRJ4B5K-BBiu3sCW.js";import"./index-PFjB6g6l.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-lmNq9nYk.js";import{_ as i}from"./mermaid.core-DY9_apIE.js";import"./chunk-XXDRQBXY-CGsbJoTV.js";import"./chunk-KBJHAD2P-v4BbT_r2.js";import"./chunk-2GRJ4B5K-BBiu3sCW.js";import"./index-PFjB6g6l.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-UKHOOZJN-C4ycm8KM.js";import{_ as a}from"./mermaid.core-DY9_apIE.js";import"./chunk-5VM5RSS4-boBu74dU.js";import"./chunk-XXDRQBXY-CGsbJoTV.js";import"./chunk-KBJHAD2P-v4BbT_r2.js";import"./chunk-2GRJ4B5K-BBiu3sCW.js";import"./channel-Ym13M3Lb.js";import"./index-PFjB6g6l.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};