@basou/core 0.23.0 → 0.25.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,69 +21,165 @@ function summarizeAdapterOutput(_stream, _raw) {
21
21
  throw new Error("adapter_output summary is not implemented in this release");
22
22
  }
23
23
 
24
+ // src/adapters/claude-code/ask-user-question.ts
25
+ function readString(value) {
26
+ return typeof value === "string" && value.length > 0 ? value : void 0;
27
+ }
28
+ function isObject(value) {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30
+ }
31
+ function toolUsesOf(record) {
32
+ const message = isObject(record.message) ? record.message : void 0;
33
+ const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
34
+ const result = [];
35
+ for (const item of content) {
36
+ if (isObject(item) && readString(item.type) === "tool_use") result.push(item);
37
+ }
38
+ return result;
39
+ }
40
+ function indexAskAnswers(records) {
41
+ const byId = /* @__PURE__ */ new Map();
42
+ for (const record of records) {
43
+ const result = record.toolUseResult;
44
+ if (!isObject(result)) continue;
45
+ const answers = result.answers;
46
+ if (!isObject(answers)) continue;
47
+ const message = isObject(record.message) ? record.message : void 0;
48
+ const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
49
+ for (const item of content) {
50
+ if (isObject(item) && readString(item.type) === "tool_result") {
51
+ const id = readString(item.tool_use_id);
52
+ if (id !== void 0) byId.set(id, answers);
53
+ }
54
+ }
55
+ }
56
+ return byId;
57
+ }
58
+ function readOfferedOptions(input) {
59
+ const byQuestion = /* @__PURE__ */ new Map();
60
+ const questions = Array.isArray(input.questions) ? input.questions : [];
61
+ for (const q of questions) {
62
+ if (!isObject(q)) continue;
63
+ const text = readString(q.question);
64
+ if (text === void 0) continue;
65
+ const labels = /* @__PURE__ */ new Set();
66
+ const options = Array.isArray(q.options) ? q.options : [];
67
+ for (const o of options) {
68
+ if (!isObject(o)) continue;
69
+ const label = readString(o.label);
70
+ if (label !== void 0) labels.add(label.trim());
71
+ }
72
+ byQuestion.set(text, labels);
73
+ }
74
+ return byQuestion;
75
+ }
76
+ function countUncapturedDecisionPoints(records) {
77
+ const answersById = indexAskAnswers(records);
78
+ if (answersById.size === 0) return 0;
79
+ let count = 0;
80
+ for (const record of records) {
81
+ if (readString(record.type) !== "assistant") continue;
82
+ for (const tool of toolUsesOf(record)) {
83
+ if (readString(tool.name) !== "AskUserQuestion") continue;
84
+ const useId = readString(tool.id);
85
+ const answers = useId !== void 0 ? answersById.get(useId) : void 0;
86
+ if (answers === void 0) continue;
87
+ const input = isObject(tool.input) ? tool.input : void 0;
88
+ if (input === void 0) continue;
89
+ const offeredByQuestion = readOfferedOptions(input);
90
+ for (const [question, answer] of Object.entries(answers)) {
91
+ if (question.length === 0) continue;
92
+ const trimmed = typeof answer === "string" ? answer.trim() : "";
93
+ if (trimmed.length === 0) continue;
94
+ const offered = offeredByQuestion.get(question);
95
+ if (offered === void 0 || !offered.has(trimmed)) count += 1;
96
+ }
97
+ }
98
+ }
99
+ return count;
100
+ }
101
+
24
102
  // src/adapters/claude-code/stop-hook.ts
