@agent-native/core 0.227.0-nightly-20261110182415 → 0.227.0

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 (32) hide show
  1. package/dist/agent/default-agent-engine.d.ts +1 -0
  2. package/dist/agent/default-agent-engine.js +6 -3
  3. package/dist/agent/engine/builder-engine.js +10 -6
  4. package/dist/agent/engine/chat-model-availability.d.ts +9 -0
  5. package/dist/agent/engine/chat-model-availability.js +39 -0
  6. package/dist/agent/optional-placeholders.d.ts +1 -0
  7. package/dist/agent/optional-placeholders.js +3 -0
  8. package/dist/cli/index.js +1 -1
  9. package/dist/client/chat/agentkit-protocol.js +259 -200
  10. package/dist/client/chat/run-outcome.d.ts +48 -0
  11. package/dist/client/chat/run-outcome.js +50 -0
  12. package/dist/collab/routes.d.ts +1 -1
  13. package/dist/collab/struct-routes.d.ts +1 -1
  14. package/dist/framework-package-versions.json +11 -11
  15. package/dist/jobs/run-now.js +9 -1
  16. package/dist/notifications/routes.d.ts +3 -3
  17. package/dist/org/index.d.ts +1 -1
  18. package/dist/org/migrations.d.ts +1 -1
  19. package/dist/provider-api/actions/provider-api.d.ts +1 -1
  20. package/dist/resources/handlers.d.ts +1 -1
  21. package/dist/scripts/agent-engines/list-agent-engines.js +9 -5
  22. package/dist/server/index.d.ts +1 -0
  23. package/dist/server/index.js +1 -0
  24. package/dist/templates/builder-code-starter/AGENTS.md.patch +1 -1
  25. package/dist/templates/chat/AGENTS.md +1 -1
  26. package/dist/triggers/actions/list-automations.js +7 -1
  27. package/docs/content/agent-web-surfaces.mdx +54 -42
  28. package/docs/content/create-with-an-agent.mdx +1 -1
  29. package/docs/content/external-agents.mdx +8 -0
  30. package/package.json +4 -4
  31. package/src/templates/builder-code-starter/AGENTS.md.patch +1 -1
  32. package/src/templates/chat/AGENTS.md +1 -1
@@ -5,7 +5,17 @@ import { isPersistableAttachmentUrl } from "../../shared/attachments.js";
5
5
  import { parseBase64DataUrl } from "../../shared/data-url.js";
6
6
  import { emitChatFirstOpenApp, emitChatFirstOpenBrowser, } from "../chat-first-state.js";
7
7
  import { toolDisplayName } from "../tool-display.js";
8
- import { decideAfterStreamClosed, runOutcomeForCode, } from "./run-outcome.js";
8
+ import { decideAfterStreamClosed, runOutcomeForCode, transition, } from "./run-outcome.js";
9
+ function isRunTerminal(run) {
10
+ return run.phase.type === "terminal" || run.phase.type === "disposed";
11
+ }
12
+ function isRunCancelling(run) {
13
+ return run.phase.type === "cancelling";
14
+ }
15
+ // The phase can change during awaits and event handling, despite local narrowing.
16
+ function isRunPhase(run, phase) {
17
+ return run.phase.type === phase;
18
+ }
9
19
  function isTerminalRunStatus(status) {
10
20
  return (status === "completed" || status === "failed" || status === "cancelled");
11
21
  }
@@ -1087,7 +1097,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1087
1097
  }
1088
1098
  function findRunByTurnId(threadId, turnId) {
1089
1099
  const owners = [...runs.values()].filter((run) => run.threadId === threadId && run.turn.id === turnId);
1090
- const activeOwners = owners.filter((run) => !run.terminal);
1100
+ const activeOwners = owners.filter((run) => !isRunTerminal(run));
1091
1101
  if (activeOwners.length > 1) {
1092
1102
  throw new Error(`Turn ${turnId} is owned by multiple active AgentKit runs in thread ${threadId}.`);
1093
1103
  }
@@ -1114,7 +1124,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1114
1124
  if (previousRun &&
1115
1125
  previousRun !== run &&
1116
1126
  runs.get(previousRun.runId) === previousRun &&
1117
- !previousRun.terminal) {
1127
+ !isRunTerminal(previousRun)) {
1118
1128
  throw new Error(`Runtime run id ${runtimeRunId} is already owned by active AgentKit run ${previousRun.runId}.`);
1119
1129
  }
1120
1130
  runtimeRunAliases.set(aliasKey, run);
@@ -1125,7 +1135,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1125
1135
  async function resolveRuntimeRunOwner(threadId, runtimeRunId, sessionId, authorityRead) {
1126
1136
  if (!runAuthority)
1127
1137
  return {};
1128
- const candidates = [...runs.values()].filter((run) => run.threadId === threadId && !run.terminal && Boolean(run.turn.id));
1138
+ const candidates = [...runs.values()].filter((run) => run.threadId === threadId &&
1139
+ !isRunTerminal(run) &&
1140
+ Boolean(run.turn.id));
1129
1141
  const candidate = candidates[0];
1130
1142
  const resolvedSessionId = sessionId ?? candidate?.session.id;
1131
1143
  if (!resolvedSessionId)
@@ -1163,7 +1175,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1163
1175
  // coercion-ok: startRun surfaces this rejection; restore from the separate authoritative read.
1164
1176
  }
1165
1177
  }
