ftown-bridge 0.19.1 → 0.19.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 (68) hide show
  1. package/dist/agent-commands.d.ts +3 -17
  2. package/dist/agent-commands.js +8 -94
  3. package/dist/agent-commands.js.map +1 -1
  4. package/dist/bridge-auth.d.ts +21 -0
  5. package/dist/bridge-auth.js +48 -0
  6. package/dist/bridge-auth.js.map +1 -0
  7. package/dist/centrifugo-client.js +39 -0
  8. package/dist/centrifugo-client.js.map +1 -1
  9. package/dist/command-rpc.d.ts +15 -0
  10. package/dist/command-rpc.js +249 -0
  11. package/dist/command-rpc.js.map +1 -0
  12. package/dist/create-ftown-session.d.ts +43 -0
  13. package/dist/create-ftown-session.js +115 -9
  14. package/dist/create-ftown-session.js.map +1 -1
  15. package/dist/ftown-sessions-cli.js +74 -4
  16. package/dist/ftown-sessions-cli.js.map +1 -1
  17. package/dist/harness-cli.js +1 -13
  18. package/dist/harness-cli.js.map +1 -1
  19. package/dist/harness-installer.js +4 -2
  20. package/dist/harness-installer.js.map +1 -1
  21. package/dist/harness-registry.d.ts +189 -0
  22. package/dist/harness-registry.js +218 -0
  23. package/dist/harness-registry.js.map +1 -0
  24. package/dist/index.js +71 -721
  25. package/dist/index.js.map +1 -1
  26. package/dist/install-ftown-cli.js +12 -3
  27. package/dist/install-ftown-cli.js.map +1 -1
  28. package/dist/local-api-server.d.ts +14 -13
  29. package/dist/local-api-server.js +180 -424
  30. package/dist/local-api-server.js.map +1 -1
  31. package/dist/loop-controller.d.ts +87 -0
  32. package/dist/loop-controller.js +133 -0
  33. package/dist/loop-controller.js.map +1 -0
  34. package/dist/loop-run-store.js +2 -8
  35. package/dist/loop-run-store.js.map +1 -1
  36. package/dist/loop-validation.d.ts +1 -0
  37. package/dist/loop-validation.js +3 -8
  38. package/dist/loop-validation.js.map +1 -1
  39. package/dist/mail-delivery.d.ts +114 -0
  40. package/dist/mail-delivery.js +315 -0
  41. package/dist/mail-delivery.js.map +1 -0
  42. package/dist/session-controller.d.ts +107 -0
  43. package/dist/session-controller.js +141 -0
  44. package/dist/session-controller.js.map +1 -0
  45. package/dist/session-ids.d.ts +19 -0
  46. package/dist/session-ids.js +72 -0
  47. package/dist/session-ids.js.map +1 -0
  48. package/dist/session-resurrection.d.ts +24 -0
  49. package/dist/session-resurrection.js +128 -0
  50. package/dist/session-resurrection.js.map +1 -1
  51. package/dist/spawn-stagger.d.ts +22 -0
  52. package/dist/spawn-stagger.js +34 -0
  53. package/dist/spawn-stagger.js.map +1 -0
  54. package/dist/terminal-pump.d.ts +61 -0
  55. package/dist/terminal-pump.js +193 -0
  56. package/dist/terminal-pump.js.map +1 -0
  57. package/dist/types.d.ts +29 -4
  58. package/dist/usage-collector.d.ts +42 -0
  59. package/dist/usage-collector.js +317 -0
  60. package/dist/usage-collector.js.map +1 -0
  61. package/dist/wire-types.d.ts +124 -0
  62. package/dist/wire-types.js +28 -0
  63. package/dist/wire-types.js.map +1 -0
  64. package/dist/workflow-runner-cli.js +2 -1
  65. package/dist/workflow-runner-cli.js.map +1 -1
  66. package/dist/workflow-runner.d.ts +2 -1
  67. package/dist/workflow-runner.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,193 @@
