maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923

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 (131) hide show
  1. package/DISCLAIMER-WIP +6 -1
  2. package/README.md +6 -1
  3. package/dist/acp/maka-acp-agent.js +1 -1
  4. package/dist/acp/session-mcp.js +212 -0
  5. package/dist/acp/session-registry.js +52 -8
  6. package/dist/acp/stdio-server.js +3 -0
  7. package/dist/cli-core.js +1 -1
  8. package/dist/mcp-capability-provider.js +6 -1
  9. package/dist/mcp-capability-publication.js +139 -0
  10. package/dist/pi-tui-layout.js +46 -17
  11. package/dist/pi-tui-pickers.js +25 -14
  12. package/dist/pi-tui-runner.js +24 -15
  13. package/dist/runtime-host-capability-provider-command.js +25 -6
  14. package/dist/runtime-host-service-manager.js +26 -2
  15. package/dist/tui-context-refresh.js +15 -26
  16. package/dist/tui-mcp-control.js +66 -112
  17. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  18. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  19. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  20. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  21. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  22. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  23. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  24. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  25. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  26. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  27. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  28. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  29. package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
  30. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  31. package/node_modules/@maka/core/dist/mcp.js +4 -1
  32. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  33. package/node_modules/@maka/core/dist/model-thinking.js +36 -1
  34. package/node_modules/@maka/core/dist/provider-registry.js +0 -1
  35. package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
  36. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  37. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  38. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  40. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  41. package/node_modules/@maka/core/dist/session.js +19 -1
  42. package/node_modules/@maka/core/dist/settings.js +3 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  45. package/node_modules/@maka/core/package.json +3 -0
  46. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  47. package/node_modules/@maka/mcp/dist/index.js +31 -5
  48. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  49. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  50. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
  51. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
  52. package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
  53. package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
  54. package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
  55. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  56. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  57. package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
  58. package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
  59. package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
  60. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
  61. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  62. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  63. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  64. package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
  65. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  66. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  67. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  68. package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
  69. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
  70. package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
  71. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  72. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
  74. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  75. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  76. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
  77. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
  78. package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  80. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
  81. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
  82. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  83. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  84. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
  85. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
  86. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  87. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  89. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
  90. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
  91. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  92. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
  93. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  94. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  95. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  96. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  97. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
  98. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  99. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  100. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  101. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  102. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  103. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  104. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  105. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  106. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  107. package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
  109. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  110. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
  111. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  112. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  114. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  115. package/node_modules/@maka/runtime-host/package.json +2 -0
  116. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  117. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  118. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  119. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  120. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  121. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  122. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  123. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  124. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  125. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  126. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  127. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  128. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  129. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  130. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  131. package/package.json +1 -1
