@agent-native/core 0.204.0 → 0.204.1-nightly-20261008034100

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 (106) hide show
  1. package/dist/action.js +5 -6
  2. package/dist/agent/auto-continue.d.ts +35 -0
  3. package/dist/agent/auto-continue.js +38 -0
  4. package/dist/agent/connection-required-note.d.ts +52 -0
  5. package/dist/agent/connection-required-note.js +187 -0
  6. package/dist/agent/jev-tool-prefetch.d.ts +5 -1
  7. package/dist/agent/jev-tool-prefetch.js +19 -6
  8. package/dist/agent/previous-turn-note.d.ts +9 -0
  9. package/dist/agent/previous-turn-note.js +48 -0
  10. package/dist/agent/production-agent.d.ts +17 -13
  11. package/dist/agent/production-agent.js +185 -43
  12. package/dist/agent/reaped-run-thread.d.ts +13 -0
  13. package/dist/agent/reaped-run-thread.js +45 -0
  14. package/dist/agent/run-store.d.ts +29 -5
  15. package/dist/agent/run-store.js +144 -30
  16. package/dist/agent/thread-data-builder.d.ts +16 -0
  17. package/dist/agent/thread-data-builder.js +30 -8
  18. package/dist/agent/thread-message-projection.js +5 -3
  19. package/dist/agent/tool-search.js +5 -1
  20. package/dist/agent/types.d.ts +3 -0
  21. package/dist/audit/config.d.ts +6 -0
  22. package/dist/audit/config.js +12 -1
  23. package/dist/audit/index.d.ts +1 -1
  24. package/dist/audit/index.js +1 -1
  25. package/dist/audit/store.js +39 -4
  26. package/dist/cli/skills.d.ts +1 -0
  27. package/dist/cli/skills.js +1 -1
  28. package/dist/client/chat/agentkit-agent-native.js +83 -7
  29. package/dist/client/chat/agentkit-protocol.js +140 -67
  30. package/dist/client/chat/runtime.js +5 -3
  31. package/dist/client/use-run-stuck-detection.d.ts +18 -1
  32. package/dist/client/use-run-stuck-detection.js +115 -75
  33. package/dist/db/request-telemetry.d.ts +7 -1
  34. package/dist/db/request-telemetry.js +20 -1
  35. package/dist/deploy/build.d.ts +25 -1
  36. package/dist/deploy/build.js +176 -3
  37. package/dist/deploy/workspace-deploy.js +8 -3
  38. package/dist/extensions/fetch-tool.js +50 -14
  39. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  40. package/dist/integrations/adapters/email.js +11 -3
  41. package/dist/integrations/adapters/telegram.js +4 -2
  42. package/dist/jobs/actions/automation-no-op.d.ts +12 -0
  43. package/dist/jobs/actions/automation-no-op.js +20 -0
  44. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  45. package/dist/jobs/background-automation-runner.d.ts +8 -1
  46. package/dist/jobs/background-automation-runner.js +102 -19
  47. package/dist/jobs/run-history.d.ts +1 -1
  48. package/dist/jobs/run-history.js +8 -4
  49. package/dist/jobs/scheduler.js +25 -12
  50. package/dist/localization/automation-outcome-messages.d.ts +22 -0
  51. package/dist/localization/automation-outcome-messages.js +90 -0
  52. package/dist/localization/default-messages.d.ts +2 -0
  53. package/dist/localization/default-messages.js +4 -2
  54. package/dist/notifications/routes.d.ts +1 -1
  55. package/dist/observability/metrics.d.ts +3 -0
  56. package/dist/observability/metrics.js +10 -0
  57. package/dist/observability/migrations.js +22 -0
  58. package/dist/observability/routes.d.ts +13 -8
  59. package/dist/observability/routes.js +88 -14
  60. package/dist/observability/sentiment.d.ts +5 -0
  61. package/dist/observability/sentiment.js +75 -25
  62. package/dist/observability/store.d.ts +28 -0
  63. package/dist/observability/store.js +58 -0
  64. package/dist/observability/traces.js +37 -5
  65. package/dist/org/index.d.ts +2 -2
  66. package/dist/progress/actions.js +1 -0
  67. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  68. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  69. package/dist/provider-api/actions/provider-api.js +6 -2
  70. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  71. package/dist/provider-api/index.d.ts +1 -0
  72. package/dist/provider-api/index.js +18 -0
  73. package/dist/provider-api/result-outcome.d.ts +1 -0
  74. package/dist/provider-api/result-outcome.js +20 -0
  75. package/dist/provider-api/staging.js +10 -9
  76. package/dist/server/agent-chat/prompt-resources.d.ts +8 -2
  77. package/dist/server/agent-chat/prompt-resources.js +30 -7
  78. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +19 -4
  79. package/dist/server/agent-chat/recurring-jobs-runtime.js +52 -16
  80. package/dist/server/agent-chat/sweep-trigger-auth.d.ts +18 -0
  81. package/dist/server/agent-chat/sweep-trigger-auth.js +36 -0
  82. package/dist/server/agent-chat-plugin.js +20 -13
  83. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  84. package/dist/server/auth.js +4 -4
  85. package/dist/server/framework-request-handler.js +19 -0
  86. package/dist/server/http-response-telemetry.js +4 -1
  87. package/dist/server/realtime-token.d.ts +1 -1
  88. package/dist/server/request-context.d.ts +6 -0
  89. package/dist/shared/context-status.d.ts +21 -0
  90. package/dist/shared/context-status.js +54 -0
  91. package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  92. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  93. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
  94. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  95. package/dist/triggers/dispatcher.js +10 -7
  96. package/docs/content/audit-log.mdx +3 -3
  97. package/docs/content/cloudflare.mdx +27 -0
  98. package/docs/content/deployment-environment-variables.mdx +2 -0
  99. package/docs/content/durable-resume.mdx +3 -1
  100. package/docs/content/environment-variables.mdx +2 -0
  101. package/docs/content/recurring-jobs.mdx +29 -9
  102. package/docs/content/vercel.mdx +30 -0
  103. package/package.json +3 -3
  104. package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  105. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  106. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