1
+ const FLUSH_INTERVAL_MS = 16;
2
+ const MAX_BUFFER_BYTES = 32_000;
3
+ /** Raw terminal chars considered for the errorReason tail (pre-sanitize). */
4
+ const ERROR_TAIL_RAW_CHARS = 1200;
5
+ /** Max sanitized tail chars kept in errorReason. */
6
+ const ERROR_TAIL_MAX_CHARS = 300;
7
+ /** Hard cap on the whole errorReason string. */
8
+ const ERROR_REASON_MAX_CHARS = 400;
9
+ // CSI (\x1b[...X), OSC (\x1b]...BEL/ST), and any leftover lone ESC sequences.
10
+ const ANSI_ESCAPE_RE = new RegExp('\\x1b\\[[0-9;?]*[ -/]*[@-~]|\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)?|\\x1b[@-_]?', 'g');
11
+ /** Strip ANSI escapes and collapse all whitespace/control runs to single spaces. */
12
+ function sanitizeTerminalTail(raw) {
13
+ return raw
14
+ .replace(ANSI_ESCAPE_RE, '')
15
+ .replace(/[\s\u0000-\u001f\u007f]+/g, ' ')
16
+ .trim();
17
+ }
18
+ /**
19
+ * Terminal output pump: buffers runner PTY output per session (coalescing into
20
+ * ≤16ms / ≤32KB flushes to the store + transports), and owns the runner
21
+ * lifecycle handlers ('complete'/'error') that persist final loop-run/session
22
+ * statuses — the loop scheduler reads the statuses this module produces.
23
+ */
24
+ export class TerminalPump {
25
+ deps;
26
+ outputBuffers = new Map();
27
+ flushTimers = new Map();
28
+ // Per-session write queue so concurrent load/modify/save flows (hooks,
29
+ // runner exit handlers, stop_session) cannot interleave and resurrect a
30
+ // stale status.
31
+ sessionWrites = new Map();
32
+ flushIntervalMs;
33
+ maxBufferBytes;
34
+ constructor(deps) {
35
+ this.deps = deps;
36
+ this.flushIntervalMs = deps.flushIntervalMs ?? FLUSH_INTERVAL_MS;
37
+ this.maxBufferBytes = deps.maxBufferBytes ?? MAX_BUFFER_BYTES;
38
+ }
39
+ /** Wire the pump onto the runner's data/lifecycle events. */
40
+ attach(runner) {
41
+ runner.on('data', (sessionId, data) => this.handleData(sessionId, data));
42
+ runner.on('complete', (sessionId) => this.handleComplete(sessionId));
43
+ runner.on('error', (sessionId, error) => this.handleError(sessionId, error));
44
+ }
45
+ handleData(sessionId, data) {
46
+ this.deps.terminalManager.write(sessionId, data);
47
+ const existing = this.outputBuffers.get(sessionId) ?? '';
48
+ this.outputBuffers.set(sessionId, existing + data);
49
+ if ((existing.length + data.length) >= this.maxBufferBytes) {
50
+ this.flush(sessionId);
51
+ }
52
+ else if (!this.flushTimers.has(sessionId)) {
53
+ this.flushTimers.set(sessionId, setTimeout(() => this.flush(sessionId), this.flushIntervalMs));
54
+ }
55
+ }
56
+ flush(sessionId) {
57
+ const buf = this.outputBuffers.get(sessionId);
58
+ if (!buf)
59
+ return;
60
+ this.outputBuffers.delete(sessionId);
61
+ const timer = this.flushTimers.get(sessionId);
62
+ if (timer)
63
+ clearTimeout(timer);
64
+ this.flushTimers.delete(sessionId);
65
+ this.deps.store.appendTerminalData(sessionId, buf).catch((err) => {
66
+ console.error(`[Bridge] Failed to store terminal data for ${sessionId}:`, err);
67
+ });
68
+ this.deps.publishTerminalData(sessionId, buf);
69
+ }
70
+ withSessionWrite(sessionId, task) {
71
+ const prev = this.sessionWrites.get(sessionId) ?? Promise.resolve();
72
+ const run = prev.then(task);
73
+ const settled = run.catch(() => undefined);
74
+ this.sessionWrites.set(sessionId, settled);
75
+ void settled.finally(() => {
76
+ if (this.sessionWrites.get(sessionId) === settled) {
77
+ this.sessionWrites.delete(sessionId);
78
+ }
79
+ });
80
+ return run;
81
+ }
82
+ // Synthetic activity reset: some stop paths never produce a real Stop/stop
83
+ // hook (Claude's Stop hook doesn't fire on user interrupt, SessionEnd may be
84
+ // absent, runner exits/crashes). Publishing a synthetic Stop event guarantees
85
+ // every dashboard clears its "thinking"/"tool_use" indicator. Idle is
86
+ // idempotent, so an extra synthetic Stop after a real one is harmless.
87
+ publishSyntheticStop(sessionId, reason) {
88
+ this.deps.publishHookEvent(sessionId, {
89
+ type: 'hook_event',
90
+ eventName: 'Stop',
91
+ data: { synthetic: true, reason },
92
+ }).catch((err) => {
93
+ console.error(`[Bridge] Failed to publish synthetic stop for ${sessionId}:`, err);
94
+ });
95
+ }
96
+ /**
97
+ * Fire-and-forget usage collection after a terminal status persist. Runs
98
+ * OUTSIDE the caller's write (the collect can take seconds on huge
99
+ * transcripts); the persist itself is serialized via withSessionWrite.
100
+ * Guard: a null collection result never clobbers previously saved usage.
101
+ */
102
+ collectAndPersistUsage(sessionId) {
103
+ const collect = this.deps.collectUsage;
104
+ if (!collect)
105
+ return;
106
+ void (async () => {
107
+ const session = await this.deps.store.loadSession(sessionId);
108
+ if (!session)
109
+ return;
110
+ const usage = await collect(session);
111
+ if (!usage)
112
+ return;
113
+ await this.withSessionWrite(sessionId, async () => {
114
+ const fresh = await this.deps.store.loadSession(sessionId);
115
+ if (!fresh)
116
+ return;
117
+ fresh.usage = usage;
118
+ fresh.updatedAt = new Date().toISOString();
119
+ await this.deps.store.saveSession(fresh);
120
+ await this.deps.publishSessionUpdate(fresh);
121
+ });
122
+ })().catch((err) => {
123
+ console.error(`[Bridge] Failed to collect usage for session ${sessionId}:`, err);
124
+ });
125
+ }
126
+ handleComplete(sessionId) {
127
+ this.flush(sessionId);
128
+ this.publishSyntheticStop(sessionId, 'complete');
129
+ this.withSessionWrite(sessionId, async () => {
130
+ const session = await this.deps.store.loadSession(sessionId);
131
+ if (session) {
132
+ session.status = 'completed';
133
+ session.updatedAt = new Date().toISOString();
134
+ await this.deps.store.saveSession(session);
135
+ await this.deps.publishSessionUpdate(session);
136
+ }
137
+ console.log(`[Bridge] Session ${sessionId} completed`);
138
+ }).catch((err) => {
139
+ console.error(`[Bridge] Failed to handle completion for session ${sessionId}:`, err);
140
+ }).finally(() => {
141
+ this.collectAndPersistUsage(sessionId);
142
+ this.deps.unregisterSession(sessionId);
143
+ });
144
+ }
145
+ /**
146
+ * `<error message> — <recent terminal tail>` (ANSI-stripped, whitespace
147
+ * collapsed, capped at ~400 chars) so a startup crash's cause is readable
148
+ * straight off the session record. The tail comes from the flushed terminal
149
+ * log; flush() fires its append without awaiting, so the chunk buffered at
150
+ * error time is re-appended if it hasn't landed on disk yet.
151
+ */
152
+ async buildErrorReason(sessionId, error, pendingAtError) {
153
+ let log = '';
154
+ try {
155
+ log = await this.deps.store.loadTerminalLog(sessionId);
156
+ }
157
+ catch {
158
+ // Tail is best-effort — fall back to whatever was buffered at error time.
159
+ }
160
+ const source = pendingAtError && !log.endsWith(pendingAtError)
161
+ ? log + pendingAtError
162
+ : log;
163
+ const tail = sanitizeTerminalTail(source.slice(-ERROR_TAIL_RAW_CHARS))
164
+ .slice(-ERROR_TAIL_MAX_CHARS);
165
+ const reason = tail ? `${error.message} — ${tail}` : error.message;
166
+ return reason.slice(0, ERROR_REASON_MAX_CHARS);
167
+ }
168
+ handleError(sessionId, error) {
169
+ // Capture what's still buffered BEFORE flush drains it — the errorReason
170
+ // tail needs it if the (unawaited) flush append hasn't hit the store yet.
171
+ const pendingAtError = this.outputBuffers.get(sessionId) ?? '';
172
+ this.flush(sessionId);
173
+ this.publishSyntheticStop(sessionId, 'error');
174
+ this.withSessionWrite(sessionId, async () => {
175
+ const session = await this.deps.store.loadSession(sessionId);
176
+ if (session) {
177
+ session.status = 'error';
178
+ session.errorReason = await this.buildErrorReason(sessionId, error, pendingAtError);
179
+ session.updatedAt = new Date().toISOString();
180
+ await this.deps.store.saveSession(session);
181
+ await this.deps.publishSessionUpdate(session);
182
+ }
183
+ console.error(`[Bridge] Session ${sessionId} error:`, error.message);
184
+ }).catch((err) => {
185
+ console.error(`[Bridge] Failed to handle error for session ${sessionId}:`, err);
186
+ }).finally(() => {
187
+ this.collectAndPersistUsage(sessionId);
188
+ this.deps.unregisterSession(sessionId);
189
+ this.deps.terminalManager.destroy(sessionId);
190
+ });
191
+ }
192
+ }
193
+ //# sourceMappingURL=terminal-pump.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-pump.js","sourceRoot":"","sources":["../src/terminal-pump.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,5 +1,26 @@
1
- export type ShellType = 'claude' | 'cursor' | 'codex' | 'shell' | 'zai' | 'kimi' | 'opencode' | 'deepseek' | 'fireworks' | 'grok';
1
+ export type { LoopHarness, ShellType } from './harness-registry.js';
2
+ import type { LoopHarness, ShellType } from './harness-registry.js';
2
3
  export type SessionRuntime = 'tmux' | 'direct';
