@makerbi/remodex 1.5.3 → 1.5.8

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.
@@ -2,6 +2,7 @@
2
2
  // Purpose: Reconstructs a small thread/turns/list page from local Codex session JSONL files.
3
3
 
4
4
  const fs = require("fs");
5
+ const { buildApplyPatchFileChangeItem } = require("./apply-patch-changes");
5
6
 
6
7
  function readThreadTurnsListPageFromSessionJsonl(filePath, {
7
8
  threadId = "",
@@ -31,15 +32,72 @@ function readThreadTurnsListPageFromSessionJsonl(filePath, {
31
32
  };
32
33
  }
33
34
 
35
+ // Extracts thread-level context that app-server history can omit for desktop-origin runs.
36
+ function parseSessionJsonlMetadata(content) {
37
+ let threadId = "";
38
+ let cwd = "";
39
+
40
+ const raw = String(content || "");
41
+ let lineStart = 0;
42
+ while (lineStart < raw.length) {
43
+ let lineEnd = raw.indexOf("\n", lineStart);
44
+ if (lineEnd === -1) {
45
+ lineEnd = raw.length;
46
+ }
47
+ const line = raw.substring(lineStart, lineEnd).trim();
48
+ lineStart = lineEnd + 1;
49
+ if (!line) {
50
+ continue;
51
+ }
52
+
53
+ let entry;
54
+ try {
55
+ entry = JSON.parse(line);
56
+ } catch {
57
+ continue;
58
+ }
59
+
60
+ if (entry?.type !== "session_meta") {
61
+ continue;
62
+ }
63
+
64
+ const payload = objectValue(entry.payload);
65
+ threadId ||= normalizeString(payload?.id)
66
+ || normalizeString(payload?.thread_id)
67
+ || normalizeString(payload?.threadId);
68
+ cwd ||= normalizeString(payload?.cwd)
69
+ || normalizeString(payload?.current_working_directory)
70
+ || normalizeString(payload?.working_directory);
71
+
72
+ if (threadId && cwd) {
73
+ break;
74
+ }
75
+ }
76
+
77
+ return { threadId, cwd };
78
+ }
79
+
34
80
  function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
35
81
  const turns = [];
36
82
  const turnsById = new Map();
37
83
  let activeTurnId = "";
38
84
  let sessionThreadId = normalizeString(threadId);
85
+ let sessionCwd = "";
86
+ const skippedCallIds = new Set();
87
+ const toolCallsByCallId = new Map();
88
+ const pendingUserMessages = [];
39
89
 
40
- const lines = String(content || "").split(/\r?\n/);
41
- for (let index = 0; index < lines.length; index += 1) {
42
- const line = lines[index].trim();
90
+ const raw = String(content || "");
91
+ let index = -1;
92
+ let lineStart = 0;
93
+ while (lineStart < raw.length) {
94
+ index += 1;
95
+ let lineEnd = raw.indexOf("\n", lineStart);
96
+ if (lineEnd === -1) {
97
+ lineEnd = raw.length;
98
+ }
99
+ const line = raw.substring(lineStart, lineEnd).trim();
100
+ lineStart = lineEnd + 1;
43
101
  if (!line) {
44
102
  continue;
45
103
  }
@@ -56,6 +114,7 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
56
114
  sessionThreadId ||= normalizeString(payload?.id)
57
115
  || normalizeString(payload?.thread_id)
58
116
  || normalizeString(payload?.threadId);
117
+ sessionCwd ||= normalizeString(payload?.cwd);
59
118
  continue;
60
119
  }
61
120
 
@@ -67,7 +126,8 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
67
126
  || normalizeString(payload?.turnId)
68
127
  || activeTurnId
69
128
  || `turn-line-${index + 1}`;
70
- ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
129
+ const turn = ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
130
+ flushPendingUserMessagesToTurn(turn, pendingUserMessages);
71
131
  continue;
72
132
  }
73
133
 
@@ -80,10 +140,16 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
80
140
  entry.timestamp
81
141
  );
82
142
  turn.status = "completed";
143
+ activeTurnId = "";
83
144
  continue;
84
145
  }
85
146
 