@@ -365,6 +365,31 @@ function durableRunFailures(messages) {
365
365
  ];
366
366
  }));
367
367
  }
368
+ /** The turn each durable prompt started, keyed by the run it was submitted to. */
369
+ function durableSubmittedTurns(messages) {
370
+ return new Map(messages.flatMap((message) => {
371
+ const custom = asRecord(asRecord(message.metadata)?.custom);
372
+ return message.role === "user" &&
373
+ typeof custom?.submittedRunId === "string" &&
374
+ typeof custom.submittedTurnId === "string"
375
+ ? [[custom.submittedRunId, custom.submittedTurnId]]
376
+ : [];
377
+ }));
378
+ }
379
+ /** The runs that left a durable reply on each turn. */
380
+ function durableReplyRunsByTurn(messages) {
381
+ const result = new Map();
382
+ for (const message of messages) {
383
+ const turnId = asRecord(asRecord(message.metadata)?.custom)?.turnId;
384
+ if (message.role !== "assistant" || typeof turnId !== "string")
385
+ continue;
386
+ const runIds = result.get(turnId) ?? new Set();
387
+ for (const runId of durableRunIds(message))
388
+ runIds.add(runId);
389
+ result.set(turnId, runIds);
390
+ }
391
+ return result;
392
+ }
368
393
  /** A durable reply's terminal run plus every continuation run folded into it. */
369
394
  function durableRunIds(message) {
370
395
  const metadata = asRecord(message.metadata);
@@ -561,6 +586,38 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
561
586
  durableByFoldedRun.set(runId, durableByFoldedRun.has(runId) ? null : message);
562
587
  }
563
588
  }