4
+ /** Per-model token breakdown within a session. */
5
+ export interface ModelUsage {
6
+ model: string;
7
+ inputTokens: number;
8
+ outputTokens: number;
9
+ cacheReadTokens: number;
10
+ cacheWriteTokens: number;
11
+ }
12
+ /** Per-session token usage, extracted from harness-native session files. */
13
+ export interface SessionUsage {
14
+ inputTokens: number;
15
+ outputTokens: number;
16
+ cacheReadTokens: number;
17
+ cacheWriteTokens: number;
18
+ totalTokens: number;
19
+ models: string[];
20
+ perModel?: ModelUsage[];
21
+ harness: string;
22
+ collectedAt: string;
23
+ }
3
24
  export interface Session {
4
25
  id: string;
5
26
  name: string;
@@ -20,13 +41,13 @@ export interface Session {
20
41
  runtime?: SessionRuntime;
21
42
  errorReason?: string;
22
43
  loopId?: string;
44
+ usage?: SessionUsage;
23
45
  }
24
46
  export type SessionStatus = 'pending' | 'running' | 'completed' | 'error';
25
47
  /** Tombstone written to <dataDir>/archive.jsonl when a session is removed. */
26
48
  export interface ArchivedSession extends Session {
27
49
  removedAt: string;
28
50
  }
29
- export type LoopHarness = 'claude' | 'cursor' | 'codex' | 'grok' | 'opencode' | 'shell';
30
51
  export type LoopRunStatus = 'ok' | 'error' | 'running' | 'skipped';
31
52
  export type LoopSchedule = {
32
53
  kind: 'interval';
@@ -139,7 +160,7 @@ export interface Command {
139
160
  payload: CommandPayload;
140
161
  requestId: string;
141
162
  }
142
- export type CommandType = 'create_session' | 'stop_session' | 'list_sessions' | 'get_history' | 'retry_session' | 'send_message' | 'rename_session' | 'remove_session' | 'bridge_exec' | 'clear_terminal' | 'update_session_parent' | 'create_loop' | 'list_loops' | 'update_loop' | 'delete_loop' | 'run_loop_now' | 'get_loop_runs';
163
+ export type CommandType = 'create_session' | 'stop_session' | 'list_sessions' | 'get_history' | 'retry_session' | 'send_message' | 'rename_session' | 'remove_session' | 'bridge_exec' | 'clear_terminal' | 'update_session_parent' | 'get_session_usage' | 'create_loop' | 'list_loops' | 'update_loop' | 'delete_loop' | 'run_loop_now' | 'get_loop_runs';
143
164
  export interface CreateSessionPayload {
144
165
  command: string;
145
166
  prompt?: string;
@@ -188,6 +209,10 @@ export interface RemoveSessionPayload {
188
209
  export interface ClearTerminalPayload {
189
210
  sessionId: string;
190
211
  }
212
+ export interface GetSessionUsagePayload {
213
+ sessionId: string;
214
+ bridgeId?: string;
215
+ }
191
216
  export interface CreateLoopPayload extends LoopDraft {
192
217
  bridgeId: string;
193
218
  }
@@ -211,7 +236,7 @@ export interface GetLoopRunsPayload {
211
236
  bridgeId?: string;
212
237
  loopId: string;
213
238
  }
214
- export type CommandPayload = CreateSessionPayload | StopSessionPayload | GetHistoryPayload | RenameSessionPayload | RemoveSessionPayload | BridgeExecPayload | ClearTerminalPayload | UpdateSessionParentPayload | CreateLoopPayload | ListLoopsPayload | UpdateLoopPayload | DeleteLoopPayload | RunLoopNowPayload | GetLoopRunsPayload | Record<string, unknown>;
239
+ export type CommandPayload = CreateSessionPayload | StopSessionPayload | GetHistoryPayload | RenameSessionPayload | RemoveSessionPayload | BridgeExecPayload | ClearTerminalPayload | UpdateSessionParentPayload | GetSessionUsagePayload | CreateLoopPayload | ListLoopsPayload | UpdateLoopPayload | DeleteLoopPayload | RunLoopNowPayload | GetLoopRunsPayload | Record<string, unknown>;
215
240
  export interface CommandResponse {
216
241
  requestId: string;
217
242
  success: boolean;
@@ -0,0 +1,42 @@
1
+ import type { Session, SessionUsage } from './types.js';
2
+ /**
3
+ * Per-session token usage extraction from harness-native session files.
4
+ *
5
+ * The extractor is keyed by which NATIVE session id is present on the Session,
6
+ * not by shellType: provider flavors (zai/kimi/deepseek/fireworks) run the
7
+ * claude CLI under the hood and get a claudeSessionId persisted by the hook
8
+ * pipeline, so any session with a claudeSessionId uses the claude extractor
9
+ * regardless of shellType. The kimi-code harness has no native session-id field
10
+ * on Session, so its extractor keys off workingDir + createdAt instead (see
11
+ * collectKimiCodeUsage). Sessions with none of these (cursor, grok, plain shell)
12
+ * have no structured usage source and yield null.
13
+ *
14
+ * TODO(opencode): add an opencode extractor once Session carries an
15
+ * opencodeSessionId (no such field exists yet).
16
+ *
17
+ * Robustness contract: missing files, unparseable lines, and I/O errors never
18
+ * throw — they degrade to null (or to a partial sum if the read-time cap fires
19
+ * mid-file).
20
+ */
21
+ export type UsageSessionRef = Pick<Session, 'shellType' | 'claudeSessionId' | 'codexSessionId' | 'workingDir'> & Partial<Pick<Session, 'createdAt'>>;
22
+ export interface UsageCollectorOptions {
23
+ /** Override for tests. Default: ~/.claude/projects */
24
+ claudeProjectsDir?: string;
25
+ /** Override for tests. Default: ~/.codex/sessions */
26
+ codexSessionsDir?: string;
27
+ /**
28
+ * Override for tests. Default: ~/.kimi-code. The kimi-code home dir holding
29
+ * session_index.jsonl and sessions/<...>/session_<uuid>/.
30
+ */
31
+ kimiCodeDir?: string;
32
+ /** Cap on wall-clock read time per collection. Default 15s. */
33
+ timeoutMs?: number;
34
+ }
35
+ /**
36
+ * Claude Code project-directory slug: every non-alphanumeric character of the
37
+ * working directory becomes '-'. Verified against real transcript dirs on this
38
+ * machine — e.g. /Users/x/projects/ftown/.claude/worktrees/foo maps to
39
+ * -Users-x-projects-ftown--claude-worktrees-foo ('.' → '-', '/' → '-').
40
+ */
41
+ export declare function claudeProjectSlug(workingDir: string): string;
42
+ export declare function collectSessionUsage(session: UsageSessionRef, options?: UsageCollectorOptions): Promise<SessionUsage | null>;
@@ -0,0 +1,317 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ import { createInterface } from 'node:readline';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ const DEFAULT_TIMEOUT_MS = 15_000;
7
+ /**
8
+ * Claude Code project-directory slug: every non-alphanumeric character of the
9
+ * working directory becomes '-'. Verified against real transcript dirs on this
10
+ * machine — e.g. /Users/x/projects/ftown/.claude/worktrees/foo maps to
11
+ * -Users-x-projects-ftown--claude-worktrees-foo ('.' → '-', '/' → '-').
12
+ */
13
+ export function claudeProjectSlug(workingDir) {
14
+ return workingDir.replace(/[^a-zA-Z0-9]/g, '-');
15
+ }
16
+ export async function collectSessionUsage(session, options = {}) {
17
+ try {
18
+ if (session.claudeSessionId && session.workingDir) {
19
+ return await collectClaudeUsage(session.claudeSessionId, session.workingDir, options);
20
+ }
21
+ if (session.codexSessionId) {
22
+ return await collectCodexUsage(session.codexSessionId, options);
23
+ }
24
+ if (session.shellType === 'kimi-code' && session.workingDir) {
25
+ return await collectKimiCodeUsage(session.workingDir, session.createdAt, options);
26
+ }
27
+ return null;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /** Stream a JSONL file line by line (files can be tens of MB — never buffer whole). */
34
+ async function* jsonlLines(filePath, deadline) {
35
+ const stream = createReadStream(filePath, { encoding: 'utf8' });
36
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
37
+ try {
38
+ for await (const line of rl) {
39
+ if (Date.now() > deadline)
40
+ break;
41
+ if (!line)
42
+ continue;
43
+ try {
44
+ yield JSON.parse(line);
45
+ }
46
+ catch {
47
+ // Skip unparseable lines (truncated tail writes, etc.).
48
+ }
49
+ }
50
+ }
51
+ finally {
52
+ rl.close();
53
+ stream.destroy();
54
+ }
55
+ }
56
+ async function collectClaudeUsage(claudeSessionId, workingDir, options) {
57
+ const baseDir = options.claudeProjectsDir ?? join(homedir(), '.claude', 'projects');
58
+ const filePath = join(baseDir, claudeProjectSlug(workingDir), `${claudeSessionId}.jsonl`);
59
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
60
+ let counted = 0;
61
+ // Keyed by model id; Map preserves first-appearance order.
62
+ const byModel = new Map();
63
+ // An assistant message spans MULTIPLE jsonl lines (one per content block),
64
+ // each repeating the same message.id and identical usage — count each
65
+ // message id exactly once or sums double/triple.
66
+ const seenMessageIds = new Set();
67
+ for await (const raw of jsonlLines(filePath, deadline)) {
68
+ const entry = raw;
69
+ if (entry?.type !== 'assistant')
70
+ continue;
71
+ const message = entry.message;
72
+ const usage = message?.usage;
73
+ if (!message || !usage)
74
+ continue;
75
+ const model = message.model ?? '';
76
+ // '<synthetic>' rows are harness-injected placeholders with zero usage.
77
+ if (!model || model === '<synthetic>')
78
+ continue;
79
+ const id = message.id;
80
+ if (id) {
81
+ if (seenMessageIds.has(id))
82
+ continue;
83
+ seenMessageIds.add(id);
84
+ }
85
+ // Models can vary mid-session — attribute tokens per message's model.
86
+ let acc = byModel.get(model);
87
+ if (!acc) {
88
+ acc = { model, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
89
+ byModel.set(model, acc);
90
+ }
91
+ acc.inputTokens += usage.input_tokens ?? 0;
92
+ acc.outputTokens += usage.output_tokens ?? 0;
93
+ acc.cacheReadTokens += usage.cache_read_input_tokens ?? 0;
94
+ acc.cacheWriteTokens += usage.cache_creation_input_tokens ?? 0;
95
+ counted += 1;
96
+ }
97
+ if (counted === 0)
98
+ return null;
99
+ const perModel = [...byModel.values()];
100
+ const sum = (pick) => perModel.reduce((acc, m) => acc + pick(m), 0);
101
+ const inputTokens = sum((m) => m.inputTokens);
102
+ const outputTokens = sum((m) => m.outputTokens);
103
+ const cacheReadTokens = sum((m) => m.cacheReadTokens);
104
+ const cacheWriteTokens = sum((m) => m.cacheWriteTokens);
105
+ return {
106
+ inputTokens,
107
+ outputTokens,
108
+ cacheReadTokens,
109
+ cacheWriteTokens,
110
+ totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
111
+ models: perModel.map((m) => m.model),
112
+ perModel,
113
+ harness: 'claude',
114
+ collectedAt: new Date().toISOString(),
115
+ };
116
+ }
117
+ /** Resolve ~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-*-<id>.jsonl (date-partitioned). */
118
+ async function findCodexRollout(baseDir, codexSessionId) {
119
+ const suffix = `-${codexSessionId}.jsonl`;
120
+ const listDirs = async (dir) => {
121
+ try {
122
+ const entries = await readdir(dir, { withFileTypes: true });
123
+ // Newest partitions first — sessions are usually recent.
124
+ return entries
125
+ .filter((e) => e.isDirectory())
126
+ .map((e) => e.name)
127
+ .sort()
128
+ .reverse();
129
+ }
130
+ catch {
131
+ return [];
132
+ }
133
+ };
134
+ for (const year of await listDirs(baseDir)) {
135
+ for (const month of await listDirs(join(baseDir, year))) {
136
+ for (const day of await listDirs(join(baseDir, year, month))) {
137
+ const dir = join(baseDir, year, month, day);
138
+ let files;
139
+ try {
140
+ files = await readdir(dir);
141
+ }
142
+ catch {
143
+ continue;
144
+ }
145
+ const hit = files.find((f) => f.startsWith('rollout-') && f.endsWith(suffix));
146
+ if (hit)
147
+ return join(dir, hit);
148
+ }
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ async function collectCodexUsage(codexSessionId, options) {
154
+ const baseDir = options.codexSessionsDir ?? join(homedir(), '.codex', 'sessions');
155
+ const filePath = await findCodexRollout(baseDir, codexSessionId);
156
+ if (!filePath)
157
+ return null;
158
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
159
+ const models = [];
160
+ let lastTotals = null;
161
+ for await (const raw of jsonlLines(filePath, deadline)) {
162
+ const entry = raw;
163
+ const payload = entry?.payload;
164
+ if (!payload)
165
+ continue;
166
+ if (entry.type === 'turn_context' && payload.model) {
167
+ if (!models.includes(payload.model))
168
+ models.push(payload.model);
169
+ continue;
170
+ }
171
+ // token_count events carry cumulative totals — the LAST one wins. Some
172
+ // token_count events have info: null (rate-limit-only updates); skip them.
173
+ if (payload.type === 'token_count') {
174
+ const totals = payload.info?.total_token_usage;
175
+ if (totals) {
176
+ lastTotals = {
177
+ input: totals.input_tokens ?? 0,
178
+ cached: totals.cached_input_tokens ?? 0,
179
+ output: totals.output_tokens ?? 0,
180
+ };
181
+ }
182
+ }
183
+ }
184
+ if (!lastTotals)
185
+ return null;
186
+ // Codex counts cached tokens INSIDE input_tokens (input 11276 / cached 9088 /
187
+ // total 11305 = 11276 + 29 in real rollouts) — split them so totalTokens
188
+ // (sum of the four) matches codex's own total_tokens. output_tokens already
189
+ // includes reasoning tokens. Codex reports no cache writes.
190
+ const cacheReadTokens = Math.min(lastTotals.cached, lastTotals.input);
191
+ const inputTokens = lastTotals.input - cacheReadTokens;
192
+ const outputTokens = lastTotals.output;
193
+ const cacheWriteTokens = 0;
194
+ // Codex rollouts carry only cumulative totals — there is no per-model
195
+ // attribution, so perModel is deliberately absent (models lists what ran).
196
+ return {
197
+ inputTokens,
198
+ outputTokens,
199
+ cacheReadTokens,
200
+ cacheWriteTokens,
201
+ totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
202
+ models,
203
+ harness: 'codex',
204
+ collectedAt: new Date().toISOString(),
205
+ };
206
+ }
207
+ /**
208
+ * Resolve the kimi-code session dir that this ftown session spawned.
209
+ *
210
+ * session_index.jsonl is an append log mapping workDir → sessionDir; multiple
211
+ * lines can share a workDir (successive runs in the same directory). Among the
212
+ * candidates matching this session's workingDir we pick by OWN creation time:
213
+ * each candidate's session dir has a state.json whose createdAt is when kimi
214
+ * started. The ftown session spawned kimi, so the true match is the newest
215
+ * candidate created at-or-after the ftown session — falling back to the newest
216
+ * overall when clock skew / a missing state.json leaves none at-or-after.
217
+ */
218
+ async function resolveKimiSessionDir(baseDir, indexPath, workingDir, sessionCreatedAt, deadline) {
219
+ // Dedupe: the same sessionDir can appear on multiple index lines.
220
+ const dirs = new Set();
221
+ for await (const raw of jsonlLines(indexPath, deadline)) {
222
+ const line = raw;
223
+ if (line?.workDir === workingDir && typeof line.sessionDir === 'string') {
224
+ dirs.add(line.sessionDir);
225
+ }
226
+ }
227
+ if (dirs.size === 0)
228
+ return null;
229
+ // Read each candidate's own creation time from its state.json.
230
+ const candidates = [];
231
+ for (const dir of dirs) {
232
+ try {
233
+ const parsed = JSON.parse(await readFile(join(dir, 'state.json'), 'utf8'));
234
+ const createdAtMs = parsed?.createdAt ? Date.parse(parsed.createdAt) : NaN;
235
+ candidates.push({ dir, createdAtMs: Number.isNaN(createdAtMs) ? 0 : createdAtMs });
236
+ }
237
+ catch {
238
+ // Missing / unparseable state.json — still a candidate, just undatable.
239
+ candidates.push({ dir, createdAtMs: 0 });
240
+ }
241
+ }
242
+ const sessionMs = sessionCreatedAt ? Date.parse(sessionCreatedAt) : NaN;
243
+ const newest = (list) => list.reduce((best, c) => (c.createdAtMs >= best.createdAtMs ? c : best));
244
+ if (!Number.isNaN(sessionMs)) {
245
+ const atOrAfter = candidates.filter((c) => c.createdAtMs >= sessionMs);
246
+ if (atOrAfter.length > 0)
247
+ return newest(atOrAfter).dir;
248
+ }
249
+ return newest(candidates).dir;
250
+ }
251
+ async function collectKimiCodeUsage(workingDir, sessionCreatedAt, options) {
252
+ const baseDir = options.kimiCodeDir ?? join(homedir(), '.kimi-code');
253
+ const indexPath = join(baseDir, 'session_index.jsonl');
254
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
255
+ const sessionDir = await resolveKimiSessionDir(baseDir, indexPath, workingDir, sessionCreatedAt, deadline);
256
+ if (!sessionDir)
257
+ return null;
258
+ // Sum usage.record events across every agent's wire.jsonl (main + sub-agents).
259
+ const agentsDir = join(sessionDir, 'agents');
260
+ let agentNames;
261
+ try {
262
+ const entries = await readdir(agentsDir, { withFileTypes: true });
263
+ // Sort for deterministic per-model first-appearance order across agents.
264
+ agentNames = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ let counted = 0;
270
+ // Keyed by model id; Map preserves first-appearance order across agents.
271
+ const byModel = new Map();
272
+ for (const agent of agentNames) {
273
+ const wirePath = join(agentsDir, agent, 'wire.jsonl');
274
+ for await (const raw of jsonlLines(wirePath, deadline)) {
275
+ const entry = raw;
276
+ if (entry?.type !== 'usage.record')
277
+ continue;
278
+ const usage = entry.usage;
279
+ if (!usage)
280
+ continue;
281
+ const model = entry.model ?? '';
282
+ if (!model)
283
+ continue;
284
+ let acc = byModel.get(model);
285
+ if (!acc) {
286
+ acc = { model, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
287
+ byModel.set(model, acc);
288
+ }
289
+ // Per-turn incremental records — SUM across all of them.
290
+ acc.inputTokens += usage.inputOther ?? 0;
291
+ acc.outputTokens += usage.output ?? 0;
292
+ acc.cacheReadTokens += usage.inputCacheRead ?? 0;
293
+ acc.cacheWriteTokens += usage.inputCacheCreation ?? 0;
294
+ counted += 1;
295
+ }
296
+ }
297
+ if (counted === 0)
298
+ return null;
299
+ const perModel = [...byModel.values()];
300
+ const sum = (pick) => perModel.reduce((acc, m) => acc + pick(m), 0);
301
+ const inputTokens = sum((m) => m.inputTokens);
302
+ const outputTokens = sum((m) => m.outputTokens);
303
+ const cacheReadTokens = sum((m) => m.cacheReadTokens);
304
+ const cacheWriteTokens = sum((m) => m.cacheWriteTokens);
305
+ return {
306
+ inputTokens,
307
+ outputTokens,
308
+ cacheReadTokens,
309
+ cacheWriteTokens,
310
+ totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
311
+ models: perModel.map((m) => m.model),
312
+ perModel,
313
+ harness: 'kimi-code',
314
+ collectedAt: new Date().toISOString(),
315
+ };
316
+ }
317
+ //# sourceMappingURL=usage-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-collector.js","sourceRoot":"","sources":["../src/usage-collector.ts"],"names":[],"mappings":"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"}