@basou/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -143,6 +143,7 @@ declare const SessionInnerImportSchema: z.ZodObject<{
143
143
  }, z.core.$strip>>>;
144
144
  active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
145
145
  active_time_method: z.ZodOptional<z.ZodString>;
146
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
146
147
  }, z.core.$strip>>;
147
148
  }, z.core.$strict>;
148
149
  /**
@@ -202,6 +203,7 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
202
203
  }, z.core.$strip>>>;
203
204
  active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
204
205
  active_time_method: z.ZodOptional<z.ZodString>;
206
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
205
207
  }, z.core.$strip>>;
206
208
  }, z.core.$strict>;
207
209
  events: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -516,6 +518,13 @@ type CodexRolloutToPayloadOptions = {
516
518
  * are parsed from the paired `function_call_output` (matched by `call_id`),
517
519
  * whose text carries `Process exited with code N` and `Wall time: X seconds`.
518
520
  *
521
+ * Per-session `metrics` are also derived: token totals from the cumulative
522
+ * `token_count` events; active time from the real `task_started` ->
523
+ * `task_complete` turn spans (in-turn, uncapped) unioned with the gap-capped
524
+ * engagement series (between-turn bridging), labeled `turn-intervals`; and
525
+ * `machine_active_time_ms` from the summed `task_complete.duration_ms` (model
526
+ * compute time, a subset of active time).
527
+ *
519
528
  * Unlike the Claude importer this derives no `file_changed`: Codex has no
520
529
  * dedicated edit tool and applies edits inside `exec_command` (e.g.
521
530
  * `apply_patch`), so there is no clean file-change signal to map. Decisions
@@ -1283,7 +1292,17 @@ type SessionSourceKind = z.infer<typeof SessionSourceKindSchema>;
1283
1292
  * wall-clock ranges (so cross-session totals can de-duplicate overlapping
1284
1293
  * work by interval union); `active_time_ms` is their summed duration;
1285
1294
  * `active_gap_cap_ms` and `active_time_method` lock the methodology so the
1286
- * stored numbers stay interpretable if the method changes later.
1295
+ * stored numbers stay interpretable if the method changes later. When a
1296
+ * source records explicit per-turn intervals (Codex), `active_time_method` is
1297
+ * `turn-intervals` and the in-turn time is the log's real wall-clock span
1298
+ * rather than a gap-capped approximation; the active semantics are unchanged.
1299
+ * - `machine_active_time_ms`: model compute time — the summed duration of the
1300
+ * source's per-turn spans (Codex `task_complete.duration_ms`), a SUBSET of a
1301
+ * single session's engaged active time. Unlike `active_intervals` it is a
1302
+ * plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum
1303
+ * past their billable (union) active wall-clock — that is intended (two models
1304
+ * working at once did two machine-hours in one wall-clock hour). Captured only
1305
+ * for sources that record per-turn duration (Codex); absent otherwise.
1287
1306
  *
1288
1307
  * Absent on sessions imported before a given field existed (re-import to
1289
1308
  * backfill). Live sessions carry no engaged-time metrics and fall back to
@@ -1301,6 +1320,7 @@ declare const SessionMetricsSchema: z.ZodObject<{
1301
1320
  }, z.core.$strip>>>;
1302
1321
  active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
1303
1322
  active_time_method: z.ZodOptional<z.ZodString>;
1323
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
1304
1324
  }, z.core.$strip>;
1305
1325
  /** Inferred runtime type for {@link SessionMetricsSchema}. */
1306
1326
  type SessionMetrics = z.infer<typeof SessionMetricsSchema>;
@@ -1361,6 +1381,7 @@ declare const SessionSchema: z.ZodObject<{
1361
1381
  }, z.core.$strip>>>;
1362
1382
  active_gap_cap_ms: z.ZodOptional<z.ZodNumber>;
1363
1383
  active_time_method: z.ZodOptional<z.ZodString>;
