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

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 (139) 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/automation/index.d.ts +1 -1
  27. package/dist/cli/skills.d.ts +1 -0
  28. package/dist/cli/skills.js +1 -1
  29. package/dist/client/agent-chat/index.d.ts +1 -1
  30. package/dist/client/agent-chat/index.js +1 -1
  31. package/dist/client/chat/agentkit-agent-native.js +83 -7
  32. package/dist/client/chat/agentkit-protocol.js +140 -67
  33. package/dist/client/chat/runtime.js +10 -4
  34. package/dist/client/embed-auth.d.ts +15 -0
  35. package/dist/client/embed-auth.js +77 -7
  36. package/dist/client/host/index.d.ts +1 -1
  37. package/dist/client/host/index.js +1 -1
  38. package/dist/client/mcp-app-host.d.ts +19 -0
  39. package/dist/client/mcp-app-host.js +88 -2
  40. package/dist/client/use-chat-threads.d.ts +1 -1
  41. package/dist/client/use-chat-threads.js +2 -0
  42. package/dist/client/use-run-stuck-detection.d.ts +18 -1
  43. package/dist/client/use-run-stuck-detection.js +115 -75
  44. package/dist/collab/routes.d.ts +1 -1
  45. package/dist/collab/struct-routes.d.ts +1 -1
  46. package/dist/db/request-telemetry.d.ts +7 -1
  47. package/dist/db/request-telemetry.js +20 -1
  48. package/dist/deploy/build.d.ts +25 -1
  49. package/dist/deploy/build.js +176 -3
  50. package/dist/deploy/workspace-deploy.js +8 -3
  51. package/dist/extensions/fetch-tool.js +50 -14
  52. package/dist/file-upload/builder.js +1 -1
  53. package/dist/file-upload/s3.js +1 -1
  54. package/dist/file-upload/types.d.ts +1 -0
  55. package/dist/integrations/adapters/email.js +11 -3
  56. package/dist/integrations/adapters/telegram.js +4 -2
  57. package/dist/jobs/actions/automation-no-op.d.ts +12 -0
  58. package/dist/jobs/actions/automation-no-op.js +20 -0
  59. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  60. package/dist/jobs/background-automation-runner.d.ts +8 -1
  61. package/dist/jobs/background-automation-runner.js +102 -19
  62. package/dist/jobs/run-history.d.ts +1 -1
  63. package/dist/jobs/run-history.js +8 -4
  64. package/dist/jobs/scheduler.js +25 -12
  65. package/dist/localization/automation-outcome-messages.d.ts +22 -0
  66. package/dist/localization/automation-outcome-messages.js +90 -0
  67. package/dist/localization/default-messages.d.ts +2 -0
  68. package/dist/localization/default-messages.js +4 -2
  69. package/dist/mcp/build-server.js +22 -11
  70. package/dist/mcp/embed-app.js +143 -9
  71. package/dist/mcp/index.d.ts +1 -1
  72. package/dist/mcp/index.js +1 -1
  73. package/dist/observability/metrics.d.ts +3 -0
  74. package/dist/observability/metrics.js +10 -0
  75. package/dist/observability/migrations.js +22 -0
  76. package/dist/observability/routes.d.ts +10 -5
  77. package/dist/observability/routes.js +88 -14
  78. package/dist/observability/sentiment.d.ts +5 -0
  79. package/dist/observability/sentiment.js +75 -25
  80. package/dist/observability/store.d.ts +28 -0
  81. package/dist/observability/store.js +58 -0
  82. package/dist/observability/traces.js +37 -5
  83. package/dist/org/handlers.d.ts +1 -1
  84. package/dist/progress/actions.js +1 -0
  85. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  86. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  87. package/dist/provider-api/actions/provider-api.js +6 -2
  88. package/dist/provider-api/index.d.ts +1 -0
  89. package/dist/provider-api/index.js +18 -0
  90. package/dist/provider-api/result-outcome.d.ts +1 -0
  91. package/dist/provider-api/result-outcome.js +20 -0
  92. package/dist/provider-api/staging.js +10 -9
  93. package/dist/resources/handlers.d.ts +1 -1
  94. package/dist/resources/script-helpers.d.ts +1 -0
  95. package/dist/resources/script-helpers.js +1 -1
  96. package/dist/resources/store.js +81 -0
  97. package/dist/scripts/resources/save-memory.js +3 -0
  98. package/dist/server/action-routes.js +34 -6
  99. package/dist/server/agent-chat/prompt-resources.d.ts +8 -2
  100. package/dist/server/agent-chat/prompt-resources.js +31 -8
  101. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +19 -4
  102. package/dist/server/agent-chat/recurring-jobs-runtime.js +52 -16
  103. package/dist/server/agent-chat/script-entries.js +30 -3
  104. package/dist/server/agent-chat/sweep-trigger-auth.d.ts +18 -0
  105. package/dist/server/agent-chat/sweep-trigger-auth.js +36 -0
  106. package/dist/server/agent-chat-plugin.js +51 -47
  107. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  108. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  109. package/dist/server/auth.js +21 -6
  110. package/dist/server/embed-route.js +5 -2
  111. package/dist/server/framework-request-handler.js +19 -0
  112. package/dist/server/http-response-telemetry.js +4 -1
  113. package/dist/server/realtime-token.d.ts +1 -1
  114. package/dist/server/request-context.d.ts +6 -0
  115. package/dist/shared/context-status.d.ts +21 -0
  116. package/dist/shared/context-status.js +54 -0
  117. package/dist/shared/mcp-app-display.d.ts +20 -0
  118. package/dist/shared/mcp-app-display.js +34 -0
  119. package/dist/shared/mcp-app-widget-embed.d.ts +18 -0
  120. package/dist/shared/mcp-app-widget-embed.js +41 -0
  121. package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +9 -2
  122. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +9 -2
  123. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  124. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
  125. package/dist/triggers/actions/manage-automation.d.ts +5 -5
  126. package/dist/triggers/dispatcher.js +10 -7
  127. package/docs/content/audit-log.mdx +3 -3
  128. package/docs/content/cloudflare.mdx +27 -0
  129. package/docs/content/deployment-environment-variables.mdx +2 -0
  130. package/docs/content/durable-resume.mdx +3 -1
  131. package/docs/content/environment-variables.mdx +2 -0
  132. package/docs/content/mcp-apps.mdx +1 -1
  133. package/docs/content/recurring-jobs.mdx +29 -9
  134. package/docs/content/vercel.mdx +30 -0
  135. package/package.json +4 -3
  136. package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +9 -2
  137. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +9 -2
  138. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  139. 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,12 +2449,17 @@ 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;
