ftown-bridge 0.19.0 → 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.
- package/dist/agent-commands.d.ts +3 -17
- package/dist/agent-commands.js +8 -90
- package/dist/agent-commands.js.map +1 -1
- package/dist/bridge-auth.d.ts +21 -0
- package/dist/bridge-auth.js +48 -0
- package/dist/bridge-auth.js.map +1 -0
- package/dist/centrifugo-client.js +39 -0
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/command-rpc.d.ts +15 -0
- package/dist/command-rpc.js +249 -0
- package/dist/command-rpc.js.map +1 -0
- package/dist/create-ftown-session.d.ts +43 -0
- package/dist/create-ftown-session.js +115 -9
- package/dist/create-ftown-session.js.map +1 -1
- package/dist/ftown-sessions-cli.js +74 -4
- package/dist/ftown-sessions-cli.js.map +1 -1
- package/dist/harness-cli.js +1 -13
- package/dist/harness-cli.js.map +1 -1
- package/dist/harness-installer.js +4 -2
- package/dist/harness-installer.js.map +1 -1
- package/dist/harness-registry.d.ts +189 -0
- package/dist/harness-registry.js +218 -0
- package/dist/harness-registry.js.map +1 -0
- package/dist/index.js +72 -721
- package/dist/index.js.map +1 -1
- package/dist/install-ftown-cli.js +12 -3
- package/dist/install-ftown-cli.js.map +1 -1
- package/dist/local-api-server.d.ts +14 -13
- package/dist/local-api-server.js +180 -424
- package/dist/local-api-server.js.map +1 -1
- package/dist/loop-controller.d.ts +87 -0
- package/dist/loop-controller.js +133 -0
- package/dist/loop-controller.js.map +1 -0
- package/dist/loop-run-store.js +2 -8
- package/dist/loop-run-store.js.map +1 -1
- package/dist/loop-validation.d.ts +1 -0
- package/dist/loop-validation.js +3 -8
- package/dist/loop-validation.js.map +1 -1
- package/dist/mail-delivery.d.ts +114 -0
- package/dist/mail-delivery.js +315 -0
- package/dist/mail-delivery.js.map +1 -0
- package/dist/session-controller.d.ts +107 -0
- package/dist/session-controller.js +141 -0
- package/dist/session-controller.js.map +1 -0
- package/dist/session-ids.d.ts +19 -0
- package/dist/session-ids.js +72 -0
- package/dist/session-ids.js.map +1 -0
- package/dist/session-resurrection.d.ts +24 -0
- package/dist/session-resurrection.js +128 -0
- package/dist/session-resurrection.js.map +1 -1
- package/dist/spawn-stagger.d.ts +22 -0
- package/dist/spawn-stagger.js +34 -0
- package/dist/spawn-stagger.js.map +1 -0
- package/dist/terminal-pump.d.ts +61 -0
- package/dist/terminal-pump.js +193 -0
- package/dist/terminal-pump.js.map +1 -0
- package/dist/types.d.ts +29 -4
- package/dist/usage-collector.d.ts +42 -0
- package/dist/usage-collector.js +317 -0
- package/dist/usage-collector.js.map +1 -0
- package/dist/wire-types.d.ts +124 -0
- package/dist/wire-types.js +28 -0
- package/dist/wire-types.js.map +1 -0
- package/dist/workflow-runner-cli.js +2 -1
- package/dist/workflow-runner-cli.js.map +1 -1
- package/dist/workflow-runner.d.ts +2 -1
- package/dist/workflow-runner.js.map +1 -1
- package/package.json +1 -1
- package/skills/factory/SKILL.md +167 -0
- package/skills/factory/factory-template/bin/dispatch.py +472 -0
- package/skills/factory/factory-template/factory.yaml +36 -0
- package/skills/factory/factory-template/skills/_protocol.md +85 -0
- package/skills/factory/factory-template/skills/design.md +259 -0
- package/skills/factory/factory-template/skills/digest.md +78 -0
- package/skills/factory/factory-template/skills/groom.md +243 -0
- package/skills/factory/factory-template/skills/implement.md +316 -0
- package/skills/factory/factory-template/skills/pr.md +220 -0
- package/skills/factory/factory-template/skills/qa.md +226 -0
- package/skills/factory/factory-template/skills/review.md +231 -0
- package/skills/factory/factory-template/skills/triage.md +157 -0
|
@@ -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
|
|
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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"}
|