oh-my-knowledge 0.51.2 → 0.52.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 (49) hide show
  1. package/README.md +27 -6
  2. package/README.zh.md +27 -6
  3. package/dist/assets/agent-skills/omk/SKILL.md +8 -8
  4. package/dist/observability/codex-conversation-index.d.ts +88 -0
  5. package/dist/observability/codex-conversation-index.js +566 -0
  6. package/dist/observability/codex-protocol.d.ts +12 -0
  7. package/dist/observability/codex-protocol.js +78 -0
  8. package/dist/observability/codex-tool-status.d.ts +16 -0
  9. package/dist/observability/codex-tool-status.js +114 -0
  10. package/dist/observability/codex-trace-adapter.js +342 -119
  11. package/dist/observability/conversation-catalog.d.ts +38 -0
  12. package/dist/observability/conversation-catalog.js +573 -0
  13. package/dist/observability/conversation-index-process.d.ts +1 -0
  14. package/dist/observability/conversation-index-process.js +31 -0
  15. package/dist/observability/conversation-view-model.d.ts +5 -0
  16. package/dist/observability/conversation-view-model.js +100 -0
  17. package/dist/observability/experience.d.ts +3 -1
  18. package/dist/observability/experience.js +248 -7
  19. package/dist/observability/inbox.js +40 -2
  20. package/dist/observability/knowledge-debugger.d.ts +4 -0
  21. package/dist/observability/knowledge-debugger.js +364 -0
  22. package/dist/observability/polling-subscription-hub.d.ts +27 -0
  23. package/dist/observability/polling-subscription-hub.js +149 -0
  24. package/dist/observability/source-record-archive.d.ts +13 -0
  25. package/dist/observability/source-record-archive.js +324 -0
  26. package/dist/observability/task-window.d.ts +8 -0
  27. package/dist/observability/task-window.js +36 -0
  28. package/dist/observability/trace-ir.d.ts +66 -1
  29. package/dist/observability/trace-source.d.ts +1 -0
  30. package/dist/observability/trace-source.js +12 -5
  31. package/dist/observability/turn-index.d.ts +3 -0
  32. package/dist/observability/turn-index.js +251 -0
  33. package/dist/renderer/conversation-renderer.d.ts +9 -0
  34. package/dist/renderer/conversation-renderer.js +410 -0
  35. package/dist/renderer/icons.js +1 -0
  36. package/dist/renderer/inline-markdown.d.ts +6 -0
  37. package/dist/renderer/inline-markdown.js +158 -0
  38. package/dist/renderer/knowledge-debugger-renderer.d.ts +9 -0
  39. package/dist/renderer/knowledge-debugger-renderer.js +2280 -0
  40. package/dist/renderer/observation-inbox-renderer.js +9 -0
  41. package/dist/renderer/skill-list-renderer.js +2 -1
  42. package/dist/renderer/trajectory-live.d.ts +42 -0
  43. package/dist/renderer/trajectory-live.js +258 -0
  44. package/dist/renderer/trajectory-routing.d.ts +60 -0
  45. package/dist/renderer/trajectory-routing.js +351 -0
  46. package/dist/server/report-server.d.ts +4 -1
  47. package/dist/server/report-server.js +274 -6
  48. package/dist/types/observability.d.ts +225 -1
  49. package/package.json +8 -5
@@ -1,38 +1,11 @@
1
1
  /** Codex rollout JSONL -> source-neutral Trace IR. */
2
2
  import { basename } from 'node:path';
3
- import { isToolResultFailureText } from './text-signals.js';
4
3
  import { correlateTraceToolEvents, createTraceId, normalizeTraceTimestamp, traceTimestampBounds, } from './trace-ir.js';
5
4
  import { nonNegativeMetric, optionalTokenCount, splitInclusiveInputTokens, tokenCount, } from '../shared/token-usage.js';
6
5
  import { normalizeToolIdentity } from '../shared/tool-identity.js';
7
6
  import { extractCodexExecCommands } from './codex-exec-command.js';
