oh-my-knowledge 0.52.2 → 0.52.3-oidc.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/dist/observability/codex-conversation-index.d.ts +1 -1
- package/dist/observability/codex-conversation-index.js +1 -1
- package/dist/observability/codex-protocol.d.ts +6 -0
- package/dist/observability/codex-protocol.js +37 -2
- package/dist/observability/codex-trace-adapter.js +12 -4
- package/dist/observability/experience.js +9 -1
- package/dist/observability/trace-ir.d.ts +6 -0
- package/dist/renderer/conversation-renderer.d.ts +7 -0
- package/dist/renderer/conversation-renderer.js +108 -6
- package/dist/renderer/icons.js +1 -0
- package/dist/renderer/knowledge-debugger-renderer.js +16 -1
- package/dist/server/report-server.js +25 -2
- package/dist/types/observability.d.ts +4 -0
- package/package.json +4 -4
|
@@ -3,7 +3,7 @@ import { codexUserDisplayText, codexUserMessageOrigin } from './codex-protocol.j
|
|
|
3
3
|
import { codexRuntimeToolOutcomeFromPayload, codexToolOutputOutcome, } from './codex-tool-status.js';
|
|
4
4
|
const READ_CHUNK_BYTES = 256 * 1024;
|
|
5
5
|
const MAX_RECORD_BYTES = 32 * 1024 * 1024;
|
|
6
|
-
const INDEX_SCHEMA_VERSION =
|
|
6
|
+
const INDEX_SCHEMA_VERSION = 12;
|
|
7
7
|
const MAX_CURRENT_INDEX_ATTEMPTS = 4;
|
|
8
8
|
/**
|
|
9
9
|
* Build a compact byte-range index without retaining the rollout. Only records
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/** Stable inventory of Codex rollout record shapes understood by OMK. */
|
|
2
|
+
export interface CodexUserAttachment {
|
|
3
|
+
attachmentKind: 'image' | 'file';
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
2
6
|
export declare function isCodexResponseItemType(value: string | undefined): boolean;
|
|
3
7
|
export declare function isCodexEventMessageType(value: string | undefined): boolean;
|
|
4
8
|
/** Preserve source text separately while removing Codex UI transport envelopes from semantic display. */
|
|
5
9
|
export declare function codexUserDisplayText(text: string): string | undefined;
|
|
10
|
+
/** Extract privacy-safe attachment metadata without projecting local paths into Trace IR. */
|
|
11
|
+
export declare function codexUserAttachments(text: string): CodexUserAttachment[];
|
|
6
12
|
export declare function codexUserMessageOrigin(text: string): 'human' | 'runtime';
|
|
7
13
|
/**
|
|
8
14
|
* Records consumed by correlation or retained only as source provenance do not
|
|
@@ -32,6 +32,9 @@ const EVENT_MESSAGE_TYPES = new Set([
|
|
|
32
32
|
]);
|
|
33
33
|
const IN_APP_BROWSER_CONTEXT_RE = /<in-app-browser-context\b[^>]*>[\s\S]*?<\/in-app-browser-context>/gi;
|
|
34
34
|
const CODEX_RUNTIME_MESSAGE_RE = /^# AGENTS\.md instructions\b|^<(?:app-context|environment_context|permissions instructions|collaboration_mode|apps_instructions|plugins_instructions|skills_instructions|recommended_plugins)>/i;
|
|
35
|
+
const CODEX_USER_REQUEST_HEADING_RE = /^## My request(?: for Codex)?:\s*$/im;
|
|
36
|
+
const CODEX_USER_FILES_HEADING_RE = /^# Files mentioned by the user:\s*$/im;
|
|
37
|
+
const IMAGE_ATTACHMENT_RE = /\.(?:avif|bmp|gif|heic|heif|jpe?g|png|webp)$/i;
|
|
35
38
|
export function isCodexResponseItemType(value) {
|
|
36
39
|
return value !== undefined && RESPONSE_ITEM_TYPES.has(value);
|
|
37
40
|
}
|
|
@@ -40,8 +43,7 @@ export function isCodexEventMessageType(value) {
|
|
|
40
43
|
}
|
|
41
44
|
/** Preserve source text separately while removing Codex UI transport envelopes from semantic display. */
|
|
42
45
|
export function codexUserDisplayText(text) {
|
|
43
|
-
const
|
|
44
|
-
const requestMatch = requestHeading.exec(text);
|
|
46
|
+
const requestMatch = CODEX_USER_REQUEST_HEADING_RE.exec(text);
|
|
45
47
|
const request = requestMatch
|
|
46
48
|
? text.slice(requestMatch.index + requestMatch[0].length)
|
|
47
49
|
: text;
|
|
@@ -53,6 +55,39 @@ export function codexUserDisplayText(text) {
|
|
|
53
55
|
.trim();
|
|
54
56
|
return visible || undefined;
|
|
55
57
|
}
|
|
58
|
+
/** Extract privacy-safe attachment metadata without projecting local paths into Trace IR. */
|
|
59
|
+
export function codexUserAttachments(text) {
|
|
60
|
+
const attachments = new Map();
|
|
61
|
+
const requestMatch = CODEX_USER_REQUEST_HEADING_RE.exec(text);
|
|
62
|
+
const filesMatch = CODEX_USER_FILES_HEADING_RE.exec(text);
|
|
63
|
+
if (filesMatch && (!requestMatch || filesMatch.index < requestMatch.index)) {
|
|
64
|
+
const sectionEnd = requestMatch?.index ?? text.length;
|
|
65
|
+
const section = text.slice(filesMatch.index + filesMatch[0].length, sectionEnd);
|
|
66
|
+
for (const match of section.matchAll(/^##\s+(.+?):\s+(.+)\s*$/gm)) {
|
|
67
|
+
addCodexUserAttachment(attachments, match[1], match[2]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const match of text.matchAll(/<image\b[^>]*\bpath="([^"]+)"[^>]*>/gi)) {
|
|
71
|
+
addCodexUserAttachment(attachments, undefined, match[1]);
|
|
72
|
+
}
|
|
73
|
+
return [...attachments.values()];
|
|
74
|
+
}
|
|
75
|
+
function addCodexUserAttachment(attachments, declaredName, sourcePath) {
|
|
76
|
+
const normalizedPath = sourcePath?.trim();
|
|
77
|
+
const inferredName = normalizedPath?.split(/[\\/]/).at(-1);
|
|
78
|
+
const name = declaredName?.trim() || inferredName?.trim();
|
|
79
|
+
if (!name)
|
|
80
|
+
return;
|
|
81
|
+
const key = name.toLocaleLowerCase('en-US');
|
|
82
|
+
if (attachments.has(key))
|
|
83
|
+
return;
|
|
84
|
+
attachments.set(key, {
|
|
85
|
+
attachmentKind: IMAGE_ATTACHMENT_RE.test(name) || IMAGE_ATTACHMENT_RE.test(normalizedPath ?? '')
|
|
86
|
+
? 'image'
|
|
87
|
+
: 'file',
|
|
88
|
+
name,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
56
91
|
export function codexUserMessageOrigin(text) {
|
|
57
92
|
const trimmed = text.trimStart();
|
|
58
93
|
if (CODEX_RUNTIME_MESSAGE_RE.test(trimmed))
|
|
@@ -4,7 +4,7 @@ import { correlateTraceToolEvents, createTraceId, normalizeTraceTimestamp, trace
|
|
|
4
4
|
import { nonNegativeMetric, optionalTokenCount, splitInclusiveInputTokens, tokenCount, } from '../shared/token-usage.js';
|
|
5
5
|
import { normalizeToolIdentity } from '../shared/tool-identity.js';
|
|
6
6
|
import { extractCodexExecCommands } from './codex-exec-command.js';
|
|
7
|
-
import { codexUserDisplayText, codexUserMessageOrigin, isCodexEventMessageType, isCodexRecordConsumedWithoutDirectEvent, isCodexResponseItemType, } from './codex-protocol.js';
|
|
7
|
+
import { codexUserAttachments, codexUserDisplayText, codexUserMessageOrigin, isCodexEventMessageType, isCodexRecordConsumedWithoutDirectEvent, isCodexResponseItemType, } from './codex-protocol.js';
|
|
8
8
|
import { codexRuntimeToolOutcome, codexToolOutputOutcome, codexToolStatusFromValue, } from './codex-tool-status.js';
|
|
9
9
|
export function isCodexJsonl(records) {
|
|
10
10
|
return records.some((record) => {
|
|
@@ -231,6 +231,7 @@ function convertCodexRecords(rawRecords, runId) {
|
|
|
231
231
|
: normalizedRole === 'system' ? 'runtime' : 'synthetic',
|
|
232
232
|
text,
|
|
233
233
|
displayText: normalizedRole === 'user' ? codexUserDisplayText(text) : undefined,
|
|
234
|
+
attachments: normalizedRole === 'user' ? codexUserAttachments(text) : undefined,
|
|
234
235
|
model: normalizedRole === 'assistant' ? activeModel : undefined,
|
|
235
236
|
});
|
|
236
237
|
}
|
|
@@ -444,6 +445,7 @@ function convertCodexRecords(rawRecords, runId) {
|
|
|
444
445
|
origin: codexUserMessageOrigin(text),
|
|
445
446
|
text,
|
|
446
447
|
displayText: codexUserDisplayText(text),
|
|
448
|
+
attachments: codexUserAttachments(text),
|
|
447
449
|
});
|
|
448
450
|
}
|
|
449
451
|
return;
|
|
@@ -995,6 +997,7 @@ function indexDuplicateEventMessages(records) {
|
|
|
995
997
|
const text = stringValue(payload.message);
|
|
996
998
|
if (!role || !text)
|
|
997
999
|
return;
|
|
1000
|
+
const mirrorText = role === 'user' ? codexUserDisplayText(text) : text;
|
|
998
1001
|
const nearbyRecords = [-1, 1].flatMap((direction) => {
|
|
999
1002
|
let candidateIndex = sourceIndex + direction;
|
|
1000
1003
|
while (candidateIndex >= 0 && candidateIndex < records.length) {
|
|
@@ -1010,9 +1013,14 @@ function indexDuplicateEventMessages(records) {
|
|
|
1010
1013
|
if (adjacent?.type !== 'response_item')
|
|
1011
1014
|
return false;
|
|
1012
1015
|
const adjacentPayload = isObject(adjacent.payload) ? adjacent.payload : {};
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
+
if (adjacentPayload.type !== 'message' || adjacentPayload.role !== role)
|
|
1017
|
+
return false;
|
|
1018
|
+
const adjacentText = codexContentText(adjacentPayload.content);
|
|
1019
|
+
if (!adjacentText)
|
|
1020
|
+
return false;
|
|
1021
|
+
return role === 'user'
|
|
1022
|
+
? Boolean(mirrorText) && codexUserDisplayText(adjacentText) === mirrorText
|
|
1023
|
+
: adjacentText === text;
|
|
1016
1024
|
});
|
|
1017
1025
|
if (mirrored)
|
|
1018
1026
|
duplicateIndexes.add(sourceIndex);
|
|
@@ -983,13 +983,20 @@ function isTimelineEvent(value) {
|
|
|
983
983
|
];
|
|
984
984
|
if (!optionalStrings.every((field) => field === undefined || typeof field === 'string')
|
|
985
985
|
|| !isOptionalTimestamp(value.timestamp)
|
|
986
|
-
|| (value.isError !== undefined && typeof value.isError !== 'boolean')
|
|
986
|
+
|| (value.isError !== undefined && typeof value.isError !== 'boolean')
|
|
987
|
+
|| (value.attachments !== undefined && !isTimelineAttachmentArray(value.attachments)))
|
|
987
988
|
return false;
|
|
988
989
|
return value.kind !== 'tool_result'
|
|
989
990
|
|| value.toolStatus === undefined
|
|
990
991
|
|| value.isError === undefined
|
|
991
992
|
|| value.isError === (value.toolStatus === 'failure');
|
|
992
993
|
}
|
|
994
|
+
function isTimelineAttachmentArray(value) {
|
|
995
|
+
return Array.isArray(value) && value.every((attachment) => (isObjectRecord(attachment)
|
|
996
|
+
&& (attachment.attachmentKind === 'image' || attachment.attachmentKind === 'file')
|
|
997
|
+
&& typeof attachment.name === 'string'
|
|
998
|
+
&& attachment.name.length > 0));
|
|
999
|
+
}
|
|
993
1000
|
function isTimelineEventArray(value) {
|
|
994
1001
|
return Array.isArray(value) && value.every(isTimelineEvent);
|
|
995
1002
|
}
|
|
@@ -2746,6 +2753,7 @@ function userTimelineEvents(event, base, order) {
|
|
|
2746
2753
|
order: order + (commandEnvelope ? 1 : 0),
|
|
2747
2754
|
snippet: snippet(semanticText, 700),
|
|
2748
2755
|
fullText: fullText(semanticText),
|
|
2756
|
+
attachments: event.attachments,
|
|
2749
2757
|
label: userTextEventLabel(kind),
|
|
2750
2758
|
}));
|
|
2751
2759
|
return events;
|
|
@@ -12,6 +12,11 @@ export type TraceModelActivityVisibility = 'plaintext' | 'opaque';
|
|
|
12
12
|
export type TraceModelActivityContentSource = 'summary' | 'content' | 'text';
|
|
13
13
|
export type TraceRuntimeContextKind = 'session_context' | 'execution_context' | 'settings' | 'goal';
|
|
14
14
|
export type TraceAgentActivityKind = 'communication' | 'status';
|
|
15
|
+
export interface TraceMessageAttachment {
|
|
16
|
+
attachmentKind: 'image' | 'file';
|
|
17
|
+
/** Privacy-safe display name. Source-local paths remain available only in raw logs. */
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
15
20
|
/** Source-neutral tool identity, including provider namespaces when present. */
|
|
16
21
|
export type TraceToolRef = NormalizedToolIdentity;
|
|
17
22
|
interface TraceEventBase {
|
|
@@ -30,6 +35,7 @@ export interface TraceMessageEvent extends TraceEventBase {
|
|
|
30
35
|
text: string;
|
|
31
36
|
/** Human-facing text after a source adapter removes transport/UI envelopes. */
|
|
32
37
|
displayText?: string;
|
|
38
|
+
attachments?: TraceMessageAttachment[];
|
|
33
39
|
model?: string;
|
|
34
40
|
attributionSkill?: string;
|
|
35
41
|
}
|
|
@@ -4,6 +4,13 @@ export interface ConversationActivitySnapshot {
|
|
|
4
4
|
revision: string;
|
|
5
5
|
runningCount: number;
|
|
6
6
|
}
|
|
7
|
+
export interface ConversationDetailActivitySnapshot {
|
|
8
|
+
schemaVersion: 1;
|
|
9
|
+
revision: string;
|
|
10
|
+
taskCount: number;
|
|
11
|
+
runningCount: number;
|
|
12
|
+
}
|
|
7
13
|
export declare function buildConversationActivitySnapshot(model: ConversationIndexViewModel): ConversationActivitySnapshot;
|
|
14
|
+
export declare function buildConversationDetailActivitySnapshot(conversation: ConversationListItem): ConversationDetailActivitySnapshot;
|
|
8
15
|
export declare function renderConversationIndexPage(model: ConversationIndexViewModel, lang?: Lang): string;
|
|
9
16
|
export declare function renderConversationDetailPage(conversation: ConversationListItem, lang?: Lang): string;
|
|
@@ -21,6 +21,20 @@ export function buildConversationActivitySnapshot(model) {
|
|
|
21
21
|
runningCount,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
export function buildConversationDetailActivitySnapshot(conversation) {
|
|
25
|
+
const state = conversation.tasks.map((task) => [
|
|
26
|
+
task.turnId,
|
|
27
|
+
task.status,
|
|
28
|
+
task.startTimestamp ?? null,
|
|
29
|
+
task.endTimestamp ?? null,
|
|
30
|
+
]);
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
revision: createHash('sha256').update(JSON.stringify(state)).digest('hex').slice(0, 24),
|
|
34
|
+
taskCount: conversation.tasks.length,
|
|
35
|
+
runningCount: conversation.tasks.filter((task) => task.status === 'open').length,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
24
38
|
function latestOpenConversationTask(conversation) {
|
|
25
39
|
for (let index = conversation.tasks.length - 1; index >= 0; index -= 1) {
|
|
26
40
|
const task = conversation.tasks[index];
|
|
@@ -94,11 +108,12 @@ export function renderConversationIndexPage(model, lang = DEFAULT_LANG) {
|
|
|
94
108
|
export function renderConversationDetailPage(conversation, lang = DEFAULT_LANG) {
|
|
95
109
|
const zh = lang === 'zh';
|
|
96
110
|
const langSuffix = lang === DEFAULT_LANG ? '' : '?lang=en';
|
|
111
|
+
const activity = buildConversationDetailActivitySnapshot(conversation);
|
|
97
112
|
const taskRows = conversationTaskEntries(conversation.tasks)
|
|
98
113
|
.map(({ task, ordinal }) => renderTaskRow(task, ordinal, lang))
|
|
99
114
|
.join('');
|
|
100
115
|
return layout(zh ? '对话任务' : 'Conversation tasks', `
|
|
101
|
-
<main class="conversation-page conversation-detail-page">
|
|
116
|
+
<main class="conversation-page conversation-detail-page" data-activity-revision="${e(activity.revision)}" data-activity-endpoint="/api/conversations/${encodeURIComponent(conversation.threadId)}/activity${langSuffix}">
|
|
102
117
|
<header class="conversation-page-head conversation-detail-head">
|
|
103
118
|
<div>
|
|
104
119
|
<a class="back-link" href="/conversations${langSuffix}">${zh ? '返回对话总览' : 'Back to conversations'}</a>
|
|
@@ -114,11 +129,12 @@ export function renderConversationDetailPage(conversation, lang = DEFAULT_LANG)
|
|
|
114
129
|
</div>
|
|
115
130
|
</header>
|
|
116
131
|
<section class="task-list" aria-label="${zh ? '任务列表' : 'Task list'}">
|
|
117
|
-
<header class="task-list-head"><span>${zh ? '任务' : 'Task'}</span><span>${zh ? '时间' : 'Time'}</span><span>${zh ? '执行' : 'Execution'}</span><span
|
|
132
|
+
<header class="task-list-head"><span>${zh ? '任务' : 'Task'}</span><span>${zh ? '时间' : 'Time'}</span><span>${zh ? '执行' : 'Execution'}</span><button type="button" class="task-order-toggle" data-task-order-toggle data-task-order="desc" aria-label="${zh ? '切换为最早优先' : 'Switch to oldest first'}" title="${zh ? '切换为最早优先' : 'Switch to oldest first'}">${icon('arrow-up-down', { size: 14 })}<span data-task-order-label>${zh ? '最新优先' : 'Newest first'}</span></button></header>
|
|
118
133
|
${taskRows || `<div class="empty-state"><strong>${zh ? '没有识别到任务边界' : 'No task boundaries found'}</strong><span>${zh ? '该对话的原始日志可能尚未写入完整的 turn 生命周期。' : 'The raw log may not contain complete turn lifecycle records yet.'}</span></div>`}
|
|
119
134
|
</section>
|
|
120
135
|
</main>
|
|
121
136
|
<style>${CSS}</style>
|
|
137
|
+
<script>${conversationDetailScript(lang)}</script>
|
|
122
138
|
`, lang);
|
|
123
139
|
}
|
|
124
140
|
function renderConversationRow(conversation, lang) {
|
|
@@ -161,14 +177,13 @@ function renderConversationRow(conversation, lang) {
|
|
|
161
177
|
function conversationTaskEntries(tasks) {
|
|
162
178
|
return tasks
|
|
163
179
|
.map((task, index) => ({ task, ordinal: index + 1 }))
|
|
164
|
-
.sort((left, right) =>
|
|
165
|
-
|| left.ordinal - right.ordinal));
|
|
180
|
+
.sort((left, right) => right.ordinal - left.ordinal);
|
|
166
181
|
}
|
|
167
182
|
function renderTaskRow(task, ordinal, lang) {
|
|
168
183
|
const zh = lang === 'zh';
|
|
169
184
|
const href = taskTrajectoryHref(task, lang) ?? '#';
|
|
170
185
|
const status = statusLabel(task.status, lang);
|
|
171
|
-
return `<article class="task-row${task.status === 'open' ? ' is-running' : ''}" data-task-status="${e(task.status)}">
|
|
186
|
+
return `<article class="task-row${task.status === 'open' ? ' is-running' : ''}" data-task-status="${e(task.status)}" data-task-ordinal="${ordinal}">
|
|
172
187
|
<div class="task-index">${String(ordinal).padStart(2, '0')}</div>
|
|
173
188
|
<div class="task-main"><div class="task-title">${renderSafeInlineMarkdown(task.title, { links: 'text' })}</div><div class="task-context">${task.eventCount} ${zh ? '条原始日志' : 'raw records'}</div></div>
|
|
174
189
|
<div class="task-time"><span>${e(formatTime(task.startTimestamp))}</span><small>${e(formatDuration(task.durationMs, lang))}</small></div>
|
|
@@ -176,6 +191,93 @@ function renderTaskRow(task, ordinal, lang) {
|
|
|
176
191
|
<a class="trajectory-link" href="${e(href)}">${zh ? '查看任务轨迹' : 'View trajectory'} →</a>
|
|
177
192
|
</article>`;
|
|
178
193
|
}
|
|
194
|
+
function conversationDetailScript(lang) {
|
|
195
|
+
const newestLabel = lang === 'zh' ? '最新优先' : 'Newest first';
|
|
196
|
+
const oldestLabel = lang === 'zh' ? '最早优先' : 'Oldest first';
|
|
197
|
+
const switchToNewest = lang === 'zh' ? '切换为最新优先' : 'Switch to newest first';
|
|
198
|
+
const switchToOldest = lang === 'zh' ? '切换为最早优先' : 'Switch to oldest first';
|
|
199
|
+
return `
|
|
200
|
+
(() => {
|
|
201
|
+
const root = document.querySelector('.conversation-detail-page');
|
|
202
|
+
const taskList = root?.querySelector('.task-list');
|
|
203
|
+
const toggle = root?.querySelector('[data-task-order-toggle]');
|
|
204
|
+
const label = toggle?.querySelector('[data-task-order-label]');
|
|
205
|
+
const rows = [...(taskList?.querySelectorAll('.task-row') || [])];
|
|
206
|
+
const preferenceKey = 'omk.conversationTaskOrder';
|
|
207
|
+
let activityTimer;
|
|
208
|
+
let activityRequest;
|
|
209
|
+
let activityStopped = false;
|
|
210
|
+
|
|
211
|
+
const applyOrder = (order) => {
|
|
212
|
+
if (!taskList || !toggle) return;
|
|
213
|
+
const sorted = [...rows].sort((left, right) => {
|
|
214
|
+
const leftOrdinal = Number(left.dataset.taskOrdinal || 0);
|
|
215
|
+
const rightOrdinal = Number(right.dataset.taskOrdinal || 0);
|
|
216
|
+
return order === 'asc' ? leftOrdinal - rightOrdinal : rightOrdinal - leftOrdinal;
|
|
217
|
+
});
|
|
218
|
+
for (const row of sorted) taskList.append(row);
|
|
219
|
+
toggle.dataset.taskOrder = order;
|
|
220
|
+
if (label) label.textContent = order === 'asc' ? '${oldestLabel}' : '${newestLabel}';
|
|
221
|
+
const actionLabel = order === 'asc' ? '${switchToNewest}' : '${switchToOldest}';
|
|
222
|
+
toggle.setAttribute('aria-label', actionLabel);
|
|
223
|
+
toggle.setAttribute('title', actionLabel);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
let initialOrder = 'desc';
|
|
227
|
+
try {
|
|
228
|
+
const storedOrder = sessionStorage.getItem(preferenceKey);
|
|
229
|
+
if (storedOrder === 'asc' || storedOrder === 'desc') initialOrder = storedOrder;
|
|
230
|
+
} catch { /* Storage can be unavailable in hardened browser contexts. */ }
|
|
231
|
+
applyOrder(initialOrder);
|
|
232
|
+
toggle?.addEventListener('click', () => {
|
|
233
|
+
const order = toggle.dataset.taskOrder === 'asc' ? 'desc' : 'asc';
|
|
234
|
+
applyOrder(order);
|
|
235
|
+
try { sessionStorage.setItem(preferenceKey, order); } catch { /* Ignore unavailable storage. */ }
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const stopActivityPolling = () => {
|
|
239
|
+
activityStopped = true;
|
|
240
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
241
|
+
activityRequest?.abort();
|
|
242
|
+
};
|
|
243
|
+
const scheduleActivityPoll = (delay = 5000) => {
|
|
244
|
+
if (activityStopped) return;
|
|
245
|
+
if (activityTimer) clearTimeout(activityTimer);
|
|
246
|
+
activityTimer = setTimeout(pollActivity, delay);
|
|
247
|
+
};
|
|
248
|
+
const pollActivity = async () => {
|
|
249
|
+
if (activityStopped) return;
|
|
250
|
+
if (document.hidden) {
|
|
251
|
+
scheduleActivityPoll();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const endpoint = root?.dataset.activityEndpoint;
|
|
255
|
+
if (!endpoint) return;
|
|
256
|
+
activityRequest?.abort();
|
|
257
|
+
activityRequest = new AbortController();
|
|
258
|
+
try {
|
|
259
|
+
const response = await fetch(endpoint, {
|
|
260
|
+
cache: 'no-store',
|
|
261
|
+
signal: activityRequest.signal,
|
|
262
|
+
});
|
|
263
|
+
if (!response.ok) throw new Error('conversation activity unavailable');
|
|
264
|
+
const snapshot = await response.json();
|
|
265
|
+
if (snapshot.revision && snapshot.revision !== root?.dataset.activityRevision) {
|
|
266
|
+
window.location.reload();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
} catch (cause) {
|
|
270
|
+
if (cause?.name === 'AbortError') return;
|
|
271
|
+
}
|
|
272
|
+
scheduleActivityPoll();
|
|
273
|
+
};
|
|
274
|
+
document.addEventListener('visibilitychange', () => {
|
|
275
|
+
if (!document.hidden) scheduleActivityPoll(0);
|
|
276
|
+
});
|
|
277
|
+
window.addEventListener('pagehide', stopActivityPolling, { once: true });
|
|
278
|
+
scheduleActivityPoll();
|
|
279
|
+
})();`;
|
|
280
|
+
}
|
|
179
281
|
function taskTrajectoryHref(task, lang) {
|
|
180
282
|
if (task.trajectoryHref)
|
|
181
283
|
return withLang(task.trajectoryHref, lang);
|
|
@@ -391,7 +493,7 @@ const CSS = `
|
|
|
391
493
|
.conversation-activity{display:flex;flex-direction:column;min-width:0}.conversation-activity strong{font-size:12px;font-weight:650;white-space:nowrap}.conversation-activity span{color:var(--text-muted);font:500 11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.running-label{display:flex;align-items:center;gap:6px;color:var(--accent)}.running-label i,.live-task-link i{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 3px rgba(79,70,229,.1)}
|
|
392
494
|
.conversation-main{min-width:0}.conversation-title{display:block;font-size:14px;font-weight:650;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0}.conversation-main:has(.conversation-context) .conversation-title{margin-bottom:4px}.conversation-title code,.task-title code{padding:1px 3px;border-radius:3px;background:var(--bg-elevated);font:600 .92em ui-monospace,SFMono-Regular,Menlo,monospace}.inline-markdown-link{color:var(--accent);text-decoration:underline;text-decoration-color:rgba(79,70,229,.28);text-underline-offset:3px}.inline-markdown-link:hover{text-decoration-color:currentColor}.conversation-meta,.conversation-context,.detail-meta{display:flex;align-items:center;gap:8px;color:var(--text-muted);font-size:11px;min-width:0}.conversation-meta span+span:before,.conversation-context span+span:before,.detail-meta span+span:before{content:'·';margin-right:8px}.source-mark{color:var(--accent);font-weight:700}.conversation-workspace{min-width:0;color:var(--text-secondary);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
393
495
|
.conversation-counts{display:flex;flex-direction:column;justify-content:center;align-items:flex-end;gap:2px;color:var(--text-secondary);font-size:11px;white-space:nowrap}.conversation-counts span{display:flex;gap:4px;align-items:baseline}.conversation-counts b{font-size:14px;color:var(--text-primary);font-variant-numeric:tabular-nums}.live-task-link{z-index:3!important;display:flex;align-items:center;gap:6px;color:var(--accent);font-weight:650;text-decoration:none;pointer-events:auto!important}.live-task-link:hover{text-decoration:underline}.failure-count{color:var(--red)!important}.index-pending{color:var(--text-muted)}.row-arrow{display:flex;align-items:center;justify-content:center;color:var(--text-faint);transition:color .14s,transform .14s}.conversation-row:hover .row-arrow{color:var(--accent);transform:translateX(2px)}.empty-state{display:flex;flex-direction:column;gap:4px;padding:36px;border:1px solid var(--border);border-radius:8px;background:var(--bg-surface);color:var(--text-secondary)}.empty-state strong{color:var(--text-primary)}
|
|
394
|
-
.conversation-detail-head{align-items:flex-start}.conversation-detail-head h1{max-width:920px;font-size:24px}.detail-meta{margin-top:10px}.task-list-head,.task-row{display:grid;grid-template-columns:minmax(0,1fr) 140px 160px 150px;gap:18px;align-items:center}.task-list-head{padding:
|
|
496
|
+
.conversation-detail-head{align-items:flex-start}.conversation-detail-head h1{max-width:920px;font-size:24px}.detail-meta{margin-top:10px}.task-list-head,.task-row{display:grid;grid-template-columns:minmax(0,1fr) 140px 160px 150px;gap:18px;align-items:center}.task-list-head{padding:8px 22px 8px 72px;color:var(--text-muted);font-size:12px;background:var(--bg-elevated);border-bottom:1px solid var(--border)}.task-order-toggle{justify-self:end;display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 8px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--text-secondary);font:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.task-order-toggle:hover{border-color:var(--border);background:var(--bg-surface);color:var(--text-primary)}.task-order-toggle:focus-visible{outline:2px solid rgba(79,70,229,.42);outline-offset:1px}.task-row{position:relative;padding:17px 22px 17px 72px;border-bottom:1px solid var(--border)}.task-row.is-running{background:rgba(79,70,229,.035);box-shadow:inset 3px 0 0 var(--accent)}.task-row:last-child{border-bottom:0}.task-index{position:absolute;left:22px;top:19px;font:700 12px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text-muted)}.task-row.is-running .task-index{color:var(--accent)}.task-main{min-width:0}.task-title{display:block;font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:6px}.task-context{color:var(--text-muted);font-size:12px}.task-time,.task-execution{display:flex;flex-direction:column;gap:3px}.task-time small,.task-execution small{color:var(--text-muted)}.task-status{font-weight:650}.status-aborted,.status-interrupted{color:var(--red)}.status-open{color:var(--accent)}.status-unknown{color:var(--yellow)}.status-completed{color:var(--green)}.trajectory-link{text-align:right;font-size:13px;white-space:nowrap}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
395
497
|
|
|
396
498
|
html:has(.conversation-index-app),body:has(.conversation-index-app){height:100%;overflow:hidden;scrollbar-gutter:auto}
|
|
397
499
|
body:has(.conversation-index-app) .app-bar{display:none}
|
package/dist/renderer/icons.js
CHANGED
|
@@ -16,6 +16,7 @@ const PATHS = {
|
|
|
16
16
|
chip: '<rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 12h8"/>',
|
|
17
17
|
'chevron-right': '<path d="M9 6l6 6-6 6"/>',
|
|
18
18
|
'chevron-left': '<path d="M15 18l-6-6 6-6"/>',
|
|
19
|
+
'arrow-up-down': '<path d="M7 20V4M3 8l4-4 4 4M17 4v16M13 16l4 4 4-4"/>',
|
|
19
20
|
'zoom-in': '<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.4-4.4M11 8v6M8 11h6"/>',
|
|
20
21
|
'zoom-out': '<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.4-4.4M8 11h6"/>',
|
|
21
22
|
'maximize-2': '<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/>',
|
|
@@ -1335,6 +1335,8 @@ function projectReplay(model, lang, options) {
|
|
|
1335
1335
|
? 'failure'
|
|
1336
1336
|
: conversation ? 'message' : 'result';
|
|
1337
1337
|
const opaqueModelActivity = modelActivity && event?.contentVisibility === 'opaque';
|
|
1338
|
+
const messageAttachments = event?.attachments ?? [];
|
|
1339
|
+
const messageAttachmentLabel = attachmentSummary(messageAttachments, lang);
|
|
1338
1340
|
const text = eventPreview(event, opaqueModelActivity
|
|
1339
1341
|
? (zh ? '不可见' : 'Unavailable')
|
|
1340
1342
|
: (zh ? '没有可展示的事件内容。' : 'No event content available.'));
|
|
@@ -1351,7 +1353,7 @@ function projectReplay(model, lang, options) {
|
|
|
1351
1353
|
: STEP_LABELS[step.stepKind][lang],
|
|
1352
1354
|
model: eventModel,
|
|
1353
1355
|
title: text,
|
|
1354
|
-
detail:
|
|
1356
|
+
detail: messageAttachmentLabel,
|
|
1355
1357
|
facetIds: operationFacetIds,
|
|
1356
1358
|
rawId: event ? `${event.kind} · ${event.id}` : step.id, primary: true,
|
|
1357
1359
|
compact: opaqueModelActivity,
|
|
@@ -1393,6 +1395,11 @@ function projectReplay(model, lang, options) {
|
|
|
1393
1395
|
{ label: zh ? '角色' : 'Role', value: roleLabel(event, lang), detail: event?.kind ?? step.stepKind },
|
|
1394
1396
|
{ label: zh ? '时间' : 'Time', value: formatRelativeTimestamp(step.timestamp, startTimestamp), detail: formatDisplayTimestamp(step.timestamp, lang) },
|
|
1395
1397
|
...(eventModel ? [{ label: zh ? '模型' : 'Model', value: eventModel, detail: zh ? '由 trace 明确记录的事件模型' : 'Event model explicitly recorded by the trace' }] : []),
|
|
1398
|
+
...(messageAttachments.length > 0 ? [{
|
|
1399
|
+
label: zh ? '附件' : 'Attachments',
|
|
1400
|
+
value: messageAttachmentLabel,
|
|
1401
|
+
detail: messageAttachments.map((attachment) => attachment.name).join('\n'),
|
|
1402
|
+
}] : []),
|
|
1396
1403
|
{
|
|
1397
1404
|
label: zh ? '内容' : 'Content',
|
|
1398
1405
|
value: text,
|
|
@@ -1488,6 +1495,14 @@ function replayEventModel(step, event) {
|
|
|
1488
1495
|
? event?.model?.trim() || undefined
|
|
1489
1496
|
: undefined;
|
|
1490
1497
|
}
|
|
1498
|
+
function attachmentSummary(attachments, lang) {
|
|
1499
|
+
const imageCount = attachments.filter((attachment) => attachment.attachmentKind === 'image').length;
|
|
1500
|
+
const fileCount = attachments.length - imageCount;
|
|
1501
|
+
const parts = lang === 'zh'
|
|
1502
|
+
? [imageCount > 0 ? `图片 ${imageCount} 张` : '', fileCount > 0 ? `文件 ${fileCount} 个` : '']
|
|
1503
|
+
: [imageCount > 0 ? `${imageCount} image${imageCount === 1 ? '' : 's'}` : '', fileCount > 0 ? `${fileCount} file${fileCount === 1 ? '' : 's'}` : ''];
|
|
1504
|
+
return parts.filter(Boolean).join(' · ');
|
|
1505
|
+
}
|
|
1491
1506
|
function replayCardWidth(step, lang, pendingToolResults) {
|
|
1492
1507
|
const event = step.events[0];
|
|
1493
1508
|
if (step.stepKind === 'model_activity' && event?.contentVisibility === 'opaque')
|
|
@@ -9,7 +9,7 @@ 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
11
|
import { renderKnowledgeDebuggerPage } from '../renderer/knowledge-debugger-renderer.js';
|
|
12
|
-
import { buildConversationActivitySnapshot, renderConversationDetailPage, renderConversationIndexPage, } from '../renderer/conversation-renderer.js';
|
|
12
|
+
import { buildConversationActivitySnapshot, buildConversationDetailActivitySnapshot, renderConversationDetailPage, renderConversationIndexPage, } from '../renderer/conversation-renderer.js';
|
|
13
13
|
import { DEFAULT_LANG, e, t, layout } from '../renderer/layout.js';
|
|
14
14
|
import { loadAllManagedRecords, resolveManagedDir, managedDir as projectManagedDir, listManagedRows, buildVersionScores } from '../managed/index.js';
|
|
15
15
|
import { renderManagedList, renderManagedHistory } from '../renderer/managed-history-renderer.js';
|
|
@@ -901,6 +901,26 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
|
|
|
901
901
|
res.end(JSON.stringify(snapshot));
|
|
902
902
|
return;
|
|
903
903
|
}
|
|
904
|
+
const conversationActivityMatch = path.match(/^\/api\/conversations\/([^/]+)\/activity$/);
|
|
905
|
+
if (conversationActivityMatch) {
|
|
906
|
+
let threadId = '';
|
|
907
|
+
try {
|
|
908
|
+
threadId = decodeURIComponent(conversationActivityMatch[1]);
|
|
909
|
+
}
|
|
910
|
+
catch { /* invalid path */ }
|
|
911
|
+
const conversation = threadId ? await resolvedConversationCatalog.getConversation(threadId) : undefined;
|
|
912
|
+
if (!conversation) {
|
|
913
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
914
|
+
res.end(JSON.stringify({ error: 'conversation_not_found' }));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
res.writeHead(200, {
|
|
918
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
919
|
+
'Cache-Control': 'no-store',
|
|
920
|
+
});
|
|
921
|
+
res.end(JSON.stringify(buildConversationDetailActivitySnapshot(conversation)));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
904
924
|
if (path === '/conversations') {
|
|
905
925
|
const html = renderConversationIndexPage(await resolvedConversationCatalog.listConversations(), lang);
|
|
906
926
|
res.writeHead(200, {
|
|
@@ -1069,7 +1089,10 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
|
|
|
1069
1089
|
res.end(lang === 'en' ? 'conversation not found' : '对话不存在');
|
|
1070
1090
|
return;
|
|
1071
1091
|
}
|
|
1072
|
-
res.writeHead(200, {
|
|
1092
|
+
res.writeHead(200, {
|
|
1093
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
1094
|
+
'Cache-Control': 'no-store',
|
|
1095
|
+
});
|
|
1073
1096
|
res.end(renderConversationDetailPage(conversation, lang));
|
|
1074
1097
|
return;
|
|
1075
1098
|
}
|
|
@@ -371,6 +371,10 @@ export interface ExperienceTimelineEvent extends ExperienceEvidenceRef {
|
|
|
371
371
|
toolStatus?: ToolCallStatus;
|
|
372
372
|
isError?: boolean;
|
|
373
373
|
fullText?: string;
|
|
374
|
+
attachments?: Array<{
|
|
375
|
+
attachmentKind: 'image' | 'file';
|
|
376
|
+
name: string;
|
|
377
|
+
}>;
|
|
374
378
|
}
|
|
375
379
|
export type DebugKnowledgeKind = 'project_instruction' | 'skill' | 'runtime_evidence';
|
|
376
380
|
export type DebugKnowledgeAccessKind = 'injected' | 'read' | 'returned';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-knowledge",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.3-oidc.0",
|
|
4
4
|
"packageManager": "yarn@4.16.0",
|
|
5
5
|
"description": "OMK — Observe. Measure. Know. Evidence-backed knowledge changes for AI applications.",
|
|
6
6
|
"type": "module",
|
|
@@ -99,11 +99,11 @@
|
|
|
99
99
|
"license": "MIT",
|
|
100
100
|
"dependencies": {
|
|
101
101
|
"@anthropic-ai/claude-agent-sdk": "^0.3.143",
|
|
102
|
-
"@anthropic-ai/sdk": "^0.
|
|
102
|
+
"@anthropic-ai/sdk": "^0.117.1",
|
|
103
103
|
"@inquirer/prompts": "^8.4.3",
|
|
104
104
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
105
105
|
"@oclif/core": "^4",
|
|
106
|
-
"@openai/codex-sdk": "0.
|
|
106
|
+
"@openai/codex-sdk": "0.147.0",
|
|
107
107
|
"ajv": "^8.18.0",
|
|
108
108
|
"chart.js": "^4.5.1",
|
|
109
109
|
"es-module-lexer": "^2.0.0",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@types/node": "^25.5.0",
|
|
120
120
|
"eslint": "^10.1.0",
|
|
121
121
|
"husky": "^9.1.7",
|
|
122
|
-
"lint-staged": "17.
|
|
122
|
+
"lint-staged": "17.3.0",
|
|
123
123
|
"npm-run-all2": "^9.0.3",
|
|
124
124
|
"typescript": "^6.0.2",
|
|
125
125
|
"typescript-eslint": "^8.58.0",
|