589
+ const textOf = (parts) => parts
590
+ .filter((part) => part.type === "text")
591
+ .map((part) => part.text)
592
+ .join("");
593
+ // A run that recovered an interrupted turn records only itself on its reply,
594
+ // while an open page streamed it into the run the prompt was submitted to.
595
+ // The recovering run's events open their own message, so the reply is on
596
+ // screen only when one of that run's messages is exactly it; anything less
597
+ // keeps the saved reply, so the answer is never dropped.
598
+ const submittedRunByTurn = new Map();
599
+ for (const stored of submittedUsers) {
600
+ const turnId = asRecord(asRecord(stored.metadata)?.custom)?.submittedTurnId;
601
+ if (typeof turnId !== "string")
602
+ continue;
603
+ submittedRunByTurn.set(turnId, submittedRunByTurn.has(turnId) ? null : submittedRunId(stored));
604
+ }
605
+ const shownByInterruptedRun = (reply) => {
606
+ const turnId = asRecord(asRecord(reply.metadata)?.custom)?.turnId;
607
+ const submitted = typeof turnId === "string" ? submittedRunByTurn.get(turnId) : undefined;
608
+ if (!submitted ||
609
+ durableRunIds(reply).includes(submitted) ||
610
+ durableByRun.has(submitted)) {
611
+ return false;
612
+ }
613
+ const ids = assistantIdsByRun.get(submitted);
614
+ const replyText = textOf(reply.parts);
615
+ if (!ids || !replyText)
616
+ return false;
617
+ return messages.some((message) => message.role === "assistant" &&
618
+ ids.has(message.id) &&
619
+ textOf(message.parts) === replyText);
620
+ };
564
621
  const rootAssistantProjection = projectRootAssistantMessages({
565
622
  rootMessages: durable,
566
623
  snapshotMessages: messages,
@@ -676,6 +733,8 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
676
733
  if (typeof runId !== "string" || !recoverableAssistantRunIds.has(runId)) {
677
734
  continue;
678
735
  }
736
+ if (shownByInterruptedRun(message))
737
+ continue;
679
738
  missingMessages.push(message);
680
739
  representedAssistantIds.add(message.id);
681
740
  }
@@ -694,10 +753,6 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
694
753
  snapshotRunIdsByMessageId.get(message.id) ??
695
754
  (typeof metadataRunId === "string" ? metadataRunId : undefined));
696
755
  };
