sidekick-shared 0.28.3 → 0.29.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +188 -27
  3. package/dist/aggregation/EventAggregator.d.ts +19 -0
  4. package/dist/aggregation/EventAggregator.js +174 -35
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +13 -0
  8. package/dist/authStatus.d.ts +54 -0
  9. package/dist/authStatus.js +91 -0
  10. package/dist/browser.d.ts +16 -6
  11. package/dist/browser.js +24 -2
  12. package/dist/codex.d.ts +23 -0
  13. package/dist/codex.js +44 -0
  14. package/dist/codexProfiles.d.ts +1 -0
  15. package/dist/codexProfiles.js +8 -11
  16. package/dist/context/sessionContext.js +4 -0
  17. package/dist/esm/aggregation/EventAggregator.js +174 -35
  18. package/dist/esm/aggregation/snapshot.js +31 -9
  19. package/dist/esm/authStatus.js +87 -0
  20. package/dist/esm/browser.js +5 -2
  21. package/dist/esm/codex.js +18 -0
  22. package/dist/esm/codexProfiles.js +8 -11
  23. package/dist/esm/context/sessionContext.js +4 -0
  24. package/dist/esm/formatters/toolSummary.js +4 -1
  25. package/dist/esm/formatting.js +10 -1
  26. package/dist/esm/index.js +11 -6
  27. package/dist/esm/modelCatalog.js +7 -5
  28. package/dist/esm/modelContext.js +4 -3
  29. package/dist/esm/modelInfo.js +425 -47
  30. package/dist/esm/node.js +9 -3
  31. package/dist/esm/observedContextWindows.js +2 -1
  32. package/dist/esm/ownKey.js +26 -0
  33. package/dist/esm/parsers/codexParser.js +351 -54
  34. package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
  35. package/dist/esm/parsers/openCodeParser.js +85 -22
  36. package/dist/esm/parsers/subagentScanner.js +33 -12
  37. package/dist/esm/parsers/subagentTraceParser.js +97 -37
  38. package/dist/esm/pricing.js +18 -0
  39. package/dist/esm/pricingCatalog.js +17 -9
  40. package/dist/esm/promptHistory.js +66 -4
  41. package/dist/esm/providerFailure.js +124 -3
  42. package/dist/esm/providers/claudeCode.js +10 -2
  43. package/dist/esm/providers/codex.js +84 -62
  44. package/dist/esm/providers/codexDatabase.js +3 -19
  45. package/dist/esm/providers/codexRollouts.js +24 -1
  46. package/dist/esm/providers/openCode.js +9 -17
  47. package/dist/esm/report/htmlReportGenerator.js +42 -5
  48. package/dist/esm/report/transcriptParser.js +13 -11
  49. package/dist/esm/schemas/providerFailure.js +3 -1
  50. package/dist/esm/schemas/sessionEvent.js +35 -14
  51. package/dist/esm/schemas/transcript.js +4 -0
  52. package/dist/esm/sessionPromptHistory.js +2 -2
  53. package/dist/esm/statusline/payload.js +9 -0
  54. package/dist/esm/subagentSpawn.js +163 -0
  55. package/dist/esm/testing/credentialGuard.js +87 -5
  56. package/dist/esm/toolCallOrigins.js +310 -0
  57. package/dist/esm/transcript.js +46 -3
  58. package/dist/esm/turns/assistantTurn.js +3 -2
  59. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  60. package/dist/esm/usage/usageEvents.js +146 -17
  61. package/dist/esm/usage/usageReports.js +39 -6
  62. package/dist/esm/watchers/eventBridge.js +15 -0
  63. package/dist/esm/watchers/factory.js +5 -3
  64. package/dist/esm/watchers/jsonlTail.js +442 -58
  65. package/dist/esm/watchers/jsonlWatcher.js +51 -4
  66. package/dist/esm/watchers/providerReaderWatcher.js +13 -0
  67. package/dist/esm/zaiQuota.js +2 -1
  68. package/dist/formatters/toolSummary.js +4 -1
  69. package/dist/formatting.d.ts +26 -1
  70. package/dist/formatting.js +11 -1
  71. package/dist/index.d.ts +22 -11
  72. package/dist/index.js +44 -12
  73. package/dist/modelCatalog.js +7 -5
  74. package/dist/modelContext.js +4 -3
  75. package/dist/modelInfo.d.ts +185 -16
  76. package/dist/modelInfo.js +434 -49
  77. package/dist/node.d.ts +16 -5
  78. package/dist/node.js +33 -2
  79. package/dist/observedContextWindows.js +2 -1
  80. package/dist/ownKey.d.ts +16 -0
  81. package/dist/ownKey.js +30 -0
  82. package/dist/parsers/codexParser.d.ts +118 -0
  83. package/dist/parsers/codexParser.js +352 -54
  84. package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
  85. package/dist/parsers/codexRolloutOrigin.js +111 -0
  86. package/dist/parsers/openCodeParser.d.ts +38 -0
  87. package/dist/parsers/openCodeParser.js +88 -22
  88. package/dist/parsers/subagentScanner.d.ts +19 -6
  89. package/dist/parsers/subagentScanner.js +33 -12
  90. package/dist/parsers/subagentTraceParser.d.ts +25 -5
  91. package/dist/parsers/subagentTraceParser.js +97 -37
  92. package/dist/pricing.d.ts +22 -0
  93. package/dist/pricing.js +50 -0
  94. package/dist/pricingCatalog.d.ts +10 -0
  95. package/dist/pricingCatalog.js +16 -8
  96. package/dist/promptHistory.d.ts +65 -4
  97. package/dist/promptHistory.js +68 -5
  98. package/dist/providerFailure.d.ts +44 -0
  99. package/dist/providerFailure.js +126 -4
  100. package/dist/providers/claudeCode.js +9 -1
  101. package/dist/providers/codex.d.ts +3 -22
  102. package/dist/providers/codex.js +85 -64
  103. package/dist/providers/codexDatabase.d.ts +3 -2
  104. package/dist/providers/codexDatabase.js +4 -22
  105. package/dist/providers/codexRollouts.d.ts +22 -0
  106. package/dist/providers/codexRollouts.js +24 -0
  107. package/dist/providers/openCode.js +8 -16
  108. package/dist/providers/types.d.ts +14 -0
  109. package/dist/report/htmlReportGenerator.js +42 -5
  110. package/dist/report/transcriptParser.d.ts +10 -1
  111. package/dist/report/transcriptParser.js +13 -11
  112. package/dist/schemas/index.d.ts +1 -0
  113. package/dist/schemas/providerFailure.d.ts +39 -0
  114. package/dist/schemas/providerFailure.js +2 -0
  115. package/dist/schemas/sessionEvent.d.ts +27 -10
  116. package/dist/schemas/sessionEvent.js +35 -14
  117. package/dist/schemas/transcript.d.ts +2 -0
  118. package/dist/schemas/transcript.js +4 -0
  119. package/dist/sessionPromptHistory.d.ts +7 -1
  120. package/dist/sessionPromptHistory.js +1 -1
  121. package/dist/statusline/payload.d.ts +10 -0
  122. package/dist/statusline/payload.js +14 -0
  123. package/dist/subagentSpawn.d.ts +80 -0
  124. package/dist/subagentSpawn.js +174 -0
  125. package/dist/testing/credentialGuard.d.ts +14 -0
  126. package/dist/testing/credentialGuard.js +87 -5
  127. package/dist/toolCallOrigins.d.ts +70 -0
  128. package/dist/toolCallOrigins.js +346 -0
  129. package/dist/transcript.d.ts +12 -0
  130. package/dist/transcript.js +46 -3
  131. package/dist/turns/assistantTurn.d.ts +1 -0
  132. package/dist/turns/assistantTurn.js +3 -2
  133. package/dist/types/opencode.d.ts +2 -0
  134. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  135. package/dist/usage/claudeUsageDedupe.js +68 -0
  136. package/dist/usage/usageEvents.d.ts +54 -6
  137. package/dist/usage/usageEvents.js +146 -17
  138. package/dist/usage/usageReports.d.ts +35 -4
  139. package/dist/usage/usageReports.js +39 -6
  140. package/dist/watchers/eventBridge.js +15 -0
  141. package/dist/watchers/factory.d.ts +6 -0
  142. package/dist/watchers/factory.js +5 -3
  143. package/dist/watchers/jsonlTail.d.ts +124 -4
  144. package/dist/watchers/jsonlTail.js +446 -59
  145. package/dist/watchers/jsonlWatcher.d.ts +18 -1
  146. package/dist/watchers/jsonlWatcher.js +50 -3
  147. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  148. package/dist/watchers/providerReaderWatcher.js +13 -0
  149. package/dist/watchers/types.d.ts +12 -0
  150. package/dist/zaiQuota.js +2 -1
  151. package/package.json +41 -1
