@viewportai/daemon 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +75 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +59 -1
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/cli/bind-command.d.ts.map +1 -1
  6. package/dist/cli/bind-command.js +7 -3
  7. package/dist/cli/bind-command.js.map +1 -1
  8. package/dist/cli/commands.d.ts +2 -0
  9. package/dist/cli/commands.d.ts.map +1 -1
  10. package/dist/cli/commands.js +2 -0
  11. package/dist/cli/commands.js.map +1 -1
  12. package/dist/cli/context-vault-metadata-command.d.ts +33 -0
  13. package/dist/cli/context-vault-metadata-command.d.ts.map +1 -1
  14. package/dist/cli/context-vault-metadata-command.js +15 -5
  15. package/dist/cli/context-vault-metadata-command.js.map +1 -1
  16. package/dist/cli/context-vault-use-command.d.ts.map +1 -1
  17. package/dist/cli/context-vault-use-command.js +20 -0
  18. package/dist/cli/context-vault-use-command.js.map +1 -1
  19. package/dist/cli/daemon-settings.js +1 -1
  20. package/dist/cli/daemon-settings.js.map +1 -1
  21. package/dist/cli/lifecycle-commands.d.ts.map +1 -1
  22. package/dist/cli/lifecycle-commands.js +6 -1
  23. package/dist/cli/lifecycle-commands.js.map +1 -1
  24. package/dist/cli/lifecycle-pair-command.js +16 -1
  25. package/dist/cli/lifecycle-pair-command.js.map +1 -1
  26. package/dist/cli/lifecycle-status-command.d.ts.map +1 -1
  27. package/dist/cli/lifecycle-status-command.js +6 -0
  28. package/dist/cli/lifecycle-status-command.js.map +1 -1
  29. package/dist/cli/org-binding.d.ts +2 -0
  30. package/dist/cli/org-binding.d.ts.map +1 -1
  31. package/dist/cli/org-binding.js +20 -1
  32. package/dist/cli/org-binding.js.map +1 -1
  33. package/dist/cli/profile-command.d.ts +3 -0
  34. package/dist/cli/profile-command.d.ts.map +1 -0
  35. package/dist/cli/profile-command.js +320 -0
  36. package/dist/cli/profile-command.js.map +1 -0
  37. package/dist/cli/profile-runtime-commands.d.ts +6 -0
  38. package/dist/cli/profile-runtime-commands.d.ts.map +1 -0
  39. package/dist/cli/profile-runtime-commands.js +127 -0
  40. package/dist/cli/profile-runtime-commands.js.map +1 -0
  41. package/dist/cli/setup-command.d.ts +5 -0
  42. package/dist/cli/setup-command.d.ts.map +1 -1
  43. package/dist/cli/setup-command.js +52 -0
  44. package/dist/cli/setup-command.js.map +1 -1
  45. package/dist/cli/uninstall-command.d.ts +2 -0
  46. package/dist/cli/uninstall-command.d.ts.map +1 -0
  47. package/dist/cli/uninstall-command.js +111 -0
  48. package/dist/cli/uninstall-command.js.map +1 -0
  49. package/dist/config-resolution/config-writer.d.ts +4 -0
  50. package/dist/config-resolution/config-writer.d.ts.map +1 -1
  51. package/dist/config-resolution/config-writer.js +18 -2
  52. package/dist/config-resolution/config-writer.js.map +1 -1
  53. package/dist/config-resolution/resolver.js +6 -0
  54. package/dist/config-resolution/resolver.js.map +1 -1
  55. package/dist/config-resolution/schema.d.ts +4 -0
  56. package/dist/config-resolution/schema.d.ts.map +1 -1
  57. package/dist/config-resolution/schema.js +4 -0
  58. package/dist/config-resolution/schema.js.map +1 -1
  59. package/dist/config-resolution/types.d.ts +2 -0
  60. package/dist/config-resolution/types.d.ts.map +1 -1
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +2 -5
  63. package/dist/core/config.js.map +1 -1
  64. package/dist/core/interfaces.d.ts +38 -0
  65. package/dist/core/interfaces.d.ts.map +1 -1
  66. package/dist/core/profiles.d.ts +43 -0
  67. package/dist/core/profiles.d.ts.map +1 -0
  68. package/dist/core/profiles.js +181 -0
  69. package/dist/core/profiles.js.map +1 -0
  70. package/dist/core/session-context-prompt.d.ts.map +1 -1
  71. package/dist/core/session-context-prompt.js +22 -4
  72. package/dist/core/session-context-prompt.js.map +1 -1
  73. package/dist/discovery/claude.js +10 -0
  74. package/dist/discovery/claude.js.map +1 -1
  75. package/dist/discovery/codex-parser.d.ts +10 -0
  76. package/dist/discovery/codex-parser.d.ts.map +1 -1
  77. package/dist/discovery/codex-parser.js +138 -6
  78. package/dist/discovery/codex-parser.js.map +1 -1
  79. package/dist/discovery/codex.d.ts.map +1 -1
  80. package/dist/discovery/codex.js +49 -2
  81. package/dist/discovery/codex.js.map +1 -1
  82. package/dist/discovery/jsonl-entry-parser.d.ts +38 -0
  83. package/dist/discovery/jsonl-entry-parser.d.ts.map +1 -1
  84. package/dist/discovery/jsonl-entry-parser.js +628 -8
  85. package/dist/discovery/jsonl-entry-parser.js.map +1 -1
  86. package/dist/discovery/jsonl-reader.d.ts +10 -0
  87. package/dist/discovery/jsonl-reader.d.ts.map +1 -1
  88. package/dist/discovery/jsonl-reader.js +50 -2
  89. package/dist/discovery/jsonl-reader.js.map +1 -1
  90. package/dist/hooks/types.d.ts +2 -2
  91. package/dist/index.js +5 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/server/hello-builder.d.ts +22 -0
  94. package/dist/server/hello-builder.d.ts.map +1 -1
  95. package/dist/server/hello-builder.js +31 -0
  96. package/dist/server/hello-builder.js.map +1 -1
  97. package/dist/server/ws-command-handlers.d.ts.map +1 -1
  98. package/dist/server/ws-command-handlers.js +1 -0
  99. package/dist/server/ws-command-handlers.js.map +1 -1
  100. package/dist/server/ws-daemon-event-bridge.d.ts.map +1 -1
  101. package/dist/server/ws-daemon-event-bridge.js +10 -0
  102. package/dist/server/ws-daemon-event-bridge.js.map +1 -1
  103. package/dist/server/ws-session-command-handlers.d.ts.map +1 -1
  104. package/dist/server/ws-session-command-handlers.js +47 -4
  105. package/dist/server/ws-session-command-handlers.js.map +1 -1
  106. package/docs/configuration.md +91 -4
  107. package/package.json +1 -1
