@basou/cli 0.41.0 → 0.42.1

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/program.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  appendChainedEventLocked,
13
13
  assertBasouRootSafe,
14
14
  basouPaths,
15
+ EVENT_SCHEMA_VERSION,
15
16
  enumerateApprovals,
16
17
  findErrorCode,
17
18
  isLazyExpired,
@@ -101,6 +102,18 @@ function printReplayWarning(warning, sessionId) {
101
102
  `Warning: skipped invalid event at line ${warning.line} in ${short}/events.jsonl`
102
103
  );
103
104
  break;
105
+ case "retired_zero_duration":
106
+ console.error(
107
+ `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`
108
+ );
109
+ break;
110
+ default: {
111
+ const unhandled = warning;
112
+ console.error(
113
+ `Warning: unhandled replay warning in ${short}/events.jsonl: ${String(unhandled.kind)}`
114
+ );
115
+ break;
116
+ }
104
117
  }
105
118
  }
106
119
  function printSessionSkip(sid, reason) {
@@ -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,
@@ -1135,6 +1149,15 @@ async function warnLinkedFilesOutsideRoots(input) {
1135
1149
  } catch {
1136
1150
  }
1137
1151
  }
1152
+ var TRACK_MARKER_IN_TITLE = /^\s*track\s*[:\uff1a]|[[(\uff08\uff3b\u3010]\s*track\s*[\])\uff09\uff3d\u3011]/i;
1153
+ function warnTrackMarkerWithoutKind(decisions, markerWithoutKind) {
1154
+ for (const index of markerWithoutKind) {
1155
+ const title = (decisions[index]?.title ?? "").trim();
1156
+ console.error(
1157
+ `basou: decision[${index}] title carries a track marker (${title.slice(0, 40)}) but "kind" is absent \u2014 it is recorded as a point-in-time decision and will NOT resurface in orient. Set "kind": "track" to open a track, or "kind": "decision" to say the marker is part of the title.`
1158
+ );
1159
+ }
1160
+ }
1138
1161
  async function doRunDecisionRecord(options, ctx) {
1139
1162
  const cwd = ctx.cwd ?? process.cwd();
1140
1163
  const repositoryRoot = await resolveRepositoryRootForDecision(cwd);
@@ -1232,7 +1255,8 @@ async function doRunDecisionCapture(options, ctx) {
1232
1255
  const paths = basouPaths3(repositoryRoot);
1233
1256
  await assertWorkspaceInitialized2(paths.root);
1234
1257
  const raw = await readCaptureInput(options, ctx);
1235
- const decisions = parseCaptureInput(raw);
1258
+ const { decisions, markerWithoutKind } = parseCaptureInput(raw);
1259
+ warnTrackMarkerWithoutKind(decisions, markerWithoutKind);
1236
1260
  await warnLinkedFilesOutsideRoots({
1237
1261
  linkedFiles: decisions.flatMap((d) => d.linked_files ?? []),
1238
1262
  cwd,
@@ -1400,7 +1424,7 @@ async function decisionExists(paths, decisionId) {
1400
1424
  }
1401
1425
  function buildDecisionVoidedEvent(input) {
1402
1426
  return {
1403
- schema_version: "0.1.0",
1427
+ schema_version: EVENT_SCHEMA_VERSION2,
1404
1428
  id: input.eventId,
1405
1429
  session_id: input.sessionId,
1406
1430
  occurred_at: input.occurredAt,
@@ -1493,7 +1517,14 @@ function parseCaptureInput(raw) {
1493
1517
  if (parsed.length === 0) {
1494
1518
  throw new Error("Input array must contain at least one decision.");
1495
1519
  }
1496
- return parsed.map((item, index) => validateCaptureItem(item, index));
1520
+ const decisions = [];
1521
+ const markerWithoutKind = [];
1522
+ parsed.forEach((item, index) => {
1523
+ const { input, kindWasPresent } = validateCaptureItem(item, index);
1524
+ decisions.push(input);
1525
+ if (!kindWasPresent && TRACK_MARKER_IN_TITLE.test(input.title)) markerWithoutKind.push(index);
1526
+ });
1527
+ return { decisions, markerWithoutKind };
1497
1528
  }
1498
1529
  function validateCaptureItem(item, index) {
1499
1530
  if (typeof item !== "object" || item === null || Array.isArray(item)) {
@@ -1554,7 +1585,7 @@ function validateCaptureItem(item, index) {
1554
1585
  }
1555
1586
  });
1556
1587
  }
1557
- return out;
1588
+ return { input: out, kindWasPresent: obj.kind !== void 0 };
1558
1589
  }
1559
1590
  function requireNonEmptyString(value, index, field) {
1560
1591
  if (typeof value !== "string" || isBlank(value)) {
@@ -1605,6 +1636,9 @@ function captureItemToPayload(item) {
1605
1636
  if (item.input.kind !== void 0) payload.kind = item.input.kind;
1606
1637
  return payload;
1607
1638
  }
1639
+ function kindMarker(kind) {
1640
+ return kind === "track" ? " [TRACK]" : " [DECISION]";
1641
+ }
1608
1642
  function printCapturePreview(options, decisions) {
1609
1643
  if (options.json === true) {
1610
1644
  console.log(JSON.stringify({ dry_run: true, count: decisions.length, decisions }));
@@ -1614,7 +1648,7 @@ function printCapturePreview(options, decisions) {
1614
1648
  `Would capture ${decisions.length} decision${decisions.length === 1 ? "" : "s"} (dry run; nothing written):`
1615
1649
  );
1616
1650
  for (const decision of decisions) {
1617
- console.log(`- ${decision.title}${decision.kind === "track" ? " [TRACK]" : ""}`);
1651
+ console.log(`- ${decision.title}${kindMarker(decision.kind)}`);
1618
1652
  }
1619
1653
  }
1620
1654
  function printCaptureResult(options, result) {
@@ -1635,9 +1669,7 @@ function printCaptureResult(options, result) {
1635
1669
  `Captured ${result.items.length} decision${result.items.length === 1 ? "" : "s"} in ad-hoc session ${sid}:`
1636
1670
  );
1637
1671
  for (const item of result.items) {
1638
- console.log(
1639
- `- ${item.decisionId}: ${item.input.title}${item.input.kind === "track" ? " [TRACK]" : ""}`
1640
- );
1672
+ console.log(`- ${item.decisionId}: ${item.input.title}${kindMarker(item.input.kind)}`);
1641
1673
  }
1642
1674
  }
1643
1675
  function pickRichFields(options) {
@@ -1658,7 +1690,7 @@ function pickRichFields(options) {
1658
1690
  }
1659
1691
  function buildDecisionEvent(input) {
1660
1692
  return {
1661
- schema_version: "0.1.0",
1693
+ schema_version: EVENT_SCHEMA_VERSION2,
1662
1694
  id: input.eventId,
1663
1695
  session_id: input.sessionId,
1664
1696
  occurred_at: input.occurredAt,
@@ -1855,6 +1887,7 @@ import {
1855
1887
  basouPaths as basouPaths5,
1856
1888
  ChildProcessRunner,
1857
1889
  appendChainedEvent as coreAppendChainedEvent,
1890
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION3,
1858
1891
  finalizeSessionYaml,
1859
1892
  getSnapshot,
1860
1893
  overwriteYamlFile,
@@ -1865,6 +1898,7 @@ import {
1865
1898
  resolveRepositoryRoot as resolveRepositoryRoot4,
1866
1899
  SessionSchema,
1867
1900
  sanitizeWorkingDirectory,
1901
+ writeObservedDuration,
1868
1902
  writeYamlFile
1869
1903
  } from "@basou/core";
1870
1904
  function registerExecCommand(program) {
@@ -1905,7 +1939,7 @@ async function runExec(command, args, options, ctx = {}) {
1905
1939
  });
1906
1940
  await writeYamlFile(sessionYamlPath, session);
1907
1941
  await appendEvent(sessionDir, {
1908
- schema_version: "0.1.0",
1942
+ schema_version: EVENT_SCHEMA_VERSION3,
1909
1943
  type: "session_started",
1910
1944
  id: prefixedUlid3("evt"),
1911
1945
  session_id: sessionId,
@@ -1917,7 +1951,7 @@ async function runExec(command, args, options, ctx = {}) {
1917
1951
  }
1918
1952
  const runningAt = now().toISOString();
1919
1953
  await appendEvent(sessionDir, {
1920
- schema_version: "0.1.0",
1954
+ schema_version: EVENT_SCHEMA_VERSION3,
1921
1955
  type: "session_status_changed",
1922
1956
  id: prefixedUlid3("evt"),
1923
1957
  session_id: sessionId,
@@ -1986,7 +2020,7 @@ async function runExec(command, args, options, ctx = {}) {
1986
2020
  }
1987
2021
  const endedAt = now().toISOString();
1988
2022
  await appendEvent(sessionDir, {
1989
- schema_version: "0.1.0",
2023
+ schema_version: EVENT_SCHEMA_VERSION3,
1990
2024
  type: "command_executed",
1991
2025
  id: prefixedUlid3("evt"),
1992
2026
  session_id: sessionId,
@@ -1998,14 +2032,21 @@ async function runExec(command, args, options, ctx = {}) {
1998
2032
  exit_code: result.exit_code,
1999
2033
  ...result.signal !== null ? { signal: result.signal } : {},
2000
2034
  ...signalReceived !== null ? { received_signal: signalReceived } : {},
2001
- duration_ms: result.duration_ms
2035
+ // Reached only when the child actually ran, and the runner times it on a
2036
+ // monotonic sub-millisecond clock, so the cheapest spawn on this host
2037
+ // (`/usr/bin/true`, median 0.83ms) still rounds to 1ms. A non-positive
2038
+ // value therefore means the measurement is unusable, not that the command
2039
+ // was instant, and is recorded as unobserved. A spawn that never ran
2040
+ // (ENOENT) does not reach this line at all: the runner rejects and
2041
+ // `finalizeSessionAsFailed` writes its own null.
2042
+ duration_ms: writeObservedDuration(result.duration_ms)
2002
2043
  });
2003
2044
  if (options.snapshot !== false) {
2004
2045
  await tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, appendEvent);
2005
2046
  }
2006
2047
  const finalStatus = decideFinalStatus(result, signalReceived);
2007
2048
  await appendEvent(sessionDir, {
2008
- schema_version: "0.1.0",
2049
+ schema_version: EVENT_SCHEMA_VERSION3,
2009
2050
  type: "session_status_changed",
2010
2051
  id: prefixedUlid3("evt"),
2011
2052
  session_id: sessionId,
@@ -2015,7 +2056,7 @@ async function runExec(command, args, options, ctx = {}) {
2015
2056
  to: finalStatus
2016
2057
  });
2017
2058
  await appendEvent(sessionDir, {
2018
- schema_version: "0.1.0",
2059
+ schema_version: EVENT_SCHEMA_VERSION3,
2019
2060
  type: "session_ended",
2020
2061
  id: prefixedUlid3("evt"),
2021
2062
  session_id: sessionId,
@@ -2062,7 +2103,7 @@ async function tryAppendGitSnapshot(sessionDir, sessionId, repoRoot, now, append
2062
2103
  return;
2063
2104
  }
2064
2105
  await appendEvent(sessionDir, {
2065
- schema_version: "0.1.0",
2106
+ schema_version: EVENT_SCHEMA_VERSION3,
2066
2107
  type: "git_snapshot",
2067
2108
  id: prefixedUlid3("evt"),
2068
2109
  session_id: sessionId,
@@ -2119,7 +2160,7 @@ async function mutateSessionYaml(filePath, mutator) {
2119
2160
  }
2120
2161
  async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent, ctx) {
2121
2162
  await appendEvent(sessionDir, {
2122
- schema_version: "0.1.0",
2163
+ schema_version: EVENT_SCHEMA_VERSION3,
2123
2164
  type: "command_executed",
2124
2165
  id: prefixedUlid3("evt"),
2125
2166
  session_id: sessionId,
@@ -2131,10 +2172,12 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
2131
2172
  exit_code: null,
2132
2173
  signal: null,
2133
2174
  ...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
2134
- duration_ms: 0
2175
+ // Not observed: this event stands in for a run that ended before a duration
2176
+ // could be measured, so 0 would claim a measured zero.
2177
+ duration_ms: null
2135
2178
  });
2136
2179
  await appendEvent(sessionDir, {
2137
- schema_version: "0.1.0",
2180
+ schema_version: EVENT_SCHEMA_VERSION3,
2138
2181
  type: "session_status_changed",
2139
2182
  id: prefixedUlid3("evt"),
2140
2183
  session_id: sessionId,
@@ -2144,7 +2187,7 @@ async function finalizeSessionAsFailed(paths, sessionDir, sessionId, appendEvent
2144
2187
  to: "failed"
2145
2188
  });
2146
2189
  await appendEvent(sessionDir, {
2147
- schema_version: "0.1.0",
2190
+ schema_version: EVENT_SCHEMA_VERSION3,
2148
2191
  type: "session_ended",
2149
2192
  id: prefixedUlid3("evt"),
2150
2193
  session_id: sessionId,
@@ -2609,6 +2652,7 @@ import {
2609
2652
  readSessionYaml as readSessionYaml2,
2610
2653
  reimportPreservingId,
2611
2654
  resolveRepositoryRoot as resolveRepositoryRoot6,
2655
+ SESSION_IMPORT_SCHEMA_VERSION,
2612
2656
  SessionImportPayloadSchema
2613
2657
  } from "@basou/core";
2614
2658
  var SES_PREFIX2 = "ses_";
@@ -2796,7 +2840,7 @@ async function importDerivedSessions(paths, manifest, options, sourceKind, candi
2796
2840
  if (!parsed.success) {
2797
2841
  throw new Error("Invalid import payload", { cause: parsed.error });
2798
2842
  }
2799
- if (parsed.data.schema_version !== "0.1.0") {
2843
+ if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION) {
2800
2844
  throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
2801
2845
  }
2802
2846
  return parsed.data;
@@ -4223,6 +4267,7 @@ import {
4223
4267
  assertBasouRootSafe as assertBasouRootSafe8,
4224
4268
  basouPaths as basouPaths9,
4225
4269
  createAdHocSessionWithEvent as createAdHocSessionWithEvent2,
4270
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION4,
4226
4271
  findErrorCode as findErrorCode7,
4227
4272
  readManifest as readManifest5,
4228
4273
  resolveSessionId as resolveSessionId2
@@ -4327,7 +4372,7 @@ async function doRunNote(body, options, ctx) {
4327
4372
  }
4328
4373
  function buildNoteEvent(input) {
4329
4374
  return {
4330
- schema_version: "0.1.0",
4375
+ schema_version: EVENT_SCHEMA_VERSION4,
4331
4376
  id: input.eventId,
4332
4377
  session_id: input.sessionId,
4333
4378
  occurred_at: input.occurredAt,
@@ -8844,6 +8889,7 @@ import {
8844
8889
  claudeCodeAdapterMetadata,
8845
8890
  codexAdapterMetadata,
8846
8891
  appendChainedEvent as coreAppendChainedEvent2,
8892
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION5,
8847
8893
  finalizeSessionYaml as finalizeSessionYaml2,
8848
8894
  findBasouSessionStartHook as findBasouSessionStartHook2,
8849
8895
  getDiff,
@@ -8858,6 +8904,7 @@ import {
8858
8904
  SessionSchema as SessionSchema2,
8859
8905
  sanitizeRelatedFiles,
8860
8906
  sanitizeWorkingDirectory as sanitizeWorkingDirectory2,
8907
+ writeObservedDuration as writeObservedDuration2,
8861
8908
  writeYamlFile as writeYamlFile2
8862
8909
  } from "@basou/core";
8863
8910
  function registerRunCommand(program, ctx = {}) {
@@ -8930,7 +8977,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
8930
8977
  });
8931
8978
  await writeYamlFile2(sessionYamlPath, session);
8932
8979
  await appendEvent(sessionDir, {
8933
- schema_version: "0.1.0",
8980
+ schema_version: EVENT_SCHEMA_VERSION5,
8934
8981
  type: "session_started",
8935
8982
  id: prefixedUlid4("evt"),
8936
8983
  session_id: sessionId,
@@ -8943,7 +8990,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
8943
8990
  }
8944
8991
  const runningAt = now().toISOString();
8945
8992
  await appendEvent(sessionDir, {
8946
- schema_version: "0.1.0",
8993
+ schema_version: EVENT_SCHEMA_VERSION5,
8947
8994
  type: "session_status_changed",
8948
8995
  id: prefixedUlid4("evt"),
8949
8996
  session_id: sessionId,
@@ -9016,7 +9063,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9016
9063
  }
9017
9064
  const endedAt = now().toISOString();
9018
9065
  await appendEvent(sessionDir, {
9019
- schema_version: "0.1.0",
9066
+ schema_version: EVENT_SCHEMA_VERSION5,
9020
9067
  type: "command_executed",
9021
9068
  id: prefixedUlid4("evt"),
9022
9069
  session_id: sessionId,
@@ -9028,7 +9075,14 @@ async function runTrackedTool(args, options, ctx, adapter) {
9028
9075
  exit_code: result.exit_code,
9029
9076
  ...result.signal !== null ? { signal: result.signal } : {},
9030
9077
  ...signalReceived !== null ? { received_signal: signalReceived } : {},
9031
- duration_ms: result.duration_ms
9078
+ // Reached only when the child actually ran, and the runner times it on a
9079
+ // monotonic sub-millisecond clock, so the cheapest spawn on this host
9080
+ // (`/usr/bin/true`, median 0.83ms) still rounds to 1ms. A non-positive
9081
+ // value therefore means the measurement is unusable, not that the command
9082
+ // was instant, and is recorded as unobserved. A spawn that never ran
9083
+ // (ENOENT) does not reach this line at all: the runner rejects and
9084
+ // `finalizeSessionAsFailed` writes its own null.
9085
+ duration_ms: writeObservedDuration2(result.duration_ms)
9032
9086
  });
9033
9087
  let postSnapshot = null;
9034
9088
  if (options.snapshot !== false) {
@@ -9054,7 +9108,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9054
9108
  }).sanitized;
9055
9109
  const finalStatus = decideFinalStatus2(result, signalReceived);
9056
9110
  await appendEvent(sessionDir, {
9057
- schema_version: "0.1.0",
9111
+ schema_version: EVENT_SCHEMA_VERSION5,
9058
9112
  type: "session_status_changed",
9059
9113
  id: prefixedUlid4("evt"),
9060
9114
  session_id: sessionId,
@@ -9064,7 +9118,7 @@ async function runTrackedTool(args, options, ctx, adapter) {
9064
9118
  to: finalStatus
9065
9119
  });
9066
9120
  await appendEvent(sessionDir, {
9067
- schema_version: "0.1.0",
9121
+ schema_version: EVENT_SCHEMA_VERSION5,
9068
9122
  type: "session_ended",
9069
9123
  id: prefixedUlid4("evt"),
9070
9124
  session_id: sessionId,
@@ -9110,7 +9164,7 @@ async function tryAppendGitSnapshot2(sessionDir, sessionId, repoRoot, now, appen
9110
9164
  return null;
9111
9165
  }
9112
9166
  await appendEvent(sessionDir, {
9113
- schema_version: "0.1.0",
9167
+ schema_version: EVENT_SCHEMA_VERSION5,
9114
9168
  type: "git_snapshot",
9115
9169
  id: prefixedUlid4("evt"),
9116
9170
  session_id: sessionId,
@@ -9130,7 +9184,7 @@ async function tryAppendFileChangedEvents(sessionDir, sessionId, repoRoot, baseR
9130
9184
  }
9131
9185
  for (const change of diff.changed_files) {
9132
9186
  await appendEvent(sessionDir, {
9133
- schema_version: "0.1.0",
9187
+ schema_version: EVENT_SCHEMA_VERSION5,
9134
9188
  type: "file_changed",
9135
9189
  id: prefixedUlid4("evt"),
9136
9190
  session_id: sessionId,
@@ -9214,7 +9268,7 @@ async function mutateSessionYaml2(filePath, mutator) {
9214
9268
  }
9215
9269
  async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEvent, ctx) {
9216
9270
  await appendEvent(sessionDir, {
9217
- schema_version: "0.1.0",
9271
+ schema_version: EVENT_SCHEMA_VERSION5,
9218
9272
  type: "command_executed",
9219
9273
  id: prefixedUlid4("evt"),
9220
9274
  session_id: sessionId,
@@ -9226,10 +9280,12 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
9226
9280
  exit_code: null,
9227
9281
  signal: null,
9228
9282
  ...ctx.signalReceived !== null ? { received_signal: ctx.signalReceived } : {},
9229
- duration_ms: 0
9283
+ // Not observed: this event stands in for a run that ended before a duration
9284
+ // could be measured, so 0 would claim a measured zero.
9285
+ duration_ms: null
9230
9286
  });
9231
9287
  await appendEvent(sessionDir, {
9232
- schema_version: "0.1.0",
9288
+ schema_version: EVENT_SCHEMA_VERSION5,
9233
9289
  type: "session_status_changed",
9234
9290
  id: prefixedUlid4("evt"),
9235
9291
  session_id: sessionId,
@@ -9239,7 +9295,7 @@ async function finalizeSessionAsFailed2(paths, sessionDir, sessionId, appendEven
9239
9295
  to: "failed"
9240
9296
  });
9241
9297
  await appendEvent(sessionDir, {
9242
- schema_version: "0.1.0",
9298
+ schema_version: EVENT_SCHEMA_VERSION5,
9243
9299
  type: "session_ended",
9244
9300
  id: prefixedUlid4("evt"),
9245
9301
  session_id: sessionId,
@@ -9289,16 +9345,19 @@ import {
9289
9345
  appendEventToExistingSession as appendEventToExistingSession3,
9290
9346
  assertBasouRootSafe as assertBasouRootSafe13,
9291
9347
  basouPaths as basouPaths16,
9348
+ EVENT_SCHEMA_VERSION as EVENT_SCHEMA_VERSION6,
9292
9349
  enumerateSessionDirs as enumerateSessionDirs2,
9293
9350
  findErrorCode as findErrorCode12,
9294
9351
  importSessionFromJson as importSessionFromJson2,
9295
9352
  loadSessionEntries as loadSessionEntries2,
9296
9353
  readAllEvents,
9297
9354
  readManifest as readManifest10,
9355
+ readObservedDuration,
9298
9356
  readYamlFile as readYamlFile7,
9299
9357
  rechainSessionInPlace,
9300
9358
  resolveSessionId as resolveSessionId3,
9301
9359
  resolveTaskId,
9360
+ SESSION_IMPORT_SCHEMA_VERSION as SESSION_IMPORT_SCHEMA_VERSION2,
9302
9361
  SessionImportPayloadSchema as SessionImportPayloadSchema2,
9303
9362
  SessionSchema as SessionSchema3,
9304
9363
  SessionStatusSchema,
@@ -9420,15 +9479,19 @@ async function doRunSessionShow(idInput, options, ctx) {
9420
9479
  }
9421
9480
  throw new Error("Failed to read session", { cause: error });
9422
9481
  }
9482
+ let eventsLostLines = 0;
9423
9483
  const events = await readAllEvents(sessionDir, {
9424
- onWarning: (w) => printReplayWarning(w, sessionId)
9484
+ onWarning: (w) => {
9485
+ if (w.kind === "malformed_json" || w.kind === "schema_violation") eventsLostLines++;
9486
+ printReplayWarning(w, sessionId);
9487
+ }
9425
9488
  });
9426
9489
  if (options.json === true) {
9427
9490
  console.log(JSON.stringify({ session: session.session, events }, null, 2));
9428
9491
  return;
9429
9492
  }
9430
9493
  const now = ctx.nowProvider?.() ?? /* @__PURE__ */ new Date();
9431
- printSessionShowText(session, events, options, repositoryRoot, now);
9494
+ printSessionShowText(session, events, options, repositoryRoot, now, eventsLostLines);
9432
9495
  }
9433
9496
  function suspectLabel(reason) {
9434
9497
  if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
@@ -9474,7 +9537,7 @@ function printSessionListText(records) {
9474
9537
  );
9475
9538
  }
9476
9539
  }
9477
- function printSessionShowText(session, events, options, repositoryRoot, now) {
9540
+ function printSessionShowText(session, events, options, repositoryRoot, now, eventsLostLines) {
9478
9541
  const s = session.session;
9479
9542
  console.log(`Session: ${s.id} (status: ${s.status})`);
9480
9543
  console.log(`Source: ${s.source.kind} (v${s.source.version})`);
@@ -9500,7 +9563,7 @@ function printSessionShowText(session, events, options, repositoryRoot, now) {
9500
9563
  console.log(` ${pad2(`${type}:`, 24)} ${n}`);
9501
9564
  }
9502
9565
  console.log("");
9503
- console.log(`Work: ${formatSessionWork(session, events, now)}`);
9566
+ console.log(`Work: ${formatSessionWork(session, events, now, eventsLostLines)}`);
9504
9567
  if (events.length === 0) return;
9505
9568
  const last = options.last ?? 5;
9506
9569
  const showAll = options.events === true && options.last === void 0;
@@ -9512,8 +9575,15 @@ function printSessionShowText(session, events, options, repositoryRoot, now) {
9512
9575
  console.log(` ${formatEventLine(ev)}`);
9513
9576
  }
9514
9577
  }
9515
- function formatSessionWork(session, events, now) {
9516
- const w = sessionWorkStatsFromEvents(session.session.id, session.session, events, now);
9578
+ function formatSessionWork(session, events, now, eventsLostLines) {
9579
+ const w = sessionWorkStatsFromEvents(
9580
+ session.session.id,
9581
+ session.session,
9582
+ events,
9583
+ now,
9584
+ false,
9585
+ eventsLostLines
9586
+ );
9517
9587
  const parts = [];
9518
9588
  if (w.tokens.output > 0) parts.push(`${w.tokens.output.toLocaleString("en-US")} output tokens`);
9519
9589
  parts.push(`${w.commandCount} cmd / ${w.fileChangedCount} files / ${w.decisionCount} dec`);
@@ -9524,7 +9594,7 @@ function formatSessionWork(session, events, now) {
9524
9594
  }
9525
9595
  parts.push(`span ${formatDurationMs(w.sessionSpanMs)}${w.open ? " (open)" : ""}`);
9526
9596
  parts.push(
9527
- w.availability.commandTime ? `command ${formatDurationMs(w.commandTimeMs)}` : "command n/a (import)"
9597
+ w.availability.commandTime ? `command ${formatDurationMs(w.commandTimeMs)}` : "command n/a (no duration observed, or the stream was not read in full)"
9528
9598
  );
9529
9599
  return parts.join(", ");
9530
9600
  }
@@ -9563,7 +9633,9 @@ function eventVariantSummary(ev) {
9563
9633
  const argsPart = ev.args.length > 0 ? ` ${ev.args.join(" ")}` : "";
9564
9634
  const executorPart = ev.command ?? "(executor unrecorded)";
9565
9635
  const exitPart = ev.exit_code === null ? "exit=unknown" : `exit=${ev.exit_code}`;
9566
- return `${executorPart}${argsPart} (${exitPart}, ${ev.duration_ms}ms)`;
9636
+ const observedDuration = readObservedDuration(ev);
9637
+ const durationPart = observedDuration === null ? "duration=unknown" : `${observedDuration}ms`;
9638
+ return `${executorPart}${argsPart} (${exitPart}, ${durationPart})`;
9567
9639
  }
9568
9640
  case "git_snapshot":
9569
9641
  return `branch=${ev.branch} dirty=${ev.dirty}`;
@@ -9713,7 +9785,7 @@ async function doRunSessionImport(options, ctx) {
9713
9785
  if (!parsed.success) {
9714
9786
  throw new Error("Invalid import payload", { cause: parsed.error });
9715
9787
  }
9716
- if (parsed.data.schema_version !== "0.1.0") {
9788
+ if (parsed.data.schema_version !== SESSION_IMPORT_SCHEMA_VERSION2) {
9717
9789
  throw new Error(`Unsupported import schema_version: ${parsed.data.schema_version}`);
9718
9790
  }
9719
9791
  const importOptions2 = { dryRun: options.dryRun === true };
@@ -9834,7 +9906,7 @@ async function doRunSessionNote(sessionIdInput, options, ctx) {
9834
9906
  paths,
9835
9907
  sessionId: sesId,
9836
9908
  eventBuilder: (eventId) => ({
9837
- schema_version: "0.1.0",
9909
+ schema_version: EVENT_SCHEMA_VERSION6,
9838
9910
  id: eventId,
9839
9911
  session_id: sesId,
9840
9912
  occurred_at: occurredAt,
@@ -10028,7 +10100,7 @@ function printStatsText(result, bySource, byDay) {
10028
10100
  console.log(
10029
10101
  ` Span: ${formatDurationMs(t.sessionSpanMs)} (total elapsed${openPart})`
10030
10102
  );
10031
- const cmdCaveat = t.commandTimeReliable ? "" : "; some sessions (e.g. claude-code-import) report 0 shell time";
10103
+ 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
10104
  console.log(
10033
10105
  ` Command: ${formatDurationMs(t.commandTimeMs)} (real shell execution${cmdCaveat})`
10034
10106
  );
@@ -10051,11 +10123,15 @@ function printStatsText(result, bySource, byDay) {
10051
10123
  }
10052
10124
  }
10053
10125
  function describeSource(s) {
10054
- const cmd = s.commandTimeReliable ? formatDurationMs(s.commandTimeMs) : "n/a";
10126
+ const cmd = s.commandTimeReliable ? formatDurationMs(s.commandTimeMs) : s.commandTimeMs > 0 ? `>=${formatFloorMs(s.commandTimeMs)}` : "n/a";
10055
10127
  const tokens = s.tokensAvailable ? `${formatInt(s.tokens.output)} out tok` : "no tokens";
10056
10128
  const machine = s.machineActiveAvailable ? `, model ${formatDurationMs(s.machineActiveTimeMs)}` : "";
10057
10129
  return `${s.sessionCount} sessions, ${tokens}, active ${formatDurationMs(s.activeTimeMs)}${machine}, command ${cmd}`;
10058
10130
  }
10131
+ function formatFloorMs(ms) {
10132
+ const coarse = formatDurationMs(ms);
10133
+ return coarse === "0s" ? `${Math.round(ms)}ms` : coarse;
10134
+ }
10059
10135
  function formatInt(n) {
10060
10136
  return n.toLocaleString("en-US");
10061
10137
  }
@@ -12386,6 +12462,12 @@ var VIEW_HTML = `<!doctype html>
12386
12462
  if (m > 0) return m + 'm ' + (sec < 10 ? '0' : '') + sec + 's';
12387
12463
  return sec + 's';
12388
12464
  }
12465
+ // A floor under half a second renders as '>=0s' -- true of any total, and
12466
+ // reading as a measured zero. Show those milliseconds instead.
12467
+ function fmtFloor(ms) {
12468
+ var coarse = fmtDur(ms);
12469
+ return coarse === '0s' ? Math.round(ms || 0) + 'ms' : coarse;
12470
+ }
12389
12471
  function kvrow(k, v) {
12390
12472
  return el('tr', {}, [el('td', { class: 'k', text: k }), el('td', { text: v })]);
12391
12473
  }
@@ -12427,12 +12509,12 @@ var VIEW_HTML = `<!doctype html>
12427
12509
  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
12510
  }
12429
12511
  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)')));
12512
+ timeRows.push(kvrow('command', fmtDur(t.commandTimeMs) + (t.commandTimeReliable ? '' : ' (floor: some sessions had no duration observed, or could not be read in full)')));
12431
12513
  detail.appendChild(el('table', { class: 'kv' }, [el('tbody', {}, timeRows)]));
12432
12514
  if (d.bySource && d.bySource.length) {
12433
12515
  detail.appendChild(el('h3', { text: 'By source' }));
12434
12516
  d.bySource.forEach(function (s) {
12435
- var cmd = s.commandTimeReliable ? fmtDur(s.commandTimeMs) : 'n/a';
12517
+ var cmd = s.commandTimeReliable ? fmtDur(s.commandTimeMs) : (s.commandTimeMs > 0 ? '>=' + fmtFloor(s.commandTimeMs) : 'n/a');
12436
12518
  var machine = s.machineActiveAvailable ? ', model ' + fmtDur(s.machineActiveTimeMs) : '';
12437
12519
  detail.appendChild(el('div', { class: 'row' }, [
12438
12520
  el('span', { text: s.sourceKind + ': ' + s.sessionCount + ' sessions, ' + numfmt(s.tokens.output) + ' out tok, active ' + fmtDur(s.activeTimeMs) + machine + ', command ' + cmd })