@@ -15,6 +15,10 @@
15
15
  * record over `maxRecordBytes` is skipped without being buffered and reported
16
16
  * in `exclusions`, the only way a record is permanently left out.
17
17
  *
18
+ * `includeText: false` returns the same entries, ordinals, timestamps,
19
+ * metadata, exclusions, and cursor without prompt text, for consumers that
20
+ * only count or attribute prompts. Cursors are interchangeable between modes.
21
+ *
18
22
  * A prompt can be written before its answer. The cursor then keeps the byte
19
23
  * offset of that prompt's line and its answer-tracking state (never text), and
20
24
  * the prompt is returned again under the same identity once its first
@@ -54,8 +58,9 @@ var __importStar = (this && this.__importStar) || (function () {
54
58
  };
55
59
  })();
56
60
  Object.defineProperty(exports, "__esModule", { value: true });
57
- exports.__promptHistoryTesting = exports.DEFAULT_BOUNDS = void 0;
61
+ exports.__promptHistoryTesting = exports.DEFAULT_BOUNDS = exports.PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES = void 0;
58
62
  exports.collectPromptHistory = collectPromptHistory;
63
+ exports.readPromptOrdinalMarks = readPromptOrdinalMarks;
59
64
  exports.emptyStats = emptyStats;
60
65
  exports.isPromptHistorySessionSettled = isPromptHistorySessionSettled;