@@ -13,7 +13,12 @@ export function parseJSONLEntry(entry) {
13
13
  if (typeof entry !== 'object' || entry === null)
14
14
  return [];
15
15
  const e = entry;
16
- return parseClaudeEntry(e) ?? parseCodexEntry(e) ?? parseCodexCompactedEntry(e) ?? [];
16
+ return (parseClaudeEntry(e) ??
17
+ parseCodexEventMsgEntry(e) ??
18
+ parseCodexEntry(e) ??
19
+ parseCodexCompactedEntry(e) ??
20
+ parseProviderMetadataEntry(e) ??
21
+ []);
17
22
  }
18
23
  function parseClaudeEntry(e) {
19
24
  const type = e.type;
@@ -29,7 +34,7 @@ function parseClaudeEntry(e) {
29
34
  // Simple string content → single text block
30
35
  if (typeof content === 'string') {
31
36
  if (content) {
32
- blocks.push({ kind: 'text', role: type, text: content, ts, uuid });
37
+ blocks.push(...mapClaudeTextToRichBlocks(type, content, ts, uuid));
33
38
  }
34
39
  return blocks;
35
40
  }
@@ -38,7 +43,7 @@ function parseClaudeEntry(e) {
38
43
  for (const block of content) {
39
44
  if (typeof block === 'string') {
40
45
  if (block) {
41
- blocks.push({ kind: 'text', role: type, text: block, ts, uuid });
46
+ blocks.push(...mapClaudeTextToRichBlocks(type, block, ts, uuid));
42
47
  }
43
48
  continue;
44
49
  }
@@ -49,7 +54,7 @@ function parseClaudeEntry(e) {
49
54
  switch (blockType) {
50
55
  case 'text':
51
56
  if (typeof b.text === 'string' && b.text) {
52
- blocks.push({ kind: 'text', role: type, text: b.text, ts, uuid });
57
+ blocks.push(...mapClaudeTextToRichBlocks(type, b.text, ts, uuid));
53
58
  }
54
59
  break;
55
60
  case 'thinking':
@@ -58,8 +63,7 @@ function parseClaudeEntry(e) {
58
63
  }
59
64
  break;
60
65
  case 'tool_use':
61
- blocks.push({
62
- kind: 'tool_use',
66
+ blocks.push(...mapClaudeToolUseToRichBlocks({
63
67
  toolName: b.name || 'unknown',
64
68
  toolId: b.id || '',
65
69
  input: typeof b.input === 'object' && b.input !== null
@@ -67,19 +71,194 @@ function parseClaudeEntry(e) {
67
71
  : {},
68
72
  ts,
69
73
  uuid,
70
- });
74
+ }));
71
75
  break;
72
76
  case 'tool_result': {
73
77
  const toolId = b.tool_use_id || '';
74
78
  const isError = b.is_error === true;
75
79
  const output = extractToolResultContent(b.content);
76
80
  blocks.push({ kind: 'tool_result', toolId, output, isError, ts, uuid });
81
+ blocks.push(...viewportCliEventsFromOutput(output, ts, `${uuid || toolId}-viewport-cli`));
77
82
  break;
78
83
  }
79
84
  }
80
85
  }
81
86
  return blocks;
82
87
  }
88
+ function mapClaudeTextToRichBlocks(role, text, ts, uuid) {
89
+ if (role === 'user') {
90
+ const localCommandBlocks = claudeLocalCommandEventsFromText(text, ts, uuid);
91
+ if (localCommandBlocks)
92
+ return localCommandBlocks;
93
+ }
94
+ return [
95
+ { kind: 'text', role, text, ts, uuid },
96
+ ...viewportPlanEventsFromText(text, ts, `${uuid}-viewport-plan`),
97
+ ];
98
+ }
99
+ function claudeLocalCommandEventsFromText(text, ts, uuid) {
100
+ if (!isClaudeLocalCommandText(text))
101
+ return null;
102
+ if (/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/i.test(text))
103
+ return [];
104
+ const commandName = taggedText(text, 'command-name');
105
+ if (commandName) {
106
+ const slashCommand = commandName.startsWith('/') ? commandName : `/${commandName}`;
107
+ const commandMessage = taggedText(text, 'command-message') || commandName;
108
+ const commandArgs = taggedText(text, 'command-args');
109
+ return [
110
+ {
111
+ kind: 'event',
112
+ title: `Claude command: ${slashCommand}`,
113
+ body: [commandMessage, commandArgs ? `args ${commandArgs}` : null]
114
+ .filter(Boolean)
115
+ .join('\n'),
116
+ tone: 'muted',
117
+ ts,
118
+ uuid: `${uuid}:local-command:${commandName}`,
119
+ },
120
+ ];
121
+ }
122
+ const stdout = taggedText(text, 'local-command-stdout');
123
+ if (stdout) {
124
+ return [
125
+ {
126
+ kind: 'event',
127
+ title: 'Claude command output',
128
+ body: stdout,
129
+ tone: 'muted',
130
+ ts,
131
+ uuid: `${uuid}:local-command-stdout`,
132
+ },
133
+ ];
134
+ }
135
+ const stderr = taggedText(text, 'local-command-stderr');
136
+ if (stderr) {
137
+ return [
138
+ {
139
+ kind: 'event',
140
+ title: 'Claude command warning',
141
+ body: stderr,
142
+ tone: 'warning',
143
+ ts,
144
+ uuid: `${uuid}:local-command-stderr`,
145
+ },
146
+ ];
147
+ }
148
+ const taskNotification = taggedText(text, 'task-notification');
149
+ if (taskNotification) {
150
+ return [
151
+ {
152
+ kind: 'event',
153
+ title: 'Claude task notification',
154
+ body: taskNotification,
155
+ tone: 'muted',
156
+ ts,
157
+ uuid: `${uuid}:task-notification`,
158
+ },
159
+ ];
160
+ }
161
+ const teammateMessage = taggedText(text, 'teammate-message');
162
+ if (teammateMessage) {
163
+ return [
164
+ {
165
+ kind: 'event',
166
+ title: 'Teammate message',
167
+ body: teammateMessage,
168
+ tone: 'muted',
169
+ ts,
170
+ uuid: `${uuid}:teammate-message`,
171
+ },
172
+ ];
173
+ }
174
+ return [];
175
+ }
176
+ function mapClaudeToolUseToRichBlocks(input) {
177
+ if (input.toolName === 'Bash') {
178
+ const command = typeof input.input.command === 'string' ? input.input.command.trim() : '';
179
+ if (command) {
180
+ return [
181
+ {
182
+ kind: 'command',
183
+ command,
184
+ cwd: typeof input.input.cwd === 'string' ? input.input.cwd : undefined,
185
+ status: 'started',
186
+ ts: input.ts,
187
+ uuid: input.toolId || input.uuid,
188
+ },
189
+ ];
190
+ }
191
+ }
192
+ if (input.toolName === 'Agent') {
193
+ const subagentType = typeof input.input.subagent_type === 'string' ? input.input.subagent_type.trim() : '';
194
+ const description = typeof input.input.description === 'string' ? input.input.description.trim() : '';
195
+ const prompt = typeof input.input.prompt === 'string' ? input.input.prompt.trim() : '';
196
+ return [
197
+ {
198
+ kind: 'event',
199
+ title: `Subagent started${subagentType ? `: ${subagentType}` : ''}`,
200
+ body: [description, prompt ? firstLine(prompt) : null].filter(Boolean).join('\n'),
201
+ tone: 'muted',
202
+ ts: input.ts,
203
+ uuid: input.toolId || input.uuid,
204
+ },
205
+ ];
206
+ }
207
+ if (input.toolName.startsWith('Task')) {
208
+ const subject = typeof input.input.task_subject === 'string'
209
+ ? input.input.task_subject.trim()
210
+ : typeof input.input.subject === 'string'
211
+ ? input.input.subject.trim()
212
+ : '';
213
+ const description = typeof input.input.task_description === 'string'
214
+ ? input.input.task_description.trim()
215
+ : typeof input.input.description === 'string'
216
+ ? input.input.description.trim()
217
+ : '';
218
+ return [
219
+ {
220
+ kind: 'event',
221
+ title: subject ? `${input.toolName}: ${subject}` : `Claude task: ${input.toolName}`,
222
+ body: description || safeJsonPreview(input.input),
223
+ tone: input.toolName === 'TaskCompleted' ? 'success' : 'muted',
224
+ ts: input.ts,
225
+ uuid: input.toolId || input.uuid,
226
+ },
227
+ ];
228
+ }
229
+ if (isClaudeFileMutationTool(input.toolName)) {
230
+ const filePath = typeof input.input.file_path === 'string'
231
+ ? input.input.file_path
232
+ : typeof input.input.path === 'string'
233
+ ? input.input.path
234
+ : undefined;
235
+ return [
236
+ {
237
+ kind: 'file_change',
238
+ path: filePath,
239
+ operation: input.toolName,
240
+ ts: input.ts,
241
+ uuid: input.toolId || input.uuid,
242
+ },
243
+ ];
244
+ }
245
+ return [
246
+ {
247
+ kind: 'tool_use',
248
+ toolName: input.toolName,
249
+ toolId: input.toolId,
250
+ input: input.input,
251
+ ts: input.ts,
252
+ uuid: input.uuid,
253
+ },
254
+ ];
255
+ }
256
+ function isClaudeFileMutationTool(toolName) {
257
+ return (toolName === 'Edit' ||
258
+ toolName === 'Write' ||
259
+ toolName === 'MultiEdit' ||
260
+ toolName === 'NotebookEdit');
261
+ }
83
262
  function parseCodexEntry(e) {
84
263
  const envelopeType = e.type;
85
264
  if (envelopeType !== 'response_item')
@@ -102,6 +281,7 @@ function parseCodexEntry(e) {
102
281
  const text = content.trim();
103
282
  if (text) {
104
283
  messageBlocks.push({ kind: 'text', role, text, ts, uuid: baseUuid });
284
+ messageBlocks.push(...viewportPlanEventsFromText(text, ts, `${baseUuid}-viewport-plan`));
105
285
  }
106
286
  return messageBlocks;
107
287
  }
@@ -120,6 +300,7 @@ function parseCodexEntry(e) {
120
300
  ts,
121
301
  uuid: `${baseUuid}-${i}`,
122
302
  });
303
+ messageBlocks.push(...viewportPlanEventsFromText(text, ts, `${baseUuid}-${i}-viewport-plan`));
123
304
  }
124
305
  return messageBlocks;
125
306
  }
@@ -152,6 +333,7 @@ function parseCodexEntry(e) {
152
333
  ts,
153
334
  uuid: codexUuid(payload, ts, 'tool-result'),
154
335
  },
336
+ ...viewportCliEventsFromOutput(output, ts, `${codexUuid(payload, ts, 'tool-result')}-viewport-cli`),
155
337
  ];
156
338
  }