2455
2456
  if (continuationMessageState) {
2456
2457
  messageStates.set(turnId, continuationMessageState);
2457
2458
  }
2459
+ const turnEngine = turn.metadata?.engine;
2460
+ const engine = typeof turnEngine === "string" && turnEngine.trim()
2461
+ ? turnEngine
2462
+ : options.engine;
2458
2463
  const history = nativeHistoryFromMessages(turn.messages, prompt);
2459
2464
  const pendingApprovalHistory = approvedToolCalls && continuationMessageState
2460
2465
  ? pendingApprovalRuntimeMessages(continuationMessageState)
@@ -2481,6 +2486,7 @@ export function createAgentNativeChatRuntime(options = {}) {
2481
2486
  ? { internalContinuation: true }
2482
2487
  : {}),
2483
2488
  ...(autoContinueOfRunId ? { autoContinueOfRunId } : {}),
2489
+ ...(continueOfRunId ? { continueOfRunId } : {}),
2484
2490
  ...(turn.metadata?.agentNativeSkipPendingSelectionContext === true
2485
2491
  ? { skipPendingSelectionContext: true }
2486
2492
  : {}),
@@ -2489,7 +2495,7 @@ export function createAgentNativeChatRuntime(options = {}) {
2489
2495
  ...((turn.model ?? options.model)
2490
2496
  ? { model: turn.model ?? options.model }
2491
2497
  : {}),
2492
- ...(options.engine ? { engine: options.engine } : {}),
2498
+ ...(engine ? { engine } : {}),
2493
2499
  ...((turn.reasoningEffort ?? options.effort)
2494
2500
  ? { effort: turn.reasoningEffort ?? options.effort }
2495
2501
  : {}),
@@ -2527,8 +2533,8 @@ export function createAgentNativeChatRuntime(options = {}) {
2527
2533
  },
2528
2534
  continueTurn: ({ session, continuation, previousTurn, startTurn }) => {
2529
2535
  // 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 ?? {};
2536
+ // or connection answered later is not another continuation of that run.
2537
+ const { [AUTO_CONTINUE_OF_RUN_METADATA_KEY]: _autoContinueOf, [CONTINUE_OF_RUN_METADATA_KEY]: _continueOf, ...previousMetadata } = previousTurn?.metadata ?? {};
2532
2538
  const approval = continuation.approval;
2533
2539
  const connection = continuation.connection;
2534
2540
  const messageStateKey = continuation.turnId ?? session.id;
@@ -2,6 +2,21 @@ export declare function readEmbedMcpChatBridgeFlagFromUrl(): boolean;
2
2
  export declare function markEmbedMcpChatBridgeActive(): void;
3
3
  export declare function isEmbedMcpChatBridgeActive(): boolean;
4
4
  export declare function getEmbedAuthToken(): string | null;
5
+ /**
6
+ * True when the embed credential is a directory-widget read capability, which
7
+ * only the MCP App widget flows mint. It stays readable after a client
8
+ * navigation drops the URL params, so it identifies a widget document without
9
+ * the chat-bridge flag. The token's claims are only a UI hint (the signature
10
+ * is checked server-side), so this decides what not to attempt, never what is
11
+ * allowed.
12
+ */
13
+ export declare function hasMcpDirectoryWidgetCapabilityToken(): boolean;
14
+ /**
15
+ * True when this document is the app nested in an MCP App widget shell (the
16
+ * chat bridge is active) on a directory-widget read capability, which the
17
+ * server limits to the widget's own resource reads.
18
+ */
19
+ export declare function isMcpDirectoryWidgetReadOnlyEmbed(): boolean;
5
20
  export declare function isEmbedAuthActive(): boolean;
6
21
  export declare function _resetEmbedAuthForTests(): void;
7
22
  /**
@@ -1,13 +1,14 @@
1
1
  import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
2
+ import { isMcpDirectoryWidgetReadCapabilityScope } from "../shared/embed-auth.js";
2
3
  import { FRAMEWORK_INTERNAL_ROUTE_PREFIX } from "../shared/framework-route-prefix.js";
4
+ import { MCP_APP_HOST_FILL_ATTRIBUTE } from "../shared/mcp-app-display.js";
5
+ import { EMBED_TOKEN_STORAGE_KEY, MCP_CHAT_BRIDGE_STORAGE_KEY, } from "../shared/mcp-app-widget-embed.js";
3
6
  import { SIGN_IN_ENTRY_PATH, SIGN_IN_LEGACY_ENTRY_PATH, } from "../shared/sign-in-journey.js";
4
7
  import { frameworkRoutePrefix } from "./api-path.js";
5
8
  let installed = false;
6
9
  let memoryToken = null;
7
10
  let mcpChatBridgeActive = false;
8
11
  let mcpChatBridgeScope = null;
9
- const EMBED_TOKEN_STORAGE_KEY = "agent-native:embed-auth-token";
10
- const MCP_CHAT_BRIDGE_STORAGE_KEY = "agent-native:mcp-chat-bridge";
11
12
  const AUTH_FAILURE_COOLDOWN_MS = 60_000;
12
13
  const GUARDED_METHODS = new Set(["GET", "HEAD"]);
13
14
  const AUTH_FAILURE_HEADER = "x-agent-native-auth-circuit-breaker";
@@ -146,6 +147,48 @@ export function getEmbedAuthToken() {
146
147
  }
147
148
  return memoryToken ?? storedToken(win);
148
149
  }
150
+ function readEmbedTokenScope(token) {
151
+ const payload = token.split(".")[0] ?? "";
152
+ try {
153
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
154
+ const bytes = Uint8Array.from(atob(base64 + "=".repeat((4 - (base64.length % 4)) % 4)), (char) => char.charCodeAt(0));
155
+ const claims = JSON.parse(new TextDecoder().decode(bytes));
156
+ return typeof claims.scope === "string" ? claims.scope : undefined;
157
+ // coercion-ok: an unreadable token reads as a normal session; the server still enforces the real scope on every request.
158
+ }
159
+ catch {
160
+ return undefined;
161
+ }
162
+ }
163
+ let readOnlyScopeCache = null;
164
+ /**
165
+ * True when the embed credential is a directory-widget read capability, which
166
+ * only the MCP App widget flows mint. It stays readable after a client
167
+ * navigation drops the URL params, so it identifies a widget document without
168
+ * the chat-bridge flag. The token's claims are only a UI hint (the signature
169
+ * is checked server-side), so this decides what not to attempt, never what is
170
+ * allowed.
171
+ */
172
+ export function hasMcpDirectoryWidgetCapabilityToken() {
173
+ const token = getEmbedAuthToken();
174
+ if (!token)
175
+ return false;
176
+ if (readOnlyScopeCache?.token !== token) {
177
+ readOnlyScopeCache = {
178
+ token,
179
+ readOnly: isMcpDirectoryWidgetReadCapabilityScope(readEmbedTokenScope(token)),
180
+ };
181
+ }
182
+ return readOnlyScopeCache.readOnly;
183
+ }
184
+ /**
185
+ * True when this document is the app nested in an MCP App widget shell (the
186
+ * chat bridge is active) on a directory-widget read capability, which the
187
+ * server limits to the widget's own resource reads.
188
+ */
189
+ export function isMcpDirectoryWidgetReadOnlyEmbed() {
190
+ return hasMcpDirectoryWidgetCapabilityToken() && isEmbedMcpChatBridgeActive();
191
+ }
149
192
  export function isEmbedAuthActive() {
150
193
  const win = browserWindow();
151
194
  if (!win)
@@ -165,18 +208,22 @@ function ensureMcpChatBridgeViewportClamp(win) {
165
208
  const style = doc.createElement("style");
166
209
  style.id = MCP_CHAT_BRIDGE_VIEWPORT_STYLE_ID;
167
210
  const height = `${MCP_CHAT_BRIDGE_VIEWPORT_HEIGHT}px`;
211
+ // An inline card sizes itself to this document, so `100vh` must not follow
212
+ // the frame. A host that owns the frame's height (see
213
+ // mcpAppHostFillsContainer) sets the fill attribute and the clamp lifts.
214
+ const inline = `html:not([${MCP_APP_HOST_FILL_ATTRIBUTE}])`;
168
215
  style.textContent = `
169
- html,
170
- body {
216
+ ${inline},
217
+ ${inline} body {
171
218
  min-height: 0 !important;
172
219
  height: ${height} !important;
173
220
  max-height: ${height} !important;
174
221
  overflow: hidden !important;
175
222
  }
176
223
 
177
- #root,
178
- #__next,
179
- [data-agent-native-app-root] {
224
+ ${inline} #root,
225
+ ${inline} #__next,
226
+ ${inline} [data-agent-native-app-root] {
180
227
  min-height: 0 !important;
181
228
  height: ${height} !important;
182
229
  max-height: ${height} !important;
@@ -256,6 +303,7 @@ export function _resetEmbedAuthForTests() {
256
303
  }
257
304
  installed = false;
258
305
  memoryToken = null;
306
+ readOnlyScopeCache = null;
259
307
  mcpChatBridgeActive = false;
260
308
  mcpChatBridgeScope = null;
261
309
  authFailureCache.clear();
@@ -337,6 +385,22 @@ function isAgentNativeRuntimePath(pathname) {
337
385
  pathname.endsWith(prefix) ||
338
386
  pathname.includes(`${prefix}/`));
339
387
  }
388
+ // What a read-only widget session is refused whatever it asks for: the agent
389
+ // state it can never write.
390
+ function isReadOnlyWidgetRefusedPath(pathname) {
391
+ return [FRAMEWORK_INTERNAL_ROUTE_PREFIX, frameworkRoutePrefix()].some((prefix) => pathname.endsWith(`${prefix}/application-state`) ||
392
+ pathname.includes(`${prefix}/application-state/`));
393
+ }
394
+ // The refusal the server's auth guard already sends such a session, answered
395
+ // locally so each caller takes the error path it takes today without the
396
+ // request or its console error.
397
+ function readOnlyWidgetRefusal() {
398
+ return new Response(JSON.stringify({ error: "Unauthorized" }), {
399
+ status: 401,
400
+ statusText: "Unauthorized",
401
+ headers: { "Content-Type": "application/json" },
402
+ });
403
+ }
340
404
  function requestMethod(input, init) {
341
405
  return (init?.method ??
342
406
  (input instanceof Request ? input.method : undefined) ??
@@ -480,6 +544,12 @@ export function ensureEmbedAuthFetchInterceptor() {
480
544
  return;
481
545
  const originalFetch = win.fetch.bind(win);
482
546
  const patchedFetch = (async (input, init) => {
547
+ if (isMcpDirectoryWidgetReadOnlyEmbed() && sameOrigin(input, win)) {
548
+ const url = inputUrl(input, win);
549
+ if (url && isReadOnlyWidgetRefusedPath(url.pathname)) {
550
+ return readOnlyWidgetRefusal();
551
+ }
552
+ }
483
553
  const request = requestUrlAndKey(input, init, win);
484
554
  if (request?.shouldGuard) {
485
555
  const cached = getCachedAuthFailure(request.key);
@@ -1,6 +1,6 @@
1
1
  export { initializeAgentNativeClient } from "../client-bootstrap.js";
2
2
  export { agentNativeApiDisabledReason, AgentNativeApiDisabledError, setAgentNativeApiDisabled, } from "../api-surface.js";
3
- export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, } from "../embed-auth.js";
3
+ export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, } from "../embed-auth.js";
4
4
  export { sendToFrame, onFrameMessage, requestUserInfo, getFrameOrigin, getFramePostMessageTargetOrigin, getCallbackOrigin, oauthRedirectUri, isInFrame, isTrustedFrameMessage, enterStyleEditing, enterTextEditing, exitSelectionMode, type UserInfo, } from "../frame.js";
5
5
  export { getBuilderParentOrigin, isInBuilderFrame, sendToBuilderChat, shouldParentFrameOwnAgentPanel, isTrustedBuilderMessage, tryDelegateBuildRequestToBuilder, type BuilderChatMessage, } from "../builder-frame.js";
6
6
  export { getClientSurface, type ClientSurface } from "../client-surface.js";
@@ -1,6 +1,6 @@
1
1
  export { initializeAgentNativeClient } from "../client-bootstrap.js";
2
2
  export { agentNativeApiDisabledReason, AgentNativeApiDisabledError, setAgentNativeApiDisabled, } from "../api-surface.js";
3
- export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, } from "../embed-auth.js";
3
+ export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, } from "../embed-auth.js";
4
4
  export { sendToFrame, onFrameMessage, requestUserInfo, getFrameOrigin, getFramePostMessageTargetOrigin, getCallbackOrigin, oauthRedirectUri, isInFrame, isTrustedFrameMessage, enterStyleEditing, enterTextEditing, exitSelectionMode, } from "../frame.js";
5
5
  export { getBuilderParentOrigin, isInBuilderFrame, sendToBuilderChat, shouldParentFrameOwnAgentPanel, isTrustedBuilderMessage, tryDelegateBuildRequestToBuilder, } from "../builder-frame.js";
6
6
  export { getClientSurface } from "../client-surface.js";
@@ -45,6 +45,25 @@ export interface McpAppHostContextSnapshot {
45
45
  version: unknown;
46
46
  hostInfo?: McpAppHostInfo;
47
47
  }
48
+ /**
49
+ * True when this document is an app running inside an MCP App widget (a
50
+ * ChatGPT, Codex, or Claude card or panel). The host, not the app, owns
51
+ * navigation and chat there, so apps drop their own navigation chrome.
52
+ *
53
+ * Being a widget is a property of the document, not of its current URL: the
54
+ * first positive answer is kept (and marked on `<html>`, where the first-paint
55
+ * script in `getMcpAppWidgetEmbedBootScriptBody` also marks it), so a client
56
+ * navigation that drops the embed query params, or a token swap that
57
+ * de-enrolls the chat bridge, never brings the app's own chrome back.
58
+ */
59
+ export declare function isMcpAppWidgetEmbed(): boolean;
60
+ export declare function useIsMcpAppWidgetEmbed(): boolean;
61
+ /**
62
+ * True for a directory widget, whose scoped session can only read its own
63
+ * resource. Apps skip application-state writes and save-failure surfaces there
64
+ * instead of showing errors for requests that can never land.
65
+ */
66
+ export declare function useIsMcpDirectoryWidgetReadOnlyEmbed(): boolean;
48
67
  export declare function isOpenAiMcpAppHost(): boolean;
49
68
  export declare function initializeMcpAppHost(): Promise<boolean>;
50
69
  export declare function sendMcpAppHostMessage(chat: McpAppHostChatMessage): Promise<boolean | null> | false;