1166
- const currentCandidates = [...runs.values()].filter((run) => run.threadId === threadId && !run.terminal && Boolean(run.turn.id));
1178
+ const currentCandidates = [...runs.values()].filter((run) => run.threadId === threadId &&
1179
+ !isRunTerminal(run) &&
1180
+ Boolean(run.turn.id));
1167
1181
  const owners = currentCandidates.filter((run) => run.turn.id === state.turnId);
1168
1182
  if (owners.length !== 1)
1169
1183
  return { read };
@@ -1196,7 +1210,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1196
1210
  if (disposed)
1197
1211
  return;
1198
1212
  const nextExpiry = [...runs.values()]
1199
- .filter((run) => run.terminal &&
1213
+ .filter((run) => isRunTerminal(run) &&
1200
1214
  run.activeReaders === 0 &&
1201
1215
  run.terminalAtMs !== undefined)
1202
1216
  .reduce((earliest, run) => {
@@ -1213,7 +1227,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1213
1227
  timer.unref?.();
1214
1228
  }
1215
1229
  function pruneRetainedRuns(referenceTimeMs = timeMs()) {
1216
- const evictable = [...runs.values()].filter((run) => run.terminal && run.activeReaders === 0);
1230
+ const evictable = [...runs.values()].filter((run) => isRunTerminal(run) && run.activeReaders === 0);
1217
1231
  for (const run of evictable) {
1218
1232
  if (run.terminalAtMs !== undefined &&
1219
1233
  referenceTimeMs - run.terminalAtMs >= retainedRunTtlMs) {
@@ -1221,9 +1235,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1221
1235
  }
1222
1236
  }
1223
1237
  const retainedCompleted = [...runs.values()]
1224
- .filter((run) => run.terminal &&
1225
- !run.waitingForContinuation &&
1226
- run.activeReaders === 0)
1238
+ .filter((run) => isRunTerminal(run) && run.activeReaders === 0)
1227
1239
  .sort((left, right) => left.lastAccessedAtMs - right.lastAccessedAtMs);
1228
1240
  while (retainedCompleted.length > maxRetainedRuns) {
1229
1241
  const run = retainedCompleted.shift();
@@ -1485,10 +1497,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1485
1497
  terminalAppendDepth: 0,
1486
1498
  pumpPromise: null,
1487
1499
  continuationPromise: null,
1488
- streamClosed: false,
1489
- terminal: false,
1490
- waitingForContinuation: false,
1491
- ...(runAuthority ? { restoredWithoutStream: true } : {}),
1500
+ phase: { type: runAuthority ? "followingSuccessor" : "streaming" },
1492
1501
  ...(restoreOptions.includePendingConnectionRequest
1493
1502
  ? { requiresRestoredConnectionRequestReplay: true }
1494
1503
  : {}),
@@ -1559,9 +1568,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1559
1568
  terminalAppendDepth: 0,
1560
1569
  pumpPromise: null,
1561
1570
  continuationPromise: null,
1562
- streamClosed: false,
1563
- terminal: false,
1564
- waitingForContinuation: false,
1571
+ phase: { type: "streaming" },
1565
1572
  listeners: new Set(),
1566
1573
  };
1567
1574
  if (disposed) {
@@ -1579,7 +1586,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1579
1586
  }
1580
1587
  const isRuntimeAlias = existingRun.runId !== runId;
1581
1588
  const isSameRuntimeTurn = existingRun.turn.id !== undefined && existingRun.turn.id === turn.id;
1582
- if (isRuntimeAlias && !isSameRuntimeTurn && !existingRun.terminal) {
1589
+ if (isRuntimeAlias && !isSameRuntimeTurn && !isRunTerminal(existingRun)) {
1583
1590
  await closeDiscardedTurnEvents(turn);
1584
1591
  throw new Error(`Duplicate AgentKit run id: ${runId}`);
1585
1592
  }
@@ -1672,7 +1679,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1672
1679
  (event.type === "run.status" && event.status === "cancelled")
1673
1680
  ? "cancelled"
1674
1681
  : undefined;
1675
- const terminalFollowup = run.terminal &&
1682
+ const terminalFollowup = isRunTerminal(run) &&
1676
1683
  lastType === "run.status" &&
1677
1684
  ((run.status === "completed" && event.type === "run.completed") ||
1678
1685
  (run.status === "failed" && event.type === "run.failed") ||
@@ -1682,7 +1689,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1682
1689
  lastType === "run.cancelled") {
1683
1690
  return;
1684
1691
  }
1685
- if (run.terminal &&
1692
+ if (isRunTerminal(run) &&
1686
1693
  run.terminalAppendDepth === 0 &&
1687
1694
  !terminalFollowup &&
1688
1695
  !(terminalStatus && !isTerminalRunStatus(run.status))) {
@@ -1750,7 +1757,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1750
1757
  run.completedAt = protocolEvent.occurredAt;
1751
1758
  run.terminalAtMs = timeMs(protocolEvent.occurredAt);
1752
1759
  run.actions.clear();
1753
- run.terminal = true;
1760
+ run.phase = transition(run.phase, { type: "terminal" });
1754
1761
  becameTerminal = true;
1755
1762
  }
1756
1763
  }
@@ -1765,7 +1772,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1765
1772
  run.usage = protocolEvent.usage;
1766
1773
  }
1767
1774
  run.actions.clear();
1768
- run.terminal = true;
1775
+ run.phase = transition(run.phase, { type: "terminal" });
1769
1776
  becameTerminal = true;
1770
1777
  }
1771
1778
  else if (protocolEvent.type === "run.cancelled") {
@@ -1773,7 +1780,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1773
1780
  run.completedAt = protocolEvent.occurredAt;
1774
1781
  run.terminalAtMs = timeMs(protocolEvent.occurredAt);
1775
1782
  run.actions.clear();
1776
- run.terminal = true;
1783
+ run.phase = transition(run.phase, { type: "terminal" });
1777
1784
  becameTerminal = true;
1778
1785
  }
1779
1786
  else if (protocolEvent.type === "run.failed") {
@@ -1782,7 +1789,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1782
1789
  run.terminalAtMs = timeMs(protocolEvent.occurredAt);
1783
1790
  run.error = protocolEvent.error;
1784
1791
  run.actions.clear();
1785
- run.terminal = true;
1792
+ run.phase = transition(run.phase, { type: "terminal" });
1786
1793
  becameTerminal = true;
1787
1794
  }
1788
1795
  if (protocolEvent.type === "run.completed" ||
@@ -2219,12 +2226,14 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2219
2226
  run.pendingApprovalId !== event.approvalId) {
2220
2227
  throw new Error(`AgentKit run ${run.runId} received approval ${event.approvalId} while approval ${run.pendingApprovalId} is still pending.`);
2221
2228
  }
2222
- run.waitingForContinuation = true;
2229
+ run.phase = transition(run.phase, {
2230
+ type: "awaitContinuation",
2231
+ streamClosed: true,
2232
+ });
2223
2233
  run.pendingApprovalId = event.approvalId;
2224
2234
  // AG-UI models an approval interrupt as RUN_FINISHED. Close the wire
2225
2235
  // stream while retaining ownership of the paused Core turn so it can
2226
2236
  // still be resumed, cancelled, or disposed.
2227
- run.streamClosed = true;
2228
2237
  return [
2229
2238
  {
2230
2239
  type: "run.status",
@@ -2272,9 +2281,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2272
2281
  run.pendingConnectionRequestId !== event.requestId) {
2273
2282
  throw new Error(`AgentKit run ${run.runId} received connection request ${event.requestId} while ${run.pendingConnectionRequestId} is still pending.`);
2274
2283
  }
2275
- run.waitingForContinuation = true;
2284
+ run.phase = transition(run.phase, {
2285
+ type: "awaitContinuation",
2286
+ streamClosed: true,
2287
+ });
2276
2288
  run.pendingConnectionRequestId = event.requestId;
2277
- run.streamClosed = true;
2278
2289
  return [
2279
2290
  { type: "run.status", ...base, status: "awaiting_input" },
2280
2291
  {
@@ -2609,10 +2620,10 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2609
2620
  if (runAuthority &&
2610
2621
  capabilities.resumableRuns === true &&
2611
2622
  runOutcomeForCode(error.code) === "interrupted") {
2612
- run.heldInterruption = error;
2623
+ run.phase = transition(run.phase, { type: "interrupted", error });
2613
2624
  return [];
2614
2625
  }
2615
- run.terminal = true;
2626
+ run.phase = transition(run.phase, { type: "terminal" });
2616
2627
  return [
2617
2628
  { type: "run.status", ...base, status: "failed" },
2618
2629
  { type: "run.failed", ...base, error },
@@ -2620,7 +2631,10 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2620
2631
  }
2621
2632
  case "done":
2622
2633
  if (!isTerminalReason(event.reason)) {
2623
- run.waitingForContinuation = true;
2634
+ run.phase = transition(run.phase, {
2635
+ type: "awaitContinuation",
2636
+ streamClosed: false,
2637
+ });
2624
2638
  return [
2625
2639
  {
2626
2640
  type: "run.status",
@@ -2631,7 +2645,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2631
2645
  },
2632
2646
  ];
2633
2647
  }
2634
- run.terminal = true;
2648
+ run.phase = transition(run.phase, { type: "terminal" });
2635
2649
  if (event.reason === "cancelled" || event.reason === "interrupted") {
2636
2650
  return [
2637
2651
  { type: "run.status", ...base, status: "cancelled" },
@@ -2728,13 +2742,13 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2728
2742
  run.events.some((event) => event.type === "connection.requested" &&
2729
2743
  event.request.id === requestId);
2730
2744
  const deadline = Date.now() + RESTORED_CONNECTION_REQUEST_REPLAY_TIMEOUT_MS;
2731
- while (!isPending() && !run.terminal && !readers.signal.aborted) {
2745
+ while (!isPending() && !isRunTerminal(run) && !readers.signal.aborted) {
2732
2746
  const remaining = deadline - Date.now();
2733
2747
  if (remaining <= 0)
2734
2748
  break;
2735
2749
  await notifyWhenChanged(run, readers.signal, remaining);
2736
2750
  }
2737
- if (!isPending() && !run.terminal) {
2751
+ if (!isPending() && !isRunTerminal(run)) {
2738
2752
  throw restoredConnectionRequestUnverifiedError();
2739
2753
  }
2740
2754
  }
@@ -2756,7 +2770,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2756
2770
  };
2757
2771
  let lastError;
2758
2772
  while ((run.resumeAttempts ?? 0) < MAX_DURABLE_RESUME_ATTEMPTS) {
2759
- if (run.terminal || run.streamClosed)
2773
+ if (run.phase.type !== "followingSuccessor")
2760
2774
  return null;
2761
2775
  run.resumeAttempts = (run.resumeAttempts ?? 0) + 1;
2762
2776
  try {
@@ -2793,18 +2807,15 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2793
2807
  let failure = stream;
2794
2808
  try {
2795
2809
  const resumed = await resumeRuntimeTurn(run, stream.failed ? stream.error : undefined);
2796
- if (resumed || run.terminal || run.streamClosed)
2810
+ if (resumed || run.phase.type !== "followingSuccessor")
2797
2811
  return resumed;
2798
- if (run.waitingForContinuation)
2799
- return null;
2800
2812
  }
2801
2813
  catch (resumeError) {
2802
2814
  failure = { failed: true, error: resumeError };
2803
2815
  }
2804
- if (run.terminal || run.streamClosed)
2816
+ if (run.phase.type !== "followingSuccessor")
2805
2817
  return null;
2806
- run.streamClosed = true;
2807
- run.terminal = true;
2818
+ run.phase = transition(run.phase, { type: "terminal" });
2808
2819
  run.terminalSource = "pipe";
2809
2820
  append(run, {
2810
2821
  type: "run.failed",
@@ -2825,7 +2836,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2825
2836
  * next, or null once the run is terminal or this adapter stopped reading.
2826
2837
  */
2827
2838
  async function followRunAuthority(run, authority, stream) {
2828
- const stopped = () => run.terminal || run.streamClosed || readers.signal.aborted;
2839
+ const stopped = () => (run.phase.type !== "followingSuccessor" &&
2840
+ run.phase.type !== "holdingInterruption") ||
2841
+ readers.signal.aborted;
2829
2842
  if (run.readingTerminalDrain && run.terminalDrain) {
2830
2843
  run.readingTerminalDrain = false;
2831
2844
  if (!stream.failed) {
@@ -2845,7 +2858,8 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2845
2858
  return null;
2846
2859
  // An unreachable server says nothing about a successor; keep holding
2847
2860
  // through the usual backoff until it answers.
2848
- if (run.heldInterruption && read.kind !== "unreachable") {
2861
+ if (run.phase.type === "holdingInterruption" &&
2862
+ read.kind !== "unreachable") {
2849
2863
  const successor = read.kind === "read" &&
2850
2864
  read.state.status !== "missing" &&
2851
2865
  read.state.runId !== runtimeRunId;
@@ -2853,7 +2867,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2853
2867
  appendHeldInterruption(run);
2854
2868
  return null;
2855
2869
  }
2856
- run.heldInterruption = undefined;
2870
+ run.phase = transition(run.phase, { type: "followSuccessor" });
2857
2871
  }
2858
2872
  const decision = decideAfterStreamClosed(read, {
2859
2873
  runId: runtimeRunId,
@@ -2974,7 +2988,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2974
2988
  if (refusal)
2975
2989
  asked.set(decision.runId, refusal);
2976
2990
  }
2977
- if (activity && !run.terminal) {
2991
+ while (isRunCancelling(run))
2992
+ await notifyWhenChanged(run, readers.signal);
2993
+ if (activity && !isRunTerminal(run)) {
2978
2994
  append(run, {
2979
2995
  type: "activity.completed",
2980
2996
  activity: {
@@ -2986,11 +3002,15 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2986
3002
  }
2987
3003
  // A Stop that landed while the request was in flight stopped the whole
2988
3004
  // turn server-side, including the run this request started.
2989
- return run.terminal || run.streamClosed ? null : next;
3005
+ if (isRunTerminal(run)) {
3006
+ if (next)
3007
+ await closeDiscardedTurnEvents(next);
3008
+ return null;
3009
+ }
3010
+ return next;
2990
3011
  }
2991
3012
  function appendAuthorityTerminal(run, decision) {
2992
- run.streamClosed = true;
2993
- run.terminal = true;
3013
+ run.phase = transition(run.phase, { type: "terminal" });
2994
3014
  run.terminalSource = "authority";
2995
3015
  if ("error" in decision) {
2996
3016
  append(run, { type: "run.status", status: "failed" });
@@ -3007,9 +3027,10 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3007
3027
  }
3008
3028
  /** The runtime run's own interruption, once no newer run carries the turn. */
3009
3029
  function appendHeldInterruption(run) {
3010
- const error = run.heldInterruption;
3011
- run.heldInterruption = undefined;
3012
- run.terminal = true;
3030
+ if (run.phase.type !== "holdingInterruption")
3031
+ return;
3032
+ const error = run.phase.error;
3033
+ run.phase = transition(run.phase, { type: "terminal" });
3013
3034
  append(run, { type: "run.status", status: "failed" });
3014
3035
  append(run, { type: "run.failed", error });
3015
3036
  }
@@ -3031,63 +3052,77 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3031
3052
  }
3032
3053
  function ensurePump(run) {
3033
3054
  if (run.pumpPromise ||
3034
- run.streamClosed ||
3035
- run.terminal ||
3036
- !run.turn ||
3055
+ (run.phase.type !== "streaming" &&
3056
+ run.phase.type !== "followingSuccessor") ||
3037
3057
  readers.signal.aborted)
3038
3058
  return;
3039
3059
  run.pumpPromise = (async () => {
3040
3060
  try {
3041
- while (!run.terminal && !run.waitingForContinuation) {
3042
- const stream = {
3043
- failed: false,
3044
- };
3045
- try {
3046
- for await (const event of run.turn.events) {
3047
- const sequence = event.metadata?.seq;
3048
- if (typeof sequence === "number" &&
3049
- (run.runtimeSequence === undefined ||
3050
- sequence > run.runtimeSequence)) {
3051
- run.runtimeSequence = sequence;
3052
- run.resumeAttempts = 0;
3053
- run.quietAuthorityReads = 0;
3054
- }
3055
- for (const protocolEvent of runtimeEventToProtocolEvents(run, event)) {
3056
- append(run, protocolEvent);
3057
- }
3058
- if (run.waitingForContinuation ||
3059
- run.terminal ||
3060
- run.heldInterruption) {
3061
- break;
3061
+ let stream = { failed: false };
3062
+ for (;;) {
3063
+ while (isRunCancelling(run))
3064
+ await notifyWhenChanged(run, readers.signal);
3065
+ if (run.phase.type === "streaming") {
3066
+ stream = { failed: false };
3067
+ try {
3068
+ for await (const event of run.turn.events) {
3069
+ while (isRunCancelling(run))
3070
+ await notifyWhenChanged(run, readers.signal);
3071
+ if (!isRunPhase(run, "streaming"))
3072
+ break;
3073
+ const sequence = event.metadata?.seq;
3074
+ if (typeof sequence === "number" &&
3075
+ (run.runtimeSequence === undefined ||
3076
+ sequence > run.runtimeSequence)) {
3077
+ run.runtimeSequence = sequence;
3078
+ run.resumeAttempts = 0;
3079
+ run.quietAuthorityReads = 0;
3080
+ }
3081
+ for (const protocolEvent of runtimeEventToProtocolEvents(run, event)) {
3082
+ append(run, protocolEvent);
3083
+ }
3084
+ if (!isRunPhase(run, "streaming"))
3085
+ break;
3062
3086
  }
3063
3087
  }
3088
+ catch (error) {
3089
+ stream = { failed: true, error };
3090
+ }
3091
+ while (isRunCancelling(run))
3092
+ await notifyWhenChanged(run, readers.signal);
3093
+ if (isRunPhase(run, "streaming")) {
3094
+ run.pipeClosedBeforeTerminal = true;
3095
+ run.phase = transition(run.phase, {
3096
+ type: stream.failed ? "streamFailed" : "streamEnded",
3097
+ });
3098
+ }
3064
3099
  }
3065
- catch (error) {
3066
- stream.failed = true;
3067
- stream.error = error;
3068
- }
3069
- if (run.terminal || run.streamClosed || run.waitingForContinuation) {
3100
+ if (run.phase.type !== "followingSuccessor" &&
3101
+ run.phase.type !== "holdingInterruption")
3070
3102
  break;
3071
- }
3072
- if (run.restoredWithoutStream)
3073
- run.restoredWithoutStream = false;
3074
- else if (!run.heldInterruption)
3075
- run.pipeClosedBeforeTerminal = true;
3076
3103
  const resumed = runAuthority && capabilities.resumableRuns === true
3077
3104
  ? await followRunAuthority(run, runAuthority, stream)
3078
3105
  : await resumeWithoutAuthority(run, stream);
3079
- if (!resumed || run.terminal || run.waitingForContinuation)
3106
+ while (isRunCancelling(run))
3107
+ await notifyWhenChanged(run, readers.signal);
3108
+ if (!resumed) {
3109
+ if (isRunPhase(run, "followingSuccessor") ||
3110
+ isRunPhase(run, "holdingInterruption"))
3111
+ continue;
3112
+ break;
3113
+ }
3114
+ if (run.phase.type !== "followingSuccessor") {
3115
+ await closeDiscardedTurnEvents(resumed);
3080
3116
  break;
3117
+ }
3081
3118
  setResumedRuntimeTurn(run, resumed);
3119
+ run.phase = transition(run.phase, { type: "streamOpened" });
3082
3120
  }
3083
3121
  }
3084
3122
  finally {
3085
3123
  run.pumpPromise = null;
3086
3124
  for (const listener of run.listeners)
3087
3125
  listener();
3088
- if (!run.streamClosed && !run.terminal && !run.waitingForContinuation) {
3089
- ensurePump(run);
3090
- }
3091
3126
  }
3092
3127
  })();
3093
3128
  }
@@ -3108,7 +3143,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3108
3143
  }
3109
3144
  yield event;
3110
3145
  }
3111
- if ((run.streamClosed || run.terminal) && !run.cancelling)
3146
+ if (run.phase.type === "disposed" ||
3147
+ run.phase.type === "terminal" ||
3148
+ (run.phase.type === "awaitingContinuation" &&
3149
+ run.phase.streamClosed &&
3150
+ !run.pendingConnectionResponses))
3112
3151
  return;
3113
3152
  await notifyWhenChanged(run, signal);
3114
3153
  }
@@ -3303,44 +3342,35 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3303
3342
  run = await restoreRunFromRuntime(input);
3304
3343
  }
3305
3344
  touchRun(run);
3306
- if (run.terminal)
3345
+ if (isRunTerminal(run) || run.phase.type === "cancelling")
3307
3346
  return;
3308
- const streamWasClosed = run.streamClosed;
3309
3347
  // "user" is the server's turn-wide stop: it also stops any successor
3310
3348
  // run the turn would otherwise hand off to.
3311
3349
  const cancellation = cancelCoreTurn(run, "user");
3312
- run.streamClosed = true;
3313
- run.terminal = true;
3314
- // Stopping the turn can end its event stream before the cancel settles,
3315
- // and the pump wakes readers when it does. Without this hold they would
3316
- // see a closed run and leave before the cancelled events below exist.
3317
- run.cancelling = true;
3350
+ // Keep readers attached until the cancellation has produced its events.
3351
+ run.phase = transition(run.phase, { type: "cancelStarted" });
3318
3352
  try {
3319
3353
  let result;
3320
3354
  try {
3321
3355
  result = await cancellation;
3322
3356
  }
3323
3357
  catch (error) {
3324
- run.streamClosed = streamWasClosed;
3325
- run.terminal = false;
3326
- ensurePump(run);
3358
+ run.phase = transition(run.phase, { type: "cancelFailed" });
3327
3359
  throw error;
3328
3360
  }
3329
3361
  if (result.status === "unsupported") {
3330
- run.streamClosed = streamWasClosed;
3331
- run.terminal = false;
3332
- ensurePump(run);
3362
+ run.phase = transition(run.phase, { type: "cancelFailed" });
3333
3363
  throw new Error("The Core runtime does not support run cancellation.");
3334
3364
  }
3335
- run.waitingForContinuation = false;
3365
+ if (run.phase.type === "disposed")
3366
+ return;
3336
3367
  delete run.pendingApprovalId;
3337
3368
  run.terminalSource = "local";
3338
- run.cancelling = false;
3369
+ run.phase = transition(run.phase, { type: "cancelSucceeded" });
3339
3370
  append(run, { type: "run.status", status: "cancelled" });
3340
3371
  append(run, { type: "run.cancelled" });
3341
3372
  }
3342
3373
  finally {
3343
- run.cancelling = false;
3344
3374
  for (const listener of run.listeners)
3345
3375
  listener();
3346
3376
  }
@@ -3354,10 +3384,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3354
3384
  // Readers end without a terminal event: this adapter stopped watching,
3355
3385
  // and the server still owns how each run ends.
3356
3386
  for (const run of runs.values()) {
3357
- run.streamClosed = true;
3358
- run.terminal = true;
3359
- run.cancelling = false;
3360
- run.waitingForContinuation = false;
3387
+ run.phase = transition(run.phase, { type: "dispose" });
3361
3388
  run.pendingApprovalId = undefined;
3362
3389
  for (const listener of run.listeners)
3363
3390
  listener();
@@ -3501,10 +3528,12 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3501
3528
  const activePump = run.pumpPromise;
3502
3529
  if (activePump)
3503
3530
  await activePump;
3504
- if (run.terminal && !run.waitingForContinuation) {
3531
+ while (isRunCancelling(run))
3532
+ await notifyWhenChanged(run, readers.signal);
3533
+ if (isRunTerminal(run)) {
3505
3534
  throw new Error("The AgentKit run is already terminal.");
3506
3535
  }
3507
- if (!run.waitingForContinuation) {
3536
+ if (run.phase.type !== "awaitingContinuation") {
3508
3537
  throw new Error("The AgentKit run is not awaiting approval.");
3509
3538
  }
3510
3539
  if (run.pendingApprovalId !== entry.interruptId) {
@@ -3527,6 +3556,12 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3527
3556
  },
3528
3557
  abortSignal: readers.signal,
3529
3558
  });
3559
+ while (isRunCancelling(run))
3560
+ await notifyWhenChanged(run, readers.signal);
3561
+ if (!isRunPhase(run, "awaitingContinuation")) {
3562
+ await closeDiscardedTurnEvents(nextTurn);
3563
+ throw new Error("The AgentKit run is already terminal.");
3564
+ }
3530
3565
  const replacementMetadata = mergeTrustedProtocolMetadata(run.metadata, nextTurn.metadata, {
3531
3566
  [AGENT_NATIVE_PROTOCOL_METADATA_KEY]: {
3532
3567
  observability: {
@@ -3579,14 +3614,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3579
3614
  terminalAppendDepth: 0,
3580
3615
  pumpPromise: null,
3581
3616
  continuationPromise: null,
3582
- streamClosed: false,
3583
- terminal: false,
3584
- waitingForContinuation: false,
3617
+ phase: { type: "streaming" },
3585
3618
  listeners: new Set(),
3586
3619
  };
3587
- run.waitingForContinuation = false;
3588
3620
  run.pendingApprovalId = undefined;
3589
- run.terminal = true;
3621
+ run.phase = transition(run.phase, { type: "terminal" });
3590
3622
  run.terminalAtMs = timeMs();
3591
3623
  indexRun(replacementRun, nextTurn.runId);
3592
3624
  append(replacementRun, {
@@ -3703,94 +3735,121 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3703
3735
  await run.pumpPromise;
3704
3736
  }
3705
3737
  touchRun(run);
3706
- if (input.response.status === "declined" && run.terminal)
3707
- return;
3708
- if (run.pendingConnectionRequestId !== input.requestId) {
3709
- const staleDecline = input.response.status === "declined" &&
3710
- run.initialAuthorityRead?.kind === "read" &&
3711
- run.initialAuthorityRead.state.status !== "running";
3712
- if (staleDecline)
3713
- return;
3714
- throw new Error(`Connection response ${input.requestId} does not match pending request ${run.pendingConnectionRequestId ?? "<none>"}.`);
3715
- }
3716
- const pendingEvent = [...run.events]
3717
- .reverse()
3718
- .find((event) => event.type === "connection.requested" &&
3719
- event.request.id === input.requestId);
3720
- if (!pendingEvent || pendingEvent.type !== "connection.requested") {
3721
- throw new Error(`Unknown connection request: ${input.requestId}`);
3722
- }
3723
- const update = (status) => append(run, {
3724
- type: "connection.updated",
3725
- request: {
3726
- ...pendingEvent.request,
3727
- status,
3728
- updatedAt: now(),
3729
- },
3730
- });
3731
- if (input.response.status === "failed") {
3732
- update("failed");
3733
- run.waitingForContinuation = false;
3734
- run.pendingConnectionRequestId = undefined;
3735
- run.streamClosed = true;
3736
- run.terminalSource = "local";
3737
- append(run, { type: "run.status", status: "failed" });
3738
- append(run, {
3739
- type: "run.failed",
3740
- error: {
3741
- code: "connection_failed",
3742
- message: input.response.message?.trim() ||
3743
- `Failed to connect ${pendingEvent.request.provider}.`,
3744
- },
3745
- });
3746
- pruneRetainedRuns(run.terminalAtMs);
3747
- return;
3748
- }
3749
- if (!run.session.continueTurn) {
3750
- throw new Error("The Core runtime does not support connection continuation.");
3751
- }
3752
- if (run.continuationPromise) {
3753
- throw new Error("A connection continuation is already in progress.");
3754
- }
3755
- update("connecting");
3756
- const continuation = (async () => {
3757
- const activePump = run.pumpPromise;
3758
- if (activePump)
3759
- await activePump;
3760
- if (run.terminal) {
3738
+ run.pendingConnectionResponses =
3739
+ (run.pendingConnectionResponses ?? 0) + 1;
3740
+ try {
3741
+ while (isRunCancelling(run))
3742
+ await notifyWhenChanged(run, readers.signal);
3743
+ if (isRunTerminal(run)) {
3744
+ if (input.response.status === "declined")
3745
+ return;
3761
3746
  throw new Error("The AgentKit run is already terminal.");
3762
3747
  }
3763
- const nextTurn = await run.session.continueTurn({
3764
- turnId: run.turn.id,
3765
- connection: {
3766
- id: pendingEvent.request.provider,
3767
- status: input.response.status === "connected" ? "connected" : "declined",
3768
- connectionId: input.response.connectionId,
3769
- message: input.response.message,
3748
+ if (run.pendingConnectionRequestId !== input.requestId) {
3749
+ const staleDecline = input.response.status === "declined" &&
3750
+ run.initialAuthorityRead?.kind === "read" &&
3751
+ run.initialAuthorityRead.state.status !== "running";
3752
+ if (staleDecline)
3753
+ return;
3754
+ throw new Error(`Connection response ${input.requestId} does not match pending request ${run.pendingConnectionRequestId ?? "<none>"}.`);
3755
+ }
3756
+ const pendingEvent = [...run.events]
3757
+ .reverse()
3758
+ .find((event) => event.type === "connection.requested" &&
3759
+ event.request.id === input.requestId);
3760
+ if (!pendingEvent || pendingEvent.type !== "connection.requested") {
3761
+ throw new Error(`Unknown connection request: ${input.requestId}`);
3762
+ }
3763
+ const update = (status) => append(run, {
3764
+ type: "connection.updated",
3765
+ request: {
3766
+ ...pendingEvent.request,
3767
+ status,
3768
+ updatedAt: now(),
3770
3769
  },
3771
- abortSignal: readers.signal,
3772
3770
  });
3773
- run.waitingForContinuation = false;
3774
- run.pendingConnectionRequestId = undefined;
3775
- run.turn = nextTurn;
3776
- indexRun(run, nextTurn.runId);
3777
- run.streamClosed = false;
3778
- update(input.response.status);
3779
- append(run, { type: "run.status", status: "running" });
3780
- ensurePump(run);
3781
- })();
3782
- run.continuationPromise = continuation;
3783
- try {
3784
- await continuation;
3785
- }
3786
- catch (error) {
3787
- update("failed");
3788
- throw error;
3771
+ if (input.response.status === "failed") {
3772
+ update("failed");
3773
+ delete run.pendingConnectionRequestId;
3774
+ run.phase = transition(run.phase, { type: "terminal" });
3775
+ run.terminalSource = "local";
3776
+ append(run, { type: "run.status", status: "failed" });
3777
+ append(run, {
3778
+ type: "run.failed",
3779
+ error: {
3780
+ code: "connection_failed",
3781
+ message: input.response.message?.trim() ||
3782
+ `Failed to connect ${pendingEvent.request.provider}.`,
3783
+ },
3784
+ });
3785
+ pruneRetainedRuns(run.terminalAtMs);
3786
+ return;
3787
+ }
3788
+ const continueTurn = run.session.continueTurn?.bind(run.session);
3789
+ if (!continueTurn) {
3790
+ throw new Error("The Core runtime does not support connection continuation.");
3791
+ }
3792
+ if (run.continuationPromise) {
3793
+ throw new Error("A connection continuation is already in progress.");
3794
+ }
3795
+ update("connecting");
3796
+ const continuation = (async () => {
3797
+ const activePump = run.pumpPromise;
3798
+ if (activePump)
3799
+ await activePump;
3800
+ while (isRunCancelling(run))
3801
+ await notifyWhenChanged(run, readers.signal);
3802
+ if (isRunTerminal(run)) {
3803
+ throw new Error("The AgentKit run is already terminal.");
3804
+ }
3805
+ const nextTurn = await continueTurn({
3806
+ ...(run.turn.id === undefined ? {} : { turnId: run.turn.id }),
3807
+ connection: {
3808
+ id: pendingEvent.request.provider,
3809
+ status: input.response.status === "connected"
3810
+ ? "connected"
3811
+ : "declined",
3812
+ ...(input.response.connectionId === undefined
3813
+ ? {}
3814
+ : { connectionId: input.response.connectionId }),
3815
+ ...(input.response.message === undefined
3816
+ ? {}
3817
+ : { message: input.response.message }),
3818
+ },
3819
+ abortSignal: readers.signal,
3820
+ });
3821
+ while (isRunCancelling(run))
3822
+ await notifyWhenChanged(run, readers.signal);
3823
+ if (run.phase.type !== "awaitingContinuation") {
3824
+ await closeDiscardedTurnEvents(nextTurn);
3825
+ throw new Error("The AgentKit run is already terminal.");
3826
+ }
3827
+ delete run.pendingConnectionRequestId;
3828
+ run.turn = nextTurn;
3829
+ indexRun(run, nextTurn.runId);
3830
+ run.phase = transition(run.phase, { type: "continuationResumed" });
3831
+ update(input.response.status);
3832
+ append(run, { type: "run.status", status: "running" });
3833
+ ensurePump(run);
3834
+ })();
3835
+ run.continuationPromise = continuation;
3836
+ try {
3837
+ await continuation;
3838
+ }
3839
+ catch (error) {
3840
+ update("failed");
3841
+ throw error;
3842
+ }
3843
+ finally {
3844
+ if (run.continuationPromise === continuation) {
3845
+ run.continuationPromise = null;
3846
+ }
3847
+ }
3789
3848
  }
3790
3849
  finally {
3791
- if (run.continuationPromise === continuation) {
3792
- run.continuationPromise = null;
3793
- }
3850
+ run.pendingConnectionResponses -= 1;
3851
+ for (const listener of run.listeners)
3852
+ listener();
3794
3853
  }
3795
3854
  };
3796
3855
  }