157
339
  if (itemType === 'reasoning') {
@@ -167,7 +349,137 @@ function parseCodexEntry(e) {
167
349
  },
168
350
  ];
169
351
  }
170
- return [];
352
+ return [
353
+ {
354
+ kind: 'event',
355
+ title: `Provider item: ${itemType || 'unknown'}`,
356
+ body: safeJsonPreview(payload),
357
+ tone: 'muted',
358
+ ts,
359
+ uuid: codexUuid(payload, ts, 'item'),
360
+ },
361
+ ];
362
+ }
363
+ function parseCodexEventMsgEntry(e) {
364
+ if (e.type !== 'event_msg')
365
+ return null;
366
+ const payload = typeof e.payload === 'object' && e.payload !== null
367
+ ? e.payload
368
+ : null;
369
+ if (!payload)
370
+ return [];
371
+ const ts = timestampFromEntry(e, payload);
372
+ const itemType = payload.type;
373
+ const uuid = codexUuid(payload, ts, itemType || 'event');
374
+ if (itemType === 'user_message') {
375
+ const text = extractText(payload.message ?? payload.text);
376
+ return text ? [{ kind: 'text', role: 'user', text, ts, uuid }] : [];
377
+ }
378
+ if (itemType === 'agent_message' || itemType === 'assistant_message') {
379
+ const text = extractText(payload.message ?? payload.text);
380
+ return text
381
+ ? [
382
+ { kind: 'text', role: 'assistant', text, ts, uuid },
383
+ ...viewportPlanEventsFromText(text, ts, `${uuid}-viewport-plan`),
384
+ ]
385
+ : [];
386
+ }
387
+ if (itemType === 'agent_message_delta' || itemType === 'agent_message_content_delta') {
388
+ const text = extractText(payload.delta ?? payload.text ?? payload.content);
389
+ return text ? [{ kind: 'text', role: 'assistant', text, ts, uuid }] : [];
390
+ }
391
+ if (itemType === 'exec_command_begin') {
392
+ return [
393
+ {
394
+ kind: 'command',
395
+ command: commandToString(payload.command),
396
+ cwd: typeof payload.cwd === 'string' ? payload.cwd : undefined,
397
+ status: 'started',
398
+ ts,
399
+ uuid,
400
+ },
401
+ ];
402
+ }
403
+ if (itemType === 'exec_command_end') {
404
+ return [
405
+ {
406
+ kind: 'command',
407
+ command: commandToString(payload.command),
408
+ cwd: typeof payload.cwd === 'string' ? payload.cwd : undefined,
409
+ status: 'completed',
410
+ exitCode: numberOrNull(payload.exit_code),
411
+ output: extractText(payload.aggregated_output ?? payload.formatted_output ?? payload.stdout),
412
+ durationMs: durationToMs(payload.duration),
413
+ ts,
414
+ uuid,
415
+ },
416
+ ];
417
+ }
418
+ if (itemType === 'exec_approval_request') {
419
+ const command = commandToString(payload.command);
420
+ const reason = typeof payload.reason === 'string' ? payload.reason : '';
421
+ return [
422
+ {
423
+ kind: 'approval',
424
+ title: 'Command approval needed',
425
+ body: [reason, command].filter(Boolean).join('\n'),
426
+ input: payload,
427
+ ts,
428
+ uuid,
429
+ },
430
+ ];
431
+ }
432
+ if (itemType === 'turn_diff') {
433
+ return [
434
+ {
435
+ kind: 'file_change',
436
+ diff: typeof payload.unified_diff === 'string' ? payload.unified_diff : undefined,
437
+ operation: 'diff',
438
+ ts,
439
+ uuid,
440
+ },
441
+ ];
442
+ }
443
+ if (itemType === 'turn_aborted' || itemType === 'turn_failed') {
444
+ return [
445
+ {
446
+ kind: 'event',
447
+ title: itemType === 'turn_aborted' ? 'Turn aborted' : 'Turn failed',
448
+ body: extractText(payload.error ?? payload.message) || itemType,
449
+ tone: 'danger',
450
+ ts,
451
+ uuid,
452
+ },
453
+ ];
454
+ }
455
+ if (itemType === 'thread_name_updated') {
456
+ const title = typeof payload.thread_name === 'string' ? payload.thread_name : '';
457
+ return title
458
+ ? [{ kind: 'event', title: 'Session renamed', body: title, tone: 'muted', ts, uuid }]
459
+ : [];
460
+ }
461
+ if (itemType === 'token_count') {
462
+ return [
463
+ {
464
+ kind: 'usage',
465
+ inputTokens: numberOrUndefined(payload.input_tokens ?? payload.inputTokens),
466
+ outputTokens: numberOrUndefined(payload.output_tokens ?? payload.outputTokens),
467
+ totalTokens: numberOrUndefined(payload.total_tokens ?? payload.totalTokens),
468
+ ts,
469
+ uuid,
470
+ },
471
+ ];
472
+ }
473
+ return [
474
+ {
475
+ kind: 'event',
476
+ title: `Provider event: ${itemType || 'unknown'}`,
477
+ body: safeJsonPreview(payload),
478
+ tone: 'muted',
479
+ ts,
480
+ uuid,
481
+ },
482
+ ];
171
483
  }