86
- if (eventType === "user_message") {
147
+ if (eventType === "item_completed") {
148
+ const completedItem = objectValue(payload?.item);
149
+ if (!completedItem) {
150
+ continue;
151
+ }
152
+
87
153
  const turn = ensureTurn(
88
154
  turns,
89
155
  turnsById,
@@ -91,12 +157,32 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
91
157
  sessionThreadId,
92
158
  entry.timestamp
93
159
  );
94
- turn.items.push({
95
- id: normalizeString(payload?.id) || `user-message-line-${index + 1}`,
96
- type: "user_message",
97
- role: "user",
98
- text: normalizeString(payload?.message) || normalizeString(payload?.text),
160
+ const item = normalizeResponseItemForHistory(completedItem, index + 1, {
161
+ cwd: sessionCwd,
162
+ toolCallsByCallId,
99
163
  });
164
+ if (item) {
165
+ turn.items.push(item);
166
+ }
167
+ continue;
168
+ }
169
+
170
+ if (eventType === "user_message") {
171
+ const explicitTurnId = normalizeString(payload?.turn_id) || normalizeString(payload?.turnId);
172
+ const item = createUserMessageHistoryItem(payload, index + 1, entry.timestamp);
173
+ if (!explicitTurnId && !activeTurnId) {
174
+ pushPendingUserMessage(pendingUserMessages, item);
175
+ continue;
176
+ }
177
+
178
+ const turn = ensureTurn(
179
+ turns,
180
+ turnsById,
181
+ explicitTurnId || activeTurnId || `turn-line-${index + 1}`,
182
+ sessionThreadId,
183
+ entry.timestamp
184
+ );
185
+ addHistoryItemToTurn(turn, item);
100
186
  continue;
101
187
  }
102
188
 
@@ -110,6 +196,10 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
110
196
  if (!payload) {
111
197
  continue;
112
198
  }
199
+ rememberToolCallForHistory(payload, toolCallsByCallId);
200
+ if (shouldSkipResponseItemForHistory(payload, skippedCallIds)) {
201
+ continue;
202
+ }
113
203
  const turn = ensureTurn(
114
204
  turns,
115
205
  turnsById,
@@ -117,9 +207,22 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
117
207
  sessionThreadId,
118
208
  entry.timestamp
119
209
  );
120
- const item = normalizeResponseItemForHistory(payload, index + 1);
210
+ const item = normalizeResponseItemForHistory(payload, index + 1, {
211
+ cwd: sessionCwd,
212
+ toolCallsByCallId,
213
+ });
121
214
  if (item) {
122
- turn.items.push(item);
215
+ if (shouldSkipDuplicateProposedPlanMessage(turn, item)) {
216
+ continue;
217
+ }
218
+ const itemTimestamp = historyItemTimestamp(item, entry.timestamp);
219
+ if (itemTimestamp && !item.createdAt) {
220
+ item.createdAt = itemTimestamp;
221
+ }
222
+ if (itemTimestamp && !item.timestamp) {
223
+ item.timestamp = itemTimestamp;
224
+ }
225
+ addHistoryItemToTurn(turn, item);
123
226
  }
124
227
  }
125
228
  }
@@ -127,6 +230,159 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
127
230
  return turns.filter((turn) => turn.items.length > 0);
128
231
  }
129
232
 
