oh-my-knowledge 0.51.1 → 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 (75) hide show
  1. package/README.md +27 -6
  2. package/README.zh.md +27 -6
  3. package/dist/assets/agent-skills/omk/SKILL.md +10 -8
  4. package/dist/assets/agent-skills/omk/references/commands.md +2 -2
  5. package/dist/authoring/generator.d.ts +15 -5
  6. package/dist/authoring/generator.js +181 -18
  7. package/dist/authoring/sample-fixer.d.ts +2 -0
  8. package/dist/authoring/sample-fixer.js +19 -5
  9. package/dist/cli/commands/sample.js +11 -4
  10. package/dist/eval-core/evaluation-execution.js +11 -2
  11. package/dist/eval-core/fact-checker.d.ts +15 -2
  12. package/dist/eval-core/fact-checker.js +75 -20
  13. package/dist/eval-core/mock-hook.cjs +40 -1
  14. package/dist/eval-core/mocks-runtime.js +39 -2
  15. package/dist/eval-core/task-planner.d.ts +2 -2
  16. package/dist/eval-core/task-planner.js +7 -7
  17. package/dist/eval-workflows/evaluation-pipeline.js +2 -0
  18. package/dist/eval-workflows/run-evaluation.js +2 -1
  19. package/dist/executors/capabilities.d.ts +15 -0
  20. package/dist/executors/capabilities.js +64 -0
  21. package/dist/executors/index.d.ts +1 -0
  22. package/dist/executors/index.js +4 -1
  23. package/dist/observability/codex-conversation-index.d.ts +88 -0
  24. package/dist/observability/codex-conversation-index.js +566 -0
  25. package/dist/observability/codex-protocol.d.ts +12 -0
  26. package/dist/observability/codex-protocol.js +78 -0
  27. package/dist/observability/codex-tool-status.d.ts +16 -0
  28. package/dist/observability/codex-tool-status.js +114 -0
  29. package/dist/observability/codex-trace-adapter.js +342 -119
  30. package/dist/observability/conversation-catalog.d.ts +38 -0
  31. package/dist/observability/conversation-catalog.js +573 -0
  32. package/dist/observability/conversation-index-process.d.ts +1 -0
  33. package/dist/observability/conversation-index-process.js +31 -0
  34. package/dist/observability/conversation-view-model.d.ts +5 -0
  35. package/dist/observability/conversation-view-model.js +100 -0
  36. package/dist/observability/experience.d.ts +3 -1
  37. package/dist/observability/experience.js +248 -7
  38. package/dist/observability/inbox.js +40 -2
  39. package/dist/observability/knowledge-debugger.d.ts +4 -0
  40. package/dist/observability/knowledge-debugger.js +364 -0
  41. package/dist/observability/polling-subscription-hub.d.ts +27 -0
  42. package/dist/observability/polling-subscription-hub.js +149 -0
  43. package/dist/observability/source-record-archive.d.ts +13 -0
  44. package/dist/observability/source-record-archive.js +324 -0
  45. package/dist/observability/task-window.d.ts +8 -0
  46. package/dist/observability/task-window.js +36 -0
  47. package/dist/observability/trace-ir.d.ts +66 -1
  48. package/dist/observability/trace-source.d.ts +1 -0
  49. package/dist/observability/trace-source.js +12 -5
  50. package/dist/observability/turn-index.d.ts +3 -0
  51. package/dist/observability/turn-index.js +251 -0
  52. package/dist/renderer/conversation-renderer.d.ts +9 -0
  53. package/dist/renderer/conversation-renderer.js +410 -0
  54. package/dist/renderer/icons.js +1 -0
  55. package/dist/renderer/inline-markdown.d.ts +6 -0
  56. package/dist/renderer/inline-markdown.js +158 -0
  57. package/dist/renderer/knowledge-debugger-renderer.d.ts +9 -0
  58. package/dist/renderer/knowledge-debugger-renderer.js +2280 -0
  59. package/dist/renderer/observation-inbox-renderer.js +9 -0
  60. package/dist/renderer/skill-list-renderer.js +2 -1
  61. package/dist/renderer/trajectory-live.d.ts +42 -0
  62. package/dist/renderer/trajectory-live.js +258 -0
  63. package/dist/renderer/trajectory-routing.d.ts +60 -0
  64. package/dist/renderer/trajectory-routing.js +351 -0
  65. package/dist/server/report-server.d.ts +4 -1
  66. package/dist/server/report-server.js +274 -6
  67. package/dist/server/skill-insights.js +1 -1
  68. package/dist/shared/sample-contract.d.ts +1 -0
  69. package/dist/shared/sample-contract.js +35 -0
  70. package/dist/shared/tool-identity.d.ts +8 -0
  71. package/dist/shared/tool-identity.js +13 -0
  72. package/dist/types/eval.d.ts +7 -6
  73. package/dist/types/executor.d.ts +3 -2
  74. package/dist/types/observability.d.ts +225 -1
  75. package/package.json +10 -7