61
66
  exports.parsePromptHistoryCursor = parsePromptHistoryCursor;
@@ -72,12 +77,19 @@ const codexRollouts_1 = require("./providers/codexRollouts");
72
77
  const directoryListingCache_1 = require("./providers/directoryListingCache");
73
78
  const workspaceRoots_1 = require("./workspaceRoots");
74
79
  const fileHead_1 = require("./fileHead");
80
+ /**
81
+ * The default `maxRecordBytes` (16 MiB). Prompt ordinals are only comparable
82
+ * between readers that skip the same records, so every reader that publishes
83
+ * or joins on ordinals (prompt history, session prompt history, usage events'
84
+ * `promptOrdinal`) uses this bound.
85
+ */
86
+ exports.PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES = 16 * 1024 * 1024;
75
87
  /** @internal */
76
88
  exports.DEFAULT_BOUNDS = {
77
89
  maxSessions: 1000,
78
90
  maxFileBytes: 16 * 1024 * 1024,
79
91
  maxTotalBytes: 256 * 1024 * 1024,
80
- maxRecordBytes: 16 * 1024 * 1024,
92
+ maxRecordBytes: exports.PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES,
81
93
  maxEntries: 10_000,
82
94
  deadlineMs: 30_000,
83
95
  };
@@ -152,6 +164,7 @@ async function collectPromptHistory(options) {
152
164
  if (exclusions.length < MAX_EXCLUSIONS)
153
165
  exclusions.push(exclusion);
154
166
  },
167
+ includeText: options.includeText !== false,
155
168
  };
156
169
  let totalBytes = 0;
157
170
  let scannedAny = false;
@@ -265,6 +278,46 @@ async function collectPromptHistory(options) {
265
278
  }
266
279
  return finish();
267
280
  }
