poe-code 4.0.31 → 4.0.32
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/cli/commands/traces.js +3 -1
- package/dist/cli/commands/traces.js.map +1 -1
- package/dist/index.js +1807 -1261
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/agent-trace-viewer/dist/index.d.ts +2 -2
- package/packages/agent-trace-viewer/dist/index.js +1 -1
- package/packages/agent-trace-viewer/dist/loader.d.ts +1 -0
- package/packages/agent-trace-viewer/dist/loader.js +90 -6
- package/packages/agent-trace-viewer/dist/run.d.ts +2 -0
- package/packages/agent-trace-viewer/dist/run.js +43 -11
- package/packages/agent-trace-viewer/dist/types.d.ts +9 -0
- package/packages/agent-traces/dist/collect.js +36 -8
- package/packages/agent-traces/dist/index-store/concurrency.d.ts +1 -0
- package/packages/agent-traces/dist/index-store/concurrency.js +16 -0
- package/packages/agent-traces/dist/index-store/head.d.ts +3 -0
- package/packages/agent-traces/dist/index-store/head.js +19 -0
- package/packages/agent-traces/dist/index-store/store.d.ts +45 -0
- package/packages/agent-traces/dist/index-store/store.js +263 -0
- package/packages/agent-traces/dist/index.d.ts +3 -1
- package/packages/agent-traces/dist/index.js +1 -0
- package/packages/agent-traces/dist/readers/claude.js +25 -6
- package/packages/agent-traces/dist/readers/pi.js +61 -10
- package/packages/agent-traces/dist/readers/poe-code.js +27 -9
- package/packages/agent-traces/dist/types.d.ts +18 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mapConcurrent } from "./concurrency.js";
|
|
4
|
+
import { readHead } from "./head.js";
|
|
5
|
+
const MANIFEST_VERSION = 1;
|
|
6
|
+
const HOT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
7
|
+
const IO_CONCURRENCY = 32;
|
|
8
|
+
function isMissingFile(error) {
|
|
9
|
+
return (typeof error === "object" &&
|
|
10
|
+
error !== null &&
|
|
11
|
+
Object.prototype.hasOwnProperty.call(error, "code") &&
|
|
12
|
+
error.code === "ENOENT");
|
|
13
|
+
}
|
|
14
|
+
function shardFileName(directory) {
|
|
15
|
+
return `${createHash("sha256").update(directory).digest("hex").slice(0, 16)}.jsonl`;
|
|
16
|
+
}
|
|
17
|
+
function parseShardLines(contents) {
|
|
18
|
+
const records = [];
|
|
19
|
+
for (const line of contents.split("\n")) {
|
|
20
|
+
if (line.trim().length === 0) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(line);
|
|
25
|
+
if (typeof value === "object" &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
typeof value.path === "string" &&
|
|
28
|
+
typeof value.id === "string") {
|
|
29
|
+
records.push(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Corrupt shard lines are dropped; the next sync re-derives them.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return records;
|
|
37
|
+
}
|
|
38
|
+
function referenceFromRecord(record) {
|
|
39
|
+
return {
|
|
40
|
+
source: record.source,
|
|
41
|
+
id: record.id,
|
|
42
|
+
path: record.path,
|
|
43
|
+
...(record.cwd !== undefined ? { cwd: record.cwd } : {}),
|
|
44
|
+
...(record.updatedAtMs > 0 ? { updatedAt: new Date(record.updatedAtMs) } : {}),
|
|
45
|
+
...(record.title !== undefined ? { title: record.title } : {}),
|
|
46
|
+
...(record.metadata !== undefined ? { metadata: record.metadata } : {})
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function openTraceIndex(options) {
|
|
50
|
+
const { dir, fs } = options;
|
|
51
|
+
const manifestPath = path.join(dir, "manifest.json");
|
|
52
|
+
const shardsDir = path.join(dir, "shards");
|
|
53
|
+
const writeAtomic = async (filePath, data) => {
|
|
54
|
+
const rename = fs.rename;
|
|
55
|
+
if (typeof rename === "function") {
|
|
56
|
+
const tempPath = `${filePath}.tmp`;
|
|
57
|
+
await fs.writeFile(tempPath, data);
|
|
58
|
+
await rename.call(fs, tempPath, filePath);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await fs.writeFile(filePath, data);
|
|
62
|
+
};
|
|
63
|
+
const loadManifest = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
66
|
+
if (parsed.version === MANIFEST_VERSION && typeof parsed.shards === "object") {
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Missing or corrupt manifest: start empty and re-derive on sync.
|
|
72
|
+
}
|
|
73
|
+
return { version: MANIFEST_VERSION, shards: {} };
|
|
74
|
+
};
|
|
75
|
+
const loadShard = async (shard) => {
|
|
76
|
+
try {
|
|
77
|
+
return parseShardLines(await fs.readFile(path.join(shardsDir, shard.file), "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (isMissingFile(error)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const sync = async (syncOptions) => {
|
|
87
|
+
const now = syncOptions.now ?? Date.now;
|
|
88
|
+
const stats = {
|
|
89
|
+
scannedDirs: 0,
|
|
90
|
+
statted: 0,
|
|
91
|
+
headReads: 0,
|
|
92
|
+
added: 0,
|
|
93
|
+
updated: 0,
|
|
94
|
+
removed: 0
|
|
95
|
+
};
|
|
96
|
+
await fs.mkdir(shardsDir, { recursive: true });
|
|
97
|
+
const manifest = await loadManifest();
|
|
98
|
+
let manifestDirty = false;
|
|
99
|
+
const scannedSources = new Set();
|
|
100
|
+
const seenDirectories = new Set();
|
|
101
|
+
const hotCutoffMs = now() - HOT_WINDOW_MS;
|
|
102
|
+
for (const reader of syncOptions.readers) {
|
|
103
|
+
const scan = reader.scan?.bind(reader);
|
|
104
|
+
const readHeadMetadata = reader.readHeadMetadata?.bind(reader);
|
|
105
|
+
if (scan === undefined || readHeadMetadata === undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
scannedSources.add(reader.id);
|
|
109
|
+
for await (const { directory, files } of scan({ homeDir: syncOptions.homeDir, fs })) {
|
|
110
|
+
stats.scannedDirs += 1;
|
|
111
|
+
seenDirectories.add(directory);
|
|
112
|
+
const shardEntry = manifest.shards[directory];
|
|
113
|
+
const existing = shardEntry === undefined ? [] : await loadShard(shardEntry);
|
|
114
|
+
const byPath = new Map(existing.map((record) => [record.path, record]));
|
|
115
|
+
const fileSet = new Set(files);
|
|
116
|
+
const toStat = files.filter((filePath) => {
|
|
117
|
+
const record = byPath.get(filePath);
|
|
118
|
+
return record === undefined || record.updatedAtMs >= hotCutoffMs;
|
|
119
|
+
});
|
|
120
|
+
stats.statted += toStat.length;
|
|
121
|
+
const statResults = await mapConcurrent(toStat, IO_CONCURRENCY, async (filePath) => ({
|
|
122
|
+
filePath,
|
|
123
|
+
stat: await fs.stat(filePath).catch(() => undefined)
|
|
124
|
+
}));
|
|
125
|
+
const changed = statResults.filter(({ filePath, stat }) => {
|
|
126
|
+
if (stat === undefined || !stat.isFile()) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const record = byPath.get(filePath);
|
|
130
|
+
const mtimeMs = stat.mtime?.getTime() ?? 0;
|
|
131
|
+
const size = stat.size ?? -1;
|
|
132
|
+
return record === undefined || record.mtimeMs !== mtimeMs || record.size !== size;
|
|
133
|
+
});
|
|
134
|
+
const updatedRecords = await mapConcurrent(changed, IO_CONCURRENCY, async ({ filePath, stat }) => {
|
|
135
|
+
stats.headReads += 1;
|
|
136
|
+
const head = await readHead(fs, filePath).catch(() => undefined);
|
|
137
|
+
if (head === undefined) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const metadata = readHeadMetadata(head, filePath);
|
|
141
|
+
if (metadata === undefined) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const mtimeMs = stat?.mtime?.getTime() ?? 0;
|
|
145
|
+
return {
|
|
146
|
+
path: filePath,
|
|
147
|
+
source: reader.id,
|
|
148
|
+
id: metadata.id,
|
|
149
|
+
...(metadata.cwd !== undefined ? { cwd: metadata.cwd } : {}),
|
|
150
|
+
...(metadata.title !== undefined ? { title: metadata.title } : {}),
|
|
151
|
+
...(metadata.metadata !== undefined ? { metadata: metadata.metadata } : {}),
|
|
152
|
+
updatedAtMs: mtimeMs > 0 ? mtimeMs : (metadata.updatedAt?.getTime() ?? 0),
|
|
153
|
+
mtimeMs,
|
|
154
|
+
size: stat?.size ?? -1
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
const removedCount = existing.filter((record) => !fileSet.has(record.path)).length;
|
|
158
|
+
const freshRecords = updatedRecords.filter((record) => record !== undefined);
|
|
159
|
+
if (freshRecords.length === 0 && removedCount === 0) {
|
|
160
|
+
syncOptions.onProgress?.({ scannedDirs: stats.scannedDirs, headReads: stats.headReads });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const record of freshRecords) {
|
|
164
|
+
if (byPath.has(record.path)) {
|
|
165
|
+
stats.updated += 1;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
stats.added += 1;
|
|
169
|
+
}
|
|
170
|
+
byPath.set(record.path, record);
|
|
171
|
+
}
|
|
172
|
+
for (const record of existing) {
|
|
173
|
+
if (!fileSet.has(record.path)) {
|
|
174
|
+
byPath.delete(record.path);
|
|
175
|
+
stats.removed += 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const records = [...byPath.values()].sort((a, b) => b.updatedAtMs - a.updatedAtMs);
|
|
179
|
+
const file = shardEntry?.file ?? shardFileName(directory);
|
|
180
|
+
if (records.length === 0) {
|
|
181
|
+
delete manifest.shards[directory];
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
await writeAtomic(path.join(shardsDir, file), records.map((record) => JSON.stringify(record)).join("\n") + "\n");
|
|
185
|
+
manifest.shards[directory] = {
|
|
186
|
+
file,
|
|
187
|
+
source: reader.id,
|
|
188
|
+
maxUpdatedAtMs: records[0]?.updatedAtMs ?? 0,
|
|
189
|
+
count: records.length
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
manifestDirty = true;
|
|
193
|
+
syncOptions.onProgress?.({ scannedDirs: stats.scannedDirs, headReads: stats.headReads });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const [directory, shard] of Object.entries(manifest.shards)) {
|
|
197
|
+
if (scannedSources.has(shard.source) && !seenDirectories.has(directory)) {
|
|
198
|
+
stats.removed += shard.count;
|
|
199
|
+
delete manifest.shards[directory];
|
|
200
|
+
manifestDirty = true;
|
|
201
|
+
const unlink = fs.unlink;
|
|
202
|
+
if (typeof unlink === "function") {
|
|
203
|
+
await unlink.call(fs, path.join(shardsDir, shard.file)).catch(() => undefined);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (manifestDirty) {
|
|
208
|
+
await writeAtomic(manifestPath, JSON.stringify({ ...manifest, version: MANIFEST_VERSION }));
|
|
209
|
+
}
|
|
210
|
+
return stats;
|
|
211
|
+
};
|
|
212
|
+
const query = async (queryOptions) => {
|
|
213
|
+
const manifest = await loadManifest();
|
|
214
|
+
const sinceMs = queryOptions.since?.getTime();
|
|
215
|
+
const limit = queryOptions.limit;
|
|
216
|
+
const entries = Object.values(manifest.shards)
|
|
217
|
+
.filter((shard) => queryOptions.sources === undefined || queryOptions.sources.includes(shard.source))
|
|
218
|
+
.sort((a, b) => b.maxUpdatedAtMs - a.maxUpdatedAtMs);
|
|
219
|
+
let results = [];
|
|
220
|
+
for (const entry of entries) {
|
|
221
|
+
if (sinceMs !== undefined && entry.maxUpdatedAtMs < sinceMs) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (results.length >= limit) {
|
|
225
|
+
const cutoff = results[results.length - 1]?.updatedAtMs ?? 0;
|
|
226
|
+
if (entry.maxUpdatedAtMs <= cutoff) {
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const record of await loadShard(entry)) {
|
|
231
|
+
if (sinceMs !== undefined && record.updatedAtMs < sinceMs) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (queryOptions.allWorkspaces !== true &&
|
|
235
|
+
queryOptions.cwd !== undefined &&
|
|
236
|
+
record.cwd !== queryOptions.cwd) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
results.push(record);
|
|
240
|
+
}
|
|
241
|
+
results.sort((a, b) => b.updatedAtMs - a.updatedAtMs);
|
|
242
|
+
if (Number.isFinite(limit) && results.length > limit) {
|
|
243
|
+
results = results.slice(0, limit);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return results.map(referenceFromRecord);
|
|
247
|
+
};
|
|
248
|
+
const rebuild = async (syncOptions) => {
|
|
249
|
+
const unlink = fs.unlink;
|
|
250
|
+
if (typeof unlink === "function") {
|
|
251
|
+
const names = await fs.readdir(shardsDir).catch(() => []);
|
|
252
|
+
for (const name of names) {
|
|
253
|
+
await unlink.call(fs, path.join(shardsDir, name)).catch(() => undefined);
|
|
254
|
+
}
|
|
255
|
+
await unlink.call(fs, manifestPath).catch(() => undefined);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
await writeAtomic(manifestPath, JSON.stringify({ version: MANIFEST_VERSION, shards: {} }));
|
|
259
|
+
}
|
|
260
|
+
return sync(syncOptions);
|
|
261
|
+
};
|
|
262
|
+
return { sync, query, rebuild };
|
|
263
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { collectHumanPrompts, collectHumanPromptsFromReaders, collectHumanPromptsWithStats } from "./collect.js";
|
|
2
2
|
export { writeHumanPromptJsonl } from "./jsonl.js";
|
|
3
|
+
export { openTraceIndex } from "./index-store/store.js";
|
|
4
|
+
export type { TraceIndex, TraceIndexQueryOptions, TraceIndexSyncOptions, TraceIndexSyncStats } from "./index-store/store.js";
|
|
3
5
|
export { claudeTraceReader, codexTraceReader, piTraceReader, poeCodeTraceReader, traceReaders } from "./readers/index.js";
|
|
4
|
-
export type { AgentTraceFileSystem, AgentTraceSource, CollectHumanPromptsOptions, CollectHumanPromptsResult, HumanPromptRecord, NormalizedTrace, NormalizedTraceTurn, SqliteTraceDatabase, SqliteTraceDatabaseFactory, TraceDiscoverOptions, TraceReadOptions, TraceReader, TraceReference, TraceUsage } from "./types.js";
|
|
6
|
+
export type { AgentTraceFileSystem, AgentTraceSource, CollectHumanPromptsOptions, CollectHumanPromptsResult, HumanPromptRecord, NormalizedTrace, NormalizedTraceTurn, SqliteTraceDatabase, SqliteTraceDatabaseFactory, TraceDiscoverOptions, TraceHeadMetadata, TraceReadOptions, TraceReader, TraceReference, TraceScanDirectory, TraceScanOptions, TraceUsage } from "./types.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { collectHumanPrompts, collectHumanPromptsFromReaders, collectHumanPromptsWithStats } from "./collect.js";
|
|
2
2
|
export { writeHumanPromptJsonl } from "./jsonl.js";
|
|
3
|
+
export { openTraceIndex } from "./index-store/store.js";
|
|
3
4
|
export { claudeTraceReader, codexTraceReader, piTraceReader, poeCodeTraceReader, traceReaders } from "./readers/index.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { mapConcurrent } from "../index-store/concurrency.js";
|
|
3
|
+
import { readHead } from "../index-store/head.js";
|
|
2
4
|
import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
|
|
3
5
|
function isMissingFile(error) {
|
|
4
6
|
return (typeof error === "object" &&
|
|
@@ -547,6 +549,7 @@ function parseRecordLine(rawLine) {
|
|
|
547
549
|
return undefined;
|
|
548
550
|
}
|
|
549
551
|
}
|
|
552
|
+
const DISCOVER_CONCURRENCY = 32;
|
|
550
553
|
export const claudeTraceReader = {
|
|
551
554
|
id: "claude",
|
|
552
555
|
defaultRoots(homeDir) {
|
|
@@ -559,24 +562,40 @@ export const claudeTraceReader = {
|
|
|
559
562
|
: [path.join(projectsRoot, encodeClaudeProjectPath(options.cwd))];
|
|
560
563
|
const references = [];
|
|
561
564
|
for (const directory of directories) {
|
|
562
|
-
|
|
565
|
+
const files = await listJsonlFiles(options.fs, directory);
|
|
566
|
+
const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
|
|
563
567
|
const updatedAt = await fileUpdatedAt(options.fs, filePath);
|
|
564
568
|
if (options.since && updatedAt && updatedAt < options.since) {
|
|
565
|
-
|
|
569
|
+
return undefined;
|
|
566
570
|
}
|
|
567
|
-
const metadata = traceHeadMetadata(await options.fs
|
|
568
|
-
|
|
571
|
+
const metadata = traceHeadMetadata(await readHead(options.fs, filePath));
|
|
572
|
+
return {
|
|
569
573
|
source: "claude",
|
|
570
574
|
id: metadata.sessionId ?? fileId(filePath),
|
|
571
575
|
path: filePath,
|
|
572
576
|
...(metadata.cwd ? { cwd: metadata.cwd } : {}),
|
|
573
577
|
...(updatedAt ? { updatedAt } : {}),
|
|
574
578
|
...(metadata.title ? { title: metadata.title } : {})
|
|
575
|
-
}
|
|
576
|
-
}
|
|
579
|
+
};
|
|
580
|
+
});
|
|
581
|
+
references.push(...discovered.filter((reference) => reference !== undefined));
|
|
577
582
|
}
|
|
578
583
|
return references;
|
|
579
584
|
},
|
|
585
|
+
async *scan(options) {
|
|
586
|
+
const projectsRoot = path.join(options.homeDir, ".claude", "projects");
|
|
587
|
+
for (const directory of await listProjectDirectories(options.fs, projectsRoot)) {
|
|
588
|
+
yield { directory, files: await listJsonlFiles(options.fs, directory) };
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
readHeadMetadata(head, filePath) {
|
|
592
|
+
const metadata = traceHeadMetadata(head);
|
|
593
|
+
return {
|
|
594
|
+
id: metadata.sessionId ?? fileId(filePath),
|
|
595
|
+
...(metadata.cwd ? { cwd: metadata.cwd } : {}),
|
|
596
|
+
...(metadata.title ? { title: metadata.title } : {})
|
|
597
|
+
};
|
|
598
|
+
},
|
|
580
599
|
async read(reference, options) {
|
|
581
600
|
if (!reference.path) {
|
|
582
601
|
throw new Error(`Claude trace ${reference.id} has no path.`);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { mapConcurrent } from "../index-store/concurrency.js";
|
|
3
|
+
import { readHead } from "../index-store/head.js";
|
|
2
4
|
import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
|
|
3
5
|
function isMissingFile(error) {
|
|
4
6
|
return (typeof error === "object" &&
|
|
@@ -272,6 +274,44 @@ function readTraceContents(filePath, contents) {
|
|
|
272
274
|
turns
|
|
273
275
|
};
|
|
274
276
|
}
|
|
277
|
+
function headMetadataFromContents(head, filePath) {
|
|
278
|
+
let id = fileId(filePath);
|
|
279
|
+
let cwd;
|
|
280
|
+
let model;
|
|
281
|
+
let title;
|
|
282
|
+
const toolCalls = new Map();
|
|
283
|
+
for (const value of parseJsonLines(head)) {
|
|
284
|
+
const record = asRecord(value);
|
|
285
|
+
if (!record) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (record.type === "session") {
|
|
289
|
+
if (typeof record.id === "string") {
|
|
290
|
+
id = record.id;
|
|
291
|
+
}
|
|
292
|
+
if (typeof record.cwd === "string") {
|
|
293
|
+
cwd = record.cwd;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (record.type === "model_change" && typeof record.modelId === "string") {
|
|
297
|
+
model = record.modelId;
|
|
298
|
+
}
|
|
299
|
+
const message = asRecord(record.message);
|
|
300
|
+
if (message?.role === "assistant" && typeof message.model === "string") {
|
|
301
|
+
model = message.model;
|
|
302
|
+
}
|
|
303
|
+
if (title === undefined) {
|
|
304
|
+
title = turnsFromMessage(record, toolCalls).find((turn) => turn.role === "human")?.text;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
id,
|
|
309
|
+
...(cwd ? { cwd } : {}),
|
|
310
|
+
...(title ? { title } : {}),
|
|
311
|
+
...(model ? { metadata: { model } } : {})
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const DISCOVER_CONCURRENCY = 32;
|
|
275
315
|
export const piTraceReader = {
|
|
276
316
|
id: "pi",
|
|
277
317
|
defaultRoots(homeDir) {
|
|
@@ -284,26 +324,37 @@ export const piTraceReader = {
|
|
|
284
324
|
: [path.join(sessionsRoot, encodeWorkspacePath(options.cwd))];
|
|
285
325
|
const references = [];
|
|
286
326
|
for (const directory of directories) {
|
|
287
|
-
|
|
327
|
+
const files = await listJsonlFiles(options.fs, directory);
|
|
328
|
+
const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
|
|
288
329
|
const stat = await options.fs.stat(filePath);
|
|
289
330
|
const updatedAt = stat.mtime;
|
|
290
331
|
if (options.since && updatedAt && updatedAt < options.since) {
|
|
291
|
-
|
|
332
|
+
return undefined;
|
|
292
333
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
334
|
+
const metadata = headMetadataFromContents(await readHead(options.fs, filePath), filePath);
|
|
335
|
+
return {
|
|
295
336
|
source: "pi",
|
|
296
|
-
id:
|
|
337
|
+
id: metadata.id,
|
|
297
338
|
path: filePath,
|
|
298
|
-
...(
|
|
339
|
+
...(metadata.cwd ? { cwd: metadata.cwd } : {}),
|
|
299
340
|
...(updatedAt ? { updatedAt } : {}),
|
|
300
|
-
...(
|
|
301
|
-
...(
|
|
302
|
-
}
|
|
303
|
-
}
|
|
341
|
+
...(metadata.title ? { title: metadata.title } : {}),
|
|
342
|
+
...(metadata.metadata ? { metadata: metadata.metadata } : {})
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
references.push(...discovered.filter((reference) => reference !== undefined));
|
|
304
346
|
}
|
|
305
347
|
return references;
|
|
306
348
|
},
|
|
349
|
+
async *scan(options) {
|
|
350
|
+
const sessionsRoot = path.join(options.homeDir, ".pi", "agent", "sessions");
|
|
351
|
+
for (const directory of await listSessionDirectories(options.fs, sessionsRoot)) {
|
|
352
|
+
yield { directory, files: await listJsonlFiles(options.fs, directory) };
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
readHeadMetadata(head, filePath) {
|
|
356
|
+
return headMetadataFromContents(head, filePath);
|
|
357
|
+
},
|
|
307
358
|
async read(reference, options) {
|
|
308
359
|
if (!reference.path) {
|
|
309
360
|
throw new Error(`Pi trace ${reference.id} has no path.`);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { mapConcurrent } from "../index-store/concurrency.js";
|
|
3
|
+
import { readHead } from "../index-store/head.js";
|
|
2
4
|
import { asRecord, newestDate, parseDate, parseJsonLines } from "../line-json.js";
|
|
3
5
|
const HEAD_SCAN_MAX_LINES = 50;
|
|
4
6
|
const REDACTED_LOG_CONTENT = "[redacted]";
|
|
@@ -208,6 +210,7 @@ function turnFromRecord(record) {
|
|
|
208
210
|
return undefined;
|
|
209
211
|
}
|
|
210
212
|
}
|
|
213
|
+
const DISCOVER_CONCURRENCY = 32;
|
|
211
214
|
export const poeCodeTraceReader = {
|
|
212
215
|
id: "poe-code",
|
|
213
216
|
defaultRoots(homeDir) {
|
|
@@ -215,26 +218,41 @@ export const poeCodeTraceReader = {
|
|
|
215
218
|
},
|
|
216
219
|
async discover(options) {
|
|
217
220
|
const root = path.join(options.homeDir, ".poe-code", "spawn-logs");
|
|
218
|
-
const
|
|
219
|
-
|
|
221
|
+
const files = await listJsonlFiles(options.fs, root);
|
|
222
|
+
const discovered = await mapConcurrent(files, DISCOVER_CONCURRENCY, async (filePath) => {
|
|
220
223
|
const parsed = parseLogFileName(filePath);
|
|
221
224
|
if (!parsed) {
|
|
222
|
-
|
|
225
|
+
return undefined;
|
|
223
226
|
}
|
|
224
227
|
const updatedAt = await updatedAtForFile(options.fs, filePath, parsed.timestamp);
|
|
225
228
|
if (options.since && updatedAt && updatedAt < options.since) {
|
|
226
|
-
|
|
229
|
+
return undefined;
|
|
227
230
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
231
|
+
const head = await readHead(options.fs, filePath);
|
|
232
|
+
return {
|
|
230
233
|
source: "poe-code",
|
|
231
234
|
id: parsed.sessionId,
|
|
232
235
|
path: filePath,
|
|
233
236
|
...(updatedAt ? { updatedAt } : {}),
|
|
234
|
-
title: titleFromLogContent(
|
|
235
|
-
}
|
|
237
|
+
title: titleFromLogContent(head) ?? parsed.agent
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
return discovered.filter((reference) => reference !== undefined);
|
|
241
|
+
},
|
|
242
|
+
async *scan(options) {
|
|
243
|
+
const root = path.join(options.homeDir, ".poe-code", "spawn-logs");
|
|
244
|
+
yield { directory: root, files: await listJsonlFiles(options.fs, root) };
|
|
245
|
+
},
|
|
246
|
+
readHeadMetadata(head, filePath) {
|
|
247
|
+
const parsed = parseLogFileName(filePath);
|
|
248
|
+
if (!parsed) {
|
|
249
|
+
return undefined;
|
|
236
250
|
}
|
|
237
|
-
return
|
|
251
|
+
return {
|
|
252
|
+
id: parsed.sessionId,
|
|
253
|
+
title: titleFromLogContent(head) ?? parsed.agent,
|
|
254
|
+
...(parsed.timestamp ? { updatedAt: parsed.timestamp } : {})
|
|
255
|
+
};
|
|
238
256
|
},
|
|
239
257
|
async read(reference, options) {
|
|
240
258
|
if (!reference.path) {
|
|
@@ -89,14 +89,32 @@ export interface CollectHumanPromptsOptions {
|
|
|
89
89
|
allWorkspaces?: boolean;
|
|
90
90
|
fs?: AgentTraceFileSystem;
|
|
91
91
|
sqlite?: SqliteTraceDatabaseFactory;
|
|
92
|
+
indexDir?: string;
|
|
92
93
|
}
|
|
93
94
|
export interface CollectHumanPromptsResult {
|
|
94
95
|
records: HumanPromptRecord[];
|
|
95
96
|
traceCount: number;
|
|
96
97
|
}
|
|
98
|
+
export interface TraceScanOptions {
|
|
99
|
+
homeDir: string;
|
|
100
|
+
fs: AgentTraceFileSystem;
|
|
101
|
+
}
|
|
102
|
+
export interface TraceScanDirectory {
|
|
103
|
+
directory: string;
|
|
104
|
+
files: string[];
|
|
105
|
+
}
|
|
106
|
+
export interface TraceHeadMetadata {
|
|
107
|
+
id: string;
|
|
108
|
+
cwd?: string;
|
|
109
|
+
title?: string;
|
|
110
|
+
updatedAt?: Date;
|
|
111
|
+
metadata?: Record<string, unknown>;
|
|
112
|
+
}
|
|
97
113
|
export interface TraceReader {
|
|
98
114
|
id: AgentTraceSource;
|
|
99
115
|
defaultRoots(homeDir: string): string[];
|
|
100
116
|
discover(options: TraceDiscoverOptions): Promise<TraceReference[]>;
|
|
101
117
|
read(reference: TraceReference, options: TraceReadOptions): Promise<NormalizedTrace>;
|
|
118
|
+
scan?(options: TraceScanOptions): AsyncIterable<TraceScanDirectory>;
|
|
119
|
+
readHeadMetadata?(head: string, filePath: string): TraceHeadMetadata | undefined;
|
|
102
120
|
}
|