maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909

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 (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -22,8 +22,12 @@ import { basename, join, resolve, sep } from 'node:path';
22
22
  import { isSupportedCodexThreadSource, sanitizeForeignTitle } from '@maka/core/foreign-session';
23
23
  import { externalSessionMatchesQuery } from '@maka/core/external-session';
24
24
  export const CODEX_SESSION_ADAPTER_ID = 'codex';
25
- export const CODEX_ROLLOUT_MAX_BYTES = 64 * 1024 * 1024;
25
+ export const CODEX_ROLLOUT_MAX_BYTES = 2 * 1024 * 1024 * 1024;
26
26
  const CODEX_ROLLOUT_HEAD_BYTES = 512 * 1024;
27
+ const CODEX_ROLLOUT_READ_BYTES = 64 * 1024;
28
+ const CODEX_ROLLOUT_MAX_RECORD_BYTES = 64 * 1024 * 1024;
29
+ const CODEX_ROLLOUT_MAX_CONVERTED_BYTES = 256 * 1024 * 1024;
30
+ const CODEX_ROLLOUT_MAX_MESSAGES = 250_000;
27
31
  const CODEX_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
28
32
  const CODEX_UNSAFE_PATH_CHARS = /[\u0000-\u001F\u007F\u0080-\u009F\u061C\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/;
29
33
  /**
@@ -40,12 +44,19 @@ export class CodexSessionAdapter {
40
44
  id = CODEX_SESSION_ADAPTER_ID;
41
45
  codexHome;
42
46
  maxRolloutBytes;
47
+ maxRecordBytes;
48
+ maxConvertedBytes;
49
+ maxMessages;
43
50
  constructor(options = {}) {
44
51
  this.codexHome = resolve(options.codexHome ?? process.env.CODEX_HOME ?? join(homedir(), '.codex'));
45
52
  this.maxRolloutBytes = options.maxRolloutBytes ?? CODEX_ROLLOUT_MAX_BYTES;
46
- if (!Number.isSafeInteger(this.maxRolloutBytes) || this.maxRolloutBytes <= 0) {
47
- throw new Error('Codex rollout byte limit must be a positive safe integer');
48
- }
53
+ this.maxRecordBytes = options.maxRecordBytes ?? CODEX_ROLLOUT_MAX_RECORD_BYTES;
54
+ this.maxConvertedBytes = options.maxConvertedBytes ?? CODEX_ROLLOUT_MAX_CONVERTED_BYTES;
55
+ this.maxMessages = options.maxMessages ?? CODEX_ROLLOUT_MAX_MESSAGES;
56
+ assertPositiveSafeInteger(this.maxRolloutBytes, 'Codex rollout byte limit');
57
+ assertPositiveSafeInteger(this.maxRecordBytes, 'Codex rollout record byte limit');
58
+ assertPositiveSafeInteger(this.maxConvertedBytes, 'Codex converted message byte limit');
59
+ assertPositiveSafeInteger(this.maxMessages, 'Codex converted message count limit');
49
60
  }
50
61
  async detect() {
51
62
  return ((await isDirectory(join(this.codexHome, 'sessions'))) ||
@@ -64,13 +75,12 @@ export class CodexSessionAdapter {
64
75
  const rolloutPath = await this.resolveRolloutPath(catalogEntry.rolloutPath, sessionId);
65
76
  if (!rolloutPath)
66
77
  throw new Error(`Codex rollout is unavailable: ${sessionId}`);
67
- const text = await readBoundedUtf8File(rolloutPath, this.maxRolloutBytes);
68
- const converted = convertCodexRollout(text, sessionId, catalogEntry.name, catalogEntry.cwd);
69
- return {
70
- sourceSessionId: sessionId,
71
- metadata: converted.metadata,
72
- messages: converted.messages,
73
- };
78
+ return convertCodexRollout(rolloutPath, sessionId, catalogEntry.name, catalogEntry.cwd, {
79
+ maxRolloutBytes: this.maxRolloutBytes,
80
+ maxRecordBytes: this.maxRecordBytes,
81
+ maxConvertedBytes: this.maxConvertedBytes,
82
+ maxMessages: this.maxMessages,
83
+ });
74
84
  }
75
85
  async listCatalog(query) {
76
86
  for (const dbPath of await codexStateDbsNewestFirst(this.codexHome)) {
@@ -181,261 +191,275 @@ export class CodexSessionAdapter {
181
191
  }
182
192
  }
183
193
  }
184
- function convertCodexRollout(text, expectedSessionId, fallbackName, fallbackCwd) {
185
- const records = parseRolloutRecords(text, expectedSessionId);
186
- const sessionMeta = records.find((record) => record.value.type === 'session_meta')?.value;
187
- const metaPayload = asRecord(sessionMeta?.payload);
188
- const actualSessionId = stringField(metaPayload, 'session_id') ?? stringField(metaPayload, 'id');
189
- if (actualSessionId !== expectedSessionId) {
190
- throw new Error(`Codex rollout Session id mismatch: expected ${expectedSessionId}`);
191
- }
192
- const metaCwd = safeCodexCwd(metaPayload?.cwd);
193
- const messages = [];
194
- let activeTurnId;
195
- let activeTurnIsExplicit = false;
196
- let activeModel = stringField(metaPayload, 'model_provider') ?? 'codex';
197
- let lastTimestamp = normalizeEpochMs(sessionMeta?.timestamp) ?? 0;
198
- let firstUserText;
199
- const failedTurnIds = new Set();
200
- const timestampFor = (record) => {
201
- const parsed = normalizeEpochMs(record.value.timestamp);
202
- if (parsed !== undefined)
203
- lastTimestamp = Math.max(lastTimestamp, parsed);
204
- else
205
- lastTimestamp += 1;
206
- return parsed ?? lastTimestamp;
207
- };
208
- const ensureTurnId = (line) => {
209
- activeTurnId ??= generatedCodexId(expectedSessionId, 'turn', line);
210
- return activeTurnId;
211
- };
212
- for (const record of records) {
194
+ async function convertCodexRollout(path, expectedSessionId, fallbackName, fallbackCwd, limits) {
195
+ const converter = new CodexRolloutConverter(expectedSessionId, fallbackName, fallbackCwd, limits);
196
+ for await (const record of readCodexRolloutRecords(path, expectedSessionId, limits)) {
197
+ converter.accept(record);
198
+ }
199
+ return converter.finish();
200
+ }
201
+ class CodexRolloutConverter {
202
+ expectedSessionId;
203
+ fallbackName;
204
+ fallbackCwd;
205
+ limits;
206
+ messages = [];
207
+ failedTurnIds = new Set();
208
+ activeTurnId;
209
+ activeTurnIsExplicit = false;
210
+ activeModel = 'codex';
211
+ lastTimestamp = 0;
212
+ firstUserText;
213
+ metaCwd = '';
214
+ hasSessionMeta = false;
215
+ convertedBytes = 0;
216
+ constructor(expectedSessionId, fallbackName, fallbackCwd, limits) {
217
+ this.expectedSessionId = expectedSessionId;
218
+ this.fallbackName = fallbackName;
219
+ this.fallbackCwd = fallbackCwd;
220
+ this.limits = limits;
221
+ }
222
+ accept(record) {
213
223
  const envelope = record.value;
224
+ if (envelope.type === 'session_meta' && !this.hasSessionMeta) {
225
+ this.hasSessionMeta = true;
226
+ const metaPayload = asRecord(envelope.payload);
227
+ const actualSessionId = stringField(metaPayload, 'session_id') ?? stringField(metaPayload, 'id');
228
+ if (actualSessionId !== this.expectedSessionId) {
229
+ throw new Error(`Codex rollout Session id mismatch: expected ${this.expectedSessionId}`);
230
+ }
231
+ this.metaCwd = safeCodexCwd(metaPayload?.cwd);
232
+ this.activeModel = stringField(metaPayload, 'model_provider') ?? this.activeModel;
233
+ const timestamp = normalizeEpochMs(envelope.timestamp);
234
+ if (timestamp !== undefined)
235
+ this.lastTimestamp = Math.max(this.lastTimestamp, timestamp);
236
+ return;
237
+ }
214
238
  const payload = asRecord(envelope.payload);
215
239
  if (!payload)
216
- continue;
240
+ return;
217
241
  if (envelope.type === 'turn_context') {
218
- activeTurnId = stringField(payload, 'turn_id') ?? activeTurnId;
219
- activeModel = stringField(payload, 'model') ?? activeModel;
220
- continue;
242
+ this.activeTurnId = stringField(payload, 'turn_id') ?? this.activeTurnId;
243
+ this.activeModel = stringField(payload, 'model') ?? this.activeModel;
244
+ return;
221
245
  }
222
246
  if (envelope.type === 'event_msg') {
223
247
  const eventType = stringField(payload, 'type');
224
248
  if (eventType === 'task_started' || eventType === 'turn_started') {
225
249
  const turnId = stringField(payload, 'turn_id');
226
250
  if (turnId) {
227
- activeTurnId = turnId;
228
- activeTurnIsExplicit = true;
251
+ this.activeTurnId = turnId;
252
+ this.activeTurnIsExplicit = true;
229
253
  }
230
- continue;
254
+ return;
231
255
  }
232
256
  if (eventType === 'item_completed') {
233
257
  const item = asRecord(payload.item);
234
258
  const itemType = stringField(item, 'type')?.toLowerCase();
235
259
  const eventTurnId = stringField(payload, 'turn_id');
236
260
  if (eventTurnId) {
237
- activeTurnId = eventTurnId;
238
- activeTurnIsExplicit = true;
261
+ this.activeTurnId = eventTurnId;
262
+ this.activeTurnIsExplicit = true;
239
263
  }
240
264
  if (itemType === 'usermessage') {
241
- if (!activeTurnIsExplicit) {
242
- activeTurnId = generatedCodexId(expectedSessionId, 'turn', record.line);
265
+ if (!this.activeTurnIsExplicit) {
266
+ this.activeTurnId = generatedCodexId(this.expectedSessionId, 'turn', record.line);
243
267
  }
244
268
  const text = codexCompletedItemText(item) || codexCompletedItemMediaText(item);
245
269
  if (text.length === 0)
246
- continue;
247
- firstUserText ??= text;
248
- messages.push({
270
+ return;
271
+ this.firstUserText ??= text;
272
+ this.append({
249
273
  type: 'user',
250
274
  id: stringField(item, 'client_id') ??
251
275
  stringField(item, 'id') ??
252
- generatedCodexId(expectedSessionId, 'user', record.line),
253
- turnId: ensureTurnId(record.line),
254
- ts: timestampFor(record),
276
+ generatedCodexId(this.expectedSessionId, 'user', record.line),
277
+ turnId: this.ensureTurnId(record.line),
278
+ ts: this.timestampFor(record),
255
279
  text,
256
280
  });
257
- continue;
281
+ return;
258
282
  }
259
283
  if (itemType === 'agentmessage') {
260
284
  const text = codexCompletedItemText(item);
261
285
  if (text.length === 0)
262
- continue;
286
+ return;
263
287
  const providerOptions = codexAssistantProviderOptions(item);
264
- messages.push({
288
+ this.append({
265
289
  type: 'assistant',
266
290
  id: stringField(item, 'id') ??
267
- generatedCodexId(expectedSessionId, 'assistant', record.line),
268
- turnId: ensureTurnId(record.line),
269
- ts: timestampFor(record),
291
+ generatedCodexId(this.expectedSessionId, 'assistant', record.line),
292
+ turnId: this.ensureTurnId(record.line),
293
+ ts: this.timestampFor(record),
270
294
  text,
271
295
  ...(providerOptions !== undefined ? { providerOptions } : {}),
272
- modelId: activeModel,
296
+ modelId: this.activeModel,
273
297
  contentOrder: ['text'],
274
298
  });
275
- continue;
299
+ return;
276
300
  }
277
301
  if (itemType === 'reasoning') {
278
302
  const reasoning = codexCompletedReasoningText(item);
279
303
  if (reasoning.length === 0)
280
- continue;
281
- messages.push({
304
+ return;
305
+ this.append({
282
306
  type: 'assistant',
283
307
  id: stringField(item, 'id') ??
284
- generatedCodexId(expectedSessionId, 'reasoning', record.line),
285
- turnId: ensureTurnId(record.line),
286
- ts: timestampFor(record),
308
+ generatedCodexId(this.expectedSessionId, 'reasoning', record.line),
309
+ turnId: this.ensureTurnId(record.line),
310
+ ts: this.timestampFor(record),
287
311
  text: '',
288
312
  thinking: { text: reasoning },
289
313
  contentOrder: ['thinking'],
290
- modelId: activeModel,
314
+ modelId: this.activeModel,
291
315
  });
292
- continue;
316
+ return;
293
317
  }
294
318
  }
295
319
  if (eventType === 'user_message') {
296
- if (!activeTurnIsExplicit) {
297
- activeTurnId = generatedCodexId(expectedSessionId, 'turn', record.line);
320
+ if (!this.activeTurnIsExplicit) {
321
+ this.activeTurnId = generatedCodexId(this.expectedSessionId, 'turn', record.line);
298
322
  }
299
323
  const text = stringField(payload, 'message') ?? mediaOnlyUserText(payload);
300
324
  if (text.length === 0)
301
- continue;
302
- firstUserText ??= text;
303
- const turnId = ensureTurnId(record.line);
304
- messages.push({
325
+ return;
326
+ this.firstUserText ??= text;
327
+ const turnId = this.ensureTurnId(record.line);
328
+ this.append({
305
329
  type: 'user',
306
330
  id: stringField(payload, 'client_id') ??
307
- generatedCodexId(expectedSessionId, 'user', record.line),
331
+ generatedCodexId(this.expectedSessionId, 'user', record.line),
308
332
  turnId,
309
- ts: timestampFor(record),
333
+ ts: this.timestampFor(record),
310
334
  text,
311
335
  });
312
- continue;
336
+ return;
313
337
  }
314
338
  if (eventType === 'agent_message') {
315
339
  const text = stringField(payload, 'message');
316
340
  if (!text)
317
- continue;
341
+ return;
318
342
  const providerOptions = codexAssistantProviderOptions(payload);
319
- messages.push({
343
+ this.append({
320
344
  type: 'assistant',
321
- id: generatedCodexId(expectedSessionId, 'assistant', record.line),
322
- turnId: ensureTurnId(record.line),
323
- ts: timestampFor(record),
345
+ id: generatedCodexId(this.expectedSessionId, 'assistant', record.line),
346
+ turnId: this.ensureTurnId(record.line),
347
+ ts: this.timestampFor(record),
324
348
  text,
325
349
  ...(providerOptions !== undefined ? { providerOptions } : {}),
326
- modelId: activeModel,
350
+ modelId: this.activeModel,
327
351
  contentOrder: ['text'],
328
352
  });
329
- continue;
353
+ return;
330
354
  }
331
355
  if (eventType === 'agent_reasoning') {
332
356
  const reasoning = stringField(payload, 'text');
333
357
  if (!reasoning)
334
- continue;
335
- messages.push({
358
+ return;
359
+ this.append({
336
360
  type: 'assistant',
337
- id: generatedCodexId(expectedSessionId, 'reasoning', record.line),
338
- turnId: ensureTurnId(record.line),
339
- ts: timestampFor(record),
361
+ id: generatedCodexId(this.expectedSessionId, 'reasoning', record.line),
362
+ turnId: this.ensureTurnId(record.line),
363
+ ts: this.timestampFor(record),
340
364
  text: '',
341
365
  thinking: { text: reasoning },
342
366
  contentOrder: ['thinking'],
343
- modelId: activeModel,
367
+ modelId: this.activeModel,
344
368
  });
345
- continue;
369
+ return;
346
370
  }
347
371
  if (eventType === 'context_compacted') {
348
- messages.push({
372
+ this.append({
349
373
  type: 'system_note',
350
- id: generatedCodexId(expectedSessionId, 'compact', record.line),
351
- turnId: activeTurnId,
352
- ts: timestampFor(record),
374
+ id: generatedCodexId(this.expectedSessionId, 'compact', record.line),
375
+ turnId: this.activeTurnId,
376
+ ts: this.timestampFor(record),
353
377
  kind: 'context_compacted',
354
378
  });
355
- continue;
379
+ return;
356
380
  }
357
381
  if (eventType === 'error') {
358
- if (activeTurnId && codexErrorAffectsTurnStatus(payload)) {
359
- failedTurnIds.add(activeTurnId);
382
+ if (this.activeTurnId && codexErrorAffectsTurnStatus(payload)) {
383
+ this.failedTurnIds.add(this.activeTurnId);
360
384
  }
361
- messages.push({
385
+ this.append({
362
386
  type: 'system_note',
363
- id: generatedCodexId(expectedSessionId, 'error', record.line),
364
- turnId: activeTurnId,
365
- ts: timestampFor(record),
387
+ id: generatedCodexId(this.expectedSessionId, 'error', record.line),
388
+ turnId: this.activeTurnId,
389
+ ts: this.timestampFor(record),
366
390
  kind: 'error',
367
391
  data: JSON.parse(JSON.stringify(payload)),
368
392
  });
369
- continue;
393
+ return;
370
394
  }
371
395
  if (eventType === 'task_complete' || eventType === 'turn_complete') {
372
- const turnId = stringField(payload, 'turn_id') ?? ensureTurnId(record.line);
373
- const failed = failedTurnIds.has(turnId) || payload.error != null;
374
- messages.push({
396
+ const turnId = stringField(payload, 'turn_id') ?? this.ensureTurnId(record.line);
397
+ const failed = this.failedTurnIds.has(turnId) || payload.error != null;
398
+ this.append({
375
399
  type: 'turn_state',
376
- id: generatedCodexId(expectedSessionId, 'turn-state', record.line),
400
+ id: generatedCodexId(this.expectedSessionId, 'turn-state', record.line),
377
401
  turnId,
378
- ts: timestampFor(record),
402
+ ts: this.timestampFor(record),
379
403
  status: failed ? 'failed' : 'completed',
380
404
  ...(failed ? { errorClass: 'codex_error' } : {}),
381
405
  });
382
- failedTurnIds.delete(turnId);
383
- if (activeTurnId === turnId) {
384
- activeTurnId = undefined;
385
- activeTurnIsExplicit = false;
406
+ this.failedTurnIds.delete(turnId);
407
+ if (this.activeTurnId === turnId) {
408
+ this.activeTurnId = undefined;
409
+ this.activeTurnIsExplicit = false;
386
410
  }
387
- continue;
411
+ return;
388
412
  }
389
413
  if (eventType === 'turn_aborted') {
390
- const turnId = stringField(payload, 'turn_id') ?? ensureTurnId(record.line);
391
- const ts = timestampFor(record);
392
- messages.push({
414
+ const turnId = stringField(payload, 'turn_id') ?? this.ensureTurnId(record.line);
415
+ const ts = this.timestampFor(record);
416
+ this.append({
393
417
  type: 'turn_state',
394
- id: generatedCodexId(expectedSessionId, 'turn-state', record.line),
418
+ id: generatedCodexId(this.expectedSessionId, 'turn-state', record.line),
395
419
  turnId,
396
420
  ts,
397
421
  status: 'aborted',
398
422
  abortedAt: normalizeEpochMs(payload.completed_at) ?? ts,
399
423
  abortSource: stringField(payload, 'reason') ?? 'codex',
400
424
  });
401
- if (activeTurnId === turnId) {
402
- activeTurnId = undefined;
403
- activeTurnIsExplicit = false;
425
+ if (this.activeTurnId === turnId) {
426
+ this.activeTurnId = undefined;
427
+ this.activeTurnIsExplicit = false;
404
428
  }
405
- continue;
429
+ return;
406
430
  }
407
431
  }
408
432
  if (envelope.type !== 'response_item')
409
- continue;
433
+ return;
410
434
  const itemType = stringField(payload, 'type');
411
435
  if (itemType === 'function_call' || itemType === 'custom_tool_call') {
412
436
  const callId = stringField(payload, 'call_id');
413
437
  const toolName = namespacedToolName(payload);
414
438
  if (!callId || !toolName)
415
- continue;
439
+ return;
416
440
  const rawArgs = itemType === 'function_call'
417
441
  ? stringField(payload, 'arguments')
418
442
  : stringField(payload, 'input');
419
- messages.push({
443
+ this.append({
420
444
  type: 'tool_call',
421
445
  id: callId,
422
- turnId: ensureTurnId(record.line),
423
- ts: timestampFor(record),
446
+ turnId: this.ensureTurnId(record.line),
447
+ ts: this.timestampFor(record),
424
448
  toolName,
425
449
  args: parseJsonString(rawArgs),
426
450
  });
427
- continue;
451
+ return;
428
452
  }
429
453
  if (itemType === 'function_call_output' || itemType === 'custom_tool_call_output') {
430
454
  const callId = stringField(payload, 'call_id');
431
455
  if (!callId)
432
- continue;
433
- messages.push({
456
+ return;
457
+ this.append({
434
458
  type: 'tool_result',
435
459
  id: stringField(payload, 'id') ??
436
- generatedCodexId(expectedSessionId, 'tool-result', record.line),
437
- turnId: ensureTurnId(record.line),
438
- ts: timestampFor(record),
460
+ generatedCodexId(this.expectedSessionId, 'tool-result', record.line),
461
+ turnId: this.ensureTurnId(record.line),
462
+ ts: this.timestampFor(record),
439
463
  toolUseId: callId,
440
464
  // Codex persists the output body but not FunctionCallOutputPayload.success.
441
465
  // Preserve the raw body and avoid guessing failure from its text.
@@ -444,37 +468,134 @@ function convertCodexRollout(text, expectedSessionId, fallbackName, fallbackCwd)
444
468
  });
445
469
  }
446
470
  }
447
- const name = sanitizeForeignTitle(fallbackName) || sanitizeForeignTitle(firstUserText) || expectedSessionId;
448
- return {
449
- sourceSessionId: expectedSessionId,
450
- metadata: { name, cwd: metaCwd || fallbackCwd },
451
- messages,
452
- };
471
+ finish() {
472
+ if (!this.hasSessionMeta) {
473
+ throw new Error(`Codex rollout Session id mismatch: expected ${this.expectedSessionId}`);
474
+ }
475
+ const name = sanitizeForeignTitle(this.fallbackName) ||
476
+ sanitizeForeignTitle(this.firstUserText) ||
477
+ this.expectedSessionId;
478
+ return {
479
+ sourceSessionId: this.expectedSessionId,
480
+ metadata: { name, cwd: this.metaCwd || this.fallbackCwd },
481
+ messages: this.messages,
482
+ };
483
+ }
484
+ timestampFor(record) {
485
+ const parsed = normalizeEpochMs(record.value.timestamp);
486
+ if (parsed !== undefined)
487
+ this.lastTimestamp = Math.max(this.lastTimestamp, parsed);
488
+ else
489
+ this.lastTimestamp += 1;
490
+ return parsed ?? this.lastTimestamp;
491
+ }
492
+ ensureTurnId(line) {
493
+ this.activeTurnId ??= generatedCodexId(this.expectedSessionId, 'turn', line);
494
+ return this.activeTurnId;
495
+ }
496
+ append(message) {
497
+ if (this.messages.length >= this.limits.maxMessages) {
498
+ throw new Error(`Codex rollout converts to more than ${this.limits.maxMessages} messages`);
499
+ }
500
+ const encodedBytes = Buffer.byteLength(JSON.stringify(message), 'utf8');
501
+ if (encodedBytes > this.limits.maxConvertedBytes - this.convertedBytes) {
502
+ throw new Error(`Codex rollout converts to more than ${this.limits.maxConvertedBytes} bytes`);
503
+ }
504
+ this.convertedBytes += encodedBytes;
505
+ this.messages.push(message);
506
+ }
453
507
  }
454
- function parseRolloutRecords(text, sessionId) {
455
- const endsWithNewline = text.endsWith('\n');
456
- const lines = text.split('\n');
457
- if (endsWithNewline)
458
- lines.pop();
459
- const records = [];
460
- for (let index = 0; index < lines.length; index += 1) {
461
- const line = lines[index];
462
- if (line.trim().length === 0)
463
- continue;
464
- try {
465
- const value = JSON.parse(line);
466
- if (!isRecord(value))
467
- throw new Error('record is not an object');
468
- records.push({ line: index + 1, value });
508
+ async function* readCodexRolloutRecords(path, sessionId, limits) {
509
+ const handle = await open(path, 'r');
510
+ try {
511
+ const metadata = await handle.stat();
512
+ if (!metadata.isFile())
513
+ throw new Error('Codex rollout is not a regular file');
514
+ if (metadata.size > limits.maxRolloutBytes) {
515
+ throw new Error(`Codex rollout exceeds ${limits.maxRolloutBytes} bytes`);
469
516
  }
470
- catch (error) {
471
- if (!endsWithNewline && index === lines.length - 1)
472
- break;
473
- const detail = error instanceof Error ? error.message : String(error);
474
- throw new Error(`Invalid Codex rollout ${sessionId} at line ${index + 1}: ${detail}`);
517
+ const snapshotBytes = metadata.size;
518
+ const pending = [];
519
+ let pendingBytes = 0;
520
+ let observedBytes = 0;
521
+ let line = 0;
522
+ if (snapshotBytes > 0) {
523
+ for await (const value of handle.createReadStream({
524
+ autoClose: false,
525
+ emitClose: false,
526
+ start: 0,
527
+ end: snapshotBytes - 1,
528
+ highWaterMark: CODEX_ROLLOUT_READ_BYTES,
529
+ })) {
530
+ const chunk = Buffer.from(value);
531
+ observedBytes += chunk.byteLength;
532
+ if (observedBytes > snapshotBytes) {
533
+ throw new Error('Codex rollout changed while being read');
534
+ }
535
+ let start = 0;
536
+ for (;;) {
537
+ const newline = chunk.indexOf(0x0a, start);
538
+ if (newline === -1)
539
+ break;
540
+ const segment = chunk.subarray(start, newline);
541
+ assertCodexRecordSize(pendingBytes + segment.byteLength, limits.maxRecordBytes, line + 1);
542
+ line += 1;
543
+ const record = parseCodexRolloutLine(pending.length === 0
544
+ ? segment
545
+ : Buffer.concat([...pending, segment], pendingBytes + segment.byteLength), sessionId, line, false);
546
+ if (record)
547
+ yield record;
548
+ pending.length = 0;
549
+ pendingBytes = 0;
550
+ start = newline + 1;
551
+ }
552
+ if (start < chunk.byteLength) {
553
+ const segment = chunk.subarray(start);
554
+ assertCodexRecordSize(pendingBytes + segment.byteLength, limits.maxRecordBytes, line + 1);
555
+ pending.push(segment);
556
+ pendingBytes += segment.byteLength;
557
+ }
558
+ }
559
+ }
560
+ if (observedBytes !== snapshotBytes)
561
+ throw new Error('Codex rollout changed while being read');
562
+ if (pendingBytes > 0) {
563
+ line += 1;
564
+ const record = parseCodexRolloutLine(pending.length === 1 ? pending[0] : Buffer.concat(pending, pendingBytes), sessionId, line, true);
565
+ if (record)
566
+ yield record;
475
567
  }
476
568
  }
477
- return records;
569
+ finally {
570
+ await handle.close();
571
+ }
572
+ }
573
+ function parseCodexRolloutLine(bytes, sessionId, line, tolerateTornTail) {
574
+ const text = bytes.toString('utf8');
575
+ if (text.trim().length === 0)
576
+ return undefined;
577
+ try {
578
+ const value = JSON.parse(text);
579
+ if (!isRecord(value))
580
+ throw new Error('record is not an object');
581
+ return { line, value };
582
+ }
583
+ catch (error) {
584
+ if (tolerateTornTail)
585
+ return undefined;
586
+ const detail = error instanceof Error ? error.message : String(error);
587
+ throw new Error(`Invalid Codex rollout ${sessionId} at line ${line}: ${detail}`);
588
+ }
589
+ }
590
+ function assertCodexRecordSize(actualBytes, maxBytes, line) {
591
+ if (actualBytes > maxBytes) {
592
+ throw new Error(`Codex rollout record at line ${line} exceeds ${maxBytes} bytes`);
593
+ }
594
+ }
595
+ function assertPositiveSafeInteger(value, label) {
596
+ if (!Number.isSafeInteger(value) || value <= 0) {
597
+ throw new Error(`${label} must be a positive safe integer`);
598
+ }
478
599
  }
479
600
  function catalogEntryFromRolloutHead(text, candidate) {
480
601
  const lines = text.split('\n');
@@ -644,39 +765,6 @@ async function walkRolloutFiles(root, archived) {
644
765
  await visit(root);
645
766
  return files;
646
767
  }
647
- async function readBoundedUtf8File(path, maxBytes) {
648
- const handle = await open(path, 'r');
649
- try {
650
- const metadata = await handle.stat();
651
- if (!metadata.isFile())
652
- throw new Error('Codex rollout is not a regular file');
653
- if (metadata.size > maxBytes)
654
- throw new Error(`Codex rollout exceeds ${maxBytes} bytes`);
655
- const chunks = [];
656
- let total = 0;
657
- for (;;) {
658
- const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1 - total));
659
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, total);
660
- if (bytesRead === 0)
661
- break;
662
- total += bytesRead;
663
- if (total > maxBytes)
664
- throw new Error(`Codex rollout exceeds ${maxBytes} bytes`);
665
- chunks.push(buffer.subarray(0, bytesRead));
666
- if (total === maxBytes) {
667
- const probe = Buffer.allocUnsafe(1);
668
- if ((await handle.read(probe, 0, 1, total)).bytesRead > 0) {
669
- throw new Error(`Codex rollout exceeds ${maxBytes} bytes`);
670
- }
671
- break;
672
- }
673
- }
674
- return Buffer.concat(chunks, total).toString('utf8');
675
- }
676
- finally {
677
- await handle.close();
678
- }
679
- }
680
768
  async function readUtf8Prefix(path, maxBytes) {
681
769
  const handle = await open(path, 'r');
682
770
  try {