maka-agent 0.2.0-dev.64.20260929 → 0.2.0-dev.67.20261002

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 (94) hide show
  1. package/LICENSE +1 -1
  2. package/THIRD_PARTY_NOTICES.txt +3 -3
  3. package/dist/pi-tui-runner.js +26 -29
  4. package/dist/tui-copy-catalog.js +9 -9
  5. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  6. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  7. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  8. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  9. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  10. package/node_modules/@maka/core/dist/settings.js +2 -2
  11. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +53 -150
  12. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +8 -1
  13. package/node_modules/@maka/runtime/dist/conversation-copy.js +6 -0
  14. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +17 -0
  15. package/node_modules/@maka/runtime/dist/session-manager.js +25 -40
  16. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +79 -13
  17. package/node_modules/@maka/runtime/dist/stream-graph-projection.js +64 -0
  18. package/node_modules/@maka/runtime/dist/stream-graph-read-model.js +138 -20
  19. package/node_modules/@maka/runtime/dist/tool-availability.js +10 -3
  20. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +15 -0
  21. package/node_modules/@maka/runtime-host/dist/candidate-startup-failure.js +5 -0
  22. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +1 -0
  23. package/node_modules/@maka/runtime-host/dist/client/startup-error.js +5 -0
  24. package/node_modules/@maka/runtime-host/dist/protocol/agent-graph.js +46 -1
  25. package/node_modules/@maka/runtime-host/dist/protocol/index.js +14 -1
  26. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +5 -1
  27. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +12 -0
  28. package/node_modules/@maka/runtime-host/dist/protocol/session-retirement.js +46 -10
  29. package/node_modules/@maka/runtime-host/dist/protocol/storage-usage.js +155 -0
  30. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +23 -0
  31. package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +5 -0
  32. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +27 -6
  33. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +2 -0
  34. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +6 -0
  35. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +112 -36
  36. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +27 -1
  37. package/node_modules/@maka/runtime-host/dist/server/storage-usage-coordinator.js +67 -0
  38. package/node_modules/@maka/runtime-host/dist/server/workhub-inspection-tool.js +208 -0
  39. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +3 -1
  40. package/node_modules/@maka/storage/dist/operational-state-store.js +0 -13
  41. package/node_modules/@maka/storage/dist/session-store.js +9 -6
  42. package/node_modules/@maka/storage/dist/sqlite-file-set.js +22 -0
  43. package/node_modules/@maka/storage/dist/sqlite-long-term-memory-store.js +1 -3
  44. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +10 -260
  45. package/node_modules/@maka/storage/dist/sqlite-session-catalog-query.js +1 -0
  46. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +18 -6
  47. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +28 -6
  48. package/node_modules/@maka/storage/dist/storage-footprint.js +239 -0
  49. package/node_modules/@maka/storage/dist/storage-writer-composition.js +10 -1
  50. package/node_modules/axios/CHANGELOG.md +62 -0
  51. package/node_modules/axios/README.md +297 -5
  52. package/node_modules/axios/dist/axios.js +728 -170
  53. package/node_modules/axios/dist/axios.min.js +3 -3
  54. package/node_modules/axios/dist/browser/axios.cjs +871 -188
  55. package/node_modules/axios/dist/esm/axios.js +871 -188
  56. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  57. package/node_modules/axios/dist/node/axios.cjs +1037 -225
  58. package/node_modules/axios/lib/adapters/fetch.js +64 -11
  59. package/node_modules/axios/lib/adapters/http.js +140 -67
  60. package/node_modules/axios/lib/adapters/xhr.js +52 -3
  61. package/node_modules/axios/lib/core/Axios.js +43 -20
  62. package/node_modules/axios/lib/core/AxiosError.js +33 -2
  63. package/node_modules/axios/lib/core/AxiosHeaders.js +124 -1
  64. package/node_modules/axios/lib/core/InterceptorManager.js +107 -8
  65. package/node_modules/axios/lib/core/buildFullPath.js +41 -15
  66. package/node_modules/axios/lib/core/dispatchRequest.js +8 -2
  67. package/node_modules/axios/lib/core/mergeConfig.js +19 -4
  68. package/node_modules/axios/lib/core/methodList.js +17 -0
  69. package/node_modules/axios/lib/core/setFormDataHeaders.js +27 -0
  70. package/node_modules/axios/lib/defaults/index.js +2 -1
  71. package/node_modules/axios/lib/env/data.js +1 -1
  72. package/node_modules/axios/lib/helpers/Http2Sessions.js +2 -0
  73. package/node_modules/axios/lib/helpers/HttpStatusCode.js +12 -1
  74. package/node_modules/axios/lib/helpers/combineURLs.js +11 -3
  75. package/node_modules/axios/lib/helpers/composeSignals.js +12 -1
  76. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +115 -54
  77. package/node_modules/axios/lib/helpers/formDataToJSON.js +11 -5
  78. package/node_modules/axios/lib/helpers/fromDataURI.js +5 -4
  79. package/node_modules/axios/lib/helpers/normalizeURLForProtocolCheck.js +24 -0
  80. package/node_modules/axios/lib/helpers/parseHeaders.js +5 -3
  81. package/node_modules/axios/lib/helpers/progressEventReducer.js +4 -4
  82. package/node_modules/axios/lib/helpers/resolveConfig.js +5 -17
  83. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +300 -5
  84. package/node_modules/axios/lib/helpers/throttle.js +3 -2
  85. package/node_modules/axios/lib/helpers/toFormData.js +17 -23
  86. package/node_modules/axios/lib/platform/node/classes/Buffer.js +11 -0
  87. package/node_modules/axios/lib/utils.js +151 -18
  88. package/node_modules/axios/package.json +21 -19
  89. package/node_modules/brace-expansion/dist/commonjs/index.js +69 -25
  90. package/node_modules/brace-expansion/dist/esm/index.js +68 -24
  91. package/node_modules/brace-expansion/package.json +2 -1
  92. package/node_modules/fast-uri/index.js +10 -6
  93. package/node_modules/fast-uri/package.json +1 -1
  94. package/package.json +1 -1
