mixdog 0.9.0 → 0.9.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 (240) hide show
  1. package/package.json +10 -3
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/session-ingest-smoke.mjs +2 -2
  23. package/scripts/task-bench.mjs +207 -0
  24. package/scripts/tool-failures.mjs +6 -6
  25. package/scripts/tool-smoke.mjs +306 -66
  26. package/scripts/toolcall-args-test.mjs +81 -0
  27. package/src/agents/debugger/AGENT.md +4 -4
  28. package/src/agents/heavy-worker/AGENT.md +4 -2
  29. package/src/agents/reviewer/AGENT.md +4 -4
  30. package/src/agents/worker/AGENT.md +4 -2
  31. package/src/app.mjs +10 -6
  32. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  33. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  34. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  35. package/src/headless-role.mjs +14 -14
  36. package/src/help.mjs +1 -0
  37. package/src/lib/mixdog-debug.cjs +0 -22
  38. package/src/lib/plugin-paths.cjs +1 -7
  39. package/src/lib/rules-builder.cjs +34 -56
  40. package/src/mixdog-session-runtime.mjs +710 -319
  41. package/src/output-styles/default.md +12 -7
  42. package/src/output-styles/minimal.md +25 -0
  43. package/src/output-styles/oneline.md +21 -0
  44. package/src/output-styles/simple.md +10 -9
  45. package/src/repl.mjs +12 -4
  46. package/src/rules/agent/00-common.md +7 -5
  47. package/src/rules/agent/30-explorer.md +7 -8
  48. package/src/rules/lead/01-general.md +3 -1
  49. package/src/rules/lead/lead-tool.md +7 -0
  50. package/src/rules/shared/01-tool.md +17 -12
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  52. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  54. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  55. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  56. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  57. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  59. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  60. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  61. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  62. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
  66. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
  67. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  68. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
  75. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  76. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  78. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
  79. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  80. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  81. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  82. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  83. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  85. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  86. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  87. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  92. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  99. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  100. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  104. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  105. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  106. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  107. package/src/runtime/channels/backends/discord.mjs +99 -9
  108. package/src/runtime/channels/backends/telegram.mjs +501 -0
  109. package/src/runtime/channels/index.mjs +441 -1254
  110. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  111. package/src/runtime/channels/lib/config.mjs +54 -3
  112. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  113. package/src/runtime/channels/lib/executor.mjs +0 -3
  114. package/src/runtime/channels/lib/format.mjs +4 -2
  115. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  116. package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
  117. package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
  118. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  119. package/src/runtime/channels/lib/session-discovery.mjs +0 -4
  120. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  121. package/src/runtime/channels/lib/tool-format.mjs +1 -2
  122. package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
  123. package/src/runtime/channels/lib/webhook.mjs +59 -31
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  126. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  127. package/src/runtime/memory/index.mjs +187 -43
  128. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  129. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  130. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  131. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  132. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  133. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  134. package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
  135. package/src/runtime/memory/lib/memory.mjs +101 -4
  136. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  137. package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
  138. package/src/runtime/memory/lib/session-ingest.mjs +116 -7
  139. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  140. package/src/runtime/memory/tool-defs.mjs +6 -3
  141. package/src/runtime/search/index.mjs +2 -7
  142. package/src/runtime/search/lib/config.mjs +0 -4
  143. package/src/runtime/search/lib/state.mjs +1 -15
  144. package/src/runtime/search/lib/web-tools.mjs +0 -1
  145. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  146. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  147. package/src/runtime/shared/child-spawn-gate.mjs +0 -6
  148. package/src/runtime/shared/config.mjs +9 -0
  149. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  150. package/src/runtime/shared/schedules-store.mjs +21 -19
  151. package/src/runtime/shared/tool-surface.mjs +98 -13
  152. package/src/runtime/shared/transcript-writer.mjs +129 -0
  153. package/src/runtime/shared/update-checker.mjs +214 -0
  154. package/src/standalone/agent-tool.mjs +255 -109
  155. package/src/standalone/channel-admin.mjs +133 -40
  156. package/src/standalone/channel-worker.mjs +8 -291
  157. package/src/standalone/explore-tool.mjs +2 -2
  158. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  159. package/src/standalone/provider-admin.mjs +11 -0
  160. package/src/standalone/seeds.mjs +1 -11
  161. package/src/standalone/usage-dashboard.mjs +1 -1
  162. package/src/tui/App.jsx +2137 -750
  163. package/src/tui/components/ConfirmBar.jsx +47 -0
  164. package/src/tui/components/ContextPanel.jsx +5 -3
  165. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  166. package/src/tui/components/Markdown.jsx +22 -98
  167. package/src/tui/components/Message.jsx +14 -35
  168. package/src/tui/components/Picker.jsx +87 -12
  169. package/src/tui/components/PromptInput.jsx +146 -9
  170. package/src/tui/components/QueuedCommands.jsx +1 -1
  171. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  172. package/src/tui/components/Spinner.jsx +7 -7
  173. package/src/tui/components/StatusLine.jsx +40 -21
  174. package/src/tui/components/TextEntryPanel.jsx +51 -7
  175. package/src/tui/components/ToolExecution.jsx +177 -100
  176. package/src/tui/components/TurnDone.jsx +4 -4
  177. package/src/tui/components/UsagePanel.jsx +1 -1
  178. package/src/tui/components/tool-output-format.mjs +312 -40
  179. package/src/tui/components/tool-output-format.test.mjs +180 -1
  180. package/src/tui/display-width.mjs +69 -0
  181. package/src/tui/display-width.test.mjs +35 -0
  182. package/src/tui/dist/index.mjs +7324 -2393
  183. package/src/tui/engine.mjs +287 -126
  184. package/src/tui/index.jsx +117 -7
  185. package/src/tui/keyboard-protocol.mjs +42 -0
  186. package/src/tui/lib/voice-recorder.mjs +453 -0
  187. package/src/tui/markdown/format-token.mjs +354 -142
  188. package/src/tui/markdown/format-token.test.mjs +155 -17
  189. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  190. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  191. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  192. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  193. package/src/tui/markdown/table-layout.mjs +9 -9
  194. package/src/tui/paste-attachments.mjs +0 -11
  195. package/src/tui/prompt-history-store.mjs +129 -0
  196. package/src/tui/prompt-history-store.test.mjs +52 -0
  197. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  198. package/src/tui/theme.mjs +41 -647
  199. package/src/tui/themes/base.mjs +86 -0
  200. package/src/tui/themes/basic.mjs +85 -0
  201. package/src/tui/themes/catppuccin.mjs +72 -0
  202. package/src/tui/themes/dracula.mjs +70 -0
  203. package/src/tui/themes/everforest.mjs +71 -0
  204. package/src/tui/themes/gruvbox.mjs +71 -0
  205. package/src/tui/themes/index.mjs +71 -0
  206. package/src/tui/themes/indigo.mjs +78 -0
  207. package/src/tui/themes/kanagawa.mjs +80 -0
  208. package/src/tui/themes/light.mjs +81 -0
  209. package/src/tui/themes/nord.mjs +72 -0
  210. package/src/tui/themes/onedark.mjs +16 -0
  211. package/src/tui/themes/rosepine.mjs +70 -0
  212. package/src/tui/themes/teal.mjs +81 -0
  213. package/src/tui/themes/tokyonight.mjs +79 -0
  214. package/src/tui/themes/utils.mjs +106 -0
  215. package/src/tui/themes/warm.mjs +79 -0
  216. package/src/tui/transcript-tool-failures.mjs +13 -2
  217. package/src/ui/markdown.mjs +1 -1
  218. package/src/ui/model-display.mjs +2 -2
  219. package/src/ui/statusline.mjs +26 -27
  220. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
  221. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  222. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  223. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  224. package/src/workflows/default/WORKFLOW.md +39 -12
  225. package/src/workflows/sequential/WORKFLOW.md +46 -0
  226. package/src/workflows/solo/WORKFLOW.md +7 -0
  227. package/vendor/ink/build/display-width.js +62 -0
  228. package/vendor/ink/build/ink.js +154 -20
  229. package/vendor/ink/build/measure-text.js +4 -1
  230. package/vendor/ink/build/output.js +115 -9
  231. package/vendor/ink/build/render-node-to-output.js +4 -1
  232. package/vendor/ink/build/render.js +4 -0
  233. package/src/hooks/lib/permission-rules.cjs +0 -170
  234. package/src/hooks/lib/settings-loader.cjs +0 -112
  235. package/src/lib/hook-pipe-path.cjs +0 -10
  236. package/src/output-styles/extreme-simple.md +0 -20
  237. package/src/rules/lead/04-workflow.md +0 -51
  238. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  239. package/src/workflows/default/workflow.json +0 -13
  240. package/src/workflows/solo/workflow.json +0 -7