8
- const CODEX_RESPONSE_ITEM_TYPES = new Set([
9
- 'message',
10
- 'reasoning',
11
- 'tool_search_call',
12
- 'tool_search_output',
13
- 'web_search_call',
14
- 'image_generation_call',
15
- 'function_call',
16
- 'custom_tool_call',
17
- 'function_call_output',
18
- 'custom_tool_call_output',
19
- ]);
20
- const CODEX_EVENT_MESSAGE_TYPES = new Set([
21
- 'token_count',
22
- 'task_started',
23
- 'task_complete',
24
- 'turn_aborted',
25
- 'user_message',
26
- 'agent_message',
27
- 'mcp_tool_call_end',
28
- 'patch_apply_end',
29
- 'agent_reasoning',
30
- 'thread_settings_applied',
31
- 'web_search_end',
32
- 'context_compacted',
33
- 'image_generation_end',
34
- 'thread_goal_updated',
35
- ]);
7
+ import { codexUserDisplayText, codexUserMessageOrigin, isCodexEventMessageType, isCodexRecordConsumedWithoutDirectEvent, isCodexResponseItemType, } from './codex-protocol.js';
8
+ import { codexRuntimeToolOutcome, codexToolOutputOutcome, codexToolStatusFromValue, } from './codex-tool-status.js';
36
9
  export function isCodexJsonl(records) {
37
10
  return records.some((record) => {
38
11
  const raw = asCodexRecord(record);
@@ -48,11 +21,10 @@ export function isCodexJsonl(records) {
48
21
  || stringValue(raw.payload.model) !== undefined;
49
22
  }
50
23
  if (raw.type === 'response_item') {
51
- return payloadType !== undefined && CODEX_RESPONSE_ITEM_TYPES.has(payloadType);
24
+ return isCodexResponseItemType(payloadType);
52
25
  }
53
26
  return raw.type === 'event_msg'
54
- && payloadType !== undefined
55
- && CODEX_EVENT_MESSAGE_TYPES.has(payloadType);
27
+ && isCodexEventMessageType(payloadType);
56
28
  });
57
29
  }
