@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/index.js +132 -50
- package/dist/index.js.map +1 -1
- package/dist/program.js +132 -50
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(program2) {
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 !==
|
|
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:
|
|
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(program2, 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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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) =>
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 !==
|
|
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:
|
|
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
|
|
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
|
|
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 })
|