@@ -27,11 +27,11 @@ import { canonicalToolArgsHash } from './tool-args-identity.js';
27
27
  * at `running` with no terminal event at all (#2234). This error is always a
28
28
  * producer bug: something emitted a fact the ledger's invariants forbid.
29
29
  *
30
- * It deliberately does NOT cover a ledger that is already corrupt. That refusal
31
- * rejects well-formed candidates because of damage elsewhere in the workspace,
32
- * so "the store is healthy" is false and the run must keep failing closed —
33
- * see `ToolLedgerCorruptionError`, which is a plain durability failure and is
34
- * classified as one.
30
+ * It deliberately does NOT cover a ledger dependency closure that is already
31
+ * corrupt. That refusal rejects well-formed candidates because of damage in
32
+ * the facts they depend on, so "the store is healthy for this run" is false and
33
+ * the run must keep failing closed — see `ToolLedgerCorruptionError`, which is
34
+ * a plain durability failure and is classified as one.
35
35
  */
36
36
  export class ToolLedgerRejectionError extends Error {
37
37
  code;
@@ -44,10 +44,11 @@ export class ToolLedgerRejectionError extends Error {
44
44
  }
45
45
  }
46
46
  /**
47
- * The ledger the store already holds is corrupt, so it refuses writes that have
48
- * nothing wrong with them. Unlike `ToolLedgerRejectionError` this is not a
49
- * producer bug and the store is not usable: nothing the run emits next can be
50
- * trusted to land, so it stays on the fail-closed path.
47
+ * The selected tool dependency closure is already corrupt, so it refuses a
48
+ * candidate that has nothing wrong with it. Unlike `ToolLedgerRejectionError`
49
+ * this is not a producer bug. The affected run stays on the fail-closed path;
50
+ * unrelated closures remain writable and explicit full-ledger audit owns
51
+ * discovery outside the active closure.
51
52
  */
52
53
  export class ToolLedgerCorruptionError extends Error {
53
54
  code;
@@ -59,6 +60,23 @@ export class ToolLedgerCorruptionError extends Error {
59
60
  this.eventId = eventId;
60
61
  }
61
62
  }
63
+ /** Operation identities whose invocation closure must accompany these facts. */
64
+ export function referencedToolOperationIds(events) {
65
+ const operationIds = new Set();
66
+ for (const event of events) {
67
+ const dispatchOperationId = event.actions?.toolDispatch?.operationId;
68
+ const recoveryOperationId = event.actions?.toolRecovery?.payload.operationId;
69
+ if (dispatchOperationId)
70
+ operationIds.add(dispatchOperationId);
71
+ if (recoveryOperationId)
72
+ operationIds.add(recoveryOperationId);
73
+ if (event.refs?.operationId)
74
+ operationIds.add(event.refs.operationId);
75
+ if (event.refs?.parentOperationId)
76
+ operationIds.add(event.refs.parentOperationId);
77
+ }
78
+ return operationIds;
79
+ }
62
80
  /**
63
81
  * Enforces the semantic boundary for durable tool-ledger facts.
64
82
  *
@@ -113,95 +131,153 @@ export function validateGenericToolLedgerAppend(event) {
113
131
  * before committing its candidate events.
114
132
  */
115
133
  export function validateToolLedgerTransition(input) {
116
- const existing = scanToolLedger(input.existingEvents);
117
- if (existing.hasCorruption)
118
- return issueValidation(existing.issues[0]);
119
- const existingById = new Map(input.existingEvents.map((event) => [event.id, event]));
120
- const candidates = [];
121
- for (const candidate of input.candidateEvents) {
122
- const prior = existingById.get(candidate.id);
123
- if (!prior) {
124
- candidates.push(candidate);
125
- continue;
126
- }
127
- if (!nodeUtil.isDeepStrictEqual(prior, candidate)) {
128
- return { ok: false, code: 'duplicate_event_id', eventId: candidate.id };
129
- }
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 };
130
143
  }
131
- const shape = validateTransitionShape(candidates, input.expectedTransition);
132
- if (!shape.ok)
133
- return shape;
134
- const prospective = scanToolLedger([...input.existingEvents, ...candidates]);
135
- if (prospective.hasCorruption)
136
- return issueValidation(prospective.issues[0]);
137
- return { ok: true };
144
+ const { source: _source, ...failure } = validation;
145
+ return failure;
138
146
  }
139
147
  /**
140
- * Scans immutable RuntimeEvents once, in physical ledger order. It owns the
141
- * duplicate/order/identity interpretation shared by Resolver and projection
142
- * rebuild; neither consumer may rebuild these maps independently.
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.
143
154
  */
