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
@@ -8,6 +8,8 @@ import { renderSkillHealthReport } from '../renderer/skill-health-renderer.js';
8
8
  import { renderDoctorDetail } from '../renderer/doctor-detail-renderer.js';
9
9
  import { assessHealth, renderSkillDetail } from '../renderer/skill-detail-renderer.js';
10
10
  import { renderObservationInboxPage } from '../renderer/observation-inbox-renderer.js';
11
+ import { renderKnowledgeDebuggerPage } from '../renderer/knowledge-debugger-renderer.js';
12
+ import { buildConversationActivitySnapshot, renderConversationDetailPage, renderConversationIndexPage, } from '../renderer/conversation-renderer.js';
11
13
  import { DEFAULT_LANG, e, t, layout } from '../renderer/layout.js';
12
14
  import { loadAllManagedRecords, resolveManagedDir, managedDir as projectManagedDir, listManagedRows, buildVersionScores } from '../managed/index.js';
13
15
  import { renderManagedList, renderManagedHistory } from '../renderer/managed-history-renderer.js';
@@ -26,6 +28,9 @@ import { confidenceOf, toolStabilityOf, } from '../observability/skill-health-an
26
28
  import { parseSkillHealthReport } from '../observability/skill-health-report.js';
27
29
  import { DEFAULT_OBSERVATIONS_DIR, findObservationInboxItem, formatObservationShow, queryObservationInbox } from '../observability/inbox.js';
28
30
  import { buildObservationInboxViewModel } from '../observability/inbox-view-model.js';
31
+ import { buildKnowledgeDebuggerViewModel } from '../observability/knowledge-debugger.js';
32
+ import { createCodexConversationCatalog, } from '../observability/conversation-catalog.js';
33
+ import { loadObservationSourceRecordArchive, summarizeObservationSourceRecordArchive, } from '../observability/source-record-archive.js';
29
34
  import { activeStudioDiagnostics } from '../diagnosis/studio-projection.js';
30
35
  import { deleteObservationReviewState, loadObservationReviewState, ObservationReviewStateValidationError, updateObservationReviewState, } from '../observability/review-state.js';
31
36
  const DEFAULT_PORT = 7799;
@@ -685,9 +690,19 @@ export function formatListenError(p, err) {
685
690
  // EADDRINUSE or unknown — let caller try the omk-takeover flow.
686
691
  return null;
687
692
  }
688
- export function createReportServer({ port, host: hostOption, reportsDir, analysesDir, doctorsDir, observationsDir = DEFAULT_OBSERVATIONS_DIR, jobsDir = DEFAULT_JOBS_DIR, managedDir, store, jobStore, includeObserveCards = false, includeDoctorCards = false } = {}) {
693
+ function findKnowledgeDebuggerContext(observationsDir, experienceSessionId) {
694
+ const inbox = buildObservationInboxViewModel(observationsDir);
695
+ const report = inbox.reports.find((candidate) => candidate.experience?.sessions.some((session) => session.id === experienceSessionId));
696
+ const session = report?.experience?.sessions.find((candidate) => candidate.id === experienceSessionId);
697
+ if (!report || !session)
698
+ return undefined;
699
+ const sourceRecordRef = report.meta.sourceRecordArchives?.find((candidate) => candidate.experienceSessionId === experienceSessionId);
700
+ return { report, session, sourceRecordRef };
701
+ }
702
+ export function createReportServer({ port, host: hostOption, reportsDir, analysesDir, doctorsDir, observationsDir = DEFAULT_OBSERVATIONS_DIR, jobsDir = DEFAULT_JOBS_DIR, managedDir, store, jobStore, conversationCatalog, includeObserveCards = false, includeDoctorCards = false } = {}) {
689
703
  let server = null;
690
704
  let serverUrl = null;
705
+ const liveStreamClosers = new Set();
691
706
  // reports store:显式注入 store > 显式 reportsDir(eval serve 的本次 outputDir / 测试 / 覆盖,固定单目录)
692
707
  // > 缺省 indexed(机器级总览:当前项目 + 全局 live ∪ 别项目的索引卡片,按 id dedup)。indexed 在 store 层做
693
708
  // merge,底层 createFileStore 的 mtime 指纹自动反映内容变化,一次构建即可。
@@ -696,6 +711,7 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
696
711
  ? createFileStore(reportsDir)
697
712
  : createIndexedReportStore({ projectDir: projectReportsDir(), globalDir: globalReportsDir() }));
698
713
  const resolvedJobStore = jobStore || createFileJobStore(jobsDir);
714
+ const resolvedConversationCatalog = conversationCatalog ?? createCodexConversationCatalog();
699
715
  // 受管根目录按**请求**解析,不在启动时冻结 —— 否则长会话里会跟 omk list 分叉:Studio 启动时项目 .omk/managed
700
716
  // 还空、回退到 global,随后用户在项目里首次 omk install,omk list 下次会切到 project,而冻结了 root 的 Studio
701
717
  // 仍盯着旧 global,页面与 CLI 不一致。cwd 在进程内不变,变的是目录里有没有记录,故每次请求重判。
@@ -773,8 +789,12 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
773
789
  res.writeHead(200, { 'Content-Type': 'application/json' });
774
790
  res.end(JSON.stringify({ ok: true }));
775
791
  // Graceful shutdown after response sent
776
- setTimeout(() => { if (server)
777
- server.close(); }, 100);
792
+ setTimeout(() => {
793
+ for (const close of [...liveStreamClosers])
794
+ close();
795
+ if (server)
796
+ server.close();
797
+ }, 100);
778
798
  return;
779
799
  }