697
- const textOf = (parts) => parts
698
- .filter((part) => part.type === "text")
699
- .map((part) => part.text)
700
- .join("");
701
756
  const reconciledMessages = projectedMessages.map((message) => {
702
757
  if (message.role === "user") {
703
758
  const stored = storedUserBySnapshotId.get(message.id);
@@ -1652,8 +1707,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1652
1707
  }
1653
1708
  return value;
1654
1709
  }
1655
- async function activeRunSnapshot(threadId) {
1656
- const value = await activeRunStatus(threadId);
1710
+ async function activeRunSnapshot(threadId, value) {
1657
1711
  const status = value.status;
1658
1712
  if (typeof value.active !== "boolean")
1659
1713
  return undefined;
@@ -1775,7 +1829,8 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1775
1829
  const completedRunIds = completedDurableRunIds(durableMessages);
1776
1830
  const userStoppedRunIds = userStoppedDurableRunIds(durableMessages);
1777
1831
  const durableFailures = durableRunFailures(durableMessages);
1778
- const activeRun = await activeRunSnapshot(threadId);
1832
+ const activeStatus = await activeRunStatus(threadId);
1833
+ const activeRun = await activeRunSnapshot(threadId, activeStatus);
1779
1834
  if (activeRun === undefined)
1780
1835
  return thread;
1781
1836
  let discoveredRun = activeRun;
@@ -1792,9 +1847,30 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1792
1847
  !["completed", "failed", "cancelled"].includes(discoveredRun.status)
1793
1848
  ? discoveredRun.id
1794
1849
  : undefined;
1850
+ const submittedTurns = durableSubmittedTurns(durableMessages);
1851
+ const replyRunsByTurn = durableReplyRunsByTurn(durableMessages);
1852
+ const discoveredTurnId = typeof activeStatus.turnId === "string" ? activeStatus.turnId : undefined;
1853
+ // The stale-run reaper hands an interrupted turn to a successor run, which
1854
+ // then owns the turn's outcome: the server reports it, or it completed a
1855
+ // reply. A successor that failed or was stopped leaves the failure showing.
1856
+ const carriedOnByNewerRun = (runId) => {
1857
+ const turnId = submittedTurns.get(runId);
1858
+ if (!turnId)
1859
+ return false;
1860
+ if (discoveredRun && discoveredTurnId === turnId)
1861
+ return true;
1862
+ return [...(replyRunsByTurn.get(turnId) ?? [])].some((id) => id !== runId && completedRunIds.has(id));
1863
+ };
1795
1864
  const runs = (thread.runs ?? [])
1796
1865
  .filter((entry) => entry.id !== discoveredRun?.id)
1797
1866
  .map((run) => {
1867
+ if ((["queued", "running"].includes(run.status) ||
1868
+ (run.status === "failed" &&
1869
+ runOutcomeForCode(run.error?.code) === "interrupted")) &&
1870
+ carriedOnByNewerRun(run.id)) {
1871
+ const { error: _error, ...attempt } = run;
1872
+ return { ...attempt, status: "completed" };
1873
+ }
1798
1874
  if (activeRun === null &&
1799
1875
  run.status === "running" &&
1800
1876
  userStoppedRunIds.has(run.id)) {
@@ -1,5 +1,5 @@
1
1
  import { AGENT_TOOL_CALL_HISTORY_MEDIA_TYPE, AGENT_TOOL_RESULT_HISTORY_MEDIA_TYPE, AGENTKIT_PROTOCOL_VERSION, AgentProtocolValidationError, approvalResponseFromResume, createCapabilityUnavailableError, createCapabilityUnsupportedError, inferAgentActivityKind, negotiateAgentKitProtocolVersion, resumeEntryFromApproval, resumeOptionId, } from "@agent-native/agentkit/protocol";
2
- import { AUTO_CONTINUE_OF_RUN_METADATA_KEY, AUTO_CONTINUE_PROMPT, AUTO_CONTINUE_REFUSAL_CODES, } from "../../agent/auto-continue.js";
2
+ import { AUTO_CONTINUE_OF_RUN_METADATA_KEY, AUTO_CONTINUE_PROMPT, AUTO_CONTINUE_REFUSAL_CODES, CONTINUE_OF_RUN_METADATA_KEY, CONTINUE_UNAVAILABLE_CODE, } from "../../agent/auto-continue.js";
3
3
  import { emitChatFirstOpenApp, emitChatFirstOpenBrowser, } from "../chat-first-state.js";
4
4
  import { decideAfterStreamClosed, runOutcomeForCode, } from "./run-outcome.js";
5
5
  function isTerminalRunStatus(status) {
@@ -1104,6 +1104,67 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1104
1104
  ensurePump(run);
1105
1105
  return run;
1106
1106
  }
1107
+ /**
1108
+ * Registers a run the runtime just started and begins reading it. A disposed
1109
+ * adapter keeps no run it can no longer read.
1110
+ */
1111
+ async function openStartedRun(threadId, session, turn, turnMetadata, observability) {
1112
+ const runId = turn.runId ?? turn.id ?? createId("run");
1113
+ const runMetadata = mergeTrustedProtocolMetadata(options.metadata, turnMetadata, turn.metadata, {
1114
+ [AGENT_NATIVE_PROTOCOL_METADATA_KEY]: {
1115
+ observability: {
1116
+ protocolRunId: runId,
1117
+ ...(turn.runId === undefined ? {} : { runtimeRunId: turn.runId }),
1118
+ runtimeId: runtime.id,
1119
+ sessionId: session.id,
1120
+ turnId: turn.id,
1121
+ threadId,
1122
+ ...observability,
1123
+ },
1124
+ },
1125
+ });
1126
+ setRuntimeRunIdMetadata(runMetadata, turn.runId);
1127
+ const run = {
1128
+ runId,
1129
+ threadId,
1130
+ session,
1131
+ turn,
1132
+ events: [],
1133
+ firstRetainedSequence: 1,
1134
+ sequence: 0,
1135
+ status: "queued",
1136
+ lastAccessedAtMs: timeMs(),
1137
+ activeReaders: 0,
1138
+ metadata: runMetadata,
1139
+ activeMessageCompleted: false,
1140
+ pendingWidgets: new Map(),
1141
+ actions: new Map(),
1142
+ activeTools: new Map(),
1143
+ pendingToolMessageAssociations: new Map(),
1144
+ activeActivities: new Map(),
1145
+ terminalAppendDepth: 0,
1146
+ pumpPromise: null,
1147
+ continuationPromise: null,
1148
+ streamClosed: false,
1149
+ terminal: false,
1150
+ waitingForContinuation: false,
1151
+ listeners: new Set(),
1152
+ };
1153
+ if (disposed) {
1154
+ await disposeSession(session).catch(() => undefined);
1155
+ throw new Error("The AgentKit adapter was disposed during turn creation.");
1156
+ }
1157
+ pruneRetainedRuns(run.lastAccessedAtMs);
1158
+ indexRun(run, turn.runId);
1159
+ append(run, {
1160
+ type: "run.started",
1161
+ agentId: runtime.id,
1162
+ metadata: runMetadata,
1163
+ });
1164
+ append(run, { type: "run.status", status: "running" });
1165
+ ensurePump(run);
1166
+ return runId;
1167
+ }
1107
1168
  async function disposeSession(session) {
1108
1169
  if (disposedSessions.has(session))
1109
1170
  return;
@@ -2012,21 +2073,26 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2012
2073
  payload: run.usage,
2013
2074
  },
2014
2075
  ];
2015
- case "error":
2076
+ case "error": {
2077
+ const error = {
2078
+ code: event.code ?? "runtime_error",
2079
+ message: event.error,
2080
+ retryable: event.retryable ?? event.recoverable,
2081
+ details: event.details ?? event.cause,
2082
+ };
2083
+ // An interruption ends this runtime run, not necessarily the turn.
2084
+ if (runAuthority &&
2085
+ capabilities.resumableRuns === true &&
2086
+ runOutcomeForCode(error.code) === "interrupted") {
2087
+ run.heldInterruption = error;
2088
+ return [];
2089
+ }
2016
2090
  run.terminal = true;
2017
2091
  return [
2018
2092
  { type: "run.status", ...base, status: "failed" },
2019
- {
2020
- type: "run.failed",
2021
- ...base,
2022
- error: {
2023
- code: event.code ?? "runtime_error",
2024
- message: event.error,
2025
- retryable: event.retryable ?? event.recoverable,
2026
- details: event.details ?? event.cause,
2027
- },
2028
- },
2093
+ { type: "run.failed", ...base, error },
2029
2094
  ];
2095
+ }
2030
2096
  case "done":
2031
2097
  if (!isTerminalReason(event.reason)) {
2032
2098
  run.waitingForContinuation = true;
@@ -2256,6 +2322,18 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2256
2322
  }
2257
2323
  if (stopped())
2258
2324
  return null;
2325
+ // An unreachable server says nothing about a successor; keep holding
2326
+ // through the usual backoff until it answers.
2327
+ if (run.heldInterruption && read.kind !== "unreachable") {
2328
+ const successor = read.kind === "read" &&
2329
+ read.state.status !== "missing" &&
2330
+ read.state.runId !== runtimeRunId;
2331
+ if (!successor) {
2332
+ appendHeldInterruption(run);
2333
+ return null;
2334
+ }
2335
+ run.heldInterruption = undefined;
2336
+ }
2259
2337
  const decision = decideAfterStreamClosed(read, {
2260
2338
  runId: runtimeRunId,
2261
2339
  drain: run.terminalDrain,
@@ -2406,6 +2484,14 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2406
2484
  append(run, { type: "run.cancelled" });
2407
2485
  }
2408
2486
  }
2487
+ /** The runtime run's own interruption, once no newer run carries the turn. */
2488
+ function appendHeldInterruption(run) {
2489
+ const error = run.heldInterruption;
2490
+ run.heldInterruption = undefined;
2491
+ run.terminal = true;
2492
+ append(run, { type: "run.status", status: "failed" });
2493
+ append(run, { type: "run.failed", error });
2494
+ }
2409
2495
  function setResumedRuntimeTurn(run, resumed) {
2410
2496
  const runtimeRunId = run.turn.runId ?? run.runId;
2411
2497
  if (resumed.runId && resumed.runId !== runtimeRunId) {
@@ -2439,8 +2525,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2439
2525
  for (const protocolEvent of runtimeEventToProtocolEvents(run, event)) {
2440
2526
  append(run, protocolEvent);
2441
2527
  }
2442
- if (run.waitingForContinuation || run.terminal)
2528
+ if (run.waitingForContinuation ||
2529
+ run.terminal ||
2530
+ run.heldInterruption) {
2443
2531
  break;
2532
+ }
2444
2533
  }
2445
2534
  }
2446
2535
  catch (error) {
@@ -2452,7 +2541,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2452
2541
  }
2453
2542
  if (run.restoredWithoutStream)
2454
2543
  run.restoredWithoutStream = false;
2455
- else
2544
+ else if (!run.heldInterruption)
2456
2545
  run.pipeClosedBeforeTerminal = true;
2457
2546
  const resumed = runAuthority && capabilities.resumableRuns === true
2458
2547
  ? await followRunAuthority(run, runAuthority, stream)
@@ -2578,59 +2667,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2578
2667
  metadata: turnMetadata,
2579
2668
  abortSignal: readers.signal,
2580
2669
  });
2581
- const runId = turn.runId ?? turn.id ?? createId("run");
2582
- const runMetadata = mergeTrustedProtocolMetadata(options.metadata, turnMetadata, turn.metadata, {
2583
- [AGENT_NATIVE_PROTOCOL_METADATA_KEY]: {
2584
- observability: {
2585
- protocolRunId: runId,
2586
- ...(turn.runId === undefined ? {} : { runtimeRunId: turn.runId }),
2587
- runtimeId: runtime.id,
2588
- sessionId: session.id,
2589
- turnId: turn.id,
2590
- threadId: input.threadId,
2591
- },
2592
- },
2593
- });
2594
- setRuntimeRunIdMetadata(runMetadata, turn.runId);
2595
- const run = {
2596
- runId,
2597
- threadId: input.threadId,
2598
- session,
2599
- turn,
2600
- events: [],
2601
- firstRetainedSequence: 1,
2602
- sequence: 0,
2603
- status: "queued",
2604
- lastAccessedAtMs: timeMs(),
2605
- activeReaders: 0,
2606
- metadata: runMetadata,
2607
- activeMessageCompleted: false,
2608
- pendingWidgets: new Map(),
2609
- actions: new Map(),
2610
- activeTools: new Map(),
2611
- pendingToolMessageAssociations: new Map(),
2612
- activeActivities: new Map(),
2613
- terminalAppendDepth: 0,
2614
- pumpPromise: null,
2615
- continuationPromise: null,
2616
- streamClosed: false,
2617
- terminal: false,
2618
- waitingForContinuation: false,
2619
- listeners: new Set(),
2620
- };
2621
- if (disposed) {
2622
- await disposeSession(session).catch(() => undefined);
2623
- throw new Error("The AgentKit adapter was disposed during turn creation.");
2624
- }
2625
- pruneRetainedRuns(run.lastAccessedAtMs);
2626
- indexRun(run, turn.runId);
2627
- append(run, {
2628
- type: "run.started",
2629
- agentId: runtime.id,
2630
- metadata: runMetadata,
2631
- });
2632
- append(run, { type: "run.status", status: "running" });
2633
- ensurePump(run);
2670
+ const runId = await openStartedRun(input.threadId, session, turn, turnMetadata);
2634
2671
  return { runId, capabilities };
2635
2672
  },
2636
2673
  async *subscribeToRun(input) {
@@ -2984,6 +3021,42 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
2984
3021
  });
2985
3022
  };
