@basou/core 0.4.0 → 0.5.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/dist/index.d.ts +2724 -2328
- package/dist/index.js +843 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -21,27 +21,6 @@ function summarizeAdapterOutput(_stream, _raw) {
|
|
|
21
21
|
throw new Error("adapter_output summary is not implemented in this release");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// src/approval/approval-store.ts
|
|
25
|
-
import { readdir } from "fs/promises";
|
|
26
|
-
import { join } from "path";
|
|
27
|
-
|
|
28
|
-
// src/lib/error-codes.ts
|
|
29
|
-
function findErrorCode(error, code, depth = 4) {
|
|
30
|
-
let cur = error;
|
|
31
|
-
for (let i = 0; i < depth && cur instanceof Error; i++) {
|
|
32
|
-
const c = cur.code;
|
|
33
|
-
if (typeof c === "string" && c === code) return true;
|
|
34
|
-
cur = cur.cause;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/schemas/approval.schema.ts
|
|
40
|
-
import { z as z2 } from "zod";
|
|
41
|
-
|
|
42
|
-
// src/schemas/shared.schema.ts
|
|
43
|
-
import { z } from "zod";
|
|
44
|
-
|
|
45
24
|
// src/ids/ulid.ts
|
|
46
25
|
import { isValid as isValidUlid, monotonicFactory } from "ulid";
|
|
47
26
|
var ID_PREFIXES = Object.freeze(["ws", "task", "ses", "evt", "appr", "decision"]);
|
|
@@ -67,7 +46,502 @@ function isValidPrefixedId(value) {
|
|
|
67
46
|
return isValidUlid(ulidPart);
|
|
68
47
|
}
|
|
69
48
|
|
|
49
|
+
// src/stats/active-time.ts
|
|
50
|
+
var ACTIVE_GAP_CAP_MS = 5 * 60 * 1e3;
|
|
51
|
+
var ENGAGED_TURNS_METHOD = "engaged-turns";
|
|
52
|
+
function activeTimeFromTimestamps(timestampsMs, capMs) {
|
|
53
|
+
const sorted = timestampsMs.filter((t) => Number.isFinite(t)).sort((a, b) => a - b);
|
|
54
|
+
const raw = [];
|
|
55
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
56
|
+
const prev = sorted[i - 1];
|
|
57
|
+
const curr = sorted[i];
|
|
58
|
+
if (prev === void 0 || curr === void 0) continue;
|
|
59
|
+
const gap = curr - prev;
|
|
60
|
+
if (gap <= 0) continue;
|
|
61
|
+
raw.push([prev, prev + Math.min(gap, capMs)]);
|
|
62
|
+
}
|
|
63
|
+
const intervals = mergeIntervals(raw);
|
|
64
|
+
return { ms: sumDurations(intervals), intervals };
|
|
65
|
+
}
|
|
66
|
+
function mergeIntervals(intervals) {
|
|
67
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
68
|
+
const merged = [];
|
|
69
|
+
for (const [start, end] of sorted) {
|
|
70
|
+
const last = merged[merged.length - 1];
|
|
71
|
+
if (last !== void 0 && start <= last[1]) {
|
|
72
|
+
if (end > last[1]) last[1] = end;
|
|
73
|
+
} else {
|
|
74
|
+
merged.push([start, end]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return merged;
|
|
78
|
+
}
|
|
79
|
+
function unionDurationMs(intervals) {
|
|
80
|
+
const merged = mergeIntervals(intervals);
|
|
81
|
+
return { ms: sumDurations(merged), merged };
|
|
82
|
+
}
|
|
83
|
+
function intervalsMsToIso(intervals) {
|
|
84
|
+
return intervals.map(([start, end]) => ({
|
|
85
|
+
start: new Date(start).toISOString(),
|
|
86
|
+
end: new Date(end).toISOString()
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
function intervalsIsoToMs(intervals) {
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const { start, end } of intervals) {
|
|
92
|
+
const s = Date.parse(start);
|
|
93
|
+
const e = Date.parse(end);
|
|
94
|
+
if (Number.isFinite(s) && Number.isFinite(e) && e >= s) out.push([s, e]);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function sumDurations(intervals) {
|
|
99
|
+
let total = 0;
|
|
100
|
+
for (const [start, end] of intervals) total += end - start;
|
|
101
|
+
return total;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/adapters/claude-code/transcript-importer.ts
|
|
105
|
+
var CLAUDE_IMPORT_SOURCE = "claude-code-import";
|
|
106
|
+
function claudeTranscriptToImportPayload(records, options) {
|
|
107
|
+
const placeholderSessionId = prefixedUlid("ses");
|
|
108
|
+
const askAnswers = indexAskAnswers(records);
|
|
109
|
+
const derived = [];
|
|
110
|
+
const relatedFiles = /* @__PURE__ */ new Set();
|
|
111
|
+
let minTs;
|
|
112
|
+
let maxTs;
|
|
113
|
+
let workingDir;
|
|
114
|
+
let claudeSessionId;
|
|
115
|
+
let outputTokens = 0;
|
|
116
|
+
let inputTokens = 0;
|
|
117
|
+
let cachedInputTokens = 0;
|
|
118
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
119
|
+
const engagementTsMs = [];
|
|
120
|
+
const seenEngagementMessageIds = /* @__PURE__ */ new Set();
|
|
121
|
+
for (const record of records) {
|
|
122
|
+
const ts = readString(record.timestamp);
|
|
123
|
+
if (ts === void 0) continue;
|
|
124
|
+
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
125
|
+
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
126
|
+
if (workingDir === void 0) workingDir = readString(record.cwd);
|
|
127
|
+
if (claudeSessionId === void 0) claudeSessionId = readString(record.sessionId);
|
|
128
|
+
if (record.isSidechain !== true) {
|
|
129
|
+
const tsMs = Date.parse(ts);
|
|
130
|
+
if (Number.isFinite(tsMs)) {
|
|
131
|
+
const recType = readString(record.type);
|
|
132
|
+
if (recType === "user") {
|
|
133
|
+
if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);
|
|
134
|
+
} else if (recType === "assistant") {
|
|
135
|
+
const msg = isObject(record.message) ? record.message : void 0;
|
|
136
|
+
const mid = msg !== void 0 ? readString(msg.id) : void 0;
|
|
137
|
+
if (mid === void 0 || !seenEngagementMessageIds.has(mid)) {
|
|
138
|
+
if (mid !== void 0) seenEngagementMessageIds.add(mid);
|
|
139
|
+
engagementTsMs.push(tsMs);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (readString(record.type) !== "assistant") continue;
|
|
145
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
146
|
+
const usage = message !== void 0 && isObject(message.usage) ? message.usage : void 0;
|
|
147
|
+
if (usage !== void 0) {
|
|
148
|
+
const messageId = message !== void 0 ? readString(message.id) : void 0;
|
|
149
|
+
const alreadyCounted = messageId !== void 0 && seenMessageIds.has(messageId);
|
|
150
|
+
if (!alreadyCounted) {
|
|
151
|
+
if (messageId !== void 0) seenMessageIds.add(messageId);
|
|
152
|
+
outputTokens += readNonNegInt(usage.output_tokens);
|
|
153
|
+
inputTokens += readNonNegInt(usage.input_tokens);
|
|
154
|
+
cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const cwd = readString(record.cwd) ?? workingDir ?? ".";
|
|
158
|
+
for (const item of toolUses(record)) {
|
|
159
|
+
const name = readString(item.name);
|
|
160
|
+
const input = isObject(item.input) ? item.input : void 0;
|
|
161
|
+
if (input === void 0) continue;
|
|
162
|
+
if (name === "Bash") {
|
|
163
|
+
const command = readString(input.command);
|
|
164
|
+
if (command !== void 0) {
|
|
165
|
+
derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (name === "AskUserQuestion") {
|
|
170
|
+
const useId = readString(item.id);
|
|
171
|
+
const answers = useId !== void 0 ? askAnswers.get(useId) : void 0;
|
|
172
|
+
if (answers !== void 0) {
|
|
173
|
+
for (const [question, answer] of Object.entries(answers)) {
|
|
174
|
+
if (question.length === 0) continue;
|
|
175
|
+
const answerStr = typeof answer === "string" && answer.length > 0 ? answer : void 0;
|
|
176
|
+
const title = answerStr !== void 0 ? `${question} -> ${answerStr}` : question;
|
|
177
|
+
derived.push(decisionRecordedEvent(ts, placeholderSessionId, title));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
183
|
+
const path2 = readString(input.file_path) ?? readString(input.notebook_path);
|
|
184
|
+
if (path2 !== void 0) {
|
|
185
|
+
const changeType = name === "Write" ? "added" : "modified";
|
|
186
|
+
relatedFiles.add(path2);
|
|
187
|
+
derived.push(fileChangedEvent(ts, placeholderSessionId, path2, changeType));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (minTs === void 0 || maxTs === void 0) return null;
|
|
193
|
+
if (derived.length === 0) return null;
|
|
194
|
+
derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));
|
|
195
|
+
const events = [
|
|
196
|
+
sessionStartedEvent(minTs, placeholderSessionId),
|
|
197
|
+
...derived,
|
|
198
|
+
sessionEndedEvent(maxTs, placeholderSessionId)
|
|
199
|
+
];
|
|
200
|
+
const externalId = options.externalId ?? claudeSessionId;
|
|
201
|
+
const commandCount = derived.reduce((n, e) => e.type === "command_executed" ? n + 1 : n, 0);
|
|
202
|
+
const fileCount = relatedFiles.size;
|
|
203
|
+
const date = minTs.slice(0, 10);
|
|
204
|
+
const label = `claude-code ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}, ${fileCount} ${fileCount === 1 ? "file" : "files"}`;
|
|
205
|
+
const active = engagementTsMs.length >= 2 ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS) : void 0;
|
|
206
|
+
const metricsFields = {
|
|
207
|
+
...outputTokens > 0 ? { output_tokens: outputTokens } : {},
|
|
208
|
+
...inputTokens > 0 ? { input_tokens: inputTokens } : {},
|
|
209
|
+
...cachedInputTokens > 0 ? { cached_input_tokens: cachedInputTokens } : {},
|
|
210
|
+
...active !== void 0 && active.ms > 0 ? {
|
|
211
|
+
active_time_ms: active.ms,
|
|
212
|
+
active_intervals: intervalsMsToIso(active.intervals),
|
|
213
|
+
active_gap_cap_ms: ACTIVE_GAP_CAP_MS,
|
|
214
|
+
active_time_method: ENGAGED_TURNS_METHOD
|
|
215
|
+
} : {}
|
|
216
|
+
};
|
|
217
|
+
const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : void 0;
|
|
218
|
+
const payload = {
|
|
219
|
+
schema_version: "0.1.0",
|
|
220
|
+
session: {
|
|
221
|
+
label,
|
|
222
|
+
workspace_id: options.workspaceId,
|
|
223
|
+
source: {
|
|
224
|
+
kind: CLAUDE_IMPORT_SOURCE,
|
|
225
|
+
version: "0.1.0",
|
|
226
|
+
...externalId !== void 0 ? { external_id: externalId } : {}
|
|
227
|
+
},
|
|
228
|
+
started_at: minTs,
|
|
229
|
+
ended_at: maxTs,
|
|
230
|
+
// Validated against the canonical enum here; importSessionFromJson
|
|
231
|
+
// overwrites it with the literal "imported" regardless.
|
|
232
|
+
status: "imported",
|
|
233
|
+
working_directory: workingDir ?? ".",
|
|
234
|
+
invocation: { command: "claude", args: [], exit_code: null },
|
|
235
|
+
related_files: [...relatedFiles].sort(),
|
|
236
|
+
summary: null,
|
|
237
|
+
...metrics !== void 0 ? { metrics } : {}
|
|
238
|
+
},
|
|
239
|
+
events
|
|
240
|
+
};
|
|
241
|
+
return payload;
|
|
242
|
+
}
|
|
243
|
+
function baseEvent(occurredAt, sessionId) {
|
|
244
|
+
return {
|
|
245
|
+
schema_version: "0.1.0",
|
|
246
|
+
id: prefixedUlid("evt"),
|
|
247
|
+
session_id: sessionId,
|
|
248
|
+
occurred_at: occurredAt,
|
|
249
|
+
source: CLAUDE_IMPORT_SOURCE
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function sessionStartedEvent(occurredAt, sessionId) {
|
|
253
|
+
return { ...baseEvent(occurredAt, sessionId), type: "session_started" };
|
|
254
|
+
}
|
|
255
|
+
function sessionEndedEvent(occurredAt, sessionId) {
|
|
256
|
+
return { ...baseEvent(occurredAt, sessionId), type: "session_ended" };
|
|
257
|
+
}
|
|
258
|
+
function commandExecutedEvent(occurredAt, sessionId, command, cwd) {
|
|
259
|
+
return {
|
|
260
|
+
...baseEvent(occurredAt, sessionId),
|
|
261
|
+
type: "command_executed",
|
|
262
|
+
command: "bash",
|
|
263
|
+
args: ["-c", command],
|
|
264
|
+
cwd,
|
|
265
|
+
exit_code: null,
|
|
266
|
+
duration_ms: 0
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function fileChangedEvent(occurredAt, sessionId, path2, changeType) {
|
|
270
|
+
return {
|
|
271
|
+
...baseEvent(occurredAt, sessionId),
|
|
272
|
+
type: "file_changed",
|
|
273
|
+
path: path2,
|
|
274
|
+
change_type: changeType
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function decisionRecordedEvent(occurredAt, sessionId, title) {
|
|
278
|
+
return {
|
|
279
|
+
...baseEvent(occurredAt, sessionId),
|
|
280
|
+
type: "decision_recorded",
|
|
281
|
+
decision_id: prefixedUlid("decision"),
|
|
282
|
+
title
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function readString(value) {
|
|
286
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
287
|
+
}
|
|
288
|
+
function readNonNegInt(value) {
|
|
289
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
290
|
+
}
|
|
291
|
+
function isObject(value) {
|
|
292
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
293
|
+
}
|
|
294
|
+
function isHumanUserMessage(record) {
|
|
295
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
296
|
+
if (message === void 0) return false;
|
|
297
|
+
const content = message.content;
|
|
298
|
+
if (typeof content === "string") return content.length > 0;
|
|
299
|
+
if (Array.isArray(content)) {
|
|
300
|
+
return content.some((block) => {
|
|
301
|
+
if (!isObject(block)) return false;
|
|
302
|
+
const type = readString(block.type);
|
|
303
|
+
return type !== void 0 && type !== "tool_result";
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
function toolUses(record) {
|
|
309
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
310
|
+
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
311
|
+
const result = [];
|
|
312
|
+
for (const item of content) {
|
|
313
|
+
if (isObject(item) && readString(item.type) === "tool_use") {
|
|
314
|
+
result.push(item);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
function indexAskAnswers(records) {
|
|
320
|
+
const byId = /* @__PURE__ */ new Map();
|
|
321
|
+
for (const record of records) {
|
|
322
|
+
const result = record.toolUseResult;
|
|
323
|
+
if (!isObject(result)) continue;
|
|
324
|
+
const answers = result.answers;
|
|
325
|
+
if (!isObject(answers)) continue;
|
|
326
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
327
|
+
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
328
|
+
for (const item of content) {
|
|
329
|
+
if (isObject(item) && readString(item.type) === "tool_result") {
|
|
330
|
+
const id = readString(item.tool_use_id);
|
|
331
|
+
if (id !== void 0) byId.set(id, answers);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return byId;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/adapters/codex/rollout-importer.ts
|
|
339
|
+
var CODEX_IMPORT_SOURCE = "codex-import";
|
|
340
|
+
function codexRolloutToImportPayload(records, options) {
|
|
341
|
+
const placeholderSessionId = prefixedUlid("ses");
|
|
342
|
+
const outputsByCallId = indexOutputs(records);
|
|
343
|
+
const derived = [];
|
|
344
|
+
let minTs;
|
|
345
|
+
let maxTs;
|
|
346
|
+
let workingDir;
|
|
347
|
+
let codexSessionId;
|
|
348
|
+
let lastTokenTotals;
|
|
349
|
+
const engagementTsMs = [];
|
|
350
|
+
for (const record of records) {
|
|
351
|
+
const ts = readString2(record.timestamp);
|
|
352
|
+
if (ts === void 0) continue;
|
|
353
|
+
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
354
|
+
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
355
|
+
const payload2 = isObject2(record.payload) ? record.payload : void 0;
|
|
356
|
+
if (payload2 === void 0) continue;
|
|
357
|
+
if (readString2(record.type) === "session_meta") {
|
|
358
|
+
if (workingDir === void 0) workingDir = readString2(payload2.cwd);
|
|
359
|
+
if (codexSessionId === void 0) codexSessionId = readString2(payload2.id);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (readString2(record.type) === "event_msg" && readString2(payload2.type) === "token_count") {
|
|
363
|
+
const info = isObject2(payload2.info) ? payload2.info : void 0;
|
|
364
|
+
const totals = info !== void 0 && isObject2(info.total_token_usage) ? info.total_token_usage : void 0;
|
|
365
|
+
if (totals !== void 0) lastTokenTotals = totals;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (readString2(record.type) === "event_msg") {
|
|
369
|
+
const pt = readString2(payload2.type);
|
|
370
|
+
if (pt === "user_message" || pt === "agent_message" || pt === "task_started" || pt === "task_complete") {
|
|
371
|
+
const tsMs = Date.parse(ts);
|
|
372
|
+
if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);
|
|
373
|
+
}
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (readString2(record.type) !== "response_item") continue;
|
|
377
|
+
if (readString2(payload2.type) !== "function_call") continue;
|
|
378
|
+
if (readString2(payload2.name) !== "exec_command") continue;
|
|
379
|
+
const command = readExecCommand(payload2.arguments);
|
|
380
|
+
if (command === void 0) continue;
|
|
381
|
+
const cwd = command.workdir ?? workingDir ?? ".";
|
|
382
|
+
const output = readCallId(payload2.call_id, outputsByCallId);
|
|
383
|
+
const execTsMs = Date.parse(ts);
|
|
384
|
+
if (Number.isFinite(execTsMs)) engagementTsMs.push(execTsMs);
|
|
385
|
+
derived.push(
|
|
386
|
+
commandExecutedEvent2(ts, placeholderSessionId, command.cmd, cwd, {
|
|
387
|
+
exitCode: parseExitCode(output),
|
|
388
|
+
durationMs: parseWallTimeMs(output)
|
|
389
|
+
})
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (minTs === void 0 || maxTs === void 0) return null;
|
|
393
|
+
if (derived.length === 0) return null;
|
|
394
|
+
derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));
|
|
395
|
+
const events = [
|
|
396
|
+
sessionStartedEvent2(minTs, placeholderSessionId),
|
|
397
|
+
...derived,
|
|
398
|
+
sessionEndedEvent2(maxTs, placeholderSessionId)
|
|
399
|
+
];
|
|
400
|
+
const externalId = options.externalId ?? codexSessionId;
|
|
401
|
+
const commandCount = derived.length;
|
|
402
|
+
const date = minTs.slice(0, 10);
|
|
403
|
+
const label = `codex ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
|
|
404
|
+
const active = engagementTsMs.length >= 2 ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS) : void 0;
|
|
405
|
+
const tokenFields = lastTokenTotals === void 0 ? {} : {
|
|
406
|
+
...readNonNegInt2(lastTokenTotals.output_tokens) > 0 ? { output_tokens: readNonNegInt2(lastTokenTotals.output_tokens) } : {},
|
|
407
|
+
...readNonNegInt2(lastTokenTotals.input_tokens) > 0 ? { input_tokens: readNonNegInt2(lastTokenTotals.input_tokens) } : {},
|
|
408
|
+
...readNonNegInt2(lastTokenTotals.cached_input_tokens) > 0 ? { cached_input_tokens: readNonNegInt2(lastTokenTotals.cached_input_tokens) } : {},
|
|
409
|
+
...readNonNegInt2(lastTokenTotals.reasoning_output_tokens) > 0 ? { reasoning_output_tokens: readNonNegInt2(lastTokenTotals.reasoning_output_tokens) } : {}
|
|
410
|
+
};
|
|
411
|
+
const metricsFields = {
|
|
412
|
+
...tokenFields,
|
|
413
|
+
...active !== void 0 && active.ms > 0 ? {
|
|
414
|
+
active_time_ms: active.ms,
|
|
415
|
+
active_intervals: intervalsMsToIso(active.intervals),
|
|
416
|
+
active_gap_cap_ms: ACTIVE_GAP_CAP_MS,
|
|
417
|
+
active_time_method: ENGAGED_TURNS_METHOD
|
|
418
|
+
} : {}
|
|
419
|
+
};
|
|
420
|
+
const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : void 0;
|
|
421
|
+
const payload = {
|
|
422
|
+
schema_version: "0.1.0",
|
|
423
|
+
session: {
|
|
424
|
+
label,
|
|
425
|
+
workspace_id: options.workspaceId,
|
|
426
|
+
source: {
|
|
427
|
+
kind: CODEX_IMPORT_SOURCE,
|
|
428
|
+
version: "0.1.0",
|
|
429
|
+
...externalId !== void 0 ? { external_id: externalId } : {}
|
|
430
|
+
},
|
|
431
|
+
started_at: minTs,
|
|
432
|
+
ended_at: maxTs,
|
|
433
|
+
// Validated against the canonical enum here; importSessionFromJson
|
|
434
|
+
// overwrites it with the literal "imported" regardless.
|
|
435
|
+
status: "imported",
|
|
436
|
+
working_directory: workingDir ?? ".",
|
|
437
|
+
invocation: { command: "codex", args: [], exit_code: null },
|
|
438
|
+
related_files: [],
|
|
439
|
+
summary: null,
|
|
440
|
+
...metrics !== void 0 ? { metrics } : {}
|
|
441
|
+
},
|
|
442
|
+
events
|
|
443
|
+
};
|
|
444
|
+
return payload;
|
|
445
|
+
}
|
|
446
|
+
function baseEvent2(occurredAt, sessionId) {
|
|
447
|
+
return {
|
|
448
|
+
schema_version: "0.1.0",
|
|
449
|
+
id: prefixedUlid("evt"),
|
|
450
|
+
session_id: sessionId,
|
|
451
|
+
occurred_at: occurredAt,
|
|
452
|
+
source: CODEX_IMPORT_SOURCE
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function sessionStartedEvent2(occurredAt, sessionId) {
|
|
456
|
+
return { ...baseEvent2(occurredAt, sessionId), type: "session_started" };
|
|
457
|
+
}
|
|
458
|
+
function sessionEndedEvent2(occurredAt, sessionId) {
|
|
459
|
+
return { ...baseEvent2(occurredAt, sessionId), type: "session_ended" };
|
|
460
|
+
}
|
|
461
|
+
function commandExecutedEvent2(occurredAt, sessionId, command, cwd, outcome) {
|
|
462
|
+
return {
|
|
463
|
+
...baseEvent2(occurredAt, sessionId),
|
|
464
|
+
type: "command_executed",
|
|
465
|
+
command: "bash",
|
|
466
|
+
args: ["-c", command],
|
|
467
|
+
cwd,
|
|
468
|
+
exit_code: outcome.exitCode,
|
|
469
|
+
duration_ms: outcome.durationMs
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function readString2(value) {
|
|
473
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
474
|
+
}
|
|
475
|
+
function readNonNegInt2(value) {
|
|
476
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
477
|
+
}
|
|
478
|
+
function isObject2(value) {
|
|
479
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
480
|
+
}
|
|
481
|
+
function readExecCommand(value) {
|
|
482
|
+
const raw = readString2(value);
|
|
483
|
+
if (raw === void 0) return void 0;
|
|
484
|
+
let parsed;
|
|
485
|
+
try {
|
|
486
|
+
parsed = JSON.parse(raw);
|
|
487
|
+
} catch {
|
|
488
|
+
return void 0;
|
|
489
|
+
}
|
|
490
|
+
if (!isObject2(parsed)) return void 0;
|
|
491
|
+
const cmd = readString2(parsed.cmd);
|
|
492
|
+
if (cmd === void 0) return void 0;
|
|
493
|
+
return { cmd, workdir: readString2(parsed.workdir) };
|
|
494
|
+
}
|
|
495
|
+
function readCallId(value, outputs) {
|
|
496
|
+
const callId = readString2(value);
|
|
497
|
+
return callId !== void 0 ? outputs.get(callId) : void 0;
|
|
498
|
+
}
|
|
499
|
+
function parseExitCode(output) {
|
|
500
|
+
if (output === void 0) return null;
|
|
501
|
+
const match = output.match(/Process exited with code (-?\d+)/);
|
|
502
|
+
return match?.[1] !== void 0 ? Number.parseInt(match[1], 10) : null;
|
|
503
|
+
}
|
|
504
|
+
function parseWallTimeMs(output) {
|
|
505
|
+
if (output === void 0) return 0;
|
|
506
|
+
const match = output.match(/Wall time:\s*([\d.]+)\s*seconds/);
|
|
507
|
+
if (match?.[1] === void 0) return 0;
|
|
508
|
+
const seconds = Number.parseFloat(match[1]);
|
|
509
|
+
return Number.isFinite(seconds) ? Math.round(seconds * 1e3) : 0;
|
|
510
|
+
}
|
|
511
|
+
function indexOutputs(records) {
|
|
512
|
+
const byId = /* @__PURE__ */ new Map();
|
|
513
|
+
for (const record of records) {
|
|
514
|
+
if (readString2(record.type) !== "response_item") continue;
|
|
515
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
516
|
+
if (payload === void 0) continue;
|
|
517
|
+
if (readString2(payload.type) !== "function_call_output") continue;
|
|
518
|
+
const callId = readString2(payload.call_id);
|
|
519
|
+
const output = readString2(payload.output);
|
|
520
|
+
if (callId !== void 0 && output !== void 0) byId.set(callId, output);
|
|
521
|
+
}
|
|
522
|
+
return byId;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/approval/approval-store.ts
|
|
526
|
+
import { readdir } from "fs/promises";
|
|
527
|
+
import { join } from "path";
|
|
528
|
+
|
|
529
|
+
// src/lib/error-codes.ts
|
|
530
|
+
function findErrorCode(error, code, depth = 4) {
|
|
531
|
+
let cur = error;
|
|
532
|
+
for (let i = 0; i < depth && cur instanceof Error; i++) {
|
|
533
|
+
const c = cur.code;
|
|
534
|
+
if (typeof c === "string" && c === code) return true;
|
|
535
|
+
cur = cur.cause;
|
|
536
|
+
}
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/schemas/approval.schema.ts
|
|
541
|
+
import { z as z2 } from "zod";
|
|
542
|
+
|
|
70
543
|
// src/schemas/shared.schema.ts
|
|
544
|
+
import { z } from "zod";
|
|
71
545
|
var SchemaVersionSchema = z.literal("0.1.0");
|
|
72
546
|
var IsoTimestampSchema = z.string().datetime({ offset: true });
|
|
73
547
|
var createPrefixedIdSchema = (prefix) => {
|
|
@@ -488,13 +962,19 @@ var SessionStatusSchema = z4.enum([
|
|
|
488
962
|
]);
|
|
489
963
|
var SessionSourceKindSchema = z4.enum([
|
|
490
964
|
"claude-code-adapter",
|
|
965
|
+
"claude-code-import",
|
|
966
|
+
"codex-import",
|
|
491
967
|
"human",
|
|
492
968
|
"import",
|
|
493
969
|
"terminal"
|
|
494
970
|
]);
|
|
495
971
|
var SessionSourceSchema = z4.object({
|
|
496
972
|
kind: SessionSourceKindSchema,
|
|
497
|
-
version: z4.literal("0.1.0")
|
|
973
|
+
version: z4.literal("0.1.0"),
|
|
974
|
+
// Optional id of the originating session in the SOURCE tool's own
|
|
975
|
+
// namespace (e.g. the Claude Code session UUID for a `claude-code-import`).
|
|
976
|
+
// Lets re-imports of the same source be deduplicated; absent for live runs.
|
|
977
|
+
external_id: z4.string().optional()
|
|
498
978
|
});
|
|
499
979
|
var InvocationSchema = z4.object({
|
|
500
980
|
command: z4.string().min(1),
|
|
@@ -503,6 +983,16 @@ var InvocationSchema = z4.object({
|
|
|
503
983
|
// code; the same nullability is mirrored in CommandExecutedEventSchema.
|
|
504
984
|
exit_code: z4.number().int().nullable()
|
|
505
985
|
});
|
|
986
|
+
var SessionMetricsSchema = z4.object({
|
|
987
|
+
output_tokens: z4.number().int().nonnegative().optional(),
|
|
988
|
+
input_tokens: z4.number().int().nonnegative().optional(),
|
|
989
|
+
cached_input_tokens: z4.number().int().nonnegative().optional(),
|
|
990
|
+
reasoning_output_tokens: z4.number().int().nonnegative().optional(),
|
|
991
|
+
active_time_ms: z4.number().int().nonnegative().optional(),
|
|
992
|
+
active_intervals: z4.array(z4.object({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
|
|
993
|
+
active_gap_cap_ms: z4.number().int().nonnegative().optional(),
|
|
994
|
+
active_time_method: z4.string().optional()
|
|
995
|
+
});
|
|
506
996
|
var SessionInnerSchema = z4.object({
|
|
507
997
|
id: SessionIdSchema,
|
|
508
998
|
label: z4.string().optional(),
|
|
@@ -517,7 +1007,8 @@ var SessionInnerSchema = z4.object({
|
|
|
517
1007
|
invocation: InvocationSchema,
|
|
518
1008
|
related_files: z4.array(z4.string()).default([]),
|
|
519
1009
|
events_log: z4.string().default("events.jsonl"),
|
|
520
|
-
summary: z4.string().nullable().optional()
|
|
1010
|
+
summary: z4.string().nullable().optional(),
|
|
1011
|
+
metrics: SessionMetricsSchema.optional()
|
|
521
1012
|
});
|
|
522
1013
|
var SessionSchema = z4.object({
|
|
523
1014
|
schema_version: SchemaVersionSchema,
|
|
@@ -2905,18 +3396,14 @@ async function renderHandoff(input) {
|
|
|
2905
3396
|
const latestSession = [...liveEntries].sort(
|
|
2906
3397
|
(a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
|
|
2907
3398
|
)[0];
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2910
|
-
if (e.session.session.source.kind === "import") continue;
|
|
2911
|
-
for (const f of e.session.session.related_files) allFiles.add(f);
|
|
2912
|
-
}
|
|
2913
|
-
const sortedFiles = [...allFiles].sort();
|
|
3399
|
+
const latestFiles = latestSession?.session.session.related_files ?? [];
|
|
3400
|
+
const sortedFiles = [...new Set(latestFiles)].sort();
|
|
2914
3401
|
const displayedFiles = sortedFiles.slice(0, limit);
|
|
2915
3402
|
const overflow = Math.max(0, sortedFiles.length - limit);
|
|
2916
3403
|
const suspectCount = entries.filter((e) => e.suspect).length;
|
|
2917
3404
|
const firstEntry = entries[0];
|
|
2918
3405
|
const lastEntry = entries[entries.length - 1];
|
|
2919
|
-
const sessionRange = firstEntry !== void 0 && lastEntry !== void 0 ? `${firstEntry.sessionId}..${lastEntry.sessionId}` : "";
|
|
3406
|
+
const sessionRange = firstEntry !== void 0 && lastEntry !== void 0 ? `${shortIdWithPrefix(firstEntry.sessionId)}..${shortIdWithPrefix(lastEntry.sessionId)}` : "";
|
|
2920
3407
|
const body = formatHandoffBody({
|
|
2921
3408
|
nowIso: input.nowIso,
|
|
2922
3409
|
sessionRange,
|
|
@@ -2956,18 +3443,23 @@ function formatHandoffBody(args) {
|
|
|
2956
3443
|
lines.push("## \u73FE\u5728\u306E\u72B6\u614B");
|
|
2957
3444
|
lines.push("");
|
|
2958
3445
|
if (args.latestSession !== void 0) {
|
|
2959
|
-
const sid = args.latestSession.sessionId;
|
|
2960
3446
|
const status = args.latestSession.session.session.status;
|
|
2961
|
-
|
|
3447
|
+
const label = args.latestSession.session.session.label;
|
|
3448
|
+
const shortId = shortIdWithPrefix(args.latestSession.sessionId);
|
|
3449
|
+
if (label !== void 0 && label !== "") {
|
|
3450
|
+
lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId}]`);
|
|
3451
|
+
} else {
|
|
3452
|
+
lines.push(`- \u6700\u7D42 session: ${shortId} (${status})`);
|
|
3453
|
+
}
|
|
2962
3454
|
} else {
|
|
2963
3455
|
lines.push("- \u6700\u7D42 session: (no live sessions)");
|
|
2964
3456
|
}
|
|
2965
3457
|
if (args.latestActivityRecord !== void 0) {
|
|
2966
3458
|
const statusLabel = args.latestTaskDoc !== void 0 ? args.latestTaskDoc.task.task.status : "status unknown \u2014 task.md missing or invalid";
|
|
2967
3459
|
const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;
|
|
2968
|
-
const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ?
|
|
3460
|
+
const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : "";
|
|
2969
3461
|
lines.push(
|
|
2970
|
-
`- \u6700\u7D42 task: ${args.latestActivityRecord.
|
|
3462
|
+
`- \u6700\u7D42 task: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
|
|
2971
3463
|
);
|
|
2972
3464
|
} else {
|
|
2973
3465
|
lines.push("- \u6700\u7D42 task: (no tasks recorded yet)");
|
|
@@ -2988,7 +3480,7 @@ function formatHandoffBody(args) {
|
|
|
2988
3480
|
lines.push("(no decisions recorded yet)");
|
|
2989
3481
|
} else {
|
|
2990
3482
|
const last = args.decisions[args.decisions.length - 1];
|
|
2991
|
-
lines.push(`- ${last.
|
|
3483
|
+
lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);
|
|
2992
3484
|
lines.push("");
|
|
2993
3485
|
lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
|
|
2994
3486
|
}
|
|
@@ -3016,7 +3508,9 @@ function formatHandoffBody(args) {
|
|
|
3016
3508
|
lines.push("(no pending tasks)");
|
|
3017
3509
|
} else {
|
|
3018
3510
|
for (const t of args.pendingTasks) {
|
|
3019
|
-
lines.push(
|
|
3511
|
+
lines.push(
|
|
3512
|
+
`- ${t.task.task.title} (${t.task.task.status}) [${shortIdWithPrefix(t.task.task.id)}]`
|
|
3513
|
+
);
|
|
3020
3514
|
}
|
|
3021
3515
|
}
|
|
3022
3516
|
lines.push("");
|
|
@@ -3085,6 +3579,11 @@ function shortHandoffId(sessionId) {
|
|
|
3085
3579
|
if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);
|
|
3086
3580
|
return sessionId.slice(0, 10);
|
|
3087
3581
|
}
|
|
3582
|
+
function shortIdWithPrefix(id) {
|
|
3583
|
+
const sep = id.indexOf("_");
|
|
3584
|
+
if (sep === -1) return id.slice(0, 10);
|
|
3585
|
+
return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
|
|
3586
|
+
}
|
|
3088
3587
|
|
|
3089
3588
|
// src/lib/duration.ts
|
|
3090
3589
|
var DURATION_RE = /^([1-9]\d*)(ms|s|m|h)$/;
|
|
@@ -3350,7 +3849,10 @@ var SessionInnerImportSchema = z10.object({
|
|
|
3350
3849
|
workspace_id: WorkspaceIdSchema,
|
|
3351
3850
|
source: z10.object({
|
|
3352
3851
|
kind: SessionSourceKindSchema,
|
|
3353
|
-
version: z10.literal("0.1.0")
|
|
3852
|
+
version: z10.literal("0.1.0"),
|
|
3853
|
+
// Source-tool-native id (e.g. Claude Code session UUID), retained so
|
|
3854
|
+
// re-imports of the same source can be deduplicated.
|
|
3855
|
+
external_id: z10.string().optional()
|
|
3354
3856
|
}),
|
|
3355
3857
|
started_at: IsoTimestampSchema,
|
|
3356
3858
|
ended_at: IsoTimestampSchema.optional(),
|
|
@@ -3363,7 +3865,8 @@ var SessionInnerImportSchema = z10.object({
|
|
|
3363
3865
|
}),
|
|
3364
3866
|
related_files: z10.array(z10.string()).default([]),
|
|
3365
3867
|
events_log: z10.string().optional(),
|
|
3366
|
-
summary: z10.string().nullable().optional()
|
|
3868
|
+
summary: z10.string().nullable().optional(),
|
|
3869
|
+
metrics: SessionMetricsSchema.optional()
|
|
3367
3870
|
}).strict();
|
|
3368
3871
|
var SessionImportPayloadSchema = z10.object({
|
|
3369
3872
|
schema_version: z10.string(),
|
|
@@ -3371,29 +3874,301 @@ var SessionImportPayloadSchema = z10.object({
|
|
|
3371
3874
|
events: z10.array(EventSchema)
|
|
3372
3875
|
}).strict();
|
|
3373
3876
|
|
|
3877
|
+
// src/stats/work-stats.ts
|
|
3878
|
+
import { join as join11 } from "path";
|
|
3879
|
+
function resolveTimeZone(timeZone) {
|
|
3880
|
+
if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
|
|
3881
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
3882
|
+
}
|
|
3883
|
+
var STATUS_ORDER = [
|
|
3884
|
+
"completed",
|
|
3885
|
+
"failed",
|
|
3886
|
+
"running",
|
|
3887
|
+
"interrupted",
|
|
3888
|
+
"waiting_approval",
|
|
3889
|
+
"initialized",
|
|
3890
|
+
"imported",
|
|
3891
|
+
"archived"
|
|
3892
|
+
];
|
|
3893
|
+
async function computeWorkStats(input) {
|
|
3894
|
+
const { now } = input;
|
|
3895
|
+
const timeZone = resolveTimeZone(input.timeZone);
|
|
3896
|
+
const unreadableEmitted = /* @__PURE__ */ new Set();
|
|
3897
|
+
const wrappedSkip = (sid, reason) => {
|
|
3898
|
+
if (reason === "events_jsonl_unreadable") unreadableEmitted.add(sid);
|
|
3899
|
+
input.onSessionSkip?.(sid, reason);
|
|
3900
|
+
};
|
|
3901
|
+
const loadOpts = { now, onSkip: wrappedSkip };
|
|
3902
|
+
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
3903
|
+
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
3904
|
+
const sessions = [];
|
|
3905
|
+
for (const entry of entries) {
|
|
3906
|
+
const events = [];
|
|
3907
|
+
let eventsUnreadable = false;
|
|
3908
|
+
try {
|
|
3909
|
+
for await (const ev of replayEvents(join11(input.paths.sessions, entry.sessionId), {
|
|
3910
|
+
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
3911
|
+
})) {
|
|
3912
|
+
events.push(ev);
|
|
3913
|
+
}
|
|
3914
|
+
} catch {
|
|
3915
|
+
eventsUnreadable = true;
|
|
3916
|
+
if (!unreadableEmitted.has(entry.sessionId)) {
|
|
3917
|
+
wrappedSkip(entry.sessionId, "events_jsonl_unreadable");
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
sessions.push(
|
|
3921
|
+
sessionWorkStatsFromEvents(
|
|
3922
|
+
entry.sessionId,
|
|
3923
|
+
entry.session.session,
|
|
3924
|
+
events,
|
|
3925
|
+
now,
|
|
3926
|
+
eventsUnreadable
|
|
3927
|
+
)
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3930
|
+
const allIntervals = [];
|
|
3931
|
+
for (const s of sessions) allIntervals.push(...intervalsIsoToMs(s.activeIntervals));
|
|
3932
|
+
const union = unionDurationMs(allIntervals);
|
|
3933
|
+
return {
|
|
3934
|
+
generatedAt: now.toISOString(),
|
|
3935
|
+
activeGapCapMs: ACTIVE_GAP_CAP_MS,
|
|
3936
|
+
timeZone,
|
|
3937
|
+
totals: computeTotals(sessions, union.ms),
|
|
3938
|
+
sessions,
|
|
3939
|
+
bySource: computeBySource(sessions),
|
|
3940
|
+
byStatus: computeByStatus(sessions),
|
|
3941
|
+
byDay: computeByDay(sessions, union.merged, timeZone)
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
function sessionWorkStatsFromEvents(sessionId, inner, events, now, eventsUnreadable = false) {
|
|
3945
|
+
let commandCount = 0;
|
|
3946
|
+
let fileChangedCount = 0;
|
|
3947
|
+
let decisionCount = 0;
|
|
3948
|
+
let commandTimeMs = 0;
|
|
3949
|
+
const timestamps = [];
|
|
3950
|
+
for (const ev of events) {
|
|
3951
|
+
const t = Date.parse(ev.occurred_at);
|
|
3952
|
+
if (Number.isFinite(t)) timestamps.push(t);
|
|
3953
|
+
if (ev.type === "command_executed") {
|
|
3954
|
+
commandCount++;
|
|
3955
|
+
commandTimeMs += ev.duration_ms;
|
|
3956
|
+
} else if (ev.type === "file_changed") {
|
|
3957
|
+
fileChangedCount++;
|
|
3958
|
+
} else if (ev.type === "decision_recorded") {
|
|
3959
|
+
decisionCount++;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
const span = computeSpan(inner.started_at, inner.ended_at, now);
|
|
3963
|
+
const tokens = readTokens(inner.metrics);
|
|
3964
|
+
const active = resolveActiveTime(inner.metrics, timestamps);
|
|
3965
|
+
return {
|
|
3966
|
+
sessionId,
|
|
3967
|
+
label: inner.label,
|
|
3968
|
+
status: inner.status,
|
|
3969
|
+
sourceKind: inner.source.kind,
|
|
3970
|
+
startedAt: inner.started_at,
|
|
3971
|
+
endedAt: inner.ended_at,
|
|
3972
|
+
open: inner.ended_at === void 0,
|
|
3973
|
+
sessionSpanMs: span.ms,
|
|
3974
|
+
commandTimeMs,
|
|
3975
|
+
activeTimeMs: active.ms,
|
|
3976
|
+
activeTimeBasis: active.basis,
|
|
3977
|
+
activeIntervals: intervalsMsToIso(active.intervals),
|
|
3978
|
+
commandCount,
|
|
3979
|
+
fileChangedCount,
|
|
3980
|
+
decisionCount,
|
|
3981
|
+
eventCount: events.length,
|
|
3982
|
+
tokens,
|
|
3983
|
+
availability: {
|
|
3984
|
+
span: true,
|
|
3985
|
+
commandTime: inner.source.kind !== "claude-code-import",
|
|
3986
|
+
activeTime: active.intervals.length > 0,
|
|
3987
|
+
tokens: hasTokens(tokens)
|
|
3988
|
+
},
|
|
3989
|
+
spanClamped: span.clamped,
|
|
3990
|
+
eventsUnreadable
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
function resolveActiveTime(metrics, eventTimestamps) {
|
|
3994
|
+
const stored = metrics?.active_intervals;
|
|
3995
|
+
if (stored !== void 0 && stored.length > 0) {
|
|
3996
|
+
const intervals = intervalsIsoToMs(stored);
|
|
3997
|
+
const ms = intervals.reduce((n, [start, end]) => n + (end - start), 0);
|
|
3998
|
+
return { ms, intervals, basis: "engaged-turns" };
|
|
3999
|
+
}
|
|
4000
|
+
const derived = activeTimeFromTimestamps(eventTimestamps, ACTIVE_GAP_CAP_MS);
|
|
4001
|
+
return { ms: derived.ms, intervals: derived.intervals, basis: "events" };
|
|
4002
|
+
}
|
|
4003
|
+
function computeSpan(startedAt, endedAt, now) {
|
|
4004
|
+
const start = Date.parse(startedAt);
|
|
4005
|
+
const end = endedAt !== void 0 ? Date.parse(endedAt) : now.getTime();
|
|
4006
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return { ms: 0, clamped: true };
|
|
4007
|
+
const raw = end - start;
|
|
4008
|
+
return raw < 0 ? { ms: 0, clamped: true } : { ms: raw, clamped: false };
|
|
4009
|
+
}
|
|
4010
|
+
function readTokens(metrics) {
|
|
4011
|
+
return {
|
|
4012
|
+
output: metrics?.output_tokens ?? 0,
|
|
4013
|
+
input: metrics?.input_tokens ?? 0,
|
|
4014
|
+
cached: metrics?.cached_input_tokens ?? 0,
|
|
4015
|
+
reasoning: metrics?.reasoning_output_tokens ?? 0
|
|
4016
|
+
};
|
|
4017
|
+
}
|
|
4018
|
+
function hasTokens(t) {
|
|
4019
|
+
return t.output > 0 || t.input > 0 || t.cached > 0 || t.reasoning > 0;
|
|
4020
|
+
}
|
|
4021
|
+
function emptyTokens() {
|
|
4022
|
+
return { output: 0, input: 0, cached: 0, reasoning: 0 };
|
|
4023
|
+
}
|
|
4024
|
+
function addTokens(a, b) {
|
|
4025
|
+
a.output += b.output;
|
|
4026
|
+
a.input += b.input;
|
|
4027
|
+
a.cached += b.cached;
|
|
4028
|
+
a.reasoning += b.reasoning;
|
|
4029
|
+
}
|
|
4030
|
+
function computeTotals(sessions, billableActiveTimeMs) {
|
|
4031
|
+
const tokens = emptyTokens();
|
|
4032
|
+
const totals = {
|
|
4033
|
+
sessionCount: sessions.length,
|
|
4034
|
+
openSessionCount: 0,
|
|
4035
|
+
sessionSpanMs: 0,
|
|
4036
|
+
commandTimeMs: 0,
|
|
4037
|
+
activeTimeMs: 0,
|
|
4038
|
+
billableActiveTimeMs,
|
|
4039
|
+
commandCount: 0,
|
|
4040
|
+
fileChangedCount: 0,
|
|
4041
|
+
decisionCount: 0,
|
|
4042
|
+
eventCount: 0,
|
|
4043
|
+
tokens,
|
|
4044
|
+
commandTimeReliable: true,
|
|
4045
|
+
tokensAvailable: false
|
|
4046
|
+
};
|
|
4047
|
+
for (const s of sessions) {
|
|
4048
|
+
if (s.open) totals.openSessionCount++;
|
|
4049
|
+
totals.sessionSpanMs += s.sessionSpanMs;
|
|
4050
|
+
totals.commandTimeMs += s.commandTimeMs;
|
|
4051
|
+
totals.activeTimeMs += s.activeTimeMs;
|
|
4052
|
+
totals.commandCount += s.commandCount;
|
|
4053
|
+
totals.fileChangedCount += s.fileChangedCount;
|
|
4054
|
+
totals.decisionCount += s.decisionCount;
|
|
4055
|
+
totals.eventCount += s.eventCount;
|
|
4056
|
+
addTokens(tokens, s.tokens);
|
|
4057
|
+
if (!s.availability.commandTime) totals.commandTimeReliable = false;
|
|
4058
|
+
if (s.availability.tokens) totals.tokensAvailable = true;
|
|
4059
|
+
}
|
|
4060
|
+
return totals;
|
|
4061
|
+
}
|
|
4062
|
+
function computeBySource(sessions) {
|
|
4063
|
+
const map = /* @__PURE__ */ new Map();
|
|
4064
|
+
for (const s of sessions) {
|
|
4065
|
+
let row = map.get(s.sourceKind);
|
|
4066
|
+
if (row === void 0) {
|
|
4067
|
+
row = {
|
|
4068
|
+
sourceKind: s.sourceKind,
|
|
4069
|
+
sessionCount: 0,
|
|
4070
|
+
sessionSpanMs: 0,
|
|
4071
|
+
commandTimeMs: 0,
|
|
4072
|
+
activeTimeMs: 0,
|
|
4073
|
+
commandCount: 0,
|
|
4074
|
+
fileChangedCount: 0,
|
|
4075
|
+
decisionCount: 0,
|
|
4076
|
+
eventCount: 0,
|
|
4077
|
+
tokens: emptyTokens(),
|
|
4078
|
+
commandTimeReliable: true,
|
|
4079
|
+
tokensAvailable: false
|
|
4080
|
+
};
|
|
4081
|
+
map.set(s.sourceKind, row);
|
|
4082
|
+
}
|
|
4083
|
+
row.sessionCount++;
|
|
4084
|
+
row.sessionSpanMs += s.sessionSpanMs;
|
|
4085
|
+
row.commandTimeMs += s.commandTimeMs;
|
|
4086
|
+
row.activeTimeMs += s.activeTimeMs;
|
|
4087
|
+
row.commandCount += s.commandCount;
|
|
4088
|
+
row.fileChangedCount += s.fileChangedCount;
|
|
4089
|
+
row.decisionCount += s.decisionCount;
|
|
4090
|
+
row.eventCount += s.eventCount;
|
|
4091
|
+
addTokens(row.tokens, s.tokens);
|
|
4092
|
+
if (!s.availability.commandTime) row.commandTimeReliable = false;
|
|
4093
|
+
if (s.availability.tokens) row.tokensAvailable = true;
|
|
4094
|
+
}
|
|
4095
|
+
return [...map.values()].sort((a, b) => a.sourceKind.localeCompare(b.sourceKind));
|
|
4096
|
+
}
|
|
4097
|
+
function computeByStatus(sessions) {
|
|
4098
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4099
|
+
for (const s of sessions) counts.set(s.status, (counts.get(s.status) ?? 0) + 1);
|
|
4100
|
+
const ordered = [];
|
|
4101
|
+
for (const status of STATUS_ORDER) {
|
|
4102
|
+
const count = counts.get(status);
|
|
4103
|
+
if (count !== void 0 && count > 0) ordered.push({ status, count });
|
|
4104
|
+
}
|
|
4105
|
+
return ordered;
|
|
4106
|
+
}
|
|
4107
|
+
function computeByDay(sessions, unionMerged, timeZone) {
|
|
4108
|
+
const days = /* @__PURE__ */ new Map();
|
|
4109
|
+
const ensure = (date) => {
|
|
4110
|
+
let day = days.get(date);
|
|
4111
|
+
if (day === void 0) {
|
|
4112
|
+
day = {
|
|
4113
|
+
date,
|
|
4114
|
+
billableActiveTimeMs: 0,
|
|
4115
|
+
sessionCount: 0,
|
|
4116
|
+
commandCount: 0,
|
|
4117
|
+
fileChangedCount: 0,
|
|
4118
|
+
decisionCount: 0,
|
|
4119
|
+
tokens: emptyTokens()
|
|
4120
|
+
};
|
|
4121
|
+
days.set(date, day);
|
|
4122
|
+
}
|
|
4123
|
+
return day;
|
|
4124
|
+
};
|
|
4125
|
+
for (const [start, end] of unionMerged) {
|
|
4126
|
+
ensure(tzDate(start, timeZone)).billableActiveTimeMs += end - start;
|
|
4127
|
+
}
|
|
4128
|
+
for (const s of sessions) {
|
|
4129
|
+
const startedMs = Date.parse(s.startedAt);
|
|
4130
|
+
if (!Number.isFinite(startedMs)) continue;
|
|
4131
|
+
const day = ensure(tzDate(startedMs, timeZone));
|
|
4132
|
+
day.sessionCount++;
|
|
4133
|
+
day.commandCount += s.commandCount;
|
|
4134
|
+
day.fileChangedCount += s.fileChangedCount;
|
|
4135
|
+
day.decisionCount += s.decisionCount;
|
|
4136
|
+
addTokens(day.tokens, s.tokens);
|
|
4137
|
+
}
|
|
4138
|
+
return [...days.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
4139
|
+
}
|
|
4140
|
+
function tzDate(ms, timeZone) {
|
|
4141
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
4142
|
+
timeZone,
|
|
4143
|
+
year: "numeric",
|
|
4144
|
+
month: "2-digit",
|
|
4145
|
+
day: "2-digit"
|
|
4146
|
+
}).format(new Date(ms));
|
|
4147
|
+
}
|
|
4148
|
+
|
|
3374
4149
|
// src/storage/basou-dir.ts
|
|
3375
4150
|
import { lstat as lstat3, mkdir as mkdir3 } from "fs/promises";
|
|
3376
|
-
import { join as
|
|
4151
|
+
import { join as join12 } from "path";
|
|
3377
4152
|
function basouPaths(repositoryRoot) {
|
|
3378
|
-
const root =
|
|
3379
|
-
const approvalsBase =
|
|
4153
|
+
const root = join12(repositoryRoot, ".basou");
|
|
4154
|
+
const approvalsBase = join12(root, "approvals");
|
|
3380
4155
|
return {
|
|
3381
4156
|
root,
|
|
3382
|
-
sessions:
|
|
3383
|
-
tasks:
|
|
4157
|
+
sessions: join12(root, "sessions"),
|
|
4158
|
+
tasks: join12(root, "tasks"),
|
|
3384
4159
|
approvals: {
|
|
3385
|
-
pending:
|
|
3386
|
-
resolved:
|
|
4160
|
+
pending: join12(approvalsBase, "pending"),
|
|
4161
|
+
resolved: join12(approvalsBase, "resolved")
|
|
3387
4162
|
},
|
|
3388
|
-
locks:
|
|
3389
|
-
logs:
|
|
3390
|
-
raw:
|
|
3391
|
-
tmp:
|
|
4163
|
+
locks: join12(root, "locks"),
|
|
4164
|
+
logs: join12(root, "logs"),
|
|
4165
|
+
raw: join12(root, "raw"),
|
|
4166
|
+
tmp: join12(root, "tmp"),
|
|
3392
4167
|
files: {
|
|
3393
|
-
manifest:
|
|
3394
|
-
status:
|
|
3395
|
-
handoff:
|
|
3396
|
-
decisions:
|
|
4168
|
+
manifest: join12(root, "manifest.yaml"),
|
|
4169
|
+
status: join12(root, "status.json"),
|
|
4170
|
+
handoff: join12(root, "handoff.md"),
|
|
4171
|
+
decisions: join12(root, "decisions.md")
|
|
3397
4172
|
}
|
|
3398
4173
|
};
|
|
3399
4174
|
}
|
|
@@ -3450,11 +4225,11 @@ function hasErrorCode3(error) {
|
|
|
3450
4225
|
|
|
3451
4226
|
// src/storage/gitignore.ts
|
|
3452
4227
|
import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
3453
|
-
import { join as
|
|
4228
|
+
import { join as join13 } from "path";
|
|
3454
4229
|
var MARKER = "# Basou - default ignore";
|
|
3455
4230
|
var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
|
|
3456
4231
|
async function appendBasouGitignore(repositoryRoot) {
|
|
3457
|
-
const gitignorePath =
|
|
4232
|
+
const gitignorePath = join13(repositoryRoot, ".gitignore");
|
|
3458
4233
|
let body;
|
|
3459
4234
|
let existed;
|
|
3460
4235
|
try {
|
|
@@ -3668,7 +4443,7 @@ function hasErrorCode6(error) {
|
|
|
3668
4443
|
// src/storage/session-import.ts
|
|
3669
4444
|
import { mkdir as mkdir4, rm as rm2 } from "fs/promises";
|
|
3670
4445
|
import { homedir as homedir2 } from "os";
|
|
3671
|
-
import { join as
|
|
4446
|
+
import { join as join14 } from "path";
|
|
3672
4447
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
3673
4448
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
3674
4449
|
throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
|
|
@@ -3693,7 +4468,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
3693
4468
|
pathSanitizeReport
|
|
3694
4469
|
};
|
|
3695
4470
|
}
|
|
3696
|
-
const sessionDir =
|
|
4471
|
+
const sessionDir = join14(paths.sessions, newSessionId);
|
|
3697
4472
|
try {
|
|
3698
4473
|
await mkdir4(sessionDir, { recursive: true });
|
|
3699
4474
|
} catch (error) {
|
|
@@ -3706,7 +4481,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
3706
4481
|
throw error;
|
|
3707
4482
|
}
|
|
3708
4483
|
try {
|
|
3709
|
-
const sessionYamlPath =
|
|
4484
|
+
const sessionYamlPath = join14(sessionDir, "session.yaml");
|
|
3710
4485
|
await linkYamlFile(sessionYamlPath, sessionRecord);
|
|
3711
4486
|
} catch (error) {
|
|
3712
4487
|
await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -3787,7 +4562,8 @@ function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
|
3787
4562
|
invocation: input.invocation,
|
|
3788
4563
|
related_files: relatedSanitized.sanitized,
|
|
3789
4564
|
events_log: "events.jsonl",
|
|
3790
|
-
summary: input.summary ?? null
|
|
4565
|
+
summary: input.summary ?? null,
|
|
4566
|
+
...input.metrics !== void 0 ? { metrics: input.metrics } : {}
|
|
3791
4567
|
};
|
|
3792
4568
|
return {
|
|
3793
4569
|
record: { schema_version: "0.1.0", session: inner },
|
|
@@ -3801,10 +4577,13 @@ function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
|
3801
4577
|
// src/index.ts
|
|
3802
4578
|
var BASOU_CORE_VERSION = "0.1.0";
|
|
3803
4579
|
export {
|
|
4580
|
+
ACTIVE_GAP_CAP_MS,
|
|
3804
4581
|
ApprovalIdSchema,
|
|
3805
4582
|
ApprovalSchema,
|
|
3806
4583
|
ApprovalStatusSchema,
|
|
3807
4584
|
BASOU_CORE_VERSION,
|
|
4585
|
+
CLAUDE_IMPORT_SOURCE,
|
|
4586
|
+
CODEX_IMPORT_SOURCE,
|
|
3808
4587
|
ChildProcessRunner,
|
|
3809
4588
|
DecisionIdSchema,
|
|
3810
4589
|
EventIdSchema,
|
|
@@ -3822,6 +4601,7 @@ export {
|
|
|
3822
4601
|
SessionIdSchema,
|
|
3823
4602
|
SessionImportPayloadSchema,
|
|
3824
4603
|
SessionInnerImportSchema,
|
|
4604
|
+
SessionMetricsSchema,
|
|
3825
4605
|
SessionSchema,
|
|
3826
4606
|
SessionSourceKindSchema,
|
|
3827
4607
|
SessionStatusSchema,
|
|
@@ -3841,6 +4621,9 @@ export {
|
|
|
3841
4621
|
buildStatusSnapshot,
|
|
3842
4622
|
classifySuspect,
|
|
3843
4623
|
claudeCodeAdapterMetadata,
|
|
4624
|
+
claudeTranscriptToImportPayload,
|
|
4625
|
+
codexRolloutToImportPayload,
|
|
4626
|
+
computeWorkStats,
|
|
3844
4627
|
createAdHocSessionWithEvent,
|
|
3845
4628
|
createManifest,
|
|
3846
4629
|
createTaskWithEvent,
|
|
@@ -3887,6 +4670,7 @@ export {
|
|
|
3887
4670
|
sanitizePath,
|
|
3888
4671
|
sanitizeRelatedFiles,
|
|
3889
4672
|
sanitizeWorkingDirectory,
|
|
4673
|
+
sessionWorkStatsFromEvents,
|
|
3890
4674
|
summarizeAdapterOutput,
|
|
3891
4675
|
tryRemoteUrl,
|
|
3892
4676
|
ulid,
|