@@ -0,0 +1,595 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+ import { isInclusiveProvider } from '../src/runtime/shared/llm/cost.mjs';
6
+
7
+ function argValue(name, fallback = null) {
8
+ const idx = process.argv.indexOf(name);
9
+ if (idx >= 0 && idx + 1 < process.argv.length) return process.argv[idx + 1];
10
+ const pref = `${name}=`;
11
+ const hit = process.argv.find((arg) => arg.startsWith(pref));
12
+ return hit ? hit.slice(pref.length) : fallback;
13
+ }
14
+
15
+ function intArg(name, fallback) {
16
+ const n = Number.parseInt(argValue(name, String(fallback)), 10);
17
+ return Number.isFinite(n) && n > 0 ? n : fallback;
18
+ }
19
+
20
+ const pathArg = argValue('--path', null);
21
+ const dataDir = argValue('--data-dir', null);
22
+ const sinceArg = argValue('--since', null);
23
+ const agentFilter = argValue('--agent', null);
24
+ const sessionArg = argValue('--session', null);
25
+ const limit = intArg('--limit', 30);
26
+ const jsonMode = process.argv.includes('--json');
27
+ const treeMode = process.argv.includes('--tree');
28
+
29
+ const mixdogHome = process.env.MIXDOG_HOME || resolve(homedir(), '.mixdog');
30
+ const mixdogDataDir = process.env.MIXDOG_DATA_DIR || resolve(mixdogHome, 'data');
31
+
32
+ function unique(values) {
33
+ const seen = new Set();
34
+ const out = [];
35
+ for (const value of values) {
36
+ const key = String(value || '');
37
+ if (!key || seen.has(key)) continue;
38
+ seen.add(key);
39
+ out.push(value);
40
+ }
41
+ return out;
42
+ }
43
+
44
+ function defaultTraceFiles() {
45
+ if (pathArg) return [resolve(pathArg)];
46
+ const dirs = dataDir
47
+ ? [resolve(dataDir)]
48
+ : [resolve(process.cwd(), '.mixdog', 'data'), mixdogDataDir];
49
+ return unique(dirs.flatMap((dir) => [
50
+ resolve(dir, 'history', 'agent-trace.jsonl.1'),
51
+ resolve(dir, 'history', 'agent-trace.jsonl'),
52
+ ]));
53
+ }
54
+
55
+ function parseSince(value) {
56
+ const raw = String(value || '').trim();
57
+ if (!raw) return null;
58
+ if (/^now$/i.test(raw)) return Date.now();
59
+ if (/^\d+$/.test(raw)) {
60
+ const n = Number(raw);
61
+ return n > 10_000_000_000 ? n : n * 1000;
62
+ }
63
+ const rel = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i);
64
+ if (rel) {
65
+ const n = Number(rel[1]);
66
+ const unit = rel[2].toLowerCase();
67
+ const mult = unit === 'ms' ? 1 : unit === 's' ? 1000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000;
68
+ return Date.now() - n * mult;
69
+ }
70
+ const parsed = Date.parse(raw);
71
+ return Number.isFinite(parsed) ? parsed : null;
72
+ }
73
+
74
+ function readRows(file) {
75
+ if (!existsSync(file)) return [];
76
+ return readFileSync(file, 'utf8')
77
+ .split(/\r?\n/)
78
+ .filter(Boolean)
79
+ .flatMap((line) => {
80
+ try {
81
+ return [{ file, ...JSON.parse(line) }];
82
+ } catch {
83
+ return [];
84
+ }
85
+ });
86
+ }
87
+
88
+ function payload(row) {
89
+ return row && row.payload && typeof row.payload === 'object' ? row.payload : {};
90
+ }
91
+
92
+ function field(row, name) {
93
+ if (row && row[name] != null) return row[name];
94
+ const p = payload(row);
95
+ return p[name] != null ? p[name] : null;
96
+ }
97
+
98
+ function numberField(row, name) {
99
+ const n = Number(field(row, name));
100
+ return Number.isFinite(n) ? n : null;
101
+ }
102
+
103
+ function values(nums) {
104
+ return nums.filter((n) => Number.isFinite(n));
105
+ }
106
+
107
+ function percentile(sorted, p) {
108
+ if (sorted.length === 0) return null;
109
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
110
+ return sorted[idx];
111
+ }
112
+
113
+ function stats(nums) {
114
+ const arr = values(nums).sort((a, b) => a - b);
115
+ if (arr.length === 0) return null;
116
+ const sum = arr.reduce((a, b) => a + b, 0);
117
+ return {
118
+ n: arr.length,
119
+ sum,
120
+ avg: Math.round(sum / arr.length),
121
+ p50: percentile(arr, 50),
122
+ p90: percentile(arr, 90),
123
+ p99: percentile(arr, 99),
124
+ max: arr[arr.length - 1],
125
+ };
126
+ }
127
+
128
+ function mean(nums) {
129
+ const arr = values(nums);
130
+ if (arr.length === 0) return null;
131
+ return Math.round(arr.reduce((a, b) => a + b, 0) / arr.length);
132
+ }
133
+
134
+ function timeHms(ts) {
135
+ const n = Number(ts);
136
+ if (!Number.isFinite(n) || n <= 0) return '-';
137
+ const d = new Date(n);
138
+ const hh = String(d.getHours()).padStart(2, '0');
139
+ const mm = String(d.getMinutes()).padStart(2, '0');
140
+ const ss = String(d.getSeconds()).padStart(2, '0');
141
+ return `${hh}:${mm}:${ss}`;
142
+ }
143
+
144
+ function shortModel(model) {
145
+ const text = String(model || '-');
146
+ if (text.length <= 18) return text;
147
+ const slash = text.lastIndexOf('/');
148
+ if (slash >= 0 && text.length - slash - 1 <= 18) return text.slice(slash + 1);
149
+ return `${text.slice(0, 15)}…`;
150
+ }
151
+
152
+ function shortSessionId(sessionId) {
153
+ const raw = String(sessionId || '');
154
+ const core = raw.startsWith('sess_') ? raw.slice(5) : raw;
155
+ if (core.length <= 12) return core;
156
+ return core.slice(0, 10);
157
+ }
158
+
159
+ function inferProviderFromModel(model) {
160
+ const m = String(model || '').toLowerCase();
161
+ if (!m) return null;
162
+ if (m.includes('claude')) return 'anthropic';
163
+ if (m.includes('gemini')) return 'google';
164
+ if (m.includes('gpt') || m.includes('codex')) return 'openai';
165
+ if (m.includes('grok') || m.includes('xai')) return 'xai';
166
+ if (m.includes('deepseek')) return 'deepseek';
167
+ return null;
168
+ }
169
+
170
+ function sessionInclusive(provider) {
171
+ if (provider) return isInclusiveProvider(provider);
172
+ return true;
173
+ }
174
+
175
+ function usageDenom(row, provider) {
176
+ const prompt = numberField(row, 'prompt_tokens');
177
+ const input = numberField(row, 'input_tokens');
178
+ if (isInclusiveProvider(provider)) return prompt;
179
+ const p = String(provider || '').toLowerCase();
180
+ if (p.includes('anthropic')) return prompt || input;
181
+ return input ?? prompt;
182
+ }
183
+
184
+ function cacheHitPctRow(row, provider) {
185
+ const cached = numberField(row, 'cached_tokens') || 0;
186
+ const denom = usageDenom(row, provider);
187
+ if (!denom || denom <= 0) return null;
188
+ return (cached / denom) * 100;
189
+ }
190
+
191
+ function aggregateCacheHit(usageRows, provider) {
192
+ let sumCached = 0;
193
+ let sumDenom = 0;
194
+ for (const row of usageRows) {
195
+ sumCached += numberField(row, 'cached_tokens') || 0;
196
+ const denom = usageDenom(row, provider);
197
+ if (denom && denom > 0) sumDenom += denom;
198
+ }
199
+ if (sumDenom <= 0) return null;
200
+ return (sumCached / sumDenom) * 100;
201
+ }
202
+
203
+ function nearestPreceding(rows, ts) {
204
+ const t = Number(ts);
205
+ if (!Number.isFinite(t)) return null;
206
+ let best = null;
207
+ let bestTs = -Infinity;
208
+ for (const row of rows) {
209
+ const rts = Number(row.ts || 0);
210
+ if (rts <= t && rts > bestTs) {
211
+ bestTs = rts;
212
+ best = row;
213
+ }
214
+ }
215
+ return best;
216
+ }
217
+
218
+ function formatKb(bytes) {
219
+ const n = Number(bytes);
220
+ if (!Number.isFinite(n)) return '-';
221
+ return `${Math.round(n / 1024)}KB`;
222
+ }
223
+
224
+ function formatTok(n) {
225
+ const v = Number(n);
226
+ if (!Number.isFinite(v)) return '-';
227
+ if (v >= 1000) return `${(v / 1000).toFixed(1)}ktok`;
228
+ return `${Math.round(v)}tok`;
229
+ }
230
+
231
+ function formatPct(p) {
232
+ if (p == null || !Number.isFinite(p)) return '-';
233
+ return `${Math.round(p)}%`;
234
+ }
235
+
236
+ function padColumns(tableRows) {
237
+ if (tableRows.length === 0) return [];
238
+ const colCount = tableRows[0].length;
239
+ const widths = Array.from({ length: colCount }, (_, i) =>
240
+ Math.max(...tableRows.map((r) => String(r[i] ?? '').length)));
241
+ return tableRows.map((r) => r.map((cell, i) => String(cell ?? '').padEnd(widths[i])).join(' '));
242
+ }
243
+
244
+ function groupToolsByIteration(toolRows) {
245
+ const map = new Map();
246
+ for (const row of toolRows) {
247
+ const it = numberField(row, 'iteration');
248
+ if (it == null) continue;
249
+ const name = String(field(row, 'tool_name') || '(unknown)');
250
+ if (!map.has(it)) map.set(it, new Map());
251
+ const inner = map.get(it);
252
+ inner.set(name, (inner.get(name) || 0) + 1);
253
+ }
254
+ return map;
255
+ }
256
+
257
+ function toolsLabel(toolMap) {
258
+ if (!toolMap || toolMap.size === 0) return '-';
259
+ return [...toolMap.entries()]
260
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
261
+ .map(([name, count]) => `${name}×${count}`)
262
+ .join(', ');
263
+ }
264
+
265
+ function deriveSessionMeta(rows) {
266
+ const sorted = [...rows].sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
267
+ let agent = null;
268
+ for (const row of sorted) {
269
+ const kind = String(row.kind || '');
270
+ if (kind !== 'preset_assign' && kind !== 'tool') continue;
271
+ const r = field(row, 'agent');
272
+ if (r) {
273
+ agent = String(r);
274
+ break;
275
+ }
276
+ }
277
+ const preset = sorted.find((r) => r.kind === 'preset_assign');
278
+ let provider = preset ? field(preset, 'provider') : null;
279
+ let model = preset ? field(preset, 'model') : null;
280
+ if (!model) {
281
+ const usage = sorted.find((r) => r.kind === 'usage_raw' && field(r, 'model'));
282
+ model = usage ? field(usage, 'model') : null;
283
+ }
284
+ if (!provider) provider = inferProviderFromModel(model);
285
+ const inclusive = sessionInclusive(provider);
286
+ const tsList = sorted.map((r) => Number(r.ts || 0)).filter((n) => n > 0);
287
+ const minTs = tsList.length ? Math.min(...tsList) : 0;
288
+ const maxTs = tsList.length ? Math.max(...tsList) : 0;
289
+ const batchRows = sorted.filter((r) => r.kind === 'batch');
290
+ const toolRows = sorted.filter((r) => r.kind === 'tool');
291
+ const usageRows = sorted.filter((r) => r.kind === 'usage_raw');
292
+ const sseRows = sorted.filter((r) => r.kind === 'sse');
293
+ const contextRows = sorted.filter((r) => r.kind === 'context');
294
+ let turns = batchRows.length;
295
+ if (turns === 0) {
296
+ const iters = usageRows.map((r) => numberField(r, 'iteration')).filter((n) => n != null);
297
+ turns = iters.length ? Math.max(...iters) : 0;
298
+ }
299
+ const tools = toolRows.length;
300
+ const toolsPerTurn = turns > 0 ? (tools / turns).toFixed(1) : '-';
301
+ const avgPromptTok = mean(usageRows.map((r) => numberField(r, 'prompt_tokens')));
302
+ const cacheHit = aggregateCacheHit(usageRows, provider);
303
+ const avgTtft = mean(sseRows.map((r) => numberField(r, 'ttft_ms')));
304
+ const spanSec = maxTs >= minTs ? Math.round((maxTs - minTs) / 1000) : 0;
305
+ const totalPrompt = usageRows.reduce((s, r) => s + (numberField(r, 'prompt_tokens') || 0), 0);
306
+ const totalOutput = usageRows.reduce((s, r) => s + (numberField(r, 'output_tokens') || 0), 0);
307
+ const parentSessionId = preset ? field(preset, 'parent_session_id') : null;
308
+ return {
309
+ agent,
310
+ provider,
311
+ model,
312
+ inclusive,
313
+ minTs,
314
+ maxTs,
315
+ turns,
316
+ tools,
317
+ toolsPerTurn,
318
+ avgPromptTok,
319
+ cacheHit,
320
+ avgTtft,
321
+ spanSec,
322
+ totalPrompt,
323
+ totalOutput,
324
+ usageRows,
325
+ toolRows,
326
+ sseRows,
327
+ contextRows,
328
+ batchRows,
329
+ parentSessionId,
330
+ sorted,
331
+ };
332
+ }
333
+
334
+ function collectIterations(meta) {
335
+ const iters = new Set();
336
+ for (const row of meta.usageRows) {
337
+ const it = numberField(row, 'iteration');
338
+ if (it != null) iters.add(it);
339
+ }
340
+ for (const row of meta.toolRows) {
341
+ const it = numberField(row, 'iteration');
342
+ if (it != null) iters.add(it);
343
+ }
344
+ return [...iters].sort((a, b) => a - b);
345
+ }
346
+
347
+ function usageForIteration(usageRows, it) {
348
+ const hits = usageRows.filter((r) => numberField(r, 'iteration') === it);
349
+ if (hits.length === 0) return null;
350
+ return hits.reduce((a, b) => (Number(a.ts || 0) >= Number(b.ts || 0) ? a : b));
351
+ }
352
+
353
+ function buildTimeline(meta) {
354
+ const toolByIt = groupToolsByIteration(meta.toolRows);
355
+ const iterations = collectIterations(meta);
356
+ const sessionAvgCache = meta.cacheHit;
357
+ const lines = [];
358
+ let prevPrompt = null;
359
+ const issues = [];
360
+
361
+ for (const it of iterations) {
362
+ const usage = usageForIteration(meta.usageRows, it);
363
+ const ts = Number(usage?.ts || 0);
364
+ const ctx = nearestPreceding(meta.contextRows, ts);
365
+ const sse = nearestPreceding(meta.sseRows, ts);
366
+ const prompt = usage ? numberField(usage, 'prompt_tokens') : null;
367
+ const cachedPct = usage ? cacheHitPctRow(usage, meta.provider) : null;
368
+ const ttft = sse ? numberField(sse, 'ttft_ms') : null;
369
+ const ctxBytes = ctx ? numberField(ctx, 'totalBytes') : null;
370
+ const flags = [];
371
+ if (sessionAvgCache != null && cachedPct != null && cachedPct < sessionAvgCache - 20) {
372
+ flags.push('cache');
373
+ }
374
+ if (prevPrompt != null && prompt != null && prompt > prevPrompt * 1.5) {
375
+ flags.push('prompt');
376
+ }
377
+ if (ttft != null && ttft > 5000) flags.push('ttft');
378
+ const warn = flags.length ? ' ⚠' : '';
379
+ if (flags.length) {
380
+ issues.push({ it, flags, prompt, cachedPct, ttft, ctxBytes, tools: toolByIt.get(it) });
381
+ }
382
+ lines.push({
383
+ it,
384
+ tools: toolsLabel(toolByIt.get(it)),
385
+ ctx: formatKb(ctxBytes),
386
+ prompt: formatTok(prompt),
387
+ cached: formatPct(cachedPct),
388
+ ttft: ttft != null ? `${Math.round(ttft)}ms` : '-',
389
+ warn,
390
+ promptRaw: prompt,
391
+ cachedPct,
392
+ ttftRaw: ttft,
393
+ ctxBytes,
394
+ flags,
395
+ });
396
+ if (prompt != null) prevPrompt = prompt;
397
+ }
398
+ return { lines, issues };
399
+ }
400
+
401
+ function autoDiagnosis(meta, timeline) {
402
+ const { issues, lines } = timeline;
403
+ if (lines.length === 0) {
404
+ return 'No iterations with usage or tool rows; cannot infer per-turn behavior.';
405
+ }
406
+ const ranked = [...issues].sort((a, b) => {
407
+ const score = (x) => (x.flags.includes('prompt') ? 3 : 0)
408
+ + (x.flags.includes('cache') ? 2 : 0)
409
+ + (x.flags.includes('ttft') ? 1 : 0)
410
+ + ((x.ttft || 0) / 1000);
411
+ return score(b) - score(a) || (b.prompt || 0) - (a.prompt || 0);
412
+ });
413
+ const worst = ranked.slice(0, 2);
414
+ if (worst.length === 0) {
415
+ return `Session span ${meta.spanSec}s across ${meta.turns} turns looks stable: cache ~${formatPct(meta.cacheHit)}, mean prompt ~${formatTok(meta.avgPromptTok)}tok; no iteration tripped cache, prompt-jump, or TTFT thresholds.`;
416
+ }
417
+ const parts = worst.map((w) => {
418
+ const causes = [];
419
+ if (w.flags.includes('prompt') || (w.ctxBytes && w.ctxBytes > 500_000)) causes.push('context spike');
420
+ if (w.flags.includes('cache')) causes.push('cache break');
421
+ if (w.flags.includes('ttft')) causes.push('slow TTFT');
422
+ if (w.tools && w.tools.size >= 4) causes.push('tool overuse');
423
+ if (causes.length === 0) causes.push('mixed pressure');
424
+ return `it=${w.it} (${causes.join(', ')})`;
425
+ });
426
+ return `Worst iterations: ${parts.join('; ')}. Classification follows fired thresholds: prompt jump → context spike; cache drop → cache break; many tools → tool overuse; high TTFT → latency.`;
427
+ }
428
+
429
+ function sessionMatchesQuery(id, query) {
430
+ const q = String(query || '').trim();
431
+ if (!q) return false;
432
+ if (id === q) return true;
433
+ if (id.startsWith(q)) return true;
434
+ const short = shortSessionId(id);
435
+ if (short === q || short.startsWith(q)) return true;
436
+ const core = id.startsWith('sess_') ? id.slice(5) : id;
437
+ return core.startsWith(q);
438
+ }
439
+
440
+ function findSessionsByQuery(summaries, query) {
441
+ return summaries.filter((s) => sessionMatchesQuery(s.id, query));
442
+ }
443
+
444
+ const files = defaultTraceFiles();
445
+ const sinceTs = parseSince(sinceArg);
446
+ const allRows = files.flatMap(readRows)
447
+ .filter((row) => sinceTs == null || Number(row.ts || 0) >= sinceTs)
448
+ .sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
449
+
450
+ const bySession = new Map();
451
+ for (const row of allRows) {
452
+ const sid = row.session_id || field(row, 'session_id');
453
+ if (!sid) continue;
454
+ if (!bySession.has(sid)) bySession.set(sid, []);
455
+ bySession.get(sid).push(row);
456
+ }
457
+
458
+ const sessionSummaries = [];
459
+ for (const [id, rows] of bySession.entries()) {
460
+ const meta = deriveSessionMeta(rows);
461
+ if (agentFilter && String(meta.agent || '') !== agentFilter) continue;
462
+ sessionSummaries.push({ id, meta });
463
+ }
464
+
465
+ function buildListJson(limited) {
466
+ return limited.map(({ id, meta }) => ({
467
+ session_id: id,
468
+ last_activity: timeHms(meta.maxTs),
469
+ last_ts: meta.maxTs,
470
+ agent: meta.agent,
471
+ model: meta.model,
472
+ provider: meta.provider,
473
+ short_id: shortSessionId(id),
474
+ turns: meta.turns,
475
+ tools: meta.tools,
476
+ tools_per_turn: meta.toolsPerTurn,
477
+ avg_prompt_tokens: meta.avgPromptTok,
478
+ cache_hit_pct: meta.cacheHit,
479
+ avg_ttft_ms: meta.avgTtft,
480
+ span_sec: meta.spanSec,
481
+ }));
482
+ }
483
+
484
+ function renderListView() {
485
+ const sorted = [...sessionSummaries].sort((a, b) => Number(b.meta.maxTs || 0) - Number(a.meta.maxTs || 0));
486
+ const limited = sorted.slice(0, limit);
487
+ if (jsonMode) {
488
+ console.log(JSON.stringify(buildListJson(limited), null, 2));
489
+ return;
490
+ }
491
+ const header = [
492
+ 'lastActivity',
493
+ 'agent',
494
+ 'model',
495
+ 'sess',
496
+ 'turns',
497
+ 'tools',
498
+ 'tools/turn',
499
+ 'avgPromptTok',
500
+ 'cacheHit%',
501
+ 'avgTTFTms',
502
+ 'spanSec',
503
+ ];
504
+ const dataRows = limited.map(({ id, meta }) => [
505
+ timeHms(meta.maxTs),
506
+ meta.agent || '-',
507
+ shortModel(meta.model),
508
+ shortSessionId(id),
509
+ String(meta.turns),
510
+ String(meta.tools),
511
+ String(meta.toolsPerTurn),
512
+ formatTok(meta.avgPromptTok),
513
+ formatPct(meta.cacheHit),
514
+ meta.avgTtft != null ? String(meta.avgTtft) : '-',
515
+ String(meta.spanSec),
516
+ ]);
517
+ for (const line of padColumns([header, ...dataRows])) console.log(line);
518
+ }
519
+
520
+ function buildDetailObject(full) {
521
+ const meta = full.meta;
522
+ const ttftStats = stats(meta.sseRows.map((r) => numberField(r, 'ttft_ms')));
523
+ const timeline = buildTimeline(meta);
524
+ const children = sessionSummaries
525
+ .filter((s) => s.meta.parentSessionId && String(s.meta.parentSessionId) === String(full.id))
526
+ .map((s) => ({
527
+ session_id: s.id,
528
+ agent: s.meta.agent,
529
+ short_id: shortSessionId(s.id),
530
+ turns: s.meta.turns,
531
+ total_prompt_tokens: s.meta.totalPrompt,
532
+ cache_hit_pct: s.meta.cacheHit,
533
+ }));
534
+ return {
535
+ session_id: full.id,
536
+ short_id: shortSessionId(full.id),
537
+ agent: meta.agent,
538
+ model: meta.model,
539
+ provider: meta.provider,
540
+ turns: meta.turns,
541
+ span_sec: meta.spanSec,
542
+ total_prompt_tokens: meta.totalPrompt,
543
+ total_output_tokens: meta.totalOutput,
544
+ cache_hit_pct: meta.cacheHit,
545
+ ttft_p50_ms: ttftStats?.p50 ?? null,
546
+ ttft_p90_ms: ttftStats?.p90 ?? null,
547
+ timeline: timeline.lines,
548
+ diagnosis: autoDiagnosis(meta, timeline),
549
+ children,
550
+ };
551
+ }
552
+
553
+ function renderDetailView(query) {
554
+ const matches = findSessionsByQuery(sessionSummaries, query);
555
+ if (matches.length === 0) {
556
+ console.log(`no session matched: ${query}`);
557
+ return;
558
+ }
559
+ if (matches.length > 1) {
560
+ console.log(`multiple sessions matched "${query}":`);
561
+ for (const s of matches) {
562
+ console.log(` ${shortSessionId(s.id)} ${s.id} agent=${s.meta.agent || '-'}`);
563
+ }
564
+ return;
565
+ }
566
+ const full = matches[0];
567
+ const detail = buildDetailObject(full);
568
+ if (jsonMode) {
569
+ console.log(JSON.stringify(detail, null, 2));
570
+ return;
571
+ }
572
+ const meta = full.meta;
573
+ const ttftStats = stats(meta.sseRows.map((r) => numberField(r, 'ttft_ms')));
574
+ console.log(`session ${detail.short_id} agent=${meta.agent || '-'} model=${meta.model || '-'} provider=${meta.provider || '-'}`);
575
+ console.log(`turns=${meta.turns} span=${meta.spanSec}s prompt=${meta.totalPrompt}tok output=${meta.totalOutput}tok cacheHit=${formatPct(meta.cacheHit)} ttft p50/p90=${ttftStats?.p50 ?? '-'} / ${ttftStats?.p90 ?? '-'}ms`);
576
+ console.log('');
577
+ for (const l of detail.timeline) {
578
+ console.log(`it=${l.it} tools: ${l.tools} ctx: ${l.ctx} prompt: ${l.prompt} cached: ${l.cached} ttft: ${l.ttft}${l.warn}`);
579
+ }
580
+ if (treeMode && detail.children.length > 0) {
581
+ console.log('');
582
+ console.log('child sessions:');
583
+ for (const c of detail.children) {
584
+ console.log(` ↳ ${c.agent || '-'} ${c.short_id} turns=${c.turns} prompt=${c.total_prompt_tokens}tok cacheHit=${formatPct(c.cache_hit_pct)}`);
585
+ }
586
+ }
587
+ console.log('');
588
+ console.log(detail.diagnosis);
589
+ }
590
+
591
+ if (sessionArg) {
592
+ renderDetailView(sessionArg);
593
+ } else {
594
+ renderListView();
595
+ }
@@ -15,8 +15,8 @@ function assert(condition, message) {
15
15
  // --- Role normalization ---
16
16
  assert(normalizeIngestRole('human') === 'user', 'human should normalize to user');
17
17
  assert(normalizeIngestRole('AI') === 'assistant', 'AI should normalize to assistant');
18
- assert(normalizeIngestRole('tool_result') === 'tool', 'tool_result should normalize to tool');
19
- assert(normalizeIngestRole('developer') === 'developer', 'developer should be preserved');
18
+ assert(normalizeIngestRole('tool_result') === null, 'tool_result is not ingested (maps to tool, dropped)');
19
+ assert(normalizeIngestRole('developer') === null, 'developer is not ingested');
20
20
  assert(normalizeIngestRole('mystery') === null, 'unknown role should be dropped');
21
21
 
22
22
  // --- Stable source_ref across index/time for untimestamped messages ---