@basou/cli 0.41.0 → 0.42.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
@@ -77,6 +77,18 @@ function printReplayWarning(warning, sessionId) {
77
77
  `Warning: skipped invalid event at line ${warning.line} in ${short}/events.jsonl`
78
78
  );
79
79
  break;
80
+ case "retired_zero_duration":
81
+ console.error(
82
+ `Warning: kept an event with duration_ms: 0 at line ${warning.line} in ${short}/events.jsonl \u2014 no writer at that schema_version emits one; reading it as unobserved`
83
+ );
84
+ break;
85
+ default: {
86
+ const unhandled = warning;
87
+ console.error(
88
+ `Warning: unhandled replay warning in ${short}/events.jsonl: ${String(unhandled.kind)}`
89
+ );
90
+ break;
91
+ }
80
92
  }
81
93
  }
82
94
  function printSessionSkip(sid, reason) {
@@ -119,6 +131,7 @@ import {
119
131
  appendChainedEventLocked,
120
132
  assertBasouRootSafe,
121
133
  basouPaths,
134
+ EVENT_SCHEMA_VERSION,
122
135
  enumerateApprovals,
123
136
  findErrorCode,
124
137
  isLazyExpired,
@@ -352,7 +365,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
352
365
  if (decision === "approve") {
353
366
  const note = options.note ?? null;
354
367
  await appendChainedEventLocked(paths, approval.session_id, {
355
- schema_version: "0.1.0",
368
+ schema_version: EVENT_SCHEMA_VERSION,
356
369
  id: eventId,
357
370
  session_id: approval.session_id,
358
371
  occurred_at: occurredAt,
@@ -365,7 +378,7 @@ async function doRunApprovalResolve(idInput, options, ctx, decision) {
365
378
  } else {
366
379
  const reason = options.reason;
367
380
  await appendChainedEventLocked(paths, approval.session_id, {
368
- schema_version: "0.1.0",
381
+ schema_version: EVENT_SCHEMA_VERSION,
369
382
  id: eventId,
370
383
  session_id: approval.session_id,
371
384
  occurred_at: occurredAt,
@@ -851,6 +864,7 @@ import {
851
864
  basouPaths as basouPaths3,
852
865
  classifyFilesBySourceRoot,
853
866
  createAdHocSessionWithEvent,
867
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION2,
854
868
  findErrorCode as findErrorCode2,
855
869
  isValidPrefixedId,
856
870
  loadSessionEntries,
@@ -1400,7 +1414,7 @@ async function decisionExists(paths, decisionId) {
1400
1414
  }
1401
1415
  function buildDecisionVoidedEvent(input) {
1402
1416
  return {
1403
- schema_version: "0.1.0",
1417
+ schema_version: EVENT_SCHEMA_VERSION2,
1404
1418
  id: input.eventId,
1405
1419
  session_id: input.sessionId,
1406
1420
  occurred_at: input.occurredAt,
@@ -1658,7 +1672,7 @@ function pickRichFields(options) {
1658
1672
  }
1659
1673
  function buildDecisionEvent(input) {
1660
1674
  return {
1661
- schema_version: "0.1.0",
1675
+ schema_version: EVENT_SCHEMA_VERSION2,
1662
1676
  id: input.eventId,
1663
1677
  session_id: input.sessionId,
1664
1678
  occurred_at: input.occurredAt,
@@ -1855,6 +1869,7 @@ import {
1855
1869
  basouPaths as basouPaths5,
1856
1870
  ChildProcessRunner,
1857
1871
  appendChainedEvent as coreAppendChainedEvent,
1872
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
1858
1873
  finalizeSessionYaml,
1859
1874
  getSnapshot,
1860
1875
  overwriteYamlFile,
@@ -1865,6 +1880,7 @@ import {
1865
1880
  resolveRepositoryRoot as resolveRepositoryRoot4,
1866
1881
  SessionSchema,
1867
1882
  sanitizeWorkingDirectory,
1883
+ writeObservedDuration,
1868
1884
  writeYamlFile
1869
1885
  } from "@basou/core";
1870
1886
  function registerExecCommand(program2) {
@@ -1905,7 +1921,7 @@ async function runExec(command, args, options, ctx = {}) {
1905
1921
  });
1906
1922
  await writeYamlFile(sessionYamlPath, session);
1907
1923
  await appendEvent(sessionDir, {
1908
- schema_version: "0.1.0",
1924
+ schema_version: EVENT_SCHEMA_VERSION3,
1909
1925
  type: "session_started",
1910
1926
  id: prefixedUlid3("evt"),
1911
1927
  session_id: sessionId,
@@ -1917,7 +1933,7 @@ async function runExec(command, args, options, ctx = {}) {
1917
1933
  }
1918
1934
  const runningAt = now().toISOString();
1919
1935
  await appendEvent(sessionDir, {
1920
- schema_version: "0.1.0",
1936
+ schema_version: EVENT_SCHEMA_VERSION3,
1921
1937
  type: "session_status_changed",
1922
1938
  id: prefixedUlid3("evt"),
1923
1939
  session_id: sessionId,
@@ -1986,7 +2002,7 @@ async function runExec(command, args, options, ctx = {}) {
1986
2002
  }
1987
2003
  const endedAt = now().toISOString();
1988
2004
  await appendEvent(sessionDir, {
1989
- schema_version: "0.1.0",
2005
+ schema_version: EVENT_SCHEMA_VERSION3,
1990
2006
  type: "command_executed",
1991
2007
  id: prefixedUlid3("evt"),
1992
2008
  session_id: sessionId,
@@ -1998,14 +2014,21 @@ async function runExec(command, args, options, ctx = {}) {
1998
2014
  exit_code: result.exit_code,
1999
2015
  ...result.signal !== null ? { signal: result.signal } : {},
2000
2016
  ...signalReceived !== null ? { received_signal: signalReceived } : {},
2001
- duration_ms: result.duration_ms
2017
+ // Reached only when the child actually ran, and the runner times it on a
2018
+ // monotonic sub-millisecond clock, so the cheapest spawn on this host
2019
+ // (`/usr/bin/true`, median 0.83ms) still rounds to 1ms. A non-positive
2020
+ // value therefore means the measurement is unusable, not that the command
2021
+ // was instant, and is recorded as unobserved. A spawn that never ran
2022
+ // (ENOENT) does not reach this line at all: the runner rejects and
2023
+ // `finalizeSessionAsFailed` writes its own null.
2024
+ duration_ms: writeObservedDuration(result.duration_ms)
2002
2025
  });
2003
2026
  if (options.snapshot !== false) {
2004
2027
  await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent);
2005
2028
  }
2006
2029
  const finalStatus = decideFinalStatus(result, signalReceived);
2007
2030
  await appendEvent(sessionDir, {
2008
- schema_version: "0.1.0",
2031
+ schema_version: EVENT_SCHEMA_VERSION3,
2009
2032
  type: "session_status_changed",
2010
2033
  id: prefixedUlid3("evt"),
2011
2034
  session_id: sessionId,
@@ -2015,7 +2038,7 @@ async function runExec(command, args, options, ctx = {}) {
2015
2038
  to: finalStatus
2016
2039
  });
2017
2040
  await appendEvent(sessionDir, {
2018
- schema_version: "0.1.0",
2041
+ schema_version: EVENT_SCHEMA_VERSION3,
2019
2042
  type: "session_ended",
2020
2043
  id: prefixedUlid3("evt"),
2021
2044
  session_id: sessionId,
@@ -2062,7 +2085,7 @@ async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, append
2062
2085
  return;
2063
2086
  }
2064
2087
  await appendEvent(sessionDir, {
2065
- schema_version: "0.1.0",
2088
+ schema_version: EVENT_SCHEMA_VERSION3,
2066
2089
  type: "git_snapshot",
2067
2090
  id: prefixedUlid3("evt"),
2068
2091
  session_id: sessionId,
@@ -2119,7 +2142,7 @@ async function mutateSessionYaml(filePath, mutator) {
2119
2142
  }
2120
2143
  async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent, ctx) {
2121
2144
  await appendEvent(sessionDir, {
2122
- schema_version: "0.1.0",
2145
+ schema_version: EVENT_SCHEMA_VERSION3,
2123
2146
  type: "command_executed",
2124
2147
  id: prefixedUlid3("evt"),
2125
2148
  session_id: sessionId,
@@ -2131,10 +2154,12 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
2131
2154
  exit_code: null,
2132
2155
  signal: null,
2133
2156
  ...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
2134
- duration_ms: 0
2157
+ // Not observed: this event stands in for a run that ended before a duration
2158
+ // could be measured, so 0 would claim a measured zero.
2159
+ duration_ms: null
2135
2160
  });
2136
2161
  await appendEvent(sessionDir, {
2137
- schema_version: "0.1.0",
2162
+ schema_version: EVENT_SCHEMA_VERSION3,
2138
2163
  type: "session_status_changed",
2139
2164
  id: prefixedUlid3("evt"),
2140
2165
  session_id: sessionId,
@@ -2144,7 +2169,7 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
2144
2169
  to: "failed"
2145
2170
  });
2146
2171
  await appendEvent(sessionDir, {
2147
- schema_version: "0.1.0",
2172
+ schema_version: EVENT_SCHEMA_VERSION3,
2148
2173
  type: "session_ended",
2149
2174
  id: prefixedUlid3("evt"),
2150
2175
  session_id: sessionId,
@@ -2609,6 +2634,7 @@ import {
2609
2634
  readSessionYaml as readSessionYaml2,
2610
2635
  reimportPreservingId,
2611
2636
  resolveRepositoryRoot as resolveRepositoryRoot6,
2637
+ SESSION_IMPORT_SCHEMA_VERSION,
2612
2638
  SessionImportPayloadSchema
2613
2639
  } from "@basou/core";
2614
2640
  var SES_PREFIX2 = "ses_";
@@ -2796,7 +2822,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2796
2822
  if (!parsed.success) {
2797
2823
  throw new Error("Invalid import payload", { cause: parsed.error });
2798
2824
  }
2799
- if (parsed.data.schema_version !== "0.1.0") {
2825
+ if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION) {
2800
2826
  throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
2801
2827
  }
2802
2828
  return parsed.data;
@@ -4223,6 +4249,7 @@ import {
4223
4249
  assertBasouRootSafe as assertBasouRootSafe8,
4224
4250
  basouPaths as basouPaths9,
4225
4251
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
4252
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
4226
4253
  findErrorCode as findErrorCode7,
4227
4254
  readManifest as readManifest5,
4228
4255
  resolveSessionId as resolveSessionId2
@@ -4327,7 +4354,7 @@ async function doRunNote(body, options, ctx) {
4327
4354
  }
4328
4355
  function buildNoteEvent(input) {
4329
4356
  return {
4330
- schema_version: "0.1.0",
4357
+ schema_version: EVENT_SCHEMA_VERSION4,
4331
4358
  id: input.eventId,
4332
4359
  session_id: input.sessionId,
4333
4360
  occurred_at: input.occurredAt,
@@ -8844,6 +8871,7 @@ import {
8844
8871
  claudeCodeAdapterMetadata,
8845
8872
  codexAdapterMetadata,
8846
8873
  appendChainedEvent as coreAppendChainedEvent2,
8874
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION5,
8847
8875
  finalizeSessionYaml as finalizeSessionYaml2,
8848
8876
  findBasouSessionStartHook as findBasouSessionStartHook2,
8849
8877
  getDiff,
@@ -8858,6 +8886,7 @@ import {
8858
8886
  SessionSchema as SessionSchema2,
8859
8887
  sanitizeRelatedFiles,
8860
8888
  sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
8889
+ writeObservedDuration as writeObservedDuration2,
8861
8890
  writeYamlFile as writeYamlFile2
8862
8891
  } from "@basou/core";
8863
8892
  function registerRunCommand(program2, ctx = {}) {
@@ -8930,7 +8959,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
8930
8959
  });
8931
8960
  await writeYamlFile2(sessionYamlPath, session);
8932
8961
  await appendEvent(sessionDir, {
8933
- schema_version: "0.1.0",
8962
+ schema_version: EVENT_SCHEMA_VERSION5,
8934
8963
  type: "session_started",
8935
8964
  id: prefixedUlid4("evt"),
8936
8965
  session_id: sessionId,
@@ -8943,7 +8972,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
8943
8972
  }
8944
8973
  const runningAt = now().toISOString();
8945
8974
  await appendEvent(sessionDir, {
8946
- schema_version: "0.1.0",
8975
+ schema_version: EVENT_SCHEMA_VERSION5,
8947
8976
  type: "session_status_changed",
8948
8977
  id: prefixedUlid4("evt"),
8949
8978
  session_id: sessionId,
@@ -9016,7 +9045,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9016
9045
  }
9017
9046
  const endedAt = now().toISOString();
9018
9047
  await appendEvent(sessionDir, {
9019
- schema_version: "0.1.0",
9048
+ schema_version: EVENT_SCHEMA_VERSION5,
9020
9049
  type: "command_executed",
9021
9050
  id: prefixedUlid4("evt"),
9022
9051
  session_id: sessionId,
@@ -9028,7 +9057,14 @@ async function runTrackedTool(args, options, ctx, adapter) {
9028
9057
  exit_code: result.exit_code,
9029
9058
  ...result.signal !== null ? { signal: result.signal } : {},
9030
9059
  ...signalReceived !== null ? { received_signal: signalReceived } : {},
9031
- duration_ms: result.duration_ms
9060
+ // Reached only when the child actually ran, and the runner times it on a
9061
+ // monotonic sub-millisecond clock, so the cheapest spawn on this host
9062
+ // (`/usr/bin/true`, median 0.83ms) still rounds to 1ms. A non-positive
9063
+ // value therefore means the measurement is unusable, not that the command
9064
+ // was instant, and is recorded as unobserved. A spawn that never ran
9065
+ // (ENOENT) does not reach this line at all: the runner rejects and
9066
+ // `finalizeSessionAsFailed` writes its own null.
9067
+ duration_ms: writeObservedDuration2(result.duration_ms)
9032
9068
  });
9033
9069
  let postSnapshot = null;
9034
9070
  if (options.snapshot !== false) {
@@ -9054,7 +9090,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9054
9090
  }).sanitized;
9055
9091
  const finalStatus = decideFinalStatus2(result, signalReceived);
9056
9092
  await appendEvent(sessionDir, {
9057
- schema_version: "0.1.0",
9093
+ schema_version: EVENT_SCHEMA_VERSION5,
9058
9094
  type: "session_status_changed",
9059
9095
  id: prefixedUlid4("evt"),
9060
9096
  session_id: sessionId,
@@ -9064,7 +9100,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9064
9100
  to: finalStatus
9065
9101
  });
9066
9102
  await appendEvent(sessionDir, {
9067
- schema_version: "0.1.0",
9103
+ schema_version: EVENT_SCHEMA_VERSION5,
9068
9104
  type: "session_ended",
9069
9105
  id: prefixedUlid4("evt"),
9070
9106
  session_id: sessionId,
@@ -9110,7 +9146,7 @@ async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appen
9110
9146
  return null;
9111
9147
  }
9112
9148
  await appendEvent(sessionDir, {
9113
- schema_version: "0.1.0",
9149
+ schema_version: EVENT_SCHEMA_VERSION5,
9114
9150
  type: "git_snapshot",
9115
9151
  id: prefixedUlid4("evt"),
9116
9152
  session_id: sessionId,
@@ -9130,7 +9166,7 @@ async function tryAppendFileChangedEvents(sessionDir, sessionId, repoRoot, baseR
9130
9166
  }
9131
9167
  for (const change of diff.changed_files) {
9132
9168
  await appendEvent(sessionDir, {
9133
- schema_version: "0.1.0",
9169
+ schema_version: EVENT_SCHEMA_VERSION5,
9134
9170
  type: "file_changed",
9135
9171
  id: prefixedUlid4("evt"),
9136
9172
  session_id: sessionId,
@@ -9214,7 +9250,7 @@ async function mutateSessionYaml2(filePath, mutator) {
9214
9250
  }
9215
9251
  async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, ctx) {
9216
9252
  await appendEvent(sessionDir, {
9217
- schema_version: "0.1.0",
9253
+ schema_version: EVENT_SCHEMA_VERSION5,
9218
9254
  type: "command_executed",
9219
9255
  id: prefixedUlid4("evt"),
9220
9256
  session_id: sessionId,
@@ -9226,10 +9262,12 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
9226
9262
  exit_code: null,
9227
9263
  signal: null,
9228
9264
  ...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
9229
- duration_ms: 0
9265
+ // Not observed: this event stands in for a run that ended before a duration
9266
+ // could be measured, so 0 would claim a measured zero.
9267
+ duration_ms: null
9230
9268
  });
9231
9269
  await appendEvent(sessionDir, {
9232
- schema_version: "0.1.0",
9270
+ schema_version: EVENT_SCHEMA_VERSION5,
9233
9271
  type: "session_status_changed",
9234
9272
  id: prefixedUlid4("evt"),
9235
9273
  session_id: sessionId,
@@ -9239,7 +9277,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
9239
9277
  to: "failed"
9240
9278
  });
9241
9279
  await appendEvent(sessionDir, {
9242
- schema_version: "0.1.0",
9280
+ schema_version: EVENT_SCHEMA_VERSION5,
9243
9281
  type: "session_ended",
9244
9282
  id: prefixedUlid4("evt"),
9245
9283
  session_id: sessionId,
@@ -9289,16 +9327,19 @@ import {
9289
9327
  appendEventToExistingSession as appendEventToExistingSession3,
9290
9328
  assertBasouRootSafe as assertBasouRootSafe13,
9291
9329
  basouPaths as basouPaths16,
9330
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
9292
9331
  enumerateSessionDirs as enumerateSessionDirs2,
9293
9332
  findErrorCode as findErrorCode12,
9294
9333
  importSessionFromJson as importSessionFromJson2,
9295
9334
  loadSessionEntries as loadSessionEntries2,
9296
9335
  readAllEvents,
9297
9336
  readManifest as readManifest10,
9337
+ readObservedDuration,
9298
9338
  readYamlFile as readYamlFile7,
9299
9339
  rechainSessionInPlace,
9300
9340
  resolveSessionId as resolveSessionId3,
9301
9341
  resolveTaskId,
9342
+ SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
9302
9343
  SessionImportPayloadSchema as SessionImportPayloadSchema2,
9303
9344
  SessionSchema as SessionSchema3,
9304
9345
  SessionStatusSchema,
@@ -9420,15 +9461,19 @@ async function doRunSessionShow(idInput, options, ctx) {
9420
9461
  }
9421
9462
  throw new Error("Failed to read session", { cause: error });
9422
9463
  }
9464
+ let eventsLostLines = 0;
9423
9465
  const events = await readAllEvents(sessionDir, {
9424
- onWarning: (w) => printReplayWarning(w, sessionId)
9466
+ onWarning: (w) => {
9467
+ if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
9468
+ printReplayWarning(w, sessionId);
9469
+ }
9425
9470
  });
9426
9471
  if (options.json === true) {
9427
9472
  console.log(JSON.stringify({ session: session.session, events }, null, 2));
9428
9473
  return;
9429
9474
  }
9430
9475
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
9431
- printSessionShowText(session, events, options, repositoryRoot, now);
9476
+ printSessionShowText(session, events, options, repositoryRoot, now, eventsLostLines);
9432
9477
  }
9433
9478
  function suspectLabel(reason) {
9434
9479
  if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
@@ -9474,7 +9519,7 @@ function printSessionListText(records) {
9474
9519
  );
9475
9520
  }
9476
9521
  }
9477
- function printSessionShowText(session, events, options, repositoryRoot, now) {
9522
+ function printSessionShowText(session, events, options, repositoryRoot, now, eventsLostLines) {
9478
9523
  const s = session.session;
9479
9524
  console.log(`Session: ${s.id} (status: ${s.status})`);
9480
9525
  console.log(`Source: ${s.source.kind} (v${s.source.version})`);
@@ -9500,7 +9545,7 @@ function printSessionShowText(session, events, options, repositoryRoot, now) {
9500
9545
  console.log(` ${pad2(`${type}:`, 24)} ${n}`);
9501
9546
  }
9502
9547
  console.log("");
9503
- console.log(`Work: ${formatSessionWork(session, events, now)}`);
9548
+ console.log(`Work: ${formatSessionWork(session, events, now, eventsLostLines)}`);
9504
9549
  if (events.length === 0) return;
9505
9550
  const last = options.last ?? 5;
9506
9551
  const showAll = options.events === true && options.last === void 0;
@@ -9512,8 +9557,15 @@ function printSessionShowText(session, events, options, repositoryRoot, now) {
9512
9557
  console.log(` ${formatEventLine(ev)}`);
9513
9558
  }
9514
9559
  }
9515
- function formatSessionWork(session, events, now) {
9516
- const w = sessionWorkStatsFromEvents(session.session.id, session.session, events, now);
9560
+ function formatSessionWork(session, events, now, eventsLostLines) {
9561
+ const w = sessionWorkStatsFromEvents(
9562
+ session.session.id,
9563
+ session.session,
9564
+ events,
9565
+ now,
9566
+ false,
9567
+ eventsLostLines
9568
+ );
9517
9569
  const parts = [];
9518
9570
  if (w.tokens.output > 0) parts.push(`${w.tokens.output.toLocaleString("en-US")} output tokens`);
9519
9571
  parts.push(`${w.commandCount} cmd / ${w.fileChangedCount} files / ${w.decisionCount} dec`);
@@ -9524,7 +9576,7 @@ function formatSessionWork(session, events, now) {
9524
9576
  }
9525
9577
  parts.push(`span ${formatDurationMs(w.sessionSpanMs)}${w.open ? " (open)" : ""}`);
9526
9578
  parts.push(
9527
- w.availability.commandTime ? `command ${formatDurationMs(w.commandTimeMs)}` : "command n/a (import)"
9579
+ w.availability.commandTime ? `command ${formatDurationMs(w.commandTimeMs)}` : "command n/a (no duration observed, or the stream was not read in full)"
9528
9580
  );
9529
9581
  return parts.join(", ");
9530
9582
  }
@@ -9563,7 +9615,9 @@ function eventVariantSummary(ev) {
9563
9615
  const argsPart = ev.args.length > 0 ? ` ${ev.args.join(" ")}` : "";
9564
9616
  const executorPart = ev.command ?? "(executor unrecorded)";
9565
9617
  const exitPart = ev.exit_code === null ? "exit=unknown" : `exit=${ev.exit_code}`;
9566
- return `${executorPart}${argsPart} (${exitPart}, ${ev.duration_ms}ms)`;
9618
+ const observedDuration = readObservedDuration(ev);
9619
+ const durationPart = observedDuration === null ? "duration=unknown" : `${observedDuration}ms`;
9620
+ return `${executorPart}${argsPart} (${exitPart}, ${durationPart})`;
9567
9621
  }
9568
9622
  case "git_snapshot":
9569
9623
  return `branch=${ev.branch} dirty=${ev.dirty}`;
@@ -9713,7 +9767,7 @@ async function doRunSessionImport(options, ctx) {
9713
9767
  if (!parsed.success) {
9714
9768
  throw new Error("Invalid import payload", { cause: parsed.error });
9715
9769
  }
9716
- if (parsed.data.schema_version !== "0.1.0") {
9770
+ if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION2) {
9717
9771
  throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
9718
9772
  }
9719
9773
  const importOptions2 = { dryRun: options.dryRun === true };
@@ -9834,7 +9888,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
9834
9888
  paths,
9835
9889
  sessionId: sesId,
9836
9890
  eventBuilder: (eventId) => ({
9837
- schema_version: "0.1.0",
9891
+ schema_version: EVENT_SCHEMA_VERSION6,
9838
9892
  id: eventId,
9839
9893
  session_id: sesId,
9840
9894
  occurred_at: occurredAt,
@@ -10028,7 +10082,7 @@ function printStatsText(result, bySource, byDay) {
10028
10082
  console.log(
10029
10083
  ` Span: ${formatDurationMs(t.sessionSpanMs)} (total elapsed${openPart})`
10030
10084
  );
10031
- const cmdCaveat = t.commandTimeReliable ? "" : "; some sessions (e.g. claude-code-import) report 0 shell time";
10085
+ const cmdCaveat = t.commandTimeReliable ? "; at least, only durations the sources reported are counted" : "; some sessions ran commands with no duration observed, or could not be read in full, so this is a floor";
10032
10086
  console.log(
10033
10087
  ` Command: ${formatDurationMs(t.commandTimeMs)} (real shell execution${cmdCaveat})`
10034
10088
  );
@@ -10051,11 +10105,15 @@ function printStatsText(result, bySource, byDay) {
10051
10105
  }
10052
10106
  }
10053
10107
  function describeSource(s) {
10054
- const cmd = s.commandTimeReliable ? formatDurationMs(s.commandTimeMs) : "n/a";
10108
+ const cmd = s.commandTimeReliable ? formatDurationMs(s.commandTimeMs) : s.commandTimeMs > 0 ? `>=${formatFloorMs(s.commandTimeMs)}` : "n/a";
10055
10109
  const tokens = s.tokensAvailable ? `${formatInt(s.tokens.output)} out tok` : "no tokens";
10056
10110
  const machine = s.machineActiveAvailable ? `, model ${formatDurationMs(s.machineActiveTimeMs)}` : "";
10057
10111
  return `${s.sessionCount} sessions, ${tokens}, active ${formatDurationMs(s.activeTimeMs)}${machine}, command ${cmd}`;
10058
10112
  }
10113
+ function formatFloorMs(ms) {
10114
+ const coarse = formatDurationMs(ms);
10115
+ return coarse === "0s" ? `${Math.round(ms)}ms` : coarse;
10116
+ }
10059
10117
  function formatInt(n) {
10060
10118
  return n.toLocaleString("en-US");
10061
10119
  }
@@ -12386,6 +12444,12 @@ var VIEW_HTML = `<!doctype html>
12386
12444
  if (m > 0) return m + 'm ' + (sec < 10 ? '0' : '') + sec + 's';
12387
12445
  return sec + 's';
12388
12446
  }
12447
+ // A floor under half a second renders as '>=0s' -- true of any total, and
12448
+ // reading as a measured zero. Show those milliseconds instead.
12449
+ function fmtFloor(ms) {
12450
+ var coarse = fmtDur(ms);
12451
+ return coarse === '0s' ? Math.round(ms || 0) + 'ms' : coarse;
12452
+ }
12389
12453
  function kvrow(k, v) {
12390
12454
  return el('tr', {}, [el('td', { class: 'k', text: k }), el('td', { text: v })]);
12391
12455
  }
@@ -12427,12 +12491,12 @@ var VIEW_HTML = `<!doctype html>
12427
12491
  timeRows.push(kvrow('model working', fmtDur(t.machineActiveTimeMs) + ' (model compute, subset of active; Codex turn duration on ' + machineSessions + ' of ' + t.sessionCount + ' sessions; not wall-clock-deduped)'));
12428
12492
  }
12429
12493
  timeRows.push(kvrow('span', fmtDur(t.sessionSpanMs) + (t.openSessionCount > 0 ? ' (' + t.openSessionCount + ' open)' : '')));
12430
- timeRows.push(kvrow('command', fmtDur(t.commandTimeMs) + (t.commandTimeReliable ? '' : ' (some sessions report 0)')));
12494
+ timeRows.push(kvrow('command', fmtDur(t.commandTimeMs) + (t.commandTimeReliable ? '' : ' (floor: some sessions had no duration observed, or could not be read in full)')));
12431
12495
  detail.appendChild(el('table', { class: 'kv' }, [el('tbody', {}, timeRows)]));
12432
12496
  if (d.bySource && d.bySource.length) {
12433
12497
  detail.appendChild(el('h3', { text: 'By source' }));
12434
12498
  d.bySource.forEach(function (s) {
12435
- var cmd = s.commandTimeReliable ? fmtDur(s.commandTimeMs) : 'n/a';
12499
+ var cmd = s.commandTimeReliable ? fmtDur(s.commandTimeMs) : (s.commandTimeMs > 0 ? '>=' + fmtFloor(s.commandTimeMs) : 'n/a');
12436
12500
  var machine = s.machineActiveAvailable ? ', model ' + fmtDur(s.machineActiveTimeMs) : '';
12437
12501
  detail.appendChild(el('div', { class: 'row' }, [
12438
12502
  el('span', { text: s.sourceKind + ': ' + s.sessionCount + ' sessions, ' + numfmt(s.tokens.output) + ' out tok, active ' + fmtDur(s.activeTimeMs) + machine + ', command ' + cmd })