@agent-native/core 0.208.0 → 0.209.0-nightly-20261014070126

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 (74) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/agent/approval-key.d.ts +2 -0
  3. package/dist/agent/approval-key.js +12 -0
  4. package/dist/agent/context-xray/tokenize.js +12 -3
  5. package/dist/agent/engine/translate-anthropic.js +4 -0
  6. package/dist/agent/engine/types.d.ts +2 -0
  7. package/dist/agent/production-agent.d.ts +20 -5
  8. package/dist/agent/production-agent.js +227 -34
  9. package/dist/agent/run-manager.js +57 -11
  10. package/dist/agent/run-store.d.ts +10 -0
  11. package/dist/agent/run-store.js +49 -4
  12. package/dist/agent/thread-data-builder.d.ts +2 -0
  13. package/dist/agent/thread-data-builder.js +288 -14
  14. package/dist/agent/tool-approval-store.d.ts +3 -1
  15. package/dist/agent/tool-approval-store.js +26 -12
  16. package/dist/agent/tool-call-journal.d.ts +1 -1
  17. package/dist/agent/tool-call-journal.js +5 -3
  18. package/dist/agent/types.d.ts +7 -0
  19. package/dist/client/chat/agentkit-agent-native.js +18 -0
  20. package/dist/client/chat/agentkit-protocol.js +56 -5
  21. package/dist/client/chat/runtime.d.ts +10 -0
  22. package/dist/client/chat/runtime.js +257 -26
  23. package/dist/client/sse-event-processor.d.ts +22 -0
  24. package/dist/client/sse-event-processor.js +91 -11
  25. package/dist/collab/struct-routes.d.ts +1 -1
  26. package/dist/connections/catalog.d.ts +24 -1
  27. package/dist/connections/catalog.js +12 -0
  28. package/dist/data-programs/schema.d.ts +1 -0
  29. package/dist/data-programs/schema.js +3 -0
  30. package/dist/data-programs/store.js +2 -1
  31. package/dist/deploy/build.js +16 -14
  32. package/dist/eject/workspace-connections.js +1 -0
  33. package/dist/extensions/fetch-tool.js +114 -23
  34. package/dist/extensions/schema.d.ts +1 -0
  35. package/dist/extensions/schema.js +3 -0
  36. package/dist/extensions/store.js +2 -1
  37. package/dist/extensions/web-content.d.ts +5 -1
  38. package/dist/extensions/web-content.js +24 -6
  39. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  40. package/dist/notifications/routes.d.ts +1 -1
  41. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  42. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  43. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  44. package/dist/provider-api/index.d.ts +2 -0
  45. package/dist/provider-api/index.js +95 -12
  46. package/dist/resources/handlers.d.ts +1 -1
  47. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  48. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  49. package/dist/server/auth.js +1 -0
  50. package/dist/server/core-routes-plugin.js +1 -0
  51. package/dist/server/index.d.ts +2 -1
  52. package/dist/server/index.js +2 -1
  53. package/dist/server/realtime-token.d.ts +1 -1
  54. package/dist/server/transcribe-voice.d.ts +1 -1
  55. package/dist/server/workspace-provider-oauth.d.ts +1 -1
  56. package/dist/server/workspace-provider-oauth.js +2 -0
  57. package/dist/shared/context-status.js +1 -1
  58. package/dist/shared/optional-peer-ssr.d.ts +2 -0
  59. package/dist/shared/optional-peer-ssr.js +123 -0
  60. package/dist/shared/tool-inputs.d.ts +2 -0
  61. package/dist/shared/tool-inputs.js +56 -0
  62. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  63. package/dist/tracking/index.d.ts +1 -0
  64. package/dist/tracking/index.js +1 -0
  65. package/dist/vite/client.js +46 -3
  66. package/dist/workspace-connections/store.d.ts +3 -1
  67. package/dist/workspace-connections/store.js +18 -8
  68. package/dist/workspace-files/constants.d.ts +3 -0
  69. package/dist/workspace-files/constants.js +3 -0
  70. package/dist/workspace-files/store.d.ts +2 -3
  71. package/dist/workspace-files/store.js +2 -3
  72. package/docs/content/automations.mdx +19 -13
  73. package/package.json +3 -3
  74. package/src/eject/workspace-connections.ts +1 -0
@@ -4,6 +4,8 @@ import { isCredentialGapCodeAgentEvent, normalizeCodeAgentTranscript, } from "..
4
4
  import { stripAgentChatContextFromMessage } from "../shared/agent-chat-context.js";
5
5
  import { RUN_NOT_STARTED_METADATA_KEY, } from "../shared/agent-chat-run-not-started.js";
6
6
  import { parseBase64DataUrl } from "../shared/data-url.js";
7
+ import { toolInputFingerprint, toolInputsMatch, } from "../shared/tool-inputs.js";
8
+ import { approvalKeyForHistory } from "./approval-key.js";
7
9
  import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE } from "./engine/error-detail.js";
8
10
  import { INTERRUPTED_TOOL_RESULT_MARKER as INTERRUPTED_TOOL_RESULT, stringifyToolUseInputForGateway, } from "./engine/translate-anthropic.js";