2986
3023
  }
3024
+ if (runAuthority) {
3025
+ const authority = runAuthority;
3026
+ // Continues the turn a stopped run belonged to rather than starting a new
3027
+ // one, so the turn's journal still blocks repeating a finished write. The
3028
+ // server reads which run ended the turn; this page may know only the one
3029
+ // it watched, or none after a reload.
3030
+ transport.continueRun = async (input) => {
3031
+ if (disposed)
3032
+ throw new Error("The AgentKit adapter is disposed.");
3033
+ pruneRetainedRuns();
3034
+ const local = runs.get(input.runId);
3035
+ if (local && local.threadId !== input.threadId) {
3036
+ throw new Error(`Unknown AgentKit run: ${input.runId}`);
3037
+ }
3038
+ const session = local?.session ?? (await getSession(input.threadId));
3039
+ if (!session.continueTurn) {
3040
+ throw new Error("The Core runtime does not support run continuation.");
3041
+ }
3042
+ const state = await authority.readRunState({
3043
+ sessionId: session.id,
3044
+ runId: local?.turn.runId ?? input.runId,
3045
+ abortSignal: readers.signal,
3046
+ });
3047
+ if (state.status === "missing" || !state.turnId) {
3048
+ throw Object.assign(new Error(`Agent run ${input.runId} cannot be continued.`), { code: CONTINUE_UNAVAILABLE_CODE, retryable: false });
3049
+ }
3050
+ const turn = await session.continueTurn({
3051
+ turnId: state.turnId,
3052
+ prompt: AUTO_CONTINUE_PROMPT,
3053
+ metadata: { [CONTINUE_OF_RUN_METADATA_KEY]: state.runId },
3054
+ abortSignal: readers.signal,
3055
+ });
3056
+ const runId = await openStartedRun(input.threadId, session, turn, undefined, { continuedRunId: input.runId });
3057
+ return { runId, capabilities };
3058
+ };
3059
+ }
2987
3060
  if (runtime.capabilities.rich?.connectionRequests) {
2988
3061
  transport.resolveConnectionRequest = async (input) => {
2989
3062
  pruneRetainedRuns();
@@ -1,5 +1,5 @@
1
1
  import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
2
- import { AUTO_CONTINUE_OF_RUN_METADATA_KEY } from "../../agent/auto-continue.js";
2
+ import { AUTO_CONTINUE_OF_RUN_METADATA_KEY, CONTINUE_OF_RUN_METADATA_KEY, } from "../../agent/auto-continue.js";
3
3
  import { agentChatStreamingUrl, agentNativePath } from "../api-path.js";
4
4
  import { CHAT_REQUEST_TOO_LARGE_MESSAGE } from "../error-format.js";
5
5
  import { appendMissingFinalResponseWarning, } from "../sse-event-processor.js";
@@ -2449,6 +2449,7 @@ export function createAgentNativeChatRuntime(options = {}) {
2449
2449
  const approvedToolCalls = metadataStringList(turn.metadata, AGENT_NATIVE_APPROVED_TOOL_CALLS_METADATA_KEY);
2450
2450
  const continuationTurnId = metadataString(turn.metadata, AGENT_NATIVE_CONTINUATION_TURN_ID_METADATA_KEY);
2451
2451
  const autoContinueOfRunId = metadataString(turn.metadata, AUTO_CONTINUE_OF_RUN_METADATA_KEY);
2452
+ const continueOfRunId = metadataString(turn.metadata, CONTINUE_OF_RUN_METADATA_KEY);
2452
2453
  const continuationMessageState = continuationTurnId
2453
2454
  ? messageStates.get(continuationTurnId)
2454
2455
  : undefined;
@@ -2481,6 +2482,7 @@ export function createAgentNativeChatRuntime(options = {}) {
2481
2482
  ? { internalContinuation: true }
2482
2483
  : {}),
2483
2484
  ...(autoContinueOfRunId ? { autoContinueOfRunId } : {}),
2485
+ ...(continueOfRunId ? { continueOfRunId } : {}),
2484
2486
  ...(turn.metadata?.agentNativeSkipPendingSelectionContext === true
2485
2487
  ? { skipPendingSelectionContext: true }
2486
2488
  : {}),
@@ -2527,8 +2529,8 @@ export function createAgentNativeChatRuntime(options = {}) {
2527
2529
  },
2528
2530
  continueTurn: ({ session, continuation, previousTurn, startTurn }) => {
2529
2531
  // Only the continuation that names a stopped run continues it; an approval
2530
- // or connection answered later is not another automatic continuation.
2531
- const { [AUTO_CONTINUE_OF_RUN_METADATA_KEY]: _autoContinueOf, ...previousMetadata } = previousTurn?.metadata ?? {};
2532
+ // or connection answered later is not another continuation of that run.
2533
+ const { [AUTO_CONTINUE_OF_RUN_METADATA_KEY]: _autoContinueOf, [CONTINUE_OF_RUN_METADATA_KEY]: _continueOf, ...previousMetadata } = previousTurn?.metadata ?? {};
2532
2534
  const approval = continuation.approval;
2533
2535
  const connection = continuation.connection;
2534
2536
  const messageStateKey = continuation.turnId ?? session.id;
@@ -9,10 +9,27 @@ export interface RunStuckState {
9
9
  heartbeatSinceMs: number | null;
10
10
  dispatchMode: string | null;
11
11
  hasInFlightWork: boolean | null;
12
+ /**
13
+ * The server reported no run in flight on this thread on two polls in a row.
14
+ * A chat still showing "running" at that point has lost the run's ending and
15
+ * should reload the thread, not keep waiting.
16
+ */
17
+ serverSettled: boolean;
18
+ /**
19
+ * The last polls failed, so nothing here says whether the run is alive.
20
+ * Distinct from "no run": the other fields are the last answer, not a fresh one.
21
+ */
22
+ statusUnreadable: boolean;
12
23
  }
13
24
  export interface UseRunStuckDetectionOptions {
14
25
  threadId: string | null | undefined;
15
26
  enabled?: boolean;
27
+ /**
28
+ * Whether the chat is showing a run in progress. Each time it turns on, the
29
+ * hook starts over, so `serverSettled` only ever reflects polls taken after
30
+ * the chat began waiting, never an idle answer from before the user sent.
31
+ */
32
+ awaitingResponse?: boolean;
16
33
  stuckThresholdMs?: number;
17
34
  /**
18
35
  * Threshold for BACKGROUND-dispatched runs (dispatchMode starts with
@@ -29,5 +46,5 @@ export interface UseRunStuckDetectionOptions {
29
46
  }
30
47
  export declare const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180000;
31
48
  export declare const DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS: number;
32
- export declare function useRunStuckDetection({ threadId, enabled, stuckThresholdMs, backgroundStuckThresholdMs, liveBackgroundStuckThresholdMs, pollIntervalMs, apiUrl, }: UseRunStuckDetectionOptions): RunStuckState;
49
+ export declare function useRunStuckDetection({ threadId, enabled, awaitingResponse, stuckThresholdMs, backgroundStuckThresholdMs, liveBackgroundStuckThresholdMs, pollIntervalMs, apiUrl, }: UseRunStuckDetectionOptions): RunStuckState;
33
50
  export declare function useAbortRun(apiUrl?: string): (runId: string, reason?: string) => Promise<string | null>;