780
800
  if (path === '/api/reports') {
@@ -872,6 +892,245 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
872
892
  res.end(html);
873
893
  return;
874
894
  }
895
+ if (path === '/api/conversations/activity') {
896
+ const snapshot = buildConversationActivitySnapshot(await resolvedConversationCatalog.listConversations());
897
+ res.writeHead(200, {
898
+ 'Content-Type': 'application/json; charset=utf-8',
899
+ 'Cache-Control': 'no-store',
900
+ });
901
+ res.end(JSON.stringify(snapshot));
902
+ return;
903
+ }
904
+ if (path === '/conversations') {
905
+ const html = renderConversationIndexPage(await resolvedConversationCatalog.listConversations(), lang);
906
+ res.writeHead(200, {
907
+ 'Content-Type': 'text/html; charset=utf-8',
908
+ 'Cache-Control': 'no-store',
909
+ });
910
+ res.end(html);
911
+ return;
912
+ }
913
+ const conversationTaskLiveMatch = path.match(/^\/api\/conversations\/([^/]+)\/tasks\/([^/]+)\/live$/);
914
+ if (conversationTaskLiveMatch) {
915
+ let threadId = '';
916
+ let turnId = '';
917
+ try {
918
+ threadId = decodeURIComponent(conversationTaskLiveMatch[1]);
919
+ turnId = decodeURIComponent(conversationTaskLiveMatch[2]);
920
+ }
921
+ catch { /* invalid path */ }
922
+ const initial = threadId && turnId
923
+ ? await resolvedConversationCatalog.loadTaskTrajectory(threadId, turnId)
924
+ : undefined;
925
+ if (!initial) {
926
+ res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
927
+ res.end(JSON.stringify({ error: 'task_trajectory_not_found' }));
928
+ return;
929
+ }
930
+ if (!resolvedConversationCatalog.observeTaskTrajectory) {
931
+ res.writeHead(501, { 'Content-Type': 'application/json; charset=utf-8' });
932
+ res.end(JSON.stringify({ error: 'live_task_trajectory_unavailable' }));
933
+ return;
934
+ }
935
+ res.writeHead(200, {
936
+ 'Content-Type': 'text/event-stream; charset=utf-8',
937
+ 'Cache-Control': 'no-cache, no-transform',
938
+ Connection: 'keep-alive',
939
+ 'X-Accel-Buffering': 'no',
940
+ });
941
+ res.flushHeaders();
942
+ let closed = false;
943
+ let unsubscribe;
944
+ const lifecycle = new AbortController();
945
+ const heartbeat = setInterval(() => {
946
+ if (!res.destroyed && !res.writableEnded)
947
+ res.write(': keepalive\n\n');
948
+ }, 15_000);
949
+ heartbeat.unref?.();
950
+ const close = () => {
951
+ if (closed)
952
+ return;
953
+ closed = true;
954
+ clearInterval(heartbeat);
955
+ lifecycle.abort();
956
+ unsubscribe?.();
957
+ liveStreamClosers.delete(close);
958
+ if (!res.destroyed && !res.writableEnded)
959
+ res.end();
960
+ };
961
+ liveStreamClosers.add(close);
962
+ req.once('close', close);
963
+ res.once('close', close);
964
+ try {
965
+ unsubscribe = await resolvedConversationCatalog.observeTaskTrajectory(threadId, turnId, {
966
+ next: (trajectory) => {
967
+ if (closed || res.destroyed || res.writableEnded)
968
+ return;
969
+ res.write(`id: ${trajectory.revision}\n`);
970
+ res.write('event: trajectory\n');
971
+ res.write(`data: ${JSON.stringify({
972
+ revision: trajectory.revision,
973
+ status: trajectory.status,
974
+ liveObservable: trajectory.liveObservable,
975
+ })}\n\n`);
976
+ },
977
+ complete: close,
978
+ error: (cause) => {
979
+ if (!closed && !res.destroyed && !res.writableEnded) {
980
+ res.write('event: trajectory-error\n');
981
+ res.write(`data: ${JSON.stringify({ error: getErrorMessage(cause) })}\n\n`);
982
+ }
983
+ close();
984
+ },
985
+ }, { signal: lifecycle.signal });
986
+ if (closed)
987
+ unsubscribe();
988
+ }
989
+ catch (cause) {
990
+ if (!closed && !res.destroyed && !res.writableEnded) {
991
+ res.write('event: trajectory-error\n');
992
+ res.write(`data: ${JSON.stringify({ error: getErrorMessage(cause) })}\n\n`);
993
+ }
994
+ close();
995
+ }
996
+ return;
997
+ }
998
+ const conversationTaskMatch = path.match(/^\/conversations\/([^/]+)\/tasks\/([^/]+)$/);
999
+ if (conversationTaskMatch) {
1000
+ let threadId = '';
1001
+ let turnId = '';
1002
+ try {
1003
+ threadId = decodeURIComponent(conversationTaskMatch[1]);
1004
+ turnId = decodeURIComponent(conversationTaskMatch[2]);
1005
+ }
1006
+ catch { /* invalid path */ }
1007
+ const trajectory = threadId && turnId
1008
+ ? await resolvedConversationCatalog.loadTaskTrajectory(threadId, turnId)
1009
+ : undefined;
1010
+ if (!trajectory) {
1011
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
1012
+ res.end(lang === 'en' ? 'task trajectory not found' : '任务轨迹不存在');
1013
+ return;
1014
+ }
1015
+ const sourceRecords = {
1016
+ ...trajectory.sourceRecords,
1017
+ records: [],
1018
+ };
1019
+ const html = renderKnowledgeDebuggerPage(buildKnowledgeDebuggerViewModel(trajectory.session, turnId, trajectory.ingestion, sourceRecords), lang, {
1020
+ sourceRecordsEndpoint: `/api/conversations/${encodeURIComponent(threadId)}/tasks/${encodeURIComponent(turnId)}/source-records`,
1021
+ ...(trajectory.liveObservable && resolvedConversationCatalog.observeTaskTrajectory ? {
1022
+ live: {
1023
+ endpoint: `/api/conversations/${encodeURIComponent(threadId)}/tasks/${encodeURIComponent(turnId)}/live`,
1024
+ revision: trajectory.revision,
1025
+ },
1026
+ } : {}),
1027
+ });
1028
+ res.writeHead(200, {
1029
+ 'Content-Type': 'text/html; charset=utf-8',
1030
+ 'Cache-Control': 'no-store',
1031
+ });
1032
+ res.end(html);
1033
+ return;
1034
+ }
1035
+ const conversationTaskSourceMatch = path.match(/^\/api\/conversations\/([^/]+)\/tasks\/([^/]+)\/source-records$/);
1036
+ if (conversationTaskSourceMatch) {
1037
+ let threadId = '';
1038
+ let turnId = '';
1039
+ try {
1040
+ threadId = decodeURIComponent(conversationTaskSourceMatch[1]);
1041
+ turnId = decodeURIComponent(conversationTaskSourceMatch[2]);
1042
+ }
1043
+ catch { /* invalid path */ }
1044
+ const trajectory = threadId && turnId
1045
+ ? await resolvedConversationCatalog.loadTaskTrajectory(threadId, turnId)
1046
+ : undefined;
1047
+ if (!trajectory) {
1048
+ res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
1049
+ res.end(JSON.stringify({ error: 'task_trajectory_not_found' }));
1050
+ return;
1051
+ }
1052
+ res.writeHead(200, {
1053
+ 'Content-Type': 'application/json; charset=utf-8',
1054
+ 'Cache-Control': 'no-store',
1055
+ });
1056
+ res.end(JSON.stringify(trajectory.sourceRecords));
1057
+ return;
1058
+ }
1059
+ const conversationDetailMatch = path.match(/^\/conversations\/([^/]+)$/);
1060
+ if (conversationDetailMatch) {
1061
+ let threadId = '';
1062
+ try {
1063
+ threadId = decodeURIComponent(conversationDetailMatch[1]);
1064
+ }
1065
+ catch { /* invalid path */ }
1066
+ const conversation = threadId ? await resolvedConversationCatalog.getConversation(threadId) : undefined;
1067
+ if (!conversation) {
1068
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
1069
+ res.end(lang === 'en' ? 'conversation not found' : '对话不存在');
1070
+ return;
1071
+ }
1072
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1073
+ res.end(renderConversationDetailPage(conversation, lang));
1074
+ return;
1075
+ }
1076
+ const knowledgeDebuggerMatch = path.match(/^\/observe-debugger\/(.+)$/);
1077
+ if (knowledgeDebuggerMatch) {
1078
+ let experienceSessionId = '';
1079
+ try {
1080
+ experienceSessionId = decodeURIComponent(knowledgeDebuggerMatch[1]);
1081
+ }
1082
+ catch { /* invalid path */ }
1083
+ const context = experienceSessionId
1084
+ ? findKnowledgeDebuggerContext(observationsDir, experienceSessionId)
1085
+ : undefined;
1086
+ if (!context) {
1087
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
1088
+ res.end(lang === 'en' ? 'experience session not found' : '观测会话不存在');
1089
+ return;
1090
+ }
1091
+ const targetTurnId = parsed.searchParams.get('turnId')?.trim();
1092
+ if (!targetTurnId) {
1093
+ const langQuery = lang === DEFAULT_LANG ? '' : '?lang=en';
1094
+ res.writeHead(302, {
1095
+ Location: `/conversations/${encodeURIComponent(context.session.threadId)}${langQuery}`,
1096
+ });
1097
+ res.end();
1098
+ return;
1099
+ }
1100
+ if (!context.session.turns.some((turn) => turn.turnId === targetTurnId)) {
1101
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
1102
+ res.end(lang === 'en' ? 'task turn not found' : '任务不存在');
1103
+ return;
1104
+ }
1105
+ const html = renderKnowledgeDebuggerPage(buildKnowledgeDebuggerViewModel(context.session, targetTurnId, context.report.meta.ingestion, summarizeObservationSourceRecordArchive(context.sourceRecordRef)), lang, {
1106
+ sourceRecordsEndpoint: `/api/observe-debugger/${encodeURIComponent(experienceSessionId)}/source-records`,
1107
+ });
1108
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1109
+ res.end(html);
1110
+ return;
1111
+ }
1112
+ const knowledgeDebuggerSourceMatch = path.match(/^\/api\/observe-debugger\/(.+)\/source-records$/);
1113
+ if (knowledgeDebuggerSourceMatch) {
1114
+ let experienceSessionId = '';
1115
+ try {
1116
+ experienceSessionId = decodeURIComponent(knowledgeDebuggerSourceMatch[1]);
1117
+ }
1118
+ catch { /* invalid path */ }
1119
+ const context = experienceSessionId
1120
+ ? findKnowledgeDebuggerContext(observationsDir, experienceSessionId)
1121
+ : undefined;
1122
+ if (!context) {
1123
+ res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
1124
+ res.end(JSON.stringify({ error: 'experience_session_not_found' }));
1125
+ return;
1126
+ }
1127
+ res.writeHead(200, {
1128
+ 'Content-Type': 'application/json; charset=utf-8',
1129
+ 'Cache-Control': 'no-store',
1130
+ });
1131
+ res.end(JSON.stringify(loadObservationSourceRecordArchive(context.sourceRecordRef, observationsDir)));
1132
+ return;
1133
+ }
875
1134
  if (path === '/api/observe-inbox') {
876
1135
  const severity = parsed.searchParams.get('severity');
877
1136
  const skill = parsed.searchParams.get('skill');
@@ -1155,10 +1414,17 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
1155
1414
  res.end(renderRunList(runs, lang));
1156
1415
  return;
1157
1416
  }