9
11
  import { parseFollowUpSuggestions } from "./follow-up-suggestions.js";
@@ -138,6 +140,7 @@ function isInternalContinuationError(event) {
138
140
  export function buildAssistantMessage(events, runId, options = {}) {
139
141
  const content = [];
140
142
  const explicitUnknownOutcomes = new Set();
143
+ const approvalPauseCandidates = new Set();
141
144
  let toolCallCounter = 0;
142
145
  let runError = null;
143
146
  let endedAtInternalContinuationBoundary = false;
@@ -203,16 +206,17 @@ export function buildAssistantMessage(events, runId, options = {}) {
203
206
  continue;
204
207
  }
205
208
  if (event.type === "approval_required") {
206
- const matchingIndex = findApprovalToolCallIndex(content, event.tool ?? "unknown", event.toolCallId);
209
+ const matchingIndex = findApprovalToolCallIndex(content, event.tool ?? "unknown", event.toolCallId, event.input);
207
210
  const part = content[matchingIndex];
208
211
  if (part?.type === "tool-call") {
209
212
  part.approval = {
210
- approvalKey: event.approvalKey,
213
+ approvalKey: approvalKeyForHistory(event.approvalKey),
211
214
  ...(event.askId ? { askId: event.askId } : {}),
212
215
  ...(event.allowPersistentApproval === false
213
216
  ? { allowPersistentApproval: false }
214
217
  : {}),
215
218
  };
219
+ approvalPauseCandidates.add(part);
216
220
  }
217
221
  continue;
218
222
  }
@@ -244,6 +248,17 @@ export function buildAssistantMessage(events, runId, options = {}) {
244
248
  const part = content[matchingIndex];
245
249
  if (part?.type === "tool-call") {
246
250
  part.result = event.result ?? "";
251
+ // Older journals have approval_required but no pause marker on tool_done.
252
+ const inferredLegacyPause = event.approvalPause === undefined &&
253
+ approvalPauseCandidates.has(part);
254
+ if (event.approvalPause === true || inferredLegacyPause) {
255
+ part.approvalPause = true;
256
+ }
257
+ else {
258
+ delete part.approvalPause;
259
+ delete part.approval;
260
+ }
261
+ approvalPauseCandidates.delete(part);
247
262
  if (event.isError !== undefined)
248
263
  part.isError = event.isError;
249
264
  if (event.outcomeUnknown === true) {
@@ -484,7 +499,7 @@ function normalizeAttachmentIdentity(attachments) {
484
499
  contentType: att?.contentType,
485
500
  }));
486
501
  }
487
- function findApprovalToolCallIndex(content, toolName, toolCallId) {
502
+ function findApprovalToolCallIndex(content, toolName, toolCallId, approvalInput) {
488
503
  if (toolCallId) {
489
504
  for (let i = content.length - 1; i >= 0; i--) {
490
505
  const part = content[i];
@@ -501,21 +516,26 @@ function findApprovalToolCallIndex(content, toolName, toolCallId) {
501
516
  part.toolName === toolName &&
502
517
  part.result === undefined &&
503
518
  typeof part.toolCallId === "string" &&
504
- /^tc_\d+$/.test(part.toolCallId)) {
519
+ /(?:^|:)tc_\d+$/.test(part.toolCallId) &&
520
+ toolInputsMatch(part.args, approvalInput)) {
505
521
  readerLocalCandidates.push(i);
506
522
  }
507
523
  }
508
524
  return readerLocalCandidates.length === 1 ? readerLocalCandidates[0] : -1;
509
525
  }
510
- for (let i = content.length - 1; i >= 0; i--) {
526
+ const matchingInputCandidates = [];
527
+ for (let i = 0; i < content.length; i += 1) {
511
528
  const part = content[i];
512
529
  if (part.type === "tool-call" &&
513
530
  part.toolName === toolName &&
514
- part.result === undefined) {
515
- return i;
531
+ part.result === undefined &&
532
+ toolInputsMatch(part.args, approvalInput)) {
533
+ matchingInputCandidates.push(i);
516
534
  }
517
535
  }
518
- return -1;
536
+ return matchingInputCandidates.length === 1
537
+ ? matchingInputCandidates[0]
538
+ : -1;
519
539
  }
520
540
  function normalizeContentForFingerprint(content) {
521
541
  if (!Array.isArray(content))
@@ -853,6 +873,12 @@ function assistantReplayContent(message, text) {
853
873
  toolName: name,
854
874
  toolInput: stringifyToolUseInputForGateway(input),
855
875
  ...result,
876
+ ...(part.approvalPause === true ? { approvalPause: true } : {}),
877
+ ...(typeof part.approval?.askId === "string"
878
+ ? { approvalAskId: part.approval.askId }
879
+ : typeof part.approvalAskId === "string"
880
+ ? { approvalAskId: part.approvalAskId }
881
+ : {}),
856
882
  ...(part.isError === true || result.outcome === "unknown"
857
883
  ? { isError: true }
858
884
  : {}),
@@ -1327,7 +1353,227 @@ function snapshotAssistantTextKey(message, runIds) {
1327
1353
  message.parts.map((part) => [part.text, part.format ?? null]),
1328
1354
  ]);
1329
1355
  }
1330
- function preferIncomingSnapshotEntry(kind, existing, incoming) {
1356
+ function snapshotAssistantText(message) {
1357
+ if (message?.role !== "assistant" ||
1358
+ !Array.isArray(message.parts) ||
1359
+ message.parts.length === 0 ||
1360
+ !message.parts.every((part) => part?.type === "text" && typeof part.text === "string")) {
1361
+ return undefined;
1362
+ }
1363
+ return message.parts.map((part) => part.text).join("");
1364
+ }
1365
+ function removeSupersededAgentKitMessagePrefixes(messages, agentKit, previousAgentKit, incomingAgentKit) {
1366
+ if (!Array.isArray(messages))
1367
+ return undefined;
1368
+ const completedMessagesByRun = new Map();
1369
+ const events = [previousAgentKit, incomingAgentKit, agentKit].flatMap((snapshot) => (Array.isArray(snapshot.events) ? snapshot.events : []));
1370
+ for (const event of events) {
1371
+ const messageId = event?.message?.id;
1372
+ if (event?.type !== "message.completed" ||
1373
+ event.message?.role !== "assistant" ||
1374
+ typeof event.runId !== "string" ||
1375
+ typeof messageId !== "string") {
1376
+ continue;
1377
+ }
1378
+ const messageIds = completedMessagesByRun.get(event.runId) ?? new Set();
1379
+ messageIds.add(messageId);
1380
+ completedMessagesByRun.set(event.runId, messageIds);
1381
+ }
1382
+ if (completedMessagesByRun.size === 0)
1383
+ return messages;
1384
+ const runningRunSnapshots = [previousAgentKit, incomingAgentKit].flatMap((snapshot) => Array.isArray(snapshot.runs)
1385
+ ? snapshot.runs.flatMap((run) => run?.status === "running" &&
1386
+ typeof run.id === "string" &&
1387
+ typeof run.activeMessageId === "string" &&
1388
+ typeof run.lastSequence === "number" &&
1389
+ Number.isSafeInteger(run.lastSequence)
1390
+ ? [{ run, messages: snapshot.messages }]
1391
+ : [])
1392
+ : []);
1393
+ const removedIds = new Set();
1394
+ for (const run of Array.isArray(agentKit.runs) ? agentKit.runs : []) {
1395
+ if (run?.status !== "completed" ||
1396
+ typeof run.id !== "string" ||
1397
+ typeof run.activeMessageId !== "string" ||
1398
+ typeof run.lastSequence !== "number" ||
1399
+ !Number.isSafeInteger(run.lastSequence)) {
1400
+ continue;
1401
+ }
1402
+ const staleRunningSnapshot = runningRunSnapshots
1403
+ .filter(({ run: candidateRun }) => candidateRun.id === run.id &&
1404
+ candidateRun.activeMessageId !== run.activeMessageId &&
1405
+ candidateRun.lastSequence < run.lastSequence &&
1406
+ !completedMessagesByRun
1407
+ .get(run.id)
1408
+ ?.has(candidateRun.activeMessageId))
1409
+ .sort((left, right) => right.run.lastSequence - left.run.lastSequence)[0];
1410
+ if (!staleRunningSnapshot ||
1411
+ !completedMessagesByRun.get(run.id)?.has(run.activeMessageId) ||
1412
+ !events.some((event) => event?.type === "message.completed" &&
1413
+ event.runId === run.id &&
1414
+ event.message?.role === "assistant" &&
1415
+ event.message?.id === run.activeMessageId &&
1416
+ typeof event.sequence === "number" &&
1417
+ Number.isSafeInteger(event.sequence) &&
1418
+ event.sequence > staleRunningSnapshot.run.lastSequence &&
1419
+ event.sequence <= run.lastSequence)) {
1420
+ continue;
1421
+ }
1422
+ const completedIndex = messages.findIndex((entry) => snapshotEntryId(entry, "message") === run.activeMessageId &&
1423
+ entry?.role === "assistant");
1424
+ if (completedIndex === -1)
1425
+ continue;
1426
+ const completedText = snapshotAssistantText(messages[completedIndex]);
1427
+ if (completedText === undefined)
1428
+ continue;
1429
+ const lastUserIndexBefore = (messageIndex) => {
1430
+ for (let index = messageIndex - 1; index >= 0; index--) {
1431
+ if (messages[index]?.role === "user")
1432
+ return index;
1433
+ }
1434
+ return -1;
1435
+ };
1436
+ const lastUserIndex = lastUserIndexBefore(completedIndex);
1437
+ if (lastUserIndex === -1 ||
1438
+ lastUserIndexBefore(messages.findIndex((entry) => snapshotEntryId(entry, "message") ===
1439
+ staleRunningSnapshot.run.activeMessageId)) !== lastUserIndex) {
1440
+ continue;
1441
+ }
1442
+ const candidateId = staleRunningSnapshot.run.activeMessageId;
1443
+ const candidate = Array.isArray(staleRunningSnapshot.messages)
1444
+ ? staleRunningSnapshot.messages.find((entry) => snapshotEntryId(entry, "message") === candidateId)
1445
+ : undefined;
1446
+ const candidateIndex = messages.findIndex((entry) => snapshotEntryId(entry, "message") === candidateId);
1447
+ const candidateText = snapshotAssistantText(candidate);
1448
+ if (candidateIndex <= lastUserIndex ||
1449
+ candidateText === undefined ||
1450
+ candidateText.length === 0 ||
1451
+ candidateText.length >= completedText.length ||
1452
+ !completedText.startsWith(candidateText)) {
1453
+ continue;
1454
+ }
1455
+ removedIds.add(candidateId);
1456
+ }
1457
+ return removedIds.size === 0
1458
+ ? messages
1459
+ : messages.filter((entry) => {
1460
+ const id = snapshotEntryId(entry, "message");
1461
+ return !id || !removedIds.has(id);
1462
+ });
1463
+ }
1464
+ function isApprovalPauseToolCall(toolCall) {
1465
+ if (toolCall?.metadata?.approvalPause === true)
1466
+ return true;
1467
+ return (typeof toolCall?.output === "string" &&
1468
+ toolCall.output.startsWith("Awaiting") &&
1469
+ toolCall.output.includes("did NOT execute"));
1470
+ }
1471
+ function toolCallHasCompletedSideEffect(toolCall) {
1472
+ return toolCall?.metadata?.completedSideEffect === true;
1473
+ }
1474
+ function toolCallInputValuesMatch(left, right) {
1475
+ if (Object.is(left, right))
1476
+ return true;
1477
+ if (Array.isArray(left) || Array.isArray(right)) {
1478
+ return (Array.isArray(left) &&
1479
+ Array.isArray(right) &&
1480
+ left.length === right.length &&
1481
+ left.every((value, index) => toolCallInputValuesMatch(value, right[index])));
1482
+ }
1483
+ return toolInputsMatch(left, right);
1484
+ }
1485
+ function toolCallInputsMatch(existing, incoming) {
1486
+ const existingInput = existing?.input;
1487
+ const incomingInput = incoming?.input;
1488
+ if (existingInput !== undefined && incomingInput !== undefined) {
1489
+ return toolCallInputValuesMatch(existingInput, incomingInput);
1490
+ }
1491
+ const existingSnapshot = existing?.metadata?.agentKitSnapshot;
1492
+ const incomingSnapshot = incoming?.metadata?.agentKitSnapshot;
1493
+ const existingFingerprint = typeof existingSnapshot?.inputFingerprint === "string"
1494
+ ? existingSnapshot.inputFingerprint
1495
+ : existingInput === undefined
1496
+ ? undefined
1497
+ : toolInputFingerprint(existingInput);
1498
+ const incomingFingerprint = typeof incomingSnapshot?.inputFingerprint === "string"
1499
+ ? incomingSnapshot.inputFingerprint
1500
+ : incomingInput === undefined
1501
+ ? undefined
1502
+ : toolInputFingerprint(incomingInput);
1503
+ if (existingFingerprint || incomingFingerprint) {
1504
+ return (typeof existingFingerprint === "string" &&
1505
+ existingFingerprint === incomingFingerprint);
1506
+ }
1507
+ return existingInput === undefined && incomingInput === undefined;
1508
+ }
1509
+ function toolCallIdentityMatches(existing, incoming) {
1510
+ return (typeof existing?.id === "string" &&
1511
+ existing.id === incoming?.id &&
1512
+ typeof existing?.name === "string" &&
1513
+ existing.name === incoming?.name &&
1514
+ toolCallInputsMatch(existing, incoming));
1515
+ }
1516
+ function toolCallsMatch(existing, incoming) {
1517
+ return (existing?.status === "completed" &&
1518
+ incoming?.status === "completed" &&
1519
+ toolCallIdentityMatches(existing, incoming));
1520
+ }
1521
+ function isIncomingToolCallRunNewer(existing, incoming, existingRuns, incomingRuns) {
1522
+ const existingRunId = existing?.runId;
1523
+ const incomingRunId = incoming?.runId;
1524
+ if (typeof incomingRunId !== "string")
1525
+ return false;
1526
+ if (typeof existingRunId !== "string")
1527
+ return true;
1528
+ const findRun = (runs, runId) => Array.isArray(runs) ? runs.find((run) => run?.id === runId) : undefined;
1529
+ const existingRun = findRun(existingRuns, existingRunId);
1530
+ const incomingRun = findRun(incomingRuns, incomingRunId);
1531
+ if (existingRunId === incomingRunId) {
1532
+ return (Number.isSafeInteger(existingRun?.lastSequence) &&
1533
+ Number.isSafeInteger(incomingRun?.lastSequence) &&
1534
+ incomingRun.lastSequence > existingRun.lastSequence);
1535
+ }
1536
+ const existingStartedAt = Date.parse(existingRun?.startedAt ?? "");
1537
+ const incomingStartedAt = Date.parse(incomingRun?.startedAt ?? "");
1538
+ return (Number.isFinite(existingStartedAt) &&
1539
+ Number.isFinite(incomingStartedAt) &&
1540
+ incomingStartedAt > existingStartedAt);
1541
+ }
1542
+ function prefersApprovalReplaySnapshot(existing, incoming, existingRuns, incomingRuns) {
1543
+ if (!toolCallsMatch(existing, incoming))
1544
+ return false;
1545
+ const incomingIsNewer = isIncomingToolCallRunNewer(existing, incoming, existingRuns, incomingRuns);
1546
+ return ((isApprovalPauseToolCall(existing) &&
1547
+ toolCallHasCompletedSideEffect(incoming) &&
1548
+ incomingIsNewer) ||
1549
+ (toolCallHasCompletedSideEffect(existing) &&
1550
+ isApprovalPauseToolCall(incoming) &&
1551
+ incomingIsNewer));
1552
+ }
1553
+ function incomingRunIsNotProvenNewer(existing, incoming, existingRuns, incomingRuns) {
1554
+ const existingRunId = existing?.runId;
1555
+ const incomingRunId = incoming?.runId;
1556
+ if (typeof existingRunId !== "string" && typeof incomingRunId !== "string") {
1557
+ return false;
1558
+ }
1559
+ if (typeof existingRunId !== "string" || typeof incomingRunId !== "string") {
1560
+ return true;
1561
+ }
1562
+ const findRun = (runs, runId) => Array.isArray(runs) ? runs.find((run) => run?.id === runId) : undefined;
1563
+ const existingRun = findRun(existingRuns, existingRunId);
1564
+ const incomingRun = findRun(incomingRuns, incomingRunId);
1565
+ if (existingRunId === incomingRunId) {
1566
+ return !(Number.isSafeInteger(existingRun?.lastSequence) &&
1567
+ Number.isSafeInteger(incomingRun?.lastSequence) &&
1568
+ incomingRun.lastSequence > existingRun.lastSequence);
1569
+ }
1570
+ const existingStartedAt = Date.parse(existingRun?.startedAt ?? "");
1571
+ const incomingStartedAt = Date.parse(incomingRun?.startedAt ?? "");
1572
+ return (!Number.isFinite(existingStartedAt) ||
1573
+ !Number.isFinite(incomingStartedAt) ||
1574
+ incomingStartedAt <= existingStartedAt);
1575
+ }
1576
+ function preferIncomingSnapshotEntry(kind, existing, incoming, existingRuns, incomingRuns) {
1331
1577
  if (kind === "message") {
1332
1578
  const rank = (message) => message.status === "complete" ? 2 : message.status === "error" ? 1 : 0;
1333
1579
  if (rank(existing) !== rank(incoming))
@@ -1340,6 +1586,11 @@ function preferIncomingSnapshotEntry(kind, existing, incoming) {
1340
1586
  return textLength(incoming) > textLength(existing);
1341
1587
  }
1342
1588
  if (kind === "toolCall") {
1589
+ if (existing.status === "running" &&
1590
+ incoming.status !== "running" &&
1591
+ incomingRunIsNotProvenNewer(existing, incoming, existingRuns, incomingRuns)) {
1592
+ return false;
1593
+ }
1343
1594
  return existing.status === "running" && incoming.status !== "running";
1344
1595
  }
1345
1596
  return (existing.widget?.state === "active" && incoming.widget?.state !== "active");
@@ -1357,7 +1608,7 @@ function withToolCallMessageIdFallback(preferred, fallback) {
1357
1608
  }
1358
1609
  return preferred;
1359
1610
  }
1360
- function mergeAgentKitHistoryArray(existing, incoming, kind, existingMessageRunIds, incomingMessageRunIds, promptRunIds) {
1611
+ function mergeAgentKitHistoryArray(existing, incoming, kind, existingMessageRunIds, incomingMessageRunIds, promptRunIds, existingRuns, incomingRuns) {
1361
1612
  if (!Array.isArray(existing) && !Array.isArray(incoming))
1362
1613
  return undefined;
1363
1614
  const merged = Array.isArray(existing) ? [...existing] : [];
@@ -1400,11 +1651,13 @@ function mergeAgentKitHistoryArray(existing, incoming, kind, existingMessageRunI
1400
1651
  matchedAssistantTextPositions.add(index);
1401
1652
  if (id && idIndex === undefined)
1402
1653
  positions.set(id, index);
1403
- const preferIncoming = preferIncomingSnapshotEntry(kind, merged[index], entry);
1654
+ const preferIncoming = preferIncomingSnapshotEntry(kind, merged[index], entry, existingRuns, incomingRuns);
1655
+ const preferApprovedReplay = kind === "toolCall" &&
1656
+ prefersApprovalReplaySnapshot(merged[index], entry, existingRuns, incomingRuns);
1404
1657
  const preferCurrentMessageId = kind === "message" &&
1405
1658
  textIndex !== undefined &&
1406
- !preferIncomingSnapshotEntry(kind, entry, merged[index]);
1407
- if (preferIncoming || preferCurrentMessageId) {
1659
+ !preferIncomingSnapshotEntry(kind, entry, merged[index], incomingRuns, existingRuns);
1660
+ if (preferIncoming || preferApprovedReplay || preferCurrentMessageId) {
1408
1661
  merged[index] =
1409
1662
  kind === "toolCall"
1410
1663
  ? withToolCallMessageIdFallback(entry, merged[index])
@@ -2495,7 +2748,7 @@ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConf
2495
2748
  ["toolCalls", "toolCall"],
2496
2749
  ["widgets", "widget"],
2497
2750
  ]) {
2498
- const entries = mergeAgentKitHistoryArray(previous[key], next[key], kind, existingMessageRunIds, incomingMessageRunIds, promptRunIds);
2751
+ const entries = mergeAgentKitHistoryArray(previous[key], next[key], kind, existingMessageRunIds, incomingMessageRunIds, promptRunIds, previous.runs, next.runs);
2499
2752
  if (entries)
2500
2753
  merged[key] = entries;
2501
2754
  }
@@ -2569,6 +2822,9 @@ function mergeAgentKitHistory(existing, incoming, promptRunIds, onAnnotationConf
2569
2822
  merged._eventRunWatermarks = previous._eventRunWatermarks;
2570
2823
  }
2571
2824
  }
2825
+ const messages = removeSupersededAgentKitMessagePrefixes(merged.messages, merged, previous, next);
2826
+ if (messages)
2827
+ merged.messages = messages;
2572
2828
  delete merged._snapshotDelta;
2573
2829
  delete merged.eventRunReplacements;
2574
2830
  delete merged.eventRunSnapshotWatermarks;
@@ -3131,6 +3387,24 @@ function assistantContentWeight(content) {
3131
3387
  function appendFoldedContent(existing, incoming) {
3132
3388
  const merged = existing.map((p) => ({ ...p }));
3133
3389
  for (const part of incoming) {
3390
+ if (part?.type === "tool-call" && part.result !== undefined) {
3391
+ let pausedCallIndex = -1;
3392
+ for (let index = merged.length - 1; index >= 0; index -= 1) {
3393
+ const candidate = merged[index];
3394
+ if (candidate?.type === "tool-call" &&
3395
+ candidate.approvalPause === true &&
3396
+ candidate.toolCallId === part.toolCallId &&
3397
+ candidate.toolName === part.toolName &&
3398
+ toolInputsMatch(candidate.args, part.args)) {
3399
+ pausedCallIndex = index;
3400
+ break;
3401
+ }
3402
+ }
3403
+ if (pausedCallIndex !== -1) {
3404
+ merged[pausedCallIndex] = { ...part };
3405
+ continue;
3406
+ }
3407
+ }
3134
3408
  const last = merged[merged.length - 1];
3135
3409
  if (part?.type === "text" &&
3136
3410
  typeof part.text === "string" &&
@@ -1,3 +1,4 @@
1
+ export { hashAgentToolApprovalKey } from "./approval-key.js";
1
2
  export declare function ensureAgentToolApprovalTable(): Promise<void>;
2
3
  export declare function ensureAgentToolApprovalPolicyTable(): Promise<void>;
3
4
  export interface AgentToolApprovalBinding {
@@ -8,9 +9,9 @@ export interface AgentToolApprovalBinding {
8
9
  toolName: string;
9
10
  callId: string;
10
11
  approvalKey: string;
12
+ approvalId?: string;
11
13
  expiresAt?: number;
12
14
  }
13
- export declare function hashAgentToolApprovalKey(approvalKey: string): string;
14
15
  export interface AgentToolApprovalPolicyBinding {
15
16
  ownerEmail: string;
16
17
  orgId?: string | null;
@@ -27,6 +28,7 @@ export declare function resolveAgentToolApprovalTurnId(binding: {
27
28
  threadId?: string | null;
28
29
  requestedTurnId?: string | null;
29
30
  approvalKeys: readonly string[];
31
+ approvalId?: string;
30
32
  }): Promise<string | null>;
31
33
  export declare function createAgentToolApproval(binding: AgentToolApprovalBinding): Promise<string>;
32
34
  /**
@@ -2,7 +2,9 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { getDbExec } from "../db/client.js";
3
3
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
4
4
  import { retainRequestDbPoolScope } from "../db/request-pool-context.js";
5
+ import { hashAgentToolApprovalKey } from "./approval-key.js";
5
6
  import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, AGENT_TOOL_APPROVAL_POLICY_INDEX_SQL, AGENT_TOOL_APPROVAL_POLICY_TABLE_SQL, AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
7
+ export { hashAgentToolApprovalKey } from "./approval-key.js";
6
8
  const APPROVAL_TTL_MS = 60 * 60_000;
7
9
  const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
8
10
  let initPromise;
@@ -35,9 +37,6 @@ export async function ensureAgentToolApprovalPolicyTable() {
35
37
  }
36
38
  return policyInitPromise;
37
39
  }
38
- export function hashAgentToolApprovalKey(approvalKey) {
39
- return createHash("sha256").update(approvalKey).digest("hex");
40
- }
41
40
  function normalizeApprovalPolicyBinding(binding) {
42
41
  const ownerEmail = binding.ownerEmail.trim().toLowerCase();
43
42
  const toolName = binding.toolName.trim();
@@ -85,7 +84,7 @@ export async function isAgentToolAlwaysAllowed(input) {
85
84
  const result = await getDbExec().execute({
86
85
  sql: `SELECT 1 FROM agent_tool_approval_policies
87
86
  WHERE owner_email = ?
88
- AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
87
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
89
88
  AND tool_name = ?
90
89
  AND enabled = TRUE
91
90
  LIMIT 1`,
@@ -101,12 +100,15 @@ export async function resolveAgentToolApprovalTurnId(binding) {
101
100
  const now = Date.now();
102
101
  const hashes = approvalKeys.map(hashAgentToolApprovalKey);
103
102
  const placeholders = hashes.map(() => "?").join(", ");
103
+ const exactApprovalFilter = binding.approvalId ? "AND id = ?" : "";
104
+ const approvalIdArgs = binding.approvalId ? [binding.approvalId] : [];
104
105
  const result = await getDbExec().execute({
105
106
  sql: `SELECT turn_id FROM agent_tool_approvals
106
107
  WHERE owner_email = ?
107
- AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
108
- AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
108
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
109
+ AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = CAST(? AS TEXT))
109
110
  AND approval_key_hash IN (${placeholders})
111
+ ${exactApprovalFilter}
110
112
  AND status = 'pending'
111
113
  AND expires_at > ?
112
114
  AND turn_id IS NOT NULL`,
@@ -117,6 +119,7 @@ export async function resolveAgentToolApprovalTurnId(binding) {
117
119
  binding.threadId,
118
120
  binding.threadId,
119
121
  ...hashes,
122
+ ...approvalIdArgs,
120
123
  now,
121
124
  ],
122
125
  });
@@ -127,6 +130,14 @@ export async function resolveAgentToolApprovalTurnId(binding) {
127
130
  })
128
131
  .filter((turnId) => turnId !== null));
129
132
  const requestedTurnId = binding.requestedTurnId?.trim();
133
+ if (binding.approvalId) {
134
+ if (turnIds.size !== 1)
135
+ return null;
136
+ const exactTurnId = [...turnIds][0];
137
+ return requestedTurnId && requestedTurnId !== exactTurnId
138
+ ? null
139
+ : exactTurnId;
140
+ }
130
141
  if (requestedTurnId && turnIds.has(requestedTurnId))
131
142
  return requestedTurnId;
132
143
  return turnIds.size === 1 ? [...turnIds][0] : null;
@@ -177,21 +188,23 @@ export async function createAgentToolApproval(binding) {
177
188
  export async function consumeAgentToolApproval(binding) {
178
189
  await ensureAgentToolApprovalTable();
179
190
  const now = Date.now();
191
+ const exactApprovalFilter = binding.approvalId ? "AND id = ?" : "";
192
+ const approvalIdArgs = binding.approvalId ? [binding.approvalId] : [];
180
193
  const result = await getDbExec().execute({
181
194
  sql: `UPDATE agent_tool_approvals
182
195
  SET status = 'consumed', consumed_at = ?, updated_at = ?
183
196
  WHERE id = (
184
- SELECT id FROM agent_tool_approvals
197
+ SELECT MIN(id) FROM agent_tool_approvals
185
198
  WHERE owner_email = ?
186
- AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
187
- AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
188
- AND ((turn_id IS NULL AND CAST(? AS TEXT) IS NULL) OR turn_id = ?)
199
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = CAST(? AS TEXT))
200
+ AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = CAST(? AS TEXT))
201
+ AND ((turn_id IS NULL AND CAST(? AS TEXT) IS NULL) OR turn_id = CAST(? AS TEXT))
189
202
  AND tool_name = ?
190
203
  AND approval_key_hash = ?
204
+ ${exactApprovalFilter}
191
205
  AND status = 'pending'
192
206
  AND expires_at > ?
193
- ORDER BY created_at DESC
194
- LIMIT 1
207
+ HAVING COUNT(*) = 1
195
208
  )
196
209
  AND status = 'pending'`,
197
210
  args: [
@@ -206,6 +219,7 @@ export async function consumeAgentToolApproval(binding) {
206
219
  binding.turnId ?? null,
207
220
  binding.toolName,
208
221
  hashAgentToolApprovalKey(binding.approvalKey),
222
+ ...approvalIdArgs,
209
223
  now,
210
224
  ],
211
225
  });
@@ -19,7 +19,7 @@ export interface ToolCallJournal {
19
19
  interrupted: ToolCallJournalEntry[];
20
20
  }
21
21
  export declare function toolCallInputFingerprint(input: unknown): string;
22
- export declare function isRedactedToolCallInput(input: unknown): boolean;
22
+ export declare function isUnavailableToolCallInput(input: unknown): boolean;
23
23
  export declare function isConsistentToolCallInputFingerprint(input: unknown, fingerprint: string | undefined, storedFingerprint?: string): boolean;
24
24
  export declare function classifyToolCallJournal(events: readonly AgentChatEvent[]): ToolCallJournal;
25
25
  export declare function isJournalEmpty(journal: ToolCallJournal): boolean;
@@ -17,14 +17,16 @@ function inputSignature(input) {
17
17
  export function toolCallInputFingerprint(input) {
18
18
  return createHash("sha256").update(inputSignature(input)).digest("hex");
19
19
  }
20
- export function isRedactedToolCallInput(input) {
20
+ export function isUnavailableToolCallInput(input) {
21
21
  if (typeof input === "string")
22
22
  return /\[inline [^\]]+ data omitted\]/.test(input);
23
23
  if (!input || typeof input !== "object")
24
24
  return false;
25
- if ("omitted" in input && input.omitted === "inline-bytes")
25
+ if ("omitted" in input &&
26
+ (input.omitted === "inline-bytes" || input.omitted === "tool-input")) {
26
27
  return true;
27
- return Object.values(input).some(isRedactedToolCallInput);
28
+ }
29
+ return Object.values(input).some(isUnavailableToolCallInput);
28
30
  }
29
31
  export function isConsistentToolCallInputFingerprint(input, fingerprint, storedFingerprint) {
30
32
  if (storedFingerprint !== undefined)
@@ -67,6 +67,8 @@ export type AgentChatStructuredContentPart = {
67
67
  toolName?: string;
68
68
  input?: unknown;
69
69
  args?: unknown;
70
+ approvalKey?: string;
71
+ approvalAskId?: string;
70
72
  } | {
71
73
  type: "tool-result";
72
74
  toolCallId: string;
@@ -74,6 +76,8 @@ export type AgentChatStructuredContentPart = {
74
76
  toolInput?: string;
75
77
  content: string;
76
78
  isError?: boolean;
79
+ approvalPause?: boolean;
80
+ approvalAskId?: string;
77
81
  };
78
82
  export interface AgentChatStructuredMessage {
79
83
  role: "user" | "assistant";
@@ -215,6 +219,7 @@ export interface AgentChatRequest {
215
219
  harness?: AgentChatHarnessRequest;
216
220
  trackInRunsTray?: boolean;
217
221
  approvedToolCalls?: string[];
222
+ approvedToolCallAskIds?: string[];
218
223
  skipPendingSelectionContext?: boolean;
219
224
  }
220
225
  export type AgentToolInput = Record<string, unknown>;
@@ -287,6 +292,8 @@ export type AgentChatEvent = {
287
292
  isError?: boolean;
288
293
  errorCode?: string;
289
294
  completedSideEffect?: boolean;
295
+ /** False marks current completed events; absence is reserved for older journals. */
296
+ approvalPause?: boolean;
290
297
  outcomeUnknown?: true;
291
298
  replayed?: true;
292
299
  fileMutation?: AgentFileMutationProof;
@@ -3,6 +3,7 @@ import { agentEventSequenceEnd, isAgentKitProtocolVersion, parseAgentQueuedMessa
3
3
  import { failedRunIdsWithError, isRenderedRunNotStartedNotice, projectRootAssistantMessages, } from "../../agent/thread-message-projection.js";
4
4
  import { RUN_NOT_STARTED_METADATA_KEY, retryContextFromRequest, } from "../../shared/agent-chat-run-not-started.js";
5
5
  import { assertNoInlineImageBytes, stripInlineBytes, } from "../../shared/inline-bytes.js";
6
+ import { toolInputFingerprint } from "../../shared/tool-inputs.js";
6
7
  import { agentEngineStatusUrlForChatApi, requireAgentEngineConfiguredForDispatch, } from "../agent-engine-readiness.js";
7
8
  import { agentNativePath } from "../api-path.js";
8
9
  import { CHAT_REQUEST_TOO_LARGE_MESSAGE } from "../error-format.js";
@@ -1585,6 +1586,15 @@ function persistedToolCalls(toolCalls = []) {
1585
1586
  new TextEncoder().encode(serialized).byteLength <= 64 * 1024) {
1586
1587
  return [JSON.parse(serialized)];
1587
1588
  }
1589
+ const metadata = asRecord(toolCall.metadata);
1590
+ const agentKitSnapshot = asRecord(metadata?.agentKitSnapshot);
1591
+ const inputFingerprint = toolCall.input === undefined
1592
+ ? agentKitSnapshot?.inputFingerprint
1593
+ : toolInputFingerprint(toolCall.input);
1594
+ const approvalAskId = typeof metadata?.approvalAskId === "string" &&
1595
+ metadata.approvalAskId.length <= 512
1596
+ ? metadata.approvalAskId
1597
+ : undefined;
1588
1598
  return [
1589
1599
  {
1590
1600
  id: toolCall.id,
@@ -1593,9 +1603,17 @@ function persistedToolCalls(toolCalls = []) {
1593
1603
  ...(toolCall.runId ? { runId: toolCall.runId } : {}),
1594
1604
  ...(toolCall.messageId ? { messageId: toolCall.messageId } : {}),
1595
1605
  metadata: {
1606
+ ...(metadata?.approvalPause === true ? { approvalPause: true } : {}),
1607
+ ...(metadata?.completedSideEffect === true
1608
+ ? { completedSideEffect: true }
1609
+ : {}),
1610
+ ...(approvalAskId ? { approvalAskId } : {}),
1596
1611
  agentKitSnapshot: {
1597
1612
  toolCallResult: "omitted",
1598
1613
  reason: serialized ? "size_limit" : "not_json",
1614
+ ...(typeof inputFingerprint === "string"
1615
+ ? { inputFingerprint }
1616
+ : {}),
1599
1617
  },
1600
1618
  },
1601
1619
  },