agmon 0.2.1 → 0.2.2
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +8 -8
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +16 -15
- package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -15
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +8 -7
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/media/route.js.nft.json +1 -1
- package/.next/server/app/api/reports/tokens/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/transcript/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/transcript/route.js.nft.json +1 -1
- package/.next/server/app/api/traces/stream/route.js +1 -1
- package/.next/server/app/api/traces/stream/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/file-content/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/chat/page.js.nft.json +1 -1
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/chat.html +1 -1
- package/.next/server/app/chat.rsc +4 -4
- package/.next/server/app/chat.segments/_full.segment.rsc +4 -4
- package/.next/server/app/chat.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +6 -5
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -6
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/reports/page.js.nft.json +1 -1
- package/.next/server/app/reports/page_client-reference-manifest.js +1 -1
- package/.next/server/app/reports.html +1 -1
- package/.next/server/app/reports.rsc +2 -2
- package/.next/server/app/reports.segments/_full.segment.rsc +2 -2
- package/.next/server/app/reports.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/reports.segments/reports/__PAGE__.segment.rsc +4 -3
- package/.next/server/app/store/page.js.nft.json +1 -1
- package/.next/server/app/store/page_client-reference-manifest.js +1 -1
- package/.next/server/app/store.html +1 -1
- package/.next/server/app/store.rsc +2 -2
- package/.next/server/app/store.segments/_full.segment.rsc +2 -2
- package/.next/server/app/store.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/store.segments/store/__PAGE__.segment.rsc +4 -3
- package/.next/server/chunks/[root-of-the-server]__0dw97_pahi8uf._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0dw97_pahi8uf._.js.map +1 -0
- package/.next/server/chunks/_0ao-_t4jkb46h._.js +2 -2
- package/.next/server/chunks/_0ao-_t4jkb46h._.js.map +1 -1
- package/.next/server/chunks/_1-8ipqazoyrrk._.js +1 -1
- package/.next/server/chunks/_1-8ipqazoyrrk._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1jwplflwix8wi._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1jwplflwix8wi._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1kt7h7bz59mdn._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1kt7h7bz59mdn._.js.map +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1otsvjy81s8my._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1otsvjy81s8my._.js.map +1 -1
- package/.next/server/chunks/ssr/{_0--937zhi743v._.js → _0zel-n0h--cnl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0jla9ugf1y6rb._.js → node_modules_01avo0bde3tpo._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0sjdrli5douui._.js → node_modules_01l0208e5kqic._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lqovp8j8ld76._.js → node_modules_033x29yqle_sj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_05i69rc9pwcdx._.js → node_modules_03wbg__aslvui._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ltys5mwtcv-n._.js → node_modules_08-bqkrz7sh0l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_09v49jhajmvl4._.js → node_modules_089w9jm7s2qa3._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_0z1e37smavebg._.js → node_modules_0cy47-s63xjzh._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_0dw0vozejrnml._.js → node_modules_0czh0jcnsm5e6._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_0e3njj436xau-._.js → node_modules_0fv321j1nslkb._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0d2ios01ot1in._.js → node_modules_0g-wb7h-hsdwc._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1l46or7mgko0z._.js → node_modules_0oz_zzlh8exwx._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0b8zi3pbl-y9n._.js → node_modules_0so2agzqd9p8l._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_1wew4nn7i1uup._.js → node_modules_0w2w61b8bh769._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_07bjxeq9dfgn1._.js → node_modules_0wd6szix_zku7._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_1aqaelh-8fme6._.js → node_modules_0wp7omf0xblv8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xh8tnq1niblb._.js → node_modules_10c51o2qv6mbi._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1d4sreyq0ubqg._.js → node_modules_19vqq7rvf-_ar._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_1e_nc2eyg4djz._.js → node_modules_1hu83gui7_nj2._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zbmc81s-4zsm._.js → node_modules_1j8hpoiygrs5e._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1pquu5wprusnc._.js → node_modules_1n4kig4mj6n4s._.js} +2 -2
- package/.next/server/chunks/ssr/{node_modules_1st9dwe66yx93._.js → node_modules_1s8oxeqa5_i3k._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01p9ctt-n2dxp._.js → node_modules_1xz1dstlo5ri-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_katex_dist_katex_mjs_0kqo9zl9cgwbo._.js → node_modules_katex_dist_katex_mjs_067omoj5szuys._.js} +1 -1
- package/.next/server/chunks/ssr/src_app_page_1vqfk0-j72fp6.js +1 -1
- package/.next/server/chunks/ssr/src_app_page_1vqfk0-j72fp6.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/0jo6fy2c_gmqn.js +1 -0
- package/.next/static/chunks/2q9cwykv6ox1t.css +1 -0
- package/.next/static/chunks/{3og0e9qcff393.js → 2zf44j8sjo0w0.js} +4 -4
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/package.json +1 -1
- package/src/app/api/traces/stream/route.js +41 -5
- package/src/app/layout.js +2 -1
- package/src/app/page.js +17 -1
- package/src/components/chat/SessionChatView.js +1 -1
- package/src/components/factory/office-canvas.js +32 -1
- package/src/components/layout/BottomPanel.jsx +6 -6
- package/src/components/layout/LeftSidebar.jsx +62 -4
- package/src/components/layout/StatusBar.jsx +1 -1
- package/src/components/layout/TitleBar.jsx +71 -1
- package/src/components/layout/VSCodeWorkbench.jsx +21 -0
- package/src/lib/mockTraces.js +13 -11
- package/src/lib/parsers/codexParser.js +373 -0
- package/src/lib/parsers/transcriptParser.js +35 -3
- package/src/lib/useFactoryTraces.js +70 -5
- package/src/lib/watchers/antigravityWatcher.js +15 -1
- package/src/lib/watchers/claudeWatcher.js +64 -53
- package/src/lib/watchers/codexWatcher.js +482 -0
- package/src/lib/watchers/watcherManager.js +46 -30
- package/.next/server/chunks/src_lib_watchers_antigravityWatcher_0w1-atg39y__p.js +0 -3
- package/.next/server/chunks/src_lib_watchers_antigravityWatcher_0w1-atg39y__p.js.map +0 -1
- package/.next/static/chunks/17jorrh_pfel_.js +0 -1
- package/.next/static/chunks/25l0z48vlvdu4.css +0 -1
- /package/.next/server/chunks/ssr/{_0--937zhi743v._.js.map → _0zel-n0h--cnl._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0jla9ugf1y6rb._.js.map → node_modules_01avo0bde3tpo._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0sjdrli5douui._.js.map → node_modules_01l0208e5kqic._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0lqovp8j8ld76._.js.map → node_modules_033x29yqle_sj._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_05i69rc9pwcdx._.js.map → node_modules_03wbg__aslvui._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1ltys5mwtcv-n._.js.map → node_modules_08-bqkrz7sh0l._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_09v49jhajmvl4._.js.map → node_modules_089w9jm7s2qa3._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0z1e37smavebg._.js.map → node_modules_0cy47-s63xjzh._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0dw0vozejrnml._.js.map → node_modules_0czh0jcnsm5e6._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0e3njj436xau-._.js.map → node_modules_0fv321j1nslkb._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0d2ios01ot1in._.js.map → node_modules_0g-wb7h-hsdwc._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0xh8tnq1niblb._.js.map → node_modules_0oz_zzlh8exwx._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_0b8zi3pbl-y9n._.js.map → node_modules_0so2agzqd9p8l._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1wew4nn7i1uup._.js.map → node_modules_0w2w61b8bh769._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_07bjxeq9dfgn1._.js.map → node_modules_0wd6szix_zku7._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1aqaelh-8fme6._.js.map → node_modules_0wp7omf0xblv8._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1d4sreyq0ubqg._.js.map → node_modules_10c51o2qv6mbi._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1l46or7mgko0z._.js.map → node_modules_19vqq7rvf-_ar._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1e_nc2eyg4djz._.js.map → node_modules_1hu83gui7_nj2._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1zbmc81s-4zsm._.js.map → node_modules_1j8hpoiygrs5e._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1pquu5wprusnc._.js.map → node_modules_1n4kig4mj6n4s._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_1st9dwe66yx93._.js.map → node_modules_1s8oxeqa5_i3k._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_01p9ctt-n2dxp._.js.map → node_modules_1xz1dstlo5ri-._.js.map} +0 -0
- /package/.next/server/chunks/ssr/{node_modules_katex_dist_katex_mjs_0kqo9zl9cgwbo._.js.map → node_modules_katex_dist_katex_mjs_067omoj5szuys._.js.map} +0 -0
- /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_buildManifest.js +0 -0
- /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_ssgManifest.js +0 -0
package/src/lib/mockTraces.js
CHANGED
|
@@ -13,19 +13,20 @@ export const MOCK_PRESETS = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const CLIENTS = [
|
|
16
|
-
{ account: "Claude Code · main", icon: "terminal" },
|
|
17
|
-
{ account: "Claude
|
|
18
|
-
{ account: "
|
|
19
|
-
{ account: "
|
|
20
|
-
{ account: "
|
|
21
|
-
{ account: "
|
|
22
|
-
{ account: "
|
|
23
|
-
{ account: "
|
|
16
|
+
{ account: "Claude Code · main", icon: "terminal", cli: "claude", clientType: "cli" },
|
|
17
|
+
{ account: "Claude Desktop", icon: "desktop_windows", cli: "claude", clientType: "desktop" },
|
|
18
|
+
{ account: "Codex CLI · tui", icon: "terminal", cli: "codex", clientType: "cli" },
|
|
19
|
+
{ account: "Codex Desktop", icon: "desktop_windows", cli: "codex", clientType: "desktop" },
|
|
20
|
+
{ account: "Antigravity · agent", icon: "smart_toy", cli: "antigravity", clientType: "cli" },
|
|
21
|
+
{ account: "Antigravity App", icon: "smart_toy", cli: "antigravity", clientType: "app" },
|
|
22
|
+
{ account: "Cursor · workspace", icon: "edit_note", cli: "cursor", clientType: "extension" },
|
|
23
|
+
{ account: "OpenCode · build", icon: "science", cli: "opencode", clientType: "cli" },
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
const PROVIDER_MODELS = [
|
|
27
27
|
{ provider: "claude", model: "claude-3-7-sonnet" },
|
|
28
28
|
{ provider: "gemini", model: "gemini-2.5-pro" },
|
|
29
|
+
{ provider: "openai (codex)", model: "gpt-5.6-terra" },
|
|
29
30
|
{ provider: "openai", model: "gpt-4o" },
|
|
30
31
|
{ provider: "minimax", model: "minimax-m2" },
|
|
31
32
|
{ provider: "deepseek", model: "deepseek-v3" },
|
|
@@ -750,9 +751,10 @@ export function generateMockTraces({ count = null, errorRatio = 0, seed = 42, no
|
|
|
750
751
|
}
|
|
751
752
|
|
|
752
753
|
traces.push({
|
|
753
|
-
traceId: `mock-${i}-${Math.floor(rand() * 1e6).toString(36)}`,
|
|
754
|
-
cli: "mock",
|
|
755
|
-
|
|
754
|
+
traceId: `mock-${client.cli || "agent"}-${i}-${Math.floor(rand() * 1e6).toString(36)}`,
|
|
755
|
+
cli: client.cli || "mock",
|
|
756
|
+
clientType: client.clientType || "cli",
|
|
757
|
+
connectionId: `${client.account} (${i})`,
|
|
756
758
|
account,
|
|
757
759
|
model: target.model,
|
|
758
760
|
provider: target.provider,
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { normalizeCodexTool, classifyCodexSession } from "../watchers/codexWatcher.js";
|
|
5
|
+
|
|
6
|
+
function compactText(value, maxLength = 160) {
|
|
7
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
8
|
+
return text ? text.slice(0, maxLength) : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function textFromContent(content) {
|
|
12
|
+
if (typeof content === "string") return content;
|
|
13
|
+
if (!Array.isArray(content)) return "";
|
|
14
|
+
return content
|
|
15
|
+
.map((block) => (typeof block?.text === "string" ? block.text : ""))
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join("\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isSyntheticInstruction(text) {
|
|
21
|
+
if (!text) return true;
|
|
22
|
+
const trimmed = text.trim();
|
|
23
|
+
return (
|
|
24
|
+
trimmed.startsWith("# AGENTS.md instructions") ||
|
|
25
|
+
trimmed.startsWith("<INSTRUCTIONS>") ||
|
|
26
|
+
trimmed.startsWith("<permissions instructions>") ||
|
|
27
|
+
trimmed.startsWith("<model_switch>") ||
|
|
28
|
+
trimmed.startsWith("<multi_agent_mode>") ||
|
|
29
|
+
trimmed.startsWith("<environment_context>") ||
|
|
30
|
+
trimmed.startsWith("<skills_instructions>")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Searches ~/.codex/sessions/ recursively for a rollout file matching the session ID or prefix.
|
|
36
|
+
*/
|
|
37
|
+
async function findCodexRolloutPath(safeId) {
|
|
38
|
+
const sessionsRoot = path.join(os.homedir(), ".codex", "sessions");
|
|
39
|
+
|
|
40
|
+
let years;
|
|
41
|
+
try {
|
|
42
|
+
years = await fs.readdir(sessionsRoot, { withFileTypes: true });
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sort years descending (newest first)
|
|
48
|
+
const yearDirs = years.filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
49
|
+
|
|
50
|
+
for (const year of yearDirs) {
|
|
51
|
+
const yearPath = path.join(sessionsRoot, year);
|
|
52
|
+
let months;
|
|
53
|
+
try {
|
|
54
|
+
months = await fs.readdir(yearPath, { withFileTypes: true });
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const monthDirs = months.filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
59
|
+
|
|
60
|
+
for (const month of monthDirs) {
|
|
61
|
+
const monthPath = path.join(yearPath, month);
|
|
62
|
+
let days;
|
|
63
|
+
try {
|
|
64
|
+
days = await fs.readdir(monthPath, { withFileTypes: true });
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const dayDirs = days.filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
69
|
+
|
|
70
|
+
for (const day of dayDirs) {
|
|
71
|
+
const dayPath = path.join(monthPath, day);
|
|
72
|
+
let files;
|
|
73
|
+
try {
|
|
74
|
+
files = await fs.readdir(dayPath);
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
81
|
+
if (file.includes(safeId)) {
|
|
82
|
+
return path.join(dayPath, file);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parses an OpenAI Codex session rollout into normalized turns
|
|
94
|
+
* compatible with AGMon Chat, Time-Machine Replay, and Diff Viewer.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} sessionId - Full or short session ID (e.g. "01a1199a-faf1..." or "01a1199a")
|
|
97
|
+
* @returns {Promise<{ ok: boolean, session: any, turns: any[] }>}
|
|
98
|
+
*/
|
|
99
|
+
export async function parseCodexTranscript(sessionId) {
|
|
100
|
+
const cleanId = String(sessionId || "").replace(/^codex-/, "").trim();
|
|
101
|
+
if (!cleanId || !/^[a-zA-Z0-9_-]{4,64}$/.test(cleanId)) {
|
|
102
|
+
throw new Error("Invalid Codex session ID format");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rolloutPath = await findCodexRolloutPath(cleanId);
|
|
106
|
+
if (!rolloutPath) {
|
|
107
|
+
throw new Error(`Codex transcript not found for session "${sessionId}"`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const raw = await fs.readFile(rolloutPath, "utf-8");
|
|
111
|
+
const lines = raw.split("\n");
|
|
112
|
+
|
|
113
|
+
let sessionMeta = null;
|
|
114
|
+
let model = "gpt-5.6-terra";
|
|
115
|
+
let cwd = process.cwd();
|
|
116
|
+
let originator = "codex-cli";
|
|
117
|
+
let source = "cli";
|
|
118
|
+
let startedAt = null;
|
|
119
|
+
let updatedAt = null;
|
|
120
|
+
|
|
121
|
+
let totalInput = 0;
|
|
122
|
+
let totalOutput = 0;
|
|
123
|
+
let totalCached = 0;
|
|
124
|
+
|
|
125
|
+
const turns = [];
|
|
126
|
+
let currentAssistantTurn = null;
|
|
127
|
+
|
|
128
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
129
|
+
const line = lines[idx].trim();
|
|
130
|
+
if (!line) continue;
|
|
131
|
+
|
|
132
|
+
let entry;
|
|
133
|
+
try {
|
|
134
|
+
entry = JSON.parse(line);
|
|
135
|
+
} catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const timestamp = entry.timestamp || new Date().toISOString();
|
|
140
|
+
if (!startedAt) startedAt = timestamp;
|
|
141
|
+
updatedAt = timestamp;
|
|
142
|
+
|
|
143
|
+
const payload = entry.payload;
|
|
144
|
+
if (!payload) continue;
|
|
145
|
+
|
|
146
|
+
if (entry.type === "session_meta") {
|
|
147
|
+
sessionMeta = payload;
|
|
148
|
+
if (payload.cwd) cwd = payload.cwd;
|
|
149
|
+
if (payload.originator) originator = payload.originator;
|
|
150
|
+
if (payload.source) source = payload.source;
|
|
151
|
+
if (payload.timestamp) startedAt = payload.timestamp;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (entry.type === "turn_context") {
|
|
156
|
+
if (payload.model) model = payload.model;
|
|
157
|
+
if (payload.cwd) cwd = payload.cwd;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (entry.type === "response_item") {
|
|
162
|
+
const type = payload.type;
|
|
163
|
+
|
|
164
|
+
// 1. Message: User or Assistant
|
|
165
|
+
if (type === "message") {
|
|
166
|
+
const role = payload.role;
|
|
167
|
+
|
|
168
|
+
if (role === "user") {
|
|
169
|
+
const rawText = textFromContent(payload.content);
|
|
170
|
+
if (isSyntheticInstruction(rawText)) continue;
|
|
171
|
+
|
|
172
|
+
// If there was an active assistant turn, finalize it before starting new turn
|
|
173
|
+
if (currentAssistantTurn) {
|
|
174
|
+
turns.push(currentAssistantTurn);
|
|
175
|
+
currentAssistantTurn = null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
turns.push({
|
|
179
|
+
id: `turn-user-${turns.length}`,
|
|
180
|
+
role: "user",
|
|
181
|
+
timestamp,
|
|
182
|
+
content: rawText.trim(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
currentAssistantTurn = {
|
|
186
|
+
id: `turn-asst-${turns.length}`,
|
|
187
|
+
role: "assistant",
|
|
188
|
+
timestamp,
|
|
189
|
+
content: "",
|
|
190
|
+
thinking: "",
|
|
191
|
+
toolCalls: [],
|
|
192
|
+
tokens: { input: 0, output: 0, cached: 0 },
|
|
193
|
+
status: "completed",
|
|
194
|
+
};
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (role === "assistant") {
|
|
199
|
+
if (!currentAssistantTurn) {
|
|
200
|
+
currentAssistantTurn = {
|
|
201
|
+
id: `turn-asst-${turns.length}`,
|
|
202
|
+
role: "assistant",
|
|
203
|
+
timestamp,
|
|
204
|
+
content: "",
|
|
205
|
+
thinking: "",
|
|
206
|
+
toolCalls: [],
|
|
207
|
+
tokens: { input: 0, output: 0, cached: 0 },
|
|
208
|
+
status: "completed",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const asstText = textFromContent(payload.content);
|
|
213
|
+
if (asstText) {
|
|
214
|
+
currentAssistantTurn.content += (currentAssistantTurn.content ? "\n\n" : "") + asstText.trim();
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 2. Reasoning / Thinking
|
|
223
|
+
if (type === "reasoning") {
|
|
224
|
+
if (!currentAssistantTurn) {
|
|
225
|
+
currentAssistantTurn = {
|
|
226
|
+
id: `turn-asst-${turns.length}`,
|
|
227
|
+
role: "assistant",
|
|
228
|
+
timestamp,
|
|
229
|
+
content: "",
|
|
230
|
+
thinking: "",
|
|
231
|
+
toolCalls: [],
|
|
232
|
+
tokens: { input: 0, output: 0, cached: 0 },
|
|
233
|
+
status: "completed",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let thinkingText = "";
|
|
238
|
+
if (Array.isArray(payload.summary) && payload.summary.length > 0) {
|
|
239
|
+
thinkingText = payload.summary.join("\n");
|
|
240
|
+
} else if (payload.content) {
|
|
241
|
+
thinkingText = typeof payload.content === "string" ? payload.content : JSON.stringify(payload.content);
|
|
242
|
+
} else if (payload.encrypted_content) {
|
|
243
|
+
thinkingText = "Thinking process completed.";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (thinkingText) {
|
|
247
|
+
currentAssistantTurn.thinking += (currentAssistantTurn.thinking ? "\n\n" : "") + thinkingText;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 3. Function Call / Tool Invocations
|
|
253
|
+
if (type === "function_call" || type === "local_shell_call" || type === "custom_tool_call") {
|
|
254
|
+
if (!currentAssistantTurn) {
|
|
255
|
+
currentAssistantTurn = {
|
|
256
|
+
id: `turn-asst-${turns.length}`,
|
|
257
|
+
role: "assistant",
|
|
258
|
+
timestamp,
|
|
259
|
+
content: "",
|
|
260
|
+
thinking: "",
|
|
261
|
+
toolCalls: [],
|
|
262
|
+
tokens: { input: 0, output: 0, cached: 0 },
|
|
263
|
+
status: "completed",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let parsedArgs = {};
|
|
268
|
+
if (typeof payload.arguments === "string") {
|
|
269
|
+
try {
|
|
270
|
+
parsedArgs = JSON.parse(payload.arguments);
|
|
271
|
+
} catch {
|
|
272
|
+
parsedArgs = { raw: payload.arguments };
|
|
273
|
+
}
|
|
274
|
+
} else if (payload.arguments && typeof payload.arguments === "object") {
|
|
275
|
+
parsedArgs = payload.arguments;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const toolName = payload.name || payload.tool || "tool";
|
|
279
|
+
const { type: toolType, detail } = normalizeCodexTool(toolName, parsedArgs);
|
|
280
|
+
const callId = payload.call_id || payload.id || `tool-${currentAssistantTurn.toolCalls.length}`;
|
|
281
|
+
|
|
282
|
+
currentAssistantTurn.toolCalls.push({
|
|
283
|
+
id: callId,
|
|
284
|
+
name: toolName,
|
|
285
|
+
type: toolType,
|
|
286
|
+
summary: detail || toolName,
|
|
287
|
+
action: toolName,
|
|
288
|
+
args: parsedArgs,
|
|
289
|
+
output: "",
|
|
290
|
+
status: "completed",
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 4. Function Call Output / Tool Result
|
|
296
|
+
if (type === "function_call_output") {
|
|
297
|
+
const callId = payload.call_id;
|
|
298
|
+
const outputVal = typeof payload.output === "string" ? payload.output : JSON.stringify(payload.output || "");
|
|
299
|
+
|
|
300
|
+
if (currentAssistantTurn) {
|
|
301
|
+
const match = currentAssistantTurn.toolCalls.find((t) => t.id === callId);
|
|
302
|
+
if (match) {
|
|
303
|
+
match.output = outputVal;
|
|
304
|
+
if (outputVal.includes("exited with code 1") || outputVal.includes("Error:") || outputVal.includes("failed")) {
|
|
305
|
+
match.status = "error";
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (entry.type === "event_msg") {
|
|
314
|
+
if (payload.type === "token_count") {
|
|
315
|
+
const usage = payload.info?.total_token_usage;
|
|
316
|
+
if (usage) {
|
|
317
|
+
if (usage.input_tokens) totalInput = usage.input_tokens;
|
|
318
|
+
if (usage.output_tokens) totalOutput = usage.output_tokens;
|
|
319
|
+
if (usage.cached_input_tokens) totalCached = usage.cached_input_tokens;
|
|
320
|
+
if (currentAssistantTurn) {
|
|
321
|
+
currentAssistantTurn.tokens = {
|
|
322
|
+
input: usage.input_tokens || 0,
|
|
323
|
+
output: usage.output_tokens || 0,
|
|
324
|
+
cached: usage.cached_input_tokens || 0,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (payload.type === "task_complete") {
|
|
332
|
+
if (payload.error?.message && currentAssistantTurn) {
|
|
333
|
+
currentAssistantTurn.status = "error";
|
|
334
|
+
currentAssistantTurn.error = payload.error.message;
|
|
335
|
+
if (!currentAssistantTurn.content) {
|
|
336
|
+
currentAssistantTurn.content = payload.error.message;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Push final assistant turn if present
|
|
344
|
+
if (currentAssistantTurn && (currentAssistantTurn.content || currentAssistantTurn.toolCalls.length > 0 || currentAssistantTurn.thinking)) {
|
|
345
|
+
turns.push(currentAssistantTurn);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const { clientType, label } = classifyCodexSession(originator, source);
|
|
349
|
+
const startMs = startedAt ? new Date(startedAt).getTime() : Date.now();
|
|
350
|
+
const endMs = updatedAt ? new Date(updatedAt).getTime() : startMs;
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
ok: true,
|
|
354
|
+
session: {
|
|
355
|
+
id: sessionMeta?.session_id || cleanId,
|
|
356
|
+
cli: "codex",
|
|
357
|
+
clientType,
|
|
358
|
+
label,
|
|
359
|
+
model,
|
|
360
|
+
startedAt,
|
|
361
|
+
updatedAt,
|
|
362
|
+
durationMs: Math.max(0, endMs - startMs),
|
|
363
|
+
cwd,
|
|
364
|
+
tokens: {
|
|
365
|
+
input: totalInput,
|
|
366
|
+
output: totalOutput,
|
|
367
|
+
cached: totalCached,
|
|
368
|
+
},
|
|
369
|
+
turnsCount: turns.length,
|
|
370
|
+
},
|
|
371
|
+
turns,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { parseAntigravityTranscript } from "./antigravityParser.js";
|
|
2
2
|
import { parseClaudeTranscript } from "./claudeParser.js";
|
|
3
|
+
import { parseCodexTranscript } from "./codexParser.js";
|
|
3
4
|
import { redactDeep } from "./secretRedactor.js";
|
|
4
5
|
|
|
5
6
|
function generateMockTranscript(rawId) {
|
|
6
7
|
const isClaude = rawId.includes("claude");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
8
|
+
const isCodex = rawId.includes("codex");
|
|
9
|
+
const model = isClaude ? "claude-3-7-sonnet" : isCodex ? "gpt-5.6-terra" : "gemini-2.5-pro";
|
|
10
|
+
const cli = isClaude ? "claude" : isCodex ? "codex" : "antigravity";
|
|
9
11
|
|
|
10
12
|
return {
|
|
11
13
|
ok: true,
|
|
@@ -117,7 +119,7 @@ export async function getSessionTranscript(rawId, cliHint = "") {
|
|
|
117
119
|
return generateMockTranscript(rawId);
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
// Strip prefixes like "agy-" or "
|
|
122
|
+
// Strip prefixes like "agy-", "claude-", or "codex-" if present
|
|
121
123
|
let cli = cliHint.toLowerCase();
|
|
122
124
|
if (id.startsWith("agy-")) {
|
|
123
125
|
cli = "antigravity";
|
|
@@ -125,6 +127,22 @@ export async function getSessionTranscript(rawId, cliHint = "") {
|
|
|
125
127
|
} else if (id.startsWith("claude-")) {
|
|
126
128
|
cli = "claude";
|
|
127
129
|
id = id.slice(7);
|
|
130
|
+
} else if (id.startsWith("codex-")) {
|
|
131
|
+
cli = "codex";
|
|
132
|
+
id = id.slice(6);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (cli === "codex") {
|
|
136
|
+
try {
|
|
137
|
+
const res = await parseCodexTranscript(id);
|
|
138
|
+
return {
|
|
139
|
+
...res,
|
|
140
|
+
session: redactDeep(res.session),
|
|
141
|
+
turns: redactDeep(res.turns),
|
|
142
|
+
};
|
|
143
|
+
} catch (err) {
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
// If ID was formatted as short hash in traceId, try to resolve full directory in brain
|
|
@@ -158,5 +176,19 @@ export async function getSessionTranscript(rawId, cliHint = "") {
|
|
|
158
176
|
}
|
|
159
177
|
}
|
|
160
178
|
|
|
179
|
+
// Fallback to codex if no cli specified
|
|
180
|
+
if (!cli) {
|
|
181
|
+
try {
|
|
182
|
+
const res = await parseCodexTranscript(id);
|
|
183
|
+
return {
|
|
184
|
+
...res,
|
|
185
|
+
session: redactDeep(res.session),
|
|
186
|
+
turns: redactDeep(res.turns),
|
|
187
|
+
};
|
|
188
|
+
} catch {
|
|
189
|
+
// ignore
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
161
193
|
throw new Error(`Unable to locate transcript for session ID "${rawId}"`);
|
|
162
194
|
}
|
|
@@ -5,7 +5,57 @@ import { generateMockTraces, mockProviderDescriptors, MOCK_PRESETS } from "./moc
|
|
|
5
5
|
import { playTick, playComplete, playAlarm, playNeedInput } from "./soundFx";
|
|
6
6
|
import { notifyAgentDone, notifyAgentAlert } from "./notifications";
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export const TIMEFRAME_HOURS_MAP = {
|
|
9
|
+
"1h": 1,
|
|
10
|
+
"6h": 6,
|
|
11
|
+
"24h": 24,
|
|
12
|
+
"3d": 72,
|
|
13
|
+
"7d": 168,
|
|
14
|
+
"all": 0,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function useFactoryTraces({
|
|
18
|
+
mode = "live",
|
|
19
|
+
preset = "cases",
|
|
20
|
+
initialTop = 20,
|
|
21
|
+
initialTimeframe = "24h",
|
|
22
|
+
} = {}) {
|
|
23
|
+
const [top, setTop] = useState(() => {
|
|
24
|
+
if (typeof window !== "undefined") {
|
|
25
|
+
try {
|
|
26
|
+
const saved = localStorage.getItem("agmon_filter_settings");
|
|
27
|
+
if (saved) {
|
|
28
|
+
const parsed = JSON.parse(saved);
|
|
29
|
+
if (parsed.top !== undefined) return parsed.top;
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
return initialTop;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [timeframe, setTimeframe] = useState(() => {
|
|
37
|
+
if (typeof window !== "undefined") {
|
|
38
|
+
try {
|
|
39
|
+
const saved = localStorage.getItem("agmon_filter_settings");
|
|
40
|
+
if (saved) {
|
|
41
|
+
const parsed = JSON.parse(saved);
|
|
42
|
+
if (parsed.timeframe) return parsed.timeframe;
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
return initialTimeframe;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const [totalCount, setTotalCount] = useState(0);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (typeof window !== "undefined") {
|
|
53
|
+
try {
|
|
54
|
+
localStorage.setItem("agmon_filter_settings", JSON.stringify({ top, timeframe }));
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}, [top, timeframe]);
|
|
58
|
+
|
|
9
59
|
const [traces, setTraces] = useState([]);
|
|
10
60
|
const [providers, setProviders] = useState([]);
|
|
11
61
|
const [connected, setConnected] = useState(false);
|
|
@@ -88,8 +138,11 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
88
138
|
|
|
89
139
|
const cfg = MOCK_PRESETS[preset] || MOCK_PRESETS.cases;
|
|
90
140
|
const updateMock = () => {
|
|
91
|
-
const
|
|
141
|
+
const allMock = generateMockTraces({ count: cfg.agents, cases: !!cfg.cases, errorRatio: cfg.errorRatio, preset });
|
|
142
|
+
const limit = top === "all" ? 0 : Number(top);
|
|
143
|
+
const next = (limit > 0 && limit < allMock.length) ? allMock.slice(0, limit) : allMock;
|
|
92
144
|
setTraces(next);
|
|
145
|
+
setTotalCount(allMock.length);
|
|
93
146
|
triggerSoundEffects(next);
|
|
94
147
|
setProviders(mockProviderDescriptors(next));
|
|
95
148
|
setConnected(true);
|
|
@@ -99,7 +152,7 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
99
152
|
updateMock();
|
|
100
153
|
const interval = setInterval(updateMock, 3000);
|
|
101
154
|
return () => clearInterval(interval);
|
|
102
|
-
}, [mode, preset, connectionEpoch]);
|
|
155
|
+
}, [mode, preset, top, timeframe, connectionEpoch, triggerSoundEffects]);
|
|
103
156
|
|
|
104
157
|
// 2. Live Mode (SSE + Wake-up Watchdog)
|
|
105
158
|
useEffect(() => {
|
|
@@ -119,7 +172,9 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
119
172
|
};
|
|
120
173
|
|
|
121
174
|
try {
|
|
122
|
-
const
|
|
175
|
+
const hours = TIMEFRAME_HOURS_MAP[timeframe] ?? 24;
|
|
176
|
+
const limit = top === "all" ? 0 : Number(top);
|
|
177
|
+
const es = new EventSource(`/api/traces/stream?source=live&hours=${hours}&limit=${limit}`);
|
|
123
178
|
eventSourceRef.current = es;
|
|
124
179
|
|
|
125
180
|
es.onopen = () => {
|
|
@@ -137,6 +192,11 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
137
192
|
setTraces(payload.traces);
|
|
138
193
|
triggerSoundEffects(payload.traces);
|
|
139
194
|
}
|
|
195
|
+
if (typeof payload.totalCount === "number") {
|
|
196
|
+
setTotalCount(payload.totalCount);
|
|
197
|
+
} else if (Array.isArray(payload.traces)) {
|
|
198
|
+
setTotalCount(payload.traces.length);
|
|
199
|
+
}
|
|
140
200
|
if (Array.isArray(payload.providers)) {
|
|
141
201
|
setProviders(payload.providers);
|
|
142
202
|
}
|
|
@@ -164,7 +224,7 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
164
224
|
eventSourceRef.current = null;
|
|
165
225
|
}
|
|
166
226
|
};
|
|
167
|
-
}, [mode, connectionEpoch]);
|
|
227
|
+
}, [mode, timeframe, top, connectionEpoch, triggerSoundEffects]);
|
|
168
228
|
|
|
169
229
|
// 3. Auto-reconnect when laptop wakes up or tab gains focus (debounced & safe)
|
|
170
230
|
useEffect(() => {
|
|
@@ -201,6 +261,11 @@ export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
|
201
261
|
|
|
202
262
|
return {
|
|
203
263
|
traces,
|
|
264
|
+
totalCount,
|
|
265
|
+
top,
|
|
266
|
+
setTop,
|
|
267
|
+
timeframe,
|
|
268
|
+
setTimeframe,
|
|
204
269
|
providers,
|
|
205
270
|
connected,
|
|
206
271
|
live: mode === "live",
|
|
@@ -183,6 +183,8 @@ export async function getAntigravityTraces(maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
|
183
183
|
const traces = [];
|
|
184
184
|
const seenConvIds = new Set();
|
|
185
185
|
|
|
186
|
+
const candidates = [];
|
|
187
|
+
|
|
186
188
|
for (const { dir: brainDir, source } of searchDirs) {
|
|
187
189
|
let entries;
|
|
188
190
|
try {
|
|
@@ -195,13 +197,26 @@ export async function getAntigravityTraces(maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
|
195
197
|
if (!ent.isDirectory()) continue;
|
|
196
198
|
const convId = ent.name;
|
|
197
199
|
if (seenConvIds.has(convId)) continue;
|
|
200
|
+
seenConvIds.add(convId);
|
|
198
201
|
const transcriptPath = path.join(brainDir, convId, ".system_generated", "logs", "transcript.jsonl");
|
|
199
202
|
|
|
200
203
|
try {
|
|
201
204
|
const stat = await fs.stat(transcriptPath);
|
|
202
205
|
const mtime = stat.mtimeMs;
|
|
203
206
|
if (now - mtime > maxAgeMs) continue;
|
|
207
|
+
candidates.push({ transcriptPath, mtime, convId, source, stat });
|
|
208
|
+
} catch {}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
204
211
|
|
|
212
|
+
// Sort candidate transcripts by mtime descending (most recent first)
|
|
213
|
+
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
214
|
+
|
|
215
|
+
// Limit parsing to the most recent 100 sessions to avoid unbounded disk I/O
|
|
216
|
+
const toProcess = candidates.slice(0, 100);
|
|
217
|
+
|
|
218
|
+
for (const { transcriptPath, mtime, convId, source, stat } of toProcess) {
|
|
219
|
+
try {
|
|
205
220
|
const content = await fs.readFile(transcriptPath, "utf-8");
|
|
206
221
|
const lines = content.trim().split("\n");
|
|
207
222
|
if (lines.length === 0) continue;
|
|
@@ -383,7 +398,6 @@ export async function getAntigravityTraces(maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
|
383
398
|
// file missing or unreadable
|
|
384
399
|
}
|
|
385
400
|
}
|
|
386
|
-
}
|
|
387
401
|
|
|
388
402
|
// Sort: active (streaming/pending) first, then by most recent startedAt
|
|
389
403
|
traces.sort((a, b) => {
|