281
+ /**
282
+ * Every human prompt of one session file with its ordinal and timestamp, read
283
+ * whole in one pass with the default record bound, so the ordinals are the
284
+ * ones `collectPromptHistory()` returns with default bounds. No scope check
285
+ * and no text: prompts are counted wherever their cwd points. Prompts whose
286
+ * line has no usable timestamp still take an ordinal but are left out. A
287
+ * non-interactive Codex session (subagent, fork, unknown source) has none.
288
+ * In file order. Rejects when the file cannot be opened or changes while its
289
+ * head is read.
290
+ * @internal
291
+ */
292
+ async function readPromptOrdinalMarks(provider, sessionId, filePath) {
293
+ const marks = [];
294
+ const stat = await fs.promises.stat(filePath);
295
+ const unbounded = Number.MAX_SAFE_INTEGER;
296
+ const context = {
297
+ roots: [],
298
+ sinceMs: null,
299
+ stats: emptyStats(),
300
+ realpaths: new Map(),
301
+ bounds: {
302
+ maxSessions: unbounded,
303
+ maxFileBytes: unbounded,
304
+ maxTotalBytes: unbounded,
305
+ maxRecordBytes: exports.PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES,
306
+ maxEntries: unbounded,
307
+ deadlineMs: unbounded,
308
+ },
309
+ shouldStop: () => null,
310
+ entriesSoFar: () => 0,
311
+ exclude: () => undefined,
312
+ includeText: false,
313
+ onPrompt: ({ ordinal, timestamp }) => {
314
+ if (timestamp !== undefined)
315
+ marks.push({ ordinal, timestampMs: Date.parse(timestamp) });
316
+ },
317
+ };
318
+ await scanFile({ provider, sessionId, filePath, key: `${provider}:${sessionId}:${hashPath(filePath)}` }, stat, undefined, { bytes: unbounded, bound: 'maxFileBytes', forceProgress: true }, context);
319
+ return marks;
320
+ }
268
321
  /** @internal */