@@ -0,0 +1,88 @@
1
+ import type { ExperienceTurnStatus } from '../types/index.js';
2
+ import type { TraceToolStatus } from './trace-ir.js';
3
+ export interface CodexIndexedTask {
4
+ turnId: string;
5
+ title: string;
6
+ startTimestamp?: string;
7
+ endTimestamp?: string;
8
+ status: ExperienceTurnStatus;
9
+ sourceRecordCount: number;
10
+ toolCallCount: number;
11
+ toolFailureCount: number;
12
+ startOffset: number;
13
+ endOffset: number;
14
+ startLine: number;
15
+ endLine: number;
16
+ }
17
+ export interface CodexRolloutIndex {
18
+ schemaVersion: 11;
19
+ sourcePath: string;
20
+ /** File size observed when the index was produced. */
21
+ sourceSize: number;
22
+ sourceMtimeMs: number;
23
+ /** Byte boundary after the last complete JSONL record. */
24
+ indexedSize: number;
25
+ /** Physical line number at indexedSize, including blank lines. */
26
+ indexedLineCount: number;
27
+ /** Whether indexedSize is immediately after a newline delimiter. */
28
+ indexedEndsWithNewline: boolean;
29
+ sourceThreadId: string;
30
+ sessionMeta?: unknown;
31
+ sessionMetaLine?: number;
32
+ tasks: CodexIndexedTask[];
33
+ /** Incremental matcher state for the final unclosed task only. */
34
+ activeToolOutcomeState?: CodexActiveToolOutcomeState;
35
+ sourceRecordCount: number;
36
+ malformedRecordCount: number;
37
+ }
38
+ export interface CodexActiveToolOutcomeState {
39
+ callOccurrences: Array<[string, number]>;
40
+ resultOccurrences: Array<[string, number]>;
41
+ runtimeOccurrences: Array<[string, number]>;
42
+ outcomes: Array<[string, IndexedToolOutcome]>;
43
+ }
44
+ interface IndexedToolOutcome {
45
+ outputStatus?: TraceToolStatus;
46
+ runtimeStatus?: TraceToolStatus;
47
+ }
48
+ export interface CodexJsonlLine {
49
+ text: string;
50
+ line: number;
51
+ startOffset: number;
52
+ endOffset: number;
53
+ }
54
+ /**
55
+ * Build a compact byte-range index without retaining the rollout. Only records
56
+ * needed for turn boundaries and list metrics are parsed.
57
+ */
58
+ export declare function buildCodexRolloutIndex(sourcePath: string, sourceThreadId: string): CodexRolloutIndex;
59
+ /**
60
+ * Extend an append-only rollout index from its last byte boundary. If the
61
+ * source was replaced or truncated, rebuild so callers never observe a mixed
62
+ * index from two different files.
63
+ */
64
+ export declare function extendCodexRolloutIndex(sourcePath: string, sourceThreadId: string, previous: CodexRolloutIndex): CodexRolloutIndex;
65
+ /**
66
+ * Catch an append-only rollout up to a snapshot that is still current after
67
+ * indexing. Returning a stale prefix here would violate current-read callers.
68
+ */
69
+ export declare function synchronizeCurrentCodexRolloutIndex(sourcePath: string, sourceThreadId: string, initial?: CodexRolloutIndex): CodexRolloutIndex;
70
+ /**
71
+ * A single Codex rollout is sequential: once a newer task starts, an older
72
+ * task without a terminal record can no longer be live. Normalize historical
73
+ * caches in memory so an omitted task_complete never becomes a permanent
74
+ * "running" conversation.
75
+ */
76
+ export declare function normalizeCodexRolloutIndex(index: CodexRolloutIndex): CodexRolloutIndex;
77
+ export declare function isCurrentCodexRolloutIndex(value: unknown, sourcePath: string): value is CodexRolloutIndex;
78
+ /**
79
+ * Codex rollouts are append-only while a conversation is active. A prefix
80
+ * index remains internally consistent even after newer records are appended.
81
+ */
82
+ export declare function isReusableCodexRolloutIndex(value: unknown, sourcePath: string): value is CodexRolloutIndex;
83
+ export declare function readCodexTaskRecords(index: CodexRolloutIndex, task: CodexIndexedTask): {
84
+ records: Array<unknown | undefined>;
85
+ lines: CodexJsonlLine[];
86
+ malformedRecordCount: number;
87
+ };
88
+ export {};
@@ -0,0 +1,566 @@
1
+ import { closeSync, openSync, readSync, statSync } from 'node:fs';
2
+ import { codexUserDisplayText, codexUserMessageOrigin } from './codex-protocol.js';
3
+ import { codexRuntimeToolOutcomeFromPayload, codexToolOutputOutcome, } from './codex-tool-status.js';
4
+ const READ_CHUNK_BYTES = 256 * 1024;
5
+ const MAX_RECORD_BYTES = 32 * 1024 * 1024;
6
+ const INDEX_SCHEMA_VERSION = 11;
7
+ const MAX_CURRENT_INDEX_ATTEMPTS = 4;
8
+ /**
9
+ * Build a compact byte-range index without retaining the rollout. Only records
10
+ * needed for turn boundaries and list metrics are parsed.
11
+ */
12
+ export function buildCodexRolloutIndex(sourcePath, sourceThreadId) {
13
+ const sourceStat = statSync(sourcePath);
14
+ const state = scanCodexRolloutIndex(sourcePath, sourceThreadId, {
15
+ tasks: [],
16
+ sourceRecordCount: 0,
17
+ malformedRecordCount: 0,
18
+ indexedSize: 0,
19
+ indexedLineCount: 0,
20
+ indexedEndsWithNewline: true,
21
+ }, 0, sourceStat.size, 0);
22
+ return indexFromState(sourcePath, sourceThreadId, sourceStat, state);
23
+ }
24
+ /**
25
+ * Extend an append-only rollout index from its last byte boundary. If the
26
+ * source was replaced or truncated, rebuild so callers never observe a mixed
27
+ * index from two different files.
28
+ */
29
+ export function extendCodexRolloutIndex(sourcePath, sourceThreadId, previous) {
30
+ const sourceStat = statSync(sourcePath);
31
+ if (!isReusableCodexRolloutIndex(previous, sourcePath)
32
+ || previous.sourceThreadId !== sourceThreadId
33
+ || sourceStat.size < previous.sourceSize) {
34
+ return buildCodexRolloutIndex(sourcePath, sourceThreadId);
35
+ }
36
+ const normalizedPrevious = normalizeCodexRolloutIndex(previous);
37
+ if (sourceStat.size === previous.sourceSize && sourceStat.mtimeMs === previous.sourceMtimeMs) {
38
+ return normalizedPrevious;
39
+ }
40
+ const tasks = normalizedPrevious.tasks.map((task) => ({ ...task }));
41
+ const resume = resumeAppendCursor(sourcePath, normalizedPrevious, sourceStat.size);
42
+ if (!resume)
43
+ return buildCodexRolloutIndex(sourcePath, sourceThreadId);
44
+ completeTailTaskDelimiter(tasks, normalizedPrevious.indexedSize, resume.indexedSize);
45
+ const lastTask = tasks.at(-1);
46
+ const active = lastTask?.status === 'open'
47
+ ? mutableTask(tasks.pop(), normalizedPrevious.activeToolOutcomeState)
48
+ : undefined;
49
+ const state = {
50
+ tasks,
51
+ active,
52
+ sessionMeta: normalizedPrevious.sessionMeta,
53
+ sessionMetaLine: normalizedPrevious.sessionMetaLine,
54
+ sourceRecordCount: normalizedPrevious.sourceRecordCount,
55
+ malformedRecordCount: normalizedPrevious.malformedRecordCount,
56
+ indexedSize: resume.indexedSize,
57
+ indexedLineCount: normalizedPrevious.indexedLineCount,
58
+ indexedEndsWithNewline: resume.indexedEndsWithNewline,
59
+ };
60
+ const extended = resume.indexedSize < sourceStat.size
61
+ ? scanCodexRolloutIndex(sourcePath, sourceThreadId, state, resume.indexedSize, sourceStat.size, normalizedPrevious.indexedLineCount)
62
+ : state;
63
+ return indexFromState(sourcePath, sourceThreadId, sourceStat, extended);
64
+ }
65
+ /**
66
+ * Catch an append-only rollout up to a snapshot that is still current after
67
+ * indexing. Returning a stale prefix here would violate current-read callers.
68
+ */
69
+ export function synchronizeCurrentCodexRolloutIndex(sourcePath, sourceThreadId, initial) {
70
+ let index = initial
71
+ && initial.sourceThreadId === sourceThreadId
72
+ && isReusableCodexRolloutIndex(initial, sourcePath)
73
+ ? normalizeCodexRolloutIndex(initial)
74
+ : buildCodexRolloutIndex(sourcePath, sourceThreadId);
75
+ for (let attempt = 0; attempt < MAX_CURRENT_INDEX_ATTEMPTS; attempt += 1) {
76
+ if (isCurrentCodexRolloutIndex(index, sourcePath))
77
+ return index;
78
+ index = extendCodexRolloutIndex(sourcePath, sourceThreadId, index);
79
+ }
80
+ if (isCurrentCodexRolloutIndex(index, sourcePath))
81
+ return index;
82
+ throw new Error('Codex 对话日志持续写入,暂时无法形成当前索引快照');
83
+ }
84
+ function resumeAppendCursor(sourcePath, previous, sourceSize) {
85
+ if (previous.indexedEndsWithNewline) {
86
+ return {
87
+ indexedSize: previous.indexedSize,
88
+ indexedEndsWithNewline: true,
89
+ };
90
+ }
91
+ const fd = openSync(sourcePath, 'r');
92
+ const buffer = Buffer.allocUnsafe(Math.min(4_096, Math.max(1, sourceSize - previous.indexedSize)));
93
+ let offset = previous.indexedSize;
94
+ try {
95
+ while (offset < sourceSize) {
96
+ const bytesRead = readSync(fd, buffer, 0, Math.min(buffer.length, sourceSize - offset), offset);
97
+ if (bytesRead === 0)
98
+ break;
99
+ for (let index = 0; index < bytesRead; index += 1) {
100
+ const byte = buffer[index];
101
+ if (byte === 0x0a) {
102
+ return {
103
+ indexedSize: offset + index + 1,
104
+ indexedEndsWithNewline: true,
105
+ };
106
+ }
107
+ if (byte !== 0x09 && byte !== 0x0d && byte !== 0x20)
108
+ return undefined;
109
+ }
110
+ offset += bytesRead;
111
+ }
112
+ }
113
+ finally {
114
+ closeSync(fd);
115
+ }
116
+ return {
117
+ indexedSize: sourceSize,
118
+ indexedEndsWithNewline: false,
119
+ };
120
+ }
121
+ function completeTailTaskDelimiter(tasks, previousIndexedSize, indexedSize) {
122
+ if (indexedSize <= previousIndexedSize)
123
+ return;
124
+ const tailTask = tasks.at(-1);
125
+ if (tailTask?.endOffset === previousIndexedSize)
126
+ tailTask.endOffset = indexedSize;
127
+ }
128
+ /**
129
+ * A single Codex rollout is sequential: once a newer task starts, an older
130
+ * task without a terminal record can no longer be live. Normalize historical
131
+ * caches in memory so an omitted task_complete never becomes a permanent
132
+ * "running" conversation.
133
+ */
134
+ export function normalizeCodexRolloutIndex(index) {
135
+ let changed = false;
136
+ const tasks = index.tasks.map((task, taskIndex) => {
137
+ if (task.status !== 'open' || taskIndex === index.tasks.length - 1)
138
+ return task;
139
+ changed = true;
140
+ return {
141
+ ...task,
142
+ status: 'unknown',
143
+ endTimestamp: task.endTimestamp ?? index.tasks[taskIndex + 1]?.startTimestamp,
144
+ };
145
+ });
146
+ return changed ? { ...index, tasks } : index;
147
+ }
148
+ function scanCodexRolloutIndex(sourcePath, sourceThreadId, state, startOffset, endOffset, startLine) {
149
+ const scanned = forEachJsonlLine(sourcePath, (record) => {
150
+ state.sourceRecordCount += 1;
151
+ const relevant = isIndexRelevantLine(record.text);
152
+ if (!relevant) {
153
+ includeRecord(state.active, record);
154
+ return;
155
+ }
156
+ let parsed;
157
+ try {
158
+ parsed = JSON.parse(record.text);
159
+ }
160
+ catch {
161
+ state.malformedRecordCount += 1;
162
+ includeRecord(state.active, record);
163
+ return;
164
+ }
165
+ const raw = objectValue(parsed);
166
+ if (!raw)
167
+ return;
168
+ const recordType = stringValue(raw.type);
169
+ const payload = objectValue(raw.payload) ?? {};
170
+ const payloadType = stringValue(payload.type);
171
+ const timestamp = stringValue(raw.timestamp);
172
+ if (recordType === 'session_meta' && state.sessionMeta === undefined) {
173
+ state.sessionMeta = parsed;
174
+ state.sessionMetaLine = record.line;
175
+ return;
176
+ }
177
+ if (recordType === 'event_msg' && payloadType === 'task_started') {
178
+ if (state.active) {
179
+ state.tasks.push(finalizeSupersededTask(state.active, record.startOffset, record.line - 1, timestamp));
180
+ }
181
+ const nativeTurnId = stringValue(payload.turn_id);
182
+ state.active = newMutableTask(nativeTurnId ?? `turn:${sourceThreadId}:${record.line}`, record, timestamp);
183
+ return;
184
+ }
185
+ includeRecord(state.active, record);
186
+ if (isUserPromptRecord(recordType, payloadType, payload)) {
187
+ const message = userPromptText(payload)?.trim();
188
+ const displayText = message && codexUserMessageOrigin(message) === 'human'
189
+ ? codexUserDisplayText(message)?.trim()
190
+ : undefined;
191
+ if (!displayText)
192
+ return;
193
+ state.active ??= newMutableTask(`turn:${sourceThreadId}:${record.line}`, record, timestamp);
194
+ const titlePriority = userPromptPriority(recordType, payloadType);
195
+ if (titlePriority > state.active.titlePriority) {
196
+ state.active.title = compactTitle(displayText);
197
+ state.active.titlePriority = titlePriority;
198
+ }
199
+ return;
200
+ }
201
+ if (!state.active)
202
+ return;
203
+ if (recordType === 'response_item' && isToolCallPayload(payloadType)) {
204
+ recordToolCall(state.active, record, payload);
205
+ }
206
+ recordToolOutcome(state.active, record, recordType, payloadType, payload);
207
+ if (recordType === 'event_msg' && payloadType === 'task_complete') {
208
+ state.active.status = 'completed';
209
+ state.active.endTimestamp = timestamp;
210
+ if (state.active.titlePriority === 0) {
211
+ const fallback = stringValue(payload.last_agent_message)?.trim();
212
+ if (fallback)
213
+ state.active.title = compactTitle(fallback);
214
+ }
215
+ state.tasks.push(stripMutable(state.active));
216
+ state.active = undefined;
217
+ return;
218
+ }
219
+ if (recordType === 'event_msg' && (payloadType === 'turn_aborted' || payloadType === 'turn_interrupted')) {
220
+ state.active.status = payloadType === 'turn_aborted' ? 'aborted' : 'interrupted';
221
+ state.active.endTimestamp = timestamp;
222
+ state.tasks.push(stripMutable(state.active));
223
+ state.active = undefined;
224
+ }
225
+ }, startOffset, endOffset, startLine);
226
+ state.indexedSize = scanned.indexedSize;
227
+ state.indexedLineCount = scanned.indexedLineCount;
228
+ state.indexedEndsWithNewline = scanned.indexedEndsWithNewline;
229
+ return state;
230
+ }
231
+ function indexFromState(sourcePath, sourceThreadId, sourceStat, state) {
232
+ const tasks = state.active
233
+ ? [...state.tasks, stripMutable(state.active)]
234
+ : state.tasks;
235
+ return {
236
+ schemaVersion: INDEX_SCHEMA_VERSION,
237
+ sourcePath,
238
+ sourceSize: sourceStat.size,
239
+ sourceMtimeMs: sourceStat.mtimeMs,
240
+ indexedSize: state.indexedSize,
241
+ indexedLineCount: state.indexedLineCount,
242
+ indexedEndsWithNewline: state.indexedEndsWithNewline,
243
+ sourceThreadId,
244
+ sessionMeta: state.sessionMeta,
245
+ sessionMetaLine: state.sessionMetaLine,
246
+ tasks,
247
+ activeToolOutcomeState: state.active
248
+ ? serializeToolOutcomeTracker(state.active.toolOutcomeTracker)
249
+ : undefined,
250
+ sourceRecordCount: state.sourceRecordCount,
251
+ malformedRecordCount: state.malformedRecordCount,
252
+ };
253
+ }
254
+ function newMutableTask(turnId, record, startTimestamp) {
255
+ return {
256
+ turnId,
257
+ title: '未命名任务',
258
+ titlePriority: 0,
259
+ startTimestamp,
260
+ status: 'open',
261
+ sourceRecordCount: 1,
262
+ toolCallCount: 0,
263
+ toolFailureCount: 0,
264
+ startOffset: record.startOffset,
265
+ endOffset: record.endOffset,
266
+ startLine: record.line,
267
+ endLine: record.line,
268
+ toolOutcomeTracker: createToolOutcomeTracker(),
269
+ };
270
+ }
271
+ function mutableTask(task, activeToolOutcomeState) {
272
+ return {
273
+ ...task,
274
+ titlePriority: task.title === '未命名任务' ? 0 : 1,
275
+ toolOutcomeTracker: createToolOutcomeTracker(activeToolOutcomeState),
276
+ };
277
+ }
278
+ export function isCurrentCodexRolloutIndex(value, sourcePath) {
279
+ const candidate = objectValue(value);
280
+ if (!candidate || candidate.schemaVersion !== INDEX_SCHEMA_VERSION || candidate.sourcePath !== sourcePath)
281
+ return false;
282
+ const sourceSize = numberValue(candidate.sourceSize);
283
+ const indexedSize = numberValue(candidate.indexedSize);
284
+ const indexedLineCount = numberValue(candidate.indexedLineCount);
285
+ if (sourceSize === undefined || indexedSize === undefined || indexedLineCount === undefined
286
+ || typeof candidate.indexedEndsWithNewline !== 'boolean')
287
+ return false;
288
+ if (indexedSize > sourceSize || !Array.isArray(candidate.tasks))
289
+ return false;
290
+ try {
291
+ const current = statSync(sourcePath);
292
+ return sourceSize === current.size
293
+ && candidate.sourceMtimeMs === current.mtimeMs
294
+ && indexedSize <= current.size;
295
+ }
296
+ catch {
297
+ return false;
298
+ }
299
+ }
300
+ /**
301
+ * Codex rollouts are append-only while a conversation is active. A prefix
302
+ * index remains internally consistent even after newer records are appended.
303
+ */
304
+ export function isReusableCodexRolloutIndex(value, sourcePath) {
305
+ const candidate = objectValue(value);
306
+ if (!candidate || candidate.schemaVersion !== INDEX_SCHEMA_VERSION || candidate.sourcePath !== sourcePath)
307
+ return false;
308
+ if (!Array.isArray(candidate.tasks))
309
+ return false;
310
+ try {
311
+ const current = statSync(sourcePath);
312
+ const sourceSize = numberValue(candidate.sourceSize);
313
+ const sourceMtimeMs = numberValue(candidate.sourceMtimeMs);
314
+ const indexedSize = numberValue(candidate.indexedSize);
315
+ const indexedLineCount = numberValue(candidate.indexedLineCount);
316
+ if (sourceSize === undefined || sourceMtimeMs === undefined
317
+ || indexedSize === undefined || indexedLineCount === undefined
318
+ || typeof candidate.indexedEndsWithNewline !== 'boolean')
319
+ return false;
320
+ if (indexedSize > sourceSize || sourceSize > current.size)
321
+ return false;
322
+ if (current.size === sourceSize)
323
+ return current.mtimeMs === sourceMtimeMs;
324
+ return current.size > sourceSize && current.mtimeMs >= sourceMtimeMs;
325
+ }
326
+ catch {
327
+ return false;
328
+ }
329
+ }
330
+ export function readCodexTaskRecords(index, task) {
331
+ const records = [];
332
+ if (index.sessionMeta !== undefined && index.sessionMetaLine !== undefined) {
333
+ records[index.sessionMetaLine] = index.sessionMeta;
334
+ }
335
+ const lines = [];
336
+ let malformedRecordCount = 0;
337
+ forEachJsonlLine(index.sourcePath, (record) => {
338
+ if (record.endOffset <= task.startOffset || record.startOffset >= task.endOffset)
339
+ return;
340
+ lines.push(record);
341
+ try {
342
+ records[record.line] = JSON.parse(record.text);
343
+ }
344
+ catch {
345
+ malformedRecordCount += 1;
346
+ }
347
+ }, task.startOffset, task.endOffset, task.startLine);
348
+ return { records, lines, malformedRecordCount };
349
+ }
350
+ function finalizeSupersededTask(task, endOffset, endLine, endTimestamp) {
351
+ return stripMutable({
352
+ ...task,
353
+ status: 'unknown',
354
+ endTimestamp: task.endTimestamp ?? endTimestamp,
355
+ endOffset: Math.max(task.endOffset, endOffset),
356
+ endLine: Math.max(task.endLine, endLine),
357
+ });
358
+ }
359
+ function stripMutable({ titlePriority: _titlePriority, toolOutcomeTracker: _toolOutcomeTracker, ...task }) {
360
+ return task;
361
+ }
362
+ function includeRecord(task, record) {
363
+ if (!task)
364
+ return;
365
+ task.sourceRecordCount += 1;
366
+ task.endOffset = record.endOffset;
367
+ task.endLine = record.line;
368
+ }
369
+ function isIndexRelevantLine(line) {
370
+ return line.includes('"type":"session_meta"')
371
+ || line.includes('"type":"task_started"')
372
+ || line.includes('"type":"task_complete"')
373
+ || line.includes('"type":"turn_aborted"')
374
+ || line.includes('"type":"turn_interrupted"')
375
+ || line.includes('"type":"user_message"')
376
+ || line.includes('"type":"message"')
377
+ || line.includes('"type":"function_call"')
378
+ || line.includes('"type":"custom_tool_call"')
379
+ || line.includes('"type":"local_shell_call"')
380
+ || line.includes('"type":"function_call_output"')
381
+ || line.includes('"type":"custom_tool_call_output"')
382
+ || line.includes('"type":"mcp_tool_call_end"')
383
+ || line.includes('"type":"patch_apply_end"');
384
+ }
385
+ function isToolCallPayload(payloadType) {
386
+ return payloadType === 'function_call'
387
+ || payloadType === 'custom_tool_call'
388
+ || payloadType === 'local_shell_call';
389
+ }
390
+ function isUserPromptRecord(recordType, payloadType, payload) {
391
+ return (recordType === 'event_msg' && payloadType === 'user_message')
392
+ || (recordType === 'response_item' && payloadType === 'message' && payload.role === 'user');
393
+ }
394
+ function userPromptPriority(recordType, payloadType) {
395
+ return recordType === 'event_msg' && payloadType === 'user_message' ? 2 : 1;
396
+ }
397
+ function userPromptText(payload) {
398
+ const direct = stringValue(payload.message) ?? stringValue(payload.content);
399
+ if (direct)
400
+ return direct;
401
+ if (!Array.isArray(payload.content))
402
+ return undefined;
403
+ const parts = payload.content.flatMap((value) => {
404
+ const item = objectValue(value);
405
+ const text = item ? stringValue(item.text) : undefined;
406
+ return text ? [text] : [];
407
+ });
408
+ return parts.length > 0 ? parts.join('\n') : undefined;
409
+ }
410
+ function recordToolOutcome(task, record, recordType, payloadType, payload) {
411
+ if (recordType === 'response_item'
412
+ && (payloadType === 'function_call_output' || payloadType === 'custom_tool_call_output')) {
413
+ const callId = stringValue(payload.call_id) ?? stringValue(payload.id) ?? `result:${record.line}`;
414
+ const occurrence = takeToolOccurrence(task.toolOutcomeTracker.resultOccurrences, callId);
415
+ updateToolOutcome(task, toolOccurrenceKey(callId, occurrence), 'outputStatus', codexToolOutputOutcome(payload.output, payload.status).status);
416
+ return;
417
+ }
418
+ if (recordType !== 'event_msg'
419
+ || (payloadType !== 'mcp_tool_call_end' && payloadType !== 'patch_apply_end'))
420
+ return;
421
+ const callId = stringValue(payload.call_id) ?? stringValue(payload.id) ?? `runtime:${record.line}`;
422
+ const occurrence = takeToolOccurrence(task.toolOutcomeTracker.runtimeOccurrences, callId);
423
+ ensureRepresentedToolCall(task, callId, occurrence);
424
+ const outcome = codexRuntimeToolOutcomeFromPayload(payloadType, payload);
425
+ if (!outcome.present)
426
+ return;
427
+ updateToolOutcome(task, toolOccurrenceKey(callId, occurrence), 'runtimeStatus', outcome.status);
428
+ }
429
+ function recordToolCall(task, record, payload) {
430
+ const callId = stringValue(payload.call_id) ?? stringValue(payload.id) ?? `call:${record.line}`;
431
+ takeToolOccurrence(task.toolOutcomeTracker.callOccurrences, callId);
432
+ task.toolCallCount += 1;
433
+ }
434
+ /** Match Trace IR's contract: a standalone runtime end still represents one tool call. */
435
+ function ensureRepresentedToolCall(task, callId, occurrence) {
436
+ const representedCount = task.toolOutcomeTracker.callOccurrences.get(callId) ?? 0;
437
+ const requiredCount = occurrence + 1;
438
+ if (representedCount >= requiredCount)
439
+ return;
440
+ task.toolOutcomeTracker.callOccurrences.set(callId, requiredCount);
441
+ task.toolCallCount += requiredCount - representedCount;
442
+ }
443
+ function updateToolOutcome(task, key, source, status) {
444
+ const previous = task.toolOutcomeTracker.outcomes.get(key) ?? {};
445
+ const wasFailure = resolvedToolStatus(previous) === 'failure';
446
+ const next = { ...previous, [source]: status };
447
+ task.toolOutcomeTracker.outcomes.set(key, next);
448
+ const isFailure = resolvedToolStatus(next) === 'failure';
449
+ if (wasFailure !== isFailure)
450
+ task.toolFailureCount += isFailure ? 1 : -1;
451
+ }
452
+ function resolvedToolStatus(outcome) {
453
+ return outcome.runtimeStatus ?? outcome.outputStatus ?? 'unknown';
454
+ }
455
+ function takeToolOccurrence(counts, callId) {
456
+ const occurrence = counts.get(callId) ?? 0;
457
+ counts.set(callId, occurrence + 1);
458
+ return occurrence;
459
+ }
460
+ function toolOccurrenceKey(callId, occurrence) {
461
+ return `${callId}\u0000${occurrence}`;
462
+ }
463
+ function createToolOutcomeTracker(state) {
464
+ return {
465
+ callOccurrences: new Map(state?.callOccurrences ?? []),
466
+ resultOccurrences: new Map(state?.resultOccurrences ?? []),
467
+ runtimeOccurrences: new Map(state?.runtimeOccurrences ?? []),
468
+ outcomes: new Map(state?.outcomes ?? []),
469
+ };
470
+ }
471
+ function serializeToolOutcomeTracker(tracker) {
472
+ return {
473
+ callOccurrences: [...tracker.callOccurrences],
474
+ resultOccurrences: [...tracker.resultOccurrences],
475
+ runtimeOccurrences: [...tracker.runtimeOccurrences],
476
+ outcomes: [...tracker.outcomes],
477
+ };
478
+ }
479
+ function compactTitle(value) {
480
+ return (codexUserDisplayText(value) ?? '').replace(/\s+/gu, ' ').trim().slice(0, 180);
481
+ }
482
+ function objectValue(value) {
483
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
484
+ ? value
485
+ : undefined;
486
+ }
487
+ function stringValue(value) {
488
+ return typeof value === 'string' && value.trim() ? value : undefined;
489
+ }
490
+ function numberValue(value) {
491
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
492
+ }
493
+ function forEachJsonlLine(filePath, visit, startOffset = 0, endOffset = Number.POSITIVE_INFINITY, startLine = 0) {
494
+ const fd = openSync(filePath, 'r');
495
+ const buffer = Buffer.allocUnsafe(READ_CHUNK_BYTES);
496
+ let absoluteOffset = startOffset;
497
+ let lineStartOffset = startOffset;
498
+ let lineNumber = startLine;
499
+ let indexedSize = startOffset;
500
+ let indexedLineCount = startLine;
501
+ let indexedEndsWithNewline = true;
502
+ let fragments = [];
503
+ try {
504
+ while (absoluteOffset < endOffset) {
505
+ const requestBytes = Math.min(buffer.length, endOffset - absoluteOffset);
506
+ const bytesRead = readSync(fd, buffer, 0, requestBytes, absoluteOffset);
507
+ if (bytesRead === 0)
508
+ break;
509
+ let cursor = 0;
510
+ for (let index = 0; index < bytesRead; index += 1) {
511
+ if (buffer[index] !== 0x0a)
512
+ continue;
513
+ const tail = buffer.subarray(cursor, index);
514
+ const lineBuffer = fragments.length > 0
515
+ ? Buffer.concat([...fragments, tail])
516
+ : tail;
517
+ const nextOffset = absoluteOffset + index + 1;
518
+ emitLine(lineBuffer, lineNumber, lineStartOffset, nextOffset, visit);
519
+ indexedSize = nextOffset;
520
+ indexedLineCount = lineNumber + 1;
521
+ indexedEndsWithNewline = true;
522
+ fragments = [];
523
+ cursor = index + 1;
524
+ lineStartOffset = nextOffset;
525
+ lineNumber += 1;
526
+ }
527
+ if (cursor < bytesRead)
528
+ fragments.push(Buffer.from(buffer.subarray(cursor, bytesRead)));
529
+ if (fragments.reduce((sum, item) => sum + item.length, 0) > MAX_RECORD_BYTES) {
530
+ throw new Error(`Codex JSONL 单条记录超过 ${MAX_RECORD_BYTES} 字节上限:${filePath}`);
531
+ }
532
+ absoluteOffset += bytesRead;
533
+ }
534
+ if (fragments.length > 0) {
535
+ const trailing = Buffer.concat(fragments);
536
+ if (isCompleteJsonRecord(trailing)) {
537
+ emitLine(trailing, lineNumber, lineStartOffset, absoluteOffset, visit);
538
+ indexedSize = absoluteOffset;
539
+ indexedLineCount = lineNumber + 1;
540
+ indexedEndsWithNewline = false;
541
+ }
542
+ }
543
+ }
544
+ finally {
545
+ closeSync(fd);
546
+ }
547
+ return { indexedSize, indexedLineCount, indexedEndsWithNewline };
548
+ }
549
+ function isCompleteJsonRecord(buffer) {
550
+ const text = buffer.toString('utf8').trim();
551
+ if (!text)
552
+ return false;
553
+ try {
554
+ JSON.parse(text);
555
+ return true;
556
+ }
557
+ catch {
558
+ return false;
559
+ }
560
+ }
561
+ function emitLine(buffer, line, startOffset, endOffset, visit) {
562
+ const text = buffer.toString('utf8').trim();
563
+ if (!text)
564
+ return;
565
+ visit({ text, line, startOffset, endOffset });
566
+ }
@@ -0,0 +1,12 @@
1
+ /** Stable inventory of Codex rollout record shapes understood by OMK. */
2
+ export declare function isCodexResponseItemType(value: string | undefined): boolean;
3
+ export declare function isCodexEventMessageType(value: string | undefined): boolean;
4
+ /** Preserve source text separately while removing Codex UI transport envelopes from semantic display. */
5
+ export declare function codexUserDisplayText(text: string): string | undefined;
6
+ export declare function codexUserMessageOrigin(text: string): 'human' | 'runtime';
7
+ /**
8
+ * Records consumed by correlation or retained only as source provenance do not
9
+ * emit a standalone Trace IR event. Keeping this inventory explicit preserves
10
+ * `unknown` as a forward-compatibility warning.
11
+ */
12
+ export declare function isCodexRecordConsumedWithoutDirectEvent(recordType: unknown, payloadType: string | undefined): boolean;