package/LICENSE CHANGED
@@ -253,7 +253,7 @@ License: MIT
253
253
 
254
254
  Maka includes an ejected adaptation of Astryx ChatReasoning v0.1.9 in
255
255
  `packages/ui/src/astryx-chat-reasoning.tsx` and generated theme artifacts in
256
- `apps/desktop/src/renderer/astryx-theme/`, based on Astryx theme-neutral v0.6.2.
256
+ `apps/desktop/src/renderer/astryx-theme/`, based on Astryx theme-neutral v0.6.3.
257
257
  Maka also redistributes
258
258
  `patches/@astryxdesign+core+0.6.3.patch`, which modifies the MIT-licensed Astryx
259
259
  core package. The following MIT License applies to that material:
@@ -2304,7 +2304,7 @@ SOFTWARE.
2304
2304
 
2305
2305
  ================================================================================
2306
2306
 
2307
- Package: axios@1.18.1
2307
+ Package: axios@1.20.0
2308
2308
  Declared license: MIT
2309
2309
  Selected license: MIT
2310
2310
  Repository: https://github.com/axios/axios.git
@@ -3004,7 +3004,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3004
3004
 
3005
3005
  ================================================================================
3006
3006
 
3007
- Package: brace-expansion@5.0.9
3007
+ Package: brace-expansion@5.0.12
3008
3008
  Declared license: MIT
3009
3009
  Selected license: MIT
3010
3010
  Repository: git+https://github.com/juliangruber/brace-expansion.git
@@ -3970,7 +3970,7 @@ SOFTWARE.
3970
3970
 
3971
3971
  ================================================================================
3972
3972
 
3973
- Package: fast-uri@3.1.7
3973
+ Package: fast-uri@3.1.8
3974
3974
  Declared license: BSD-3-Clause
3975
3975
  Selected license: BSD-3-Clause
3976
3976
  Repository: git+https://github.com/fastify/fast-uri.git
@@ -786,7 +786,7 @@ export async function runMakaPiTui(input) {
786
786
  return true;
787
787
  };
788
788
  // Refill the editor from a retract result, prepended to any current draft.
789
- // Shared by the interrupt path and the alt+↑ path. The text always comes
789
+ // Shared by the interrupt path and the Shift+← path. The text always comes
790
790
  // from `driver.retractQueued()` — an authoritative queue mutation — never
