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
@@ -0,0 +1,100 @@
1
+ import { loadLatestObservationInboxReports } from './inbox.js';
2
+ import { durationMsBetween } from '../shared/time.js';
3
+ export function buildConversationIndexViewModel(observationsDir) {
4
+ const groups = new Map();
5
+ const reports = loadLatestObservationInboxReports(observationsDir);
6
+ for (const report of reports) {
7
+ for (const session of report.experience?.sessions ?? []) {
8
+ const group = groups.get(session.threadId) ?? {
9
+ threadId: session.threadId,
10
+ sourceThreadId: session.sourceThreadId,
11
+ sourceKind: session.sourceKind,
12
+ sessions: [],
13
+ tasks: new Map(),
14
+ relatedSkills: new Set(),
15
+ };
16
+ group.sessions.push(session);
17
+ group.relatedSkills.add(session.skillName);
18
+ const attributedIds = new Set(session.attributedEventIds);
19
+ for (const turn of session.turns) {
20
+ const related = turn.eventIds.some((id) => attributedIds.has(id));
21
+ const existing = group.tasks.get(turn.turnId);
22
+ if (!existing || turn.eventIds.length > existing.turn.eventIds.length) {
23
+ group.tasks.set(turn.turnId, {
24
+ turn,
25
+ experienceSessionId: session.id,
26
+ relatedSkills: new Set(existing?.relatedSkills ?? []),
27
+ });
28
+ }
29
+ if (related) {
30
+ const task = group.tasks.get(turn.turnId);
31
+ task?.relatedSkills.add(session.skillName);
32
+ if (task && task.experienceSessionId !== session.id) {
33
+ task.experienceSessionId = session.id;
34
+ }
35
+ }
36
+ }
37
+ groups.set(session.threadId, group);
38
+ }
39
+ }
40
+ const conversations = Array.from(groups.values())
41
+ .map(finalizeConversation)
42
+ .filter((conversation) => conversation.tasks.length > 0)
43
+ .sort((left, right) => ((right.endTimestamp ?? '').localeCompare(left.endTimestamp ?? '')
44
+ || left.threadId.localeCompare(right.threadId)));
45
+ return {
46
+ conversations,
47
+ totalTurnCount: conversations.reduce((sum, item) => sum + (item.turnCount ?? 0), 0),
48
+ totalToolCallCount: conversations.reduce((sum, item) => sum + (item.toolCallCount ?? 0), 0),
49
+ totalToolFailureCount: conversations.reduce((sum, item) => sum + (item.toolFailureCount ?? 0), 0),
50
+ };
51
+ }
52
+ /** Test/compatibility adapter for persisted observe reports. */
53
+ export function createObservationConversationCatalog(observationsDir) {
54
+ return {
55
+ async listConversations() {
56
+ return buildConversationIndexViewModel(observationsDir);
57
+ },
58
+ async getConversation(threadId) {
59
+ return buildConversationIndexViewModel(observationsDir).conversations.find((item) => item.threadId === threadId);
60
+ },
61
+ async loadTaskTrajectory() {
62
+ return undefined;
63
+ },
64
+ };
65
+ }
66
+ function finalizeConversation(group) {
67
+ const tasks = Array.from(group.tasks.values())
68
+ .map(({ turn, experienceSessionId, relatedSkills }) => ({
69
+ turnId: turn.turnId,
70
+ sourceTurnId: turn.sourceTurnId,
71
+ experienceSessionId,
72
+ title: turn.title,
73
+ startTimestamp: turn.startTimestamp,
74
+ endTimestamp: turn.endTimestamp,
75
+ durationMs: durationMsBetween(turn.startTimestamp, turn.endTimestamp),
76
+ status: turn.status,
77
+ eventCount: turn.eventIds.length,
78
+ toolCallCount: turn.toolCallCount,
79
+ toolFailureCount: turn.toolFailureCount,
80
+ relatedSkillNames: Array.from(relatedSkills).sort(),
81
+ }))
82
+ .sort((left, right) => ((left.startTimestamp ?? '').localeCompare(right.startTimestamp ?? '')
83
+ || left.turnId.localeCompare(right.turnId)));
84
+ const firstTask = tasks[0];
85
+ const lastTask = tasks.at(-1);
86
+ return {
87
+ threadId: group.threadId,
88
+ sourceThreadId: group.sourceThreadId,
89
+ sourceKind: group.sourceKind,
90
+ title: firstTask?.title ?? '未命名对话',
91
+ startTimestamp: firstTask?.startTimestamp,
92
+ endTimestamp: lastTask?.endTimestamp ?? lastTask?.startTimestamp,
93
+ durationMs: durationMsBetween(firstTask?.startTimestamp, lastTask?.endTimestamp ?? lastTask?.startTimestamp),
94
+ turnCount: tasks.length,
95
+ toolCallCount: tasks.reduce((sum, task) => sum + task.toolCallCount, 0),
96
+ toolFailureCount: tasks.reduce((sum, task) => sum + task.toolFailureCount, 0),
97
+ relatedSkillNames: Array.from(group.relatedSkills).sort(),
98
+ tasks,
99
+ };
100
+ }
@@ -15,7 +15,7 @@ export type PersistedExperienceSessionStory = Omit<ExperienceSessionStory, 'cont
15
15
  export type PersistedExperienceReviewerReport = Omit<ExperienceReviewerReport, 'sessionStory' | 'sessionStoryRef'> & {
16
16
  sessionStoryRef: 'session';
17
17
  };
