agentel 0.2.6 → 0.3.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.
@@ -1,15 +1,19 @@
1
1
  "use strict";
2
2
 
3
3
  const crypto = require("crypto");
4
+ const { normalizeStructuredPatch, structuredPatchFromDiffText, structuredPatchFromToolArguments } = require("./diffs");
4
5
  const { parserVersionForSource } = require("./parser-versions");
5
6
 
6
- const CANONICAL_EVENT_SCHEMA_VERSION = "agentlog.events.v2";
7
+ const CANONICAL_EVENT_SCHEMA_VERSION = "agentlog.events.v5";
7
8
 
8
9
  const EVENT_KINDS = {
9
10
  SESSION_STARTED: "session.started",
10
11
  PROMPT_SUBMITTED: "prompt.submitted",
11
12
  TOOL_CALLED: "tool.called",
12
13
  TOOL_COMPLETED: "tool.completed",
14
+ MEMORY_READ: "memory.read",
15
+ MEMORY_WRITE: "memory.write",
16
+ MEMORY_LOADED: "memory.loaded",
13
17
  RESPONSE_GENERATED: "response.generated"
14
18
  };
15
19
 
@@ -95,7 +99,15 @@ function normalizeSessionEvents(session, messages, options = {}) {
95
99
  const parserVersion = options.parserVersion ?? session.parserVersion ?? parserVersionForSource(session.sourceType);
96
100
  const state = {
97
101
  toolCallEventIdsByKey: new Map(),
98
- unmatchedToolCallEventIds: []
102
+ // Ordered list of tool-call event ids still awaiting a result, plus a Set
103
+ // for O(1) membership and a head pointer for the FIFO fallback. Consumed
104
+ // ids are tombstoned (left in the array, removed from the set) so we never
105
+ // pay O(n) splice/indexOf per result — important for sessions with tens of
106
+ // thousands of tool calls.
107
+ unmatchedToolCallEventIds: [],
108
+ unmatchedToolCallEventIdSet: new Set(),
109
+ unmatchedHead: 0,
110
+ memoryByToolCallEventId: new Map()
99
111
  };
100
112
  const events = [
101
113
  baseEvent(session, {
@@ -125,7 +137,7 @@ function normalizeSessionEvents(session, messages, options = {}) {
125
137
 
126
138
  function messageToCanonicalEvents(message, session, options = {}) {
127
139
  const role = String(message?.role || "").toLowerCase();
128
- if (!message || role === "system" || role === "developer") return [];
140
+ if (!message) return [];
129
141
  const messageIndex = Number.isFinite(Number(message.index)) ? Number(message.index) : options.messageIndex || 0;
130
142
  const parserVersion = options.parserVersion ?? session.parserVersion ?? parserVersionForSource(session.sourceType);
131
143
  const occurredAt = message.timestamp || session.startedAt || new Date().toISOString();
@@ -133,7 +145,26 @@ function messageToCanonicalEvents(message, session, options = {}) {
133
145
  const metadata = message.metadata || {};
134
146
  const events = [];
135
147
 
148
+ if (role === "system" || role === "developer") {
149
+ const memory = memoryActivityFromSystemMessage(message);
150
+ return memory
151
+ ? [
152
+ memoryCanonicalEvent(session, {
153
+ messageIndex,
154
+ ordinal: 0,
155
+ occurredAt,
156
+ parserVersion,
157
+ role,
158
+ memory,
159
+ text: memory.contentPreview || memoryEventSummary(memory),
160
+ status: "loaded"
161
+ })
162
+ ]
163
+ : [];
164
+ }
165
+
136
166
  if (role === "user") {
167
+ const skills = skillInvocationsFromPromptText(content);
137
168
  events.push(
138
169
  baseEvent(session, {
139
170
  messageIndex,
@@ -145,7 +176,8 @@ function messageToCanonicalEvents(message, session, options = {}) {
145
176
  indexed: {
146
177
  title: "User prompt",
147
178
  summary: summarize(content),
148
- status: "submitted"
179
+ status: "submitted",
180
+ ...(skills.length ? { skills } : {})
149
181
  },
150
182
  body: { text: content, toolCall: null, toolResult: null }
151
183
  })
@@ -178,6 +210,8 @@ function messageToCanonicalEvents(message, session, options = {}) {
178
210
  const toolCall = normalizeToolCall(toolCalls[index], metadata.provider || session.provider);
179
211
  if (!toolCall) continue;
180
212
  const rendered = renderToolCallText(toolCall);
213
+ const toolSkills = skillNamesFromToolCall(toolCall);
214
+ const memory = memoryActivityFromToolCall(toolCall);
181
215
  const event = baseEvent(session, {
182
216
  messageIndex,
183
217
  ordinal: index + 1,
@@ -193,42 +227,296 @@ function messageToCanonicalEvents(message, session, options = {}) {
193
227
  toolCategory: toolCall.category || "",
194
228
  toolIcon: toolCall.icon || "",
195
229
  target: toolCall.target || "",
196
- status: toolCall.status || "tool_call"
230
+ status: toolCall.status || "tool_call",
231
+ ...(toolSkills.length ? { skills: toolSkills } : {})
197
232
  },
198
233
  body: { text: rendered, toolCall, toolResult: null }
199
234
  });
200
235
  rememberToolCallEvent(options.state, toolCall, event.eventId);
236
+ if (memory && options.state?.memoryByToolCallEventId) options.state.memoryByToolCallEventId.set(event.eventId, memory);
201
237
  events.push(event);
202
238
  }
203
239
 
204
240
  const toolResult = metadata.toolResult ? normalizeToolResult(metadata.toolResult, metadata.provider || session.provider) : role === "tool" ? normalizeToolResult(content, metadata.provider || session.provider) : null;
205
241
  if (toolResult) {
206
242
  const parentEventId = consumeToolCallEventId(options.state, toolResult);
207
- events.push(
208
- baseEvent(session, {
209
- messageIndex,
210
- ordinal: events.length + 1,
211
- kind: EVENT_KINDS.TOOL_COMPLETED,
212
- occurredAt,
213
- parserVersion,
214
- role: "tool",
215
- indexed: {
216
- title: toolResult.title || toolResult.kind || "Tool result",
217
- summary: summarize(toolResult.summary || toolResult.output || content),
218
- toolName: toolResult.name || toolResult.kind || "",
219
- toolCategory: toolResult.category || "",
220
- toolIcon: toolResult.icon || "",
221
- status: toolResult.status || "completed"
222
- },
223
- parentEventId,
224
- body: { text: toolResult.output || content, toolCall: null, toolResult }
225
- })
226
- );
243
+ const completedEvent = baseEvent(session, {
244
+ messageIndex,
245
+ ordinal: events.length + 1,
246
+ kind: EVENT_KINDS.TOOL_COMPLETED,
247
+ occurredAt,
248
+ parserVersion,
249
+ role: "tool",
250
+ indexed: {
251
+ title: toolResult.title || toolResult.kind || "Tool result",
252
+ summary: summarize(toolResult.summary || toolResult.output || content),
253
+ toolName: toolResult.name || toolResult.kind || "",
254
+ toolCategory: toolResult.category || "",
255
+ toolIcon: toolResult.icon || "",
256
+ status: toolResult.status || "completed"
257
+ },
258
+ parentEventId,
259
+ body: { text: toolResult.output || content, toolCall: null, toolResult }
260
+ });
261
+ events.push(completedEvent);
262
+ const memory = parentEventId ? options.state?.memoryByToolCallEventId?.get(parentEventId) : null;
263
+ if (memory && isSuccessfulToolResult(toolResult)) {
264
+ events.push(
265
+ memoryCanonicalEvent(session, {
266
+ messageIndex,
267
+ ordinal: events.length + 1,
268
+ occurredAt,
269
+ parserVersion,
270
+ role: "tool",
271
+ parentEventId: completedEvent.eventId,
272
+ memory,
273
+ text: memoryEventSummary(memory),
274
+ status: "completed"
275
+ })
276
+ );
277
+ }
227
278
  }
228
279
 
229
280
  return events;
230
281
  }
231
282
 
283
+ function memoryCanonicalEvent(session, options = {}) {
284
+ const memory = options.memory || {};
285
+ const action = String(memory.action || "loaded").toLowerCase();
286
+ const kind = action === "read" ? EVENT_KINDS.MEMORY_READ : action === "write" ? EVENT_KINDS.MEMORY_WRITE : EVENT_KINDS.MEMORY_LOADED;
287
+ return baseEvent(session, {
288
+ messageIndex: options.messageIndex,
289
+ ordinal: options.ordinal,
290
+ kind,
291
+ occurredAt: options.occurredAt,
292
+ parserVersion: options.parserVersion,
293
+ role: options.role || "system",
294
+ parentEventId: options.parentEventId || "",
295
+ indexed: {
296
+ title: memoryEventTitle(memory),
297
+ summary: summarize(options.text || memoryEventSummary(memory)),
298
+ status: options.status || "completed",
299
+ memoryAction: action,
300
+ memoryPath: memory.path || "",
301
+ memorySource: memory.source || "",
302
+ memoryScope: memory.scope || ""
303
+ },
304
+ body: {
305
+ text: options.text || memoryEventSummary(memory),
306
+ toolCall: null,
307
+ toolResult: null,
308
+ memory
309
+ }
310
+ });
311
+ }
312
+
313
+ // Skill invocations appear in prompt text as Codex-style markdown markers
314
+ // ("[$name](/path/to/SKILL.md)") or Claude-style command tags
315
+ // ("<command-name>/recall</command-name>"). Names are normalized without the
316
+ // "$"/"/" decorations so telemetry can aggregate across providers.
317
+ function skillInvocationsFromPromptText(text) {
318
+ const value = String(text || "");
319
+ const names = new Set();
320
+ const markerPattern = /\[\$([^\]\n]+)\]\(([^)\n]*)\)/g;
321
+ let match;
322
+ while ((match = markerPattern.exec(value))) {
323
+ const name = normalizeSkillName(match[1]);
324
+ if (name) names.add(name);
325
+ }
326
+ const commandPattern = /<command-name>([^<\n]+)<\/command-name>/g;
327
+ while ((match = commandPattern.exec(value))) {
328
+ const name = normalizeSkillName(match[1]);
329
+ if (name) names.add(name);
330
+ }
331
+ return [...names];
332
+ }
333
+
334
+ function skillNamesFromToolCall(toolCall) {
335
+ if (!toolCall || toolCall.category !== "skill") return [];
336
+ const args = toolCall.arguments && typeof toolCall.arguments === "object" ? toolCall.arguments : {};
337
+ const candidates = [args.skill, args.skill_name, args.skillName, args.name, args.command, toolCall.target];
338
+ for (const candidate of candidates) {
339
+ const name = normalizeSkillName(candidate);
340
+ if (name) return [name];
341
+ }
342
+ return [];
343
+ }
344
+
345
+ function normalizeSkillName(value) {
346
+ const text = String(value || "").trim().replace(/^[$/]+/, "").trim();
347
+ if (!text || text.length > 120 || /\s{2,}/.test(text)) return "";
348
+ return text;
349
+ }
350
+
351
+ const MEMORY_PATH_BASES = new Set([
352
+ "agents.md",
353
+ "claude.md",
354
+ "gemini.md",
355
+ "memory.md",
356
+ ".cursorrules",
357
+ ".windsurfrules",
358
+ "rules.md"
359
+ ]);
360
+
361
+ const READ_TOOL_NAMES = new Set(["read", "notebookread", "cat", "sed", "view", "open"]);
362
+ const WRITE_TOOL_NAMES = new Set(["write", "edit", "multiedit", "notebookedit", "apply_patch", "patch", "replace", "update_file"]);
363
+
364
+ function memoryActivityFromToolCall(toolCall) {
365
+ const info = memoryInfoFromToolCall(toolCall);
366
+ if (!info) return null;
367
+ const action = memoryToolAction(toolCall);
368
+ if (!action) return null;
369
+ return { action, ...info };
370
+ }
371
+
372
+ function memoryActivityFromSystemMessage(message) {
373
+ const metadata = message?.metadata || {};
374
+ const contextKind = String(metadata.contextKind || "").toLowerCase();
375
+ if (contextKind === "remote_control_nested_memory") {
376
+ const attachment = metadata.attachment && typeof metadata.attachment === "object" ? metadata.attachment : {};
377
+ const rawPath = firstString(attachment.path, attachment.contentPath, attachment.displayPath);
378
+ const info = memoryPathInfo(rawPath) || genericMemoryInfo(rawPath || "memory", "remote-control-memory", "remote");
379
+ return {
380
+ action: "load",
381
+ ...info,
382
+ contentPreview: firstString(attachment.contentPreview)
383
+ };
384
+ }
385
+ if (contextKind === "project_instructions") {
386
+ const path = projectInstructionsPathFromContent(message.content);
387
+ const info = memoryPathInfo(path);
388
+ if (info) return { action: "load", ...info, contentPreview: summarize(message.content, 240) };
389
+ }
390
+ return null;
391
+ }
392
+
393
+ function memoryInfoFromToolCall(toolCall) {
394
+ for (const candidate of memoryPathCandidates(toolCall)) {
395
+ const info = memoryPathInfo(candidate);
396
+ if (info) return info;
397
+ }
398
+ return null;
399
+ }
400
+
401
+ function memoryToolAction(toolCall) {
402
+ const name = normalizeToolKey(toolCall?.name || toolCall?.displayName || toolCall?.title);
403
+ const category = normalizeToolKey(toolCall?.category || toolCall?.rawCategory);
404
+ if (category === "read" || READ_TOOL_NAMES.has(name)) return "read";
405
+ if (category === "edit" || WRITE_TOOL_NAMES.has(name)) return "write";
406
+ return "";
407
+ }
408
+
409
+ function memoryPathCandidates(toolCall) {
410
+ const out = [];
411
+ appendMemoryPathCandidate(out, toolCall?.target);
412
+ appendMemoryPathCandidate(out, toolCall?.argument);
413
+ appendMemoryPathCandidate(out, toolCall?.rawInputSummary);
414
+ appendMemoryPathCandidate(out, toolCall?.inputPreview);
415
+ collectMemoryPathValues(toolCall?.arguments, "", 0, out);
416
+ return [...new Set(out.map(normalizeMemoryPath).filter(Boolean))];
417
+ }
418
+
419
+ function collectMemoryPathValues(value, key = "", depth = 0, out = []) {
420
+ if (value == null || depth > 5) return out;
421
+ if (typeof value === "string") {
422
+ if (isPathArgumentKey(key)) appendMemoryPathCandidate(out, value);
423
+ return out;
424
+ }
425
+ if (Array.isArray(value)) {
426
+ for (const item of value) collectMemoryPathValues(item, key, depth + 1, out);
427
+ return out;
428
+ }
429
+ if (typeof value !== "object") return out;
430
+ for (const [childKey, childValue] of Object.entries(value)) collectMemoryPathValues(childValue, childKey, depth + 1, out);
431
+ return out;
432
+ }
433
+
434
+ function appendMemoryPathCandidate(out, value) {
435
+ if (typeof value !== "string") return;
436
+ const normalized = normalizeMemoryPath(value);
437
+ if (normalized) out.push(normalized);
438
+ }
439
+
440
+ function memoryPathInfo(value) {
441
+ const normalized = normalizeMemoryPath(value);
442
+ if (!normalized) return null;
443
+ const lower = normalized.toLowerCase();
444
+ const base = lower.split("/").filter(Boolean).pop() || "";
445
+ if (/\/\.claude\/projects\/[^/]+\/memory\/[^/]+\.(md|mdx|txt|json|yaml|yml)$/i.test(normalized)) {
446
+ return genericMemoryInfo(normalized, "claude-code-project-memory", "project");
447
+ }
448
+ if (/\/\.claude\/(?:memory|memories)\//i.test(normalized)) {
449
+ return genericMemoryInfo(normalized, "claude-code-memory", "global");
450
+ }
451
+ if (/\/\.codex\/(?:memory|memories)\//i.test(normalized)) {
452
+ return genericMemoryInfo(normalized, "codex-memory", "global");
453
+ }
454
+ if (/\/\.cursor\/rules\//i.test(normalized)) {
455
+ return genericMemoryInfo(normalized, "cursor-rules", "project");
456
+ }
457
+ if (/\/\.windsurf\/rules\//i.test(normalized)) {
458
+ return genericMemoryInfo(normalized, "windsurf-rules", "project");
459
+ }
460
+ if (MEMORY_PATH_BASES.has(base)) {
461
+ const source = base === "agents.md" ? "project-instructions"
462
+ : base === "claude.md" ? "claude-instructions"
463
+ : base === "gemini.md" ? "gemini-instructions"
464
+ : base === ".cursorrules" ? "cursor-rules"
465
+ : base === ".windsurfrules" ? "windsurf-rules"
466
+ : "memory-file";
467
+ return genericMemoryInfo(normalized, source, projectMemoryScope(lower));
468
+ }
469
+ return null;
470
+ }
471
+
472
+ function genericMemoryInfo(pathValue, source, scope) {
473
+ const normalized = normalizeMemoryPath(pathValue);
474
+ const name = normalized.split("/").filter(Boolean).pop() || normalized || "memory";
475
+ return {
476
+ path: normalized,
477
+ name,
478
+ source,
479
+ scope
480
+ };
481
+ }
482
+
483
+ function projectMemoryScope(lowerPath) {
484
+ if (/\/\.(claude|codex|cursor|windsurf)\//.test(lowerPath)) return "project";
485
+ return "project";
486
+ }
487
+
488
+ function normalizeMemoryPath(value) {
489
+ const text = String(value || "").trim();
490
+ if (!text || text.length > 4096) return "";
491
+ return text.replace(/\\/g, "/").replace(/\/+/g, "/");
492
+ }
493
+
494
+ function projectInstructionsPathFromContent(content) {
495
+ const match = /^# AGENTS\.md instructions for\s+(.+)$/m.exec(String(content || ""));
496
+ if (!match) return "";
497
+ const root = normalizeMemoryPath(match[1]);
498
+ if (!root) return "";
499
+ return root.toLowerCase().endsWith("/agents.md") ? root : `${root.replace(/\/+$/, "")}/AGENTS.md`;
500
+ }
501
+
502
+ function memoryEventTitle(memory) {
503
+ const action = String(memory?.action || "loaded").toLowerCase();
504
+ if (action === "read") return "Memory read";
505
+ if (action === "write") return "Memory write";
506
+ return "Memory loaded";
507
+ }
508
+
509
+ function memoryEventSummary(memory) {
510
+ const label = memoryEventTitle(memory);
511
+ const pathValue = firstString(memory?.path, memory?.name, "memory");
512
+ return `${label}: ${pathValue}`;
513
+ }
514
+
515
+ function isSuccessfulToolResult(toolResult) {
516
+ const status = String(toolResult?.status || "completed").toLowerCase();
517
+ return !status.includes("error") && !status.includes("fail");
518
+ }
519
+
232
520
  function rememberToolCallEvent(state, toolCall, eventId) {
233
521
  if (!state || !eventId) return;
234
522
  for (const key of toolPairKeys(toolCall)) {
@@ -236,6 +524,7 @@ function rememberToolCallEvent(state, toolCall, eventId) {
236
524
  state.toolCallEventIdsByKey.get(key).push(eventId);
237
525
  }
238
526
  state.unmatchedToolCallEventIds.push(eventId);
527
+ state.unmatchedToolCallEventIdSet.add(eventId);
239
528
  }
240
529
 
241
530
  function consumeToolCallEventId(state, toolResult) {
@@ -247,14 +536,19 @@ function consumeToolCallEventId(state, toolResult) {
247
536
  if (consumeUnmatchedToolEventId(state, eventId)) return eventId;
248
537
  }
249
538
  }
250
- return state.unmatchedToolCallEventIds.shift() || "";
539
+ // FIFO fallback: advance the head pointer past tombstoned (already-consumed)
540
+ // ids and return the oldest one still unmatched.
541
+ const ids = state.unmatchedToolCallEventIds;
542
+ while (state.unmatchedHead < ids.length) {
543
+ const eventId = ids[state.unmatchedHead++];
544
+ if (state.unmatchedToolCallEventIdSet.delete(eventId)) return eventId;
545
+ }
546
+ return "";
251
547
  }
252
548
 
253
549
  function consumeUnmatchedToolEventId(state, eventId) {
254
- const index = state.unmatchedToolCallEventIds.indexOf(eventId);
255
- if (index < 0) return false;
256
- state.unmatchedToolCallEventIds.splice(index, 1);
257
- return true;
550
+ // delete() returns true only if the id was present (still unmatched).
551
+ return state.unmatchedToolCallEventIdSet.delete(eventId);
258
552
  }
259
553
 
260
554
  function toolPairKeys(tool) {
@@ -275,7 +569,7 @@ function baseEvent(session, options) {
275
569
  title: String(options.indexed?.title || ""),
276
570
  summary: String(options.indexed?.summary || "")
277
571
  };
278
- for (const key of ["toolName", "toolCategory", "toolIcon", "model", "status", "latencyMs", "target"]) {
572
+ for (const key of ["toolName", "toolCategory", "toolIcon", "model", "status", "latencyMs", "target", "skills", "memoryAction", "memoryPath", "memorySource", "memoryScope"]) {
279
573
  if (options.indexed?.[key] !== undefined && options.indexed?.[key] !== "") indexed[key] = options.indexed[key];
280
574
  }
281
575
  return {
@@ -301,7 +595,8 @@ function baseEvent(session, options) {
301
595
  body: {
302
596
  text: String(body.text || ""),
303
597
  toolCall: body.toolCall || null,
304
- toolResult: body.toolResult || null
598
+ toolResult: body.toolResult || null,
599
+ memory: body.memory || null
305
600
  },
306
601
  parentEventId: options.parentEventId || ""
307
602
  };
@@ -336,6 +631,9 @@ function renderEventText(event) {
336
631
  const text = [event.indexed?.title, result.summary, result.output || event.body?.text].filter(Boolean).join("\n");
337
632
  return approxTokenCount(text) > 700 ? [event.indexed?.title, result.summary].filter(Boolean).join(" ") : text;
338
633
  }
634
+ if (kind === EVENT_KINDS.MEMORY_READ || kind === EVENT_KINDS.MEMORY_WRITE || kind === EVENT_KINDS.MEMORY_LOADED) {
635
+ return event.body?.text || event.indexed?.summary || "";
636
+ }
339
637
  return event.body?.text || event.indexed?.summary || "";
340
638
  }
341
639
 
@@ -415,7 +713,9 @@ function normalizeToolCall(raw, provider = "") {
415
713
  const rawInputSummary = firstString(raw.rawInputSummary, argument);
416
714
  const inputPreview = firstString(raw.inputPreview, rawInputSummary, summarizeToolInput(parsedArguments));
417
715
  const target = firstString(raw.target, targetFromArguments(parsedArguments));
418
- return {
716
+ const structuredPatch = normalizeStructuredPatch(raw.structuredPatch || raw.structured_patch);
717
+ const argumentPatch = structuredPatch.length ? structuredPatch : structuredPatchFromToolArguments(parsedArguments);
718
+ const normalized = {
419
719
  provider: firstString(raw.provider, provider),
420
720
  id: firstString(raw.id, raw.callId, raw.call_id, raw.toolCallId, raw.tool_call_id, raw.toolUseId, raw.tool_use_id) || undefined,
421
721
  name,
@@ -432,6 +732,8 @@ function normalizeToolCall(raw, provider = "") {
432
732
  target,
433
733
  arguments: parsedArguments && typeof parsedArguments === "object" && !Array.isArray(parsedArguments) ? parsedArguments : undefined
434
734
  };
735
+ if (argumentPatch.length) normalized.structuredPatch = argumentPatch;
736
+ return normalized;
435
737
  }
436
738
 
437
739
  function summarizeToolInput(value) {
@@ -467,7 +769,8 @@ function normalizeToolResult(raw, provider = "") {
467
769
  const output = raw.trim();
468
770
  if (!output) return null;
469
771
  const classification = classifyTool("tool_output");
470
- return {
772
+ const structuredPatch = structuredPatchFromDiffText(output);
773
+ const normalized = {
471
774
  provider,
472
775
  id: undefined,
473
776
  kind: "Tool output",
@@ -481,6 +784,8 @@ function normalizeToolResult(raw, provider = "") {
481
784
  collapsed: output.split("\n").length > 18,
482
785
  status: "completed"
483
786
  };
787
+ if (structuredPatch.length) normalized.structuredPatch = structuredPatch;
788
+ return normalized;
484
789
  }
485
790
  if (typeof raw !== "object") return null;
486
791
  const output = firstString(raw.output, raw.text, raw.content, raw.result);
@@ -488,7 +793,9 @@ function normalizeToolResult(raw, provider = "") {
488
793
  const kind = firstString(raw.kind, raw.type, raw.toolName, "Tool output");
489
794
  const rawCategory = firstString(raw.rawCategory, raw.category, raw.kind, raw.type);
490
795
  const classification = classifyTool(firstString(raw.toolName, raw.name, kind), rawCategory);
491
- return {
796
+ const structuredPatch = normalizeStructuredPatch(raw.structuredPatch || raw.structured_patch);
797
+ const outputPatch = structuredPatch.length ? structuredPatch : structuredPatchFromDiffText(output);
798
+ const normalized = {
492
799
  provider: firstString(raw.provider, provider),
493
800
  id: firstString(raw.id, raw.callId, raw.call_id, raw.toolCallId, raw.tool_call_id, raw.toolUseId, raw.tool_use_id) || undefined,
494
801
  name: firstString(raw.name, raw.toolName, raw.tool_name) || undefined,
@@ -504,6 +811,8 @@ function normalizeToolResult(raw, provider = "") {
504
811
  collapsed: Boolean(raw.collapsed),
505
812
  status: firstString(raw.status, "completed")
506
813
  };
814
+ if (outputPatch.length) normalized.structuredPatch = outputPatch;
815
+ return normalized;
507
816
  }
508
817
 
509
818
  function toolArgumentClauses(toolCall) {
@@ -614,11 +923,13 @@ module.exports = {
614
923
  CANONICAL_EVENT_SCHEMA_VERSION,
615
924
  EVENT_KINDS,
616
925
  isLowSignalToolCall,
926
+ memoryPathInfo,
617
927
  messageToCanonicalEvents,
618
928
  normalizeSessionEvents,
619
929
  normalizeToolCall,
620
930
  normalizeToolResult,
621
931
  renderEventText,
622
932
  renderToolCallText,
933
+ skillInvocationsFromPromptText,
623
934
  stableEventId
624
935
  };