58
30
  export function isCodexGuardianRollout(records) {
@@ -118,8 +90,10 @@ function convertCodexRecords(rawRecords, runId) {
118
90
  const representedPatchCalls = new Set();
119
91
  const representedPatchResults = new Set();
120
92
  const duplicateEventMessageIndexes = indexDuplicateEventMessages(rawRecords);
93
+ const duplicateAgentReasoningIndexes = indexDuplicateAgentReasoningMessages(rawRecords);
121
94
  let activeModel;
122
95
  let activeTurnId;
96
+ let emittedSessionContext = false;
123
97
  let previousTotalUsageFingerprint;
124
98
  rawRecords.forEach((value, sourceIndex) => {
125
99
  const record = asCodexRecord(value);
@@ -136,14 +110,109 @@ function convertCodexRecords(rawRecords, runId) {
136
110
  timestamp,
137
111
  turnId: activeTurnId,
138
112
  };
139
- // Desktop may repeat session_meta during a long rollout. Metadata is read
140
- // once above and is transparent to the event stream; retaining later copies
141
- // as unknown events also breaks otherwise adjacent mirrored-message pairs.
142
- if (record.type === 'session_meta')
113
+ if (record.type === 'session_meta') {
114
+ // Desktop may repeat session_meta during a long rollout. One normalized
115
+ // context event is enough; source records retain every native copy.
116
+ if (emittedSessionContext)
117
+ return;
118
+ emittedSessionContext = true;
119
+ const runtimeName = stringValue(payload.originator);
120
+ const runtimeVersion = stringValue(payload.cli_version);
121
+ const availableTools = codexAvailableTools(payload.dynamic_tools);
122
+ const instructions = codexPlaintext(payload.base_instructions);
123
+ const memoryMode = stringValue(payload.memory_mode);
124
+ const historyMode = stringValue(payload.history_mode);
125
+ const contextWindowId = nestedString(payload.context_window, 'window_id');
126
+ const modelProvider = stringValue(payload.model_provider);
127
+ const multiAgentVersion = stringValue(payload.multi_agent_version);
128
+ if (runtimeName
129
+ || runtimeVersion
130
+ || availableTools
131
+ || instructions
132
+ || memoryMode
133
+ || historyMode
134
+ || contextWindowId
135
+ || modelProvider
136
+ || multiAgentVersion) {
137
+ events.push({
138
+ ...base,
139
+ eventKind: 'runtime_context',
140
+ eventId: eventId('session-context'),
141
+ runtimeKind: 'session_context',
142
+ runtimeName,
143
+ runtimeVersion,
144
+ modelProvider,
145
+ multiAgentVersion,
146
+ memoryMode,
147
+ historyMode,
148
+ contextWindowId,
149
+ availableTools,
150
+ instructions: instructions || undefined,
151
+ summary: codexSessionContextSummary({
152
+ runtimeName,
153
+ runtimeVersion,
154
+ memoryMode,
155
+ historyMode,
156
+ availableTools,
157
+ }),
158
+ });
159
+ }
143
160
  return;
161
+ }
144
162
  if (record.type === 'turn_context') {
145
163
  activeModel = stringValue(payload.model) ?? activeModel;
146
164
  activeTurnId = stringValue(payload.turn_id) ?? activeTurnId;
165
+ events.push({
166
+ ...base,
167
+ turnId: activeTurnId,
168
+ eventKind: 'runtime_context',
169
+ eventId: eventId('runtime-context'),
170
+ runtimeKind: 'execution_context',
171
+ cwd: stringValue(payload.cwd),
172
+ workspaceRoots: stringArray(payload.workspace_roots),
173
+ currentDate: stringValue(payload.current_date),
174
+ timezone: stringValue(payload.timezone),
175
+ model: activeModel,
176
+ reasoningEffort: stringValue(payload.effort),
177
+ personality: stringValue(payload.personality),
178
+ approvalPolicy: stringValue(payload.approval_policy),
179
+ approvalReviewer: stringValue(payload.approvals_reviewer),
180
+ permissionProfile: nestedString(payload.permission_profile, 'type'),
181
+ sandboxMode: nestedString(payload.sandbox_policy, 'type'),
182
+ collaborationMode: stringValue(payload.collaboration_mode)
183
+ ?? nestedString(payload.collaboration_mode, 'mode'),
184
+ realtimeActive: booleanValue(payload.realtime_active),
185
+ multiAgentMode: stringValue(payload.multi_agent_mode),
186
+ multiAgentVersion: stringValue(payload.multi_agent_version),
187
+ instructions: stringValue(payload.user_instructions),
188
+ summary: stringValue(payload.summary),
189
+ });
190
+ return;
191
+ }
192
+ if (record.type === 'response_item' && payloadType === 'reasoning') {
193
+ const reasoning = codexReasoningPlaintext(payload);
194
+ if (reasoning) {
195
+ events.push({
196
+ ...base,
197
+ eventKind: 'model_activity',
198
+ eventId: eventId('model-activity'),
199
+ activityKind: 'reasoning',
200
+ contentVisibility: 'plaintext',
201
+ text: reasoning.text,
202
+ contentSource: reasoning.contentSource,
203
+ model: activeModel,
204
+ });
205
+ }
206
+ else if (stringValue(payload.encrypted_content)) {
207
+ events.push({
208
+ ...base,
209
+ eventKind: 'model_activity',
210
+ eventId: eventId('model-activity'),
211
+ activityKind: 'reasoning',
212
+ contentVisibility: 'opaque',
213
+ model: activeModel,
214
+ });
215
+ }
147
216
  return;
148
217
  }
149
218
  if (record.type === 'response_item' && payloadType === 'message') {
@@ -161,11 +230,30 @@ function convertCodexRecords(rawRecords, runId) {
161
230
  ? codexUserMessageOrigin(text)
162
231
  : normalizedRole === 'system' ? 'runtime' : 'synthetic',
163
232
  text,
233
+ displayText: normalizedRole === 'user' ? codexUserDisplayText(text) : undefined,
164
234
  model: normalizedRole === 'assistant' ? activeModel : undefined,
165
235
  });
166
236
  }
167
237
  return;
168
238
  }
239
+ if (record.type === 'response_item' && payloadType === 'agent_message') {
240
+ const text = codexContentText(payload.content);
241
+ const passthrough = isObject(payload.internal_chat_message_metadata_passthrough)
242
+ ? payload.internal_chat_message_metadata_passthrough
243
+ : {};
244
+ events.push({
245
+ ...base,
246
+ sourceEventId: stringValue(payload.id),
247
+ turnId: stringValue(passthrough.turn_id) ?? activeTurnId,
248
+ eventKind: 'agent_activity',
249
+ eventId: eventId('agent-communication'),
250
+ activityKind: 'communication',
251
+ author: stringValue(payload.author),
252
+ recipient: stringValue(payload.recipient),
253
+ text: text || undefined,
254
+ });
255
+ return;
256
+ }
169
257
  if (record.type === 'response_item' && payloadType === 'tool_search_call') {
170
258
  const callId = stringValue(payload.call_id) ?? stringValue(payload.id) ?? `codex-tool-search-${sourceIndex}`;
171
259
  events.push(toolCallEvent(eventId('tool-call'), base, callId, normalizeToolIdentity({ sourceName: 'tool_search' }), parseToolInput(payload.arguments), activeModel));
@@ -178,7 +266,7 @@ function convertCodexRecords(rawRecords, runId) {
178
266
  execution: stringValue(payload.execution),
179
267
  tools: summarizeDiscoveredTools(payload.tools),
180
268
  });
181
- events.push(toolResultEvent(eventId('tool-result'), base, callId, output, statusFromCodex(payload.status), stringValue(payload.status) ? 'runtime' : 'unknown'));
269
+ events.push(toolResultEvent(eventId('tool-result'), base, callId, output, codexToolStatusFromValue(payload.status), stringValue(payload.status) ? 'runtime' : 'unknown'));
182
270
  return;
183
271
  }
184
272
  if (record.type === 'response_item' && payloadType === 'web_search_call') {
@@ -187,7 +275,7 @@ function convertCodexRecords(rawRecords, runId) {
187
275
  const resultOccurrence = takeOccurrence(externalResultOccurrences, callId);
188
276
  const completed = externalEnds.byOccurrence.has(mcpCallOccurrenceKey(callId, resultOccurrence));
189
277
  const payloadStatus = stringValue(payload.status);
190
- const explicitStatus = statusFromCodex(payloadStatus);
278
+ const explicitStatus = codexToolStatusFromValue(payloadStatus);
191
279
  events.push(toolCallEvent(eventId('tool-call'), base, callId, normalizeToolIdentity({ sourceName: 'web_search' }), isObject(payload.action) ? payload.action : {}, activeModel));
192
280
  events.push(toolResultEvent(eventId('tool-result'), base, callId, JSON.stringify({ status: stringValue(payload.status) }), explicitStatus !== 'unknown' ? explicitStatus : completed ? 'success' : 'unknown', explicitStatus !== 'unknown' ? 'runtime' : completed ? 'inferred' : 'unknown'));
193
281
  return;
@@ -199,7 +287,7 @@ function convertCodexRecords(rawRecords, runId) {
199
287
  const resultOccurrence = takeOccurrence(externalResultOccurrences, callId);
200
288
  const completed = externalEnds.byOccurrence.has(mcpCallOccurrenceKey(callId, resultOccurrence));
201
289
  const payloadStatus = stringValue(payload.status);
202
- const explicitStatus = statusFromCodex(payloadStatus);
290
+ const explicitStatus = codexToolStatusFromValue(payloadStatus);
203
291
  const inferredSuccess = result.length > 0 || completed;
204
292
  events.push(toolCallEvent(eventId('tool-call'), base, callId, normalizeToolIdentity({ sourceName: 'image_generation' }), { prompt: stringValue(payload.revised_prompt) }, activeModel));
205
293
  events.push(toolResultEvent(eventId('tool-result'), base, callId, JSON.stringify({ status: stringValue(payload.status), resultBytes: result.length }), explicitStatus !== 'unknown' ? explicitStatus : inferredSuccess ? 'success' : 'unknown', explicitStatus !== 'unknown' ? 'runtime' : inferredSuccess ? 'inferred' : 'unknown'));
@@ -237,16 +325,15 @@ function convertCodexRecords(rawRecords, runId) {
237
325
  if (patchEnd)
238
326
  representedPatchResults.add(mcpEndKey);
239
327
  const output = codexContentText(payload.output) || mcpEnd?.output || patchEnd?.output || '';
240
- const runtimeOutcome = runtimeToolEndOutcome(mcpEnd ?? patchEnd);
328
+ const runtimeOutcome = codexRuntimeToolOutcome(mcpEnd ?? patchEnd);
241
329
  const payloadStatus = stringValue(payload.status);
242
330
  const hasRuntimeStatus = runtimeOutcome.present || payloadStatus !== undefined;
243
331
  const explicitStatus = runtimeOutcome.present
244
332
  ? runtimeOutcome.status
245
- : statusFromCodex(payloadStatus);
246
- const bridgeFailure = codexToolOutputFailed(output);
247
- const bridgeSuccess = !bridgeFailure && codexToolOutputSucceeded(output);
248
- const inferredFailure = bridgeFailure || (!bridgeSuccess && isToolResultFailureText(output));
249
- const inferredSuccess = bridgeSuccess;
333
+ : codexToolStatusFromValue(payloadStatus);
334
+ const outputOutcome = codexToolOutputOutcome(output);
335
+ const inferredFailure = outputOutcome.status === 'failure';
336
+ const inferredSuccess = outputOutcome.status === 'success';
250
337
  const completedExternalCall = externalEnds.byOccurrence.has(mcpCallOccurrenceKey(callId, externalOccurrence));
251
338
  const inferredStatus = inferredFailure
252
339
  ? 'failure'
@@ -259,6 +346,12 @@ function convertCodexRecords(rawRecords, runId) {
259
346
  if (record.type === 'event_msg' && payloadType === 'token_count') {
260
347
  const info = isObject(payload.info) ? payload.info : {};
261
348
  const usage = isObject(info.last_token_usage) ? info.last_token_usage : undefined;
349
+ // Codex also emits rate-limit-only snapshots under the token_count
350
+ // protocol name. They describe account capacity, not task token usage.
351
+ // Keep the source record for provenance without manufacturing a usage
352
+ // event or reporting a known protocol shape as unknown.
353
+ if (!usage && payload.info == null && isObject(payload.rate_limits))
354
+ return;
262
355
  if (!isValidCodexTokenUsage(usage)) {
263
356
  events.push({
264
357
  ...base,
@@ -325,6 +418,19 @@ function convertCodexRecords(rawRecords, runId) {
325
418
  activeTurnId = undefined;
326
419
  return;
327
420
  }
421
+ if (record.type === 'event_msg' && payloadType === 'turn_interrupted') {
422
+ events.push({
423
+ ...base,
424
+ turnId: stringValue(payload.turn_id) ?? activeTurnId,
425
+ eventKind: 'lifecycle',
426
+ eventId: eventId('lifecycle'),
427
+ phase: 'turn_interrupted',
428
+ reason: stringValue(payload.reason),
429
+ durationMs: nonNegativeMetric(payload.duration_ms),
430
+ });
431
+ activeTurnId = undefined;
432
+ return;
433
+ }
328
434
  if (record.type === 'event_msg' && payloadType === 'user_message') {
329
435
  const text = stringValue(payload.message);
330
436
  if (text) {
@@ -337,6 +443,7 @@ function convertCodexRecords(rawRecords, runId) {
337
443
  role: 'user',
338
444
  origin: codexUserMessageOrigin(text),
339
445
  text,
446
+ displayText: codexUserDisplayText(text),
340
447
  });
341
448
  }
342
449
  return;
@@ -358,6 +465,90 @@ function convertCodexRecords(rawRecords, runId) {
358
465
  }
359
466
  return;
360
467
  }
468
+ if (record.type === 'event_msg' && payloadType === 'agent_reasoning') {
469
+ if (duplicateAgentReasoningIndexes.has(sourceIndex))
470
+ return;
471
+ const text = codexPlaintext(payload.text);
472
+ if (text) {
473
+ events.push({
474
+ ...base,
475
+ eventKind: 'model_activity',
476
+ eventId: eventId('model-activity'),
477
+ activityKind: 'reasoning',
478
+ contentVisibility: 'plaintext',
479
+ text,
480
+ contentSource: 'text',
481
+ model: activeModel,
482
+ });
483
+ }
484
+ return;
485
+ }
486
+ if (record.type === 'event_msg' && payloadType === 'thread_settings_applied') {
487
+ const settings = isObject(payload.thread_settings) ? payload.thread_settings : payload;
488
+ activeModel = stringValue(settings.model) ?? activeModel;
489
+ events.push({
490
+ ...base,
491
+ eventKind: 'runtime_context',
492
+ eventId: eventId('runtime-settings'),
493
+ runtimeKind: 'settings',
494
+ model: activeModel,
495
+ modelProvider: stringValue(settings.model_provider_id),
496
+ serviceTier: stringValue(settings.service_tier),
497
+ reasoningEffort: stringValue(settings.reasoning_effort),
498
+ reasoningSummary: stringValue(settings.reasoning_summary),
499
+ personality: stringValue(settings.personality),
500
+ cwd: stringValue(settings.cwd),
501
+ approvalPolicy: stringValue(settings.approval_policy),
502
+ approvalReviewer: stringValue(settings.approvals_reviewer),
503
+ permissionProfile: nestedString(settings.permission_profile, 'type'),
504
+ sandboxMode: nestedString(settings.sandbox_policy, 'type'),
505
+ collaborationMode: stringValue(settings.collaboration_mode)
506
+ ?? nestedString(settings.collaboration_mode, 'mode'),
507
+ summary: stringValue(settings.summary),
508
+ });
509
+ return;
510
+ }
511
+ if (record.type === 'event_msg' && payloadType === 'thread_goal_updated') {
512
+ const goal = isObject(payload.goal) ? payload.goal : {};
513
+ events.push({
514
+ ...base,
515
+ eventKind: 'runtime_context',
516
+ eventId: eventId('runtime-goal'),
517
+ runtimeKind: 'goal',
518
+ goal: stringValue(payload.goal)
519
+ ?? stringValue(goal.objective)
520
+ ?? stringValue(goal.text),
521
+ goalStatus: stringValue(goal.status),
522
+ summary: stringValue(payload.summary),
523
+ });
524
+ return;
525
+ }
526
+ if (record.type === 'compacted' || (record.type === 'event_msg' && payloadType === 'context_compacted')) {
527
+ const replacementHistory = Array.isArray(payload.replacement_history)
528
+ ? payload.replacement_history
529
+ : undefined;
530
+ events.push({
531
+ ...base,
532
+ eventKind: 'context_compaction',
533
+ eventId: eventId('context-compaction'),
534
+ summary: stringValue(payload.summary) ?? stringValue(payload.message),
535
+ replacementItemCount: replacementHistory?.length,
536
+ });
537
+ return;
538
+ }
539
+ if (record.type === 'event_msg' && payloadType === 'sub_agent_activity') {
540
+ events.push({
541
+ ...base,
542
+ sourceEventId: stringValue(payload.event_id),
543
+ eventKind: 'agent_activity',
544
+ eventId: eventId('agent-status'),
545
+ activityKind: 'status',
546
+ agentId: stringValue(payload.agent_thread_id),
547
+ agentPath: stringValue(payload.agent_path),
548
+ activity: stringValue(payload.kind),
549
+ });
550
+ return;
551
+ }
361
552
  if (payloadType === 'mcp_tool_call_end') {
362
553
  const end = mcpEnds.bySourceIndex.get(sourceIndex);
363
554
  if (end) {
@@ -374,7 +565,7 @@ function convertCodexRecords(rawRecords, runId) {
374
565
  }
375
566
  return;
376
567
  }
377
- if (isKnownNonMeasurementCodexRecord(record.type, payloadType))
568
+ if (isCodexRecordConsumedWithoutDirectEvent(record.type, payloadType))
378
569
  return;
379
570
  events.push({
380
571
  ...base,
@@ -385,7 +576,7 @@ function convertCodexRecords(rawRecords, runId) {
385
576
  });
386
577
  for (const end of mcpEnds.ordered) {
387
578
  const endKey = mcpCallOccurrenceKey(end.callId, end.occurrence);
388
- const outcome = runtimeToolEndOutcome(end);
579
+ const outcome = codexRuntimeToolOutcome(end);
389
580
  if (!representedMcpCalls.has(endKey)) {
390
581
  events.push({
391
582
  eventKind: 'tool_call',
@@ -419,7 +610,7 @@ function convertCodexRecords(rawRecords, runId) {
419
610
  }
420
611
  for (const end of patchEnds.ordered) {
421
612
  const endKey = mcpCallOccurrenceKey(end.callId, end.occurrence);
422
- const outcome = runtimeToolEndOutcome(end);
613
+ const outcome = codexRuntimeToolOutcome(end);
423
614
  if (!representedPatchCalls.has(endKey)) {
424
615
  events.push({
425
616
  eventKind: 'tool_call',
@@ -452,26 +643,6 @@ function convertCodexRecords(rawRecords, runId) {
452
643
  events.sort((a, b) => a.sourceIndex - b.sourceIndex);
453
644
  return events;
454
645
  }
455
- /**
456
- * Codex persists protocol and UI state alongside behavioral evidence. These
457
- * records are understood by this adapter but intentionally do not become Trace
458
- * IR events. Keeping them out of `unknown` means ingestion warnings remain a
459
- * forward-compatibility signal instead of firing on every normal rollout.
460
- */
461
- function isKnownNonMeasurementCodexRecord(recordType, payloadType) {
462
- if (recordType === 'compacted' || recordType === 'world_state')
463
- return true;
464
- if (recordType === 'response_item' && payloadType === 'reasoning')
465
- return true;
466
- if (recordType !== 'event_msg')
467
- return false;
468
- return payloadType === 'agent_reasoning'
469
- || payloadType === 'thread_settings_applied'
470
- || payloadType === 'web_search_end'
471
- || payloadType === 'context_compacted'
472
- || payloadType === 'image_generation_end'
473
- || payloadType === 'thread_goal_updated';
474
- }
475
646
  function mcpToolRefFromEnd(end) {
476
647
  return normalizeToolIdentity({
477
648
  sourceName: 'mcp_tool_call',
@@ -590,26 +761,6 @@ function takeOccurrence(counts, callId) {
590
761
  function mcpCallOccurrenceKey(callId, occurrence) {
591
762
  return `${callId}\u0000${occurrence}`;
592
763
  }
593
- function runtimeToolEndOutcome(end) {
594
- if (!end)
595
- return { status: 'unknown', present: false };
596
- if (end.status !== undefined) {
597
- const status = statusFromCodex(end.status);
598
- if (status === 'cancelled')
599
- return { status, present: true };
600
- if (status === 'failure' || end.isError === true) {
601
- return { status: 'failure', present: true };
602
- }
603
- if (status === 'success')
604
- return { status, present: true };
605
- return { status: 'unknown', present: true };
606
- }
607
- if (end.isError === true)
608
- return { status: 'failure', present: true };
609
- if (end.isError === false)
610
- return { status: 'success', present: true };
611
- return { status: 'unknown', present: false };
612
- }
613
764
  function normalizeCodexTool(sourceName, rawInput, mcpEnd, sourceNamespace) {
614
765
  const input = parseToolInput(rawInput);
615
766
  const sourceInput = stringValue(input.input);
@@ -654,14 +805,6 @@ function normalizeCodexTool(sourceName, rawInput, mcpEnd, sourceNamespace) {
654
805
  }
655
806
  return { tool, input };
656
807
  }
657
- function codexUserMessageOrigin(text) {
658
- const trimmed = text.trimStart();
659
- if (/^# AGENTS\.md instructions\b/i.test(trimmed))
660
- return 'runtime';
661
- if (/^<(?:app-context|environment_context|permissions instructions|collaboration_mode|apps_instructions|plugins_instructions|skills_instructions|recommended_plugins)>/i.test(trimmed))
662
- return 'runtime';
663
- return 'human';
664
- }
665
808
  function summarizeDiscoveredTools(value) {
666
809
  if (!Array.isArray(value))
667
810
  return [];
@@ -678,15 +821,29 @@ function summarizeDiscoveredTools(value) {
678
821
  }];
679
822
  });
680
823
  }
681
- function statusFromCodex(value) {
682
- const status = stringValue(value)?.toLowerCase();
683
- if (status === 'failed' || status === 'error')
684
- return 'failure';
685
- if (status === 'cancelled' || status === 'canceled')
686
- return 'cancelled';
687
- if (status === 'success' || status === 'succeeded' || status === 'completed' || status === 'complete')
688
- return 'success';
689
- return 'unknown';
824
+ function codexAvailableTools(value) {
825
+ if (!Array.isArray(value))
826
+ return undefined;
827
+ const names = value.flatMap((entry) => {
828
+ if (!isObject(entry))
829
+ return [];
830
+ const name = stringValue(entry.name);
831
+ if (!name)
832
+ return [];
833
+ const namespace = stringValue(entry.namespace);
834
+ return [namespace ? `${namespace}.${name}` : name];
835
+ });
836
+ return names.length > 0 ? Array.from(new Set(names)) : undefined;
837
+ }
838
+ function codexSessionContextSummary(input) {
839
+ const runtime = [input.runtimeName, input.runtimeVersion].filter(Boolean).join(' ');
840
+ const parts = [
841
+ runtime,
842
+ input.memoryMode ? `memory ${input.memoryMode}` : '',
843
+ input.historyMode ? `history ${input.historyMode}` : '',
844
+ input.availableTools ? `${input.availableTools.length} tools` : '',
845
+ ].filter(Boolean);
846
+ return parts.length > 0 ? parts.join(' · ') : undefined;
690
847
  }
691
848
  function parseToolInput(value) {
692
849
  if (isObject(value))
@@ -717,20 +874,36 @@ function codexContentText(value) {
717
874
  return part.type === 'input_image' ? '[image]' : '';
718
875
  }).filter(Boolean).join('\n');
719
876
  }
720
- function codexToolOutputFailed(output) {
721
- return /\b(?:process|script)\s+(?:exited|failed)\s+with\s+(?:exit\s+)?code\s+[1-9]\d*\b/i.test(output)
722
- || /\bexit[_\s-]?code\s*[:=]\s*[1-9]\d*\b/i.test(output)
723
- || /\bapply_patch verification failed\b/i.test(output);
724
- }
725
- function codexToolOutputSucceeded(output) {
726
- const trimmed = output.trim();
727
- return /\bprocess exited with code 0\b/i.test(trimmed)
728
- || /\bexit code:\s*0\b/i.test(trimmed)
729
- || /^script completed\b/i.test(trimmed)
730
- || /^success\.\s+(?:updated|added|deleted|moved)\b/i.test(trimmed)
731
- || /^plan updated\b/i.test(trimmed)
732
- || /^workspace dependencies are available\b/i.test(trimmed)
733
- || /^\[image\]$/i.test(trimmed);
877
+ function codexReasoningPlaintext(payload) {
878
+ const summary = codexPlaintext(payload.summary);
879
+ if (summary)
880
+ return { text: summary, contentSource: 'summary' };
881
+ const content = codexPlaintext(payload.content);
882
+ if (content)
883
+ return { text: content, contentSource: 'content' };
884
+ const text = codexPlaintext(payload.text ?? payload.reasoning_text ?? payload.reasoningText);
885
+ return text ? { text, contentSource: 'text' } : undefined;
886
+ }
887
+ function codexPlaintext(value, depth = 0) {
888
+ if (depth > 3)
889
+ return '';
890
+ if (typeof value === 'string')
891
+ return value.trim();
892
+ if (Array.isArray(value)) {
893
+ return value
894
+ .map((part) => codexPlaintext(part, depth + 1))
895
+ .filter(Boolean)
896
+ .join('\n\n')
897
+ .trim();
898
+ }
899
+ if (!isObject(value))
900
+ return '';
901
+ for (const key of ['text', 'input_text', 'output_text', 'summary_text']) {
902
+ const text = stringValue(value[key]);
903
+ if (text)
904
+ return text;
905
+ }
906
+ return codexPlaintext(value.content, depth + 1);
734
907
  }
735
908
  function codexSourceMetadata(records, metaPayload) {
736
909
  const models = Array.from(new Set(records.flatMap((value) => {
@@ -846,6 +1019,47 @@ function indexDuplicateEventMessages(records) {
846
1019
  });
847
1020
  return duplicateIndexes;
848
1021
  }
1022
+ function indexDuplicateAgentReasoningMessages(records) {
1023
+ const reasoningItems = records.flatMap((value, sourceIndex) => {
1024
+ const record = asCodexRecord(value);
1025
+ const payload = isObject(record?.payload) ? record.payload : {};
1026
+ if (record?.type !== 'response_item' || payload.type !== 'reasoning')
1027
+ return [];
1028
+ const reasoning = codexReasoningPlaintext(payload);
1029
+ if (!reasoning)
1030
+ return [];
1031
+ return [{
1032
+ sourceIndex,
1033
+ timestamp: normalizeTraceTimestamp(record.timestamp),
1034
+ text: normalizeReasoningMirrorText(reasoning.text),
1035
+ }];
1036
+ });
1037
+ const duplicateIndexes = new Set();
1038
+ records.forEach((value, sourceIndex) => {
1039
+ const record = asCodexRecord(value);
1040
+ const payload = isObject(record?.payload) ? record.payload : {};
1041
+ if (record?.type !== 'event_msg' || payload.type !== 'agent_reasoning')
1042
+ return;
1043
+ const text = normalizeReasoningMirrorText(codexPlaintext(payload.text));
1044
+ if (!text)
1045
+ return;
1046
+ const timestamp = normalizeTraceTimestamp(record.timestamp);
1047
+ const mirrored = reasoningItems.some((item) => {
1048
+ if (Math.abs(item.sourceIndex - sourceIndex) > 8)
1049
+ return false;
1050
+ const timeDistance = timestamp && item.timestamp
1051
+ ? Math.abs(Date.parse(timestamp) - Date.parse(item.timestamp))
1052
+ : 0;
1053
+ return timeDistance <= 1_000 && item.text.includes(text);
1054
+ });
1055
+ if (mirrored)
1056
+ duplicateIndexes.add(sourceIndex);
1057
+ });
1058
+ return duplicateIndexes;
1059
+ }
1060
+ function normalizeReasoningMirrorText(value) {
1061
+ return value.replace(/\s+/g, ' ').trim();
1062
+ }
849
1063
  function asCodexRecord(value) {
850
1064
  return isObject(value) ? value : undefined;
851
1065
  }
@@ -855,6 +1069,15 @@ function stringValue(value) {
855
1069
  function booleanValue(value) {
856
1070
  return typeof value === 'boolean' ? value : undefined;
857
1071
  }
1072
+ function stringArray(value) {
1073
+ if (!Array.isArray(value))
1074
+ return undefined;
1075
+ const strings = value.filter((item) => typeof item === 'string' && item.trim().length > 0);
1076
+ return strings.length > 0 ? strings : undefined;
1077
+ }
1078
+ function nestedString(value, key) {
1079
+ return isObject(value) ? stringValue(value[key]) : undefined;
1080
+ }
858
1081
  function isObject(value) {
859
1082
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
860
1083
  }
@@ -0,0 +1,38 @@
1
+ import type { ConversationIndexViewModel, ConversationListItem, ExperienceTurnStatus, ObservationSourceRecordArchiveView, TaskTrajectorySession, TraceIngestionSummary } from '../types/index.js';
2
+ export interface ConversationTaskTrajectory {
3
+ revision: string;
4
+ status: ExperienceTurnStatus;
5
+ /** The source task is still the final unclosed turn and may append after a quiet period. */
6
+ liveObservable: boolean;
7
+ session: TaskTrajectorySession;
8
+ ingestion: TraceIngestionSummary;
9
+ sourceRecords: ObservationSourceRecordArchiveView;
10
+ }
11
+ export interface ConversationTaskTrajectoryObserver {
12
+ next(trajectory: ConversationTaskTrajectory): void;
13
+ complete?(): void;
14
+ error?(cause: unknown): void;
15
+ }
16
+ export interface ConversationTaskTrajectorySubscriptionOptions {
17
+ signal?: AbortSignal;
18
+ }
19
+ export interface ConversationCatalog {
20
+ listConversations(): Promise<ConversationIndexViewModel>;
21
+ getConversation(threadId: string): Promise<ConversationListItem | undefined>;
22
+ loadTaskTrajectory(threadId: string, turnId: string): Promise<ConversationTaskTrajectory | undefined>;
23
+ /** Optional live capability. Static catalogs do not need to implement it. */
24
+ observeTaskTrajectory?(threadId: string, turnId: string, observer: ConversationTaskTrajectoryObserver, options?: ConversationTaskTrajectorySubscriptionOptions): Promise<() => void>;
25
+ }
26
+ export interface CodexConversationCatalogOptions {
27
+ codexHome?: string;
28
+ cacheDir?: string;
29
+ /** Tests can keep synthetic fixtures in-process. */
30
+ useBackgroundProcess?: boolean;
31
+ backgroundProcessThresholdBytes?: number;
32
+ livePollIntervalMs?: number;
33
+ /** An unclosed final task is only live while its rollout is still recent. */
34
+ liveActivityWindowMs?: number;
35
+ now?: () => number;
36
+ }
37
+ export declare function createCodexConversationCatalog(options?: CodexConversationCatalogOptions): ConversationCatalog;
38
+ export declare function conversationSourceLabel(path: string): string;