172
484
  function parseCodexCompactedEntry(e) {
173
485
  if (e.type !== 'compacted')
@@ -203,6 +515,44 @@ function parseCodexCompactedEntry(e) {
203
515
  }
204
516
  return blocks;
205
517
  }
518
+ function parseProviderMetadataEntry(e) {
519
+ const type = typeof e.type === 'string' ? e.type : '';
520
+ if (!type)
521
+ return null;
522
+ const ts = timestampFromEntry(e);
523
+ const uuid = typeof e.uuid === 'string' && e.uuid ? e.uuid : `provider-${type}-${ts}`;
524
+ if (type === 'custom-title') {
525
+ const title = stringField(e, 'customTitle') || stringField(e, 'custom_title') || stringField(e, 'title');
526
+ return title
527
+ ? [{ kind: 'event', title: 'Session renamed', body: title, tone: 'muted', ts, uuid }]
528
+ : [];
529
+ }
530
+ if (type === 'summary' || type === 'compact' || type === 'compaction') {
531
+ const summary = extractText(e.summary ?? e.message ?? e.content);
532
+ return [
533
+ {
534
+ kind: 'event',
535
+ title: 'Conversation compacted',
536
+ body: summary || safeJsonPreview(e),
537
+ tone: 'muted',
538
+ ts,
539
+ uuid,
540
+ },
541
+ ];
542
+ }
543
+ if (type === 'system' && isInjectedEnvironmentEntry(e))
544
+ return [];
545
+ return [
546
+ {
547
+ kind: 'event',
548
+ title: `Provider event: ${type}`,
549
+ body: safeJsonPreview(e),
550
+ tone: 'muted',
551
+ ts,
552
+ uuid,
553
+ },
554
+ ];
555
+ }
206
556
  // ---------------------------------------------------------------------------