18
- export type PersistedExperienceSession = Omit<ExperienceSessionSummary, 'timelineRef' | 'timelinePreviewEventIds' | 'timelinePreview' | 'fullSessionTimeline' | 'timelineTree' | 'sessionStory' | 'reviewerReport'> & {
18
+ export type PersistedExperienceSession = Omit<ExperienceSessionSummary, 'timelineRef' | 'timelinePreviewEventIds' | 'attributedEventIds' | 'timelinePreview' | 'fullSessionTimeline' | 'timelineTree' | 'sessionStory' | 'reviewerReport'> & {
19
19
  timelineRef: string;
20
20
  timelinePreviewEventIds: string[];
21
21
  sessionStory?: PersistedExperienceSessionStory;
@@ -41,6 +41,8 @@ export declare function normalizeObservationExperienceReport(value: unknown): Ob
41
41
  * invocation/session views through normalizeObservationExperienceReport().
42
42
  */
43
43
  export declare function compactObservationExperienceReport(report: ObservationExperienceReport): PersistedObservationExperienceReport;
44
+ /** Project one source-neutral Trace IR session into Studio's semantic timeline. */
45
+ export declare function projectTraceSessionTimeline(session: TraceSession): ExperienceTimelineEvent[];
44
46
  export declare function isExperienceTraceInProgress(session: ExperienceSessionSummary): boolean;
45
47
  export declare function hasRecognizableUserGoalText(value: string | undefined): boolean;
46
48
  export declare function foldExperienceChecklistItems(items: ExperienceChecklistItem[]): {
@@ -6,6 +6,7 @@ import { observationMetricAnnotationVerdict, observationReviewStateKey } from '.
6
6
  import { correlateTraceToolEvents, normalizeTraceTimestamp, } from './trace-ir.js';
7
7
  import { skillSegmentTimestampObserved, UNOBSERVED_TRACE_TIMESTAMP, } from './trace-segmenter.js';
8
8
  import { createTraceSessionIndex, traceSessionRefIdentity } from './trace-session-index.js';
9
+ import { reconstructExperienceTurns } from './turn-index.js';
9
10
  import { extractCommandEnvelopeText, stripCommandEnvelopeText } from './trace-attribution.js';
10
11
  import { hasAssistantDeliverableArtifactText, hasAssistantDeliverySignalText, hasUserHardRuleText, isAssistantProgressUpdateText, isAssistantProtocolReplyText, isSyntheticUserMessageText, isToolResultFailureText, isUserInteractionMetricText } from './text-signals.js';
11
12
  import { durationMsBetween } from '../shared/time.js';
@@ -290,6 +291,8 @@ function normalizeExperienceSessionShells(values, strict) {
290
291
  return null;
291
292
  if (records.some((value) => typeof value.id !== 'string'
292
293
  || typeof value.skillName !== 'string'
294
+ || !isOptionalString(value.threadId)
295
+ || !isOptionalString(value.sourceThreadId)
293
296
  || typeof value.sessionId !== 'string'
294
297
  || typeof value.sourceTrace !== 'string'
295
298
  || !isObservationSourceKind(value.sourceKind)
@@ -326,6 +329,7 @@ function normalizeExperienceSessionShells(values, strict) {
326
329
  || !isExperienceAssistiveInference(value.assistiveInference)
327
330
  || !isExperienceProblemPatternArray(value.problemPatterns)
328
331
  || !isStringArray(value.relatedObservationIds)
332
+ || (value.turns !== undefined && !isExperienceTurnSummaryArray(value.turns))
329
333
  || !(strict
330
334
  ? isExperienceTimelineScope(value.timelineScope)
331
335
  : isLegacyExperienceTimelineScope(value.timelineScope))
@@ -350,6 +354,14 @@ function normalizeExperienceSessionShells(values, strict) {
350
354
  && !isExperienceReviewerReport(value.reviewerReport, strict))))
351
355
  return null;
352
356
  return records.map((value) => {
357
+ const sourceThreadId = typeof value.sourceThreadId === 'string'
358
+ ? value.sourceThreadId
359
+ : typeof value.threadId === 'string'
360
+ ? value.threadId
361
+ : value.sessionId;
362
+ const threadId = typeof value.threadId === 'string'
363
+ ? value.threadId
364
+ : hashParts('thread', sourceThreadId);
353
365
  const sessionStory = isObjectRecord(value.sessionStory)
354
366
  ? {
355
367
  ...value.sessionStory,
@@ -362,7 +374,10 @@ function normalizeExperienceSessionShells(values, strict) {
362
374
  : undefined;
363
375
  return {
364
376
  ...value,
377
+ threadId,
378
+ sourceThreadId,
365
379
  invocationIds: Array.isArray(value.invocationIds) ? value.invocationIds : [],
380
+ turns: isExperienceTurnSummaryArray(value.turns) ? value.turns : [],
366
381
  timelinePreview: Array.isArray(value.timelinePreview) ? value.timelinePreview : [],
367
382
  fullSessionTimeline: Array.isArray(value.fullSessionTimeline) ? value.fullSessionTimeline : [],
368
383
  sessionStory,
@@ -491,10 +506,13 @@ function isExperienceEvidenceKind(value) {
491
506
  'user_message',
492
507
  'synthetic_user_event',
493
508
  'assistant_message',
509
+ 'model_activity',
510
+ 'agent_activity',
494
511
  'tool_use',
495
512
  'tool_result',
496
513
  'skill_context',
497
514
  'runtime_context',
515
+ 'lifecycle',
498
516
  'observation',
499
517
  ]);
500
518
  }
@@ -511,13 +529,26 @@ function isExperienceEvidenceRef(value) {
511
529
  if (value.traceRole !== undefined
512
530
  && !isEnumValue(value.traceRole, ['standalone', 'main', 'subagent']))
513
531
  return false;
532
+ if (value.modelActivityKind !== undefined && value.modelActivityKind !== 'reasoning')
533
+ return false;
534
+ if (value.contentVisibility !== undefined
535
+ && !isEnumValue(value.contentVisibility, ['plaintext', 'opaque']))
536
+ return false;
537
+ if (value.contentSource !== undefined
538
+ && !isEnumValue(value.contentSource, ['summary', 'content', 'text']))
539
+ return false;
540
+ if (value.runtimeKind !== undefined
541
+ && !isEnumValue(value.runtimeKind, ['session_context', 'execution_context', 'settings', 'goal', 'context_compaction', 'usage']))
542
+ return false;
514
543
  if (value.role !== undefined
515
544
  && !isEnumValue(value.role, ['user', 'assistant', 'tool', 'other']))
516
545
  return false;
517
546
  return [
518
547
  value.traceLabel,
519
548
  value.traceId,
549
+ value.turnId,
520
550
  value.messageUuid,
551
+ value.sourceType,
521
552
  value.callInstanceId,
522
553
  value.toolUseId,
523
554
  value.label,
@@ -880,10 +911,13 @@ function isTimelineEvent(value) {
880
911
  'user_message',
881
912
  'synthetic_user_event',
882
913
  'assistant_message',
914
+ 'model_activity',
915
+ 'agent_activity',
883
916
  'tool_use',
884
917
  'tool_result',
885
918
  'skill_context',
886
919
  'runtime_context',
920
+ 'lifecycle',
887
921
  'observation',
888
922
  ].includes(String(value.kind))
889
923
  || typeof value.sourceTrace !== 'string'
@@ -914,10 +948,32 @@ function isTimelineEvent(value) {
914
948
  && value.toolStatus !== 'cancelled'
915
949
  && value.toolStatus !== 'unknown')
916
950
  return false;
951
+ if (value.modelActivityKind !== undefined && value.modelActivityKind !== 'reasoning')
952
+ return false;
953
+ if (value.contentVisibility !== undefined
954
+ && value.contentVisibility !== 'plaintext'
955
+ && value.contentVisibility !== 'opaque')
956
+ return false;
957
+ if (value.contentSource !== undefined
958
+ && value.contentSource !== 'summary'
959
+ && value.contentSource !== 'content'
960
+ && value.contentSource !== 'text')
961
+ return false;
962
+ if (value.runtimeKind !== undefined
963
+ && value.runtimeKind !== 'session_context'
964
+ && value.runtimeKind !== 'execution_context'
965
+ && value.runtimeKind !== 'settings'
966
+ && value.runtimeKind !== 'goal'
967
+ && value.runtimeKind !== 'context_compaction'
968
+ && value.runtimeKind !== 'usage')
969
+ return false;
917
970
  const optionalStrings = [
918
971
  value.traceId,
919
972
  value.traceLabel,
973
+ value.turnId,
920
974
  value.messageUuid,
975
+ value.sourceType,
976
+ value.model,
921
977
  value.callInstanceId,
922
978
  value.toolUseId,
923
979
  value.label,
@@ -937,6 +993,29 @@ function isTimelineEvent(value) {
937
993
  function isTimelineEventArray(value) {
938
994
  return Array.isArray(value) && value.every(isTimelineEvent);
939
995
  }
996
+ function isExperienceTurnSummaryArray(value) {
997
+ return Array.isArray(value) && value.every((turn) => (isObjectRecord(turn)
998
+ && typeof turn.turnId === 'string'
999
+ && isOptionalString(turn.sourceTurnId)
1000
+ && (turn.boundaryBasis === 'turn_id'
1001
+ || turn.boundaryBasis === 'turn_lifecycle'
1002
+ || turn.boundaryBasis === 'user_message')
1003
+ && isOptionalString(turn.traceId)
1004
+ && typeof turn.sourceTrace === 'string'
1005
+ && isOptionalTimestamp(turn.startTimestamp)
1006
+ && isOptionalTimestamp(turn.endTimestamp)
1007
+ && (turn.status === 'completed'
1008
+ || turn.status === 'aborted'
1009
+ || turn.status === 'interrupted'
1010
+ || turn.status === 'open'
1011
+ || turn.status === 'unknown')
1012
+ && typeof turn.title === 'string'
1013
+ && isStringArray(turn.eventIds)
1014
+ && isNonNegativeInteger(turn.userMessageCount)
1015
+ && isNonNegativeInteger(turn.assistantMessageCount)
1016
+ && isNonNegativeInteger(turn.toolCallCount)
1017
+ && isNonNegativeInteger(turn.toolFailureCount)));
1018
+ }
940
1019
  function isTimelineTree(value) {
941
1020
  if (!isObjectRecord(value)
942
1021
  || typeof value.sessionId !== 'string'
@@ -1455,6 +1534,7 @@ export function compactObservationExperienceReport(report) {
1455
1534
  sessions: hydrated.sessions.map((session) => {
1456
1535
  const { timelinePreview, sessionStory, reviewerReport } = session;
1457
1536
  const persisted = omitProperties(session, [
1537
+ 'attributedEventIds',
1458
1538
  'timelinePreview',
1459
1539
  'fullSessionTimeline',
1460
1540
  'timelineTree',
@@ -1533,11 +1613,18 @@ function hydrateExperienceTimelines(report) {
1533
1613
  const fullSessionTimeline = storedTimeline
1534
1614
  ? flattenedTimeline(timelineRef)
1535
1615
  : session.fullSessionTimeline;
1616
+ const turns = session.turns.length > 0
1617
+ ? session.turns
1618
+ : reconstructExperienceTurns(fullSessionTimeline);
1536
1619
  const timelinePreviewEventIds = session.timelinePreviewEventIds
1537
1620
  ?? session.timelinePreview.map((event) => event.id);
1538
1621
  const timelinePreview = timelinePreviewEventIds.length > 0
1539
1622
  ? selectEvents(timelineRef, timelinePreviewEventIds, session.timelinePreview)
1540
1623
  : fullSessionTimeline.slice(0, TIMELINE_PREVIEW_EVENT_LIMIT);
1624
+ const attributedEventIds = unique(session.invocationIds.flatMap((id) => {
1625
+ const invocation = invocationById.get(id);
1626
+ return invocation?.timelineEventIds ?? invocation?.timeline.map((event) => event.id) ?? [];
1627
+ }));
1541
1628
  const contextRef = session.sessionStory?.contextRef
1542
1629
  ?? storyContextRefForSessionGroup(firstInvocation?.sessionGroupKey ?? session.sessionId);
1543
1630
  const storyContext = storyContextById.get(contextRef);
@@ -1554,9 +1641,11 @@ function hydrateExperienceTimelines(report) {
1554
1641
  return {
1555
1642
  ...session,
1556
1643
  timelineRef,
1644
+ attributedEventIds,
1557
1645
  timelinePreviewEventIds: timelinePreview.map((event) => event.id),
1558
1646
  timelinePreview,
1559
1647
  fullSessionTimeline,
1648
+ turns,
1560
1649
  timelineTree: storedTimeline?.tree ?? session.timelineTree,
1561
1650
  sessionStory,
1562
1651
  reviewerReport: session.reviewerReport && sessionStory
@@ -1962,7 +2051,8 @@ function timelineScopeMatches(session, invocations, timeline) {
1962
2051
  ? events.length - 1 - Math.max(...previewPositions)
1963
2052
  : 0;
1964
2053
  const scope = session.timelineScope;
1965
- return scope.segmentEventCount === invocationEventIds.size
2054
+ return sameStringSet(session.attributedEventIds, invocationEventIds)
2055
+ && scope.segmentEventCount === invocationEventIds.size
1966
2056
  && scope.previewEventCount === previewEventIds.length
1967
2057
  && scope.fullSessionEventCount === events.length
1968
2058
  && traceRecordRangesEqual(scope.segmentRecordRanges, traceRecordRanges(invocationEvents))
@@ -2138,6 +2228,11 @@ function countRecordsEqual(left, right) {
2138
2228
  function sameStringArray(left, right) {
2139
2229
  return left.length === right.length && left.every((value, index) => value === right[index]);
2140
2230
  }
2231
+ function sameStringSet(left, right) {
2232
+ return left.length === right.size
2233
+ && new Set(left).size === left.length
2234
+ && left.every((value) => right.has(value));
2235
+ }
2141
2236
  function storyContextsFromSessions(sessions, invocations) {
2142
2237
  const invocationById = new Map(invocations.map((invocation) => [invocation.id, invocation]));
2143
2238
  const contexts = new Map();
@@ -2386,6 +2481,10 @@ function buildTimelineWindow(session, start, end) {
2386
2481
  }
2387
2482
  return events.sort((a, b) => a.order - b.order);
2388
2483
  }
2484
+ /** Project one source-neutral Trace IR session into Studio's semantic timeline. */
2485
+ export function projectTraceSessionTimeline(session) {
2486
+ return buildTimelineWindow(session, 0, session.events.length);
2487
+ }
2389
2488
  function timelineEventsFromTraceEvent(session, event, eventIndex) {
2390
2489
  const messageIndex = event.sourceIndex;
2391
2490
  const base = {
@@ -2398,6 +2497,8 @@ function timelineEventsFromTraceEvent(session, event, eventIndex) {
2398
2497
  logicalMessageIndex: messageIndex,
2399
2498
  sourceLineIndex: messageIndex,
2400
2499
  messageUuid: event.sourceEventId ?? event.eventId,
2500
+ sourceType: event.sourceType,
2501
+ turnId: event.turnId,
2401
2502
  timestamp: event.timestamp,
2402
2503
  };
2403
2504
  const order = eventIndex * 10;
@@ -2422,11 +2523,27 @@ function timelineEventsFromTraceEvent(session, event, eventIndex) {
2422
2523
  kind: protocolReply ? 'runtime_context' : 'assistant_message',
2423
2524
  role: 'assistant',
2424
2525
  order,
2526
+ model: event.model,
2425
2527
  snippet: snippet(event.text, 700),
2426
2528
  fullText: fullText(event.text),
2427
2529
  label: protocolReply ? 'assistant protocol reply' : 'assistant message',
2428
2530
  })];
2429
2531
  }
2532
+ if (event.eventKind === 'model_activity') {
2533
+ return [timelineEvent({
2534
+ ...base,
2535
+ kind: 'model_activity',
2536
+ role: 'assistant',
2537
+ order,
2538
+ model: event.model,
2539
+ modelActivityKind: event.activityKind,
2540
+ contentVisibility: event.contentVisibility,
2541
+ contentSource: event.contentSource,
2542
+ snippet: snippet(event.text, 700),
2543
+ fullText: fullText(event.text),
2544
+ label: 'model reasoning',
2545
+ })];
2546
+ }
2430
2547
  if (event.eventKind === 'tool_call') {
2431
2548
  const inputText = JSON.stringify(event.input);
2432
2549
  return [timelineEvent({
@@ -2464,7 +2581,7 @@ function timelineEventsFromTraceEvent(session, event, eventIndex) {
2464
2581
  if (event.eventKind === 'lifecycle') {
2465
2582
  return [timelineEvent({
2466
2583
  ...base,
2467
- kind: 'runtime_context',
2584
+ kind: 'lifecycle',
2468
2585
  role: 'other',
2469
2586
  order,
2470
2587
  snippet: snippet(`${event.phase}${event.reason ? `: ${event.reason}` : ''}`, 700),
@@ -2472,6 +2589,111 @@ function timelineEventsFromTraceEvent(session, event, eventIndex) {
2472
2589
  label: event.phase,
2473
2590
  })];
2474
2591
  }
2592
+ if (event.eventKind === 'runtime_context') {
2593
+ const details = JSON.stringify({
2594
+ runtimeName: event.runtimeName,
2595
+ runtimeVersion: event.runtimeVersion,
2596
+ cwd: event.cwd,
2597
+ workspaceRoots: event.workspaceRoots,
2598
+ currentDate: event.currentDate,
2599
+ timezone: event.timezone,
2600
+ model: event.model,
2601
+ modelProvider: event.modelProvider,
2602
+ serviceTier: event.serviceTier,
2603
+ reasoningEffort: event.reasoningEffort,
2604
+ reasoningSummary: event.reasoningSummary,
2605
+ personality: event.personality,
2606
+ approvalPolicy: event.approvalPolicy,
2607
+ approvalReviewer: event.approvalReviewer,
2608
+ permissionProfile: event.permissionProfile,
2609
+ sandboxMode: event.sandboxMode,
2610
+ collaborationMode: event.collaborationMode,
2611
+ realtimeActive: event.realtimeActive,
2612
+ multiAgentMode: event.multiAgentMode,
2613
+ multiAgentVersion: event.multiAgentVersion,
2614
+ memoryMode: event.memoryMode,
2615
+ historyMode: event.historyMode,
2616
+ contextWindowId: event.contextWindowId,
2617
+ availableTools: event.availableTools,
2618
+ instructions: event.instructions,
2619
+ goal: event.goal,
2620
+ goalStatus: event.goalStatus,
2621
+ summary: event.summary,
2622
+ });
2623
+ const visible = event.summary
2624
+ ?? event.goal
2625
+ ?? event.instructions
2626
+ ?? [event.runtimeName, event.runtimeVersion, event.cwd, event.model, event.reasoningEffort, event.collaborationMode]
2627
+ .filter(Boolean)
2628
+ .join(' · ');
2629
+ return [timelineEvent({
2630
+ ...base,
2631
+ kind: 'runtime_context',
2632
+ role: 'other',
2633
+ order,
2634
+ model: event.model,
2635
+ runtimeKind: event.runtimeKind,
2636
+ snippet: snippet(visible, 700),
2637
+ fullText: fullText(details),
2638
+ label: event.runtimeKind,
2639
+ })];
2640
+ }
2641
+ if (event.eventKind === 'context_compaction') {
2642
+ return [timelineEvent({
2643
+ ...base,
2644
+ kind: 'runtime_context',
2645
+ role: 'other',
2646
+ order,
2647
+ runtimeKind: 'context_compaction',
2648
+ snippet: snippet(event.summary ?? 'context compacted', 700),
2649
+ fullText: fullText(JSON.stringify(event)),
2650
+ label: 'context compacted',
2651
+ })];
2652
+ }
2653
+ if (event.eventKind === 'agent_activity') {
2654
+ const visible = event.text
2655
+ ?? [event.activity, event.author, event.recipient, event.agentPath, event.agentId]
2656
+ .filter(Boolean)
2657
+ .join(' · ');
2658
+ return [timelineEvent({
2659
+ ...base,
2660
+ kind: 'agent_activity',
2661
+ role: 'other',
2662
+ order,
2663
+ snippet: snippet(visible, 700),
2664
+ fullText: fullText(JSON.stringify({
2665
+ activityKind: event.activityKind,
2666
+ activity: event.activity,
2667
+ author: event.author,
2668
+ recipient: event.recipient,
2669
+ agentId: event.agentId,
2670
+ agentPath: event.agentPath,
2671
+ text: event.text,
2672
+ })),
2673
+ label: event.activityKind === 'communication' ? 'agent communication' : 'agent status',
2674
+ })];
2675
+ }
2676
+ if (event.eventKind === 'usage') {
2677
+ const usageText = JSON.stringify({
2678
+ model: event.model,
2679
+ inputTokens: event.inputTokens,
2680
+ outputTokens: event.outputTokens,
2681
+ cacheReadTokens: event.cacheReadTokens,
2682
+ cacheCreationTokens: event.cacheCreationTokens,
2683
+ reasoningTokens: event.reasoningTokens,
2684
+ });
2685
+ return [timelineEvent({
2686
+ ...base,
2687
+ kind: 'runtime_context',
2688
+ role: 'other',
2689
+ order,
2690
+ model: event.model,
2691
+ runtimeKind: 'usage',
2692
+ snippet: snippet(usageText, 700),
2693
+ fullText: fullText(usageText),
2694
+ label: 'token usage',
2695
+ })];
2696
+ }
2475
2697
  if (event.eventKind === 'unknown') {
2476
2698
  const rawText = safeRecordText(event.raw);
2477
2699
  if (!hasAssistantTurnFailedText(rawText))
@@ -2503,7 +2725,12 @@ function userTimelineEvents(event, base, order) {
2503
2725
  }));
2504
2726
  }
2505
2727
  const text = commandEnvelope ? stripCommandEnvelopeText(event.text) : event.text;
2506
- if (!text.trim())
2728
+ const displayedSourceText = event.displayText?.trim() || event.text;
2729
+ const displayText = commandEnvelope
2730
+ ? stripCommandEnvelopeText(displayedSourceText)
2731
+ : displayedSourceText;
2732
+ const semanticText = event.origin === 'human' ? displayText : text;
2733
+ if (!semanticText.trim())
2507
2734
  return events;
2508
2735
  const kind = event.origin === 'human'
2509
2736
  ? 'user_message'
@@ -2517,8 +2744,8 @@ function userTimelineEvents(event, base, order) {
2517
2744
  kind,
2518
2745
  role: kind === 'user_message' ? 'user' : kind === 'synthetic_user_event' ? 'other' : 'tool',
2519
2746
  order: order + (commandEnvelope ? 1 : 0),
2520
- snippet: snippet(text, 700),
2521
- fullText: fullText(text),
2747
+ snippet: snippet(semanticText, 700),
2748
+ fullText: fullText(semanticText),
2522
2749
  label: userTextEventLabel(kind),
2523
2750
  }));
2524
2751
  return events;
@@ -2585,6 +2812,11 @@ function evidenceRefFromTimeline(event) {
2585
2812
  toolUseId: event.toolUseId,
2586
2813
  timestamp: event.timestamp,
2587
2814
  role: event.role,
2815
+ modelActivityKind: event.modelActivityKind,
2816
+ contentVisibility: event.contentVisibility,
2817
+ contentSource: event.contentSource,
2818
+ sourceType: event.sourceType,
2819
+ runtimeKind: event.runtimeKind,
2588
2820
  label: event.label,
2589
2821
  snippet: event.snippet,
2590
2822
  };
@@ -2841,12 +3073,19 @@ function summarizeExperienceSessions(invocations, sessionGroupsByKey, generatedA
2841
3073
  const ruleFindings = ruleFindingsForEvidence(indicators, timeline, observationRefs, evidenceChain, metricScopeId);
2842
3074
  const assistiveInference = assistiveInferenceForEvidence(indicators, evidenceChain, ruleFindings);
2843
3075
  const problemPatterns = mergeExperienceProblemPatterns(group.flatMap((invocation) => invocation.problemPatterns));
3076
+ const sourceThreadId = sessionGroup.find((session) => session.role === 'main')?.rootRunId
3077
+ ?? sessionGroup[0]?.rootRunId
3078
+ ?? first.sessionId;
3079
+ const threadId = hashParts('thread', first.sessionGroupKey);
3080
+ const turns = reconstructExperienceTurns(fullSessionTimeline);
2844
3081
  const storyInvocations = invocations
2845
3082
  .filter((invocation) => invocation.sessionGroupKey === first.sessionGroupKey)
2846
3083
  .sort(compareStoryInvocations);
2847
3084
  const baseSession = {
2848
3085
  id: metricScopeId,
2849
3086
  skillName: first.skillName,
3087
+ threadId,
3088
+ sourceThreadId,
2850
3089
  sessionId: first.sessionId,
2851
3090
  sourceTrace: first.sourceTrace,
2852
3091
  sourceKind: first.sourceKind,
@@ -2873,6 +3112,8 @@ function summarizeExperienceSessions(invocations, sessionGroupsByKey, generatedA
2873
3112
  relatedObservationIds,
2874
3113
  timelineRef: timelineRefForSessionGroup(first.sessionGroupKey),
2875
3114
  timelinePreviewEventIds: previewEvents.map((event) => event.id),
3115
+ attributedEventIds: timeline.map((event) => event.id),
3116
+ turns,
2876
3117
  timelinePreview: previewEvents,
2877
3118
  fullSessionTimeline,
2878
3119
  timelineTree,
@@ -3591,7 +3832,7 @@ function dispatchTerminalLifecycle(session, dispatch) {
3591
3832
  || candidate.traceId === dispatch.traceId
3592
3833
  || candidate.sourceTrace === dispatch.sourceTrace);
3593
3834
  const event = branch?.events.at(-1);
3594
- if (event?.kind !== 'runtime_context')
3835
+ if (event?.kind !== 'lifecycle' && event?.kind !== 'runtime_context')
3595
3836
  return undefined;
3596
3837
  if (event.label === 'turn_completed' || event.label === 'session_ended') {
3597
3838
  return { status: 'completed', evidenceRef: evidenceRefFromTimeline(event) };
@@ -5104,7 +5345,7 @@ function userFeedbackStepText(session, reviewState) {
5104
5345
  feedbackCounts.positiveFeedbackCount > 0 ? `正向反馈 ${feedbackCounts.positiveFeedbackCount} 次` : '',
5105
5346
  session.indicators.userGoalShiftCount > 0 ? `目标切换 ${session.indicators.userGoalShiftCount} 次` : '',
5106
5347
  ].filter(Boolean);
5107
- return parts.length > 0 ? `用户反馈信号:${parts.join(',')}。` : '原始记录里没有看到人工追问、纠正、负向反馈或目标切换。';
5348
+ return parts.length > 0 ? `用户反馈信号:${parts.join(',')}。` : '原始日志里没有看到人工追问、纠正、负向反馈或目标切换。';
5108
5349
  }
5109
5350
  function userFeedbackStepStatus(session, reviewState) {
5110
5351
  const feedbackCounts = canonicalFeedbackCountsForSession(session, reviewState);
@@ -18,6 +18,7 @@ import { parseTraceIngestionSummary } from './trace-ingestion.js';
18
18
  import { createTraceSessionIndex, traceSessionRefIdentity } from './trace-session-index.js';
19
19
  import { isInstalledSkillAssetPath } from './trace-attribution.js';
20
20
  import { writeJsonFileAtomic } from '../shared/atomic-json.js';
21
+ import { writeObservationSourceRecordArchives } from './source-record-archive.js';
21
22
  // observe inbox(观测收件箱)产物根目录。导出名沿用 *_OBSERVATIONS_DIR 以少动 importer,
22
23
  // 但落盘目录已统一到 observe-inbox 词根(命令 omk observe inbox / kind observe-inbox)。
23
24
  // 项目级 .omk/observe-inbox 优先、全局兜底 —— 这套 project/global 归属是既有正常行为,本次只改名不改归属。
@@ -700,7 +701,14 @@ export function saveObservationInboxReport(report, outDir = DEFAULT_OBSERVATIONS
700
701
  // 例: '2026-05-07T12:00:00.999Z' → '2026-05-07T12-00-00-999'
701
702
  const stamp = report.meta.generatedAt.replace(/[:.]/g, '-').replace(/Z$/, '');
702
703
  const path = reportFilePath(outDir, `${stamp}-${randomRunToken()}`);
703
- writeJsonFileAtomic(path, compact);
704
+ const sourceRecordArchives = writeObservationSourceRecordArchives(report, outDir, path);
705
+ const persisted = sourceRecordArchives.length > 0
706
+ ? { ...compact, meta: { ...compact.meta, sourceRecordArchives } }
707
+ : compact;
708
+ if (!normalizeObservationInboxReport(persisted)) {
709
+ throw new Error('拒绝写入原始日志引用无效的 observe inbox 报告。');
710
+ }
711
+ writeJsonFileAtomic(path, persisted);
704
712
  return path;
705
713
  }
706
714
  export function compactObservationInboxReport(report) {
@@ -790,6 +798,11 @@ function normalizeObservationInboxReport(value) {
790
798
  };
791
799
  }
792
800
  function observationInboxReferencesAreConsistent(meta, items, experience) {
801
+ const archiveRefs = meta.sourceRecordArchives ?? [];
802
+ const experienceSessionIds = new Set(experience?.sessions.map((session) => session.id) ?? []);
803
+ if (new Set(archiveRefs.map((ref) => ref.experienceSessionId)).size !== archiveRefs.length
804
+ || archiveRefs.some((ref) => !experienceSessionIds.has(ref.experienceSessionId)))
805
+ return false;
793
806
  const itemIds = new Set(items.map((item) => item.id));
794
807
  if (itemIds.size !== items.length)
795
808
  return false;
@@ -888,7 +901,10 @@ function isObservationInboxMeta(value) {
888
901
  && !isInboxTimestampRecord(value.skillInvocationLastSeen))
889
902
  || (value.skillToolCallCounts !== undefined
890
903
  && (!isInboxRecord(value.skillToolCallCounts)
891
- || !Object.values(value.skillToolCallCounts).every(isInboxCountRecord))))
904
+ || !Object.values(value.skillToolCallCounts).every(isInboxCountRecord)))
905
+ || (value.sourceRecordArchives !== undefined
906
+ && (!Array.isArray(value.sourceRecordArchives)
907
+ || !value.sourceRecordArchives.every(isObservationSourceRecordArchiveRef))))
892
908
  return false;
893
909
  if (value.sessionTimeRange !== undefined
894
910
  && (!isInboxRecord(value.sessionTimeRange)
@@ -914,6 +930,28 @@ function isObservationInboxMeta(value) {
914
930
  || (Array.isArray(value.sessionTimeRanges)
915
931
  && value.sessionTimeRanges.every(isObservationSessionTimeRange));
916
932
  }
933
+ function isObservationSourceRecordArchiveRef(value) {
934
+ if (!isInboxRecord(value)
935
+ || typeof value.experienceSessionId !== 'string'
936
+ || !['available', 'partial', 'unavailable'].includes(String(value.status))
937
+ || (value.relativePath !== undefined && typeof value.relativePath !== 'string')
938
+ || !isInboxCount(value.recordCount)
939
+ || !isInboxCount(value.omittedRecordCount)
940
+ || !isInboxCount(value.byteCount)
941
+ || typeof value.truncated !== 'boolean')
942
+ return false;
943
+ if (value.status === 'unavailable' && value.relativePath !== undefined)
944
+ return false;
945
+ if (value.status !== 'unavailable' && typeof value.relativePath !== 'string')
946
+ return false;
947
+ return value.reason === undefined || [
948
+ 'no_record_ranges',
949
+ 'source_missing',
950
+ 'unsupported_source',
951
+ 'read_failed',
952
+ 'archive_limit',
953
+ ].includes(String(value.reason));
954
+ }
917
955
  function isObservationSessionTimeRange(value) {
918
956
  return isInboxRecord(value)
919
957
  && typeof value.sessionId === 'string'
@@ -0,0 +1,4 @@
1
+ import type { DebugKnowledgeEvidence, ExperienceTimelineEvent, KnowledgeDebuggerViewModel, ObservationSourceRecordArchiveView, TaskTrajectorySession, TaskReplayStep, TraceIngestionSummary } from '../types/index.js';
2
+ export declare function buildKnowledgeDebuggerViewModel(session: TaskTrajectorySession, targetTurnId: string, ingestion?: TraceIngestionSummary, sourceRecords?: ObservationSourceRecordArchiveView): KnowledgeDebuggerViewModel;
3
+ export declare function buildTaskReplaySteps(session: TaskTrajectorySession, timeline: ExperienceTimelineEvent[], knowledgeEvidence: DebugKnowledgeEvidence[]): TaskReplayStep[];
4
+ export declare function projectKnowledgeEvidence(timeline: ExperienceTimelineEvent[]): DebugKnowledgeEvidence[];