233
+ function createUserMessageHistoryItem(payload, lineNumber, timestamp) {
234
+ const createdAt = historyItemTimestamp(payload, timestamp);
235
+ return {
236
+ id: normalizeString(payload?.id) || `user-message-line-${lineNumber}`,
237
+ type: "user_message",
238
+ role: "user",
239
+ text: normalizeString(payload?.message) || normalizeString(payload?.text),
240
+ createdAt: createdAt || undefined,
241
+ timestamp: createdAt || undefined,
242
+ };
243
+ }
244
+
245
+ function pushPendingUserMessage(pendingUserMessages, item) {
246
+ if (!item || !historyUserItemText(item)) {
247
+ return;
248
+ }
249
+ if (pendingUserMessages.some((candidate) => areDuplicateUserHistoryItems(candidate, item))) {
250
+ return;
251
+ }
252
+ pendingUserMessages.push(item);
253
+ }
254
+
255
+ function addHistoryItemToTurn(turn, item) {
256
+ if (!turn || !item) {
257
+ return;
258
+ }
259
+
260
+ if (isUserHistoryItem(item)) {
261
+ const duplicateIndex = turn.items.findIndex((candidate) => areDuplicateUserHistoryItems(candidate, item));
262
+ if (duplicateIndex !== -1) {
263
+ turn.items[duplicateIndex] = mergeDuplicateUserHistoryItems(turn.items[duplicateIndex], item);
264
+ return;
265
+ }
266
+ }
267
+
268
+ turn.items.push(item);
269
+ }
270
+
271
+ function mergeDuplicateUserHistoryItems(existing, incoming) {
272
+ const existingHasStructuredContent = hasStructuredUserHistoryContent(existing);
273
+ const incomingHasStructuredContent = hasStructuredUserHistoryContent(incoming);
274
+ const preferStructured = existingHasStructuredContent !== incomingHasStructuredContent;
275
+ const preferIncoming = preferStructured
276
+ ? incomingHasStructuredContent
277
+ : normalizeHistoryToken(incoming?.type) === "usermessage"
278
+ && normalizeHistoryToken(existing?.type) !== "usermessage";
279
+ const base = preferIncoming ? incoming : existing;
280
+ const fallback = preferIncoming ? existing : incoming;
281
+ return {
282
+ ...base,
283
+ content: Array.isArray(base?.content) ? base.content : fallback?.content,
284
+ attachments: Array.isArray(base?.attachments) ? base.attachments : fallback?.attachments,
285
+ createdAt: historyItemTimestamp(base, historyItemTimestamp(fallback)) || undefined,
286
+ timestamp: historyItemTimestamp(base, historyItemTimestamp(fallback)) || undefined,
287
+ };
288
+ }
289
+
290
+ function hasStructuredUserHistoryContent(item) {
291
+ const content = Array.isArray(item?.content) ? item.content : [];
292
+ return content
293
+ .map((entry) => objectValue(entry))
294
+ .filter(Boolean)
295
+ .some((entry) => {
296
+ const type = normalizeHistoryToken(entry.type);
297
+ return type === "skill"
298
+ || type === "mention"
299
+ || type === "image"
300
+ || type === "inputimage";
301
+ });
302
+ }
303
+
304
+ function areDuplicateUserHistoryItems(first, second) {
305
+ if (!isUserHistoryItem(first) || !isUserHistoryItem(second)) {
306
+ return false;
307
+ }
308
+ const firstText = historyUserItemText(first);
309
+ const secondText = historyUserItemText(second);
310
+ if (!firstText || !secondText) {
311
+ return false;
312
+ }
313
+ if (firstText === secondText) {
314
+ return true;
315
+ }
316
+ const firstKey = canonicalUserHistoryTextKey(firstText);
317
+ const secondKey = canonicalUserHistoryTextKey(secondText);
318
+ if (firstKey.hasMentions && firstKey.key === secondKey.key) {
319
+ return true;
320
+ }
321
+ return Boolean(
322
+ firstKey.text
323
+ && firstKey.text === secondKey.text
324
+ && (firstKey.hasMentions || secondKey.hasMentions)
325
+ && sameUserHistoryTimestamp(first, second)
326
+ );
327
+ }
328
+
329
+ function sameUserHistoryTimestamp(first, second) {
330
+ const firstTimestamp = historyItemTimestamp(first);
331
+ const secondTimestamp = historyItemTimestamp(second);
332
+ return Boolean(firstTimestamp && secondTimestamp && firstTimestamp === secondTimestamp);
333
+ }
334
+
335
+ function historyItemTimestamp(item, fallbackTimestamp = "") {
336
+ return firstNonEmptyString([
337
+ normalizeString(item?.createdAt),
338
+ normalizeString(item?.created_at),
339
+ normalizeString(item?.timestamp),
340
+ normalizeString(item?.time),
341
+ normalizeString(fallbackTimestamp),
342
+ ]);
343
+ }
344
+
345
+ function isUserHistoryItem(item) {
346
+ return normalizeHistoryToken(item?.type) === "usermessage"
347
+ || normalizeString(item?.role).toLowerCase() === "user";
348
+ }
349
+
350
+ function historyUserItemText(item) {
351
+ return normalizeString(item?.text)
352
+ || normalizeString(item?.message)
353
+ || responseItemMessageText(item);
354
+ }
355
+
356
+ function shouldSkipDuplicateProposedPlanMessage(turn, item) {
357
+ if (!turn || !item || normalizeHistoryToken(item.type) !== "message") {
358
+ return false;
359
+ }
360
+
361
+ const role = normalizeString(item.role).toLowerCase();
362
+ if (role && role !== "assistant") {
363
+ return false;
364
+ }
365
+
366
+ if (!responseItemMessageText(item).includes("<proposed_plan>")) {
367
+ return false;
368
+ }
369
+
370
+ return turn.items.some((candidate) => (
371
+ normalizeHistoryToken(candidate?.type) === "plan"
372
+ && candidate?.remodexJsonlProgressPlan !== true
373
+ ));
374
+ }
375
+
376
+ function flushPendingUserMessagesToTurn(turn, pendingUserMessages) {
377
+ if (!turn || pendingUserMessages.length === 0) {
378
+ return;
379
+ }
380
+
381
+ for (const item of pendingUserMessages.splice(0)) {
382
+ addHistoryItemToTurn(turn, item);
383
+ }
384
+ }
385
+
130
386
  function ensureTurn(turns, turnsById, turnId, threadId, timestamp) {
131
387
  const normalizedTurnId = normalizeString(turnId) || `turn-${turns.length + 1}`;
132
388
  let turn = turnsById.get(normalizedTurnId);
@@ -147,12 +403,34 @@ function ensureTurn(turns, turnsById, turnId, threadId, timestamp) {
147
403
  return turn;
148
404
  }
149
405
 
150
- function normalizeResponseItemForHistory(payload, lineNumber) {
406
+ function normalizeResponseItemForHistory(payload, lineNumber, { cwd = "", toolCallsByCallId = new Map() } = {}) {
151
407
  const type = normalizeHistoryItemType(payload.type);
152
408
  if (!type) {
153
409
  return null;
154
410
  }
155
411
 
412
+ const progressPlanItem = normalizeProgressPlanItemForHistory(payload);
413
+ if (progressPlanItem) {
414
+ return progressPlanItem;
415
+ }
416
+
417
+ const applyPatchItem = normalizeApplyPatchItemForHistory(payload, lineNumber, { cwd });
418
+ if (applyPatchItem) {
419
+ return applyPatchItem;
420
+ }
421
+
422
+ const toolOutputImageViewItem = normalizeToolOutputImageViewItemForHistory(payload, lineNumber, {
423
+ toolCallsByCallId,
424
+ });
425
+ if (toolOutputImageViewItem) {
426
+ return toolOutputImageViewItem;
427
+ }
428
+
429
+ const readableToolItem = normalizeReadableToolItemForHistory(payload, lineNumber, { cwd });
430
+ if (readableToolItem) {
431
+ return readableToolItem;
432
+ }
433
+
156
434
  const item = {
157
435
  ...payload,
158
436
  id: normalizeString(payload.id)
@@ -169,8 +447,568 @@ function normalizeResponseItemForHistory(payload, lineNumber) {
169
447
  return item;
170
448
  }
171
449
 
450
+ // Converts `view_image` tool output blobs into a lightweight local image reference.
451
+ function normalizeToolOutputImageViewItemForHistory(payload, lineNumber, { toolCallsByCallId = new Map() } = {}) {
452
+ const type = normalizeHistoryItemType(payload.type);
453
+ if (normalizeHistoryToken(type) !== "toolcalloutput") {
454
+ return null;
455
+ }
456
+
457
+ const callId = normalizeString(payload.call_id)
458
+ || normalizeString(payload.callId)
459
+ || normalizeString(payload.id);
460
+ const toolCall = callId ? toolCallsByCallId.get(callId) : null;
461
+ if (!toolCall || normalizeString(toolCall.toolName).toLowerCase() !== "view_image") {
462
+ return null;
463
+ }
464
+
465
+ const imagePath = normalizeString(toolCall.imagePath);
466
+ if (!imagePath || !toolCallOutputContainsInlineImage(payload.output)) {
467
+ return null;
468
+ }
469
+
470
+ return {
471
+ id: `${callId || `tool-output-line-${lineNumber}`}-image-view`,
472
+ type: "imageView",
473
+ status: normalizeString(payload.status) || "completed",
474
+ path: imagePath,
475
+ call_id: callId || undefined,
476
+ tool_name: toolCall.toolName,
477
+ remodexJsonlToolOutputImage: true,
478
+ };
479
+ }
480
+
481
+ // Enriches raw tool-call JSONL records so mobile history can render useful rows.
482
+ function normalizeReadableToolItemForHistory(payload, lineNumber, { cwd = "" } = {}) {
483
+ const type = normalizeHistoryItemType(payload.type);
484
+ const typeToken = normalizeHistoryToken(type);
485
+ if (typeToken !== "toolcall" && typeToken !== "customtoolcall") {
486
+ return null;
487
+ }
488
+
489
+ const toolName = normalizeString(payload.name)
490
+ || normalizeString(payload.tool_name)
491
+ || normalizeString(payload.toolName);
492
+ if (!toolName) {
493
+ return null;
494
+ }
495
+
496
+ const callId = normalizeString(payload.call_id)
497
+ || normalizeString(payload.callId)
498
+ || normalizeString(payload.id);
499
+ const argumentsObject = parseToolArguments(
500
+ payload.arguments !== undefined ? payload.arguments : payload.input
501
+ );
502
+ const id = callId || normalizeString(payload.id) || `tool-call-line-${lineNumber}`;
503
+ const status = normalizeString(payload.status) || "completed";
504
+
505
+ if (isCommandToolName(toolName)) {
506
+ return {
507
+ ...payload,
508
+ id,
509
+ type: "commandExecution",
510
+ status,
511
+ command: resolveToolCommand(toolName, argumentsObject),
512
+ cwd: resolveToolWorkingDirectory(argumentsObject, { cwd }),
513
+ call_id: callId || undefined,
514
+ tool_name: toolName,
515
+ arguments: payload.arguments,
516
+ };
517
+ }
518
+
519
+ const message = readableToolActivityMessage(toolName, argumentsObject, payload);
520
+ if (!message) {
521
+ return null;
522
+ }
523
+
524
+ return {
525
+ ...payload,
526
+ id,
527
+ type: "tool_call",
528
+ status,
529
+ message,
530
+ call_id: callId || undefined,
531
+ tool_name: toolName,
532
+ };
533
+ }
534
+
535
+ function rememberToolCallForHistory(payload, toolCallsByCallId) {
536
+ const typeToken = normalizeHistoryToken(normalizeHistoryItemType(payload?.type));
537
+ if (typeToken !== "toolcall" && typeToken !== "customtoolcall") {
538
+ return;
539
+ }
540
+
541
+ const callId = normalizeString(payload.call_id)
542
+ || normalizeString(payload.callId)
543
+ || normalizeString(payload.id);
544
+ const toolName = normalizeString(payload.name)
545
+ || normalizeString(payload.tool_name)
546
+ || normalizeString(payload.toolName);
547
+ if (!callId || !toolName) {
548
+ return;
549
+ }
550
+
551
+ const argumentsObject = parseToolArguments(
552
+ payload.arguments !== undefined ? payload.arguments : payload.input
553
+ );
554
+ toolCallsByCallId.set(callId, {
555
+ toolName,
556
+ imagePath: resolveToolImagePath(toolName, argumentsObject, payload),
557
+ });
558
+ }
559
+
560
+ function resolveToolImagePath(toolName, argumentsObject, payload) {
561
+ if (normalizeString(toolName).toLowerCase() !== "view_image") {
562
+ return "";
563
+ }
564
+
565
+ return firstNonEmptyString([
566
+ normalizeString(argumentsObject.path),
567
+ normalizeString(argumentsObject.filePath),
568
+ normalizeString(argumentsObject.file_path),
569
+ normalizeString(argumentsObject.localPath),
570
+ normalizeString(argumentsObject.local_path),
571
+ normalizeString(payload.path),
572
+ normalizeString(payload.filePath),
573
+ normalizeString(payload.file_path),
574
+ normalizeString(payload.localPath),
575
+ normalizeString(payload.local_path),
576
+ ]);
577
+ }
578
+
579
+ function toolCallOutputContainsInlineImage(rawOutput) {
580
+ const parsedOutput = typeof rawOutput === "string"
581
+ ? safeParseJSON(rawOutput) || rawOutput
582
+ : rawOutput;
583
+ return containsInlineImageDataURL(parsedOutput);
584
+ }
585
+
586
+ function containsInlineImageDataURL(value) {
587
+ if (typeof value === "string") {
588
+ return value.toLowerCase().startsWith("data:image");
589
+ }
590
+
591
+ if (Array.isArray(value)) {
592
+ return value.some(containsInlineImageDataURL);
593
+ }
594
+
595
+ if (value && typeof value === "object") {
596
+ return Object.values(value).some(containsInlineImageDataURL);
597
+ }
598
+
599
+ return false;
600
+ }
601
+
602
+ function normalizeApplyPatchItemForHistory(payload, lineNumber, { cwd = "" } = {}) {
603
+ const type = normalizeHistoryItemType(payload.type);
604
+ if (normalizeString(payload.name) !== "apply_patch" || normalizeHistoryToken(type) !== "customtoolcall") {
605
+ return null;
606
+ }
607
+
608
+ const callId = normalizeString(payload.call_id)
609
+ || normalizeString(payload.callId)
610
+ || normalizeString(payload.id);
611
+ const item = buildApplyPatchFileChangeItem({
612
+ callId,
613
+ patch: normalizeString(payload.input),
614
+ status: normalizeString(payload.status) || "completed",
615
+ idFallback: callId || `apply-patch-line-${lineNumber}`,
616
+ cwd,
617
+ });
618
+ return item ? { ...payload, ...item } : null;
619
+ }
620
+
621
+ function normalizeProgressPlanItemForHistory(payload) {
622
+ const type = normalizeHistoryItemType(payload.type);
623
+ if (!isInternalProgressPlanCall(payload) || normalizeHistoryToken(type) !== "toolcall") {
624
+ return null;
625
+ }
626
+
627
+ const argumentsObject = parseToolArguments(payload.arguments);
628
+ const explanation = normalizeString(argumentsObject.explanation);
629
+ const plan = normalizeHistoryPlanSteps(argumentsObject.plan);
630
+ if (!explanation && plan.length === 0) {
631
+ return null;
632
+ }
633
+
634
+ return {
635
+ id: normalizeString(payload.call_id)
636
+ || normalizeString(payload.callId)
637
+ || normalizeString(payload.id)
638
+ || undefined,
639
+ type: "plan",
640
+ text: explanation || "Planning...",
641
+ explanation: explanation || undefined,
642
+ plan,
643
+ remodexJsonlProgressPlan: true,
644
+ };
645
+ }
646
+
647
+ function normalizeHistoryPlanSteps(rawPlan) {
648
+ if (!Array.isArray(rawPlan)) {
649
+ return [];
650
+ }
651
+
652
+ return rawPlan.flatMap((rawStep) => {
653
+ const stepObject = objectValue(rawStep);
654
+ const step = normalizeString(stepObject?.step);
655
+ const status = normalizeHistoryPlanStatus(stepObject?.status);
656
+ return step && status ? [{ step, status }] : [];
657
+ });
658
+ }
659
+
660
+ function normalizeHistoryPlanStatus(rawStatus) {
661
+ const normalized = normalizeString(rawStatus);
662
+ switch (normalized) {
663
+ case "pending":
664
+ case "in_progress":
665
+ case "inProgress":
666
+ case "completed":
667
+ return normalized;
668
+ default:
669
+ return "";
670
+ }
671
+ }
672
+
673
+ function parseToolArguments(rawArguments) {
674
+ const parsed = typeof rawArguments === "string"
675
+ ? safeParseJSON(normalizeString(rawArguments))
676
+ : rawArguments;
677
+ return objectValue(parsed) || {};
678
+ }
679
+
680
+ function resolveToolCommand(toolName, argumentsObject) {
681
+ if (!isCommandToolName(toolName)) {
682
+ return toolName;
683
+ }
684
+
685
+ return firstNonEmptyString([
686
+ normalizeString(argumentsObject.cmd),
687
+ normalizeString(argumentsObject.command),
688
+ normalizeString(argumentsObject.raw_command),
689
+ normalizeString(argumentsObject.rawCommand),
690
+ normalizeString(argumentsObject.input),
691
+ ]) || toolName;
692
+ }
693
+
694
+ function resolveToolWorkingDirectory(argumentsObject, { cwd = "" } = {}) {
695
+ return firstNonEmptyString([
696
+ normalizeString(argumentsObject.workdir),
697
+ normalizeString(argumentsObject.cwd),
698
+ normalizeString(argumentsObject.working_directory),
699
+ normalizeString(argumentsObject.workingDirectory),
700
+ normalizeString(cwd),
701
+ ]) || "";
702
+ }
703
+
704
+ function isCommandToolName(toolName) {
705
+ const normalized = normalizeString(toolName).toLowerCase();
706
+ return normalized === "exec_command" || normalized === "shell_command";
707
+ }
708
+
709
+ function readableToolActivityMessage(toolName, argumentsObject, payload) {
710
+ const normalized = normalizeString(toolName).toLowerCase();
711
+ switch (normalized) {
712
+ case "write_stdin":
713
+ return "Write to terminal";
714
+ case "read_thread_terminal":
715
+ return "Read terminal output";
716
+ case "view_image": {
717
+ const imagePath = firstNonEmptyString([
718
+ normalizeString(argumentsObject.path),
719
+ normalizeString(payload.path),
720
+ ]);
721
+ return imagePath ? `Open image ${compactHistoryPath(imagePath)}` : "Open image";
722
+ }
723
+ case "open":
724
+ case "browser.open":
725
+ return readableTargetMessage("Open", argumentsObject, payload);
726
+ case "click":
727
+ case "browser.click":
728
+ return readableTargetMessage("Click", argumentsObject, payload);
729
+ case "find":
730
+ case "browser.find":
731
+ return readableTargetMessage("Find", argumentsObject, payload);
732
+ case "screenshot":
733
+ case "browser.screenshot":
734
+ return "Capture screenshot";
735
+ case "web.run":
736
+ case "search_query":
737
+ case "image_query":
738
+ return readableSearchMessage(argumentsObject, payload);
739
+ case "weather":
740
+ return readableLocationMessage("Check weather", argumentsObject, payload);
741
+ case "finance":
742
+ return readableSymbolMessage("Check market data", argumentsObject, payload);
743
+ case "sports":
744
+ return readableLocationMessage("Check sports", argumentsObject, payload);
745
+ case "automation_update":
746
+ return "Update automation";
747
+ case "update_goal":
748
+ return "Update goal";
749
+ case "create_goal":
750
+ return "Create goal";
751
+ case "get_goal":
752
+ return "Read goal";
753
+ case "request_user_input":
754
+ return "Request input";
755
+ default:
756
+ return `Run ${humanizeToolName(toolName)}`;
757
+ }
758
+ }
759
+
760
+ function readableTargetMessage(verb, argumentsObject, payload) {
761
+ const target = firstNonEmptyString([
762
+ normalizeString(argumentsObject.ref_id),
763
+ normalizeString(argumentsObject.refId),
764
+ normalizeString(argumentsObject.url),
765
+ normalizeString(argumentsObject.pattern),
766
+ normalizeString(argumentsObject.query),
767
+ normalizeString(payload.ref_id),
768
+ normalizeString(payload.url),
769
+ ]);
770
+ return target ? `${verb} ${compactHistoryPath(target)}` : verb;
771
+ }
772
+
773
+ function readableSearchMessage(argumentsObject, payload) {
774
+ const query = firstSearchQuery(argumentsObject) || firstSearchQuery(payload);
775
+ return query ? `Search ${query}` : "Search web";
776
+ }
777
+
778
+ function firstSearchQuery(object) {
779
+ const direct = normalizeString(object.q)
780
+ || normalizeString(object.query)
781
+ || normalizeString(object.search_query);
782
+ if (direct) {
783
+ return direct;
784
+ }
785
+
786
+ let searchArray = null;
787
+ if (Array.isArray(object.search_query)) {
788
+ searchArray = object.search_query;
789
+ } else if (Array.isArray(object.image_query)) {
790
+ searchArray = object.image_query;
791
+ }
792
+ if (!searchArray) {
793
+ return "";
794
+ }
795
+ for (const item of searchArray) {
796
+ const query = normalizeString(objectValue(item)?.q)
797
+ || normalizeString(objectValue(item)?.query);
798
+ if (query) {
799
+ return query;
800
+ }
801
+ }
802
+ return "";
803
+ }
804
+
805
+ function readableLocationMessage(verb, argumentsObject, payload) {
806
+ const target = firstNonEmptyString([
807
+ normalizeString(argumentsObject.location),
808
+ normalizeString(argumentsObject.team),
809
+ normalizeString(argumentsObject.league),
810
+ normalizeString(payload.location),
811
+ ]);
812
+ return target ? `${verb} ${target}` : verb;
813
+ }
814
+
815
+ function readableSymbolMessage(verb, argumentsObject, payload) {
816
+ const target = firstNonEmptyString([
817
+ normalizeString(argumentsObject.ticker),
818
+ normalizeString(argumentsObject.symbol),
819
+ normalizeString(payload.ticker),
820
+ ]);
821
+ return target ? `${verb} ${target}` : verb;
822
+ }
823
+
824
+ function humanizeToolName(toolName) {
825
+ return normalizeString(toolName)
826
+ .replace(/^[^.]+\./, "")
827
+ .replace(/[_-]+/g, " ")
828
+ .replace(/\s+/g, " ")
829
+ .trim() || "tool";
830
+ }
831
+
832
+ function compactHistoryPath(path) {
833
+ const text = normalizeString(path);
834
+ if (!text) {
835
+ return "";
836
+ }
837
+ const normalized = text.replace(/\\/g, "/");
838
+ const parts = normalized.split("/").filter(Boolean);
839
+ if (parts.length <= 2) {
840
+ return text;
841
+ }
842
+ const prefix = normalized.startsWith("/") ? "…/" : "";
843
+ return `${prefix}${parts.slice(-2).join("/")}`;
844
+ }
845
+
846
+ function firstNonEmptyString(values) {
847
+ for (const value of values) {
848
+ const normalized = normalizeString(value);
849
+ if (normalized) {
850
+ return normalized;
851
+ }
852
+ }
853
+ return "";
854
+ }
855
+
856
+ function safeParseJSON(rawValue) {
857
+ if (!rawValue) {
858
+ return null;
859
+ }
860
+ try {
861
+ return JSON.parse(rawValue);
862
+ } catch {
863
+ return null;
864
+ }
865
+ }
866
+
867
+ // Filters desktop transcript internals that are stored as response items but are not chat history.
868
+ function shouldSkipResponseItemForHistory(payload, skippedCallIds) {
869
+ const type = normalizeHistoryItemType(payload.type);
870
+ const callId = normalizeString(payload.call_id) || normalizeString(payload.callId);
871
+
872
+ if (type === "tool_call_output" && callId && skippedCallIds.has(callId)) {
873
+ return true;
874
+ }
875
+
876
+ if (type === "tool_call" && isSubagentOrchestrationCall(payload)) {
877
+ if (callId) {
878
+ skippedCallIds.add(callId);
879
+ }
880
+ return true;
881
+ }
882
+
883
+ if (type === "tool_call" && isInternalProgressPlanCall(payload)) {
884
+ if (callId) {
885
+ skippedCallIds.add(callId);
886
+ }
887
+ return false;
888
+ }
889
+
890
+ if (type !== "message") {
891
+ return false;
892
+ }
893
+
894
+ const role = normalizeString(payload.role).toLowerCase();
895
+ if (role && role !== "user" && role !== "assistant") {
896
+ return true;
897
+ }
898
+
899
+ if (role === "user" && isSubagentNotificationMessage(payload)) {
900
+ return true;
901
+ }
902
+
903
+ return false;
904
+ }
905
+
906
+ function isSubagentOrchestrationCall(payload) {
907
+ const name = normalizeString(payload.name).toLowerCase();
908
+ return name === "spawn_agent"
909
+ || name === "wait_agent"
910
+ || name === "send_input"
911
+ || name === "resume_agent"
912
+ || name === "close_agent";
913
+ }
914
+
915
+ function isInternalProgressPlanCall(payload) {
916
+ return normalizeString(payload.name).toLowerCase() === "update_plan";
917
+ }
918
+
919
+ function isSubagentNotificationMessage(payload) {
920
+ const text = responseItemMessageText(payload).trimStart();
921
+ return text.startsWith("<subagent_notification>");
922
+ }
923
+
924
+ function responseItemMessageText(payload) {
925
+ const directText = normalizeString(payload.text) || normalizeString(payload.message);
926
+ if (directText) {
927
+ return directText;
928
+ }
929
+
930
+ const content = Array.isArray(payload.content) ? payload.content : [];
931
+ return content
932
+ .map((item) => objectValue(item))
933
+ .filter(Boolean)
934
+ .map((item) => responseItemContentText(item))
935
+ .filter(Boolean)
936
+ .join("\n");
937
+ }
938
+
939
+ function responseItemContentText(item) {
940
+ const type = normalizeHistoryToken(item?.type);
941
+ if (type === "skill") {
942
+ const skillName = normalizeString(item.id) || normalizeString(item.name);
943
+ return skillName ? `$${skillName}` : "";
944
+ }
945
+ if (type === "mention") {
946
+ const mentionName = normalizeString(item.name) || normalizeString(item.id);
947
+ return mentionName ? `@${mentionName}` : "";
948
+ }
949
+ return normalizeString(item.text) || normalizeString(objectValue(item.data)?.text);
950
+ }
951
+
952
+ function canonicalUserHistoryTextKey(text) {
953
+ const mentions = { skills: new Set(), plugins: new Set() };
954
+ let body = normalizeString(text).replace(
955
+ /(^|\s)([$/@])([A-Za-z0-9][A-Za-z0-9._-]*)(?=[\s,.;:!?)\]}>]|$)/g,
956
+ (match, prefix, trigger, rawName) => {
957
+ const name = normalizeString(rawName).toLowerCase();
958
+ if (!name) {
959
+ return match;
960
+ }
961
+ if (trigger === "$" || trigger === "/") {
962
+ mentions.skills.add(name);
963
+ } else if (trigger === "@") {
964
+ mentions.plugins.add(name);
965
+ }
966
+ return prefix || "";
967
+ }
968
+ );
969
+
970
+ for (const skill of mentions.skills) {
971
+ body = removeBoundedUserMentionPhrase(body, `$${skill}`);
972
+ body = removeBoundedUserMentionPhrase(body, `/${skill}`);
973
+ body = removeBoundedUserMentionPhrase(body, displayNameForUserMention(skill));
974
+ }
975
+ for (const plugin of mentions.plugins) {
976
+ body = removeBoundedUserMentionPhrase(body, `@${plugin}`);
977
+ }
978
+
979
+ const normalizedBody = body.trim().replace(/\s+/g, " ").toLowerCase();
980
+ const skills = [...mentions.skills].sort();
981
+ const plugins = [...mentions.plugins].sort();
982
+ return {
983
+ hasMentions: skills.length > 0 || plugins.length > 0,
984
+ text: normalizedBody,
985
+ key: `${normalizedBody}|skills:${skills.join(",")}|plugins:${plugins.join(",")}`,
986
+ };
987
+ }
988
+
989
+ function removeBoundedUserMentionPhrase(text, phrase) {
990
+ const normalizedPhrase = normalizeString(phrase);
991
+ if (!normalizedPhrase) {
992
+ return text;
993
+ }
994
+ const pattern = new RegExp(`(^|\\s)${escapeRegExp(normalizedPhrase)}(?=[\\s,.;:!?)\\]}>]|$)`, "gi");
995
+ return text.replace(pattern, (match, prefix) => prefix || "");
996
+ }
997
+
998
+ function displayNameForUserMention(name) {
999
+ return normalizeString(name)
1000
+ .split(/[-_]+/)
1001
+ .filter(Boolean)
1002
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
1003
+ .join(" ");
1004
+ }
1005
+
1006
+ function escapeRegExp(value) {
1007
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1008
+ }
1009
+
172
1010
  function normalizeHistoryItemType(rawType) {
173
- const normalized = normalizeString(rawType).toLowerCase().replace(/[\s_-]+/g, "");
1011
+ const normalized = normalizeHistoryToken(rawType);
174
1012
  if (!normalized) {
175
1013
  return "";
176
1014
  }
@@ -180,9 +1018,16 @@ function normalizeHistoryItemType(rawType) {
180
1018
  if (normalized === "functioncalloutput") {
181
1019
  return "tool_call_output";
182
1020
  }
1021
+ if (normalized === "plan") {
1022
+ return "plan";
1023
+ }
183
1024
  return rawType;
184
1025
  }
185
1026
 
1027
+ function normalizeHistoryToken(rawType) {
1028
+ return normalizeString(rawType).toLowerCase().replace(/[\s_-]+/g, "");
1029
+ }
1030
+
186
1031
  function objectValue(value) {
187
1032
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
188
1033
  }
@@ -192,6 +1037,7 @@ function normalizeString(value) {
192
1037
  }
193
1038
 
194
1039
  module.exports = {
1040
+ parseSessionJsonlMetadata,
195
1041
  parseSessionJsonlTurns,
196
1042
  readThreadTurnsListPageFromSessionJsonl,
197
1043
  };