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.
- package/README.md +27 -6
- package/README.zh.md +27 -6
- package/dist/assets/agent-skills/omk/SKILL.md +8 -8
- package/dist/observability/codex-conversation-index.d.ts +88 -0
- package/dist/observability/codex-conversation-index.js +566 -0
- package/dist/observability/codex-protocol.d.ts +12 -0
- package/dist/observability/codex-protocol.js +78 -0
- package/dist/observability/codex-tool-status.d.ts +16 -0
- package/dist/observability/codex-tool-status.js +114 -0
- package/dist/observability/codex-trace-adapter.js +342 -119
- package/dist/observability/conversation-catalog.d.ts +38 -0
- package/dist/observability/conversation-catalog.js +573 -0
- package/dist/observability/conversation-index-process.d.ts +1 -0
- package/dist/observability/conversation-index-process.js +31 -0
- package/dist/observability/conversation-view-model.d.ts +5 -0
- package/dist/observability/conversation-view-model.js +100 -0
- package/dist/observability/experience.d.ts +3 -1
- package/dist/observability/experience.js +248 -7
- package/dist/observability/inbox.js +40 -2
- package/dist/observability/knowledge-debugger.d.ts +4 -0
- package/dist/observability/knowledge-debugger.js +364 -0
- package/dist/observability/polling-subscription-hub.d.ts +27 -0
- package/dist/observability/polling-subscription-hub.js +149 -0
- package/dist/observability/source-record-archive.d.ts +13 -0
- package/dist/observability/source-record-archive.js +324 -0
- package/dist/observability/task-window.d.ts +8 -0
- package/dist/observability/task-window.js +36 -0
- package/dist/observability/trace-ir.d.ts +66 -1
- package/dist/observability/trace-source.d.ts +1 -0
- package/dist/observability/trace-source.js +12 -5
- package/dist/observability/turn-index.d.ts +3 -0
- package/dist/observability/turn-index.js +251 -0
- package/dist/renderer/conversation-renderer.d.ts +9 -0
- package/dist/renderer/conversation-renderer.js +410 -0
- package/dist/renderer/icons.js +1 -0
- package/dist/renderer/inline-markdown.d.ts +6 -0
- package/dist/renderer/inline-markdown.js +158 -0
- package/dist/renderer/knowledge-debugger-renderer.d.ts +9 -0
- package/dist/renderer/knowledge-debugger-renderer.js +2280 -0
- package/dist/renderer/observation-inbox-renderer.js +9 -0
- package/dist/renderer/skill-list-renderer.js +2 -1
- package/dist/renderer/trajectory-live.d.ts +42 -0
- package/dist/renderer/trajectory-live.js +258 -0
- package/dist/renderer/trajectory-routing.d.ts +60 -0
- package/dist/renderer/trajectory-routing.js +351 -0
- package/dist/server/report-server.d.ts +4 -1
- package/dist/server/report-server.js +274 -6
- package/dist/types/observability.d.ts +225 -1
- 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
|
-
|
|
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(() => {
|
|
777
|
-
|
|
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
|
-
//
|
|
1159
|
-
//
|
|
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.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"packageManager": "yarn@4.16.0",
|
|
5
|
-
"description": "
|
|
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.
|
|
104
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
102
105
|
"@oclif/core": "^4",
|
|
103
|
-
"@openai/codex-sdk": "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.
|
|
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",
|