207
557
  // Helpers
208
558
  // ---------------------------------------------------------------------------
@@ -230,6 +580,176 @@ function extractToolResultContent(content) {
230
580
  }
231
581
  return '';
232
582
  }
583
+ function viewportCliEventsFromOutput(output, ts, uuidPrefix) {
584
+ const payload = parseViewportCliJson(output);
585
+ if (!payload)
586
+ return [];
587
+ const schemaVersion = typeof payload.schema_version === 'string' ? payload.schema_version : '';
588
+ const ok = payload.ok !== false;
589
+ if (schemaVersion === 'viewport.cli.context_propose/v1') {
590
+ const candidateId = stringField(payload, 'candidate_id');
591
+ const providerId = stringField(payload, 'provider_id');
592
+ const status = stringField(payload, 'status') || (ok ? 'pending_review' : 'not_queued');
593
+ const digest = stringField(payload, 'payload_digest');
594
+ return [
595
+ {
596
+ kind: 'event',
597
+ title: ok ? 'Context candidate proposed' : 'Context proposal did not queue',
598
+ body: [
599
+ candidateId ? `candidate ${candidateId}` : null,
600
+ providerId ? `provider ${providerId}` : null,
601
+ status ? `status ${status}` : null,
602
+ digest ? digest : null,
603
+ ]
604
+ .filter(Boolean)
605
+ .join('\n'),
606
+ tone: ok ? 'warning' : 'muted',
607
+ ts,
608
+ uuid: `${uuidPrefix}:context-propose:${candidateId || status || schemaVersion}`,
609
+ },
610
+ ];
611
+ }
612
+ if (schemaVersion === 'viewport.cli.context_search/v1') {
613
+ const count = numberOrUndefined(payload.result_count ?? payload.count);
614
+ return [
615
+ {
616
+ kind: 'event',
617
+ title: 'Context searched',
618
+ body: typeof count === 'number'
619
+ ? `${count.toLocaleString()} result${count === 1 ? '' : 's'}`
620
+ : 'Context search completed',
621
+ tone: 'muted',
622
+ ts,
623
+ uuid: `${uuidPrefix}:context-search`,
624
+ },
625
+ ];
626
+ }
627
+ if (schemaVersion === 'viewport.cli.context_get/v1') {
628
+ return [
629
+ {
630
+ kind: 'event',
631
+ title: 'Context loaded',
632
+ body: stringField(payload, 'provider_id') || 'Context item loaded',
633
+ tone: 'muted',
634
+ ts,
635
+ uuid: `${uuidPrefix}:context-get`,
636
+ },
637
+ ];
638
+ }
639
+ if (schemaVersion === 'viewport.cli.context_use/v1') {
640
+ const providerId = stringField(payload, 'provider_id');
641
+ return [
642
+ {
643
+ kind: 'event',
644
+ title: 'Context attached to repo',
645
+ body: providerId ? `provider ${providerId}` : 'Context provider attached',
646
+ tone: ok ? 'success' : 'warning',
647
+ ts,
648
+ uuid: `${uuidPrefix}:context-use:${providerId || schemaVersion}`,
649
+ },
650
+ ];
651
+ }
652
+ if (schemaVersion === 'viewport.cli.context_create/v1') {
653
+ const vaultId = stringField(payload, 'vault_id') || stringField(payload, 'id');
654
+ return [
655
+ {
656
+ kind: 'event',
657
+ title: 'Context vault created',
658
+ body: vaultId ? `vault ${vaultId}` : 'Context vault created',
659
+ tone: ok ? 'success' : 'warning',
660
+ ts,
661
+ uuid: `${uuidPrefix}:context-create:${vaultId || schemaVersion}`,
662
+ },
663
+ ];
664
+ }
665
+ if (schemaVersion === 'viewport.cli.session_manifest/v1') {
666
+ return [
667
+ {
668
+ kind: 'event',
669
+ title: 'Viewport repo config read',
670
+ body: stringField(payload, 'manifest_digest') || 'Session manifest resolved',
671
+ tone: 'muted',
672
+ ts,
673
+ uuid: `${uuidPrefix}:session-manifest`,
674
+ },
675
+ ];
676
+ }
677
+ return [];
678
+ }
679
+ function viewportPlanEventsFromText(text, ts, uuidPrefix) {
680
+ const payload = parseViewportPlanPayload(text);
681
+ if (!payload)
682
+ return [];
683
+ const schema = stringField(payload, 'schema');
684
+ if (schema !== 'viewport.plan_proposal/v1')
685
+ return [];
686
+ const title = stringField(payload, 'title') || 'Plan proposal';
687
+ const summary = stringField(payload, 'summary');
688
+ const source = stringField(payload, 'source');
689
+ const body = [summary, source ? `source ${source}` : null].filter(Boolean).join('\n');
690
+ return [
691
+ {
692
+ kind: 'event',
693
+ title: `Plan draft emitted: ${title}`,
694
+ body: body || 'A Viewport plan proposal block was emitted for trusted-edge capture.',
695
+ tone: 'warning',
696
+ ts,
697
+ uuid: `${uuidPrefix}:${title}`,
698
+ },
699
+ ];
700
+ }
701
+ function parseViewportPlanPayload(text) {
702
+ const fence = /```viewport-plan\s*\n([\s\S]*?)```/i.exec(text);
703
+ const comment = /<!--\s*viewport-plan\s*\n([\s\S]*?)-->/i.exec(text);
704
+ const raw = fence?.[1] ?? comment?.[1];
705
+ if (!raw)
706
+ return null;
707
+ const trimmed = raw.trim();
708
+ if (!trimmed.includes('viewport.plan_proposal/v1'))
709
+ return null;
710
+ try {
711
+ const parsed = JSON.parse(trimmed);
712
+ if (typeof parsed === 'object' && parsed !== null)
713
+ return parsed;
714
+ }
715
+ catch {
716
+ // YAML-frontmatter style plan blocks are allowed elsewhere. The session
717
+ // reader keeps them as normal transcript text until a structured parser is
718
+ // needed for timeline links.
719
+ }
720
+ return null;
721
+ }
722
+ function isInjectedEnvironmentEntry(entry) {
723
+ const content = extractText(entry.content ?? entry.message);
724
+ return content.includes('<environment_context>') || content.includes('<system-reminder>');
725
+ }
726
+ function parseViewportCliJson(output) {
727
+ const trimmed = output.trim();
728
+ if (!trimmed.includes('schema_version'))
729
+ return null;
730
+ const candidates = [trimmed];
731
+ const firstBrace = trimmed.indexOf('{');
732
+ const lastBrace = trimmed.lastIndexOf('}');
733
+ if (firstBrace >= 0 && lastBrace > firstBrace) {
734
+ candidates.push(trimmed.slice(firstBrace, lastBrace + 1));
735
+ }
736
+ for (const candidate of candidates) {
737
+ try {
738
+ const parsed = JSON.parse(candidate);
739
+ if (typeof parsed === 'object' && parsed !== null) {
740
+ return parsed;
741
+ }
742
+ }
743
+ catch {
744
+ // Keep trying the next candidate.
745
+ }
746
+ }
747
+ return null;
748
+ }
749
+ function stringField(record, field) {
750
+ const value = record[field];
751
+ return typeof value === 'string' ? value.trim() : '';
752
+ }
233
753
  function timestampFromEntry(entry, payload) {
234
754
  const fromPayload = payload?.timestamp;
235
755
  if (typeof fromPayload === 'string' && fromPayload)
@@ -329,4 +849,104 @@ function extractCodexReasoning(payload) {
329
849
  const fallback = extractCodexContentText(payload);
330
850
  return fallback;
331
851
  }
852
+ function extractText(value) {
853
+ if (typeof value === 'string')
854
+ return value.trim();
855
+ if (Array.isArray(value)) {
856
+ return value
857
+ .map((item) => extractText(item))
858
+ .filter(Boolean)
859
+ .join('\n')
860
+ .trim();
861
+ }
862
+ if (!value || typeof value !== 'object')
863
+ return '';
864
+ const rec = value;
865
+ const candidates = [
866
+ rec.text,
867
+ rec.output_text,
868
+ rec.input_text,
869
+ rec.value,
870
+ rec.content,
871
+ rec.message,
872
+ ];
873
+ for (const candidate of candidates) {
874
+ const text = extractText(candidate);
875
+ if (text)
876
+ return text;
877
+ }
878
+ return '';
879
+ }
880
+ function isClaudeLocalCommandText(text) {
881
+ return /<(?:local-command-caveat|local-command-std(?:out|err)|command-name|command-message|command-args|task-notification|teammate-message)>/i.test(text);
882
+ }
883
+ function firstLine(text) {
884
+ return (text
885
+ .split(/\r?\n/)
886
+ .find((line) => line.trim())
887
+ ?.trim() ?? '');
888
+ }
889
+ function taggedText(text, tag) {
890
+ const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
891
+ const match = new RegExp(`<${escaped}>([\\s\\S]*?)<\\/${escaped}>`, 'i').exec(text);
892
+ return match
893
+ ? stripAnsi(match[1] ?? '')
894
+ .replace(/\s+/g, ' ')
895
+ .trim()
896
+ : '';
897
+ }
898
+ function stripAnsi(text) {
899
+ // ANSI CSI escape sequences emitted by Claude local command status lines.
900
+ return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
901
+ }
902
+ function commandToString(value) {
903
+ if (Array.isArray(value)) {
904
+ return value
905
+ .map((part) => String(part))
906
+ .join(' ')
907
+ .trim();
908
+ }
909
+ if (typeof value === 'string')
910
+ return value.trim();
911
+ return '';
912
+ }
913
+ function numberOrNull(value) {
914
+ if (typeof value === 'number' && Number.isFinite(value))
915
+ return value;
916
+ if (typeof value === 'string') {
917
+ const parsed = Number(value);
918
+ if (Number.isFinite(parsed))
919
+ return parsed;
920
+ }
921
+ return null;
922
+ }
923
+ function numberOrUndefined(value) {
924
+ const n = numberOrNull(value);
925
+ return n === null ? undefined : n;
926
+ }
927
+ function durationToMs(value) {
928
+ if (typeof value === 'number' && Number.isFinite(value))
929
+ return value;
930
+ if (typeof value === 'string') {
931
+ const parsed = Number(value);
932
+ if (Number.isFinite(parsed))
933
+ return parsed;
934
+ }
935
+ if (!value || typeof value !== 'object')
936
+ return null;
937
+ const rec = value;
938
+ return (numberOrNull(rec.ms) ??
939
+ numberOrNull(rec.millis) ??
940
+ numberOrNull(rec.milliseconds) ??
941
+ (typeof rec.secs === 'number' ? rec.secs * 1000 : null));
942
+ }
943
+ function safeJsonPreview(value) {
944
+ try {
945
+ const json = JSON.stringify(value);
946
+ return json.length > 2_000 ? `${json.slice(0, 2_000)}…` : json;
947
+ }
948
+ catch {
949
+ return String(value);
950
+ }
951
+ }
332
952
  //# sourceMappingURL=jsonl-entry-parser.js.map