1384
+ machine_active_time_ms: z.ZodOptional<z.ZodNumber>;
1364
1385
  }, z.core.$strip>>;
1365
1386
  }, z.core.$strip>;
1366
1387
  }, z.core.$strip>;
@@ -2711,6 +2732,46 @@ declare class ChildProcessRunner implements ProcessRunner {
2711
2732
  run(command: string, args: readonly string[], options: RunOptions): Promise<RunResult>;
2712
2733
  }
2713
2734
 
2735
+ /**
2736
+ * Schema version of the on-disk Basou v0.1 formats these JSON Schemas describe.
2737
+ * It tracks {@link SchemaVersionSchema} (the `schema_version` field), NOT the
2738
+ * npm package version, so the `$id` URLs stay stable while the package moves.
2739
+ */
2740
+ declare const JSON_SCHEMA_VERSION = "0.1.0";
2741
+ /** One emitted JSON Schema artifact. */
2742
+ type JsonSchemaArtifact = {
2743
+ /** Artifact basename without extension (e.g. `session`). */
2744
+ name: string;
2745
+ /** The JSON Schema document (draft 2020-12). */
2746
+ schema: Record<string, unknown>;
2747
+ };
2748
+ /**
2749
+ * Build the published JSON Schema artifacts from the canonical Zod schemas.
2750
+ *
2751
+ * Pure: no disk or environment access. Each artifact is `z.toJSONSchema` of the
2752
+ * document schema, re-headed with a stable `$id` / `title` / `description` (the
2753
+ * draft `$schema` from zod is preserved). This is the single generator used by
2754
+ * both the `gen:schemas` script (which writes the committed files) and the
2755
+ * drift-guard test (which asserts the committed files still match), so the two
2756
+ * can never disagree.
2757
+ *
2758
+ * Generated in `io: "input"` mode so the artifacts describe what a consumer
2759
+ * AUTHORS on disk, not zod's parsed output: a field with a `.default()` (e.g.
2760
+ * `events_log`) stays optional rather than `required`, and a non-strict object
2761
+ * omits `additionalProperties: false` so additive fields are allowed. Only the
2762
+ * `.strict()` event variants (e.g. `adapter_output`) keep
2763
+ * `additionalProperties: false`, preserving their reject-unknown contract.
2764
+ *
2765
+ * Note: prefixed-id fields carry a representable `pattern` (see
2766
+ * `createPrefixedIdSchema`); other refinement-only constraints are not
2767
+ * expressible in JSON Schema and are intentionally omitted.
2768
+ */
2769
+ declare function buildJsonSchemas(): JsonSchemaArtifact[];
2770
+ /** Serialize an artifact's schema exactly as the committed file stores it
2771
+ * (2-space indent, trailing newline) so the generator and the drift-guard test
2772
+ * compare byte-for-byte. */
2773
+ declare function serializeJsonSchema(schema: Record<string, unknown>): string;
2774
+
2714
2775
  /**
2715
2776
  * Schema version literal pinned to "0.1.0" for Basou v0.1.
2716
2777
  * Reused across every entity schema so inferred types narrow to the literal.
@@ -2823,6 +2884,8 @@ type MeasureAvailability = {
2823
2884
  activeTime: boolean;
2824
2885
  /** Token totals were captured (model-usage metrics present). */
2825
2886
  tokens: boolean;
2887
+ /** Model compute time was captured (`machine_active_time_ms`; Codex only). */
2888
+ machineActive: boolean;
2826
2889
  };
2827
2890
  /** Token rollup. Zero when not captured; `reasoning` is Codex-only. */
