@basou/core 0.4.0 → 0.6.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 +2895 -2406
- package/dist/index.js +996 -60
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/schemas/approval.schema.json +130 -0
- package/schemas/event.schema.json +1188 -0
- package/schemas/manifest.schema.json +150 -0
- package/schemas/session-import.schema.json +1415 -0
- package/schemas/session.schema.json +228 -0
- package/schemas/status.schema.json +85 -0
- package/schemas/task-index.schema.json +61 -0
- package/schemas/task.schema.json +82 -0
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,12 +46,565 @@ 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
|
+
var TURN_INTERVALS_METHOD = "turn-intervals";
|
|
53
|
+
function activeTimeFromTimestamps(timestampsMs, capMs) {
|
|
54
|
+
const sorted = timestampsMs.filter((t) => Number.isFinite(t)).sort((a, b) => a - b);
|
|
55
|
+
const raw = [];
|
|
56
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
57
|
+
const prev = sorted[i - 1];
|
|
58
|
+
const curr = sorted[i];
|
|
59
|
+
if (prev === void 0 || curr === void 0) continue;
|
|
60
|
+
const gap = curr - prev;
|
|
61
|
+
if (gap <= 0) continue;
|
|
62
|
+
raw.push([prev, prev + Math.min(gap, capMs)]);
|
|
63
|
+
}
|
|
64
|
+
const intervals = mergeIntervals(raw);
|
|
65
|
+
return { ms: sumDurations(intervals), intervals };
|
|
66
|
+
}
|
|
67
|
+
function mergeIntervals(intervals) {
|
|
68
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
69
|
+
const merged = [];
|
|
70
|
+
for (const [start, end] of sorted) {
|
|
71
|
+
const last = merged[merged.length - 1];
|
|
72
|
+
if (last !== void 0 && start <= last[1]) {
|
|
73
|
+
if (end > last[1]) last[1] = end;
|
|
74
|
+
} else {
|
|
75
|
+
merged.push([start, end]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return merged;
|
|
79
|
+
}
|
|
80
|
+
function unionDurationMs(intervals) {
|
|
81
|
+
const merged = mergeIntervals(intervals);
|
|
82
|
+
return { ms: sumDurations(merged), merged };
|
|
83
|
+
}
|
|
84
|
+
function intervalsMsToIso(intervals) {
|
|
85
|
+
return intervals.map(([start, end]) => ({
|
|
86
|
+
start: new Date(start).toISOString(),
|
|
87
|
+
end: new Date(end).toISOString()
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
function intervalsIsoToMs(intervals) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const { start, end } of intervals) {
|
|
93
|
+
const s = Date.parse(start);
|
|
94
|
+
const e = Date.parse(end);
|
|
95
|
+
if (Number.isFinite(s) && Number.isFinite(e) && e >= s) out.push([s, e]);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function sumDurations(intervals) {
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (const [start, end] of intervals) total += end - start;
|
|
102
|
+
return total;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/adapters/claude-code/transcript-importer.ts
|
|
106
|
+
var CLAUDE_IMPORT_SOURCE = "claude-code-import";
|
|
107
|
+
function claudeTranscriptToImportPayload(records, options) {
|
|
108
|
+
const placeholderSessionId = prefixedUlid("ses");
|
|
109
|
+
const askAnswers = indexAskAnswers(records);
|
|
110
|
+
const derived = [];
|
|
111
|
+
const relatedFiles = /* @__PURE__ */ new Set();
|
|
112
|
+
let minTs;
|
|
113
|
+
let maxTs;
|
|
114
|
+
let workingDir;
|
|
115
|
+
let claudeSessionId;
|
|
116
|
+
let outputTokens = 0;
|
|
117
|
+
let inputTokens = 0;
|
|
118
|
+
let cachedInputTokens = 0;
|
|
119
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
120
|
+
const engagementTsMs = [];
|
|
121
|
+
const seenEngagementMessageIds = /* @__PURE__ */ new Set();
|
|
122
|
+
for (const record of records) {
|
|
123
|
+
const ts = readString(record.timestamp);
|
|
124
|
+
if (ts === void 0) continue;
|
|
125
|
+
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
126
|
+
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
127
|
+
if (workingDir === void 0) workingDir = readString(record.cwd);
|
|
128
|
+
if (claudeSessionId === void 0) claudeSessionId = readString(record.sessionId);
|
|
129
|
+
if (record.isSidechain !== true) {
|
|
130
|
+
const tsMs = Date.parse(ts);
|
|
131
|
+
if (Number.isFinite(tsMs)) {
|
|
132
|
+
const recType = readString(record.type);
|
|
133
|
+
if (recType === "user") {
|
|
134
|
+
if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);
|
|
135
|
+
} else if (recType === "assistant") {
|
|
136
|
+
const msg = isObject(record.message) ? record.message : void 0;
|
|
137
|
+
const mid = msg !== void 0 ? readString(msg.id) : void 0;
|
|
138
|
+
if (mid === void 0 || !seenEngagementMessageIds.has(mid)) {
|
|
139
|
+
if (mid !== void 0) seenEngagementMessageIds.add(mid);
|
|
140
|
+
engagementTsMs.push(tsMs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (readString(record.type) !== "assistant") continue;
|
|
146
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
147
|
+
const usage = message !== void 0 && isObject(message.usage) ? message.usage : void 0;
|
|
148
|
+
if (usage !== void 0) {
|
|
149
|
+
const messageId = message !== void 0 ? readString(message.id) : void 0;
|
|
150
|
+
const alreadyCounted = messageId !== void 0 && seenMessageIds.has(messageId);
|
|
151
|
+
if (!alreadyCounted) {
|
|
152
|
+
if (messageId !== void 0) seenMessageIds.add(messageId);
|
|
153
|
+
outputTokens += readNonNegInt(usage.output_tokens);
|
|
154
|
+
inputTokens += readNonNegInt(usage.input_tokens);
|
|
155
|
+
cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const cwd = readString(record.cwd) ?? workingDir ?? ".";
|
|
159
|
+
for (const item of toolUses(record)) {
|
|
160
|
+
const name = readString(item.name);
|
|
161
|
+
const input = isObject(item.input) ? item.input : void 0;
|
|
162
|
+
if (input === void 0) continue;
|
|
163
|
+
if (name === "Bash") {
|
|
164
|
+
const command = readString(input.command);
|
|
165
|
+
if (command !== void 0) {
|
|
166
|
+
derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (name === "AskUserQuestion") {
|
|
171
|
+
const useId = readString(item.id);
|
|
172
|
+
const answers = useId !== void 0 ? askAnswers.get(useId) : void 0;
|
|
173
|
+
if (answers !== void 0) {
|
|
174
|
+
for (const [question, answer] of Object.entries(answers)) {
|
|
175
|
+
if (question.length === 0) continue;
|
|
176
|
+
const answerStr = typeof answer === "string" && answer.length > 0 ? answer : void 0;
|
|
177
|
+
const title = answerStr !== void 0 ? `${question} -> ${answerStr}` : question;
|
|
178
|
+
derived.push(decisionRecordedEvent(ts, placeholderSessionId, title));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
|
|
184
|
+
const path2 = readString(input.file_path) ?? readString(input.notebook_path);
|
|
185
|
+
if (path2 !== void 0) {
|
|
186
|
+
const changeType = name === "Write" ? "added" : "modified";
|
|
187
|
+
relatedFiles.add(path2);
|
|
188
|
+
derived.push(fileChangedEvent(ts, placeholderSessionId, path2, changeType));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (minTs === void 0 || maxTs === void 0) return null;
|
|
194
|
+
if (derived.length === 0) return null;
|
|
195
|
+
derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));
|
|
196
|
+
const events = [
|
|
197
|
+
sessionStartedEvent(minTs, placeholderSessionId),
|
|
198
|
+
...derived,
|
|
199
|
+
sessionEndedEvent(maxTs, placeholderSessionId)
|
|
200
|
+
];
|
|
201
|
+
const externalId = options.externalId ?? claudeSessionId;
|
|
202
|
+
const commandCount = derived.reduce((n, e) => e.type === "command_executed" ? n + 1 : n, 0);
|
|
203
|
+
const fileCount = relatedFiles.size;
|
|
204
|
+
const date = minTs.slice(0, 10);
|
|
205
|
+
const label = `claude-code ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}, ${fileCount} ${fileCount === 1 ? "file" : "files"}`;
|
|
206
|
+
const active = engagementTsMs.length >= 2 ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS) : void 0;
|
|
207
|
+
const metricsFields = {
|
|
208
|
+
...outputTokens > 0 ? { output_tokens: outputTokens } : {},
|
|
209
|
+
...inputTokens > 0 ? { input_tokens: inputTokens } : {},
|
|
210
|
+
...cachedInputTokens > 0 ? { cached_input_tokens: cachedInputTokens } : {},
|
|
211
|
+
...active !== void 0 && active.ms > 0 ? {
|
|
212
|
+
active_time_ms: active.ms,
|
|
213
|
+
active_intervals: intervalsMsToIso(active.intervals),
|
|
214
|
+
active_gap_cap_ms: ACTIVE_GAP_CAP_MS,
|
|
215
|
+
active_time_method: ENGAGED_TURNS_METHOD
|
|
216
|
+
} : {}
|
|
217
|
+
};
|
|
218
|
+
const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : void 0;
|
|
219
|
+
const payload = {
|
|
220
|
+
schema_version: "0.1.0",
|
|
221
|
+
session: {
|
|
222
|
+
label,
|
|
223
|
+
workspace_id: options.workspaceId,
|
|
224
|
+
source: {
|
|
225
|
+
kind: CLAUDE_IMPORT_SOURCE,
|
|
226
|
+
version: "0.1.0",
|
|
227
|
+
...externalId !== void 0 ? { external_id: externalId } : {}
|
|
228
|
+
},
|
|
229
|
+
started_at: minTs,
|
|
230
|
+
ended_at: maxTs,
|
|
231
|
+
// Validated against the canonical enum here; importSessionFromJson
|
|
232
|
+
// overwrites it with the literal "imported" regardless.
|
|
233
|
+
status: "imported",
|
|
234
|
+
working_directory: workingDir ?? ".",
|
|
235
|
+
invocation: { command: "claude", args: [], exit_code: null },
|
|
236
|
+
related_files: [...relatedFiles].sort(),
|
|
237
|
+
summary: null,
|
|
238
|
+
...metrics !== void 0 ? { metrics } : {}
|
|
239
|
+
},
|
|
240
|
+
events
|
|
241
|
+
};
|
|
242
|
+
return payload;
|
|
243
|
+
}
|
|
244
|
+
function baseEvent(occurredAt, sessionId) {
|
|
245
|
+
return {
|
|
246
|
+
schema_version: "0.1.0",
|
|
247
|
+
id: prefixedUlid("evt"),
|
|
248
|
+
session_id: sessionId,
|
|
249
|
+
occurred_at: occurredAt,
|
|
250
|
+
source: CLAUDE_IMPORT_SOURCE
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function sessionStartedEvent(occurredAt, sessionId) {
|
|
254
|
+
return { ...baseEvent(occurredAt, sessionId), type: "session_started" };
|
|
255
|
+
}
|
|
256
|
+
function sessionEndedEvent(occurredAt, sessionId) {
|
|
257
|
+
return { ...baseEvent(occurredAt, sessionId), type: "session_ended" };
|
|
258
|
+
}
|
|
259
|
+
function commandExecutedEvent(occurredAt, sessionId, command, cwd) {
|
|
260
|
+
return {
|
|
261
|
+
...baseEvent(occurredAt, sessionId),
|
|
262
|
+
type: "command_executed",
|
|
263
|
+
command: "bash",
|
|
264
|
+
args: ["-c", command],
|
|
265
|
+
cwd,
|
|
266
|
+
exit_code: null,
|
|
267
|
+
duration_ms: 0
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function fileChangedEvent(occurredAt, sessionId, path2, changeType) {
|
|
271
|
+
return {
|
|
272
|
+
...baseEvent(occurredAt, sessionId),
|
|
273
|
+
type: "file_changed",
|
|
274
|
+
path: path2,
|
|
275
|
+
change_type: changeType
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function decisionRecordedEvent(occurredAt, sessionId, title) {
|
|
279
|
+
return {
|
|
280
|
+
...baseEvent(occurredAt, sessionId),
|
|
281
|
+
type: "decision_recorded",
|
|
282
|
+
decision_id: prefixedUlid("decision"),
|
|
283
|
+
title
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function readString(value) {
|
|
287
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
288
|
+
}
|
|
289
|
+
function readNonNegInt(value) {
|
|
290
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
291
|
+
}
|
|
292
|
+
function isObject(value) {
|
|
293
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
294
|
+
}
|
|
295
|
+
function isHumanUserMessage(record) {
|
|
296
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
297
|
+
if (message === void 0) return false;
|
|
298
|
+
const content = message.content;
|
|
299
|
+
if (typeof content === "string") return content.length > 0;
|
|
300
|
+
if (Array.isArray(content)) {
|
|
301
|
+
return content.some((block) => {
|
|
302
|
+
if (!isObject(block)) return false;
|
|
303
|
+
const type = readString(block.type);
|
|
304
|
+
return type !== void 0 && type !== "tool_result";
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
function toolUses(record) {
|
|
310
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
311
|
+
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
312
|
+
const result = [];
|
|
313
|
+
for (const item of content) {
|
|
314
|
+
if (isObject(item) && readString(item.type) === "tool_use") {
|
|
315
|
+
result.push(item);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
function indexAskAnswers(records) {
|
|
321
|
+
const byId = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const record of records) {
|
|
323
|
+
const result = record.toolUseResult;
|
|
324
|
+
if (!isObject(result)) continue;
|
|
325
|
+
const answers = result.answers;
|
|
326
|
+
if (!isObject(answers)) continue;
|
|
327
|
+
const message = isObject(record.message) ? record.message : void 0;
|
|
328
|
+
const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
|
|
329
|
+
for (const item of content) {
|
|
330
|
+
if (isObject(item) && readString(item.type) === "tool_result") {
|
|
331
|
+
const id = readString(item.tool_use_id);
|
|
332
|
+
if (id !== void 0) byId.set(id, answers);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return byId;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/adapters/codex/rollout-importer.ts
|
|
340
|
+
var CODEX_IMPORT_SOURCE = "codex-import";
|
|
341
|
+
function codexRolloutToImportPayload(records, options) {
|
|
342
|
+
const placeholderSessionId = prefixedUlid("ses");
|
|
343
|
+
const outputsByCallId = indexOutputs(records);
|
|
344
|
+
const turnStartMsByTurnId = indexTaskStarts(records);
|
|
345
|
+
const derived = [];
|
|
346
|
+
let minTs;
|
|
347
|
+
let maxTs;
|
|
348
|
+
let workingDir;
|
|
349
|
+
let codexSessionId;
|
|
350
|
+
let lastTokenTotals;
|
|
351
|
+
const engagementTsMs = [];
|
|
352
|
+
const completions = [];
|
|
353
|
+
const completedTurnIds = /* @__PURE__ */ new Set();
|
|
354
|
+
for (const record of records) {
|
|
355
|
+
const ts = readString2(record.timestamp);
|
|
356
|
+
if (ts === void 0) continue;
|
|
357
|
+
if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
|
|
358
|
+
if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
|
|
359
|
+
const payload2 = isObject2(record.payload) ? record.payload : void 0;
|
|
360
|
+
if (payload2 === void 0) continue;
|
|
361
|
+
if (readString2(record.type) === "session_meta") {
|
|
362
|
+
if (workingDir === void 0) workingDir = readString2(payload2.cwd);
|
|
363
|
+
if (codexSessionId === void 0) codexSessionId = readString2(payload2.id);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (readString2(record.type) === "event_msg" && readString2(payload2.type) === "token_count") {
|
|
367
|
+
const info = isObject2(payload2.info) ? payload2.info : void 0;
|
|
368
|
+
const totals = info !== void 0 && isObject2(info.total_token_usage) ? info.total_token_usage : void 0;
|
|
369
|
+
if (totals !== void 0) lastTokenTotals = totals;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (readString2(record.type) === "event_msg") {
|
|
373
|
+
const pt = readString2(payload2.type);
|
|
374
|
+
if (pt === "user_message" || pt === "agent_message" || pt === "task_started" || pt === "task_complete") {
|
|
375
|
+
const tsMs = Date.parse(ts);
|
|
376
|
+
if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);
|
|
377
|
+
}
|
|
378
|
+
if (pt === "task_complete") {
|
|
379
|
+
const turnId = readString2(payload2.turn_id);
|
|
380
|
+
if (turnId === void 0 || !completedTurnIds.has(turnId)) {
|
|
381
|
+
if (turnId !== void 0) completedTurnIds.add(turnId);
|
|
382
|
+
completions.push({
|
|
383
|
+
interval: turnIntervalFromComplete(ts, payload2, turnStartMsByTurnId),
|
|
384
|
+
durationMs: readNonNegInt2(payload2.duration_ms)
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (readString2(record.type) !== "response_item") continue;
|
|
391
|
+
if (readString2(payload2.type) !== "function_call") continue;
|
|
392
|
+
if (readString2(payload2.name) !== "exec_command") continue;
|
|
393
|
+
const command = readExecCommand(payload2.arguments);
|
|
394
|
+
if (command === void 0) continue;
|
|
395
|
+
const cwd = command.workdir ?? workingDir ?? ".";
|
|
396
|
+
const output = readCallId(payload2.call_id, outputsByCallId);
|
|
397
|
+
const execTsMs = Date.parse(ts);
|
|
398
|
+
if (Number.isFinite(execTsMs)) engagementTsMs.push(execTsMs);
|
|
399
|
+
derived.push(
|
|
400
|
+
commandExecutedEvent2(ts, placeholderSessionId, command.cmd, cwd, {
|
|
401
|
+
exitCode: parseExitCode(output),
|
|
402
|
+
durationMs: parseWallTimeMs(output)
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (minTs === void 0 || maxTs === void 0) return null;
|
|
407
|
+
if (derived.length === 0) return null;
|
|
408
|
+
derived.sort((a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at));
|
|
409
|
+
const events = [
|
|
410
|
+
sessionStartedEvent2(minTs, placeholderSessionId),
|
|
411
|
+
...derived,
|
|
412
|
+
sessionEndedEvent2(maxTs, placeholderSessionId)
|
|
413
|
+
];
|
|
414
|
+
const externalId = options.externalId ?? codexSessionId;
|
|
415
|
+
const commandCount = derived.length;
|
|
416
|
+
const date = minTs.slice(0, 10);
|
|
417
|
+
const label = `codex ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
|
|
418
|
+
const minTsMs = Date.parse(minTs);
|
|
419
|
+
const turnIntervals = [];
|
|
420
|
+
let machineActiveMs = 0;
|
|
421
|
+
let allCompletedTurnsHaveDuration = true;
|
|
422
|
+
for (const { interval, durationMs } of completions) {
|
|
423
|
+
if (durationMs <= 0) allCompletedTurnsHaveDuration = false;
|
|
424
|
+
if (interval === void 0) continue;
|
|
425
|
+
const start = Number.isFinite(minTsMs) ? Math.max(interval[0], minTsMs) : interval[0];
|
|
426
|
+
const end = interval[1];
|
|
427
|
+
if (!(start < end)) continue;
|
|
428
|
+
turnIntervals.push([start, end]);
|
|
429
|
+
machineActiveMs += Math.min(durationMs, end - start);
|
|
430
|
+
}
|
|
431
|
+
const pointResult = activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS);
|
|
432
|
+
const active = turnIntervals.length > 0 || pointResult.intervals.length > 0 ? unionDurationMs([...turnIntervals, ...pointResult.intervals]) : void 0;
|
|
433
|
+
const activeMethod = turnIntervals.length > 0 ? TURN_INTERVALS_METHOD : ENGAGED_TURNS_METHOD;
|
|
434
|
+
const machineActive = allCompletedTurnsHaveDuration ? machineActiveMs : 0;
|
|
435
|
+
const tokenFields = lastTokenTotals === void 0 ? {} : {
|
|
436
|
+
...readNonNegInt2(lastTokenTotals.output_tokens) > 0 ? { output_tokens: readNonNegInt2(lastTokenTotals.output_tokens) } : {},
|
|
437
|
+
...readNonNegInt2(lastTokenTotals.input_tokens) > 0 ? { input_tokens: readNonNegInt2(lastTokenTotals.input_tokens) } : {},
|
|
438
|
+
...readNonNegInt2(lastTokenTotals.cached_input_tokens) > 0 ? { cached_input_tokens: readNonNegInt2(lastTokenTotals.cached_input_tokens) } : {},
|
|
439
|
+
...readNonNegInt2(lastTokenTotals.reasoning_output_tokens) > 0 ? { reasoning_output_tokens: readNonNegInt2(lastTokenTotals.reasoning_output_tokens) } : {}
|
|
440
|
+
};
|
|
441
|
+
const metricsFields = {
|
|
442
|
+
...tokenFields,
|
|
443
|
+
...active !== void 0 && active.ms > 0 ? {
|
|
444
|
+
active_time_ms: active.ms,
|
|
445
|
+
active_intervals: intervalsMsToIso(active.merged),
|
|
446
|
+
active_gap_cap_ms: ACTIVE_GAP_CAP_MS,
|
|
447
|
+
active_time_method: activeMethod
|
|
448
|
+
} : {},
|
|
449
|
+
...machineActive > 0 ? { machine_active_time_ms: machineActive } : {}
|
|
450
|
+
};
|
|
451
|
+
const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : void 0;
|
|
452
|
+
const payload = {
|
|
453
|
+
schema_version: "0.1.0",
|
|
454
|
+
session: {
|
|
455
|
+
label,
|
|
456
|
+
workspace_id: options.workspaceId,
|
|
457
|
+
source: {
|
|
458
|
+
kind: CODEX_IMPORT_SOURCE,
|
|
459
|
+
version: "0.1.0",
|
|
460
|
+
...externalId !== void 0 ? { external_id: externalId } : {}
|
|
461
|
+
},
|
|
462
|
+
started_at: minTs,
|
|
463
|
+
ended_at: maxTs,
|
|
464
|
+
// Validated against the canonical enum here; importSessionFromJson
|
|
465
|
+
// overwrites it with the literal "imported" regardless.
|
|
466
|
+
status: "imported",
|
|
467
|
+
working_directory: workingDir ?? ".",
|
|
468
|
+
invocation: { command: "codex", args: [], exit_code: null },
|
|
469
|
+
related_files: [],
|
|
470
|
+
summary: null,
|
|
471
|
+
...metrics !== void 0 ? { metrics } : {}
|
|
472
|
+
},
|
|
473
|
+
events
|
|
474
|
+
};
|
|
475
|
+
return payload;
|
|
476
|
+
}
|
|
477
|
+
function baseEvent2(occurredAt, sessionId) {
|
|
478
|
+
return {
|
|
479
|
+
schema_version: "0.1.0",
|
|
480
|
+
id: prefixedUlid("evt"),
|
|
481
|
+
session_id: sessionId,
|
|
482
|
+
occurred_at: occurredAt,
|
|
483
|
+
source: CODEX_IMPORT_SOURCE
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function sessionStartedEvent2(occurredAt, sessionId) {
|
|
487
|
+
return { ...baseEvent2(occurredAt, sessionId), type: "session_started" };
|
|
488
|
+
}
|
|
489
|
+
function sessionEndedEvent2(occurredAt, sessionId) {
|
|
490
|
+
return { ...baseEvent2(occurredAt, sessionId), type: "session_ended" };
|
|
491
|
+
}
|
|
492
|
+
function commandExecutedEvent2(occurredAt, sessionId, command, cwd, outcome) {
|
|
493
|
+
return {
|
|
494
|
+
...baseEvent2(occurredAt, sessionId),
|
|
495
|
+
type: "command_executed",
|
|
496
|
+
command: "bash",
|
|
497
|
+
args: ["-c", command],
|
|
498
|
+
cwd,
|
|
499
|
+
exit_code: outcome.exitCode,
|
|
500
|
+
duration_ms: outcome.durationMs
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function readString2(value) {
|
|
504
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
505
|
+
}
|
|
506
|
+
function readNonNegInt2(value) {
|
|
507
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
508
|
+
}
|
|
509
|
+
function isObject2(value) {
|
|
510
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
511
|
+
}
|
|
512
|
+
function readExecCommand(value) {
|
|
513
|
+
const raw = readString2(value);
|
|
514
|
+
if (raw === void 0) return void 0;
|
|
515
|
+
let parsed;
|
|
516
|
+
try {
|
|
517
|
+
parsed = JSON.parse(raw);
|
|
518
|
+
} catch {
|
|
519
|
+
return void 0;
|
|
520
|
+
}
|
|
521
|
+
if (!isObject2(parsed)) return void 0;
|
|
522
|
+
const cmd = readString2(parsed.cmd);
|
|
523
|
+
if (cmd === void 0) return void 0;
|
|
524
|
+
return { cmd, workdir: readString2(parsed.workdir) };
|
|
525
|
+
}
|
|
526
|
+
function readCallId(value, outputs) {
|
|
527
|
+
const callId = readString2(value);
|
|
528
|
+
return callId !== void 0 ? outputs.get(callId) : void 0;
|
|
529
|
+
}
|
|
530
|
+
function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
|
|
531
|
+
const endMs = Date.parse(endTs);
|
|
532
|
+
if (!Number.isFinite(endMs)) return void 0;
|
|
533
|
+
const turnId = readString2(payload.turn_id);
|
|
534
|
+
const indexedStart = turnId !== void 0 ? startMsByTurnId.get(turnId) : void 0;
|
|
535
|
+
const durationMs = readNonNegInt2(payload.duration_ms);
|
|
536
|
+
const startMs = indexedStart !== void 0 ? indexedStart : durationMs > 0 ? endMs - durationMs : void 0;
|
|
537
|
+
if (startMs === void 0 || !(startMs < endMs)) return void 0;
|
|
538
|
+
return [startMs, endMs];
|
|
539
|
+
}
|
|
540
|
+
function indexTaskStarts(records) {
|
|
541
|
+
const byTurnId = /* @__PURE__ */ new Map();
|
|
542
|
+
for (const record of records) {
|
|
543
|
+
if (readString2(record.type) !== "event_msg") continue;
|
|
544
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
545
|
+
if (payload === void 0 || readString2(payload.type) !== "task_started") continue;
|
|
546
|
+
const turnId = readString2(payload.turn_id);
|
|
547
|
+
const startMs = Date.parse(readString2(record.timestamp) ?? "");
|
|
548
|
+
if (turnId !== void 0 && Number.isFinite(startMs) && !byTurnId.has(turnId)) {
|
|
549
|
+
byTurnId.set(turnId, startMs);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return byTurnId;
|
|
553
|
+
}
|
|
554
|
+
function parseExitCode(output) {
|
|
555
|
+
if (output === void 0) return null;
|
|
556
|
+
const match = output.match(/Process exited with code (-?\d+)/);
|
|
557
|
+
return match?.[1] !== void 0 ? Number.parseInt(match[1], 10) : null;
|
|
558
|
+
}
|
|
559
|
+
function parseWallTimeMs(output) {
|
|
560
|
+
if (output === void 0) return 0;
|
|
561
|
+
const match = output.match(/Wall time:\s*([\d.]+)\s*seconds/);
|
|
562
|
+
if (match?.[1] === void 0) return 0;
|
|
563
|
+
const seconds = Number.parseFloat(match[1]);
|
|
564
|
+
return Number.isFinite(seconds) ? Math.round(seconds * 1e3) : 0;
|
|
565
|
+
}
|
|
566
|
+
function indexOutputs(records) {
|
|
567
|
+
const byId = /* @__PURE__ */ new Map();
|
|
568
|
+
for (const record of records) {
|
|
569
|
+
if (readString2(record.type) !== "response_item") continue;
|
|
570
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
571
|
+
if (payload === void 0) continue;
|
|
572
|
+
if (readString2(payload.type) !== "function_call_output") continue;
|
|
573
|
+
const callId = readString2(payload.call_id);
|
|
574
|
+
const output = readString2(payload.output);
|
|
575
|
+
if (callId !== void 0 && output !== void 0) byId.set(callId, output);
|
|
576
|
+
}
|
|
577
|
+
return byId;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/approval/approval-store.ts
|
|
581
|
+
import { readdir } from "fs/promises";
|
|
582
|
+
import { join } from "path";
|
|
583
|
+
|
|
584
|
+
// src/lib/error-codes.ts
|
|
585
|
+
function findErrorCode(error, code, depth = 4) {
|
|
586
|
+
let cur = error;
|
|
587
|
+
for (let i = 0; i < depth && cur instanceof Error; i++) {
|
|
588
|
+
const c = cur.code;
|
|
589
|
+
if (typeof c === "string" && c === code) return true;
|
|
590
|
+
cur = cur.cause;
|
|
591
|
+
}
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/schemas/approval.schema.ts
|
|
596
|
+
import { z as z2 } from "zod";
|
|
597
|
+
|
|
70
598
|
// src/schemas/shared.schema.ts
|
|
599
|
+
import { z } from "zod";
|
|
71
600
|
var SchemaVersionSchema = z.literal("0.1.0");
|
|
72
601
|
var IsoTimestampSchema = z.string().datetime({ offset: true });
|
|
73
602
|
var createPrefixedIdSchema = (prefix) => {
|
|
74
603
|
const refiner = (value) => isValidPrefixedId(value) && value.startsWith(`${prefix}_`);
|
|
75
|
-
return z.string().refine(refiner, { message: `Expected ${prefix}_<ULID>` })
|
|
604
|
+
return z.string().refine(refiner, { message: `Expected ${prefix}_<ULID>` }).meta({
|
|
605
|
+
pattern: `^${prefix}_[0-7][0-9A-HJKMNP-TV-Z]{25}$`,
|
|
606
|
+
description: `Basou ${prefix} id: \`${prefix}_\` followed by a 26-character Crockford Base32 ULID.`
|
|
607
|
+
});
|
|
76
608
|
};
|
|
77
609
|
var WorkspaceIdSchema = createPrefixedIdSchema("ws");
|
|
78
610
|
var TaskIdSchema = createPrefixedIdSchema("task");
|
|
@@ -488,13 +1020,19 @@ var SessionStatusSchema = z4.enum([
|
|
|
488
1020
|
]);
|
|
489
1021
|
var SessionSourceKindSchema = z4.enum([
|
|
490
1022
|
"claude-code-adapter",
|
|
1023
|
+
"claude-code-import",
|
|
1024
|
+
"codex-import",
|
|
491
1025
|
"human",
|
|
492
1026
|
"import",
|
|
493
1027
|
"terminal"
|
|
494
1028
|
]);
|
|
495
1029
|
var SessionSourceSchema = z4.object({
|
|
496
1030
|
kind: SessionSourceKindSchema,
|
|
497
|
-
version: z4.literal("0.1.0")
|
|
1031
|
+
version: z4.literal("0.1.0"),
|
|
1032
|
+
// Optional id of the originating session in the SOURCE tool's own
|
|
1033
|
+
// namespace (e.g. the Claude Code session UUID for a `claude-code-import`).
|
|
1034
|
+
// Lets re-imports of the same source be deduplicated; absent for live runs.
|
|
1035
|
+
external_id: z4.string().optional()
|
|
498
1036
|
});
|
|
499
1037
|
var InvocationSchema = z4.object({
|
|
500
1038
|
command: z4.string().min(1),
|
|
@@ -503,6 +1041,17 @@ var InvocationSchema = z4.object({
|
|
|
503
1041
|
// code; the same nullability is mirrored in CommandExecutedEventSchema.
|
|
504
1042
|
exit_code: z4.number().int().nullable()
|
|
505
1043
|
});
|
|
1044
|
+
var SessionMetricsSchema = z4.object({
|
|
1045
|
+
output_tokens: z4.number().int().nonnegative().optional(),
|
|
1046
|
+
input_tokens: z4.number().int().nonnegative().optional(),
|
|
1047
|
+
cached_input_tokens: z4.number().int().nonnegative().optional(),
|
|
1048
|
+
reasoning_output_tokens: z4.number().int().nonnegative().optional(),
|
|
1049
|
+
active_time_ms: z4.number().int().nonnegative().optional(),
|
|
1050
|
+
active_intervals: z4.array(z4.object({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
|
|
1051
|
+
active_gap_cap_ms: z4.number().int().nonnegative().optional(),
|
|
1052
|
+
active_time_method: z4.string().optional(),
|
|
1053
|
+
machine_active_time_ms: z4.number().int().nonnegative().optional()
|
|
1054
|
+
});
|
|
506
1055
|
var SessionInnerSchema = z4.object({
|
|
507
1056
|
id: SessionIdSchema,
|
|
508
1057
|
label: z4.string().optional(),
|
|
@@ -517,7 +1066,8 @@ var SessionInnerSchema = z4.object({
|
|
|
517
1066
|
invocation: InvocationSchema,
|
|
518
1067
|
related_files: z4.array(z4.string()).default([]),
|
|
519
1068
|
events_log: z4.string().default("events.jsonl"),
|
|
520
|
-
summary: z4.string().nullable().optional()
|
|
1069
|
+
summary: z4.string().nullable().optional(),
|
|
1070
|
+
metrics: SessionMetricsSchema.optional()
|
|
521
1071
|
});
|
|
522
1072
|
var SessionSchema = z4.object({
|
|
523
1073
|
schema_version: SchemaVersionSchema,
|
|
@@ -2905,18 +3455,14 @@ async function renderHandoff(input) {
|
|
|
2905
3455
|
const latestSession = [...liveEntries].sort(
|
|
2906
3456
|
(a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
|
|
2907
3457
|
)[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();
|
|
3458
|
+
const latestFiles = latestSession?.session.session.related_files ?? [];
|
|
3459
|
+
const sortedFiles = [...new Set(latestFiles)].sort();
|
|
2914
3460
|
const displayedFiles = sortedFiles.slice(0, limit);
|
|
2915
3461
|
const overflow = Math.max(0, sortedFiles.length - limit);
|
|
2916
3462
|
const suspectCount = entries.filter((e) => e.suspect).length;
|
|
2917
3463
|
const firstEntry = entries[0];
|
|
2918
3464
|
const lastEntry = entries[entries.length - 1];
|
|
2919
|
-
const sessionRange = firstEntry !== void 0 && lastEntry !== void 0 ? `${firstEntry.sessionId}..${lastEntry.sessionId}` : "";
|
|
3465
|
+
const sessionRange = firstEntry !== void 0 && lastEntry !== void 0 ? `${shortIdWithPrefix(firstEntry.sessionId)}..${shortIdWithPrefix(lastEntry.sessionId)}` : "";
|
|
2920
3466
|
const body = formatHandoffBody({
|
|
2921
3467
|
nowIso: input.nowIso,
|
|
2922
3468
|
sessionRange,
|
|
@@ -2956,18 +3502,23 @@ function formatHandoffBody(args) {
|
|
|
2956
3502
|
lines.push("## \u73FE\u5728\u306E\u72B6\u614B");
|
|
2957
3503
|
lines.push("");
|
|
2958
3504
|
if (args.latestSession !== void 0) {
|
|
2959
|
-
const sid = args.latestSession.sessionId;
|
|
2960
3505
|
const status = args.latestSession.session.session.status;
|
|
2961
|
-
|
|
3506
|
+
const label = args.latestSession.session.session.label;
|
|
3507
|
+
const shortId = shortIdWithPrefix(args.latestSession.sessionId);
|
|
3508
|
+
if (label !== void 0 && label !== "") {
|
|
3509
|
+
lines.push(`- \u6700\u7D42 session: ${label} (${status}) [${shortId}]`);
|
|
3510
|
+
} else {
|
|
3511
|
+
lines.push(`- \u6700\u7D42 session: ${shortId} (${status})`);
|
|
3512
|
+
}
|
|
2962
3513
|
} else {
|
|
2963
3514
|
lines.push("- \u6700\u7D42 session: (no live sessions)");
|
|
2964
3515
|
}
|
|
2965
3516
|
if (args.latestActivityRecord !== void 0) {
|
|
2966
3517
|
const statusLabel = args.latestTaskDoc !== void 0 ? args.latestTaskDoc.task.task.status : "status unknown \u2014 task.md missing or invalid";
|
|
2967
3518
|
const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;
|
|
2968
|
-
const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ?
|
|
3519
|
+
const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : "";
|
|
2969
3520
|
lines.push(
|
|
2970
|
-
`- \u6700\u7D42 task: ${args.latestActivityRecord.
|
|
3521
|
+
`- \u6700\u7D42 task: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
|
|
2971
3522
|
);
|
|
2972
3523
|
} else {
|
|
2973
3524
|
lines.push("- \u6700\u7D42 task: (no tasks recorded yet)");
|
|
@@ -2988,7 +3539,7 @@ function formatHandoffBody(args) {
|
|
|
2988
3539
|
lines.push("(no decisions recorded yet)");
|
|
2989
3540
|
} else {
|
|
2990
3541
|
const last = args.decisions[args.decisions.length - 1];
|
|
2991
|
-
lines.push(`- ${last.
|
|
3542
|
+
lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);
|
|
2992
3543
|
lines.push("");
|
|
2993
3544
|
lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
|
|
2994
3545
|
}
|
|
@@ -3016,7 +3567,9 @@ function formatHandoffBody(args) {
|
|
|
3016
3567
|
lines.push("(no pending tasks)");
|
|
3017
3568
|
} else {
|
|
3018
3569
|
for (const t of args.pendingTasks) {
|
|
3019
|
-
lines.push(
|
|
3570
|
+
lines.push(
|
|
3571
|
+
`- ${t.task.task.title} (${t.task.task.status}) [${shortIdWithPrefix(t.task.task.id)}]`
|
|
3572
|
+
);
|
|
3020
3573
|
}
|
|
3021
3574
|
}
|
|
3022
3575
|
lines.push("");
|
|
@@ -3085,6 +3638,11 @@ function shortHandoffId(sessionId) {
|
|
|
3085
3638
|
if (sessionId.startsWith(SES)) return sessionId.slice(SES.length, SES.length + 10);
|
|
3086
3639
|
return sessionId.slice(0, 10);
|
|
3087
3640
|
}
|
|
3641
|
+
function shortIdWithPrefix(id) {
|
|
3642
|
+
const sep = id.indexOf("_");
|
|
3643
|
+
if (sep === -1) return id.slice(0, 10);
|
|
3644
|
+
return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
|
|
3645
|
+
}
|
|
3088
3646
|
|
|
3089
3647
|
// src/lib/duration.ts
|
|
3090
3648
|
var DURATION_RE = /^([1-9]\d*)(ms|s|m|h)$/;
|
|
@@ -3300,6 +3858,9 @@ function classifySpawnError(error) {
|
|
|
3300
3858
|
return new Error("Failed to spawn child process", { cause: error });
|
|
3301
3859
|
}
|
|
3302
3860
|
|
|
3861
|
+
// src/schemas/json-schema.ts
|
|
3862
|
+
import { z as z11 } from "zod";
|
|
3863
|
+
|
|
3303
3864
|
// src/schemas/manifest.schema.ts
|
|
3304
3865
|
import { z as z9 } from "zod";
|
|
3305
3866
|
var ProjectSchema = z9.object({
|
|
@@ -3350,7 +3911,10 @@ var SessionInnerImportSchema = z10.object({
|
|
|
3350
3911
|
workspace_id: WorkspaceIdSchema,
|
|
3351
3912
|
source: z10.object({
|
|
3352
3913
|
kind: SessionSourceKindSchema,
|
|
3353
|
-
version: z10.literal("0.1.0")
|
|
3914
|
+
version: z10.literal("0.1.0"),
|
|
3915
|
+
// Source-tool-native id (e.g. Claude Code session UUID), retained so
|
|
3916
|
+
// re-imports of the same source can be deduplicated.
|
|
3917
|
+
external_id: z10.string().optional()
|
|
3354
3918
|
}),
|
|
3355
3919
|
started_at: IsoTimestampSchema,
|
|
3356
3920
|
ended_at: IsoTimestampSchema.optional(),
|
|
@@ -3363,7 +3927,8 @@ var SessionInnerImportSchema = z10.object({
|
|
|
3363
3927
|
}),
|
|
3364
3928
|
related_files: z10.array(z10.string()).default([]),
|
|
3365
3929
|
events_log: z10.string().optional(),
|
|
3366
|
-
summary: z10.string().nullable().optional()
|
|
3930
|
+
summary: z10.string().nullable().optional(),
|
|
3931
|
+
metrics: SessionMetricsSchema.optional()
|
|
3367
3932
|
}).strict();
|
|
3368
3933
|
var SessionImportPayloadSchema = z10.object({
|
|
3369
3934
|
schema_version: z10.string(),
|
|
@@ -3371,29 +3936,388 @@ var SessionImportPayloadSchema = z10.object({
|
|
|
3371
3936
|
events: z10.array(EventSchema)
|
|
3372
3937
|
}).strict();
|
|
3373
3938
|
|
|
3939
|
+
// src/schemas/json-schema.ts
|
|
3940
|
+
var JSON_SCHEMA_VERSION = "0.1.0";
|
|
3941
|
+
var ID_BASE = `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}`;
|
|
3942
|
+
var JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
|
|
3943
|
+
var DOCUMENTS = [
|
|
3944
|
+
{
|
|
3945
|
+
name: "manifest",
|
|
3946
|
+
schema: ManifestSchema,
|
|
3947
|
+
title: "Basou Manifest",
|
|
3948
|
+
description: "The `.basou/manifest.yaml` workspace manifest."
|
|
3949
|
+
},
|
|
3950
|
+
{
|
|
3951
|
+
name: "session",
|
|
3952
|
+
schema: SessionSchema,
|
|
3953
|
+
title: "Basou Session",
|
|
3954
|
+
description: "A `.basou/sessions/<id>/session.yaml` session record."
|
|
3955
|
+
},
|
|
3956
|
+
{
|
|
3957
|
+
name: "event",
|
|
3958
|
+
schema: EventSchema,
|
|
3959
|
+
title: "Basou Event",
|
|
3960
|
+
description: "One line of a `.basou/sessions/<id>/events.jsonl` stream (a discriminated union over the event `type`)."
|
|
3961
|
+
},
|
|
3962
|
+
{
|
|
3963
|
+
name: "task",
|
|
3964
|
+
schema: TaskSchema,
|
|
3965
|
+
title: "Basou Task",
|
|
3966
|
+
description: "The YAML front matter of a `.basou/tasks/<id>.md` task document."
|
|
3967
|
+
},
|
|
3968
|
+
{
|
|
3969
|
+
name: "approval",
|
|
3970
|
+
schema: ApprovalSchema,
|
|
3971
|
+
title: "Basou Approval",
|
|
3972
|
+
description: "A `.basou/approvals/{pending,resolved}/<id>.yaml` approval record."
|
|
3973
|
+
},
|
|
3974
|
+
{
|
|
3975
|
+
name: "status",
|
|
3976
|
+
schema: StatusSchema,
|
|
3977
|
+
title: "Basou Status",
|
|
3978
|
+
description: "The `.basou/status.json` workspace status snapshot."
|
|
3979
|
+
},
|
|
3980
|
+
{
|
|
3981
|
+
name: "task-index",
|
|
3982
|
+
schema: TaskIndexSchema,
|
|
3983
|
+
title: "Basou Task Index",
|
|
3984
|
+
description: "The `.basou/tasks/index.json` task lookup index."
|
|
3985
|
+
},
|
|
3986
|
+
{
|
|
3987
|
+
name: "session-import",
|
|
3988
|
+
schema: SessionImportPayloadSchema,
|
|
3989
|
+
title: "Basou Session Import Payload",
|
|
3990
|
+
description: "The portable session payload consumed by `basou session import` (and produced by `basou session export`)."
|
|
3991
|
+
}
|
|
3992
|
+
];
|
|
3993
|
+
function buildJsonSchemas() {
|
|
3994
|
+
return DOCUMENTS.map((doc) => {
|
|
3995
|
+
const generated = z11.toJSONSchema(doc.schema, { io: "input" });
|
|
3996
|
+
const { $schema, ...rest } = generated;
|
|
3997
|
+
const schema = {
|
|
3998
|
+
$schema: typeof $schema === "string" ? $schema : JSON_SCHEMA_DIALECT,
|
|
3999
|
+
$id: `${ID_BASE}/${doc.name}.schema.json`,
|
|
4000
|
+
title: doc.title,
|
|
4001
|
+
description: doc.description,
|
|
4002
|
+
...rest
|
|
4003
|
+
};
|
|
4004
|
+
return { name: doc.name, schema };
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
function serializeJsonSchema(schema) {
|
|
4008
|
+
return `${JSON.stringify(schema, null, 2)}
|
|
4009
|
+
`;
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
// src/stats/work-stats.ts
|
|
4013
|
+
import { join as join11 } from "path";
|
|
4014
|
+
function resolveTimeZone(timeZone) {
|
|
4015
|
+
if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
|
|
4016
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
4017
|
+
}
|
|
4018
|
+
var STATUS_ORDER = [
|
|
4019
|
+
"completed",
|
|
4020
|
+
"failed",
|
|
4021
|
+
"running",
|
|
4022
|
+
"interrupted",
|
|
4023
|
+
"waiting_approval",
|
|
4024
|
+
"initialized",
|
|
4025
|
+
"imported",
|
|
4026
|
+
"archived"
|
|
4027
|
+
];
|
|
4028
|
+
async function computeWorkStats(input) {
|
|
4029
|
+
const { now } = input;
|
|
4030
|
+
const timeZone = resolveTimeZone(input.timeZone);
|
|
4031
|
+
const unreadableEmitted = /* @__PURE__ */ new Set();
|
|
4032
|
+
const wrappedSkip = (sid, reason) => {
|
|
4033
|
+
if (reason === "events_jsonl_unreadable") unreadableEmitted.add(sid);
|
|
4034
|
+
input.onSessionSkip?.(sid, reason);
|
|
4035
|
+
};
|
|
4036
|
+
const loadOpts = { now, onSkip: wrappedSkip };
|
|
4037
|
+
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
4038
|
+
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
4039
|
+
const sessions = [];
|
|
4040
|
+
for (const entry of entries) {
|
|
4041
|
+
const events = [];
|
|
4042
|
+
let eventsUnreadable = false;
|
|
4043
|
+
try {
|
|
4044
|
+
for await (const ev of replayEvents(join11(input.paths.sessions, entry.sessionId), {
|
|
4045
|
+
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
4046
|
+
})) {
|
|
4047
|
+
events.push(ev);
|
|
4048
|
+
}
|
|
4049
|
+
} catch {
|
|
4050
|
+
eventsUnreadable = true;
|
|
4051
|
+
if (!unreadableEmitted.has(entry.sessionId)) {
|
|
4052
|
+
wrappedSkip(entry.sessionId, "events_jsonl_unreadable");
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
sessions.push(
|
|
4056
|
+
sessionWorkStatsFromEvents(
|
|
4057
|
+
entry.sessionId,
|
|
4058
|
+
entry.session.session,
|
|
4059
|
+
events,
|
|
4060
|
+
now,
|
|
4061
|
+
eventsUnreadable
|
|
4062
|
+
)
|
|
4063
|
+
);
|
|
4064
|
+
}
|
|
4065
|
+
const allIntervals = [];
|
|
4066
|
+
for (const s of sessions) allIntervals.push(...intervalsIsoToMs(s.activeIntervals));
|
|
4067
|
+
const union = unionDurationMs(allIntervals);
|
|
4068
|
+
return {
|
|
4069
|
+
generatedAt: now.toISOString(),
|
|
4070
|
+
activeGapCapMs: ACTIVE_GAP_CAP_MS,
|
|
4071
|
+
timeZone,
|
|
4072
|
+
totals: computeTotals(sessions, union.ms),
|
|
4073
|
+
sessions,
|
|
4074
|
+
bySource: computeBySource(sessions),
|
|
4075
|
+
byStatus: computeByStatus(sessions),
|
|
4076
|
+
byDay: computeByDay(sessions, union.merged, timeZone)
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
function sessionWorkStatsFromEvents(sessionId, inner, events, now, eventsUnreadable = false) {
|
|
4080
|
+
let commandCount = 0;
|
|
4081
|
+
let fileChangedCount = 0;
|
|
4082
|
+
let decisionCount = 0;
|
|
4083
|
+
let commandTimeMs = 0;
|
|
4084
|
+
const timestamps = [];
|
|
4085
|
+
for (const ev of events) {
|
|
4086
|
+
const t = Date.parse(ev.occurred_at);
|
|
4087
|
+
if (Number.isFinite(t)) timestamps.push(t);
|
|
4088
|
+
if (ev.type === "command_executed") {
|
|
4089
|
+
commandCount++;
|
|
4090
|
+
commandTimeMs += ev.duration_ms;
|
|
4091
|
+
} else if (ev.type === "file_changed") {
|
|
4092
|
+
fileChangedCount++;
|
|
4093
|
+
} else if (ev.type === "decision_recorded") {
|
|
4094
|
+
decisionCount++;
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
const span = computeSpan(inner.started_at, inner.ended_at, now);
|
|
4098
|
+
const tokens = readTokens(inner.metrics);
|
|
4099
|
+
const active = resolveActiveTime(inner.metrics, timestamps);
|
|
4100
|
+
const machineActiveTimeMs = inner.metrics?.machine_active_time_ms ?? 0;
|
|
4101
|
+
return {
|
|
4102
|
+
sessionId,
|
|
4103
|
+
label: inner.label,
|
|
4104
|
+
status: inner.status,
|
|
4105
|
+
sourceKind: inner.source.kind,
|
|
4106
|
+
startedAt: inner.started_at,
|
|
4107
|
+
endedAt: inner.ended_at,
|
|
4108
|
+
open: inner.ended_at === void 0,
|
|
4109
|
+
sessionSpanMs: span.ms,
|
|
4110
|
+
commandTimeMs,
|
|
4111
|
+
activeTimeMs: active.ms,
|
|
4112
|
+
activeTimeBasis: active.basis,
|
|
4113
|
+
activeIntervals: intervalsMsToIso(active.intervals),
|
|
4114
|
+
machineActiveTimeMs,
|
|
4115
|
+
activeTimeMethod: inner.metrics?.active_time_method,
|
|
4116
|
+
commandCount,
|
|
4117
|
+
fileChangedCount,
|
|
4118
|
+
decisionCount,
|
|
4119
|
+
eventCount: events.length,
|
|
4120
|
+
tokens,
|
|
4121
|
+
availability: {
|
|
4122
|
+
span: true,
|
|
4123
|
+
commandTime: inner.source.kind !== "claude-code-import",
|
|
4124
|
+
activeTime: active.intervals.length > 0,
|
|
4125
|
+
tokens: hasTokens(tokens),
|
|
4126
|
+
machineActive: machineActiveTimeMs > 0
|
|
4127
|
+
},
|
|
4128
|
+
spanClamped: span.clamped,
|
|
4129
|
+
eventsUnreadable
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
function resolveActiveTime(metrics, eventTimestamps) {
|
|
4133
|
+
const stored = metrics?.active_intervals;
|
|
4134
|
+
if (stored !== void 0 && stored.length > 0) {
|
|
4135
|
+
const intervals = intervalsIsoToMs(stored);
|
|
4136
|
+
const ms = intervals.reduce((n, [start, end]) => n + (end - start), 0);
|
|
4137
|
+
return { ms, intervals, basis: "engaged-turns" };
|
|
4138
|
+
}
|
|
4139
|
+
const derived = activeTimeFromTimestamps(eventTimestamps, ACTIVE_GAP_CAP_MS);
|
|
4140
|
+
return { ms: derived.ms, intervals: derived.intervals, basis: "events" };
|
|
4141
|
+
}
|
|
4142
|
+
function computeSpan(startedAt, endedAt, now) {
|
|
4143
|
+
const start = Date.parse(startedAt);
|
|
4144
|
+
const end = endedAt !== void 0 ? Date.parse(endedAt) : now.getTime();
|
|
4145
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return { ms: 0, clamped: true };
|
|
4146
|
+
const raw = end - start;
|
|
4147
|
+
return raw < 0 ? { ms: 0, clamped: true } : { ms: raw, clamped: false };
|
|
4148
|
+
}
|
|
4149
|
+
function readTokens(metrics) {
|
|
4150
|
+
return {
|
|
4151
|
+
output: metrics?.output_tokens ?? 0,
|
|
4152
|
+
input: metrics?.input_tokens ?? 0,
|
|
4153
|
+
cached: metrics?.cached_input_tokens ?? 0,
|
|
4154
|
+
reasoning: metrics?.reasoning_output_tokens ?? 0
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
function hasTokens(t) {
|
|
4158
|
+
return t.output > 0 || t.input > 0 || t.cached > 0 || t.reasoning > 0;
|
|
4159
|
+
}
|
|
4160
|
+
function emptyTokens() {
|
|
4161
|
+
return { output: 0, input: 0, cached: 0, reasoning: 0 };
|
|
4162
|
+
}
|
|
4163
|
+
function addTokens(a, b) {
|
|
4164
|
+
a.output += b.output;
|
|
4165
|
+
a.input += b.input;
|
|
4166
|
+
a.cached += b.cached;
|
|
4167
|
+
a.reasoning += b.reasoning;
|
|
4168
|
+
}
|
|
4169
|
+
function computeTotals(sessions, billableActiveTimeMs) {
|
|
4170
|
+
const tokens = emptyTokens();
|
|
4171
|
+
const totals = {
|
|
4172
|
+
sessionCount: sessions.length,
|
|
4173
|
+
openSessionCount: 0,
|
|
4174
|
+
sessionSpanMs: 0,
|
|
4175
|
+
commandTimeMs: 0,
|
|
4176
|
+
activeTimeMs: 0,
|
|
4177
|
+
billableActiveTimeMs,
|
|
4178
|
+
machineActiveTimeMs: 0,
|
|
4179
|
+
commandCount: 0,
|
|
4180
|
+
fileChangedCount: 0,
|
|
4181
|
+
decisionCount: 0,
|
|
4182
|
+
eventCount: 0,
|
|
4183
|
+
tokens,
|
|
4184
|
+
commandTimeReliable: true,
|
|
4185
|
+
tokensAvailable: false,
|
|
4186
|
+
machineActiveAvailable: false
|
|
4187
|
+
};
|
|
4188
|
+
for (const s of sessions) {
|
|
4189
|
+
if (s.open) totals.openSessionCount++;
|
|
4190
|
+
totals.sessionSpanMs += s.sessionSpanMs;
|
|
4191
|
+
totals.commandTimeMs += s.commandTimeMs;
|
|
4192
|
+
totals.activeTimeMs += s.activeTimeMs;
|
|
4193
|
+
totals.machineActiveTimeMs += s.machineActiveTimeMs;
|
|
4194
|
+
totals.commandCount += s.commandCount;
|
|
4195
|
+
totals.fileChangedCount += s.fileChangedCount;
|
|
4196
|
+
totals.decisionCount += s.decisionCount;
|
|
4197
|
+
totals.eventCount += s.eventCount;
|
|
4198
|
+
addTokens(tokens, s.tokens);
|
|
4199
|
+
if (!s.availability.commandTime) totals.commandTimeReliable = false;
|
|
4200
|
+
if (s.availability.tokens) totals.tokensAvailable = true;
|
|
4201
|
+
if (s.availability.machineActive) totals.machineActiveAvailable = true;
|
|
4202
|
+
}
|
|
4203
|
+
return totals;
|
|
4204
|
+
}
|
|
4205
|
+
function computeBySource(sessions) {
|
|
4206
|
+
const map = /* @__PURE__ */ new Map();
|
|
4207
|
+
for (const s of sessions) {
|
|
4208
|
+
let row = map.get(s.sourceKind);
|
|
4209
|
+
if (row === void 0) {
|
|
4210
|
+
row = {
|
|
4211
|
+
sourceKind: s.sourceKind,
|
|
4212
|
+
sessionCount: 0,
|
|
4213
|
+
sessionSpanMs: 0,
|
|
4214
|
+
commandTimeMs: 0,
|
|
4215
|
+
activeTimeMs: 0,
|
|
4216
|
+
machineActiveTimeMs: 0,
|
|
4217
|
+
commandCount: 0,
|
|
4218
|
+
fileChangedCount: 0,
|
|
4219
|
+
decisionCount: 0,
|
|
4220
|
+
eventCount: 0,
|
|
4221
|
+
tokens: emptyTokens(),
|
|
4222
|
+
commandTimeReliable: true,
|
|
4223
|
+
tokensAvailable: false,
|
|
4224
|
+
machineActiveAvailable: false
|
|
4225
|
+
};
|
|
4226
|
+
map.set(s.sourceKind, row);
|
|
4227
|
+
}
|
|
4228
|
+
row.sessionCount++;
|
|
4229
|
+
row.sessionSpanMs += s.sessionSpanMs;
|
|
4230
|
+
row.commandTimeMs += s.commandTimeMs;
|
|
4231
|
+
row.activeTimeMs += s.activeTimeMs;
|
|
4232
|
+
row.machineActiveTimeMs += s.machineActiveTimeMs;
|
|
4233
|
+
row.commandCount += s.commandCount;
|
|
4234
|
+
row.fileChangedCount += s.fileChangedCount;
|
|
4235
|
+
row.decisionCount += s.decisionCount;
|
|
4236
|
+
row.eventCount += s.eventCount;
|
|
4237
|
+
addTokens(row.tokens, s.tokens);
|
|
4238
|
+
if (!s.availability.commandTime) row.commandTimeReliable = false;
|
|
4239
|
+
if (s.availability.tokens) row.tokensAvailable = true;
|
|
4240
|
+
if (s.availability.machineActive) row.machineActiveAvailable = true;
|
|
4241
|
+
}
|
|
4242
|
+
return [...map.values()].sort((a, b) => a.sourceKind.localeCompare(b.sourceKind));
|
|
4243
|
+
}
|
|
4244
|
+
function computeByStatus(sessions) {
|
|
4245
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4246
|
+
for (const s of sessions) counts.set(s.status, (counts.get(s.status) ?? 0) + 1);
|
|
4247
|
+
const ordered = [];
|
|
4248
|
+
for (const status of STATUS_ORDER) {
|
|
4249
|
+
const count = counts.get(status);
|
|
4250
|
+
if (count !== void 0 && count > 0) ordered.push({ status, count });
|
|
4251
|
+
}
|
|
4252
|
+
return ordered;
|
|
4253
|
+
}
|
|
4254
|
+
function computeByDay(sessions, unionMerged, timeZone) {
|
|
4255
|
+
const days = /* @__PURE__ */ new Map();
|
|
4256
|
+
const ensure = (date) => {
|
|
4257
|
+
let day = days.get(date);
|
|
4258
|
+
if (day === void 0) {
|
|
4259
|
+
day = {
|
|
4260
|
+
date,
|
|
4261
|
+
billableActiveTimeMs: 0,
|
|
4262
|
+
machineActiveTimeMs: 0,
|
|
4263
|
+
sessionCount: 0,
|
|
4264
|
+
commandCount: 0,
|
|
4265
|
+
fileChangedCount: 0,
|
|
4266
|
+
decisionCount: 0,
|
|
4267
|
+
tokens: emptyTokens()
|
|
4268
|
+
};
|
|
4269
|
+
days.set(date, day);
|
|
4270
|
+
}
|
|
4271
|
+
return day;
|
|
4272
|
+
};
|
|
4273
|
+
for (const [start, end] of unionMerged) {
|
|
4274
|
+
ensure(tzDate(start, timeZone)).billableActiveTimeMs += end - start;
|
|
4275
|
+
}
|
|
4276
|
+
for (const s of sessions) {
|
|
4277
|
+
const startedMs = Date.parse(s.startedAt);
|
|
4278
|
+
if (!Number.isFinite(startedMs)) continue;
|
|
4279
|
+
const day = ensure(tzDate(startedMs, timeZone));
|
|
4280
|
+
day.sessionCount++;
|
|
4281
|
+
day.machineActiveTimeMs += s.machineActiveTimeMs;
|
|
4282
|
+
day.commandCount += s.commandCount;
|
|
4283
|
+
day.fileChangedCount += s.fileChangedCount;
|
|
4284
|
+
day.decisionCount += s.decisionCount;
|
|
4285
|
+
addTokens(day.tokens, s.tokens);
|
|
4286
|
+
}
|
|
4287
|
+
return [...days.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
4288
|
+
}
|
|
4289
|
+
function tzDate(ms, timeZone) {
|
|
4290
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
4291
|
+
timeZone,
|
|
4292
|
+
year: "numeric",
|
|
4293
|
+
month: "2-digit",
|
|
4294
|
+
day: "2-digit"
|
|
4295
|
+
}).format(new Date(ms));
|
|
4296
|
+
}
|
|
4297
|
+
|
|
3374
4298
|
// src/storage/basou-dir.ts
|
|
3375
4299
|
import { lstat as lstat3, mkdir as mkdir3 } from "fs/promises";
|
|
3376
|
-
import { join as
|
|
4300
|
+
import { join as join12 } from "path";
|
|
3377
4301
|
function basouPaths(repositoryRoot) {
|
|
3378
|
-
const root =
|
|
3379
|
-
const approvalsBase =
|
|
4302
|
+
const root = join12(repositoryRoot, ".basou");
|
|
4303
|
+
const approvalsBase = join12(root, "approvals");
|
|
3380
4304
|
return {
|
|
3381
4305
|
root,
|
|
3382
|
-
sessions:
|
|
3383
|
-
tasks:
|
|
4306
|
+
sessions: join12(root, "sessions"),
|
|
4307
|
+
tasks: join12(root, "tasks"),
|
|
3384
4308
|
approvals: {
|
|
3385
|
-
pending:
|
|
3386
|
-
resolved:
|
|
4309
|
+
pending: join12(approvalsBase, "pending"),
|
|
4310
|
+
resolved: join12(approvalsBase, "resolved")
|
|
3387
4311
|
},
|
|
3388
|
-
locks:
|
|
3389
|
-
logs:
|
|
3390
|
-
raw:
|
|
3391
|
-
tmp:
|
|
4312
|
+
locks: join12(root, "locks"),
|
|
4313
|
+
logs: join12(root, "logs"),
|
|
4314
|
+
raw: join12(root, "raw"),
|
|
4315
|
+
tmp: join12(root, "tmp"),
|
|
3392
4316
|
files: {
|
|
3393
|
-
manifest:
|
|
3394
|
-
status:
|
|
3395
|
-
handoff:
|
|
3396
|
-
decisions:
|
|
4317
|
+
manifest: join12(root, "manifest.yaml"),
|
|
4318
|
+
status: join12(root, "status.json"),
|
|
4319
|
+
handoff: join12(root, "handoff.md"),
|
|
4320
|
+
decisions: join12(root, "decisions.md")
|
|
3397
4321
|
}
|
|
3398
4322
|
};
|
|
3399
4323
|
}
|
|
@@ -3450,11 +4374,11 @@ function hasErrorCode3(error) {
|
|
|
3450
4374
|
|
|
3451
4375
|
// src/storage/gitignore.ts
|
|
3452
4376
|
import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
3453
|
-
import { join as
|
|
4377
|
+
import { join as join13 } from "path";
|
|
3454
4378
|
var MARKER = "# Basou - default ignore";
|
|
3455
4379
|
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
4380
|
async function appendBasouGitignore(repositoryRoot) {
|
|
3457
|
-
const gitignorePath =
|
|
4381
|
+
const gitignorePath = join13(repositoryRoot, ".gitignore");
|
|
3458
4382
|
let body;
|
|
3459
4383
|
let existed;
|
|
3460
4384
|
try {
|
|
@@ -3668,7 +4592,7 @@ function hasErrorCode6(error) {
|
|
|
3668
4592
|
// src/storage/session-import.ts
|
|
3669
4593
|
import { mkdir as mkdir4, rm as rm2 } from "fs/promises";
|
|
3670
4594
|
import { homedir as homedir2 } from "os";
|
|
3671
|
-
import { join as
|
|
4595
|
+
import { join as join14 } from "path";
|
|
3672
4596
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
3673
4597
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
3674
4598
|
throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
|
|
@@ -3693,7 +4617,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
3693
4617
|
pathSanitizeReport
|
|
3694
4618
|
};
|
|
3695
4619
|
}
|
|
3696
|
-
const sessionDir =
|
|
4620
|
+
const sessionDir = join14(paths.sessions, newSessionId);
|
|
3697
4621
|
try {
|
|
3698
4622
|
await mkdir4(sessionDir, { recursive: true });
|
|
3699
4623
|
} catch (error) {
|
|
@@ -3706,7 +4630,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
3706
4630
|
throw error;
|
|
3707
4631
|
}
|
|
3708
4632
|
try {
|
|
3709
|
-
const sessionYamlPath =
|
|
4633
|
+
const sessionYamlPath = join14(sessionDir, "session.yaml");
|
|
3710
4634
|
await linkYamlFile(sessionYamlPath, sessionRecord);
|
|
3711
4635
|
} catch (error) {
|
|
3712
4636
|
await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -3787,7 +4711,8 @@ function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
|
3787
4711
|
invocation: input.invocation,
|
|
3788
4712
|
related_files: relatedSanitized.sanitized,
|
|
3789
4713
|
events_log: "events.jsonl",
|
|
3790
|
-
summary: input.summary ?? null
|
|
4714
|
+
summary: input.summary ?? null,
|
|
4715
|
+
...input.metrics !== void 0 ? { metrics: input.metrics } : {}
|
|
3791
4716
|
};
|
|
3792
4717
|
return {
|
|
3793
4718
|
record: { schema_version: "0.1.0", session: inner },
|
|
@@ -3801,10 +4726,13 @@ function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
|
3801
4726
|
// src/index.ts
|
|
3802
4727
|
var BASOU_CORE_VERSION = "0.1.0";
|
|
3803
4728
|
export {
|
|
4729
|
+
ACTIVE_GAP_CAP_MS,
|
|
3804
4730
|
ApprovalIdSchema,
|
|
3805
4731
|
ApprovalSchema,
|
|
3806
4732
|
ApprovalStatusSchema,
|
|
3807
4733
|
BASOU_CORE_VERSION,
|
|
4734
|
+
CLAUDE_IMPORT_SOURCE,
|
|
4735
|
+
CODEX_IMPORT_SOURCE,
|
|
3808
4736
|
ChildProcessRunner,
|
|
3809
4737
|
DecisionIdSchema,
|
|
3810
4738
|
EventIdSchema,
|
|
@@ -3815,6 +4743,7 @@ export {
|
|
|
3815
4743
|
GENERATED_START,
|
|
3816
4744
|
ID_PREFIXES,
|
|
3817
4745
|
IsoTimestampSchema,
|
|
4746
|
+
JSON_SCHEMA_VERSION,
|
|
3818
4747
|
ManifestSchema,
|
|
3819
4748
|
RiskLevelSchema,
|
|
3820
4749
|
STUCK_THRESHOLD_MS,
|
|
@@ -3822,6 +4751,7 @@ export {
|
|
|
3822
4751
|
SessionIdSchema,
|
|
3823
4752
|
SessionImportPayloadSchema,
|
|
3824
4753
|
SessionInnerImportSchema,
|
|
4754
|
+
SessionMetricsSchema,
|
|
3825
4755
|
SessionSchema,
|
|
3826
4756
|
SessionSourceKindSchema,
|
|
3827
4757
|
SessionStatusSchema,
|
|
@@ -3838,9 +4768,13 @@ export {
|
|
|
3838
4768
|
archiveTask,
|
|
3839
4769
|
assertBasouRootSafe,
|
|
3840
4770
|
basouPaths,
|
|
4771
|
+
buildJsonSchemas,
|
|
3841
4772
|
buildStatusSnapshot,
|
|
3842
4773
|
classifySuspect,
|
|
3843
4774
|
claudeCodeAdapterMetadata,
|
|
4775
|
+
claudeTranscriptToImportPayload,
|
|
4776
|
+
codexRolloutToImportPayload,
|
|
4777
|
+
computeWorkStats,
|
|
3844
4778
|
createAdHocSessionWithEvent,
|
|
3845
4779
|
createManifest,
|
|
3846
4780
|
createTaskWithEvent,
|
|
@@ -3887,6 +4821,8 @@ export {
|
|
|
3887
4821
|
sanitizePath,
|
|
3888
4822
|
sanitizeRelatedFiles,
|
|
3889
4823
|
sanitizeWorkingDirectory,
|
|
4824
|
+
serializeJsonSchema,
|
|
4825
|
+
sessionWorkStatsFromEvents,
|
|
3890
4826
|
summarizeAdapterOutput,
|
|
3891
4827
|
tryRemoteUrl,
|
|
3892
4828
|
ulid,
|