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.
Files changed (152) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/fallback-build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +8 -8
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +16 -15
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -15
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +8 -7
  16. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  17. package/.next/server/app/api/media/route.js.nft.json +1 -1
  18. package/.next/server/app/api/reports/tokens/route.js.nft.json +1 -1
  19. package/.next/server/app/api/sessions/[id]/transcript/route.js +1 -1
  20. package/.next/server/app/api/sessions/[id]/transcript/route.js.nft.json +1 -1
  21. package/.next/server/app/api/traces/stream/route.js +1 -1
  22. package/.next/server/app/api/traces/stream/route.js.nft.json +1 -1
  23. package/.next/server/app/api/version/route.js.nft.json +1 -1
  24. package/.next/server/app/api/workspace/file-content/route.js.nft.json +1 -1
  25. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  26. package/.next/server/app/chat/page.js.nft.json +1 -1
  27. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  28. package/.next/server/app/chat.html +1 -1
  29. package/.next/server/app/chat.rsc +4 -4
  30. package/.next/server/app/chat.segments/_full.segment.rsc +4 -4
  31. package/.next/server/app/chat.segments/_tree.segment.rsc +2 -2
  32. package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +6 -5
  33. package/.next/server/app/index.html +1 -1
  34. package/.next/server/app/index.rsc +5 -5
  35. package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -6
  36. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/server/app/reports/page.js.nft.json +1 -1
  41. package/.next/server/app/reports/page_client-reference-manifest.js +1 -1
  42. package/.next/server/app/reports.html +1 -1
  43. package/.next/server/app/reports.rsc +2 -2
  44. package/.next/server/app/reports.segments/_full.segment.rsc +2 -2
  45. package/.next/server/app/reports.segments/_tree.segment.rsc +2 -2
  46. package/.next/server/app/reports.segments/reports/__PAGE__.segment.rsc +4 -3
  47. package/.next/server/app/store/page.js.nft.json +1 -1
  48. package/.next/server/app/store/page_client-reference-manifest.js +1 -1
  49. package/.next/server/app/store.html +1 -1
  50. package/.next/server/app/store.rsc +2 -2
  51. package/.next/server/app/store.segments/_full.segment.rsc +2 -2
  52. package/.next/server/app/store.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/store.segments/store/__PAGE__.segment.rsc +4 -3
  54. package/.next/server/chunks/[root-of-the-server]__0dw97_pahi8uf._.js +3 -0
  55. package/.next/server/chunks/[root-of-the-server]__0dw97_pahi8uf._.js.map +1 -0
  56. package/.next/server/chunks/_0ao-_t4jkb46h._.js +2 -2
  57. package/.next/server/chunks/_0ao-_t4jkb46h._.js.map +1 -1
  58. package/.next/server/chunks/_1-8ipqazoyrrk._.js +1 -1
  59. package/.next/server/chunks/_1-8ipqazoyrrk._.js.map +1 -1
  60. package/.next/server/chunks/ssr/[root-of-the-server]__1jwplflwix8wi._.js +1 -1
  61. package/.next/server/chunks/ssr/[root-of-the-server]__1jwplflwix8wi._.js.map +1 -1
  62. package/.next/server/chunks/ssr/[root-of-the-server]__1kt7h7bz59mdn._.js +1 -1
  63. package/.next/server/chunks/ssr/[root-of-the-server]__1kt7h7bz59mdn._.js.map +1 -1
  64. package/.next/server/chunks/ssr/[root-of-the-server]__1otsvjy81s8my._.js +1 -1
  65. package/.next/server/chunks/ssr/[root-of-the-server]__1otsvjy81s8my._.js.map +1 -1
  66. package/.next/server/chunks/ssr/{_0--937zhi743v._.js → _0zel-n0h--cnl._.js} +1 -1
  67. package/.next/server/chunks/ssr/{node_modules_0jla9ugf1y6rb._.js → node_modules_01avo0bde3tpo._.js} +1 -1
  68. package/.next/server/chunks/ssr/{node_modules_0sjdrli5douui._.js → node_modules_01l0208e5kqic._.js} +1 -1
  69. package/.next/server/chunks/ssr/{node_modules_0lqovp8j8ld76._.js → node_modules_033x29yqle_sj._.js} +1 -1
  70. package/.next/server/chunks/ssr/{node_modules_05i69rc9pwcdx._.js → node_modules_03wbg__aslvui._.js} +1 -1
  71. package/.next/server/chunks/ssr/{node_modules_1ltys5mwtcv-n._.js → node_modules_08-bqkrz7sh0l._.js} +1 -1
  72. package/.next/server/chunks/ssr/{node_modules_09v49jhajmvl4._.js → node_modules_089w9jm7s2qa3._.js} +2 -2
  73. package/.next/server/chunks/ssr/{node_modules_0z1e37smavebg._.js → node_modules_0cy47-s63xjzh._.js} +2 -2
  74. package/.next/server/chunks/ssr/{node_modules_0dw0vozejrnml._.js → node_modules_0czh0jcnsm5e6._.js} +2 -2
  75. package/.next/server/chunks/ssr/{node_modules_0e3njj436xau-._.js → node_modules_0fv321j1nslkb._.js} +1 -1
  76. package/.next/server/chunks/ssr/{node_modules_0d2ios01ot1in._.js → node_modules_0g-wb7h-hsdwc._.js} +1 -1
  77. package/.next/server/chunks/ssr/{node_modules_1l46or7mgko0z._.js → node_modules_0oz_zzlh8exwx._.js} +1 -1
  78. package/.next/server/chunks/ssr/{node_modules_0b8zi3pbl-y9n._.js → node_modules_0so2agzqd9p8l._.js} +2 -2
  79. package/.next/server/chunks/ssr/{node_modules_1wew4nn7i1uup._.js → node_modules_0w2w61b8bh769._.js} +1 -1
  80. package/.next/server/chunks/ssr/{node_modules_07bjxeq9dfgn1._.js → node_modules_0wd6szix_zku7._.js} +2 -2
  81. package/.next/server/chunks/ssr/{node_modules_1aqaelh-8fme6._.js → node_modules_0wp7omf0xblv8._.js} +1 -1
  82. package/.next/server/chunks/ssr/{node_modules_0xh8tnq1niblb._.js → node_modules_10c51o2qv6mbi._.js} +1 -1
  83. package/.next/server/chunks/ssr/{node_modules_1d4sreyq0ubqg._.js → node_modules_19vqq7rvf-_ar._.js} +2 -2
  84. package/.next/server/chunks/ssr/{node_modules_1e_nc2eyg4djz._.js → node_modules_1hu83gui7_nj2._.js} +1 -1
  85. package/.next/server/chunks/ssr/{node_modules_1zbmc81s-4zsm._.js → node_modules_1j8hpoiygrs5e._.js} +1 -1
  86. package/.next/server/chunks/ssr/{node_modules_1pquu5wprusnc._.js → node_modules_1n4kig4mj6n4s._.js} +2 -2
  87. package/.next/server/chunks/ssr/{node_modules_1st9dwe66yx93._.js → node_modules_1s8oxeqa5_i3k._.js} +1 -1
  88. package/.next/server/chunks/ssr/{node_modules_01p9ctt-n2dxp._.js → node_modules_1xz1dstlo5ri-._.js} +1 -1
  89. package/.next/server/chunks/ssr/{node_modules_katex_dist_katex_mjs_0kqo9zl9cgwbo._.js → node_modules_katex_dist_katex_mjs_067omoj5szuys._.js} +1 -1
  90. package/.next/server/chunks/ssr/src_app_page_1vqfk0-j72fp6.js +1 -1
  91. package/.next/server/chunks/ssr/src_app_page_1vqfk0-j72fp6.js.map +1 -1
  92. package/.next/server/middleware-build-manifest.js +3 -3
  93. package/.next/server/pages/404.html +1 -1
  94. package/.next/server/pages/500.html +1 -1
  95. package/.next/server/preview-props.json +3 -3
  96. package/.next/server/server-reference-manifest.js +1 -1
  97. package/.next/server/server-reference-manifest.json +1 -1
  98. package/.next/static/chunks/0jo6fy2c_gmqn.js +1 -0
  99. package/.next/static/chunks/2q9cwykv6ox1t.css +1 -0
  100. package/.next/static/chunks/{3og0e9qcff393.js → 2zf44j8sjo0w0.js} +4 -4
  101. package/.next/trace +1 -1
  102. package/.next/trace-build +1 -1
  103. package/package.json +1 -1
  104. package/src/app/api/traces/stream/route.js +41 -5
  105. package/src/app/layout.js +2 -1
  106. package/src/app/page.js +17 -1
  107. package/src/components/chat/SessionChatView.js +1 -1
  108. package/src/components/factory/office-canvas.js +32 -1
  109. package/src/components/layout/BottomPanel.jsx +6 -6
  110. package/src/components/layout/LeftSidebar.jsx +62 -4
  111. package/src/components/layout/StatusBar.jsx +1 -1
  112. package/src/components/layout/TitleBar.jsx +71 -1
  113. package/src/components/layout/VSCodeWorkbench.jsx +21 -0
  114. package/src/lib/mockTraces.js +13 -11
  115. package/src/lib/parsers/codexParser.js +373 -0
  116. package/src/lib/parsers/transcriptParser.js +35 -3
  117. package/src/lib/useFactoryTraces.js +70 -5
  118. package/src/lib/watchers/antigravityWatcher.js +15 -1
  119. package/src/lib/watchers/claudeWatcher.js +64 -53
  120. package/src/lib/watchers/codexWatcher.js +482 -0
  121. package/src/lib/watchers/watcherManager.js +46 -30
  122. package/.next/server/chunks/src_lib_watchers_antigravityWatcher_0w1-atg39y__p.js +0 -3
  123. package/.next/server/chunks/src_lib_watchers_antigravityWatcher_0w1-atg39y__p.js.map +0 -1
  124. package/.next/static/chunks/17jorrh_pfel_.js +0 -1
  125. package/.next/static/chunks/25l0z48vlvdu4.css +0 -1
  126. /package/.next/server/chunks/ssr/{_0--937zhi743v._.js.map → _0zel-n0h--cnl._.js.map} +0 -0
  127. /package/.next/server/chunks/ssr/{node_modules_0jla9ugf1y6rb._.js.map → node_modules_01avo0bde3tpo._.js.map} +0 -0
  128. /package/.next/server/chunks/ssr/{node_modules_0sjdrli5douui._.js.map → node_modules_01l0208e5kqic._.js.map} +0 -0
  129. /package/.next/server/chunks/ssr/{node_modules_0lqovp8j8ld76._.js.map → node_modules_033x29yqle_sj._.js.map} +0 -0
  130. /package/.next/server/chunks/ssr/{node_modules_05i69rc9pwcdx._.js.map → node_modules_03wbg__aslvui._.js.map} +0 -0
  131. /package/.next/server/chunks/ssr/{node_modules_1ltys5mwtcv-n._.js.map → node_modules_08-bqkrz7sh0l._.js.map} +0 -0
  132. /package/.next/server/chunks/ssr/{node_modules_09v49jhajmvl4._.js.map → node_modules_089w9jm7s2qa3._.js.map} +0 -0
  133. /package/.next/server/chunks/ssr/{node_modules_0z1e37smavebg._.js.map → node_modules_0cy47-s63xjzh._.js.map} +0 -0
  134. /package/.next/server/chunks/ssr/{node_modules_0dw0vozejrnml._.js.map → node_modules_0czh0jcnsm5e6._.js.map} +0 -0
  135. /package/.next/server/chunks/ssr/{node_modules_0e3njj436xau-._.js.map → node_modules_0fv321j1nslkb._.js.map} +0 -0
  136. /package/.next/server/chunks/ssr/{node_modules_0d2ios01ot1in._.js.map → node_modules_0g-wb7h-hsdwc._.js.map} +0 -0
  137. /package/.next/server/chunks/ssr/{node_modules_0xh8tnq1niblb._.js.map → node_modules_0oz_zzlh8exwx._.js.map} +0 -0
  138. /package/.next/server/chunks/ssr/{node_modules_0b8zi3pbl-y9n._.js.map → node_modules_0so2agzqd9p8l._.js.map} +0 -0
  139. /package/.next/server/chunks/ssr/{node_modules_1wew4nn7i1uup._.js.map → node_modules_0w2w61b8bh769._.js.map} +0 -0
  140. /package/.next/server/chunks/ssr/{node_modules_07bjxeq9dfgn1._.js.map → node_modules_0wd6szix_zku7._.js.map} +0 -0
  141. /package/.next/server/chunks/ssr/{node_modules_1aqaelh-8fme6._.js.map → node_modules_0wp7omf0xblv8._.js.map} +0 -0
  142. /package/.next/server/chunks/ssr/{node_modules_1d4sreyq0ubqg._.js.map → node_modules_10c51o2qv6mbi._.js.map} +0 -0
  143. /package/.next/server/chunks/ssr/{node_modules_1l46or7mgko0z._.js.map → node_modules_19vqq7rvf-_ar._.js.map} +0 -0
  144. /package/.next/server/chunks/ssr/{node_modules_1e_nc2eyg4djz._.js.map → node_modules_1hu83gui7_nj2._.js.map} +0 -0
  145. /package/.next/server/chunks/ssr/{node_modules_1zbmc81s-4zsm._.js.map → node_modules_1j8hpoiygrs5e._.js.map} +0 -0
  146. /package/.next/server/chunks/ssr/{node_modules_1pquu5wprusnc._.js.map → node_modules_1n4kig4mj6n4s._.js.map} +0 -0
  147. /package/.next/server/chunks/ssr/{node_modules_1st9dwe66yx93._.js.map → node_modules_1s8oxeqa5_i3k._.js.map} +0 -0
  148. /package/.next/server/chunks/ssr/{node_modules_01p9ctt-n2dxp._.js.map → node_modules_1xz1dstlo5ri-._.js.map} +0 -0
  149. /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
  150. /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_buildManifest.js +0 -0
  151. /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_clientMiddlewareManifest.js +0 -0
  152. /package/.next/static/{1y6Da0YgnPr2_6ncpWrbV → dGN2VKPk5TJgezxS3JvRc}/_ssgManifest.js +0 -0
@@ -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 Code · review", icon: "rate_review" },
18
- { account: "Antigravity · agent", icon: "smart_toy" },
19
- { account: "Antigravity · dev", icon: "construction" },
20
- { account: "Cursor · workspace", icon: "edit_note" },
21
- { account: "OpenCode · build", icon: "science" },
22
- { account: "Aider", icon: "smart_toy" },
23
- { account: "Continue · dev", icon: "play_arrow" },
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
- connectionId,
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 model = isClaude ? "claude-3-7-sonnet" : "gemini-2.5-pro";
8
- const cli = isClaude ? "claude" : "antigravity";
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 "claude-" if present
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 function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
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 next = generateMockTraces({ count: cfg.agents, cases: !!cfg.cases, errorRatio: cfg.errorRatio, preset });
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 es = new EventSource("/api/traces/stream?source=live");
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) => {