@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
@@ -28,25 +28,29 @@ import { parseServiceIdentityEmail } from "../org/service-identity.js";
28
28
  import { ServicePrincipalRefusedError } from "../org/service-principal-guard.js";
29
29
  import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
30
30
  import { readOptionalKeyCache, writeOptionalKeyCache, } from "../secrets/optional-key-cache.js";
31
- import { COMPACT_PROMPT_RESOURCES_TOTAL_MAX_CHARS, preloadJevContextForPrompt, } from "../server/agent-chat/prompt-resources.js";
31
+ import { COMPACT_PROMPT_RESOURCES_TOTAL_MAX_CHARS, preloadJevContextWithStatus, } from "../server/agent-chat/prompt-resources.js";
32
32
  import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
33
+ import { captureError } from "../server/capture-error.js";
33
34
  import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, isBuilderGatewayDeployConfigured, readDeployCredentialEnv, resolveBuilderGatewayAuth, } from "../server/credential-provider.js";
34
35
  import { orderCredentialScopes } from "../server/credential-read-order.js";
35
36
  import { readBody } from "../server/h3-helpers.js";
36
37
  import { resolveHostedHarnessPolicy } from "../server/hosted-harness-policy.js";
37
38
  import { isPersonalProviderKeyUseRestricted, isPersonalProviderPolicyKey, PERSONAL_PROVIDER_KEYS_RESTRICTED_ERROR_CODE, PERSONAL_PROVIDER_KEYS_RESTRICTED_MESSAGE, } from "../server/personal-provider-key-policy.js";