791
791
  // from the render mirror, which can
792
792
  // lag a step-boundary consumption and would resurrect an already-consumed
@@ -879,7 +879,7 @@ export async function runMakaPiTui(input) {
879
879
  return;
880
880
  }
881
881
  // First-run has no connection, so the wizard is the only surface. This is
882
- // the single choke point for idle submits (Enter, Alt+Enter, steer
882
+ // the single choke point for idle submits (Enter, steer
883
883
  // fallback): reopen the wizard instead of opening a turn against a
884
884
  // connection-less driver. Slash commands above already routed to the
885
885
  // command layer (/exit still exits, /help still shows help).
@@ -959,29 +959,16 @@ export async function runMakaPiTui(input) {
959
959
  }
960
960
  submitMessage(text, 'current_turn');
961
961
  };
962
- // Alt+Enter: during a turn, queue the text to open the next turn; when idle,
963
- // it submits like Enter.
964
- const handleAltEnter = () => {
965
- // Mirror Enter's control-busy guard BEFORE touching the editor: during a
966
- // control action (busy without a running turn) submitPrompt would drop the
967
- // prompt, so keep the draft in place instead of clearing it into the void.
968
- if (busy && !turnRunning)
969
- return;
970
- // Interrupt convergence window: the turn is being stopped, so nothing may
971
- // be queued onto it and no fresh turn may open — keep the draft.
972
- if (interruptRequested)
973
- return;
962
+ // Tab: during a turn, queue the draft to open the next turn. Idle Tab never
963
+ // reaches this — it stays the editor's completion trigger (key handler gate).
964
+ const queueDraftForNextTurn = () => {
974
965
  const text = editor.getExpandedText().trim();
975
966
  if (!text)
976
967
  return;
977
968
  editor.setText('');
978
- if (!turnRunning) {
979
- submitPrompt(text);
980
- return;
981
- }
982
969
  submitMessage(text, 'next_turn');
983
970
  };
984
- // Alt+↑: take back every queued message from the Runtime Host, joined and
971
+ // Shift+←: take back every queued message from the Runtime Host, joined and
985
972
  // prepended to the current draft for re-editing.
986
973
  const retractQueuedMessages = () => {
987
974
  void (async () => {
@@ -4119,20 +4106,30 @@ export async function runMakaPiTui(input) {
4119
4106
  }
4120
4107
  return { consume: true };
4121
4108
  }
4122
- // Alt+Enter: queue a followup (during a turn) or submit (when idle). Alt+↑:
4123
- // take back the queued messages to re-edit. Neither is an editor binding
4124
- // (newline is shift+enter/ctrl+j; history is plain up), so intercepting
4125
- // here does not collide with the editor's own keys.
4126
- if (matchesKey(data, Key.alt('enter')) && !isKeyRepeat(data)) {
4127
- handleAltEnter();
4109
+ // Tab: queue a followup during a turn — the route terminals deliver
4110
+ // reliably, unlike the Alt chords they claim for themselves (#3538;
4111
+ // Codex's default). The editor owns Tab everywhere else: it
4112
+ // triggers/accepts completion, so a turn-running Tab only queues a
4113
+ // non-empty draft while no completion popup is open. Idle Tab stays
4114
+ // with the editor rather than submitting.
4115
+ if (turnRunning &&
4116
+ !interruptRequested &&
4117
+ !editorPastePending &&
4118
+ editor.getText().trim().length > 0 &&
4119
+ !editor.isShowingAutocomplete() &&
4120
+ matchesKey(data, Key.tab) &&
4121
+ !isKeyRepeat(data)) {
4122
+ queueDraftForNextTurn();
4128
4123
  return { consume: true };
4129
4124
  }
4130
- if (matchesKey(data, Key.alt('up')) && !isKeyRepeat(data)) {
4125
+ // Shift+←: retract queued messages (Codex's edit_queued_message
4126
+ // default). Not an editor binding, and an empty retract refill is a
4127
+ // no-op, so consuming unconditionally loses nothing — same rationale
4128
+ // the old Alt+↑ binding used.
4129
+ if (matchesKey(data, Key.shift('left')) && !isKeyRepeat(data)) {
4131
4130
  // Always retract from the authority: the render mirror lags the
4132
- // queue_update event, so an enqueue followed by Alt+Up in the same
4131
+ // queue_update event, so an enqueue followed by Shift+← in the same
4133
4132
  // tick would see an empty mirror while the runtime holds the message.
4134
- // Alt+Up is not an editor binding, and an empty retract refill is a
4135
- // no-op, so consuming unconditionally loses nothing.
4136
4133
  retractQueuedMessages();
4137
4134
  return { consume: true };
4138
4135
  }
@@ -1032,8 +1032,8 @@ export const TUI_COPY_RESOURCES = {
1032
1032
  ' Ctrl+T — expand or collapse all thinking in view',
1033
1033
  ' Scroll the transcript with your terminal or trackpad',
1034
1034
  ' Enter (during a turn) — steer: inject a message into the running turn',
1035
- ' Alt+Enter (during a turn) — queue a message for the next turn',
1036
- ' Alt+↑ — take queued messages back into the editor to re-edit',
1035
+ ' Tab (during a turn) — queue a message for the next turn',
1036
+ ' Shift+← — take queued messages back into the editor to re-edit',
1037
1037
  ' Esc Esc (during a turn) — interrupt the turn',
1038
1038
  ' Esc Esc (when idle) — rewind to an earlier turn',
1039
1039
  ' Ctrl+C — stop the turn, clear input, or press twice to exit',
@@ -1086,8 +1086,8 @@ export const TUI_COPY_RESOURCES = {
1086
1086
  ' Ctrl+T — 展开或折叠视图中的所有思考块',
1087
1087
  ' 使用终端或触控板滚动对话记录',
1088
1088
  ' Enter(任务运行中)— 将消息注入当前任务',
1089
- ' Alt+Enter(任务运行中)— 将消息排入下一轮',
1090
- ' Alt+↑ — 将已排队消息取回编辑器重新编辑',
1089
+ ' Tab(任务运行中)— 将消息排入下一轮',
1090
+ ' Shift+← — 将已排队消息取回编辑器重新编辑',
1091
1091
  ' Esc Esc(任务运行中)— 中断当前任务',
1092
1092
  ' Esc Esc(空闲时)— 回退到较早的轮次',
1093
1093
  ' Ctrl+C — 停止任务、清空输入,或连续按两次退出',
@@ -1140,8 +1140,8 @@ export const TUI_COPY_RESOURCES = {
1140
1140
  ' Ctrl+T — 展開或摺疊檢視中的所有思考區塊',
1141
1141
  ' 使用終端機或觸控板捲動對話記錄',
1142
1142
  ' Enter(任務執行中)— 將訊息注入目前任務',
1143
- ' Alt+Enter(任務執行中)— 將訊息排入下一輪',
1144
- ' Alt+↑ — 將已排隊訊息取回編輯器重新編輯',
1143
+ ' Tab(任務執行中)— 將訊息排入下一輪',
1144
+ ' Shift+← — 將已排隊訊息取回編輯器重新編輯',
1145
1145
  ' Esc Esc(任務執行中)— 中斷目前任務',
1146
1146
  ' Esc Esc(空閒時)— 回退到較早的輪次',
1147
1147
  ' Ctrl+C — 停止任務、清空輸入,或連續按兩次退出',
@@ -1372,17 +1372,17 @@ export const TUI_COPY_RESOURCES = {
1372
1372
  en: {
1373
1373
  steeringLabel: 'Steering:',
1374
1374
  queuedLabel: 'Queued:',
1375
- requeueHint: 'Alt+↑ take queued messages back to re-edit',
1375
+ requeueHint: 'Shift+← take queued messages back to re-edit',
1376
1376
  },
1377
1377
  'zh-CN': {
1378
1378
  steeringLabel: '注入:',
1379
1379
  queuedLabel: '已排队:',
1380
- requeueHint: 'Alt+↑ 取回队列以重新编辑',
1380
+ requeueHint: 'Shift+← 取回队列以重新编辑',
1381
1381
  },
1382
1382
  'zh-TW': {
1383
1383
  steeringLabel: '調整方向:',
1384
1384
  queuedLabel: '已排入佇列:',
1385
- requeueHint: 'Alt+↑ 取回佇列訊息重新編輯',
1385
+ requeueHint: 'Shift+← 取回佇列訊息重新編輯',
1386
1386
  },
1387
1387
  },
1388
1388
  'session-status': {
@@ -340,7 +340,7 @@ export function createDefaultSettings() {
340
340
  activeTab: 'requests',
341
341
  },
342
342
  appearance: {
343
- workbarTogglePosition: 'edge',
343
+ workbarTogglePosition: 'titlebar',
344
344
  theme: 'auto',
345
345
  palette: 'default',
346
346
  appIcon: DEFAULT_APP_ICON,
@@ -517,7 +517,7 @@ export function normalizeSettings(input) {
517
517
  // position; UI density is no longer a product setting.
518
518
  appearance: {
519
519
  ...appearanceWithoutLegacyFields,
520
- workbarTogglePosition: base.appearance.workbarTogglePosition === 'titlebar' ? 'titlebar' : 'edge',
520
+ workbarTogglePosition: base.appearance.workbarTogglePosition === 'edge' ? 'edge' : 'titlebar',
521
521
  palette: isThemePalette(base.appearance.palette) ? base.appearance.palette : 'default',
522
522
  // Same fail-closed rule as `palette` above, for the same reason: an
523
523
  // unknown id would otherwise reach the main process and resolve to a
@@ -132,37 +132,46 @@ export function validateGenericToolLedgerAppend(event) {
132
132
  */
133
133
  export function validateToolLedgerTransition(input) {
134
134
  const reducer = new ToolLedgerReducer(input.existingEvents);
135
- const validation = validateIncrementalToolLedgerTransition({
136
- reducer,
137
- candidateEvents: input.candidateEvents,
138
- expectedTransition: input.expectedTransition,
139
- });
140
- if (validation.ok) {
141
- reducer.commit(validation.checkpoint);
142
- return { ok: true };
135
+ const existing = reducer.scan();
136
+ if (existing.hasCorruption) {
137
+ return { source: 'existing', ...issueValidation(existing.issues[0]) };
138
+ }
139
+ const candidates = [];
140
+ for (const candidate of input.candidateEvents) {
141
+ const prior = reducer.event(candidate.id);
142
+ if (!prior) {
143
+ candidates.push(candidate);
144
+ continue;
145
+ }
146
+ if (!nodeUtil.isDeepStrictEqual(prior, candidate)) {
147
+ return {
148
+ ok: false,
149
+ source: 'candidate',
150
+ code: 'duplicate_event_id',
151
+ eventId: candidate.id,
152
+ };
153
+ }
143
154
  }
144
- const { source: _source, ...failure } = validation;
145
- return failure;
155
+ const shape = validateTransitionShape(candidates, input.expectedTransition);
156
+ if (!shape.ok)
157
+ return { source: 'candidate', ...shape };
158
+ reducer.append(candidates);
159
+ const issue = reducer.scan().issues[0];
160
+ return issue ? { source: 'candidate', ...issueValidation(issue) } : { ok: true };
146
161
  }
147
162
  /**
148
- * Stateful interpretation of an immutable RuntimeEvent prefix.
149
- *
150
- * Candidate appends mutate the reducer behind a checkpoint. The caller either
151
- * commits that checkpoint after durable transaction commit or rolls it back.
152
- * Rollback records only the fields touched by the candidate batch, so a small
153
- * append does not clone maps proportional to the full checked prefix.
163
+ * Invocation-scoped interpretation shared by scans and prospective validation.
164
+ * Each caller owns a fresh reducer; no state survives a validation call.
154
165
  */
155
- export class ToolLedgerReducer {
166
+ class ToolLedgerReducer {
156
167
  operations = [];
157
168
  issues = [];
158
169
  eventsById = new Map();
159
170
  byToolCall = new Map();
160
171
  byOperation = new Map();
161
172
  invocationSpines = new Map();
162
- undoLog = [];
163
- recordUndo = false;
164
- constructor(events = []) {
165
- this.consume(events, false);
173
+ constructor(events) {
174
+ this.append(events);
166
175
  }
167
176
  scan() {
168
177
  const dependencyIssues = this.toolDependencyIssues();
@@ -195,45 +204,16 @@ export class ToolLedgerReducer {
195
204
  event(eventId) {
196
205
  return this.eventsById.get(eventId);
197
206
  }
198
- checkpoint() {
199
- return this.undoLog.length;
200
- }
201
207
  append(events) {
202
- this.consume(events, true);
203
- }
204
- rollback(checkpoint) {
205
- this.assertCheckpoint(checkpoint);
206
- for (let index = this.undoLog.length - 1; index >= checkpoint; index -= 1) {
207
- this.undoLog[index]();
208
- }
209
- this.undoLog.length = checkpoint;
210
- }
211
- commit(checkpoint) {
212
- this.assertCheckpoint(checkpoint);
213
- this.undoLog.length = checkpoint;
214
- }
215
- assertCheckpoint(checkpoint) {
216
- if (!Number.isSafeInteger(checkpoint) || checkpoint < 0 || checkpoint > this.undoLog.length) {
217
- throw new RangeError('Invalid tool ledger reducer checkpoint');
218
- }
219
- }
220
- consume(events, recordUndo) {
221
- const previous = this.recordUndo;
222
- this.recordUndo = recordUndo;
223
- try {
224
- for (const event of events)
225
- this.consumeEvent(event);
226
- }
227
- finally {
228
- this.recordUndo = previous;
229
- }
208
+ for (const event of events)
209
+ this.consumeEvent(event);
230
210
  }
231
211
  consumeEvent(event) {
232
212
  if (this.eventsById.has(event.id)) {
233
213
  this.addIssue(undefined, { code: 'duplicate_event_id', eventId: event.id });
234
214
  return;
235
215
  }
236
- this.setMap(this.eventsById, event.id, event);
216
+ this.eventsById.set(event.id, event);
237
217
  const spine = JSON.stringify([event.sessionId, event.runId, event.turnId]);
238
218
  const existingSpine = this.invocationSpines.get(event.invocationId);
239
219
  if (existingSpine !== undefined && existingSpine !== spine) {
@@ -243,7 +223,7 @@ export class ToolLedgerReducer {
243
223
  });
244
224
  }
245
225
  else {
246
- this.setMap(this.invocationSpines, event.invocationId, spine);
226
+ this.invocationSpines.set(event.invocationId, spine);
247
227
  }
248
228
  const lane = validateToolLedgerEventLane(event);
249
229
  if (!lane.ok) {
@@ -260,7 +240,7 @@ export class ToolLedgerReducer {
260
240
  const existing = this.byToolCall.get(toolCallKey);
261
241
  if (existing) {
262
242
  if (!existing.callEvent) {
263
- this.setOperationField(existing, 'callEvent', event);
243
+ existing.callEvent = event;
264
244
  if (existing.toolName !== content.name) {
265
245
  this.addIssue(existing, {
266
246
  code: 'identity_conflict',
@@ -287,8 +267,8 @@ export class ToolLedgerReducer {
287
267
  decisionEvents: [],
288
268
  issues: [],
289
269
  };
290
- this.setMap(this.byToolCall, toolCallKey, operation);
291
- this.push(this.operations, operation);
270
+ this.byToolCall.set(toolCallKey, operation);
271
+ this.operations.push(operation);
292
272
  return;
293
273
  }
294
274
  if (lane.lane === 'tool_dispatch') {
@@ -307,8 +287,8 @@ export class ToolLedgerReducer {
307
287
  decisionEvents: [],
308
288
  issues: [],
309
289
  };
310
- this.setMap(this.byToolCall, toolCallKey, operation);
311
- this.push(this.operations, operation);
290
+ this.byToolCall.set(toolCallKey, operation);
291
+ this.operations.push(operation);
312
292
  this.addIssue(operation, {
313
293
  code: 'event_order_conflict',
314
294
  eventId: event.id,
@@ -333,14 +313,14 @@ export class ToolLedgerReducer {
333
313
  operationId: dispatch.operationId,
334
314
  toolCallId: dispatch.providerToolCallId,
335
315
  };
336
- this.push(this.issues, issue);
337
- this.push(existingOperation.issues, issue);
338
- this.push(operation.issues, issue);
316
+ this.issues.push(issue);
317
+ existingOperation.issues.push(issue);
318
+ operation.issues.push(issue);
339
319
  return;
340
320
  }
341
- this.setOperationField(operation, 'operationId', dispatch.operationId);
342
- this.setOperationField(operation, 'dispatchEvent', event);
343
- this.setMap(this.byOperation, dispatch.operationId, operation);
321
+ operation.operationId = dispatch.operationId;
322
+ operation.dispatchEvent = event;
323
+ this.byOperation.set(dispatch.operationId, operation);
344
324
  if (operation.toolName !== dispatch.toolName ||
345
325
  event.refs?.operationId !== dispatch.operationId ||
346
326
  event.refs?.toolCallId !== dispatch.providerToolCallId ||
@@ -406,8 +386,8 @@ export class ToolLedgerReducer {
406
386
  decisionEvents: [],
407
387
  issues: [],
408
388
  };
409
- this.push(this.operations, orphan);
410
- this.setMap(this.byToolCall, toolCallKey, orphan);
389
+ this.operations.push(orphan);
390
+ this.byToolCall.set(toolCallKey, orphan);
411
391
  this.addIssue(orphan, {
412
392
  code: 'orphan_response',
413
393
  eventId: event.id,
@@ -424,7 +404,7 @@ export class ToolLedgerReducer {
424
404
  });
425
405
  return;
426
406
  }
427
- this.setOperationField(operation, 'responseEvent', event);
407
+ operation.responseEvent = event;
428
408
  if (operation.toolName !== content.name ||
429
409
  !sameExecutionIdentity(operation.callEvent, event) ||
430
410
  (operation.operationId !== undefined &&
@@ -454,48 +434,15 @@ export class ToolLedgerReducer {
454
434
  return;
455
435
  }
456
436
  if (lane.lane === 'reconcile_result')
457
- this.push(operation.reconcileEvents, event);
437
+ operation.reconcileEvents.push(event);
458
438
  else
459
- this.push(operation.decisionEvents, event);
439
+ operation.decisionEvents.push(event);
460
440
  }
461
441
  }
462
442
  addIssue(operation, issue) {
463
- this.push(this.issues, issue);
443
+ this.issues.push(issue);
464
444
  if (operation)
465
- this.push(operation.issues, issue);
466
- }
467
- push(values, value) {
468
- const length = values.length;
469
- this.undo(() => {
470
- values.length = length;
471
- });
472
- values.push(value);
473
- }
474
- setMap(values, key, value) {
475
- const had = values.has(key);
476
- const previous = values.get(key);
477
- this.undo(() => {
478
- if (had)
479
- values.set(key, previous);
480
- else
481
- values.delete(key);
482
- });
483
- values.set(key, value);
484
- }
485
- setOperationField(operation, key, value) {
486
- const had = Object.hasOwn(operation, key);
487
- const previous = operation[key];
488
- this.undo(() => {
489
- if (had)
490
- operation[key] = previous;
491
- else
492
- delete operation[key];
493
- });
494
- operation[key] = value;
495
- }
496
- undo(operation) {
497
- if (this.recordUndo)
498
- this.undoLog.push(operation);
445
+ operation.issues.push(issue);
499
446
  }
500
447
  toolDependencyIssues() {
501
448
  const result = [];
@@ -579,53 +526,9 @@ export class ToolLedgerReducer {
579
526
  return result;
580
527
  }
581
528
  }
582
- /**
583
- * Applies one prospective writer batch to a checked prefix. A successful call
584
- * leaves the candidate delta applied until the caller commits or rolls back the
585
- * returned checkpoint. A rejected candidate restores the reducer immediately.
586
- */
587
- export function validateIncrementalToolLedgerTransition(input) {
588
- const existing = input.reducer.scan();
589
- if (existing.hasCorruption) {
590
- return { source: 'existing', ...issueValidation(existing.issues[0]) };
591
- }
592
- const candidates = [];
593
- for (const candidate of input.candidateEvents) {
594
- const prior = input.reducer.event(candidate.id);
595
- if (!prior) {
596
- candidates.push(candidate);
597
- continue;
598
- }
599
- if (!nodeUtil.isDeepStrictEqual(prior, candidate)) {
600
- return {
601
- ok: false,
602
- source: 'candidate',
603
- code: 'duplicate_event_id',
604
- eventId: candidate.id,
605
- };
606
- }
607
- }
608
- const shape = validateTransitionShape(candidates, input.expectedTransition);
609
- if (!shape.ok)
610
- return { source: 'candidate', ...shape };
611
- const checkpoint = input.reducer.checkpoint();
612
- try {
613
- input.reducer.append(candidates);
614
- }
615
- catch (error) {
616
- input.reducer.rollback(checkpoint);
617
- throw error;
618
- }
619
- const issue = input.reducer.scan().issues[0];
620
- if (issue) {
621
- input.reducer.rollback(checkpoint);
622
- return { source: 'candidate', ...issueValidation(issue) };
623
- }
624
- return { ok: true, checkpoint, appendedEvents: candidates.length };
625
- }
626
529
  /**
627
530
  * Scans immutable RuntimeEvents once, in physical ledger order. Resolver,
628
- * projection rebuild and incremental writers all use the same reducer rules.
531
+ * projection rebuild and prospective writers all use the same reducer rules.
629
532
  */
630
533
  export function scanToolLedger(events) {
631
534
  return new ToolLedgerReducer(events).scan();
@@ -132,7 +132,14 @@ export class AiSdkCompaction {
132
132
  if (previousCheckpoint) {
133
133
  const match = matchHistoryCompactCheckpointPrefix(previousCheckpoint, runtimeContext);
134
134
  if (!match.reason && match.successorRuntimeEvents.length === 0) {
135
- {
135
+ // Raw identity is not enough: a projection transition committed
136
+ // after the fold rewrites the covered span's effective view without
137
+ // touching the raw ledger, so reuse requires the pinned effective
138
+ // digest to still match — the same gate the pre-send path applies
139
+ // (#5929). On drift, fall through to the planner, whose roll-forward
140
+ // currency check discards the stale checkpoint and re-summarizes.
141
+ const effectiveCovered = await this.foldEffectiveModelHistory(match.coveredRuntimeEvents);
142
+ if (this.checkpointEffectiveCoverageMatches(previousCheckpoint, effectiveCovered)) {
136
143
  const projectedEvents = projectHistoryCompactCheckpointReplay(previousCheckpoint, match.coveredRuntimeEvents, []);
137
144
  return {
138
145
  outcome: { kind: 'unchanged', reason: 'already_compacted' },
@@ -529,6 +529,12 @@ export async function cloneConversationRuntimeLedger(input) {
529
529
  await input.runStore.appendEvent(input.referenceMap.targetSessionId, runId, clonedEvent);
530
530
  }
531
531
  if (plan.terminal.kind === 'fact' && terminalEvent) {
532
+ // The preflight rejects handoff lineage before a plan reaches cloning,
533
+ // so a handed-off run never gets here; the guard keeps the outcome type
534
+ // honest at the boundary.
535
+ if (plan.terminal.fact.runStatus === 'handed_off') {
536
+ throw new Error(`Copied AgentRun ${plan.run.runId} carries unresolved handoff lineage`);
537
+ }
532
538
  await commitTerminalRunWithRuntimeFact({
533
539
  runtimeEventStore: input.runtimeEventStore,
534
540
  newId: input.newId,
@@ -567,6 +567,23 @@ export function classifyRuntimeEventTerminalFact(invocation, events) {
567
567
  return { diagnostics };
568
568
  }
569
569
  const terminalEvent = terminalSignals[0];
570
+ // A handoff pause seals the run without ending the turn: the successor run
571
+ // owns the turn's terminal state. The pause carries no terminal status by
572
+ // design — completed/failed/aborted would each be a lie about a run that was
573
+ // none of them — so it classifies as its own status. The event is immutable
574
+ // once written, so ledgers written before this classification carry exactly
575
+ // this shape.
576
+ if (terminalEvent.actions?.handoffPause) {
577
+ const fact = {
578
+ runId: invocation.runId,
579
+ turnId: invocation.turnId,
580
+ runStatus: 'handed_off',
581
+ turnStatus: 'handed_off',
582
+ terminalEvent,
583
+ diagnostics,
584
+ };
585
+ return { fact, diagnostics };
586
+ }
570
587
  if (!isTerminalRuntimeEventStatus(terminalEvent.status)) {
571
588
  diagnostics.push(readModelDiagnostic('incomplete_event', 'terminal RuntimeEvent requires a terminal status for recovery', terminalEvent));
572
589
  return { diagnostics };