144
- export function scanToolLedger(events) {
145
- const operations = [];
146
- const issues = [];
147
- const seenEventIds = new Set();
148
- const byToolCall = new Map();
149
- const byOperation = new Map();
150
- const invocationSpines = new Map();
151
- const addIssue = (operation, issue) => {
152
- issues.push(issue);
153
- operation?.issues.push(issue);
154
- };
155
- for (const event of events) {
156
- if (seenEventIds.has(event.id)) {
157
- addIssue(undefined, { code: 'duplicate_event_id', eventId: event.id });
158
- continue;
155
+ export class ToolLedgerReducer {
156
+ operations = [];
157
+ issues = [];
158
+ eventsById = new Map();
159
+ byToolCall = new Map();
160
+ byOperation = new Map();
161
+ invocationSpines = new Map();
162
+ undoLog = [];
163
+ recordUndo = false;
164
+ constructor(events = []) {
165
+ this.consume(events, false);
166
+ }
167
+ scan() {
168
+ const dependencyIssues = this.toolDependencyIssues();
169
+ if (dependencyIssues.length === 0) {
170
+ return {
171
+ operations: this.operations,
172
+ issues: this.issues,
173
+ hasCorruption: this.issues.length > 0,
174
+ };
175
+ }
176
+ const issuesByOperation = new Map();
177
+ for (const { operation, issue } of dependencyIssues) {
178
+ const issues = issuesByOperation.get(operation);
179
+ if (issues)
180
+ issues.push(issue);
181
+ else
182
+ issuesByOperation.set(operation, [issue]);
159
183
  }
160
- seenEventIds.add(event.id);
184
+ return {
185
+ operations: this.operations.map((operation) => {
186
+ const dependency = issuesByOperation.get(operation);
187
+ return dependency
188
+ ? { ...operation, issues: [...operation.issues, ...dependency] }
189
+ : operation;
190
+ }),
191
+ issues: [...this.issues, ...dependencyIssues.map(({ issue }) => issue)],
192
+ hasCorruption: true,
193
+ };
194
+ }
195
+ event(eventId) {
196
+ return this.eventsById.get(eventId);
197
+ }
198
+ checkpoint() {
199
+ return this.undoLog.length;
200
+ }
201
+ 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
+ }
230
+ }
231
+ consumeEvent(event) {
232
+ if (this.eventsById.has(event.id)) {
233
+ this.addIssue(undefined, { code: 'duplicate_event_id', eventId: event.id });
234
+ return;
235
+ }
236
+ this.setMap(this.eventsById, event.id, event);
161
237
  const spine = JSON.stringify([event.sessionId, event.runId, event.turnId]);
162
- const existingSpine = invocationSpines.get(event.invocationId);
238
+ const existingSpine = this.invocationSpines.get(event.invocationId);
163
239
  if (existingSpine !== undefined && existingSpine !== spine) {
164
- addIssue(undefined, {
240
+ this.addIssue(undefined, {
165
241
  code: 'invocation_identity_conflict',
166
242
  eventId: event.id,
167
243
  });
168
244
  }
169
245
  else {
170
- invocationSpines.set(event.invocationId, spine);
246
+ this.setMap(this.invocationSpines, event.invocationId, spine);
171
247
  }
172
248
  const lane = validateToolLedgerEventLane(event);
173
249
  if (!lane.ok) {
174
- addIssue(undefined, { code: lane.code, eventId: lane.eventId });
175
- continue;
250
+ this.addIssue(undefined, { code: lane.code, eventId: lane.eventId });
251
+ return;
176
252
  }
177
253
  if (event.partial)
178
- continue;
254
+ return;
179
255
  if (lane.lane === 'function_call') {
180
256
  const content = event.content;
181
257
  if (content?.kind !== 'function_call')
182
- continue;
258
+ return;
183
259
  const toolCallKey = toolCallIdentity(event.invocationId, content.id);
184
- const existing = byToolCall.get(toolCallKey);
260
+ const existing = this.byToolCall.get(toolCallKey);
185
261
  if (existing) {
186
262
  if (!existing.callEvent) {
187
- existing.callEvent = event;
263
+ this.setOperationField(existing, 'callEvent', event);
188
264
  if (existing.toolName !== content.name) {
189
- addIssue(existing, {
265
+ this.addIssue(existing, {
190
266
  code: 'identity_conflict',
191
267
  eventId: event.id,
192
268
  toolCallId: content.id,
193
269
  ...(existing.operationId ? { operationId: existing.operationId } : {}),
194
270
  });
195
271
  }
196
- continue;
272
+ return;
197
273
  }
198
- addIssue(existing, {
274
+ this.addIssue(existing, {
199
275
  code: 'duplicate_call',
200
276
  eventId: event.id,
201
277
  toolCallId: content.id,
202
278
  ...(existing.operationId ? { operationId: existing.operationId } : {}),
203
279
  });
204
- continue;
280
+ return;
205
281
  }
206
282
  const operation = {
207
283
  toolCallId: content.id,
@@ -211,16 +287,16 @@ export function scanToolLedger(events) {
211
287
  decisionEvents: [],
212
288
  issues: [],
213
289
  };
214
- byToolCall.set(toolCallKey, operation);
215
- operations.push(operation);
216
- continue;
290
+ this.setMap(this.byToolCall, toolCallKey, operation);
291
+ this.push(this.operations, operation);
292
+ return;
217
293
  }
218
294
  if (lane.lane === 'tool_dispatch') {
219
295
  const dispatch = event.actions?.toolDispatch;
220
296
  if (!dispatch)
221
- continue;
297
+ return;
222
298
  const toolCallKey = toolCallIdentity(event.invocationId, dispatch.providerToolCallId);
223
- let operation = byToolCall.get(toolCallKey);
299
+ let operation = this.byToolCall.get(toolCallKey);
224
300
  if (!operation) {
225
301
  operation = {
226
302
  toolCallId: dispatch.providerToolCallId,
@@ -231,9 +307,9 @@ export function scanToolLedger(events) {
231
307
  decisionEvents: [],
232
308
  issues: [],
233
309
  };
234
- byToolCall.set(toolCallKey, operation);
235
- operations.push(operation);
236
- addIssue(operation, {
310
+ this.setMap(this.byToolCall, toolCallKey, operation);
311
+ this.push(this.operations, operation);
312
+ this.addIssue(operation, {
237
313
  code: 'event_order_conflict',
238
314
  eventId: event.id,
239
315
  operationId: dispatch.operationId,
@@ -241,15 +317,15 @@ export function scanToolLedger(events) {
241
317
  });
242
318
  }
243
319
  else if (operation.dispatchEvent) {
244
- addIssue(operation, {
320
+ this.addIssue(operation, {
245
321
  code: 'duplicate_dispatch',
246
322
  eventId: event.id,
247
323
  operationId: dispatch.operationId,
248
324
  toolCallId: dispatch.providerToolCallId,
249
325
  });
250
- continue;
326
+ return;
251
327
  }
252
- const existingOperation = byOperation.get(dispatch.operationId);
328
+ const existingOperation = this.byOperation.get(dispatch.operationId);
253
329
  if (existingOperation && existingOperation !== operation) {
254
330
  const issue = {
255
331
  code: 'duplicate_operation',
@@ -257,19 +333,20 @@ export function scanToolLedger(events) {
257
333
  operationId: dispatch.operationId,
258
334
  toolCallId: dispatch.providerToolCallId,
259
335
  };
260
- issues.push(issue);
261
- existingOperation.issues.push(issue);
262
- operation.issues.push(issue);
263
- continue;
336
+ this.push(this.issues, issue);
337
+ this.push(existingOperation.issues, issue);
338
+ this.push(operation.issues, issue);
339
+ return;
264
340
  }
265
- operation.operationId = dispatch.operationId;
266
- operation.dispatchEvent = event;
267
- byOperation.set(dispatch.operationId, operation);
341
+ this.setOperationField(operation, 'operationId', dispatch.operationId);
342
+ this.setOperationField(operation, 'dispatchEvent', event);
343
+ this.setMap(this.byOperation, dispatch.operationId, operation);
268
344
  if (operation.toolName !== dispatch.toolName ||
269
345
  event.refs?.operationId !== dispatch.operationId ||
270
346
  event.refs?.toolCallId !== dispatch.providerToolCallId ||
271
- (operation.callEvent !== undefined && !sameExecutionIdentity(operation.callEvent, event))) {
272
- addIssue(operation, {
347
+ (operation.callEvent !== undefined && !sameExecutionIdentity(operation.callEvent, event)) ||
348
+ callRefsConflict(operation.callEvent, event)) {
349
+ this.addIssue(operation, {
273
350
  code: 'identity_conflict',
274
351
  eventId: event.id,
275
352
  operationId: dispatch.operationId,
@@ -286,7 +363,7 @@ export function scanToolLedger(events) {
286
363
  // A provider call that is not strict JSON cannot authenticate T1.
287
364
  }
288
365
  if (canonicalArgsHash !== dispatch.canonicalArgsHash) {
289
- addIssue(operation, {
366
+ this.addIssue(operation, {
290
367
  code: 'canonical_args_hash_conflict',
291
368
  eventId: event.id,
292
369
  operationId: dispatch.operationId,
@@ -295,7 +372,7 @@ export function scanToolLedger(events) {
295
372
  }
296
373
  }
297
374
  if (operation.responseEvent) {
298
- addIssue(operation, {
375
+ this.addIssue(operation, {
299
376
  code: 'event_order_conflict',
300
377
  eventId: event.id,
301
378
  operationId: dispatch.operationId,
@@ -304,7 +381,7 @@ export function scanToolLedger(events) {
304
381
  if (operation.responseEvent.refs?.operationId !== dispatch.operationId ||
305
382
  operation.responseEvent.refs?.toolCallId !== dispatch.providerToolCallId ||
306
383
  !sameExecutionIdentity(event, operation.responseEvent)) {
307
- addIssue(operation, {
384
+ this.addIssue(operation, {
308
385
  code: 'identity_conflict',
309
386
  eventId: operation.responseEvent.id,
310
387
  operationId: dispatch.operationId,
@@ -312,14 +389,14 @@ export function scanToolLedger(events) {
312
389
  });
313
390
  }
314
391
  }
315
- continue;
392
+ return;
316
393
  }
317
394
  if (lane.lane === 'function_response') {
318
395
  const content = event.content;
319
396
  if (content?.kind !== 'function_response')
320
- continue;
397
+ return;
321
398
  const toolCallKey = toolCallIdentity(event.invocationId, content.id);
322
- const operation = byToolCall.get(toolCallKey);
399
+ const operation = this.byToolCall.get(toolCallKey);
323
400
  if (!operation) {
324
401
  const orphan = {
325
402
  toolCallId: content.id,
@@ -329,60 +406,229 @@ export function scanToolLedger(events) {
329
406
  decisionEvents: [],
330
407
  issues: [],
331
408
  };
332
- operations.push(orphan);
333
- byToolCall.set(toolCallKey, orphan);
334
- addIssue(orphan, {
409
+ this.push(this.operations, orphan);
410
+ this.setMap(this.byToolCall, toolCallKey, orphan);
411
+ this.addIssue(orphan, {
335
412
  code: 'orphan_response',
336
413
  eventId: event.id,
337
414
  toolCallId: content.id,
338
415
  });
339
- continue;
416
+ return;
340
417
  }
341
418
  if (operation.responseEvent) {
342
- addIssue(operation, {
419
+ this.addIssue(operation, {
343
420
  code: 'duplicate_response',
344
421
  eventId: event.id,
345
422
  toolCallId: content.id,
346
423
  ...(operation.operationId ? { operationId: operation.operationId } : {}),
347
424
  });
348
- continue;
425
+ return;
349
426
  }
350
- operation.responseEvent = event;
427
+ this.setOperationField(operation, 'responseEvent', event);
351
428
  if (operation.toolName !== content.name ||
352
429
  !sameExecutionIdentity(operation.callEvent, event) ||
353
430
  (operation.operationId !== undefined &&
354
431
  (event.refs?.operationId !== operation.operationId ||
355
432
  event.refs.toolCallId !== operation.toolCallId))) {
356
- addIssue(operation, {
433
+ this.addIssue(operation, {
357
434
  code: 'identity_conflict',
358
435
  eventId: event.id,
359
436
  toolCallId: content.id,
360
437
  ...(operation.operationId ? { operationId: operation.operationId } : {}),
361
438
  });
362
439
  }
363
- continue;
440
+ return;
364
441
  }
365
442
  if (lane.lane === 'reconcile_result' || lane.lane === 'recovery_decision') {
366
443
  const fact = event.actions?.toolRecovery;
367
444
  if (!fact)
368
- continue;
369
- const operation = byOperation.get(fact.payload.operationId);
445
+ return;
446
+ const operation = this.byOperation.get(fact.payload.operationId);
370
447
  if (!operation) {
371
- addIssue(undefined, {
448
+ this.addIssue(undefined, {
372
449
  code: 'event_order_conflict',
373
450
  eventId: event.id,
374
451
  operationId: fact.payload.operationId,
375
452
  ...(event.refs?.toolCallId ? { toolCallId: event.refs.toolCallId } : {}),
376
453
  });
377
- continue;
454
+ return;
378
455
  }
379
456
  if (lane.lane === 'reconcile_result')
380
- operation.reconcileEvents.push(event);
457
+ this.push(operation.reconcileEvents, event);
381
458
  else
382
- operation.decisionEvents.push(event);
459
+ this.push(operation.decisionEvents, event);
383
460
  }
384
461
  }
385
- return { operations, issues, hasCorruption: issues.length > 0 };
462
+ addIssue(operation, issue) {
463
+ this.push(this.issues, issue);
464
+ 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);
499
+ }
500
+ toolDependencyIssues() {
501
+ const result = [];
502
+ const parents = new Map();
503
+ for (const operation of this.operations) {
504
+ const dispatch = operation.dispatchEvent;
505
+ const parentOperationId = dispatch?.refs?.parentOperationId;
506
+ const parentToolCallId = dispatch?.refs?.parentToolCallId;
507
+ if (!dispatch || !parentOperationId || !parentToolCallId)
508
+ continue;
509
+ const parent = this.byOperation.get(parentOperationId);
510
+ if (!parent) {
511
+ result.push({
512
+ operation,
513
+ issue: {
514
+ code: 'parent_operation_missing',
515
+ eventId: dispatch.id,
516
+ ...(operation.operationId ? { operationId: operation.operationId } : {}),
517
+ toolCallId: operation.toolCallId,
518
+ },
519
+ });
520
+ continue;
521
+ }
522
+ if (parent.toolCallId !== parentToolCallId) {
523
+ result.push({
524
+ operation,
525
+ issue: {
526
+ code: 'parent_identity_conflict',
527
+ eventId: dispatch.id,
528
+ ...(operation.operationId ? { operationId: operation.operationId } : {}),
529
+ toolCallId: operation.toolCallId,
530
+ },
531
+ });
532
+ continue;
533
+ }
534
+ parents.set(operation, parent);
535
+ }
536
+ const state = new Map();
537
+ const stack = [];
538
+ const stackIndex = new Map();
539
+ const cyclic = new Set();
540
+ const visit = (operation) => {
541
+ if (state.get(operation) === 'visited')
542
+ return;
543
+ state.set(operation, 'visiting');
544
+ stackIndex.set(operation, stack.length);
545
+ stack.push(operation);
546
+ const parent = parents.get(operation);
547
+ if (parent) {
548
+ const parentState = state.get(parent);
549
+ if (parentState === 'visiting') {
550
+ const index = stackIndex.get(parent);
551
+ if (index !== undefined) {
552
+ for (let cursor = index; cursor < stack.length; cursor += 1)
553
+ cyclic.add(stack[cursor]);
554
+ }
555
+ }
556
+ else if (parentState !== 'visited') {
557
+ visit(parent);
558
+ }
559
+ }
560
+ stack.pop();
561
+ stackIndex.delete(operation);
562
+ state.set(operation, 'visited');
563
+ };
564
+ for (const operation of parents.keys())
565
+ visit(operation);
566
+ for (const operation of this.operations) {
567
+ if (!cyclic.has(operation) || !operation.dispatchEvent)
568
+ continue;
569
+ result.push({
570
+ operation,
571
+ issue: {
572
+ code: 'parent_dependency_cycle',
573
+ eventId: operation.dispatchEvent.id,
574
+ ...(operation.operationId ? { operationId: operation.operationId } : {}),
575
+ toolCallId: operation.toolCallId,
576
+ },
577
+ });
578
+ }
579
+ return result;
580
+ }
581
+ }
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
+ /**
627
+ * Scans immutable RuntimeEvents once, in physical ledger order. Resolver,
628
+ * projection rebuild and incremental writers all use the same reducer rules.
629
+ */
630
+ export function scanToolLedger(events) {
631
+ return new ToolLedgerReducer(events).scan();
386
632
  }
387
633
  function matchesLaneEnvelope(event, lane) {
388
634
  if (event.partial || event.status !== undefined || event.branch !== undefined)
@@ -472,6 +718,21 @@ function sameExecutionIdentity(first, second) {
472
718
  first.runId === second.runId &&
473
719
  first.turnId === second.turnId);
474
720
  }
721
+ function callRefsConflict(call, dispatch) {
722
+ if (!call?.refs)
723
+ return false;
724
+ const expected = dispatch.actions?.toolDispatch;
725
+ if (!expected)
726
+ return false;
727
+ if ((call.refs.operationId !== undefined && call.refs.operationId !== expected.operationId) ||
728
+ (call.refs.toolCallId !== undefined && call.refs.toolCallId !== expected.providerToolCallId)) {
729
+ return true;
730
+ }
731
+ const callHasParent = call.refs.parentOperationId !== undefined || call.refs.parentToolCallId !== undefined;
732
+ return (callHasParent &&
733
+ (call.refs.parentOperationId !== dispatch.refs?.parentOperationId ||
734
+ call.refs.parentToolCallId !== dispatch.refs?.parentToolCallId));
735
+ }
475
736
  function hasOnlyKeys(value, expected) {
476
737
  if (!value)
477
738
  return false;
@@ -25,6 +25,20 @@ const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape()(['kind', 'graphId', 'wakeId
25
25
  export function decodeTurnOrigin(value) {
26
26
  if (!isRecord(value))
27
27
  return undefined;
28
+ if (value.kind === 'workhub_result') {
29
+ const keys = ['kind', 'eventId', 'actionId', 'delegationId', 'targetSessionId', 'targetTurnId'];
30
+ if (Object.keys(value).length !== keys.length ||
31
+ !keys.every((key) => typeof value[key] === 'string' && value[key].length > 0 && value[key].length <= 256))
32
+ return undefined;
33
+ return {
34
+ kind: 'workhub_result',
35
+ eventId: value.eventId,
36
+ actionId: value.actionId,
37
+ delegationId: value.delegationId,
38
+ targetSessionId: value.targetSessionId,
39
+ targetTurnId: value.targetTurnId,
40
+ };
41
+ }
28
42
  if (hasExactShape(value, SCHEDULED_TASK_ORIGIN_SHAPE) &&
29
43
  value.kind === 'scheduled_task' &&
30
44
  typeof value.scheduledTaskId === 'string') {