oh-my-knowledge 0.51.2 → 0.52.1
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 +2300 -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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { basename, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { writeJsonFileAtomic } from '../shared/atomic-json.js';
|
|
5
|
+
import { normalizeTraceTimestamp } from './trace-ir.js';
|
|
6
|
+
import { forEachNonEmptyUtf8Line } from './trace-source.js';
|
|
7
|
+
const ARCHIVE_SCHEMA_VERSION = 1;
|
|
8
|
+
const MAX_ARCHIVE_SOURCE_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
const MAX_ARCHIVE_FILE_BYTES = 64 * 1024 * 1024;
|
|
10
|
+
const MAX_RECORD_CHARS = 512 * 1024;
|
|
11
|
+
/**
|
|
12
|
+
* Persist bounded source records beside a report. Absolute source paths are
|
|
13
|
+
* consumed only during trusted ingest; Studio later reads relative sidecars.
|
|
14
|
+
*/
|
|
15
|
+
export function writeObservationSourceRecordArchives(report, outDir, reportPath) {
|
|
16
|
+
const sessions = report.experience?.sessions ?? [];
|
|
17
|
+
if (sessions.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const reportStem = basename(reportPath).replace(/\.report\.json$/u, '');
|
|
20
|
+
const archiveDir = join(outDir, 'source-records', reportStem);
|
|
21
|
+
const builders = sessions.map((session) => ({
|
|
22
|
+
session,
|
|
23
|
+
groupedRanges: groupRecordRanges(session.timelineScope.sessionRecordRanges),
|
|
24
|
+
records: [],
|
|
25
|
+
omittedRecordCount: 0,
|
|
26
|
+
byteCount: 0,
|
|
27
|
+
truncated: false,
|
|
28
|
+
}));
|
|
29
|
+
const targetsBySource = new Map();
|
|
30
|
+
for (const builder of builders) {
|
|
31
|
+
for (const [sourceTrace, ranges] of builder.groupedRanges) {
|
|
32
|
+
const targets = targetsBySource.get(sourceTrace) ?? [];
|
|
33
|
+
targets.push({
|
|
34
|
+
builder,
|
|
35
|
+
ranges,
|
|
36
|
+
expectedCount: ranges.reduce((sum, range) => sum + range.end - range.start + 1, 0),
|
|
37
|
+
matchedCount: 0,
|
|
38
|
+
rangeIndex: 0,
|
|
39
|
+
});
|
|
40
|
+
targetsBySource.set(sourceTrace, targets);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const [sourceTrace, targets] of targetsBySource) {
|
|
44
|
+
collectSourceRecords(sourceTrace, targets);
|
|
45
|
+
}
|
|
46
|
+
return builders.map((builder) => persistSessionArchive(builder, archiveDir, outDir, report.meta.generatedAt));
|
|
47
|
+
}
|
|
48
|
+
export function loadObservationSourceRecordArchive(ref, observationsDir) {
|
|
49
|
+
if (!ref || ref.status === 'unavailable' || !ref.relativePath) {
|
|
50
|
+
return unavailableView(ref?.reason ?? 'no_record_ranges');
|
|
51
|
+
}
|
|
52
|
+
const path = safeArchivePath(observationsDir, ref.relativePath);
|
|
53
|
+
if (!path || !existsSync(path))
|
|
54
|
+
return unavailableView('archive_invalid');
|
|
55
|
+
try {
|
|
56
|
+
if (statSync(path).size > MAX_ARCHIVE_FILE_BYTES)
|
|
57
|
+
return unavailableView('archive_invalid');
|
|
58
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
59
|
+
if (!isObservationSourceRecordArchive(parsed, ref.experienceSessionId)) {
|
|
60
|
+
return unavailableView('archive_invalid');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
status: ref.status,
|
|
64
|
+
recordCount: parsed.records.length,
|
|
65
|
+
records: parsed.records,
|
|
66
|
+
omittedRecordCount: parsed.omittedRecordCount,
|
|
67
|
+
byteCount: parsed.byteCount,
|
|
68
|
+
truncated: parsed.truncated,
|
|
69
|
+
...(ref.reason ? { reason: ref.reason } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return unavailableView('archive_invalid');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the page-safe archive metadata without reading the potentially large
|
|
78
|
+
* sidecar. The source-record endpoint resolves the same ref on demand.
|
|
79
|
+
*/
|
|
80
|
+
export function summarizeObservationSourceRecordArchive(ref) {
|
|
81
|
+
if (!ref)
|
|
82
|
+
return unavailableView('no_record_ranges');
|
|
83
|
+
return {
|
|
84
|
+
status: ref.status,
|
|
85
|
+
recordCount: ref.recordCount,
|
|
86
|
+
records: [],
|
|
87
|
+
omittedRecordCount: ref.omittedRecordCount,
|
|
88
|
+
byteCount: ref.byteCount,
|
|
89
|
+
truncated: ref.truncated,
|
|
90
|
+
...(ref.reason ? { reason: ref.reason } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function collectSourceRecords(sourceTrace, targets) {
|
|
94
|
+
if (extname(sourceTrace).toLowerCase() !== '.jsonl') {
|
|
95
|
+
markSourceUnavailable(targets, 'unsupported_source');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!existsSync(sourceTrace)) {
|
|
99
|
+
markSourceUnavailable(targets, 'source_missing');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const maxEnd = targets.reduce((targetMax, target) => target.ranges.reduce((rangeMax, range) => Math.max(rangeMax, range.end), targetMax), -1);
|
|
103
|
+
let sourceIndex = 0;
|
|
104
|
+
try {
|
|
105
|
+
forEachNonEmptyUtf8Line(sourceTrace, (line) => {
|
|
106
|
+
const currentIndex = sourceIndex;
|
|
107
|
+
sourceIndex += 1;
|
|
108
|
+
const recordsByTraceId = new Map();
|
|
109
|
+
for (const target of targets) {
|
|
110
|
+
while (target.ranges[target.rangeIndex]
|
|
111
|
+
&& target.ranges[target.rangeIndex].end < currentIndex) {
|
|
112
|
+
target.rangeIndex += 1;
|
|
113
|
+
}
|
|
114
|
+
const range = target.ranges[target.rangeIndex];
|
|
115
|
+
if (!range || currentIndex < range.start || currentIndex > range.end)
|
|
116
|
+
continue;
|
|
117
|
+
target.matchedCount += 1;
|
|
118
|
+
if (target.builder.byteCount >= MAX_ARCHIVE_SOURCE_BYTES) {
|
|
119
|
+
target.builder.omittedRecordCount += 1;
|
|
120
|
+
target.builder.truncated = true;
|
|
121
|
+
target.builder.failure = 'archive_limit';
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let record = recordsByTraceId.get(range.traceId);
|
|
125
|
+
if (!record) {
|
|
126
|
+
record = observationSourceRecordFromLine(line, currentIndex, range.traceId, sourceTrace);
|
|
127
|
+
recordsByTraceId.set(range.traceId, record);
|
|
128
|
+
}
|
|
129
|
+
if (target.builder.byteCount + record.byteCount > MAX_ARCHIVE_SOURCE_BYTES) {
|
|
130
|
+
target.builder.omittedRecordCount += 1;
|
|
131
|
+
target.builder.truncated = true;
|
|
132
|
+
target.builder.failure = 'archive_limit';
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
target.builder.records.push(record);
|
|
136
|
+
target.builder.byteCount += record.byteCount;
|
|
137
|
+
target.builder.truncated ||= record.truncated;
|
|
138
|
+
}
|
|
139
|
+
return currentIndex < maxEnd;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
for (const target of targets)
|
|
144
|
+
target.builder.failure ??= 'read_failed';
|
|
145
|
+
}
|
|
146
|
+
for (const target of targets) {
|
|
147
|
+
target.builder.omittedRecordCount += Math.max(0, target.expectedCount - target.matchedCount);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function markSourceUnavailable(targets, reason) {
|
|
151
|
+
for (const target of targets) {
|
|
152
|
+
target.builder.omittedRecordCount += target.expectedCount;
|
|
153
|
+
target.builder.failure ??= reason;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function persistSessionArchive(builder, archiveDir, outDir, generatedAt) {
|
|
157
|
+
const { session, records, omittedRecordCount, byteCount, failure } = builder;
|
|
158
|
+
if (builder.groupedRanges.size === 0)
|
|
159
|
+
return unavailableRef(session.id, 'no_record_ranges');
|
|
160
|
+
if (records.length === 0)
|
|
161
|
+
return unavailableRef(session.id, failure ?? 'read_failed', omittedRecordCount);
|
|
162
|
+
const archive = {
|
|
163
|
+
archiveKind: 'observe-source-records',
|
|
164
|
+
schemaVersion: ARCHIVE_SCHEMA_VERSION,
|
|
165
|
+
experienceSessionId: session.id,
|
|
166
|
+
generatedAt,
|
|
167
|
+
records,
|
|
168
|
+
omittedRecordCount,
|
|
169
|
+
byteCount,
|
|
170
|
+
truncated: builder.truncated || omittedRecordCount > 0,
|
|
171
|
+
};
|
|
172
|
+
const fileName = `${createHash('sha256').update(session.id).digest('hex').slice(0, 24)}.json`;
|
|
173
|
+
const archivePath = join(archiveDir, fileName);
|
|
174
|
+
writeJsonFileAtomic(archivePath, archive);
|
|
175
|
+
const relativePath = relative(outDir, archivePath).split('\\').join('/');
|
|
176
|
+
const partial = archive.truncated || failure !== undefined;
|
|
177
|
+
return {
|
|
178
|
+
experienceSessionId: session.id,
|
|
179
|
+
status: partial ? 'partial' : 'available',
|
|
180
|
+
relativePath,
|
|
181
|
+
recordCount: records.length,
|
|
182
|
+
omittedRecordCount,
|
|
183
|
+
byteCount,
|
|
184
|
+
truncated: archive.truncated,
|
|
185
|
+
...(failure ? { reason: failure } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export function observationSourceRecordFromLine(line, sourceIndex, traceId, sourceTrace) {
|
|
189
|
+
let record = {};
|
|
190
|
+
let serialized = line;
|
|
191
|
+
let redacted = false;
|
|
192
|
+
try {
|
|
193
|
+
const parsed = JSON.parse(line);
|
|
194
|
+
if (isRecord(parsed)) {
|
|
195
|
+
record = parsed;
|
|
196
|
+
serialized = JSON.stringify(parsed, (key, value) => {
|
|
197
|
+
if (key !== 'encrypted_content')
|
|
198
|
+
return value;
|
|
199
|
+
redacted = true;
|
|
200
|
+
return '[opaque encrypted content omitted]';
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Malformed source lines are still useful provenance.
|
|
206
|
+
}
|
|
207
|
+
const payload = isRecord(record.payload) ? record.payload : {};
|
|
208
|
+
const recordType = textValue(record.type) ?? 'unknown';
|
|
209
|
+
const payloadType = textValue(payload.type);
|
|
210
|
+
const raw = serialized.length > MAX_RECORD_CHARS ? serialized.slice(0, MAX_RECORD_CHARS) : serialized;
|
|
211
|
+
return {
|
|
212
|
+
sourceIndex,
|
|
213
|
+
traceId,
|
|
214
|
+
sourceTrace,
|
|
215
|
+
sourceType: `${recordType}:${payloadType ?? ''}`,
|
|
216
|
+
sourceEventId: textValue(payload.id),
|
|
217
|
+
timestamp: normalizeTraceTimestamp(record.timestamp),
|
|
218
|
+
raw,
|
|
219
|
+
byteCount: Buffer.byteLength(raw),
|
|
220
|
+
truncated: raw.length < serialized.length,
|
|
221
|
+
redacted,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function groupRecordRanges(ranges) {
|
|
225
|
+
const grouped = new Map();
|
|
226
|
+
for (const range of ranges) {
|
|
227
|
+
const current = grouped.get(range.sourceTrace) ?? [];
|
|
228
|
+
current.push({ start: range.startRecordIndex, end: range.endRecordIndex, traceId: range.traceId });
|
|
229
|
+
grouped.set(range.sourceTrace, current);
|
|
230
|
+
}
|
|
231
|
+
for (const [sourceTrace, sourceRanges] of grouped) {
|
|
232
|
+
const sorted = sourceRanges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
233
|
+
const merged = [];
|
|
234
|
+
for (const range of sorted) {
|
|
235
|
+
const previous = merged.at(-1);
|
|
236
|
+
if (previous && previous.traceId === range.traceId && range.start <= previous.end + 1) {
|
|
237
|
+
previous.end = Math.max(previous.end, range.end);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
merged.push({ ...range });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
grouped.set(sourceTrace, merged);
|
|
244
|
+
}
|
|
245
|
+
return grouped;
|
|
246
|
+
}
|
|
247
|
+
function safeArchivePath(observationsDir, relativePath) {
|
|
248
|
+
if (!relativePath || isAbsolute(relativePath))
|
|
249
|
+
return undefined;
|
|
250
|
+
const candidate = resolve(observationsDir, relativePath);
|
|
251
|
+
const lexicalRelative = relative(resolve(observationsDir), candidate);
|
|
252
|
+
if (lexicalRelative.startsWith('..') || isAbsolute(lexicalRelative))
|
|
253
|
+
return undefined;
|
|
254
|
+
try {
|
|
255
|
+
const root = realpathSync(observationsDir);
|
|
256
|
+
const resolved = realpathSync(candidate);
|
|
257
|
+
const resolvedRelative = relative(root, resolved);
|
|
258
|
+
if (resolvedRelative.startsWith('..') || isAbsolute(resolvedRelative))
|
|
259
|
+
return undefined;
|
|
260
|
+
return resolved;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function unavailableRef(experienceSessionId, reason, omittedRecordCount = 0) {
|
|
267
|
+
return {
|
|
268
|
+
experienceSessionId,
|
|
269
|
+
status: 'unavailable',
|
|
270
|
+
recordCount: 0,
|
|
271
|
+
omittedRecordCount,
|
|
272
|
+
byteCount: 0,
|
|
273
|
+
truncated: omittedRecordCount > 0,
|
|
274
|
+
reason,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function unavailableView(reason) {
|
|
278
|
+
return {
|
|
279
|
+
status: 'unavailable',
|
|
280
|
+
recordCount: 0,
|
|
281
|
+
records: [],
|
|
282
|
+
omittedRecordCount: 0,
|
|
283
|
+
byteCount: 0,
|
|
284
|
+
truncated: false,
|
|
285
|
+
reason,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function isObservationSourceRecordArchive(value, experienceSessionId) {
|
|
289
|
+
if (!isRecord(value)
|
|
290
|
+
|| value.archiveKind !== 'observe-source-records'
|
|
291
|
+
|| value.schemaVersion !== ARCHIVE_SCHEMA_VERSION
|
|
292
|
+
|| value.experienceSessionId !== experienceSessionId
|
|
293
|
+
|| typeof value.generatedAt !== 'string'
|
|
294
|
+
|| !Array.isArray(value.records)
|
|
295
|
+
|| !value.records.every(isObservationSourceRecord)
|
|
296
|
+
|| !isCount(value.omittedRecordCount)
|
|
297
|
+
|| !isCount(value.byteCount)
|
|
298
|
+
|| typeof value.truncated !== 'boolean')
|
|
299
|
+
return false;
|
|
300
|
+
return value.records.reduce((sum, record) => sum + record.byteCount, 0) === value.byteCount;
|
|
301
|
+
}
|
|
302
|
+
function isObservationSourceRecord(value) {
|
|
303
|
+
return isRecord(value)
|
|
304
|
+
&& isCount(value.sourceIndex)
|
|
305
|
+
&& typeof value.traceId === 'string'
|
|
306
|
+
&& typeof value.sourceTrace === 'string'
|
|
307
|
+
&& typeof value.sourceType === 'string'
|
|
308
|
+
&& (value.sourceEventId === undefined || typeof value.sourceEventId === 'string')
|
|
309
|
+
&& (value.timestamp === undefined || typeof value.timestamp === 'string')
|
|
310
|
+
&& typeof value.raw === 'string'
|
|
311
|
+
&& isCount(value.byteCount)
|
|
312
|
+
&& Buffer.byteLength(value.raw) === value.byteCount
|
|
313
|
+
&& typeof value.truncated === 'boolean'
|
|
314
|
+
&& typeof value.redacted === 'boolean';
|
|
315
|
+
}
|
|
316
|
+
function isRecord(value) {
|
|
317
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
318
|
+
}
|
|
319
|
+
function isCount(value) {
|
|
320
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
321
|
+
}
|
|
322
|
+
function textValue(value) {
|
|
323
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
324
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExperienceTimelineEvent, TaskTrajectorySession, TaskWindowScope } from '../types/index.js';
|
|
2
|
+
export declare const TASK_SEMANTIC_EVENT_LIMIT = 240;
|
|
3
|
+
export interface ResolvedTaskWindow {
|
|
4
|
+
events: ExperienceTimelineEvent[];
|
|
5
|
+
semanticEvents: ExperienceTimelineEvent[];
|
|
6
|
+
scope: TaskWindowScope;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveTaskWindow(session: TaskTrajectorySession, targetTurnId: string, semanticLimit?: number): ResolvedTaskWindow;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const TASK_SEMANTIC_EVENT_LIMIT = 240;
|
|
2
|
+
export function resolveTaskWindow(session, targetTurnId, semanticLimit = TASK_SEMANTIC_EVENT_LIMIT) {
|
|
3
|
+
const events = session.fullSessionTimeline;
|
|
4
|
+
const attributedIds = new Set(session.attributedEventIds);
|
|
5
|
+
const attributed = events.filter((event) => attributedIds.has(event.id));
|
|
6
|
+
const selected = session.turns.find((turn) => turn.turnId === targetTurnId);
|
|
7
|
+
const eventById = new Map(events.map((event) => [event.id, event]));
|
|
8
|
+
const taskEvents = selected
|
|
9
|
+
? selected.eventIds.flatMap((id) => {
|
|
10
|
+
const event = eventById.get(id);
|
|
11
|
+
return event ? [event] : [];
|
|
12
|
+
})
|
|
13
|
+
: [];
|
|
14
|
+
const semanticEvents = semanticPreview(taskEvents, semanticLimit);
|
|
15
|
+
const matchedAttributedEventCount = taskEvents.filter((event) => attributedIds.has(event.id)).length;
|
|
16
|
+
return {
|
|
17
|
+
events: taskEvents,
|
|
18
|
+
semanticEvents,
|
|
19
|
+
scope: {
|
|
20
|
+
basis: selected?.boundaryBasis ?? 'unresolved',
|
|
21
|
+
turnId: selected?.turnId,
|
|
22
|
+
normalizedEventCount: taskEvents.length,
|
|
23
|
+
semanticEventCount: semanticEvents.length,
|
|
24
|
+
attributedEventCount: attributed.length,
|
|
25
|
+
matchedAttributedEventCount,
|
|
26
|
+
truncated: semanticEvents.length < taskEvents.length,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function semanticPreview(events, limit) {
|
|
31
|
+
if (events.length <= limit)
|
|
32
|
+
return events;
|
|
33
|
+
const headCount = Math.ceil(limit * 0.6);
|
|
34
|
+
const tailCount = limit - headCount;
|
|
35
|
+
return [...events.slice(0, headCount), ...events.slice(-tailCount)];
|
|
36
|
+
}
|
|
@@ -7,6 +7,11 @@ export type TraceMessageRole = 'user' | 'assistant' | 'system';
|
|
|
7
7
|
export type TraceMessageOrigin = 'human' | 'runtime' | 'skill-context' | 'synthetic';
|
|
8
8
|
export type TraceToolStatus = ToolCallStatus;
|
|
9
9
|
export type TraceToolStatusSource = ToolCallStatusSource;
|
|
10
|
+
export type TraceModelActivityKind = 'reasoning';
|
|
11
|
+
export type TraceModelActivityVisibility = 'plaintext' | 'opaque';
|
|
12
|
+
export type TraceModelActivityContentSource = 'summary' | 'content' | 'text';
|
|
13
|
+
export type TraceRuntimeContextKind = 'session_context' | 'execution_context' | 'settings' | 'goal';
|
|
14
|
+
export type TraceAgentActivityKind = 'communication' | 'status';
|
|
10
15
|
/** Source-neutral tool identity, including provider namespaces when present. */
|
|
11
16
|
export type TraceToolRef = NormalizedToolIdentity;
|
|
12
17
|
interface TraceEventBase {
|
|
@@ -23,6 +28,8 @@ export interface TraceMessageEvent extends TraceEventBase {
|
|
|
23
28
|
role: TraceMessageRole;
|
|
24
29
|
origin: TraceMessageOrigin;
|
|
25
30
|
text: string;
|
|
31
|
+
/** Human-facing text after a source adapter removes transport/UI envelopes. */
|
|
32
|
+
displayText?: string;
|
|
26
33
|
model?: string;
|
|
27
34
|
attributionSkill?: string;
|
|
28
35
|
}
|
|
@@ -51,17 +58,75 @@ export interface TraceUsageEvent extends TraceEventBase {
|
|
|
51
58
|
cacheCreationTokens: number;
|
|
52
59
|
reasoningTokens?: number;
|
|
53
60
|
}
|
|
61
|
+
export interface TraceModelActivityEvent extends TraceEventBase {
|
|
62
|
+
eventKind: 'model_activity';
|
|
63
|
+
activityKind: TraceModelActivityKind;
|
|
64
|
+
contentVisibility: TraceModelActivityVisibility;
|
|
65
|
+
text?: string;
|
|
66
|
+
contentSource?: TraceModelActivityContentSource;
|
|
67
|
+
model?: string;
|
|
68
|
+
}
|
|
54
69
|
export interface TraceLifecycleEvent extends TraceEventBase {
|
|
55
70
|
eventKind: 'lifecycle';
|
|
56
71
|
phase: 'session_started' | 'session_ended' | 'turn_started' | 'turn_completed' | 'turn_aborted' | 'turn_interrupted';
|
|
57
72
|
reason?: string;
|
|
58
73
|
durationMs?: number;
|
|
59
74
|
}
|
|
75
|
+
/** Source-neutral execution context visible to the agent for a turn or run. */
|
|
76
|
+
export interface TraceRuntimeContextEvent extends TraceEventBase {
|
|
77
|
+
eventKind: 'runtime_context';
|
|
78
|
+
runtimeKind: TraceRuntimeContextKind;
|
|
79
|
+
runtimeName?: string;
|
|
80
|
+
runtimeVersion?: string;
|
|
81
|
+
cwd?: string;
|
|
82
|
+
workspaceRoots?: string[];
|
|
83
|
+
currentDate?: string;
|
|
84
|
+
timezone?: string;
|
|
85
|
+
model?: string;
|
|
86
|
+
modelProvider?: string;
|
|
87
|
+
serviceTier?: string;
|
|
88
|
+
reasoningEffort?: string;
|
|
89
|
+
reasoningSummary?: string;
|
|
90
|
+
personality?: string;
|
|
91
|
+
approvalPolicy?: string;
|
|
92
|
+
approvalReviewer?: string;
|
|
93
|
+
permissionProfile?: string;
|
|
94
|
+
sandboxMode?: string;
|
|
95
|
+
collaborationMode?: string;
|
|
96
|
+
realtimeActive?: boolean;
|
|
97
|
+
multiAgentMode?: string;
|
|
98
|
+
multiAgentVersion?: string;
|
|
99
|
+
memoryMode?: string;
|
|
100
|
+
historyMode?: string;
|
|
101
|
+
contextWindowId?: string;
|
|
102
|
+
availableTools?: string[];
|
|
103
|
+
instructions?: string;
|
|
104
|
+
goal?: string;
|
|
105
|
+
goalStatus?: string;
|
|
106
|
+
summary?: string;
|
|
107
|
+
}
|
|
108
|
+
/** A source-reported context-window compaction boundary. */
|
|
109
|
+
export interface TraceContextCompactionEvent extends TraceEventBase {
|
|
110
|
+
eventKind: 'context_compaction';
|
|
111
|
+
summary?: string;
|
|
112
|
+
replacementItemCount?: number;
|
|
113
|
+
}
|
|
114
|
+
/** Observable communication or status emitted by a cooperating agent. */
|
|
115
|
+
export interface TraceAgentActivityEvent extends TraceEventBase {
|
|
116
|
+
eventKind: 'agent_activity';
|
|
117
|
+
activityKind: TraceAgentActivityKind;
|
|
118
|
+
agentId?: string;
|
|
119
|
+
agentPath?: string;
|
|
120
|
+
activity?: string;
|
|
121
|
+
author?: string;
|
|
122
|
+
recipient?: string;
|
|
123
|
+
text?: string;
|
|
124
|
+
}
|
|
60
125
|
export interface TraceUnknownEvent extends TraceEventBase {
|
|
61
126
|
eventKind: 'unknown';
|
|
62
127
|
raw?: unknown;
|
|
63
128
|
}
|
|
64
|
-
export type TraceEvent = TraceMessageEvent | TraceToolCallEvent | TraceToolResultEvent | TraceUsageEvent | TraceLifecycleEvent | TraceUnknownEvent;
|
|
129
|
+
export type TraceEvent = TraceMessageEvent | TraceToolCallEvent | TraceToolResultEvent | TraceUsageEvent | TraceModelActivityEvent | TraceLifecycleEvent | TraceRuntimeContextEvent | TraceContextCompactionEvent | TraceAgentActivityEvent | TraceUnknownEvent;
|
|
65
130
|
export interface TraceSession {
|
|
66
131
|
/** Concrete source run/thread identifier. */
|
|
67
132
|
runId: string;
|
|
@@ -96,4 +96,5 @@ export declare function loadTraceSessions(path: string): TraceSession[];
|
|
|
96
96
|
export declare function loadTraceCorpus(path: string): TraceCorpus;
|
|
97
97
|
/** @deprecated Use `loadTraceSessions`. */
|
|
98
98
|
export declare const loadCcSessions: typeof loadTraceSessions;
|
|
99
|
+
export declare function forEachNonEmptyUtf8Line(filePath: string, visit: (trimmedLine: string) => boolean | void): void;
|
|
99
100
|
export declare function legacyCcSessionToTraceSession(session: CcSession): TraceSession;
|
|
@@ -333,7 +333,7 @@ function parseJsonlSessionFile(filePath) {
|
|
|
333
333
|
},
|
|
334
334
|
};
|
|
335
335
|
}
|
|
336
|
-
function forEachNonEmptyUtf8Line(filePath, visit) {
|
|
336
|
+
export function forEachNonEmptyUtf8Line(filePath, visit) {
|
|
337
337
|
let fd;
|
|
338
338
|
try {
|
|
339
339
|
fd = openSync(filePath, 'r');
|
|
@@ -344,30 +344,37 @@ function forEachNonEmptyUtf8Line(filePath, visit) {
|
|
|
344
344
|
const decoder = new StringDecoder('utf8');
|
|
345
345
|
const buffer = Buffer.allocUnsafe(TRACE_READ_CHUNK_BYTES);
|
|
346
346
|
let pending = '';
|
|
347
|
+
let stopped = false;
|
|
347
348
|
const consumeCompleteLines = () => {
|
|
348
349
|
let newline = pending.indexOf('\n');
|
|
349
|
-
while (newline >= 0) {
|
|
350
|
+
while (newline >= 0 && !stopped) {
|
|
350
351
|
if (newline > MAX_JSONL_RECORD_CHARS) {
|
|
351
352
|
throw new Error(`trace JSONL 单条记录超过 ${MAX_JSONL_RECORD_CHARS} 字符上限:${filePath}`);
|
|
352
353
|
}
|
|
353
354
|
const trimmed = pending.slice(0, newline).trim();
|
|
354
355
|
pending = pending.slice(newline + 1);
|
|
355
|
-
if (trimmed)
|
|
356
|
-
|
|
356
|
+
if (trimmed && visit(trimmed) === false) {
|
|
357
|
+
stopped = true;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
357
360
|
newline = pending.indexOf('\n');
|
|
358
361
|
}
|
|
362
|
+
if (stopped)
|
|
363
|
+
return;
|
|
359
364
|
if (pending.length > MAX_JSONL_RECORD_CHARS) {
|
|
360
365
|
throw new Error(`trace JSONL 单条记录超过 ${MAX_JSONL_RECORD_CHARS} 字符上限:${filePath}`);
|
|
361
366
|
}
|
|
362
367
|
};
|
|
363
368
|
try {
|
|
364
|
-
while (
|
|
369
|
+
while (!stopped) {
|
|
365
370
|
const bytesRead = readSync(fd, buffer, 0, buffer.length, null);
|
|
366
371
|
if (bytesRead === 0)
|
|
367
372
|
break;
|
|
368
373
|
pending += decoder.write(buffer.subarray(0, bytesRead));
|
|
369
374
|
consumeCompleteLines();
|
|
370
375
|
}
|
|
376
|
+
if (stopped)
|
|
377
|
+
return;
|
|
371
378
|
pending += decoder.end();
|
|
372
379
|
consumeCompleteLines();
|
|
373
380
|
const trimmed = pending.trim();
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ExperienceTimelineEvent, ExperienceTurnSummary } from '../types/index.js';
|
|
2
|
+
/** Reconstruct every observable task without consulting Skill attribution. */
|
|
3
|
+
export declare function reconstructExperienceTurns(timeline: ExperienceTimelineEvent[]): ExperienceTurnSummary[];
|