269
322
  function emptyStats() {
270
323
  return {
@@ -943,7 +996,7 @@ class LineScanner {
943
996
  return {
944
997
  entry: {
945
998
  ...entry,
946
- text,
999
+ ...this.textFields(text),
947
1000
  cwd: scoped,
948
1001
  ...(place.gitBranch ? { gitBranch: place.gitBranch } : {}),
949
1002
  ...(entry.usage ? { usage: { ...entry.usage } } : {}),
@@ -962,11 +1015,21 @@ class LineScanner {
962
1015
  this.closedCarried = open;
963
1016
  this.open = null;
964
1017
  }
1018
+ /** Entry text fields: the text, or an empty string flagged as omitted. */
1019
+ textFields(text) {
1020
+ return this.context.includeText === false ? { text: '', textOmitted: true } : { text };
1021
+ }
965
1022
  /** Assign an ordinal to a human prompt and emit it when in scope. */
966
1023
  recordPrompt(text, timestamp, cwd, gitBranch, lineOffset, lineBytes, stateBefore) {
967
1024
  const ordinal = this.nextOrdinal++;
968
1025
  this.closeOpen();
969
- if (typeof timestamp !== 'string' || Number.isNaN(Date.parse(timestamp))) {
1026
+ const usable = typeof timestamp === 'string' && !Number.isNaN(Date.parse(timestamp));
1027
+ this.context.onPrompt?.({
1028
+ ordinal,
1029
+ timestamp: usable ? timestamp : undefined,
1030
+ lineOffset,
1031
+ });
1032
+ if (!usable) {
970
1033
  this.context.stats.promptsMissingTimestamp++;
971
1034
  return;
972
1035
  }
@@ -984,7 +1047,7 @@ class LineScanner {
984
1047
  sessionId: this.candidate.sessionId,
985
1048
  ordinal,
986
1049
  timestamp,
987
- text,
1050
+ ...this.textFields(text),
988
1051
  cwd: scoped,
989
1052
  ...(gitBranch ? { gitBranch } : {}),
990
1053
  metadataStatus: 'pending',
@@ -29,10 +29,26 @@ export type ProviderRetryAfter = {
29
29
  at: string;
30
30
  };
31
31
  export declare const PROVIDER_FAILURE_EVIDENCE_SOURCES: readonly ["structured-error", "http-status", "message", "authentication-check"];
32
+ /**
33
+ * The variants Codex reports in `codexErrorInfo` on `error` and failed-turn
34
+ * payloads (app-server camelCase spelling; the snake_case spelling Codex
35
+ * writes to rollout files is accepted too). `rateLimitExceeded` is an older
36
+ * spelling some Codex builds emitted.
37
+ */
38
+ export declare const CODEX_ERROR_INFO_KINDS: readonly ["contextWindowExceeded", "sessionBudgetExceeded", "usageLimitExceeded", "rateLimitExceeded", "serverOverloaded", "cyberPolicy", "httpConnectionFailed", "responseStreamConnectionFailed", "internalServerError", "unauthorized", "badRequest", "threadRollbackFailed", "sandboxError", "responseStreamDisconnected", "responseTooManyFailedAttempts", "activeTurnNotSteerable", "other"];
39
+ export type CodexErrorInfoKind = (typeof CODEX_ERROR_INFO_KINDS)[number];
40
+ /** A decoded `codexErrorInfo`: its closed variant and any upstream HTTP status. */
41
+ export interface CodexErrorInfoSummary {
42
+ kind: CodexErrorInfoKind;
43
+ /** Present when a transport variant carried a valid `httpStatusCode`. */
44
+ httpStatusCode?: number;
45
+ }
32
46
  export interface ProviderFailureEvidence {
33
47
  source: (typeof PROVIDER_FAILURE_EVIDENCE_SOURCES)[number];
34
48
  rule: ProviderFailureCode;
35
49
  checkedAt?: string;
50
+ /** Present only when a Codex `codexErrorInfo` decided the rule. */
51
+ codexErrorInfo?: CodexErrorInfoKind;
36
52
  }
37
53
  /** Safe diagnostic projection. No raw error text, request data, or credentials. */
38
54
  export interface ProviderFailureDiagnosis {
@@ -40,13 +56,41 @@ export interface ProviderFailureDiagnosis {
40
56
  credentialKind: ProviderCredentialKind;
41
57
  diagnosis: ProviderFailureCode;
42
58
  recovery: ProviderRecoveryAction;
59
+ /**
60
+ * Whether sending the same request again, unchanged and after any
61
+ * `retryAfter`, can reasonably succeed. True for a rate limit that will
62
+ * lift, overload, an unavailable service (5xx), a timeout, or a failed
63
+ * connection. False for authentication, missing runtime or configuration,
64
+ * execution policy, an invalid provider session, context overflow, a usage
65
+ * quota exhausted with no known reset, and unknown failures. Conservative:
66
+ * a hint for retry policy, not a promise. The caller still decides whether
67
+ * a repeat is safe (idempotency, output already delivered).
68
+ *
69
+ * Always set by `diagnoseProviderFailure()` since 0.29.0; optional in the
70
+ * type so diagnoses stored or built by older producers stay valid.
71
+ */
72
+ retryable?: boolean;
43
73
  evidence: ProviderFailureEvidence[];
44
74
  authentication?: ProviderAuthenticationEvidence;
45
75
  httpStatus?: number;
46
76
  retryAfter?: ProviderRetryAfter;
47
77
  }
78
+ /**
79
+ * Decode one Codex `codexErrorInfo` value: a string variant
80
+ * (`"serverOverloaded"`, `"usage_limit_exceeded"`) or an object variant
81
+ * (`{ "httpConnectionFailed": { "httpStatusCode": 503 } }`, snake_case keys
82
+ * accepted). Returns `undefined` for anything else, including `null`.
83
+ */
84
+ export declare function decodeCodexErrorInfo(value: unknown): CodexErrorInfoSummary | undefined;
48
85
  /**
49
86
  * Classify a caller-designated terminal failure without I/O or recovery actions.
50
87
  * Public status and runtime readiness do not establish request authentication.
88
+ *
89
+ * A Codex `codexErrorInfo` (or `codex_error_info`) on the error or on any
90
+ * nested `error` / `cause` / `response` / `data` / `body` / `errors` entry is
91
+ * structured evidence. It ranks ahead of the same object's `code` / `type` /
92
+ * `name`, objects are read outer first, and every structured code ranks ahead
93
+ * of HTTP status and message text. Its transport variants also supply
94
+ * `httpStatus`.
51
95
  */
52
96
  export declare function diagnoseProviderFailure(input: ProviderFailureInput): ProviderFailureDiagnosis;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PROVIDER_FAILURE_EVIDENCE_SOURCES = exports.PROVIDER_RECOVERY_ACTIONS = exports.PROVIDER_FAILURE_CODES = exports.PROVIDER_AUTHENTICATION_SOURCES = exports.PROVIDER_AUTHENTICATION_STATES = exports.PROVIDER_CREDENTIAL_KINDS = void 0;
3
+ exports.CODEX_ERROR_INFO_KINDS = exports.PROVIDER_FAILURE_EVIDENCE_SOURCES = exports.PROVIDER_RECOVERY_ACTIONS = exports.PROVIDER_FAILURE_CODES = exports.PROVIDER_AUTHENTICATION_SOURCES = exports.PROVIDER_AUTHENTICATION_STATES = exports.PROVIDER_CREDENTIAL_KINDS = void 0;
4
+ exports.decodeCodexErrorInfo = decodeCodexErrorInfo;
4
5
  exports.diagnoseProviderFailure = diagnoseProviderFailure;
5
6
  exports.PROVIDER_CREDENTIAL_KINDS = ['oauth', 'api-key', 'unknown'];
6
7
  exports.PROVIDER_AUTHENTICATION_STATES = [
@@ -46,6 +47,31 @@ exports.PROVIDER_FAILURE_EVIDENCE_SOURCES = [
46
47
  'message',
47
48
  'authentication-check',
48
49
  ];
50
+ /**
51
+ * The variants Codex reports in `codexErrorInfo` on `error` and failed-turn
52
+ * payloads (app-server camelCase spelling; the snake_case spelling Codex
53
+ * writes to rollout files is accepted too). `rateLimitExceeded` is an older
54
+ * spelling some Codex builds emitted.
55
+ */
56
+ exports.CODEX_ERROR_INFO_KINDS = [
57
+ 'contextWindowExceeded',
58
+ 'sessionBudgetExceeded',
59
+ 'usageLimitExceeded',
60
+ 'rateLimitExceeded',
61
+ 'serverOverloaded',
62
+ 'cyberPolicy',
63
+ 'httpConnectionFailed',
64
+ 'responseStreamConnectionFailed',
65
+ 'internalServerError',
66
+ 'unauthorized',
67
+ 'badRequest',
68
+ 'threadRollbackFailed',
69
+ 'sandboxError',
70
+ 'responseStreamDisconnected',
71
+ 'responseTooManyFailedAttempts',
72
+ 'activeTurnNotSteerable',
73
+ 'other',
74
+ ];
49
75
  function read(value, key) {
50
76
  try {
51
77
  return value && typeof value === 'object' ? value[key] : undefined;
@@ -81,6 +107,40 @@ function retryAfter(value) {
81
107
  const at = timestamp(value);
82
108
  return at ? { kind: 'date', at } : undefined;
83
109
  }
110
+ function compactKey(value) {
111
+ return value.toLowerCase().replace(/[^a-z0-9]/g, '');
112
+ }
113
+ const CODEX_ERROR_INFO_BY_KEY = new Map(exports.CODEX_ERROR_INFO_KINDS.map((kind) => [compactKey(kind), kind]));
114
+ /**
115
+ * Decode one Codex `codexErrorInfo` value: a string variant
116
+ * (`"serverOverloaded"`, `"usage_limit_exceeded"`) or an object variant
117
+ * (`{ "httpConnectionFailed": { "httpStatusCode": 503 } }`, snake_case keys
118
+ * accepted). Returns `undefined` for anything else, including `null`.
119
+ */
120
+ function decodeCodexErrorInfo(value) {
121
+ if (typeof value === 'string') {
122
+ const kind = value.length <= 64 ? CODEX_ERROR_INFO_BY_KEY.get(compactKey(value)) : undefined;
123
+ return kind ? { kind } : undefined;
124
+ }
125
+ if (!value || typeof value !== 'object' || Array.isArray(value))
126
+ return undefined;
127
+ let keys;
128
+ try {
129
+ keys = Object.keys(value);
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ for (const key of keys.slice(0, 8)) {
135
+ const kind = key.length <= 64 ? CODEX_ERROR_INFO_BY_KEY.get(compactKey(key)) : undefined;
136
+ if (!kind)
137
+ continue;
138
+ const payload = read(value, key);
139
+ const status = statusNumber(read(payload, 'httpStatusCode') ?? read(payload, 'http_status_code'));
140
+ return status === undefined ? { kind } : { kind, httpStatusCode: status };
141
+ }
142
+ return undefined;
143
+ }
84
144
  function rejected(kind) {
85
145
  return kind === 'oauth'
86
146
  ? 'oauth_reauthentication_required'
@@ -171,6 +231,44 @@ function classifyStatus(status, kind) {
171
231
  return 'service_unavailable';
172
232
  return undefined;
173
233
  }
234
+ /** Variants with no matching failure code (bad request, policy, budget, other) stay unclassified. */
235
+ function classifyCodexErrorInfo(info, kind) {
236
+ switch (info.kind) {
237
+ case 'usageLimitExceeded':
238
+ case 'rateLimitExceeded':
239
+ return 'rate_limited';
240
+ case 'serverOverloaded':
241
+ return 'overloaded';
242
+ case 'internalServerError':
243
+ return 'service_unavailable';
244
+ case 'unauthorized':
245
+ return rejected(kind);
246
+ case 'contextWindowExceeded':
247
+ return 'context_overflow';
248
+ case 'httpConnectionFailed':
249
+ case 'responseStreamConnectionFailed':
250
+ case 'responseStreamDisconnected':
251
+ case 'responseTooManyFailedAttempts':
252
+ return info.httpStatusCode === undefined
253
+ ? 'connection_failed'
254
+ : classifyStatus(info.httpStatusCode, kind);
255
+ default:
256
+ return undefined;
257
+ }
258
+ }
259
+ function isRetryable(code, quotaExhausted, retry) {
260
+ switch (code) {
261
+ case 'rate_limited':
262
+ return !quotaExhausted || retry !== undefined;
263
+ case 'overloaded':
264
+ case 'service_unavailable':
265
+ case 'timeout':
266
+ case 'connection_failed':
267
+ return true;
268
+ default:
269
+ return false;
270
+ }
271
+ }
174
272
  function classifyMessage(message, kind) {
175
273
  if (/\b(execution policy|sandbox policy|approval)\b.{0,60}\b(denied|blocked|rejected|not allowed)\b|\bdenied by policy\b/i.test(message))
176
274
  return 'execution_policy_denied';
@@ -236,6 +334,13 @@ function recovery(code, kind) {
236
334
  /**
237
335
  * Classify a caller-designated terminal failure without I/O or recovery actions.
238
336
  * Public status and runtime readiness do not establish request authentication.
337
+ *
338
+ * A Codex `codexErrorInfo` (or `codex_error_info`) on the error or on any
339
+ * nested `error` / `cause` / `response` / `data` / `body` / `errors` entry is
340
+ * structured evidence. It ranks ahead of the same object's `code` / `type` /
341
+ * `name`, objects are read outer first, and every structured code ranks ahead
342
+ * of HTTP status and message text. Its transport variants also supply
343
+ * `httpStatus`.
239
344
  */
240
345
  function diagnoseProviderFailure(input) {
241
346
  const codes = [];
@@ -253,11 +358,15 @@ function diagnoseProviderFailure(input) {
253
358
  if (!value || typeof value !== 'object' || visited.has(value))
254
359
  continue;
255
360
  visited.add(value);
361
+ const codex = decodeCodexErrorInfo(read(value, 'codexErrorInfo') ?? read(value, 'codex_error_info'));
362
+ if (codex)
363
+ codes.push(codex);
256
364
  for (const key of ['code', 'type', 'name'])
257
365
  codes.push(text(read(value, key)));
258
366
  messages.push(text(read(value, 'message')));
259
367
  for (const key of ['status', 'statusCode', 'httpStatus', 'status_code'])
260
368
  httpStatus ??= statusNumber(read(value, key));
369
+ httpStatus ??= codex?.httpStatusCode;
261
370
  const headers = read(value, 'headers');
262
371
  const header = typeof Headers !== 'undefined' && headers instanceof Headers
263
372
  ? headers.get('retry-after')
@@ -310,9 +419,20 @@ function diagnoseProviderFailure(input) {
310
419
  source: auth.source,
311
420
  };
312
421
  }
313
- let diagnosis = codes
314
- .map((code) => classifyCode(code, input.credentialKind))
315
- .find((code) => code !== undefined);
422
+ let diagnosis;
423
+ let codexErrorInfo;
424
+ for (const code of codes) {
425
+ if (typeof code === 'string') {
426
+ diagnosis = classifyCode(code, input.credentialKind);
427
+ }
428
+ else {
429
+ diagnosis = classifyCodexErrorInfo(code, input.credentialKind);
430
+ if (diagnosis)
431
+ codexErrorInfo = code.kind;
432
+ }
433
+ if (diagnosis)
434
+ break;
435
+ }
316
436
  let source = 'structured-error';
317
437
  if (!diagnosis && httpStatus !== undefined) {
318
438
  diagnosis = classifyStatus(httpStatus, input.credentialKind);
@@ -348,6 +468,7 @@ function diagnoseProviderFailure(input) {
348
468
  credentialKind: input.credentialKind,
349
469
  diagnosis,
350
470
  recovery: recovery(diagnosis, input.credentialKind),
471
+ retryable: isRetryable(diagnosis, codexErrorInfo === 'usageLimitExceeded', retry),
351
472
  evidence: diagnosis === 'unknown'
352
473
  ? []
353
474
  : [
@@ -357,6 +478,7 @@ function diagnoseProviderFailure(input) {
357
478
  ...(source === 'authentication-check'
358
479
  ? { checkedAt: authentication.checkedAt }
359
480
  : {}),
481
+ ...(codexErrorInfo ? { codexErrorInfo } : {}),
360
482
  },
361
483
  ],
362
484
  ...(authentication ? { authentication } : {}),
@@ -155,7 +155,8 @@ class ClaudeCodeReader {
155
155
  this.usageDeduper = usageDeduper ?? new claudeUsageDedupe_1.ClaudeUsageDeduper();
156
156
  this.parser = new jsonl_1.JsonlParser({
157
157
  onEvent: (raw) => {
158
- for (const event of (0, sessionEvent_1.extractSessionEvents)(raw)) {
158
+ // Mid-turn task notifications finish background subagents.
159
+ for (const event of (0, sessionEvent_1.extractSessionEvents)(raw, { includeTaskNotifications: true })) {
159
160
  this.events.push(this.usageDeduper.apply((0, claudeUsageDedupe_1.normalizeClaudeUsage)(event)));
160
161
  }
161
162
  },
@@ -244,6 +245,13 @@ class ClaudeCodeReader {
244
245
  wasTruncated() {
245
246
  return this._wasTruncated;
246
247
  }
248
+ /** The usage deduper's recent message ids, so a resumed reader does not recount a split response. */
249
+ getResumeState() {
250
+ return this.usageDeduper.exportState();
251
+ }
252
+ restoreResumeState(state) {
253
+ return this.usageDeduper.importState(state);
254
+ }
247
255
  }
248
256
  /**
249
257
  * Session provider for Claude Code CLI.
@@ -9,31 +9,12 @@
9
9
  * Ported from sidekick-vscode/src/services/providers/CodexSessionProvider.ts
10
10
  */
11
11
  import type { ReadSessionContextSnapshotOptions, SessionContextSnapshot } from '../context/sessionContext';
12
+ import { findCodexRolloutFile } from './codexRollouts';
12
13
  import type { ListSessionFilesOptions, SessionProviderBase, SessionReader, ProjectFolderInfo, SearchHit, SessionFileInfo, SessionFileStats, ProviderId, ProviderOperationStatus, ProviderRuntimeStatus, SessionProviderOptions, WatchedSessionFile } from './types';
13
14
  import type { SubagentStats, TokenUsage } from '../types/sessionEvent';
14
15
  import type { CodexRateLimits } from '../types/codex';
15
- export interface FindCodexRolloutFileOptions {
16
- /**
17
- * Codex home whose `sessions/` subtree to search. Defaults to every
18
- * monitored home: `$CODEX_HOME` if set, else `~/.codex`, plus managed
19
- * profile homes that recorded sessions.
20
- */
21
- codexHome?: string;
22
- }
23
- /**
24
- * Locates the rollout file for a Codex session id by walking
25
- * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
26
- * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
27
- * threads.
28
- *
29
- * Filename-only match, case-insensitive on the id — no file contents are read
30
- * and files are only stat'ed once their name matches, so the walk stays cheap
31
- * even over a large history. When the same id exists in more than one home,
32
- * the most recently modified copy wins. Returns `null` for an unknown, empty,
33
- * or whitespace id. Consumers who want the database's answer instead can query
34
- * the already-public `CodexDatabase` (requires the external `sqlite3` binary).
35
- */
36
- export declare function findCodexRolloutFile(sessionId: string, options?: FindCodexRolloutFileOptions): string | null;
16
+ export { findCodexRolloutFile };
17
+ export type { FindCodexRolloutFileOptions } from './codexRollouts';
37
18
  /**
38
19
  * Session provider for Codex CLI.
39
20
  *