2828
2891
  type TokenTotals = {
@@ -2857,6 +2920,18 @@ type SessionWorkStats = {
2857
2920
  * (concurrent) work is not double-counted in billable totals.
2858
2921
  */
2859
2922
  activeIntervals: IsoInterval[];
2923
+ /**
2924
+ * Model compute time: the source's summed per-turn duration
2925
+ * (`metrics.machine_active_time_ms`). A subset of `activeTimeMs`; 0 when the
2926
+ * source records no per-turn duration (everything but Codex today).
2927
+ */
2928
+ machineActiveTimeMs: number;
2929
+ /**
2930
+ * Methodology lock copied from `metrics.active_time_method` (e.g.
2931
+ * `turn-intervals` / `engaged-turns`); undefined when active time was derived
2932
+ * from the event stream rather than stored metrics.
2933
+ */
2934
+ activeTimeMethod: string | undefined;
2860
2935
  commandCount: number;
2861
2936
  fileChangedCount: number;
2862
2937
  decisionCount: number;
@@ -2874,6 +2949,7 @@ type SourceWorkStats = {
2874
2949
  sessionSpanMs: number;
2875
2950
  commandTimeMs: number;
2876
2951
  activeTimeMs: number;
2952
+ machineActiveTimeMs: number;
2877
2953
  commandCount: number;
2878
2954
  fileChangedCount: number;
2879
2955
  decisionCount: number;
@@ -2883,6 +2959,8 @@ type SourceWorkStats = {
2883
2959
  commandTimeReliable: boolean;
2884
2960
  /** At least one session of this kind captured token totals. */
2885
2961
  tokensAvailable: boolean;
2962
+ /** At least one session of this kind captured model compute time. */
2963
+ machineActiveAvailable: boolean;
2886
2964
  };
2887
2965
  type StatusCount = {
2888
2966
  status: SessionStatus;
@@ -2898,6 +2976,12 @@ type DayWorkStats = {
2898
2976
  /** Calendar date `YYYY-MM-DD` in the report timezone. */
2899
2977
  date: string;
2900
2978
  billableActiveTimeMs: number;
2979
+ /**
2980
+ * Model compute time for sessions started on this date (summed
2981
+ * `machine_active_time_ms`). Not wall-clock-deduplicated, so — unlike
2982
+ * `billableActiveTimeMs` — concurrent sessions sum freely.
2983
+ */
2984
+ machineActiveTimeMs: number;
2901
2985
  sessionCount: number;
2902
2986
  commandCount: number;
2903
2987
  fileChangedCount: number;
@@ -2917,6 +3001,13 @@ type WorkStatsTotals = {
2917
3001
  * `activeTimeMs` when no sessions overlap, and is smaller when they do.
2918
3002
  */
2919
3003
  billableActiveTimeMs: number;
3004
+ /**
3005
+ * Workspace-wide model compute time: summed `machine_active_time_ms`. A plain
3006
+ * sum (not interval union), so it can exceed `billableActiveTimeMs` when
3007
+ * sessions ran concurrently — two models working at once is two machine-hours
3008
+ * in one wall-clock hour.
3009
+ */
3010
+ machineActiveTimeMs: number;
2920
3011
  commandCount: number;
2921
3012
  fileChangedCount: number;
2922
3013
  decisionCount: number;
@@ -2925,6 +3016,8 @@ type WorkStatsTotals = {
2925
3016
  /** No `claude-code-import` sessions present, so command time is workspace-wide real. */
2926
3017
  commandTimeReliable: boolean;
2927
3018
  tokensAvailable: boolean;
3019
+ /** At least one session captured model compute time (`machine_active_time_ms`). */
3020
+ machineActiveAvailable: boolean;
2928
3021
  };
2929
3022
  type WorkStatsResult = {
2930
3023
  generatedAt: string;
@@ -3303,4 +3396,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
3303
3396
  */
3304
3397
  declare const BASOU_CORE_VERSION = "0.1.0";
3305
3398
 
3306
- export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, sessionWorkStatsFromEvents, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
3399
+ export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ function isValidPrefixedId(value) {
49
49
  // src/stats/active-time.ts
50
50
  var ACTIVE_GAP_CAP_MS = 5 * 60 * 1e3;
51
51
  var ENGAGED_TURNS_METHOD = "engaged-turns";
52
+ var TURN_INTERVALS_METHOD = "turn-intervals";
52
53
  function activeTimeFromTimestamps(timestampsMs, capMs) {
53
54
  const sorted = timestampsMs.filter((t) => Number.isFinite(t)).sort((a, b) => a - b);
54
55
  const raw = [];
@@ -340,6 +341,7 @@ var CODEX_IMPORT_SOURCE = "codex-import";
340
341
  function codexRolloutToImportPayload(records, options) {
341
342
  const placeholderSessionId = prefixedUlid("ses");
342
343
  const outputsByCallId = indexOutputs(records);
344
+ const turnStartMsByTurnId = indexTaskStarts(records);
343
345
  const derived = [];
344
346
  let minTs;
345
347
  let maxTs;
@@ -347,6 +349,8 @@ function codexRolloutToImportPayload(records, options) {
347
349
  let codexSessionId;
348
350
  let lastTokenTotals;
349
351
  const engagementTsMs = [];
352
+ const completions = [];
353
+ const completedTurnIds = /* @__PURE__ */ new Set();
350
354
  for (const record of records) {
351
355
  const ts = readString2(record.timestamp);
352
356
  if (ts === void 0) continue;
@@ -371,6 +375,16 @@ function codexRolloutToImportPayload(records, options) {
371
375
  const tsMs = Date.parse(ts);
372
376
  if (Number.isFinite(tsMs)) engagementTsMs.push(tsMs);
373
377
  }
378
+ if (pt === "task_complete") {
379
+ const turnId = readString2(payload2.turn_id);
380
+ if (turnId === void 0 || !completedTurnIds.has(turnId)) {
381
+ if (turnId !== void 0) completedTurnIds.add(turnId);
382
+ completions.push({
383
+ interval: turnIntervalFromComplete(ts, payload2, turnStartMsByTurnId),
384
+ durationMs: readNonNegInt2(payload2.duration_ms)
385
+ });
386
+ }
387
+ }
374
388
  continue;
375
389
  }
376
390
  if (readString2(record.type) !== "response_item") continue;
@@ -401,7 +415,23 @@ function codexRolloutToImportPayload(records, options) {
401
415
  const commandCount = derived.length;
402
416
  const date = minTs.slice(0, 10);
403
417
  const label = `codex ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
404
- const active = engagementTsMs.length >= 2 ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS) : void 0;
418
+ const minTsMs = Date.parse(minTs);
419
+ const turnIntervals = [];
420
+ let machineActiveMs = 0;
421
+ let allCompletedTurnsHaveDuration = true;
422
+ for (const { interval, durationMs } of completions) {
423
+ if (durationMs <= 0) allCompletedTurnsHaveDuration = false;
424
+ if (interval === void 0) continue;
425
+ const start = Number.isFinite(minTsMs) ? Math.max(interval[0], minTsMs) : interval[0];
426
+ const end = interval[1];
427
+ if (!(start < end)) continue;
428
+ turnIntervals.push([start, end]);
429
+ machineActiveMs += Math.min(durationMs, end - start);
430
+ }
431
+ const pointResult = activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS);
432
+ const active = turnIntervals.length > 0 || pointResult.intervals.length > 0 ? unionDurationMs([...turnIntervals, ...pointResult.intervals]) : void 0;
433
+ const activeMethod = turnIntervals.length > 0 ? TURN_INTERVALS_METHOD : ENGAGED_TURNS_METHOD;
434
+ const machineActive = allCompletedTurnsHaveDuration ? machineActiveMs : 0;
405
435
  const tokenFields = lastTokenTotals === void 0 ? {} : {
406
436
  ...readNonNegInt2(lastTokenTotals.output_tokens) > 0 ? { output_tokens: readNonNegInt2(lastTokenTotals.output_tokens) } : {},
407
437
  ...readNonNegInt2(lastTokenTotals.input_tokens) > 0 ? { input_tokens: readNonNegInt2(lastTokenTotals.input_tokens) } : {},
@@ -412,10 +442,11 @@ function codexRolloutToImportPayload(records, options) {
412
442
  ...tokenFields,
413
443
  ...active !== void 0 && active.ms > 0 ? {
414
444
  active_time_ms: active.ms,
415
- active_intervals: intervalsMsToIso(active.intervals),
445
+ active_intervals: intervalsMsToIso(active.merged),
416
446
  active_gap_cap_ms: ACTIVE_GAP_CAP_MS,
417
- active_time_method: ENGAGED_TURNS_METHOD
418
- } : {}
447
+ active_time_method: activeMethod
448
+ } : {},
449
+ ...machineActive > 0 ? { machine_active_time_ms: machineActive } : {}
419
450
  };
420
451
  const metrics = Object.keys(metricsFields).length > 0 ? metricsFields : void 0;
421
452
  const payload = {
@@ -496,6 +527,30 @@ function readCallId(value, outputs) {
496
527
  const callId = readString2(value);
497
528
  return callId !== void 0 ? outputs.get(callId) : void 0;
498
529
  }
530
+ function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
531
+ const endMs = Date.parse(endTs);
532
+ if (!Number.isFinite(endMs)) return void 0;
533
+ const turnId = readString2(payload.turn_id);
534
+ const indexedStart = turnId !== void 0 ? startMsByTurnId.get(turnId) : void 0;
535
+ const durationMs = readNonNegInt2(payload.duration_ms);
536
+ const startMs = indexedStart !== void 0 ? indexedStart : durationMs > 0 ? endMs - durationMs : void 0;
537
+ if (startMs === void 0 || !(startMs < endMs)) return void 0;
538
+ return [startMs, endMs];
539
+ }
540
+ function indexTaskStarts(records) {
541
+ const byTurnId = /* @__PURE__ */ new Map();
542
+ for (const record of records) {
543
+ if (readString2(record.type) !== "event_msg") continue;
544
+ const payload = isObject2(record.payload) ? record.payload : void 0;
545
+ if (payload === void 0 || readString2(payload.type) !== "task_started") continue;
546
+ const turnId = readString2(payload.turn_id);
547
+ const startMs = Date.parse(readString2(record.timestamp) ?? "");
548
+ if (turnId !== void 0 && Number.isFinite(startMs) && !byTurnId.has(turnId)) {
549
+ byTurnId.set(turnId, startMs);
550
+ }
551
+ }
552
+ return byTurnId;
553
+ }
499
554
  function parseExitCode(output) {
500
555
  if (output === void 0) return null;
501
556
  const match = output.match(/Process exited with code (-?\d+)/);
@@ -546,7 +601,10 @@ var SchemaVersionSchema = z.literal("0.1.0");
546
601
  var IsoTimestampSchema = z.string().datetime({ offset: true });
547
602
  var createPrefixedIdSchema = (prefix) => {
548
603
  const refiner = (value) => isValidPrefixedId(value) && value.startsWith(`${prefix}_`);
549
- return z.string().refine(refiner, { message: `Expected ${prefix}_<ULID>` });
604
+ return z.string().refine(refiner, { message: `Expected ${prefix}_<ULID>` }).meta({
605
+ pattern: `^${prefix}_[0-7][0-9A-HJKMNP-TV-Z]{25}$`,
606
+ description: `Basou ${prefix} id: \`${prefix}_\` followed by a 26-character Crockford Base32 ULID.`
607
+ });
550
608
  };
551
609
  var WorkspaceIdSchema = createPrefixedIdSchema("ws");
552
610
  var TaskIdSchema = createPrefixedIdSchema("task");
@@ -991,7 +1049,8 @@ var SessionMetricsSchema = z4.object({
991
1049
  active_time_ms: z4.number().int().nonnegative().optional(),
992
1050
  active_intervals: z4.array(z4.object({ start: IsoTimestampSchema, end: IsoTimestampSchema })).optional(),
993
1051
  active_gap_cap_ms: z4.number().int().nonnegative().optional(),
994
- active_time_method: z4.string().optional()
1052
+ active_time_method: z4.string().optional(),
1053
+ machine_active_time_ms: z4.number().int().nonnegative().optional()
995
1054
  });
996
1055
  var SessionInnerSchema = z4.object({
997
1056
  id: SessionIdSchema,
@@ -3799,6 +3858,9 @@ function classifySpawnError(error) {
3799
3858
  return new Error("Failed to spawn child process", { cause: error });
3800
3859
  }
3801
3860
 
3861
+ // src/schemas/json-schema.ts
3862
+ import { z as z11 } from "zod";
3863
+
3802
3864
  // src/schemas/manifest.schema.ts
3803
3865
  import { z as z9 } from "zod";
3804
3866
  var ProjectSchema = z9.object({
@@ -3874,6 +3936,79 @@ var SessionImportPayloadSchema = z10.object({
3874
3936
  events: z10.array(EventSchema)
3875
3937
  }).strict();
3876
3938
 
3939
+ // src/schemas/json-schema.ts
3940
+ var JSON_SCHEMA_VERSION = "0.1.0";
3941
+ var ID_BASE = `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}`;
3942
+ var JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
3943
+ var DOCUMENTS = [
3944
+ {
3945
+ name: "manifest",
3946
+ schema: ManifestSchema,
3947
+ title: "Basou Manifest",
3948
+ description: "The `.basou/manifest.yaml` workspace manifest."
3949
+ },
3950
+ {
3951
+ name: "session",
3952
+ schema: SessionSchema,
3953
+ title: "Basou Session",
3954
+ description: "A `.basou/sessions/<id>/session.yaml` session record."
3955
+ },
3956
+ {
3957
+ name: "event",
3958
+ schema: EventSchema,
3959
+ title: "Basou Event",
3960
+ description: "One line of a `.basou/sessions/<id>/events.jsonl` stream (a discriminated union over the event `type`)."
3961
+ },
3962
+ {
3963
+ name: "task",
3964
+ schema: TaskSchema,
3965
+ title: "Basou Task",
3966
+ description: "The YAML front matter of a `.basou/tasks/<id>.md` task document."
3967
+ },
3968
+ {
3969
+ name: "approval",
3970
+ schema: ApprovalSchema,
3971
+ title: "Basou Approval",
3972
+ description: "A `.basou/approvals/{pending,resolved}/<id>.yaml` approval record."
3973
+ },
3974
+ {
3975
+ name: "status",
3976
+ schema: StatusSchema,
3977
+ title: "Basou Status",
3978
+ description: "The `.basou/status.json` workspace status snapshot."
3979
+ },
3980
+ {
3981
+ name: "task-index",
3982
+ schema: TaskIndexSchema,
3983
+ title: "Basou Task Index",
3984
+ description: "The `.basou/tasks/index.json` task lookup index."
3985
+ },
3986
+ {
3987
+ name: "session-import",
3988
+ schema: SessionImportPayloadSchema,
3989
+ title: "Basou Session Import Payload",
3990
+ description: "The portable session payload consumed by `basou session import` (and produced by `basou session export`)."
3991
+ }
3992
+ ];
3993
+ function buildJsonSchemas() {
3994
+ return DOCUMENTS.map((doc) => {
3995
+ const generated = z11.toJSONSchema(doc.schema, { io: "input" });
3996
+ const { $schema, ...rest } = generated;
3997
+ const schema = {
3998
+ $schema: typeof $schema === "string" ? $schema : JSON_SCHEMA_DIALECT,
3999
+ $id: `${ID_BASE}/${doc.name}.schema.json`,
4000
+ title: doc.title,
4001
+ description: doc.description,
4002
+ ...rest
4003
+ };
4004
+ return { name: doc.name, schema };
4005
+ });
4006
+ }
4007
+ function serializeJsonSchema(schema) {
4008
+ return `${JSON.stringify(schema, null, 2)}
4009
+ `;
4010
+ }
4011
+
3877
4012
  // src/stats/work-stats.ts
3878
4013
  import { join as join11 } from "path";
3879
4014
  function resolveTimeZone(timeZone) {
@@ -3962,6 +4097,7 @@ function sessionWorkStatsFromEvents(sessionId, inner, events, now, eventsUnreada
3962
4097
  const span = computeSpan(inner.started_at, inner.ended_at, now);
3963
4098
  const tokens = readTokens(inner.metrics);
3964
4099
  const active = resolveActiveTime(inner.metrics, timestamps);
4100
+ const machineActiveTimeMs = inner.metrics?.machine_active_time_ms ?? 0;
3965
4101
  return {
3966
4102
  sessionId,
3967
4103
  label: inner.label,
@@ -3975,6 +4111,8 @@ function sessionWorkStatsFromEvents(sessionId, inner, events, now, eventsUnreada
3975
4111
  activeTimeMs: active.ms,
3976
4112
  activeTimeBasis: active.basis,
3977
4113
  activeIntervals: intervalsMsToIso(active.intervals),
4114
+ machineActiveTimeMs,
4115
+ activeTimeMethod: inner.metrics?.active_time_method,
3978
4116
  commandCount,
3979
4117
  fileChangedCount,
3980
4118
  decisionCount,
@@ -3984,7 +4122,8 @@ function sessionWorkStatsFromEvents(sessionId, inner, events, now, eventsUnreada
3984
4122
  span: true,
3985
4123
  commandTime: inner.source.kind !== "claude-code-import",
3986
4124
  activeTime: active.intervals.length > 0,
3987
- tokens: hasTokens(tokens)
4125
+ tokens: hasTokens(tokens),
4126
+ machineActive: machineActiveTimeMs > 0
3988
4127
  },
3989
4128
  spanClamped: span.clamped,
3990
4129
  eventsUnreadable
@@ -4036,19 +4175,22 @@ function computeTotals(sessions, billableActiveTimeMs) {
4036
4175
  commandTimeMs: 0,
4037
4176
  activeTimeMs: 0,
4038
4177
  billableActiveTimeMs,
4178
+ machineActiveTimeMs: 0,
4039
4179
  commandCount: 0,
4040
4180
  fileChangedCount: 0,
4041
4181
  decisionCount: 0,
4042
4182
  eventCount: 0,
4043
4183
  tokens,
4044
4184
  commandTimeReliable: true,
4045
- tokensAvailable: false
4185
+ tokensAvailable: false,
4186
+ machineActiveAvailable: false
4046
4187
  };
4047
4188
  for (const s of sessions) {
4048
4189
  if (s.open) totals.openSessionCount++;
4049
4190
  totals.sessionSpanMs += s.sessionSpanMs;
4050
4191
  totals.commandTimeMs += s.commandTimeMs;
4051
4192
  totals.activeTimeMs += s.activeTimeMs;
4193
+ totals.machineActiveTimeMs += s.machineActiveTimeMs;
4052
4194
  totals.commandCount += s.commandCount;
4053
4195
  totals.fileChangedCount += s.fileChangedCount;
4054
4196
  totals.decisionCount += s.decisionCount;
@@ -4056,6 +4198,7 @@ function computeTotals(sessions, billableActiveTimeMs) {
4056
4198
  addTokens(tokens, s.tokens);
4057
4199
  if (!s.availability.commandTime) totals.commandTimeReliable = false;
4058
4200
  if (s.availability.tokens) totals.tokensAvailable = true;
4201
+ if (s.availability.machineActive) totals.machineActiveAvailable = true;
4059
4202
  }
4060
4203
  return totals;
4061
4204
  }
@@ -4070,13 +4213,15 @@ function computeBySource(sessions) {
4070
4213
  sessionSpanMs: 0,
4071
4214
  commandTimeMs: 0,
4072
4215
  activeTimeMs: 0,
4216
+ machineActiveTimeMs: 0,
4073
4217
  commandCount: 0,
4074
4218
  fileChangedCount: 0,
4075
4219
  decisionCount: 0,
4076
4220
  eventCount: 0,
4077
4221
  tokens: emptyTokens(),
4078
4222
  commandTimeReliable: true,
4079
- tokensAvailable: false
4223
+ tokensAvailable: false,
4224
+ machineActiveAvailable: false
4080
4225
  };
4081
4226
  map.set(s.sourceKind, row);
4082
4227
  }
@@ -4084,6 +4229,7 @@ function computeBySource(sessions) {
4084
4229
  row.sessionSpanMs += s.sessionSpanMs;
4085
4230
  row.commandTimeMs += s.commandTimeMs;
4086
4231
  row.activeTimeMs += s.activeTimeMs;
4232
+ row.machineActiveTimeMs += s.machineActiveTimeMs;
4087
4233
  row.commandCount += s.commandCount;
4088
4234
  row.fileChangedCount += s.fileChangedCount;
4089
4235
  row.decisionCount += s.decisionCount;
@@ -4091,6 +4237,7 @@ function computeBySource(sessions) {
4091
4237
  addTokens(row.tokens, s.tokens);
4092
4238
  if (!s.availability.commandTime) row.commandTimeReliable = false;
4093
4239
  if (s.availability.tokens) row.tokensAvailable = true;
4240
+ if (s.availability.machineActive) row.machineActiveAvailable = true;
4094
4241
  }
4095
4242
  return [...map.values()].sort((a, b) => a.sourceKind.localeCompare(b.sourceKind));
4096
4243
  }
@@ -4112,6 +4259,7 @@ function computeByDay(sessions, unionMerged, timeZone) {
4112
4259
  day = {
4113
4260
  date,
4114
4261
  billableActiveTimeMs: 0,
4262
+ machineActiveTimeMs: 0,
4115
4263
  sessionCount: 0,
4116
4264
  commandCount: 0,
4117
4265
  fileChangedCount: 0,
@@ -4130,6 +4278,7 @@ function computeByDay(sessions, unionMerged, timeZone) {
4130
4278
  if (!Number.isFinite(startedMs)) continue;
4131
4279
  const day = ensure(tzDate(startedMs, timeZone));
4132
4280
  day.sessionCount++;
4281
+ day.machineActiveTimeMs += s.machineActiveTimeMs;
4133
4282
  day.commandCount += s.commandCount;
4134
4283
  day.fileChangedCount += s.fileChangedCount;
4135
4284
  day.decisionCount += s.decisionCount;
@@ -4594,6 +4743,7 @@ export {
4594
4743
  GENERATED_START,
4595
4744
  ID_PREFIXES,
4596
4745
  IsoTimestampSchema,
4746
+ JSON_SCHEMA_VERSION,
4597
4747
  ManifestSchema,
4598
4748
  RiskLevelSchema,
4599
4749
  STUCK_THRESHOLD_MS,
@@ -4618,6 +4768,7 @@ export {
4618
4768
  archiveTask,
4619
4769
  assertBasouRootSafe,
4620
4770
  basouPaths,
4771
+ buildJsonSchemas,
4621
4772
  buildStatusSnapshot,
4622
4773
  classifySuspect,
4623
4774
  claudeCodeAdapterMetadata,
@@ -4670,6 +4821,7 @@ export {
4670
4821
  sanitizePath,
4671
4822
  sanitizeRelatedFiles,
4672
4823
  sanitizeWorkingDirectory,
4824
+ serializeJsonSchema,
4673
4825
  sessionWorkStatsFromEvents,
4674
4826
  summarizeAdapterOutput,
4675
4827
  tryRemoteUrl,