codedeck 0.6.0 → 0.7.0
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/README.md +1 -1
- package/dist/cli/commands/open.js +94 -2
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/commands/usage-backfill.js +149 -0
- package/dist/cli/commands/usage-backfill.js.map +1 -0
- package/dist/cli/commands/usage.js +49 -4
- package/dist/cli/commands/usage.js.map +1 -1
- package/dist/core/claude-transcript.js +139 -0
- package/dist/core/claude-transcript.js.map +1 -0
- package/dist/core/pricing.js +14 -5
- package/dist/core/pricing.js.map +1 -1
- package/dist/core/run-usage.js +53 -10
- package/dist/core/run-usage.js.map +1 -1
- package/dist/core/usage-source.js +17 -0
- package/dist/core/usage-source.js.map +1 -0
- package/dist/daemon/daemon.js +216 -8
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/ipc.js +4 -3
- package/dist/daemon/ipc.js.map +1 -1
- package/dist/drivers/session-driver.js +7 -1
- package/dist/drivers/session-driver.js.map +1 -1
- package/dist/open/launchers/claude.js +78 -2
- package/dist/open/launchers/claude.js.map +1 -1
- package/dist/open/link-watcher.js +44 -0
- package/dist/open/link-watcher.js.map +1 -0
- package/dist/open/runtime.js +15 -12
- package/dist/open/runtime.js.map +1 -1
- package/dist/plugin/hooks/register.tsx +7 -11
- package/dist/plugin/hooks/session-id.sh +1 -1
- package/dist/plugin/mods/agents/pane.ts +35 -29
- package/dist/plugin/mods/agents/types.ts +5 -1
- package/dist/plugin/statusline.sh +40 -3
- package/dist/store/database.js +44 -0
- package/dist/store/database.js.map +1 -1
- package/dist/store/native-links.js +80 -0
- package/dist/store/native-links.js.map +1 -0
- package/dist/store/sessions.js +113 -41
- package/dist/store/sessions.js.map +1 -1
- package/dist/store/usage-ledger.js +153 -0
- package/dist/store/usage-ledger.js.map +1 -0
- package/package.json +1 -1
|
@@ -47,13 +47,8 @@ function vocab(entries: Record<string, string>): Record<string, string> {
|
|
|
47
47
|
return Object.assign(Object.create(null) as Record<string, string>, entries);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const STATUS_DOT = vocab({
|
|
51
|
-
working: "●",
|
|
52
|
-
starting: "●",
|
|
53
|
-
needs_input: "◉",
|
|
54
|
-
});
|
|
55
50
|
const STATUS_WORD = vocab({
|
|
56
|
-
working: "Working
|
|
51
|
+
working: "Working",
|
|
57
52
|
starting: "Starting",
|
|
58
53
|
needs_input: "Waiting for you",
|
|
59
54
|
completed: "Completed",
|
|
@@ -111,6 +106,8 @@ function toPaneRow(row: SessionRow): PaneRow {
|
|
|
111
106
|
id: row.id,
|
|
112
107
|
status: row.status || EMPTY_CELL,
|
|
113
108
|
agent: row.agent || EMPTY_CELL,
|
|
109
|
+
model: text(row.model) || undefined,
|
|
110
|
+
effort: text(row.effort) || undefined,
|
|
114
111
|
name: row.name || EMPTY_CELL,
|
|
115
112
|
updatedAt: iso,
|
|
116
113
|
};
|
|
@@ -133,7 +130,11 @@ export function selectPane(rows: SessionRow[], runId: string, budget?: number):
|
|
|
133
130
|
);
|
|
134
131
|
const orchestratorRow = matching.find((row) => row.origin === "open");
|
|
135
132
|
const orchestrator = orchestratorRow
|
|
136
|
-
? {
|
|
133
|
+
? {
|
|
134
|
+
agent: text(orchestratorRow.agent) || EMPTY_CELL,
|
|
135
|
+
model: text(orchestratorRow.model) || undefined,
|
|
136
|
+
effort: text(orchestratorRow.effort) || undefined,
|
|
137
|
+
}
|
|
137
138
|
: undefined;
|
|
138
139
|
const workers = matching.filter((row) => row.origin !== "open");
|
|
139
140
|
const ordered = [...workers].sort(compareRows);
|
|
@@ -179,16 +180,11 @@ function harnessLabel(agent: unknown): string {
|
|
|
179
180
|
return HARNESS_LABEL[key] ?? cell(agent);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
function dot(status: unknown): string {
|
|
183
|
-
return STATUS_DOT[text(status)] ?? "○";
|
|
184
|
-
}
|
|
185
|
-
|
|
186
183
|
function statusWord(status: unknown): string {
|
|
187
184
|
return STATUS_WORD[text(status)] ?? cell(status);
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
function footerLegend(columns: number):
|
|
191
|
-
const fullStatus = " ● working ◉ waiting ○ finished";
|
|
187
|
+
function footerLegend(columns: number): string {
|
|
192
188
|
const fullHarness =
|
|
193
189
|
" " +
|
|
194
190
|
glyph("claude") +
|
|
@@ -199,15 +195,11 @@ function footerLegend(columns: number): [string, string] {
|
|
|
199
195
|
" Codex " +
|
|
200
196
|
glyph("omp") +
|
|
201
197
|
" OMP";
|
|
202
|
-
if (
|
|
203
|
-
return [fullStatus, fullHarness];
|
|
204
|
-
}
|
|
198
|
+
if (fullHarness.length <= columns - 2) return fullHarness;
|
|
205
199
|
|
|
206
|
-
const compactStatus = " ● wk ◉ wait ○ done";
|
|
207
200
|
const iconHarness =
|
|
208
201
|
" " + glyph("claude") + " " + glyph("opencode") + " " + glyph("codex") + " " + glyph("omp");
|
|
209
|
-
|
|
210
|
-
return [" ● ◉ ○", iconHarness];
|
|
202
|
+
return iconHarness;
|
|
211
203
|
}
|
|
212
204
|
|
|
213
205
|
function age(iso: unknown, now: number): string {
|
|
@@ -215,7 +207,7 @@ function age(iso: unknown, now: number): string {
|
|
|
215
207
|
const then = Date.parse(iso);
|
|
216
208
|
if (Number.isNaN(then)) return "";
|
|
217
209
|
const minutes = Math.floor((now - then) / 60000);
|
|
218
|
-
if (minutes < 1) return "
|
|
210
|
+
if (minutes < 1) return "now";
|
|
219
211
|
if (minutes < 60) return `${minutes}m`;
|
|
220
212
|
return `${Math.floor(minutes / 60)}h`;
|
|
221
213
|
}
|
|
@@ -224,6 +216,16 @@ function count(value: unknown): number {
|
|
|
224
216
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
|
225
217
|
}
|
|
226
218
|
|
|
219
|
+
function detailLine(model: unknown, effort: unknown, status: string, when: string, width: number): string {
|
|
220
|
+
const state = status ? `${status}${when ? ` · ${when}` : ""}` : "";
|
|
221
|
+
const suffix = [text(effort).trim() ? `${cell(effort)} effort` : "", state]
|
|
222
|
+
.filter(Boolean)
|
|
223
|
+
.join(" · ");
|
|
224
|
+
const modelWidth = width - 3 - suffix.length - (suffix ? 3 : 0);
|
|
225
|
+
const modelText = text(model).trim() && modelWidth > 0 ? fit(cell(model), modelWidth).trimEnd() : "";
|
|
226
|
+
return ` ${[modelText, suffix].filter(Boolean).join(" · ")}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
227
229
|
// How old a row looks for eviction: an unparseable timestamp is the oldest
|
|
228
230
|
// possible age, matching how compareRows parks such rows at the end of the
|
|
229
231
|
// display. The orchestrator pins itself to +Infinity so it is the very last
|
|
@@ -256,7 +258,7 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
256
258
|
);
|
|
257
259
|
const orchestrator =
|
|
258
260
|
source.orchestrator && typeof source.orchestrator === "object"
|
|
259
|
-
? (source.orchestrator as Partial<{ agent: string }>)
|
|
261
|
+
? (source.orchestrator as Partial<{ agent: string; model: string; effort: string }>)
|
|
260
262
|
: undefined;
|
|
261
263
|
const hidden = count(source.hidden);
|
|
262
264
|
const total = count(source.total);
|
|
@@ -322,8 +324,11 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
322
324
|
if (orchestrator !== undefined) {
|
|
323
325
|
blocks.push(
|
|
324
326
|
cardBlock(Number.POSITIVE_INFINITY, "", [
|
|
325
|
-
` ${glyph(orchestrator.agent)}
|
|
327
|
+
` ${glyph(orchestrator.agent)} Orchestrator`,
|
|
326
328
|
` ${harnessLabel(orchestrator.agent)} · ${workerTotal} agents`,
|
|
329
|
+
...(text(orchestrator.model).trim() || text(orchestrator.effort).trim()
|
|
330
|
+
? [detailLine(orchestrator.model, orchestrator.effort, "", "", INNER)]
|
|
331
|
+
: []),
|
|
327
332
|
]),
|
|
328
333
|
);
|
|
329
334
|
}
|
|
@@ -337,9 +342,9 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
337
342
|
const word = statusWord(status);
|
|
338
343
|
blocks.push(
|
|
339
344
|
cardBlock(rowAge(row.updatedAt), cell(row.id), [
|
|
340
|
-
` ${glyph(row.agent)} ${
|
|
345
|
+
` ${glyph(row.agent)} ${cell(row.id)} ${harnessLabel(row.agent)}`,
|
|
341
346
|
` ${cell(row.name)}`,
|
|
342
|
-
|
|
347
|
+
detailLine(row.model, row.effort, word, when, INNER),
|
|
343
348
|
]),
|
|
344
349
|
);
|
|
345
350
|
}
|
|
@@ -373,8 +378,7 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
373
378
|
const stemAbove = (list: Block[], i: number): boolean =>
|
|
374
379
|
i > 0 && (list[i].kind === "card" || list[i - 1].kind === "card");
|
|
375
380
|
|
|
376
|
-
//
|
|
377
|
-
// they are drawn, and they are never the lines that the fit cuts.
|
|
381
|
+
// These frame lines are always drawn when the pane has content.
|
|
378
382
|
const head = [
|
|
379
383
|
frameTop(`Run canvas ${cell(source.runId)}`),
|
|
380
384
|
frameRow(` ${total} sessions · ${working} working`),
|
|
@@ -382,11 +386,9 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
382
386
|
frameSep(),
|
|
383
387
|
frameRow(""),
|
|
384
388
|
];
|
|
385
|
-
const
|
|
389
|
+
const harnessLegend = footerLegend(W);
|
|
386
390
|
const tail = [
|
|
387
|
-
frameRow(""),
|
|
388
391
|
frameSep(),
|
|
389
|
-
frameRow(statusLegend),
|
|
390
392
|
frameRow(harnessLegend),
|
|
391
393
|
frameBot(),
|
|
392
394
|
];
|
|
@@ -434,6 +436,10 @@ function draw(snapshot: PaneSnapshot, columns: number, limit: number | undefined
|
|
|
434
436
|
if (kept.length > 0 && kept[kept.length - 1].kind === "card") out.push(stem(), hiddenRow);
|
|
435
437
|
else out.push(hiddenRow);
|
|
436
438
|
}
|
|
439
|
+
if (limit !== undefined) {
|
|
440
|
+
const spare = limit - out.length - tail.length;
|
|
441
|
+
for (let i = 0; i < spare; i += 1) out.push(frameRow(""));
|
|
442
|
+
}
|
|
437
443
|
out.push(...tail);
|
|
438
444
|
return out;
|
|
439
445
|
}
|
|
@@ -13,6 +13,8 @@ export interface SessionRow {
|
|
|
13
13
|
origin?: string | null;
|
|
14
14
|
name?: string;
|
|
15
15
|
agent?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
effort?: string;
|
|
16
18
|
status?: string;
|
|
17
19
|
updatedAt?: string;
|
|
18
20
|
}
|
|
@@ -26,6 +28,8 @@ export interface PaneRow {
|
|
|
26
28
|
id: string;
|
|
27
29
|
status: string;
|
|
28
30
|
agent: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
effort?: string;
|
|
29
33
|
name: string;
|
|
30
34
|
updatedAt?: string;
|
|
31
35
|
}
|
|
@@ -34,7 +38,7 @@ export interface PaneRow {
|
|
|
34
38
|
export interface PaneSnapshot {
|
|
35
39
|
runId: string;
|
|
36
40
|
/** The orchestrator's own row, the only one whose origin is "open". */
|
|
37
|
-
orchestrator: { agent: string } | undefined;
|
|
41
|
+
orchestrator: { agent: string; model?: string; effort?: string } | undefined;
|
|
38
42
|
rows: PaneRow[];
|
|
39
43
|
/** Rows that matched the run but fell outside the budget. */
|
|
40
44
|
hidden: number;
|
|
@@ -181,8 +181,17 @@ const getRunUsage = () => {
|
|
|
181
181
|
const runId = text(process.env.CODEDECK_RUN_ID);
|
|
182
182
|
if (!runId) return undefined;
|
|
183
183
|
|
|
184
|
+
const args = ["usage", runId, "--json"];
|
|
185
|
+
const sessionId = payload.session_id;
|
|
186
|
+
const observeCost = nonNegativeNumber(payload.cost?.total_cost_usd);
|
|
187
|
+
if (
|
|
188
|
+
typeof sessionId === "string" &&
|
|
189
|
+
/^[0-9a-fA-F-]{8,}$/.test(sessionId) &&
|
|
190
|
+
observeCost !== undefined
|
|
191
|
+
) args.push("--observe", `${sessionId}=${observeCost}`);
|
|
192
|
+
|
|
184
193
|
try {
|
|
185
|
-
const output = execFileSync("codedeck",
|
|
194
|
+
const output = execFileSync("codedeck", args, {
|
|
186
195
|
encoding: "utf8",
|
|
187
196
|
stdio: ["ignore", "pipe", "ignore"],
|
|
188
197
|
timeout: 1000,
|
|
@@ -215,8 +224,30 @@ const getRunUsage = () => {
|
|
|
215
224
|
}
|
|
216
225
|
};
|
|
217
226
|
|
|
227
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
228
|
+
|
|
229
|
+
const getOrchestratorUsage = (usage) => {
|
|
230
|
+
const value = usage.orchestrator;
|
|
231
|
+
if (
|
|
232
|
+
!isObject(value) ||
|
|
233
|
+
nonNegativeNumber(value.costUsd) === undefined ||
|
|
234
|
+
typeof value.costComplete !== "boolean" ||
|
|
235
|
+
nonNegativeNumber(value.inputTokens) === undefined ||
|
|
236
|
+
nonNegativeNumber(value.outputTokens) === undefined ||
|
|
237
|
+
nonNegativeNumber(value.cachedTokens) === undefined ||
|
|
238
|
+
!Array.isArray(value.sources)
|
|
239
|
+
) return undefined;
|
|
240
|
+
if (!value.sources.every((source) =>
|
|
241
|
+
isObject(source) &&
|
|
242
|
+
typeof source.nativeId === "string" &&
|
|
243
|
+
nonNegativeNumber(source.costUsd) !== undefined
|
|
244
|
+
)) return undefined;
|
|
245
|
+
return value;
|
|
246
|
+
};
|
|
247
|
+
|
|
218
248
|
const local = localCost();
|
|
219
249
|
const runUsage = getRunUsage();
|
|
250
|
+
const orchestratorUsage = runUsage ? getOrchestratorUsage(runUsage) : undefined;
|
|
220
251
|
const workerTokens = runUsage
|
|
221
252
|
? runUsage.inputTokens + runUsage.outputTokens + runUsage.cachedTokens
|
|
222
253
|
: undefined;
|
|
@@ -227,8 +258,14 @@ const tokenField = () => {
|
|
|
227
258
|
|
|
228
259
|
const runField = () => {
|
|
229
260
|
if (!runUsage) return undefined;
|
|
230
|
-
const
|
|
231
|
-
|
|
261
|
+
const sourceCost = orchestratorUsage
|
|
262
|
+
? orchestratorUsage.sources.reduce(
|
|
263
|
+
(sum, source) => source.nativeId === payload.session_id ? sum : sum + source.costUsd,
|
|
264
|
+
0,
|
|
265
|
+
)
|
|
266
|
+
: 0;
|
|
267
|
+
const total = (local ?? 0) + runUsage.costUsd + sourceCost;
|
|
268
|
+
const incomplete = !runUsage.costComplete || Boolean(orchestratorUsage && !orchestratorUsage.costComplete);
|
|
232
269
|
if (!incomplete && total < COST_DISPLAY_THRESHOLD) return undefined;
|
|
233
270
|
return paint(MUTED, "run ") + costAmount(total, incomplete);
|
|
234
271
|
};
|
package/dist/store/database.js
CHANGED
|
@@ -89,9 +89,53 @@ export class Database {
|
|
|
89
89
|
active INTEGER NOT NULL DEFAULT 1
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
+
CREATE TABLE IF NOT EXISTS usage_sources (
|
|
93
|
+
source_key TEXT PRIMARY KEY,
|
|
94
|
+
cost REAL,
|
|
95
|
+
input_tokens INTEGER,
|
|
96
|
+
output_tokens INTEGER,
|
|
97
|
+
cached_tokens INTEGER,
|
|
98
|
+
updated_at TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS usage_attributions (
|
|
102
|
+
session_id TEXT NOT NULL,
|
|
103
|
+
source_key TEXT NOT NULL,
|
|
104
|
+
cost REAL,
|
|
105
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
106
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
107
|
+
cached_tokens INTEGER NOT NULL DEFAULT 0,
|
|
108
|
+
PRIMARY KEY (session_id, source_key),
|
|
109
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
|
110
|
+
FOREIGN KEY (source_key) REFERENCES usage_sources(source_key) ON DELETE CASCADE
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS session_native_links (
|
|
114
|
+
session_id TEXT NOT NULL,
|
|
115
|
+
native_id TEXT NOT NULL,
|
|
116
|
+
linked_at TEXT NOT NULL,
|
|
117
|
+
reconciled_at TEXT,
|
|
118
|
+
state TEXT,
|
|
119
|
+
PRIMARY KEY (session_id, native_id),
|
|
120
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS usage_legacy (
|
|
124
|
+
native_id TEXT PRIMARY KEY,
|
|
125
|
+
ended_at TEXT NOT NULL,
|
|
126
|
+
cwd TEXT,
|
|
127
|
+
repository TEXT,
|
|
128
|
+
model TEXT,
|
|
129
|
+
cost REAL,
|
|
130
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
131
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
132
|
+
cached_tokens INTEGER NOT NULL DEFAULT 0
|
|
133
|
+
);
|
|
134
|
+
|
|
92
135
|
CREATE INDEX IF NOT EXISTS idx_events_session_seq ON events(session_id, sequence);
|
|
93
136
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
94
137
|
CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at DESC);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_usage_attributions_source_key ON usage_attributions(source_key);
|
|
95
139
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_claims_active_session_path
|
|
96
140
|
ON claims(session_id, path_glob) WHERE active = 1;
|
|
97
141
|
`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAM1D,MAAM,OAAO,QAAQ;IACX,EAAE,CAAe;IACjB,MAAM,CAAS;IACf,QAAQ,CAAU;IAE1B,YAAY,MAAe,EAAE,OAAyB;QACpD,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,UAAU,EAAE,CAAC;YACb,2BAA2B;YAC3B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SessionStore } from "./sessions.js";
|
|
2
|
+
function toNativeLink(row) {
|
|
3
|
+
return {
|
|
4
|
+
sessionId: row.session_id,
|
|
5
|
+
nativeId: row.native_id,
|
|
6
|
+
linkedAt: row.linked_at,
|
|
7
|
+
reconciledAt: row.reconciled_at,
|
|
8
|
+
state: row.state,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export class NativeLinkStore {
|
|
12
|
+
db;
|
|
13
|
+
constructor(db) {
|
|
14
|
+
this.db = db;
|
|
15
|
+
}
|
|
16
|
+
link(sessionId, nativeId) {
|
|
17
|
+
const previous = this.db.prepare(`
|
|
18
|
+
SELECT native_id FROM session_native_links
|
|
19
|
+
WHERE session_id = ? AND native_id <> ? AND reconciled_at IS NULL
|
|
20
|
+
ORDER BY linked_at, native_id
|
|
21
|
+
`).all(sessionId, nativeId).map((row) => row.native_id);
|
|
22
|
+
const result = this.db.prepare(`
|
|
23
|
+
INSERT INTO session_native_links (session_id, native_id, linked_at)
|
|
24
|
+
VALUES (?, ?, ?)
|
|
25
|
+
ON CONFLICT(session_id, native_id) DO NOTHING
|
|
26
|
+
`).run(sessionId, nativeId, new Date().toISOString());
|
|
27
|
+
return { created: Number(result.changes) > 0, previous };
|
|
28
|
+
}
|
|
29
|
+
unreconciled(sessionId) {
|
|
30
|
+
const rows = this.db.prepare(`
|
|
31
|
+
SELECT session_id, native_id, linked_at, reconciled_at, state
|
|
32
|
+
FROM session_native_links
|
|
33
|
+
WHERE session_id = ? AND reconciled_at IS NULL
|
|
34
|
+
ORDER BY linked_at, native_id
|
|
35
|
+
`).all(sessionId);
|
|
36
|
+
return rows.map(toNativeLink);
|
|
37
|
+
}
|
|
38
|
+
linksFor(sessionIds) {
|
|
39
|
+
if (sessionIds.length === 0)
|
|
40
|
+
return [];
|
|
41
|
+
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
42
|
+
const rows = this.db.prepare(`
|
|
43
|
+
SELECT session_id, native_id, linked_at, reconciled_at, state
|
|
44
|
+
FROM session_native_links
|
|
45
|
+
WHERE session_id IN (${placeholders})
|
|
46
|
+
ORDER BY session_id, linked_at, native_id
|
|
47
|
+
`).all(...sessionIds);
|
|
48
|
+
return rows.map(toNativeLink);
|
|
49
|
+
}
|
|
50
|
+
markReconciled(sessionId, nativeId, state) {
|
|
51
|
+
this.db.prepare(`
|
|
52
|
+
UPDATE session_native_links SET reconciled_at = ?, state = ?
|
|
53
|
+
WHERE session_id = ? AND native_id = ?
|
|
54
|
+
`).run(new Date().toISOString(), state, sessionId, nativeId);
|
|
55
|
+
}
|
|
56
|
+
staleOpenRows(isDead) {
|
|
57
|
+
const candidates = this.db.prepare(`
|
|
58
|
+
SELECT DISTINCT sessions.id, sessions.status
|
|
59
|
+
FROM sessions
|
|
60
|
+
INNER JOIN session_native_links
|
|
61
|
+
ON session_native_links.session_id = sessions.id
|
|
62
|
+
WHERE sessions.origin = 'open'
|
|
63
|
+
AND session_native_links.reconciled_at IS NULL
|
|
64
|
+
AND sessions.status IN ('interrupted', 'working')
|
|
65
|
+
ORDER BY sessions.created_at, sessions.id
|
|
66
|
+
`).all();
|
|
67
|
+
const sessions = new SessionStore(this.db);
|
|
68
|
+
const stale = [];
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
const session = sessions.get(candidate.id);
|
|
71
|
+
if (!session)
|
|
72
|
+
continue;
|
|
73
|
+
if (candidate.status === "interrupted" || (candidate.status === "working" && isDead(session))) {
|
|
74
|
+
stale.push(session);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return stale;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=native-links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-links.js","sourceRoot":"","sources":["../../src/store/native-links.ts"],"names":[],"mappings":"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"}
|
package/dist/store/sessions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isActiveStatus } from "../core/session.js";
|
|
2
|
-
import { computeSessionCost } from "../core/pricing.js";
|
|
2
|
+
import { cachedInInputFor, computeSessionCost } from "../core/pricing.js";
|
|
3
3
|
function rowToSession(row) {
|
|
4
4
|
// `failure` is stored as JSON text; tolerate a corrupt blob rather than
|
|
5
5
|
// losing the whole session row over it.
|
|
@@ -133,21 +133,29 @@ export class SessionStore {
|
|
|
133
133
|
worktree: patch.worktree,
|
|
134
134
|
branch: patch.branch,
|
|
135
135
|
base_commit: patch.baseCommit,
|
|
136
|
-
pid: patch.pid,
|
|
137
|
-
completed_at: patch.completedAt
|
|
136
|
+
pid: patch.pid === undefined ? undefined : patch.pid ?? null,
|
|
137
|
+
completed_at: patch.completedAt === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: patch.completedAt === null
|
|
140
|
+
? null
|
|
141
|
+
: patch.completedAt.toISOString(),
|
|
138
142
|
usage_input_tokens: patch.usage?.inputTokens,
|
|
139
143
|
usage_output_tokens: patch.usage?.outputTokens,
|
|
140
144
|
usage_cached_tokens: patch.usage?.cachedTokens,
|
|
141
145
|
usage_cost: patch.usage?.cost,
|
|
142
|
-
last_event: patch.lastEvent,
|
|
146
|
+
last_event: patch.lastEvent === undefined ? undefined : patch.lastEvent ?? null,
|
|
143
147
|
effort: patch.effort,
|
|
144
148
|
fast: patch.fast === undefined ? undefined : patch.fast ? 1 : 0,
|
|
145
149
|
sandbox: patch.sandbox,
|
|
146
150
|
dangerously_bypass_approvals_and_sandbox: patch.dangerouslyBypassApprovalsAndSandbox === undefined ? undefined : patch.dangerouslyBypassApprovalsAndSandbox ? 1 : 0,
|
|
147
|
-
pid_start_time: patch.pidStartTime,
|
|
151
|
+
pid_start_time: patch.pidStartTime === undefined ? undefined : patch.pidStartTime ?? null,
|
|
148
152
|
log_offset: patch.logOffset,
|
|
149
153
|
stderr_offset: patch.stderrOffset,
|
|
150
|
-
failure: patch.failure === undefined
|
|
154
|
+
failure: patch.failure === undefined
|
|
155
|
+
? undefined
|
|
156
|
+
: patch.failure === null
|
|
157
|
+
? null
|
|
158
|
+
: JSON.stringify(patch.failure),
|
|
151
159
|
origin: patch.origin,
|
|
152
160
|
pending_message: patch.pendingMessage === undefined ? undefined : (patch.pendingMessage ?? null),
|
|
153
161
|
pending_at: patch.pendingAt === undefined ? undefined : (patch.pendingAt ?? null),
|
|
@@ -182,6 +190,10 @@ export class SessionStore {
|
|
|
182
190
|
const row = this.db.prepare(`SELECT * FROM sessions WHERE native_session_id = ? LIMIT 1`).get(nativeId);
|
|
183
191
|
return row ? rowToSession(row) : null;
|
|
184
192
|
}
|
|
193
|
+
listOpenByNativeId(nativeId) {
|
|
194
|
+
const rows = this.db.prepare(`SELECT * FROM sessions WHERE origin = 'open' AND native_session_id = ? ORDER BY updated_at DESC, id ASC`).all(nativeId);
|
|
195
|
+
return rows.map(rowToSession);
|
|
196
|
+
}
|
|
185
197
|
queryUsage(params = {}) {
|
|
186
198
|
const { since, until } = resolveUsageDateRange(params);
|
|
187
199
|
const rows = this.db.prepare(`
|
|
@@ -189,11 +201,28 @@ export class SessionStore {
|
|
|
189
201
|
id, run_id, name, agent, model, status,
|
|
190
202
|
repository, cwd, worktree,
|
|
191
203
|
created_at, updated_at, completed_at,
|
|
192
|
-
usage_input_tokens, usage_output_tokens, usage_cached_tokens, usage_cost
|
|
204
|
+
usage_input_tokens, usage_output_tokens, usage_cached_tokens, usage_cost, origin
|
|
193
205
|
FROM sessions
|
|
194
206
|
WHERE created_at >= ? AND created_at <= ?
|
|
195
207
|
ORDER BY created_at ASC
|
|
196
208
|
`).all(since, until);
|
|
209
|
+
const hasLegacyTable = this.db.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'usage_legacy'`).get() !== undefined;
|
|
210
|
+
const legacyRows = hasLegacyTable
|
|
211
|
+
? this.db.prepare(`
|
|
212
|
+
SELECT native_id, ended_at, cwd, repository, model, cost,
|
|
213
|
+
input_tokens, output_tokens, cached_tokens
|
|
214
|
+
FROM usage_legacy
|
|
215
|
+
WHERE ended_at >= ? AND ended_at <= ?
|
|
216
|
+
ORDER BY ended_at ASC
|
|
217
|
+
`).all(since, until)
|
|
218
|
+
: [];
|
|
219
|
+
const unknownOpenCostSessionIds = new Set(this.db.prepare(`
|
|
220
|
+
SELECT DISTINCT session_native_links.session_id
|
|
221
|
+
FROM session_native_links
|
|
222
|
+
INNER JOIN sessions ON sessions.id = session_native_links.session_id
|
|
223
|
+
WHERE sessions.origin = 'open'
|
|
224
|
+
AND session_native_links.state IN ('missing', 'no-price')
|
|
225
|
+
`).all().map((row) => row.session_id));
|
|
197
226
|
const totals = {
|
|
198
227
|
sessionCount: 0,
|
|
199
228
|
activeSessionCount: 0,
|
|
@@ -212,10 +241,39 @@ export class SessionStore {
|
|
|
212
241
|
const byModelMap = new Map();
|
|
213
242
|
const byAgentMap = new Map();
|
|
214
243
|
const byRunMap = new Map();
|
|
244
|
+
const byOriginMap = new Map();
|
|
215
245
|
const repoFilter = params.repository?.toLowerCase();
|
|
216
246
|
const modelFilter = params.model?.toLowerCase();
|
|
217
247
|
const agentFilter = params.agent;
|
|
218
248
|
const runIdFilter = params.runId;
|
|
249
|
+
const accumulate = (map, key, inputTokens, outputTokens, cachedTokens, totalTokens, cost, label) => {
|
|
250
|
+
let bucket = map.get(key);
|
|
251
|
+
if (!bucket) {
|
|
252
|
+
bucket = {
|
|
253
|
+
key,
|
|
254
|
+
label: label ?? key,
|
|
255
|
+
sessionCount: 0,
|
|
256
|
+
inputTokens: 0,
|
|
257
|
+
outputTokens: 0,
|
|
258
|
+
cachedTokens: 0,
|
|
259
|
+
totalTokens: 0,
|
|
260
|
+
costUsd: 0,
|
|
261
|
+
costComplete: true,
|
|
262
|
+
};
|
|
263
|
+
map.set(key, bucket);
|
|
264
|
+
}
|
|
265
|
+
bucket.sessionCount++;
|
|
266
|
+
bucket.inputTokens += inputTokens;
|
|
267
|
+
bucket.outputTokens += outputTokens;
|
|
268
|
+
bucket.cachedTokens += cachedTokens;
|
|
269
|
+
bucket.totalTokens += totalTokens;
|
|
270
|
+
if (cost === null) {
|
|
271
|
+
bucket.costComplete = false;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
bucket.costUsd += cost;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
219
277
|
for (const row of rows) {
|
|
220
278
|
if (runIdFilter && row.run_id !== runIdFilter)
|
|
221
279
|
continue;
|
|
@@ -232,11 +290,13 @@ export class SessionStore {
|
|
|
232
290
|
const outputTokens = row.usage_output_tokens ?? 0;
|
|
233
291
|
const cachedTokens = row.usage_cached_tokens ?? 0;
|
|
234
292
|
const totalTokens = inputTokens + outputTokens + cachedTokens;
|
|
235
|
-
const
|
|
293
|
+
const calculatedCost = computeSessionCost({
|
|
236
294
|
model: row.model,
|
|
295
|
+
cachedInInput: cachedInInputFor(row.agent),
|
|
237
296
|
reportedCost: row.usage_cost,
|
|
238
297
|
usage: { inputTokens, outputTokens, cachedTokens },
|
|
239
298
|
});
|
|
299
|
+
const cost = unknownOpenCostSessionIds.has(row.id) ? null : calculatedCost;
|
|
240
300
|
totals.sessionCount++;
|
|
241
301
|
if (isActiveStatus(row.status))
|
|
242
302
|
totals.activeSessionCount++;
|
|
@@ -255,49 +315,59 @@ export class SessionStore {
|
|
|
255
315
|
else {
|
|
256
316
|
totals.costUsd += cost;
|
|
257
317
|
}
|
|
258
|
-
const accumulate = (map, key, label) => {
|
|
259
|
-
let bucket = map.get(key);
|
|
260
|
-
if (!bucket) {
|
|
261
|
-
bucket = {
|
|
262
|
-
key,
|
|
263
|
-
label: label ?? key,
|
|
264
|
-
sessionCount: 0,
|
|
265
|
-
inputTokens: 0,
|
|
266
|
-
outputTokens: 0,
|
|
267
|
-
cachedTokens: 0,
|
|
268
|
-
totalTokens: 0,
|
|
269
|
-
costUsd: 0,
|
|
270
|
-
costComplete: true,
|
|
271
|
-
};
|
|
272
|
-
map.set(key, bucket);
|
|
273
|
-
}
|
|
274
|
-
bucket.sessionCount++;
|
|
275
|
-
bucket.inputTokens += inputTokens;
|
|
276
|
-
bucket.outputTokens += outputTokens;
|
|
277
|
-
bucket.cachedTokens += cachedTokens;
|
|
278
|
-
bucket.totalTokens += totalTokens;
|
|
279
|
-
if (cost === null) {
|
|
280
|
-
bucket.costComplete = false;
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
bucket.costUsd += cost;
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
318
|
// Day (local date string YYYY-MM-DD)
|
|
287
319
|
const d = new Date(row.created_at);
|
|
288
320
|
const dayKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
289
|
-
accumulate(byDayMap, dayKey);
|
|
321
|
+
accumulate(byDayMap, dayKey, inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
290
322
|
// Repo (normalized project name across worktrees)
|
|
291
323
|
const repoKey = normalizeProjectName(row);
|
|
292
|
-
accumulate(byRepoMap, repoKey);
|
|
324
|
+
accumulate(byRepoMap, repoKey, inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
293
325
|
// Model
|
|
294
|
-
accumulate(byModelMap, row.model || "unknown");
|
|
326
|
+
accumulate(byModelMap, row.model || "unknown", inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
295
327
|
// Agent
|
|
296
|
-
accumulate(byAgentMap, row.agent);
|
|
328
|
+
accumulate(byAgentMap, row.agent, inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
297
329
|
// Run
|
|
298
330
|
if (row.run_id) {
|
|
299
|
-
accumulate(byRunMap, row.run_id, row.name ? `${row.name} (${row.run_id.slice(0, 8)})` : row.run_id.slice(0, 8));
|
|
331
|
+
accumulate(byRunMap, row.run_id, inputTokens, outputTokens, cachedTokens, totalTokens, cost, row.name ? `${row.name} (${row.run_id.slice(0, 8)})` : row.run_id.slice(0, 8));
|
|
300
332
|
}
|
|
333
|
+
accumulate(byOriginMap, row.origin === "open" ? "orchestrator" : "worker", inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
334
|
+
}
|
|
335
|
+
for (const row of legacyRows) {
|
|
336
|
+
if (runIdFilter)
|
|
337
|
+
continue;
|
|
338
|
+
if (agentFilter && agentFilter !== "claude")
|
|
339
|
+
continue;
|
|
340
|
+
if (modelFilter && (!row.model || !row.model.toLowerCase().includes(modelFilter)))
|
|
341
|
+
continue;
|
|
342
|
+
if (repoFilter) {
|
|
343
|
+
const repoStr = [row.repository, row.cwd].filter(Boolean).join(" ").toLowerCase();
|
|
344
|
+
if (!repoStr.includes(repoFilter))
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const inputTokens = row.input_tokens;
|
|
348
|
+
const outputTokens = row.output_tokens;
|
|
349
|
+
const cachedTokens = row.cached_tokens;
|
|
350
|
+
const totalTokens = inputTokens + outputTokens + cachedTokens;
|
|
351
|
+
const cost = row.cost;
|
|
352
|
+
totals.sessionCount++;
|
|
353
|
+
totals.inputTokens += inputTokens;
|
|
354
|
+
totals.outputTokens += outputTokens;
|
|
355
|
+
totals.cachedTokens += cachedTokens;
|
|
356
|
+
totals.totalTokens += totalTokens;
|
|
357
|
+
if (cost === null) {
|
|
358
|
+
totals.costComplete = false;
|
|
359
|
+
totals.sessionsWithoutCost++;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
totals.costUsd += cost;
|
|
363
|
+
}
|
|
364
|
+
const d = new Date(row.ended_at);
|
|
365
|
+
const dayKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
366
|
+
accumulate(byDayMap, dayKey, inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
367
|
+
accumulate(byRepoMap, normalizeProjectName({ repository: row.repository, cwd: row.cwd }), inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
368
|
+
accumulate(byModelMap, row.model || "unknown", inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
369
|
+
accumulate(byAgentMap, "claude", inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
370
|
+
accumulate(byOriginMap, "orchestrator", inputTokens, outputTokens, cachedTokens, totalTokens, cost);
|
|
301
371
|
}
|
|
302
372
|
const sortDescending = (a, b) => {
|
|
303
373
|
if (b.costUsd !== a.costUsd)
|
|
@@ -309,6 +379,7 @@ export class SessionStore {
|
|
|
309
379
|
const byModel = [...byModelMap.values()].sort(sortDescending);
|
|
310
380
|
const byAgent = [...byAgentMap.values()].sort(sortDescending);
|
|
311
381
|
const byRun = [...byRunMap.values()].sort(sortDescending);
|
|
382
|
+
const byOrigin = [...byOriginMap.values()].sort(sortDescending);
|
|
312
383
|
return {
|
|
313
384
|
range: {
|
|
314
385
|
period: params.period,
|
|
@@ -321,6 +392,7 @@ export class SessionStore {
|
|
|
321
392
|
byModel,
|
|
322
393
|
byAgent,
|
|
323
394
|
byRun,
|
|
395
|
+
byOrigin,
|
|
324
396
|
};
|
|
325
397
|
}
|
|
326
398
|
}
|