25
- var DEFAULT_STOP_HOOK_MIN_ACTIONS = 5;
26
- var CAPTURE_COMMAND_PATTERN = /(?:^|[\n;&|(])\s*basou\s+(?:decision\s+(?:capture|record)|note)\b/;
103
+ var DEFAULT_STOP_HOOK_MIN_EDITS = 2;
104
+ var CAPTURE_INVOCATION = /(?:basou|(?:\S*\/)?node\s+\S*cli\/dist\/index\.js)/;
105
+ var CAPTURE_VERB = /(?:decision\s+(?:capture|record)|note)\b/;
106
+ var CAPTURE_COMMAND_PATTERN = new RegExp(
107
+ `(?:^|[\\n;&|(])\\s*${CAPTURE_INVOCATION.source}\\s+${CAPTURE_VERB.source}`
108
+ );
27
109
  var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "NotebookEdit"]);
28
110
  function evaluateStopHook(input) {
29
- const minActions = input.minActions ?? DEFAULT_STOP_HOOK_MIN_ACTIONS;
111
+ const minEdits = input.minEdits ?? DEFAULT_STOP_HOOK_MIN_EDITS;
30
112
  if (input.stopHookActive) {
31
- return { kind: "silent", reason: "stop_hook_active", commandCount: 0, fileCount: 0 };
113
+ return {
114
+ kind: "silent",
115
+ reason: "stop_hook_active",
116
+ commandCount: 0,
117
+ fileCount: 0,
118
+ decisionPointCount: 0
119
+ };
32
120
  }
33
121
  let commandCount = 0;
34
122
  let fileCount = 0;
35
123
  let captured = false;
36
124
  for (const record of input.records) {
37
- if (readString(record.type) !== "assistant") continue;
38
- for (const tool of toolUsesOf(record)) {
39
- const name = readString(tool.name);
125
+ if (readString2(record.type) !== "assistant") continue;
126
+ for (const tool of toolUsesOf2(record)) {
127
+ const name = readString2(tool.name);
40
128
  if (name === void 0) continue;
41
129
  if (name === "Bash") {
42
130
  commandCount += 1;
43
- const input2 = isObject(tool.input) ? tool.input : void 0;
44
- const command = input2 !== void 0 ? readString(input2.command) : void 0;
131
+ const toolInput = isObject2(tool.input) ? tool.input : void 0;
132
+ const command = toolInput !== void 0 ? readString2(toolInput.command) : void 0;
45
133
  if (command !== void 0 && CAPTURE_COMMAND_PATTERN.test(command)) captured = true;
46
134
  } else if (FILE_EDIT_TOOLS.has(name)) {
47
135
  fileCount += 1;
48
136
  }
49
137
  }
50
138
  }
139
+ const decisionPointCount = countUncapturedDecisionPoints(input.records);
140
+ const counts = { commandCount, fileCount, decisionPointCount };
51
141
  if (captured) {
52
- return { kind: "silent", reason: "already_captured", commandCount, fileCount };
142
+ return { kind: "silent", reason: "already_captured", ...counts };
53
143
  }
54
- if (commandCount + fileCount < minActions) {
55
- return { kind: "silent", reason: "not_substantive", commandCount, fileCount };
144
+ const substantive = fileCount >= minEdits || decisionPointCount > 0;
145
+ if (!substantive) {
146
+ return { kind: "silent", reason: "not_substantive", ...counts };
56
147
  }
57
- return {
58
- kind: "nudge",
59
- additionalContext: renderNudge(commandCount, fileCount),
60
- commandCount,
61
- fileCount
62
- };
148
+ return { kind: "nudge", additionalContext: renderNudge(counts), ...counts };
63
149
  }
64
- function renderNudge(commandCount, fileCount) {
65
- const ran = `${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
66
- const edited = `${fileCount} ${fileCount === 1 ? "file" : "files"}`;
150
+ function renderNudge(counts) {
151
+ const did = [];
152
+ if (counts.commandCount > 0) {
153
+ did.push(`ran ${counts.commandCount} ${counts.commandCount === 1 ? "command" : "commands"}`);
154
+ }
155
+ if (counts.fileCount > 0) {
156
+ did.push(`edited ${counts.fileCount} ${counts.fileCount === 1 ? "file" : "files"}`);
157
+ }
158
+ if (counts.decisionPointCount > 0) {
159
+ const n = counts.decisionPointCount;
160
+ did.push(`answered ${n} open-ended ${n === 1 ? "question" : "questions"}`);
161
+ }
162
+ const summary = did.length > 0 ? did.join(", ") : "did substantive work";
67
163
  return [
68
- `This session ran ${ran} and edited ${edited} but recorded no decisions or next step.`,
164
+ `This session ${summary} but recorded no decisions or next step.`,
69
165
  "If meaningful decisions were made (the chosen approach, rejected alternatives, and why) or there is a clear next step, capture them now so the next session can resume correctly:",
70
166
  ' - Decisions: run `basou decision capture` and pipe a JSON array (one object per decision; "title" required, plus optional rationale/alternatives/rejected_reason/linked_files; set "kind":"track" for an unfinished strategic direction).',
71
167
  ' - Next step: run `basou note "<what you would do next>"`.',
72
168
  "If nothing is worth capturing, just stop \u2014 do not invent decisions."
73
169
  ].join("\n");
74
170
  }
75
- function readString(value) {
171
+ function readString2(value) {
76
172
  return typeof value === "string" && value.length > 0 ? value : void 0;
77
173
  }
78
- function isObject(value) {
174
+ function isObject2(value) {
79
175
  return typeof value === "object" && value !== null && !Array.isArray(value);
80
176
  }
81
- function toolUsesOf(record) {
82
- const message = isObject(record.message) ? record.message : void 0;
177
+ function toolUsesOf2(record) {
178
+ const message = isObject2(record.message) ? record.message : void 0;
83
179
  const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
84
180
  const result = [];
85
181
  for (const item of content) {
86
- if (isObject(item) && readString(item.type) === "tool_use") result.push(item);
182
+ if (isObject2(item) && readString2(item.type) === "tool_use") result.push(item);
87
183
  }
88
184
  return result;
89
185
  }
@@ -195,21 +291,21 @@ function claudeTranscriptToImportPayload(records, options) {
195
291
  const engagementTsMs = [];
196
292
  const seenEngagementMessageIds = /* @__PURE__ */ new Set();
197
293
  for (const record of records) {
198
- const ts = readString2(record.timestamp);
294
+ const ts = readString3(record.timestamp);
199
295
  if (ts === void 0) continue;
200
296
  if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
201
297
  if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
202
- if (workingDir === void 0) workingDir = readString2(record.cwd);
203
- if (claudeSessionId === void 0) claudeSessionId = readString2(record.sessionId);
298
+ if (workingDir === void 0) workingDir = readString3(record.cwd);
299
+ if (claudeSessionId === void 0) claudeSessionId = readString3(record.sessionId);
204
300
  if (record.isSidechain !== true) {
205
301
  const tsMs = Date.parse(ts);
206
302
  if (Number.isFinite(tsMs)) {
207
- const recType = readString2(record.type);
303
+ const recType = readString3(record.type);
208
304
  if (recType === "user") {
209
305
  if (isHumanUserMessage(record)) engagementTsMs.push(tsMs);
210
306
  } else if (recType === "assistant") {
211
- const msg = isObject2(record.message) ? record.message : void 0;
212
- const mid = msg !== void 0 ? readString2(msg.id) : void 0;
307
+ const msg = isObject3(record.message) ? record.message : void 0;
308
+ const mid = msg !== void 0 ? readString3(msg.id) : void 0;
213
309
  if (mid === void 0 || !seenEngagementMessageIds.has(mid)) {
214
310
  if (mid !== void 0) seenEngagementMessageIds.add(mid);
215
311
  engagementTsMs.push(tsMs);
@@ -217,11 +313,11 @@ function claudeTranscriptToImportPayload(records, options) {
217
313
  }
218
314
  }
219
315
  }
220
- if (readString2(record.type) !== "assistant") continue;
221
- const message = isObject2(record.message) ? record.message : void 0;
222
- const usage = message !== void 0 && isObject2(message.usage) ? message.usage : void 0;
316
+ if (readString3(record.type) !== "assistant") continue;
317
+ const message = isObject3(record.message) ? record.message : void 0;
318
+ const usage = message !== void 0 && isObject3(message.usage) ? message.usage : void 0;
223
319
  if (usage !== void 0) {
224
- const messageId = message !== void 0 ? readString2(message.id) : void 0;
320
+ const messageId = message !== void 0 ? readString3(message.id) : void 0;
225
321
  const alreadyCounted = messageId !== void 0 && seenMessageIds.has(messageId);
226
322
  if (!alreadyCounted) {
227
323
  if (messageId !== void 0) seenMessageIds.add(messageId);
@@ -230,20 +326,20 @@ function claudeTranscriptToImportPayload(records, options) {
230
326
  cachedInputTokens += readNonNegInt(usage.cache_read_input_tokens);
231
327
  }
232
328
  }
233
- const cwd = readString2(record.cwd) ?? workingDir ?? ".";
329
+ const cwd = readString3(record.cwd) ?? workingDir ?? ".";
234
330
  for (const item of toolUses(record)) {
235
- const name = readString2(item.name);
236
- const input = isObject2(item.input) ? item.input : void 0;
331
+ const name = readString3(item.name);
332
+ const input = isObject3(item.input) ? item.input : void 0;
237
333
  if (input === void 0) continue;
238
334
  if (name === "Bash") {
239
- const command = readString2(input.command);
335
+ const command = readString3(input.command);
240
336
  if (command !== void 0) {
241
337
  derived.push(commandExecutedEvent(ts, placeholderSessionId, command, cwd));
242
338
  }
243
339
  continue;
244
340
  }
245
341
  if (name === "AskUserQuestion") {
246
- const useId = readString2(item.id);
342
+ const useId = readString3(item.id);
247
343
  const answers = useId !== void 0 ? askAnswers.get(useId) : void 0;
248
344
  if (answers !== void 0) {
249
345
  const offeredByQuestion = readOfferedOptions(input);
@@ -261,7 +357,7 @@ function claudeTranscriptToImportPayload(records, options) {
261
357
  continue;
262
358
  }
263
359
  if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
264
- const path2 = readString2(input.file_path) ?? readString2(input.notebook_path);
360
+ const path2 = readString3(input.file_path) ?? readString3(input.notebook_path);
265
361
  if (path2 !== void 0) {
266
362
  const changeType = name === "Write" ? "added" : "modified";
267
363
  relatedFiles.add(path2);
@@ -363,76 +459,40 @@ function decisionRecordedEvent(occurredAt, sessionId, title) {
363
459
  title
364
460
  };
365
461
  }
366
- function readString2(value) {
462
+ function readString3(value) {
367
463
  return typeof value === "string" && value.length > 0 ? value : void 0;
368
464
  }
369
465
  function readNonNegInt(value) {
370
466
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
371
467
  }
372
- function isObject2(value) {
468
+ function isObject3(value) {
373
469
  return typeof value === "object" && value !== null && !Array.isArray(value);
374
470
  }
375
471
  function isHumanUserMessage(record) {
376
- const message = isObject2(record.message) ? record.message : void 0;
472
+ const message = isObject3(record.message) ? record.message : void 0;
377
473
  if (message === void 0) return false;
378
474
  const content = message.content;
379
475
  if (typeof content === "string") return content.length > 0;
380
476
  if (Array.isArray(content)) {
381
477
  return content.some((block) => {
382
- if (!isObject2(block)) return false;
383
- const type = readString2(block.type);
478
+ if (!isObject3(block)) return false;
479
+ const type = readString3(block.type);
384
480
  return type !== void 0 && type !== "tool_result";
385
481
  });
386
482
  }
387
483
  return false;
388
484
  }
389
485
  function toolUses(record) {
390
- const message = isObject2(record.message) ? record.message : void 0;
486
+ const message = isObject3(record.message) ? record.message : void 0;
391
487
  const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
392
488
  const result = [];
393
489
  for (const item of content) {
394
- if (isObject2(item) && readString2(item.type) === "tool_use") {
490
+ if (isObject3(item) && readString3(item.type) === "tool_use") {
395
491
  result.push(item);
396
492
  }
397
493
  }
398
494
  return result;
399
495
  }
400
- function indexAskAnswers(records) {
401
- const byId = /* @__PURE__ */ new Map();
402
- for (const record of records) {
403
- const result = record.toolUseResult;
404
- if (!isObject2(result)) continue;
405
- const answers = result.answers;
406
- if (!isObject2(answers)) continue;
407
- const message = isObject2(record.message) ? record.message : void 0;
408
- const content = message !== void 0 && Array.isArray(message.content) ? message.content : [];
409
- for (const item of content) {
410
- if (isObject2(item) && readString2(item.type) === "tool_result") {
411
- const id = readString2(item.tool_use_id);
412
- if (id !== void 0) byId.set(id, answers);
413
- }
414
- }
415
- }
416
- return byId;
417
- }
418
- function readOfferedOptions(input) {
419
- const byQuestion = /* @__PURE__ */ new Map();
420
- const questions = Array.isArray(input.questions) ? input.questions : [];
421
- for (const q of questions) {
422
- if (!isObject2(q)) continue;
423
- const text = readString2(q.question);
424
- if (text === void 0) continue;
425
- const labels = /* @__PURE__ */ new Set();
426
- const options = Array.isArray(q.options) ? q.options : [];
427
- for (const o of options) {
428
- if (!isObject2(o)) continue;
429
- const label = readString2(o.label);
430
- if (label !== void 0) labels.add(label.trim());
431
- }
432
- byQuestion.set(text, labels);
433
- }
434
- return byQuestion;
435
- }
436
496
 
437
497
  // src/adapters/codex/rollout-importer.ts
438
498
  var CODEX_IMPORT_SOURCE = "codex-import";
@@ -450,31 +510,31 @@ function codexRolloutToImportPayload(records, options) {
450
510
  const completions = [];
451
511
  const completedTurnIds = /* @__PURE__ */ new Set();
452
512
  for (const record of records) {
453
- const ts = readString3(record.timestamp);
513
+ const ts = readString4(record.timestamp);
454
514
  if (ts === void 0) continue;
455
515
  if (minTs === void 0 || Date.parse(ts) < Date.parse(minTs)) minTs = ts;
456
516
  if (maxTs === void 0 || Date.parse(ts) > Date.parse(maxTs)) maxTs = ts;
457
- const payload2 = isObject3(record.payload) ? record.payload : void 0;
517
+ const payload2 = isObject4(record.payload) ? record.payload : void 0;
458
518
  if (payload2 === void 0) continue;
459
- if (readString3(record.type) === "session_meta") {
460
- if (workingDir === void 0) workingDir = readString3(payload2.cwd);
461
- if (codexSessionId === void 0) codexSessionId = readString3(payload2.id);
519
+ if (readString4(record.type) === "session_meta") {
520
+ if (workingDir === void 0) workingDir = readString4(payload2.cwd);
521
+ if (codexSessionId === void 0) codexSessionId = readString4(payload2.id);
462
522
  continue;
463
523
  }
464
- if (readString3(record.type) === "event_msg" && readString3(payload2.type) === "token_count") {
465
- const info = isObject3(payload2.info) ? payload2.info : void 0;
466
- const totals = info !== void 0 && isObject3(info.total_token_usage) ? info.total_token_usage : void 0;
524
+ if (readString4(record.type) === "event_msg" && readString4(payload2.type) === "token_count") {
525
+ const info = isObject4(payload2.info) ? payload2.info : void 0;
526
+ const totals = info !== void 0 && isObject4(info.total_token_usage) ? info.total_token_usage : void 0;
467
527
  if (totals !== void 0) lastTokenTotals = totals;
468
528
  continue;
469
529
  }
470
- if (readString3(record.type) === "event_msg") {
471
- const pt = readString3(payload2.type);
530
+ if (readString4(record.type) === "event_msg") {
531
+ const pt = readString4(payload2.type);
472
532
  if (pt === "user_message" || pt === "agent_message" || pt === "task_started" || pt === "task_complete") {
473
533
  const tsMs = Date.parse(ts);
474
534
  if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);
475
535
  }
476
536
  if (pt === "task_complete") {
477
- const turnId = readString3(payload2.turn_id);
537
+ const turnId = readString4(payload2.turn_id);
478
538
  if (turnId === void 0 || !completedTurnIds.has(turnId)) {
479
539
  if (turnId !== void 0) completedTurnIds.add(turnId);
480
540
  completions.push({
@@ -485,9 +545,9 @@ function codexRolloutToImportPayload(records, options) {
485
545
  }
486
546
  continue;
487
547
  }
488
- if (readString3(record.type) !== "response_item") continue;
489
- if (readString3(payload2.type) !== "function_call") continue;
490
- if (readString3(payload2.name) !== "exec_command") continue;
548
+ if (readString4(record.type) !== "response_item") continue;
549
+ if (readString4(payload2.type) !== "function_call") continue;
550
+ if (readString4(payload2.name) !== "exec_command") continue;
491
551
  const command = readExecCommand(payload2.arguments);
492
552
  if (command === void 0) continue;
493
553
  const cwd = command.workdir ?? workingDir ?? ".";
@@ -598,17 +658,17 @@ function commandExecutedEvent2(occurredAt, sessionId, command, cwd, outcome) {
598
658
  duration_ms: outcome.durationMs
599
659
  };
600
660
  }
601
- function readString3(value) {
661
+ function readString4(value) {
602
662
  return typeof value === "string" && value.length > 0 ? value : void 0;
603
663
  }
604
664
  function readNonNegInt2(value) {
605
665
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
606
666
  }
607
- function isObject3(value) {
667
+ function isObject4(value) {
608
668
  return typeof value === "object" && value !== null && !Array.isArray(value);
609
669
  }
610
670
  function readExecCommand(value) {
611
- const raw = readString3(value);
671
+ const raw = readString4(value);
612
672
  if (raw === void 0) return void 0;
613
673
  let parsed;
614
674
  try {
@@ -616,19 +676,19 @@ function readExecCommand(value) {
616
676
  } catch {
617
677
  return void 0;
618
678
  }
619
- if (!isObject3(parsed)) return void 0;
620
- const cmd = readString3(parsed.cmd);
679
+ if (!isObject4(parsed)) return void 0;
680
+ const cmd = readString4(parsed.cmd);
621
681
  if (cmd === void 0) return void 0;
622
- return { cmd, workdir: readString3(parsed.workdir) };
682
+ return { cmd, workdir: readString4(parsed.workdir) };
623
683
  }
624
684
  function readCallId(value, outputs) {
625
- const callId = readString3(value);
685
+ const callId = readString4(value);
626
686
  return callId !== void 0 ? outputs.get(callId) : void 0;
627
687
  }
628
688
  function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
629
689
  const endMs = Date.parse(endTs);
630
690
  if (!Number.isFinite(endMs)) return void 0;
631
- const turnId = readString3(payload.turn_id);
691
+ const turnId = readString4(payload.turn_id);
632
692
  const indexedStart = turnId !== void 0 ? startMsByTurnId.get(turnId) : void 0;
633
693
  const durationMs = readNonNegInt2(payload.duration_ms);
634
694
  const startMs = indexedStart !== void 0 ? indexedStart : durationMs > 0 ? endMs - durationMs : void 0;
@@ -638,11 +698,11 @@ function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
638
698
  function indexTaskStarts(records) {
639
699
  const byTurnId = /* @__PURE__ */ new Map();
640
700
  for (const record of records) {
641
- if (readString3(record.type) !== "event_msg") continue;
642
- const payload = isObject3(record.payload) ? record.payload : void 0;
643
- if (payload === void 0 || readString3(payload.type) !== "task_started") continue;
644
- const turnId = readString3(payload.turn_id);
645
- const startMs = Date.parse(readString3(record.timestamp) ?? "");
701
+ if (readString4(record.type) !== "event_msg") continue;
702
+ const payload = isObject4(record.payload) ? record.payload : void 0;
703
+ if (payload === void 0 || readString4(payload.type) !== "task_started") continue;
704
+ const turnId = readString4(payload.turn_id);
705
+ const startMs = Date.parse(readString4(record.timestamp) ?? "");
646
706
  if (turnId !== void 0 && Number.isFinite(startMs) && !byTurnId.has(turnId)) {
647
707
  byTurnId.set(turnId, startMs);
648
708
  }
@@ -664,12 +724,12 @@ function parseWallTimeMs(output) {
664
724
  function indexOutputs(records) {
665
725
  const byId = /* @__PURE__ */ new Map();
666
726
  for (const record of records) {
667
- if (readString3(record.type) !== "response_item") continue;
668
- const payload = isObject3(record.payload) ? record.payload : void 0;
727
+ if (readString4(record.type) !== "response_item") continue;
728
+ const payload = isObject4(record.payload) ? record.payload : void 0;
669
729
  if (payload === void 0) continue;
670
- if (readString3(payload.type) !== "function_call_output") continue;
671
- const callId = readString3(payload.call_id);
672
- const output = readString3(payload.output);
730
+ if (readString4(payload.type) !== "function_call_output") continue;
731
+ const callId = readString4(payload.call_id);
732
+ const output = readString4(payload.output);
673
733
  if (callId !== void 0 && output !== void 0) byId.set(callId, output);
674
734
  }
675
735
  return byId;
@@ -5120,12 +5180,11 @@ function stalenessBanner(staleness) {
5120
5180
  if (staleness === null) return [];
5121
5181
  if ((staleness.unverifiableSessions ?? 0) > 0) {
5122
5182
  return [
5123
- `> \u26A0\uFE0F **\u6700\u65B0\u3067\u306F\u306A\u3044\u53EF\u80FD\u6027** \u2014 \u5909\u5316\u3057\u305F\u304C\u5B89\u5168\u306B\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\u7740\u624B\u524D\u306B \`basou verify\` / \`basou refresh --force\`(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5183
+ `> \u26A0\uFE0F **\u518D\u53D6\u308A\u8FBC\u307F\u304C\u5FC5\u8981** \u2014 native \u30ED\u30B0\u304C\u5909\u5316\u3057\u305F\u304C\u901A\u5E38\u306E refresh \u3067\u306F\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh --force\` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5124
5184
  ];
5125
5185
  }
5126
- if (staleness.newSessions > 0 || staleness.updatedSessions > 0) {
5127
- const parts = [];
5128
- if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
5186
+ if (staleness.newSessions > 0) {
5187
+ const parts = [`\u65B0\u898F ${staleness.newSessions} \u4EF6`];
5129
5188
  if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5130
5189
  return [
5131
5190
  `> \u26A0\uFE0F **\u53E4\u3044\u3067\u3059\uFF08\u672A\u53D6\u308A\u8FBC\u307F ${parts.join("\u30FB")}\uFF09** \u2014 \u7740\u624B\u524D\u306B\u5FC5\u305A \`basou refresh\` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
@@ -5136,19 +5195,30 @@ function stalenessBanner(staleness) {
5136
5195
  function freshnessVerdict(summary, staleness, now) {
5137
5196
  if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {
5138
5197
  return [
5139
- `\u26A0\uFE0F \u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u5909\u5316\u3057\u305F\u304C\u5B89\u5168\u306B\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059(\u30CF\u30C3\u30B7\u30E5\u30C1\u30A7\u30FC\u30F3\u7834\u640D\u30FB\u975E\u8FFD\u8A18\u5909\u66F4\u306A\u3069)\u3002`,
5140
- "`basou verify` \u3067\u78BA\u8A8D\u3057\u3001`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5198
+ `\u26A0\uFE0F native \u30ED\u30B0\u304C\u5909\u5316\u3057\u307E\u3057\u305F\u304C\u3001\u901A\u5E38\u306E \`basou refresh\` \u3067\u306F\u5B89\u5168\u306B\u518D\u53D6\u308A\u8FBC\u307F\u3067\u304D\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059(\u975E\u8FFD\u8A18\u5909\u66F4\u30FB\u524D\u30C1\u30A7\u30FC\u30F3\u4E0D\u6574\u5408\u306A\u3069)\u3002`,
5199
+ "`basou refresh --force` \u3067\u518D\u53D6\u308A\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002(`basou verify` \u306F\u5225\u7269=\u53D6\u308A\u8FBC\u307F\u6E08\u307F\u30C7\u30FC\u30BF\u306E\u6539\u7AC4/\u7834\u640D\u691C\u67FB\u3067\u3001\u30D8\u30C3\u30C0\u306E suspect \u3068\u306F\u5225\u8EF8\u3067\u3059\u3002verify \u304C clean \u3067\u3082\u672A\u53D6\u308A\u8FBC\u307F\u306F\u6B8B\u308A\u5F97\u307E\u3059\u3002)"
5141
5200
  ];
5142
5201
  }
5143
- if (staleness !== null && (staleness.newSessions > 0 || staleness.updatedSessions > 0)) {
5144
- const parts = [];
5145
- if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
5202
+ if (staleness !== null && staleness.newSessions > 0) {
5203
+ const parts = [`\u65B0\u898F ${staleness.newSessions} \u4EF6`];
5146
5204
  if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5147
5205
  return [
5148
5206
  `\u26A0\uFE0F \u53E4\u3044\u3067\u3059\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
5149
5207
  "\u7740\u624B\u524D\u306B\u5FC5\u305A `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5150
5208
  ];
5151
5209
  }
5210
+ if (staleness !== null && staleness.updatedSessions > 0) {
5211
+ const lines2 = [
5212
+ `\u26A0\uFE0F \u66F4\u65B0\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.updatedSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\`basou refresh\` \u3067\u53D6\u308A\u8FBC\u3081\u307E\u3059\u3002`,
5213
+ "(\u9032\u884C\u4E2D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u3042\u308B\u5834\u5408\u3001\u305D\u308C\u81EA\u8EAB\u306F\u53D6\u308A\u8FBC\u307F\u5F8C\u3082\u5897\u3048\u7D9A\u3051\u308B\u305F\u3081\u6B8B\u308A\u307E\u3059\uFF1D\u6B63\u5E38\u3067\u3059\u3002)"
5214
+ ];
5215
+ if (summary.suspects.length > 0) {
5216
+ lines2.push(
5217
+ `\u307E\u305F\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${summary.suspects.length} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`
5218
+ );
5219
+ }
5220
+ return lines2;
5221
+ }
5152
5222
  if (summary.freshness.newestStartedAt === null) {
5153
5223
  return [
5154
5224
  "\u2139\uFE0F \u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
@@ -7529,7 +7599,7 @@ export {
7529
7599
  CLAUDE_IMPORT_SOURCE,
7530
7600
  CODEX_IMPORT_SOURCE,
7531
7601
  ChildProcessRunner,
7532
- DEFAULT_STOP_HOOK_MIN_ACTIONS,
7602
+ DEFAULT_STOP_HOOK_MIN_EDITS,
7533
7603
  DecisionIdSchema,
7534
7604
  EventIdSchema,
7535
7605
  EventSchema,