38
- import { assertRequestActionSurfaceIsolation, getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
39
+ import { assertRequestActionSurfaceIsolation, getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestAuthCapability, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
39
40
  import { secretKeyNames } from "../server/secret-key-aliases.js";
40
41
  import { fireInternalDispatch } from "../server/self-dispatch.js";
41
42
  import { retryContextFromRequest, } from "../shared/agent-chat-run-not-started.js";
42
43
  import { ANALYTICS_CLIENT_PLATFORM_BODY_FIELD } from "../shared/analytics-platform.js";
44
+ import { isContextDegraded, preloadedContextUnavailableNote, readReportedContextStatus, screenContextUnavailableNote, SELECTION_CONTEXT_UNAVAILABLE_NOTE, worstContextStatus, } from "../shared/context-status.js";
43
45
  import { parseBase64DataUrl } from "../shared/data-url.js";
44
46
  import { stripDiagnosticSnippets } from "../shared/diagnostic-snippet.js";
45
47
  import { isReasoningEffort, normalizeReasoningEffortForRequest, stepDownReasoningEffort, } from "../shared/reasoning-effort.js";
46
48
  import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
47
49
  import { actionPreparationContinuationNote } from "./action-continuation-guidance.js";
48
50
  import { drainAgentWarnings, formatAgentWarningsForToolResult, } from "./action-warnings.js";
51
+ import { CONTINUE_UNAVAILABLE_CODE, } from "./auto-continue.js";
49
52
  import { clipHead } from "./clip-text.js";
53
+ import { connectionRequiredMessage, priorConnectionContextNote, resolvePriorConnectionNote, } from "./connection-required-note.js";
50
54
  import { buildSystemManifestSections, readContextXraySystemSections, } from "./context-xray/manifest.js";
51
55
  import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
52
56
  import { applyContextXrayTransformForIteration } from "./engine/context-directives-transform.js";
@@ -226,6 +230,10 @@ export function normalizeChatScope(value) {
226
230
  function appStateKeyForBrowserTab(key, browserTabId) {
227
231
  return browserTabId ? `${key}:${browserTabId}` : key;
228
232
  }
233
+ /** `readAppState` throws when the request has no identity: that is no user to read for, not a failed read. */
234
+ function hasAppStateIdentity() {
235
+ return Boolean(getRequestUserEmail() || getRequestAuthCapability());
236
+ }
229
237
  async function readAppStateForBrowserTab(key, browserTabId) {
230
238
  if (!browserTabId)
231
239
  return (await readAppState(key));
@@ -5108,6 +5116,7 @@ export async function runAgentLoop(opts) {
5108
5116
  let result;
5109
5117
  let chatUIResult;
5110
5118
  let isError = false;
5119
+ let toolErrorCode;
5111
5120
  let mcpApp;
5112
5121
  let toolResultImages;
5113
5122
  let toolArtifacts = [];
@@ -5334,12 +5343,15 @@ export async function runAgentLoop(opts) {
5334
5343
  }
5335
5344
  }
5336
5345
  catch (err) {
5346
+ toolErrorCode = isActionContractError(err)
5347
+ ? err.errorCode
5348
+ : undefined;
5337
5349
  if (signal.aborted) {
5338
5350
  result = INTERRUPTED_TOOL_RESULT_MARKER;
5339
5351
  }
5340
5352
  else if (isAgentConnectionRequiredError(err)) {
5341
- const message = sanitizeToolErrorValue(err.message) ||
5342
- `Connect ${err.provider} to continue.`;
5353
+ const message = connectionRequiredMessage(sanitizeToolErrorValue(err.message) ||
5354
+ `Connect ${err.provider} to continue.`, err.reason);
5343
5355
  result = sanitizeToolErrorValue(err.toolResult || message);
5344
5356
  requestedConnection ??= {
5345
5357
  requestId: randomUUID(),
@@ -5443,6 +5455,7 @@ export async function runAgentLoop(opts) {
5443
5455
  input: toolCall.input,
5444
5456
  result,
5445
5457
  ...(isError ? { isError: true } : {}),
5458
+ ...(toolErrorCode ? { errorCode: toolErrorCode } : {}),
5446
5459
  ...(isError
5447
5460
  ? { completedSideEffect: false }
5448
5461
  : receipt
@@ -6676,12 +6689,28 @@ export function createProductionAgentHandler(options) {
6676
6689
  return { error: "Invalid request body" };
6677
6690
  }
6678
6691
  }
6679
- const { message, history = [], structuredHistory, references = [], threadId, attachments, displayMessage, parentId, queuedMessageId, queuedMessageClaimId, agentKitMessageId: requestedAgentKitMessageId, internalContinuation, autoContinueOfRunId: requestedAutoContinueOfRunId, turnId: requestTurnId, model: requestModel, engine: requestEngine, effort: requestEffort, browserTabId, scope, harness: requestHarness, trackInRunsTray, skipPendingSelectionContext, } = body;
6680
- const autoContinueOfRunId = internalContinuation === true &&
6681
- typeof requestedAutoContinueOfRunId === "string" &&
6682
- requestedAutoContinueOfRunId.trim().length <= 200
6683
- ? requestedAutoContinueOfRunId.trim() || undefined
6692
+ const { message, history = [], structuredHistory, references = [], threadId, attachments, displayMessage, parentId, queuedMessageId, queuedMessageClaimId, agentKitMessageId: requestedAgentKitMessageId, internalContinuation, autoContinueOfRunId: requestedAutoContinueOfRunId, continueOfRunId: requestedContinueOfRunId, turnId: requestTurnId, model: requestModel, engine: requestEngine, effort: requestEffort, browserTabId, scope, harness: requestHarness, trackInRunsTray, skipPendingSelectionContext, } = body;
6693
+ const continuedRunId = (requested) => internalContinuation === true &&
6694
+ typeof requested === "string" &&
6695
+ requested.trim().length <= 200
6696
+ ? requested.trim() || undefined
6684
6697
  : undefined;
6698
+ const autoContinueOfRunId = continuedRunId(requestedAutoContinueOfRunId);
6699
+ // The stopped run this request resumes in its own turn, from the turn's
6700
+ // history and journal, whether the chat or a person asked.
6701
+ const continueOf = autoContinueOfRunId
6702
+ ? { runId: autoContinueOfRunId, trigger: "auto" }
6703
+ : (() => {
6704
+ const runId = continuedRunId(requestedContinueOfRunId);
6705
+ return runId ? { runId, trigger: "manual" } : undefined;
6706
+ })();
6707
+ const continueRefusal = (code) => ({
6708
+ error: continueOf?.trigger === "manual"
6709
+ ? "This turn can no longer be continued."
6710
+ : "This turn will not continue automatically.",
6711
+ code,
6712
+ retryable: false,
6713
+ });
6685
6714
  if (requestEngine !== undefined && typeof requestEngine !== "string") {
6686
6715
  setResponseStatus(event, 400);
6687
6716
  return { error: "engine must be a string" };
@@ -6834,6 +6863,7 @@ export function createProductionAgentHandler(options) {
6834
6863
  });
6835
6864
  let jevPromptCandidates = [];
6836
6865
  let jevFallbackCandidateIds = [];
6866
+ const preparedReferenceStatus = readReportedContextStatus(preparedRequest);
6837
6867
  if (preparedRequest) {
6838
6868
  if (typeof preparedRequest.message === "string" &&
6839
6869
  preparedRequest.message.trim().length > 0) {
@@ -7089,7 +7119,7 @@ export function createProductionAgentHandler(options) {
7089
7119
  configuredEffort: options.reasoningEffort,
7090
7120
  });
7091
7121
  options.onEngineResolved?.(engine, effectiveModel);
7092
- console.log(`[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestModel=${requestModel ?? "(none)"} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource} turnId=${requestTurnId ?? "(none)"}`);
7122
+ console.log(`[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestModel=${requestModel ?? "(none)"} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource} engineDefault=${engine.defaultModel} effort=${reasoningEffort ?? "unset"} turnId=${requestTurnId ?? "(none)"}`);
7093
7123
  if (!(await isResolvedEngineUsableForRequest(engine, {
7094
7124
  apiKey: effectiveApiKey,
7095
7125
  credentialIdentity,
@@ -7180,6 +7210,17 @@ export function createProductionAgentHandler(options) {
7180
7210
  return buildCurrentTimeUserContext();
7181
7211
  }
7182
7212
  })();
7213
+ // `empty` (no navigation yet) stays silent; a timeout or a throw adds a
7214
+ // note, because the model would otherwise read it as an app with no page.
7215
+ const screenStatuses = { screen: "empty", url: "empty", selection: "empty" };
7216
+ // Once a cap fires the model has the fallback note, so a read that
7217
+ // resolves late must not change what the trace says it saw.
7218
+ const noteScreenStatus = (source, status) => {
7219
+ if (screenStatuses[source] !== "timed_out") {
7220
+ screenStatuses[source] = status;
7221
+ }
7222
+ };
7223
+ const screenUnavailableNote = screenContextUnavailableNote(Boolean(surfacedRequestActions["view-screen"]));
7183
7224
  const screenContextThunk = () => (async () => {
7184
7225
  const screenStart = Date.now();
7185
7226
  try {
@@ -7194,18 +7235,24 @@ export function createProductionAgentHandler(options) {
7194
7235
  const screenText = typeof result === "string"
7195
7236
  ? result
7196
7237
  : JSON.stringify(result, null, 2);
7238
+ noteScreenStatus("screen", "ok");
7197
7239
  return `\n\n<current-screen>\n${capScreenContext(screenText)}\n</current-screen>`;
7198
7240
  }
7199
7241
  }
7200
7242
  else {
7201
7243
  const navigation = await readAppStateForBrowserTab("navigation", requestBrowserTabId);
7202
7244
  if (navigation) {
7245
+ noteScreenStatus("screen", "ok");
7203
7246
  return `\n\n<current-screen>\n${capScreenContext(JSON.stringify(navigation, null, 2))}\n</current-screen>`;
7204
7247
  }
7205
7248
  }
7206
7249
  }
7207
- catch {
7208
- // DB not ready or no navigation state — skip silently
7250
+ catch (error) {
7251
+ if (!hasAppStateIdentity())
7252
+ return "";
7253
+ noteScreenStatus("screen", "failed");
7254
+ console.warn("[agent-chat] current-screen context unavailable:", error instanceof Error ? error.message : String(error));
7255
+ return screenUnavailableNote;
7209
7256
  }
7210
7257
  finally {
7211
7258
  setupMarks.screenMs = Date.now() - screenStart;
@@ -7241,11 +7288,16 @@ export function createProductionAgentHandler(options) {
7241
7288
  if (settingsPage)
7242
7289
  lines.push(settingsPage);
7243
7290
  }
7291
+ noteScreenStatus("url", "ok");
7244
7292
  return `\n\n<current-url>\n${lines.join("\n")}\n</current-url>`;
7245
7293
  }
7246
7294
  }
7247
- catch {
7248
- // DB not ready — skip silently
7295
+ catch (error) {
7296
+ if (!hasAppStateIdentity())
7297
+ return "";
7298
+ noteScreenStatus("url", "failed");
7299
+ console.warn("[agent-chat] current-url context unavailable:", error instanceof Error ? error.message : String(error));
7300
+ return screenUnavailableNote;
7249
7301
  }
7250
7302
  return "";
7251
7303
  })();
@@ -7260,12 +7312,17 @@ export function createProductionAgentHandler(options) {
7260
7312
  const capturedAt = typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
7261
7313
  if (Date.now() - capturedAt > SELECTION_TTL_MS)
7262
7314
  return "";
7315
+ noteScreenStatus("selection", "ok");
7263
7316
  return (`\n\nThe user has selected the following text and pressed Cmd I to focus the agent. ` +
7264
7317
  `Treat this as the immediate context to act on:\n` +
7265
7318
  `<selection>\n${capSelectionContext(sel.text)}\n</selection>`);
7266
7319
  }
7267
- catch {
7268
- // DB not ready — skip silently
7320
+ catch (error) {
7321
+ if (!hasAppStateIdentity())
7322
+ return "";
7323
+ noteScreenStatus("selection", "failed");
7324
+ console.warn("[agent-chat] selection context unavailable:", error instanceof Error ? error.message : String(error));
7325
+ return SELECTION_CONTEXT_UNAVAILABLE_NOTE;
7269
7326
  }
7270
7327
  return "";
7271
7328
  })();
@@ -7376,7 +7433,7 @@ export function createProductionAgentHandler(options) {
7376
7433
  source: "default",
7377
7434
  };
7378
7435
  const systemPromptTimeoutError = new Error("system prompt preparation timed out before the agent could start");
7379
- let [systemPrompt, timeBlock, screenBlock, urlBlock, selectionBlock, filesContext, loopSettings, enrichedMessage, jevContextCredentials,] = await Promise.all([
7436
+ let [systemPrompt, timeBlock, screenBlock, urlBlock, selectionBlock, filesContext, loopSettings, enrichedMessage, jevContextCredentials, priorConnection,] = await Promise.all([
7380
7437
  presendCap("systemPrompt", systemPromptThunk, "", 13000, () => {
7381
7438
  // An empty configured prompt is valid, but an empty timeout fallback
7382
7439
  // is not: required app instructions must either finish or fail before
@@ -7385,13 +7442,30 @@ export function createProductionAgentHandler(options) {
7385
7442
  systemPromptError ??= systemPromptTimeoutError;
7386
7443
  }),
7387
7444
  presendCap("time", timeContextThunk, "", 9000),
7388
- presendCap("screen", screenContextThunk, "", 9000),
7389
- presendCap("url", urlContextThunk, "", 9000),
7390
- presendCap("selection", selectionContextThunk, "", 9000),
7445
+ presendCap("screen", screenContextThunk, screenUnavailableNote, 9000, () => {
7446
+ screenStatuses.screen = "timed_out";
7447
+ }),
7448
+ presendCap("url", urlContextThunk, screenUnavailableNote, 9000, () => {
7449
+ screenStatuses.url = "timed_out";
7450
+ }),
7451
+ presendCap("selection", selectionContextThunk, SELECTION_CONTEXT_UNAVAILABLE_NOTE, 9000, () => {
7452
+ screenStatuses.selection = "timed_out";
7453
+ }),
7391
7454
  presendCap("files", filesContextThunk, "", 12000),
7392
7455
  presendCap("loopSettings", loopSettingsThunk, fallbackLoopSettings, 9000),
7393
7456
  presendCap("enrichedMessage", enrichedMessageThunk, requestMessage, 9000),
7394
7457
  presendCap("jevContextCredentials", () => getJevContextCredentials(ownerEmail ?? getRequestUserEmail()), { apiKey: undefined, personalApiKey: undefined, builderAuth: null }, 9000),
7458
+ presendCap("priorConnection",
7459
+ // The foreground of a dispatched run never prompts the model; the
7460
+ // worker re-reads, so reading here would pay for it twice.
7461
+ () => threadId && !dispatchToBackground
7462
+ ? resolvePriorConnectionNote({
7463
+ threadId,
7464
+ orgId: getRequestOrgId() ?? null,
7465
+ appId: options.appId,
7466
+ excludeRunId: backgroundRunMarker?.runId,
7467
+ })
7468
+ : Promise.resolve({ status: "none" }), { status: "unreadable", error: "timed out" }, 9000),
7395
7469
  ]);
7396
7470
  setupMark("ctxAll");
7397
7471
  workerStep("context_all");
@@ -7409,7 +7483,21 @@ export function createProductionAgentHandler(options) {
7409
7483
  },
7410
7484
  });
7411
7485
  }
7486
+ // Screen and URL share one note; the same failure needn't be told twice.
7487
+ if (urlBlock === screenBlock)
7488
+ urlBlock = "";
7412
7489
  const screenContext = timeBlock + screenBlock + urlBlock + selectionBlock;
7490
+ // Referenced agents don't share this run's tools, so a note telling the
7491
+ // model to call view-screen would send them looking for a tool they lack.
7492
+ const withoutUnavailableNote = (block) => block === screenUnavailableNote ||
7493
+ block === SELECTION_CONTEXT_UNAVAILABLE_NOTE
7494
+ ? ""
7495
+ : block;
7496
+ const referencedAgentContext = timeBlock +
7497
+ withoutUnavailableNote(screenBlock) +
7498
+ withoutUnavailableNote(urlBlock) +
7499
+ withoutUnavailableNote(selectionBlock);
7500
+ const screenStatus = worstContextStatus(...Object.values(screenStatuses));
7413
7501
  const surfacedActionRegistry = requestMode === "plan"
7414
7502
  ? createPlanModeActionRegistry(surfacedRequestActions)
7415
7503
  : surfacedRequestActions;
@@ -7434,7 +7522,7 @@ export function createProductionAgentHandler(options) {
7434
7522
  const jevContextMaxChars = options.jevContextCompact
7435
7523
  ? Math.max(0, COMPACT_PROMPT_RESOURCES_TOTAL_MAX_CHARS - systemPrompt.length - 2)
7436
7524
  : undefined;
7437
- const [requestTools, jevContext] = await Promise.all([
7525
+ const [requestTools, jevPreload] = await Promise.all([
7438
7526
  preloadJevTools({
7439
7527
  request: jevRequestContext,
7440
7528
  skip: Boolean(internalContinuation || dispatchToBackground),
@@ -7447,7 +7535,7 @@ export function createProductionAgentHandler(options) {
7447
7535
  availableTools: availableRequestTools,
7448
7536
  readOnlyOnly: requestMode === "plan",
7449
7537
  }),
7450
- preloadJevContextForPrompt({
7538
+ preloadJevContextWithStatus({
7451
7539
  request: jevRequestContext,
7452
7540
  appId: options.appId,
7453
7541
  owner: ownerEmail ?? undefined,
@@ -7464,8 +7552,23 @@ export function createProductionAgentHandler(options) {
7464
7552
  fallbackCandidateIds: jevFallbackCandidateIds,
7465
7553
  }),
7466
7554
  ]);
7555
+ const jevContext = jevPreload.context;
7467
7556
  if (jevContext)
7468
7557
  systemPrompt = `${systemPrompt}\n\n${jevContext}`;
7558
+ const prefetchStatus = worstContextStatus(jevPreload.status, preparedReferenceStatus);
7559
+ if (runContext) {
7560
+ runContext.contextStatus = {
7561
+ prefetch: prefetchStatus,
7562
+ screen: screenStatus,
7563
+ };
7564
+ }
7565
+ // Per-turn, so the stable system prompt prefix keeps caching.
7566
+ const prefetchNote = prefetchStatus && isContextDegraded(prefetchStatus)
7567
+ ? preloadedContextUnavailableNote(prefetchStatus, {
7568
+ // The other source delivered, so only part of the context is missing.
7569
+ partial: jevPreload.status === "ok" || preparedReferenceStatus === "ok",
7570
+ })
7571
+ : "";
7469
7572
  const contextXraySystemSections = [
7470
7573
  ...readContextXraySystemSections(event),
7471
7574
  ...(jevContext
@@ -7508,7 +7611,12 @@ export function createProductionAgentHandler(options) {
7508
7611
  ? "\n\n<plan-mode-note>Connected external agent mentions were not called because Plan mode is read-only. Mention that they can be called after the user switches to Act mode if the plan needs them.</plan-mode-note>"
7509
7612
  : "";
7510
7613
  const userContent = buildUserContentWithAttachments({
7511
- text: enrichedMessage + screenContext + filesContext + planModeAgentNote,
7614
+ text: enrichedMessage +
7615
+ screenContext +
7616
+ prefetchNote +
7617
+ priorConnectionContextNote(priorConnection) +
7618
+ filesContext +
7619
+ planModeAgentNote,
7512
7620
  attachments: requestAttachments,
7513
7621
  });
7514
7622
  const historyMessages = structuredHistoryToEngineMessages(structuredHistory) ??
@@ -7603,9 +7711,7 @@ export function createProductionAgentHandler(options) {
7603
7711
  ...(dispatchToBackground
7604
7712
  ? { dispatchPayload: JSON.stringify(body) }
7605
7713
  : {}),
7606
- ...(autoContinueOfRunId
7607
- ? { autoContinueOf: autoContinueOfRunId }
7608
- : {}),
7714
+ ...(continueOf ? { continueOf } : {}),
7609
7715
  });
7610
7716
  }
7611
7717
  catch (error) {
@@ -7621,14 +7727,10 @@ export function createProductionAgentHandler(options) {
7621
7727
  await setupResumeClaim?.release();
7622
7728
  return { ok: true, stopped: true };
7623
7729
  }
7624
- if (slot.autoContinueRefused) {
7730
+ if (slot.continueRefused) {
7625
7731
  await setupResumeClaim?.release();
7626
7732
  setResponseStatus(event, 409);
7627
- return {
7628
- error: "This turn will not continue automatically.",
7629
- code: slot.autoContinueRefused,
7630
- retryable: false,
7631
- };
7733
+ return continueRefusal(slot.continueRefused);
7632
7734
  }
7633
7735
  if (slot.completedRunId) {
7634
7736
  const stream = await replayCompletedTurn(threadId, effectiveTurnId);
@@ -7696,21 +7798,31 @@ export function createProductionAgentHandler(options) {
7696
7798
  let turnInputTokens = Number.isFinite(priorTurnInputTokensFromBody)
7697
7799
  ? Math.max(0, priorTurnInputTokensFromBody)
7698
7800
  : 0;
7699
- // A server successor and an automatic continuation resume the same turn
7700
- // the same way: the thread's tool calls and results, plus the turn's
7801
+ // A server successor and a continuation, automatic or chosen, resume the
7802
+ // same turn the same way: the thread's tool calls and results, plus the turn's
7701
7803
  // journal of finished steps, so nothing already done is sent again.
7702
- if ((isChainedBackgroundContinuation || autoContinueOfRunId) &&
7703
- effectiveThreadId) {
7804
+ if ((isChainedBackgroundContinuation || continueOf) && effectiveThreadId) {
7704
7805
  try {
7705
7806
  const { getThread } = await import("../chat-threads/store.js");
7706
- const { resumeThreadHistoryForRequest } = await import("./thread-data-builder.js");
7707
- const { messages: resumed, foundTurnPrompt } = resumeThreadHistoryForRequest((await getThread(effectiveThreadId))?.threadData);
7807
+ const { latestPromptTurnId, resumeThreadHistoryForRequest } = await import("./thread-data-builder.js");
7808
+ const threadData = (await getThread(effectiveThreadId))?.threadData;
7809
+ const { messages: resumed, foundTurnPrompt } = resumeThreadHistoryForRequest(threadData);
7708
7810
  // A continuation always follows a stopped run, so a thread without
7709
7811
  // that turn's prompt means its history was lost, not that there was
7710
7812
  // none. A successor stays best-effort and resumes from what is there.
7711
- if (autoContinueOfRunId && !foundTurnPrompt) {
7813
+ if (continueOf && !foundTurnPrompt) {
7712
7814
  throw new Error(`thread ${effectiveThreadId} has no turn prompt`);
7713
7815
  }
7816
+ // Resuming takes the thread's newest prompt, so a newer one, even one
7817
+ // whose run never started, would run under this turn's journal.
7818
+ const promptTurnId = continueOf && threadData ? latestPromptTurnId(threadData) : undefined;
7819
+ if (promptTurnId && promptTurnId !== effectiveTurnId) {
7820
+ if (await updateRunStatusIfRunning(runId, "errored")) {
7821
+ await setRunTerminalReason(runId, CONTINUE_UNAVAILABLE_CODE);
7822
+ }
7823
+ setResponseStatus(event, 409);
7824
+ return continueRefusal(CONTINUE_UNAVAILABLE_CODE);
7825
+ }
7714
7826
  if (resumed.length > 0) {
7715
7827
  const actionPreparationTool = typeof backgroundRunMarker?.actionPreparationTool === "string" &&
7716
7828
  backgroundRunMarker.actionPreparationTool.trim()
@@ -7720,7 +7832,7 @@ export function createProductionAgentHandler(options) {
7720
7832
  ? backgroundRunMarker.continuationReason
7721
7833
  : "run_timeout";
7722
7834
  const journalRead = await loadPriorTurnToolCallJournal(effectiveThreadId, effectiveTurnId);
7723
- if (autoContinueOfRunId && journalRead.status === "unreadable") {
7835
+ if (continueOf && journalRead.status === "unreadable") {
7724
7836
  throw new Error(journalRead.error);
7725
7837
  }
7726
7838
  const journalNote = journalRead.status === "read" && journalRead.toolCallJournal
@@ -7735,7 +7847,7 @@ export function createProductionAgentHandler(options) {
7735
7847
  }
7736
7848
  }
7737
7849
  catch (error) {
7738
- if (autoContinueOfRunId) {
7850
+ if (continueOf) {
7739
7851
  // The browser's history lacks the stopped run's tool results, so
7740
7852
  // continuing from it could repeat a step that already finished.
7741
7853
  console.warn(`[agent-chat] auto-continue history unreadable for thread ${effectiveThreadId}:`, error);
@@ -7767,6 +7879,22 @@ export function createProductionAgentHandler(options) {
7767
7879
  console.warn(`[agent-chat] history recovery failed for thread ${threadId}; continuing with no prior context:`, err);
7768
7880
  }
7769
7881
  }
7882
+ if (!internalContinuation && !isChainedBackgroundContinuation && threadId) {
7883
+ try {
7884
+ const { loadStoppedPreviousTurnNote } = await import("./previous-turn-note.js");
7885
+ const note = await loadStoppedPreviousTurnNote(threadId, effectiveTurnId);
7886
+ const prompt = messages.at(-1);
7887
+ if (note && prompt?.role === "user") {
7888
+ prompt.content = [{ type: "text", text: note }, ...prompt.content];
7889
+ }
7890
+ }
7891
+ catch (err) {
7892
+ captureError(err, {
7893
+ tags: { source: "agent-chat", failureClass: "previous-turn-note" },
7894
+ extra: { threadId, turnId: effectiveTurnId },
7895
+ });
7896
+ }
7897
+ }
7770
7898
  setupMark("depsThread");
7771
7899
  workerStep("owner_thread");
7772
7900
  if (options.onRunPrepared && !internalContinuation && !isBackgroundWorker) {
@@ -8166,7 +8294,10 @@ export function createProductionAgentHandler(options) {
8166
8294
  {
8167
8295
  role: "user",
8168
8296
  content: [
8169
- { type: "text", text: enrichedMessage + screenContext },
8297
+ {
8298
+ type: "text",
8299
+ text: enrichedMessage + referencedAgentContext,
8300
+ },
8170
8301
  ],
8171
8302
  },
8172
8303
  ],
@@ -8259,7 +8390,7 @@ export function createProductionAgentHandler(options) {
8259
8390
  const responseText = await callConnectedAgentReference({
8260
8391
  agent: ref.name,
8261
8392
  path: ref.path,
8262
- message: enrichedMessage + screenContext,
8393
+ message: enrichedMessage + referencedAgentContext,
8263
8394
  send,
8264
8395
  callAgent,
8265
8396
  resolveCallerAuth: resolveA2ACallerAuth,
@@ -8416,6 +8547,17 @@ export function createProductionAgentHandler(options) {
8416
8547
  : undefined,
8417
8548
  metadata: {
8418
8549
  modelSelectionSource,
8550
+ // What the request asked for; the loop may lower it on retry.
8551
+ reasoningEffortRequested: reasoningEffort ?? null,
8552
+ ...(prefetchStatus
8553
+ ? { contextPrefetchStatus: prefetchStatus }
8554
+ : {}),
8555
+ ...(screenStatus
8556
+ ? { screenContextStatus: screenStatus }
8557
+ : {}),
8558
+ ...(priorConnection.status !== "none"
8559
+ ? { priorConnectionRequest: priorConnection.status }
8560
+ : {}),
8419
8561
  ...(turnUsageLabel ? { label: turnUsageLabel } : {}),
8420
8562
  ...(experimentAssignments.length > 0
8421
8563
  ? { experimentAssignments }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Saves the reply of a run the server ended because its worker stopped
3
+ * (a crash, a killed function) into the thread, the way a run that finishes
4
+ * saves its own. That worker held the reply only in memory, so without this
5
+ * the turn's finished tool calls exist only in the run ledger, which is
6
+ * pruned, and the thread forgets an email already went out.
7
+ *
8
+ * A run continued in its turn is saved as the first part of that turn's
9
+ * reply; one with nothing after it keeps its error. Nothing is saved once a
10
+ * later turn started or a later prompt was saved, because the reply would
11
+ * land under that prompt instead of its own.
12
+ */
13
+ export declare function foldReapedRunIntoThread(runId: string): Promise<void>;
@@ -0,0 +1,45 @@
1
+ import { getThread, updateThreadData, withThreadDataLock, } from "../chat-threads/store.js";
2
+ import { readStoppedRunForThreadFold } from "./run-store.js";
3
+ import { buildAssistantMessage, extractThreadMeta, foldAssistantTurn, isRunPrompt, normalizeThreadRepository, } from "./thread-data-builder.js";
4
+ /**
5
+ * Saves the reply of a run the server ended because its worker stopped
6
+ * (a crash, a killed function) into the thread, the way a run that finishes
7
+ * saves its own. That worker held the reply only in memory, so without this
8
+ * the turn's finished tool calls exist only in the run ledger, which is
9
+ * pruned, and the thread forgets an email already went out.
10
+ *
11
+ * A run continued in its turn is saved as the first part of that turn's
12
+ * reply; one with nothing after it keeps its error. Nothing is saved once a
13
+ * later turn started or a later prompt was saved, because the reply would
14
+ * land under that prompt instead of its own.
15
+ */
16
+ export async function foldReapedRunIntoThread(runId) {
17
+ const run = await readStoppedRunForThreadFold(runId);
18
+ if (!run || run.laterTurnStarted)
19
+ return;
20
+ const assistant = buildAssistantMessage(run.events, runId, {
21
+ turnId: run.turnId,
22
+ suppressInternalContinuation: run.continuedInTurn,
23
+ });
24
+ if (!assistant)
25
+ return;
26
+ await withThreadDataLock(run.threadId, async () => {
27
+ const thread = await getThread(run.threadId);
28
+ if (!thread)
29
+ return;
30
+ const repo = normalizeThreadRepository(JSON.parse(thread.threadData || "{}"));
31
+ const last = repo.messages.at(-1)?.message ?? repo.messages.at(-1);
32
+ const ownsLast = last?.role === "user"
33
+ ? isRunPrompt(last, { runId, ...run })
34
+ : last?.role === "assistant" &&
35
+ last.metadata?.custom?.turnId === run.turnId;
36
+ if (!ownsLast)
37
+ return;
38
+ const folded = foldAssistantTurn(repo, assistant, {
39
+ runId,
40
+ turnId: run.turnId,
41
+ });
42
+ const meta = extractThreadMeta(folded);
43
+ await updateThreadData(run.threadId, JSON.stringify(folded), thread.title, meta.preview || thread.preview, folded.messages.length);
44
+ });
45
+ }
@@ -1,6 +1,6 @@
1
1
  import { type ArtifactReceipt } from "../artifacts/detect.js";
2
- import { type AutoContinueRefusalCode } from "./auto-continue.js";
3
- import type { AgentChatEvent } from "./types.js";
2
+ import { type ContinueRefusalCode, type ContinueTrigger } from "./auto-continue.js";
3
+ import type { AgentChatEvent, RunEvent } from "./types.js";
4
4
  export declare const RUN_STALE_MS = 15000;
5
5
  export declare const BACKGROUND_RUN_STALE_MS = 90000;
6
6
  export declare const BACKGROUND_PROCESSING_RUN_STALE_MS = 45000;
@@ -164,6 +164,15 @@ export declare function shouldRedispatchUnclaimedBackgroundRun(row: {
164
164
  startedAt: number;
165
165
  }, now?: number): boolean;
166
166
  export declare function countRunsForTurn(threadId: string, turnId: string): Promise<number>;
167
+ /**
168
+ * The newest run of the turn before `turnId` in a thread: how the previous
169
+ * request ended. Null when the thread has no earlier turn.
170
+ */
171
+ export declare function getPreviousTurnNewestRun(threadId: string, turnId: string): Promise<{
172
+ turnId: string;
173
+ status: string;
174
+ terminalReason: string | null;
175
+ } | null>;
167
176
  export declare function getTurnInitiatorByRun(runId: string): Promise<(AgentTurnInitiator & {
168
177
  firstRunId: string;
169
178
  }) | null>;
@@ -179,14 +188,17 @@ export declare function tryClaimRunSlot(threadId: string, runId: string, maxStal
179
188
  dispatchPayload?: string;
180
189
  continuationOrder?: number;
181
190
  turnInitiator?: AgentTurnInitiator;
182
- /** The time-limit stop this run automatically continues. */
183
- autoContinueOf?: string;
191
+ /** The stopped run this run continues in the same turn, and who asked. */
192
+ continueOf?: {
193
+ runId: string;
194
+ trigger: ContinueTrigger;
195
+ };
184
196
  }): Promise<{
185
197
  claimed: boolean;
186
198
  activeRunId: string | null;
187
199
  completedRunId?: string;
188
200
  turnAborted?: boolean;
189
- autoContinueRefused?: AutoContinueRefusalCode;
201
+ continueRefused?: ContinueRefusalCode;
190
202
  }>;
191
203
  export declare function setRunError(runId: string, errorCode: string | undefined, errorDetail: string | undefined): Promise<void>;
192
204
  export declare function setRunTerminalReason(runId: string, terminalReason: string | undefined): Promise<void>;
@@ -280,6 +292,18 @@ export declare function getRunById(runId: string): Promise<{
280
292
  errorDetail: string | null;
281
293
  terminalReason: string | null;
282
294
  } | null>;
295
+ /**
296
+ * What saving a run the server ended on its behalf needs: the run's ledger,
297
+ * and whether anything ran after it in its turn or in a later turn.
298
+ */
299
+ export declare function readStoppedRunForThreadFold(runId: string): Promise<{
300
+ threadId: string;
301
+ turnId: string;
302
+ startedAt: number;
303
+ events: RunEvent[];
304
+ continuedInTurn: boolean;
305
+ laterTurnStarted: boolean;
306
+ } | null>;
283
307
  export declare function getLastTerminalRunEvent(runId: string): Promise<{
284
308
  seq: number;
285
309
  event: Record<string, unknown>;