@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.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 allFiles = /* @__PURE__ */ new Set();
2909
- for (const e of entries) {
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
- lines.push(`- \u6700\u7D42 session: ${sid} (${status})`);
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 ? ` (linked_sessions: ${linkedCount})` : "";
3460
+ const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : "";
2969
3461
  lines.push(
2970
- `- \u6700\u7D42 task: ${args.latestActivityRecord.taskId} (${statusLabel}): ${args.latestActivityRecord.title}${linkedSuffix}`
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.decisionId}: ${last.title}`);
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(`- ${t.task.task.id} (${t.task.task.status}): ${t.task.task.title}`);
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 join11 } from "path";
4151
+ import { join as join12 } from "path";
3377
4152
  function basouPaths(repositoryRoot) {
3378
- const root = join11(repositoryRoot, ".basou");
3379
- const approvalsBase = join11(root, "approvals");
4153
+ const root = join12(repositoryRoot, ".basou");
4154
+ const approvalsBase = join12(root, "approvals");
3380
4155
  return {
3381
4156
  root,
3382
- sessions: join11(root, "sessions"),
3383
- tasks: join11(root, "tasks"),
4157
+ sessions: join12(root, "sessions"),
4158
+ tasks: join12(root, "tasks"),
3384
4159
  approvals: {
3385
- pending: join11(approvalsBase, "pending"),
3386
- resolved: join11(approvalsBase, "resolved")
4160
+ pending: join12(approvalsBase, "pending"),
4161
+ resolved: join12(approvalsBase, "resolved")
3387
4162
  },
3388
- locks: join11(root, "locks"),
3389
- logs: join11(root, "logs"),
3390
- raw: join11(root, "raw"),
3391
- tmp: join11(root, "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: join11(root, "manifest.yaml"),
3394
- status: join11(root, "status.json"),
3395
- handoff: join11(root, "handoff.md"),
3396
- decisions: join11(root, "decisions.md")
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 join12 } from "path";
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 = join12(repositoryRoot, ".gitignore");
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 join13 } from "path";
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 = join13(paths.sessions, newSessionId);
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 = join13(sessionDir, "session.yaml");
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,