1158
- // 根路径走 skill-centric 列表页 — studio 顶级实体是 skill。
1159
- // insightsBySkill 在 buildSkillIndex 里跟 SkillIndex 一起算好并享受同一份缓存,
1160
- // list renderer 直接消费,不再每请求 N×detectInsights 重算。
1417
+ // Studio 先呈现机器上的 Codex 主对话,再从某次对话进入原生 turn 任务轨迹。
1418
+ // /conversations 保留为同义入口,避免旧书签失效。
1161
1419
  if (path === '/') {
1420
+ const html = renderConversationIndexPage(await resolvedConversationCatalog.listConversations(), lang);
1421
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1422
+ res.end(html);
1423
+ return;
1424
+ }
1425
+ // 原 skill-centric 工作台迁到 /knowledge。insightsBySkill 在 buildSkillIndex 里
1426
+ // 跟 SkillIndex 一起算好并享受同一份缓存,renderer 只负责呈现。
1427
+ if (path === '/knowledge') {
1162
1428
  const runs = await reportStore.list();
1163
1429
  const idx = buildSkillIndex(runs, analysesDir, doctorsDir, observationsDir, skillIndexOptions());
1164
1430
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -1308,6 +1574,8 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
1308
1574
  async function stop() {
1309
1575
  if (!server)
1310
1576
  return;
1577
+ for (const close of [...liveStreamClosers])
1578
+ close();
1311
1579
  await new Promise((resolve) => server.close(() => resolve()));
1312
1580
  server = null;
1313
1581
  serverUrl = null;
@@ -214,6 +214,55 @@ export interface ObservationSessionTimeRange {
214
214
  endTimestamp?: string;
215
215
  durationMs?: number;
216
216
  }
217
+ export type ObservationSourceRecordArchiveStatus = 'available' | 'partial' | 'unavailable';
218
+ export type ObservationSourceRecordArchiveReason = 'no_record_ranges' | 'source_missing' | 'unsupported_source' | 'read_failed' | 'archive_limit';
219
+ /**
220
+ * A report only retains a relative pointer to its bounded source-record archive.
221
+ * The archive itself stays beside the report so Studio never follows an
222
+ * arbitrary absolute path supplied by report JSON.
223
+ */
224
+ export interface ObservationSourceRecordArchiveRef {
225
+ experienceSessionId: string;
226
+ status: ObservationSourceRecordArchiveStatus;
227
+ relativePath?: string;
228
+ recordCount: number;
229
+ omittedRecordCount: number;
230
+ byteCount: number;
231
+ truncated: boolean;
232
+ reason?: ObservationSourceRecordArchiveReason;
233
+ }
234
+ export interface ObservationSourceRecord {
235
+ sourceIndex: number;
236
+ traceId: string;
237
+ sourceTrace: string;
238
+ sourceType: string;
239
+ sourceEventId?: string;
240
+ timestamp?: string;
241
+ raw: string;
242
+ byteCount: number;
243
+ truncated: boolean;
244
+ redacted: boolean;
245
+ }
246
+ export interface ObservationSourceRecordArchive {
247
+ archiveKind: 'observe-source-records';
248
+ schemaVersion: 1;
249
+ experienceSessionId: string;
250
+ generatedAt: string;
251
+ records: ObservationSourceRecord[];
252
+ omittedRecordCount: number;
253
+ byteCount: number;
254
+ truncated: boolean;
255
+ }
256
+ export interface ObservationSourceRecordArchiveView {
257
+ status: ObservationSourceRecordArchiveStatus;
258
+ /** Total retained records, even when `records` is intentionally lazy. */
259
+ recordCount: number;
260
+ records: ObservationSourceRecord[];
261
+ omittedRecordCount: number;
262
+ byteCount: number;
263
+ truncated: boolean;
264
+ reason?: ObservationSourceRecordArchiveReason | 'archive_invalid';
265
+ }
217
266
  export interface ObservationInboxReport {
218
267
  kind: 'observe-inbox';
219
268
  schemaVersion: 2;
@@ -236,6 +285,7 @@ export interface ObservationInboxReport {
236
285
  skillToolCallCounts?: Record<string, Record<string, number>>;
237
286
  timestampedSegmentCount?: number;
238
287
  timestampCoverage?: number;
288
+ sourceRecordArchives?: ObservationSourceRecordArchiveRef[];
239
289
  };
240
290
  items: ObservationInboxItem[];
241
291
  experience?: ObservationExperienceReport;
@@ -257,7 +307,7 @@ export interface BuildObservationInboxReportOptions {
257
307
  }
258
308
  export type ExperienceReviewPriority = 'review_first' | 'sample_review' | 'routine_sample';
259
309
  export type ExperienceGoalSliceReasonCode = 'skill_segment_boundary' | 'explicit_user_goal_shift' | 'default_session_slice';
260
- export type ExperienceEvidenceKind = 'user_message' | 'synthetic_user_event' | 'assistant_message' | 'tool_use' | 'tool_result' | 'skill_context' | 'runtime_context' | 'observation';
310
+ export type ExperienceEvidenceKind = 'user_message' | 'synthetic_user_event' | 'assistant_message' | 'model_activity' | 'agent_activity' | 'tool_use' | 'tool_result' | 'skill_context' | 'runtime_context' | 'lifecycle' | 'observation';
261
311
  export type ExperienceAssistiveInferenceCode = 'review_recommended' | 'sample_recommended' | 'positive_signal_observed' | 'user_switched_topic_neutral' | 'no_obvious_issue_from_rules' | 'insufficient_human_context';
262
312
  export type ExperienceAssistiveInferenceConfidence = 'low' | 'medium' | 'high';
263
313
  export type ExperienceAssistiveInferenceCautionCode = 'no_llm_judge' | 'rule_only' | 'runtime_context_excluded' | 'skill_context_excluded' | 'no_human_user_message' | 'limited_timeline_window';
@@ -297,21 +347,187 @@ export interface ExperienceEvidenceRef {
297
347
  logicalMessageIndex?: number;
298
348
  sourceLineIndex?: number;
299
349
  messageUuid?: string;
350
+ /** Source-native record classification retained after normalization. */
351
+ sourceType?: string;
352
+ /** Source-neutral identity for one agent turn, when the trace exposes it. */
353
+ turnId?: string;
300
354
  /** Source-neutral identity for one concrete tool-call occurrence. */
301
355
  callInstanceId?: string;
302
356
  toolUseId?: string;
303
357
  timestamp?: string;
304
358
  role?: 'user' | 'assistant' | 'tool' | 'other';
359
+ modelActivityKind?: 'reasoning';
360
+ contentVisibility?: 'plaintext' | 'opaque';
361
+ contentSource?: 'summary' | 'content' | 'text';
362
+ runtimeKind?: 'session_context' | 'execution_context' | 'settings' | 'goal' | 'context_compaction' | 'usage';
305
363
  label?: string;
306
364
  snippet?: string;
307
365
  }
308
366
  export interface ExperienceTimelineEvent extends ExperienceEvidenceRef {
309
367
  order: number;
368
+ /** Model explicitly associated with this normalized event by the source adapter. */
369
+ model?: string;
310
370
  toolName?: string;
311
371
  toolStatus?: ToolCallStatus;
312
372
  isError?: boolean;
313
373
  fullText?: string;
314
374
  }
375
+ export type DebugKnowledgeKind = 'project_instruction' | 'skill' | 'runtime_evidence';
376
+ export type DebugKnowledgeAccessKind = 'injected' | 'read' | 'returned';
377
+ export type TaskReplayStepKind = 'user_request' | 'user_message' | 'user_correction' | 'runtime_context' | 'skill_context' | 'tool_exchange' | 'unmatched_tool_result' | 'assistant_message' | 'model_activity' | 'lifecycle' | 'observation' | 'system_event';
378
+ export type TaskReplayIntegrityCode = 'task_boundary_unavailable' | 'timeline_truncated' | 'malformed_records' | 'ignored_values' | 'unknown_events' | 'unmatched_tool_calls' | 'unmatched_tool_results' | 'missing_timestamps';
379
+ export type TaskWindowBasis = 'turn_id' | 'turn_lifecycle' | 'user_message' | 'unresolved';
380
+ export type ExperienceTurnStatus = 'completed' | 'aborted' | 'interrupted' | 'open' | 'unknown';
381
+ /**
382
+ * One user-visible task inside a source thread. `turnId` is the stable,
383
+ * source-neutral identity used by Studio routes. `sourceTurnId` preserves the
384
+ * runtime-native identity when the source exposes one.
385
+ */
386
+ export interface ExperienceTurnSummary {
387
+ turnId: string;
388
+ sourceTurnId?: string;
389
+ boundaryBasis: Exclude<TaskWindowBasis, 'unresolved'>;
390
+ traceId?: string;
391
+ sourceTrace: string;
392
+ startTimestamp?: string;
393
+ endTimestamp?: string;
394
+ status: ExperienceTurnStatus;
395
+ title: string;
396
+ eventIds: string[];
397
+ userMessageCount: number;
398
+ assistantMessageCount: number;
399
+ toolCallCount: number;
400
+ toolFailureCount: number;
401
+ }
402
+ export interface ConversationTaskItem {
403
+ turnId: string;
404
+ sourceTurnId?: string;
405
+ /** Existing observe reports retain their report-scoped session route. */
406
+ experienceSessionId?: string;
407
+ /** Preferred source-neutral task trajectory route. */
408
+ trajectoryHref?: string;
409
+ title: string;
410
+ startTimestamp?: string;
411
+ endTimestamp?: string;
412
+ durationMs?: number;
413
+ status: ExperienceTurnStatus;
414
+ eventCount: number;
415
+ toolCallCount: number;
416
+ toolFailureCount: number;
417
+ relatedSkillNames: string[];
418
+ }
419
+ export interface ConversationListItem {
420
+ threadId: string;
421
+ sourceThreadId: string;
422
+ sourceKind: ObservationSourceKind;
423
+ title: string;
424
+ preview?: string;
425
+ cwd?: string;
426
+ model?: string;
427
+ reasoningEffort?: string;
428
+ archived?: boolean;
429
+ tokensUsed?: number;
430
+ childThreadCount?: number;
431
+ startTimestamp?: string;
432
+ endTimestamp?: string;
433
+ durationMs?: number;
434
+ /** Undefined until the selected rollout has been indexed. */
435
+ turnCount?: number;
436
+ toolCallCount?: number;
437
+ toolFailureCount?: number;
438
+ relatedSkillNames: string[];
439
+ tasks: ConversationTaskItem[];
440
+ }
441
+ export interface ConversationIndexViewModel {
442
+ conversations: ConversationListItem[];
443
+ totalTurnCount: number;
444
+ totalToolCallCount: number;
445
+ totalToolFailureCount: number;
446
+ indexedConversationCount?: number;
447
+ unarchivedConversationCount?: number;
448
+ archivedConversationCount?: number;
449
+ workspaceCount?: number;
450
+ }
451
+ /**
452
+ * Source-neutral task trajectory input. Observe reports carry more review and
453
+ * Skill-attribution fields, but the debugger only requires this trace slice.
454
+ */
455
+ export interface TaskTrajectorySession {
456
+ id: string;
457
+ threadId: string;
458
+ sourceThreadId: string;
459
+ sessionId: string;
460
+ sourceTrace: string;
461
+ sourceKind: ObservationSourceKind;
462
+ entrypoint?: string;
463
+ sourceMetadata?: TraceSourceMetadata;
464
+ cwd?: string;
465
+ startTimestamp?: string;
466
+ endTimestamp?: string;
467
+ attributedEventIds: string[];
468
+ turns: ExperienceTurnSummary[];
469
+ fullSessionTimeline: ExperienceTimelineEvent[];
470
+ indicators?: Pick<ExperienceReviewIndicators, 'userCorrectionCount'>;
471
+ sessionStory?: ExperienceSessionStory;
472
+ }
473
+ export interface TaskWindowScope {
474
+ basis: TaskWindowBasis;
475
+ turnId?: string;
476
+ normalizedEventCount: number;
477
+ semanticEventCount: number;
478
+ attributedEventCount: number;
479
+ matchedAttributedEventCount: number;
480
+ truncated: boolean;
481
+ }
482
+ export interface DebugKnowledgeEvidence {
483
+ id: string;
484
+ knowledgeKind: DebugKnowledgeKind;
485
+ accessKind: DebugKnowledgeAccessKind;
486
+ label: string;
487
+ sourceLocator?: string;
488
+ contentHash?: string;
489
+ firstSeen?: string;
490
+ lastSeen?: string;
491
+ accessCount: number;
492
+ evidenceRefs: ExperienceEvidenceRef[];
493
+ }
494
+ export interface TaskReplayStep {
495
+ id: string;
496
+ order: number;
497
+ stepKind: TaskReplayStepKind;
498
+ timestamp?: string;
499
+ title: string;
500
+ events: ExperienceTimelineEvent[];
501
+ toolStatus?: ToolCallStatus;
502
+ knowledgeEvidenceIds: string[];
503
+ }
504
+ export interface TaskReplayIntegrityNotice {
505
+ code: TaskReplayIntegrityCode;
506
+ count: number;
507
+ }
508
+ export interface KnowledgeDebuggerViewModel {
509
+ session: TaskTrajectorySession;
510
+ taskScope: TaskWindowScope;
511
+ summary: {
512
+ userGoal?: string;
513
+ finalResponse?: string;
514
+ observedStartTimestamp?: string;
515
+ observedEndTimestamp?: string;
516
+ toolCallCount: number;
517
+ toolFailureCount: number;
518
+ hasUserCorrection: boolean;
519
+ /** Distinct models observed inside the attributed task window, in first-seen order. */
520
+ observedModels: string[];
521
+ };
522
+ steps: TaskReplayStep[];
523
+ normalizedEvents: ExperienceTimelineEvent[];
524
+ sourceRecords: ObservationSourceRecordArchiveView;
525
+ knowledgeEvidence: DebugKnowledgeEvidence[];
526
+ integrity: {
527
+ status: 'complete' | 'partial';
528
+ notices: TaskReplayIntegrityNotice[];
529
+ };
530
+ }
315
531
  export interface ExperienceTimelineBranch {
316
532
  id: string;
317
533
  label: string;
@@ -747,6 +963,10 @@ export interface ExperienceInvocation {
747
963
  export interface ExperienceSessionSummary {
748
964
  id: string;
749
965
  skillName: string;
966
+ /** Stable source-neutral identity for the root conversation/thread. */
967
+ threadId: string;
968
+ /** Runtime-native thread/run identity retained for inspection. */
969
+ sourceThreadId: string;
750
970
  sessionId: string;
751
971
  sourceTrace: string;
752
972
  sourceKind: ObservationSourceKind;
@@ -773,6 +993,10 @@ export interface ExperienceSessionSummary {
773
993
  relatedObservationIds: string[];
774
994
  timelineRef?: string;
775
995
  timelinePreviewEventIds?: string[];
996
+ /** Hydrated exact event relation derived from invocation timelineEventIds. */
997
+ attributedEventIds: string[];
998
+ /** All observable tasks in this thread, independent of Skill attribution. */
999
+ turns: ExperienceTurnSummary[];
776
1000
  timelinePreview: ExperienceTimelineEvent[];
777
1001
  fullSessionTimeline: ExperienceTimelineEvent[];
778
1002
  timelineTree?: ExperienceTimelineTree;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "oh-my-knowledge",
3
- "version": "0.51.2",
3
+ "version": "0.52.0",
4
4
  "packageManager": "yarn@4.16.0",
5
- "description": "Evaluation framework for LLM knowledge inputs — prompts, RAG corpora, skills, agent workflows. Fix the model, vary the artifact. Built-in statistical rigor: bootstrap CI, Krippendorff α, length-debias, saturation curves.",
5
+ "description": "OMK — Observe. Measure. Know. Evidence-backed knowledge changes for AI applications.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "omk": "dist/cli/index.js"
@@ -75,6 +75,9 @@
75
75
  "benchmark",
76
76
  "skill-evaluation",
77
77
  "knowledge-engineering",
78
+ "knowledge-artifacts",
79
+ "release-evidence",
80
+ "llm-observability",
78
81
  "prompt-engineering",
79
82
  "codex",
80
83
  "codex-cli",
@@ -98,9 +101,9 @@
98
101
  "@anthropic-ai/claude-agent-sdk": "^0.3.143",
99
102
  "@anthropic-ai/sdk": "^0.115.0",
100
103
  "@inquirer/prompts": "^8.4.3",
101
- "@modelcontextprotocol/sdk": "^1.29.0",
104
+ "@modelcontextprotocol/sdk": "^1.30.0",
102
105
  "@oclif/core": "^4",
103
- "@openai/codex-sdk": "0.145.0",
106
+ "@openai/codex-sdk": "0.146.0",
104
107
  "ajv": "^8.18.0",
105
108
  "chart.js": "^4.5.1",
106
109
  "es-module-lexer": "^2.0.0",
@@ -117,7 +120,7 @@
117
120
  "eslint": "^10.1.0",
118
121
  "husky": "^9.1.7",
119
122
  "lint-staged": "17.2.0",
120
- "npm-run-all2": "^9.0.1",
123
+ "npm-run-all2": "^9.0.3",
121
124
  "typescript": "^6.0.2",
122
125
  "typescript-eslint": "^8.58.0",
123
126
  "vitepress": "^1.6.4",