@agent-native/core 0.98.4 → 0.98.6
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
- package/corpus/core/docs/content/external-agents.mdx +62 -7
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +95 -21
- package/corpus/core/src/a2a/task-store.ts +65 -3
- package/corpus/core/src/agent/durable-background.ts +16 -16
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
- package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
- package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
- package/corpus/core/src/agent/production-agent.ts +200 -27
- package/corpus/core/src/agent/run-manager.ts +81 -1
- package/corpus/core/src/agent/run-store.ts +301 -41
- package/corpus/core/src/client/AgentPanel.tsx +6 -2
- package/corpus/core/src/client/AssistantChat.tsx +364 -69
- package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
- package/corpus/core/src/client/chat/message-components.tsx +3 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
- package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
- package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
- package/corpus/core/src/client/session-replay.ts +361 -29
- package/corpus/core/src/client/sse-event-processor.ts +43 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
- package/corpus/core/src/demo/redact.ts +13 -149
- package/corpus/core/src/extensions/html-shell.ts +3 -0
- package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
- package/corpus/core/src/integrations/adapters/slack.ts +123 -13
- package/corpus/core/src/integrations/identity-links-store.ts +210 -0
- package/corpus/core/src/integrations/identity.ts +132 -0
- package/corpus/core/src/integrations/index.ts +6 -0
- package/corpus/core/src/integrations/plugin.ts +58 -1
- package/corpus/core/src/integrations/types.ts +7 -0
- package/corpus/core/src/mcp/build-server.ts +127 -22
- package/corpus/core/src/mcp/external-agent-policy.ts +18 -0
- package/corpus/core/src/mcp/index.ts +1 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
- package/corpus/core/src/server/agent-chat/plugin-options.ts +12 -1
- package/corpus/core/src/server/agent-chat/script-entries.ts +20 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +3 -0
- package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
- package/corpus/core/src/shared/reasoning-effort.ts +35 -0
- package/corpus/core/src/styles/agent-conversation.css +26 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
- package/corpus/templates/analytics/AGENTS.md +27 -0
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +0 -1
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +40 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +13 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -0
- package/corpus/templates/analytics/app/global.css +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +69 -16
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-connected-external-agents-can-now-look-up-sessions-error-iss.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-identities-stay-visible-when-demo-mode-is-off.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
- package/corpus/templates/analytics/netlify.toml +0 -3
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +101 -2
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +49 -17
- package/corpus/templates/analytics/server/lib/session-replay.ts +18 -16
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +43 -6
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/brain/app/global.css +2 -2
- package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
- package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
- package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
- package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
- package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
- package/corpus/templates/forms/app/global.css +164 -13
- package/corpus/templates/forms/app/pages/AskPage.tsx +35 -13
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +143 -67
- package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +46 -20
- package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
- package/corpus/templates/forms/changelog/2026-07-12-ask-forms-suggestions-now-sit-beneath-the-composer-for-a-tig.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-builder-response-tables-now-fill-the-view-without-a-redundan.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-response-tables-now-fill-the-available-pane-with-a-flexible-.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
- package/corpus/templates/mail/AGENTS.md +3 -1
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
- package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
- package/corpus/templates/mail/server/lib/mail-integrations.ts +3 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +84 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +20 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +57 -4
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/durable-background.d.ts +10 -10
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +16 -16
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +19 -8
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -3
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/output-tokens.js +6 -6
- package/dist/agent/engine/output-tokens.js.map +1 -1
- package/dist/agent/production-agent.d.ts +52 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +154 -26
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +71 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +9 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +243 -48
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +298 -67
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +146 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +2 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +13 -37
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionEditor.js +5 -2
- package/dist/client/extensions/ExtensionEditor.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -1
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +2 -1
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js +2 -0
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +308 -29
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +5 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +37 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +1 -1
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +6 -6
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +13 -4
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/demo/redact.d.ts +7 -7
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +8 -145
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +2 -0
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/session-replay-iframe.d.ts +10 -0
- package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
- package/dist/extensions/session-replay-iframe.js +121 -0
- package/dist/extensions/session-replay-iframe.js.map +1 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +98 -13
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity-links-store.d.ts +23 -0
- package/dist/integrations/identity-links-store.d.ts.map +1 -0
- package/dist/integrations/identity-links-store.js +158 -0
- package/dist/integrations/identity-links-store.js.map +1 -0
- package/dist/integrations/identity.d.ts +11 -0
- package/dist/integrations/identity.d.ts.map +1 -0
- package/dist/integrations/identity.js +100 -0
- package/dist/integrations/identity.js.map +1 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +43 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +6 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/mcp/build-server.d.ts +9 -2
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +97 -20
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/external-agent-policy.d.ts +19 -0
- package/dist/mcp/external-agent-policy.d.ts.map +1 -0
- package/dist/mcp/external-agent-policy.js +2 -0
- package/dist/mcp/external-agent-policy.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -19
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +11 -1
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
- package/dist/server/agent-chat/script-entries.js +16 -2
- package/dist/server/agent-chat/script-entries.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/session-replay-iframe-protocol.d.ts +36 -0
- package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
- package/dist/session-replay-iframe-protocol.js +24 -0
- package/dist/session-replay-iframe-protocol.js.map +1 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.d.ts.map +1 -1
- package/dist/shared/reasoning-effort.js +36 -0
- package/dist/shared/reasoning-effort.js.map +1 -1
- package/dist/styles/agent-conversation.css +26 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/docs/content/durable-background-runs.mdx +37 -0
- package/docs/content/external-agents.mdx +62 -7
- package/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
dispatchPathTargetsNetlifyBackgroundFunction,
|
|
58
58
|
isAgentChatDurableBackgroundEnabled,
|
|
59
59
|
isAgentChatForegroundSelfChainEnabled,
|
|
60
|
+
isInBackgroundFunctionRuntime,
|
|
60
61
|
resolveAgentChatProcessRunDispatchPath,
|
|
61
62
|
shouldUseBackgroundFunctionTimeoutForWorker,
|
|
62
63
|
} from "./durable-background.js";
|
|
@@ -124,6 +125,8 @@ import {
|
|
|
124
125
|
abortRun,
|
|
125
126
|
abortRunDurably,
|
|
126
127
|
tryClaimRunSlot,
|
|
128
|
+
isHostedRuntime,
|
|
129
|
+
resolveRunSoftTimeoutMs,
|
|
127
130
|
} from "./run-manager.js";
|
|
128
131
|
import type { ActiveRun } from "./run-manager.js";
|
|
129
132
|
import {
|
|
@@ -1054,6 +1057,38 @@ const TOOL_INPUT_ACTIVITY_INTERVAL_MS = 1500;
|
|
|
1054
1057
|
const ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
1055
1058
|
const ACTION_PREPARATION_ZERO_BYTE_RESTART_LIMIT = 2;
|
|
1056
1059
|
const MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
1060
|
+
/**
|
|
1061
|
+
* FIX 2 (durable-background incident): tighter no-progress deadline for ONLY
|
|
1062
|
+
* the FIRST engine-stream event of a model call, and ONLY on the clamped
|
|
1063
|
+
* HOSTED foreground runtime — `isHostedRuntime()` (run-manager.ts, the same
|
|
1064
|
+
* predicate that selects the 40s soft budget) AND NOT proven to be running
|
|
1065
|
+
* inside a Netlify background function (`isInBackgroundFunctionRuntime`). A
|
|
1066
|
+
* hung first model event previously rode the full
|
|
1067
|
+
* `MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS` (90s) before the in-loop watchdog
|
|
1068
|
+
* could emit `auto_continue` — but the hosted foreground function is killed
|
|
1069
|
+
* around 40s, so that watchdog could never actually fire: the run died as a
|
|
1070
|
+
* silent platform kill instead of a recoverable checkpoint (observed: ~40s of
|
|
1071
|
+
* "Contacting model" with zero tokens, then a hard timeout with no
|
|
1072
|
+
* auto_continue ever emitted). Once ANY event (including a heartbeat) has
|
|
1073
|
+
* been observed for this model call, subsequent gaps revert to the normal 90s
|
|
1074
|
+
* watchdog — this only guards the "nothing has happened yet" window.
|
|
1075
|
+
*
|
|
1076
|
+
* ORDERING INVARIANT (each bound must stay strictly smaller than the next —
|
|
1077
|
+
* do not change one without re-checking the others). This cap exists ONLY
|
|
1078
|
+
* where the 40s ceiling exists: off hosted runtimes (local dev, self-hosted
|
|
1079
|
+
* long-lived Node) `resolveRunSoftTimeoutMs` resolves to 0 (no soft-timeout
|
|
1080
|
+
* regime, no platform wall), a genuinely slow first token — large local
|
|
1081
|
+
* contexts, slow local providers — is legitimate, and the full 90s window
|
|
1082
|
+
* applies unchanged:
|
|
1083
|
+
* FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS (25s, here)
|
|
1084
|
+
* < HOSTED_SOFT_TIMEOUT_CEILING_MS (40s, run-manager.ts)
|
|
1085
|
+
* < MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS (90s, above)
|
|
1086
|
+
* < RUN_NO_PROGRESS_HARD_TIMEOUT_MS (150s, run-manager.ts)
|
|
1087
|
+
*
|
|
1088
|
+
* Background-function runs (proven 15-min budget, no ~40s wall) are likewise
|
|
1089
|
+
* unaffected — they keep the full 90s window for every event, first or not.
|
|
1090
|
+
*/
|
|
1091
|
+
const FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS = 25_000;
|
|
1057
1092
|
// Raised from 1 -> 2 now that each retry actually adapts (raises the token
|
|
1058
1093
|
// ceiling and steps reasoning effort down a tier) instead of re-issuing the
|
|
1059
1094
|
// exact same doomed request twice.
|
|
@@ -3129,6 +3164,20 @@ export async function runAgentLoop(opts: {
|
|
|
3129
3164
|
let zeroByteToolInputRestart: ZeroByteToolInputRestart | undefined;
|
|
3130
3165
|
let endedForNoProgress = false;
|
|
3131
3166
|
let lastModelStreamProgressAt = Date.now();
|
|
3167
|
+
// FIX 2: true once ANY engine-stream event (including a heartbeat)
|
|
3168
|
+
// has been retrieved for THIS model call — gates
|
|
3169
|
+
// `FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS` below to only the first
|
|
3170
|
+
// await on a fresh `engine.stream()` call, never subsequent gaps.
|
|
3171
|
+
let hasReceivedFirstEngineEvent = false;
|
|
3172
|
+
// Resolved once per model-call attempt (not per event) so a single
|
|
3173
|
+
// call's deadline math can't observe the runtime predicates changing
|
|
3174
|
+
// mid-flight. Requires BOTH a hosted runtime (where the 40s clamp and
|
|
3175
|
+
// the platform wall behind it exist — local dev/self-hosted resolve
|
|
3176
|
+
// the soft timeout to 0 and legitimately tolerate slow first tokens)
|
|
3177
|
+
// AND not being a proven background-function worker. See
|
|
3178
|
+
// `FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS`.
|
|
3179
|
+
const isClampedForegroundRuntimeForThisCall =
|
|
3180
|
+
isHostedRuntime() && !isInBackgroundFunctionRuntime();
|
|
3132
3181
|
const sendToolInputActivity = (
|
|
3133
3182
|
toolName: string | undefined,
|
|
3134
3183
|
toolInputId?: string,
|
|
@@ -3185,8 +3234,23 @@ export async function runAgentLoop(opts: {
|
|
|
3185
3234
|
}
|
|
3186
3235
|
return Number.isFinite(deadlineAt) ? deadlineAt : undefined;
|
|
3187
3236
|
};
|
|
3188
|
-
const modelStreamNoProgressDeadlineAt = () =>
|
|
3189
|
-
|
|
3237
|
+
const modelStreamNoProgressDeadlineAt = () => {
|
|
3238
|
+
const baseDeadlineAt =
|
|
3239
|
+
lastModelStreamProgressAt + MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS;
|
|
3240
|
+
// FIX 2: cap the FIRST event's deadline tighter on the clamped
|
|
3241
|
+
// foreground runtime — see FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS
|
|
3242
|
+
// for the ordering invariant this protects.
|
|
3243
|
+
if (
|
|
3244
|
+
hasReceivedFirstEngineEvent ||
|
|
3245
|
+
!isClampedForegroundRuntimeForThisCall
|
|
3246
|
+
) {
|
|
3247
|
+
return baseDeadlineAt;
|
|
3248
|
+
}
|
|
3249
|
+
return Math.min(
|
|
3250
|
+
baseDeadlineAt,
|
|
3251
|
+
lastModelStreamProgressAt + FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS,
|
|
3252
|
+
);
|
|
3253
|
+
};
|
|
3190
3254
|
const noProgressDeadlineAt = () => {
|
|
3191
3255
|
const actionDeadlineAt = actionPreparationDeadlineAt();
|
|
3192
3256
|
const modelDeadlineAt = modelStreamNoProgressDeadlineAt();
|
|
@@ -3306,6 +3370,11 @@ export async function runAgentLoop(opts: {
|
|
|
3306
3370
|
eventIteratorDone = true;
|
|
3307
3371
|
break;
|
|
3308
3372
|
}
|
|
3373
|
+
// FIX 2: only the deadline for THIS await could have been
|
|
3374
|
+
// tightened by FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS — every
|
|
3375
|
+
// subsequent call in this model call reverts to the normal 90s
|
|
3376
|
+
// watchdog regardless of what kind of event this turned out to be.
|
|
3377
|
+
hasReceivedFirstEngineEvent = true;
|
|
3309
3378
|
const event = nextEvent.value;
|
|
3310
3379
|
if (hasNoProgressStalled()) {
|
|
3311
3380
|
checkpointNoProgress();
|
|
@@ -4325,7 +4394,7 @@ export async function runAgentLoop(opts: {
|
|
|
4325
4394
|
isError = true;
|
|
4326
4395
|
}
|
|
4327
4396
|
mcpApp = mcpResult?.mcpApp;
|
|
4328
|
-
// Demo mode: the agent must see the same
|
|
4397
|
+
// Demo mode: the agent must see the same anonymized data the UI shows, so
|
|
4329
4398
|
// it can't read out a real name/email on a live screen share. Redact
|
|
4330
4399
|
// the structured result (not the JSON string) so IDs/dates/URLs stay
|
|
4331
4400
|
// intact and follow-up tool calls still work. Gated — the expensive
|
|
@@ -5019,6 +5088,71 @@ export function shouldChainBackgroundContinuation(opts: {
|
|
|
5019
5088
|
);
|
|
5020
5089
|
}
|
|
5021
5090
|
|
|
5091
|
+
/**
|
|
5092
|
+
* Minimum remaining budget (ms) a synchronous self-chain continuation chunk
|
|
5093
|
+
* must have left — after subtracting the wall-clock this invocation has
|
|
5094
|
+
* ALREADY spent on its own pre-`startRun` setup (auth, DB reads, marker
|
|
5095
|
+
* validation) — before it is safe to let it start real agent-loop work at
|
|
5096
|
+
* all. Below this, even a reduced soft-timeout risks the run making an LLM
|
|
5097
|
+
* call that then gets cut off by Netlify's ~60s synchronous-function hard
|
|
5098
|
+
* kill mid-stream instead of checkpointing cleanly — exactly the "run wedged
|
|
5099
|
+
* until stale-run recovery" failure `resolveSelfChainContinuationBudget`
|
|
5100
|
+
* hardens against. 8s leaves enough runway for a small model round trip (or
|
|
5101
|
+
* the soft-timeout timer itself) to still land cleanly before the hard wall.
|
|
5102
|
+
*/
|
|
5103
|
+
export const SELF_CHAIN_MIN_CONTINUATION_BUDGET_MS = 8_000;
|
|
5104
|
+
|
|
5105
|
+
/** Result of `resolveSelfChainContinuationBudget`. */
|
|
5106
|
+
export interface SelfChainContinuationBudget {
|
|
5107
|
+
/**
|
|
5108
|
+
* True when there isn't enough wall-clock left in THIS invocation to
|
|
5109
|
+
* safely start real agent-loop work. The caller should skip straight to a
|
|
5110
|
+
* synthetic `run_timeout` boundary (the same one a normal soft-timeout
|
|
5111
|
+
* produces) instead of invoking the loop, so the existing continuation
|
|
5112
|
+
* machinery (`chainServerDrivenContinuation` / the client's `auto_continue`
|
|
5113
|
+
* re-POST fallback) hands off to a fresh invocation instead of risking a
|
|
5114
|
+
* mid-stream kill.
|
|
5115
|
+
*/
|
|
5116
|
+
skipToBoundary: boolean;
|
|
5117
|
+
/**
|
|
5118
|
+
* The soft-timeout budget to pass into `startRun` when NOT skipping — the
|
|
5119
|
+
* chunk's normal ceiling minus wall-clock already spent on setup. Always
|
|
5120
|
+
* `<= chunkCeilingMs` and `>= minContinuationBudgetMs` when `skipToBoundary`
|
|
5121
|
+
* is false.
|
|
5122
|
+
*/
|
|
5123
|
+
softTimeoutMs: number;
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
/**
|
|
5127
|
+
* Budgets a synchronous (non-`-background`-function) self-chain continuation
|
|
5128
|
+
* chunk against the wall-clock ALREADY spent on this invocation's own setup
|
|
5129
|
+
* before `startRun` is ever called, instead of handing it a fresh
|
|
5130
|
+
* `chunkCeilingMs` window that ignores that setup cost entirely.
|
|
5131
|
+
*
|
|
5132
|
+
* Root cause this hardens against: when `AGENT_CHAT_FOREGROUND_SELF_CHAIN` is
|
|
5133
|
+
* enabled, a continuation is dispatched to the `_process-run` route running as
|
|
5134
|
+
* a REGULAR synchronous serverless function (hard ~60s wall on Netlify). The
|
|
5135
|
+
* handler burns setup time (DB reads, auth, marker validation) before ever
|
|
5136
|
+
* calling `startRun()`, which — unaware of that elapsed time — grants a fresh
|
|
5137
|
+
* ~40s run ceiling. Total handler time (setup + a fresh 40s) can then exceed
|
|
5138
|
+
* the 60s wall, so the function is killed mid-stream instead of checkpointing,
|
|
5139
|
+
* and the run sits "active" until stale-run recovery (~90s later) instead of
|
|
5140
|
+
* cleanly handing off.
|
|
5141
|
+
*
|
|
5142
|
+
* Pure function — no I/O, unit-testable in isolation.
|
|
5143
|
+
*/
|
|
5144
|
+
export function resolveSelfChainContinuationBudget(
|
|
5145
|
+
elapsedSinceHandlerEntryMs: number,
|
|
5146
|
+
chunkCeilingMs: number,
|
|
5147
|
+
minContinuationBudgetMs: number = SELF_CHAIN_MIN_CONTINUATION_BUDGET_MS,
|
|
5148
|
+
): SelfChainContinuationBudget {
|
|
5149
|
+
const remaining = chunkCeilingMs - Math.max(0, elapsedSinceHandlerEntryMs);
|
|
5150
|
+
if (remaining < minContinuationBudgetMs) {
|
|
5151
|
+
return { skipToBoundary: true, softTimeoutMs: 0 };
|
|
5152
|
+
}
|
|
5153
|
+
return { skipToBoundary: false, softTimeoutMs: remaining };
|
|
5154
|
+
}
|
|
5155
|
+
|
|
5022
5156
|
export async function markBackgroundContinuationChunkTerminal(opts: {
|
|
5023
5157
|
runId: string;
|
|
5024
5158
|
continuationReason: AgentLoopContinuationReason;
|
|
@@ -6061,10 +6195,7 @@ export function createProductionAgentHandler(
|
|
|
6061
6195
|
variantId: string;
|
|
6062
6196
|
}> = [];
|
|
6063
6197
|
|
|
6064
|
-
// Database-backed experiments remain available to app operators.
|
|
6065
|
-
// platform-wide hosted rollout is layered in only when no explicit or
|
|
6066
|
-
// persisted model selection exists, and only on the cross-provider Builder
|
|
6067
|
-
// engine. This preserves the model picker's user intent.
|
|
6198
|
+
// Database-backed experiments remain available to app operators.
|
|
6068
6199
|
try {
|
|
6069
6200
|
if (ownerEmail) {
|
|
6070
6201
|
const { resolveActiveExperimentConfig } =
|
|
@@ -6080,22 +6211,6 @@ export function createProductionAgentHandler(
|
|
|
6080
6211
|
modelSelectionSource = "experiment";
|
|
6081
6212
|
}
|
|
6082
6213
|
}
|
|
6083
|
-
|
|
6084
|
-
if (modelSelectionSource === "default") {
|
|
6085
|
-
const { resolveHostedDefaultModelExperiment } =
|
|
6086
|
-
await import("../observability/hosted-model-experiment.js");
|
|
6087
|
-
const hostedExperiment = resolveHostedDefaultModelExperiment({
|
|
6088
|
-
userId: ownerEmail,
|
|
6089
|
-
engineName: engine.name,
|
|
6090
|
-
isDefaultModelSelection: true,
|
|
6091
|
-
supportedModels: engine.supportedModels,
|
|
6092
|
-
});
|
|
6093
|
-
if (hostedExperiment) {
|
|
6094
|
-
effectiveModel = hostedExperiment.model;
|
|
6095
|
-
experimentAssignments.push(hostedExperiment.assignment);
|
|
6096
|
-
modelSelectionSource = "experiment";
|
|
6097
|
-
}
|
|
6098
|
-
}
|
|
6099
6214
|
}
|
|
6100
6215
|
} catch {
|
|
6101
6216
|
// Experiments are best-effort. Model resolution must keep working if the
|
|
@@ -6854,9 +6969,11 @@ export function createProductionAgentHandler(
|
|
|
6854
6969
|
turnId: effectiveTurnId,
|
|
6855
6970
|
}
|
|
6856
6971
|
: null;
|
|
6857
|
-
// Foreground self-chain:
|
|
6858
|
-
//
|
|
6859
|
-
//
|
|
6972
|
+
// Foreground self-chain: opt-in only — requires the app/deploy to
|
|
6973
|
+
// explicitly set AGENT_CHAT_FOREGROUND_SELF_CHAIN truthy (unset/false
|
|
6974
|
+
// means disabled), on top of the hosted runtime + A2A_SECRET gates, AND
|
|
6975
|
+
// this specific run never having been routed to the durable background
|
|
6976
|
+
// worker. A run that WAS dispatched to background
|
|
6860
6977
|
// (`dispatchToBackground`) already has its recovery owned by the background
|
|
6861
6978
|
// circuit-breaker / `isBackgroundWorker` chain above — this flag must never
|
|
6862
6979
|
// double up with that path, only stand in for it on plain foreground turns. A persistent
|
|
@@ -7137,6 +7254,33 @@ export function createProductionAgentHandler(
|
|
|
7137
7254
|
(backgroundRuntimeDetail ? ` ${backgroundRuntimeDetail}` : "") +
|
|
7138
7255
|
firstRequestPayloadDetail;
|
|
7139
7256
|
|
|
7257
|
+
// Synchronous self-chain budgeting: this is a `_process-run` re-entry
|
|
7258
|
+
// that landed on the regular ~60s function (not a proven `-background`
|
|
7259
|
+
// function), AND it got here WITHOUT the durable-background path being
|
|
7260
|
+
// active for this app — the only other way to reach `isBackgroundWorker`
|
|
7261
|
+
// is `AGENT_CHAT_FOREGROUND_SELF_CHAIN` chaining onto the framework route
|
|
7262
|
+
// (see `chainServerDrivenContinuation`'s `chainViaDurableBackground`
|
|
7263
|
+
// choice). That chunk must budget its soft-timeout against the wall-clock
|
|
7264
|
+
// THIS invocation already spent on setup above, not a fresh ceiling — see
|
|
7265
|
+
// `resolveSelfChainContinuationBudget`. Deliberately excludes a durable
|
|
7266
|
+
// worker that merely landed on the wrong runtime (handled unchanged by
|
|
7267
|
+
// the existing `runsInBackgroundFunction` clamp below).
|
|
7268
|
+
const isSynchronousSelfChainContinuation =
|
|
7269
|
+
isBackgroundWorker &&
|
|
7270
|
+
!runsInBackgroundFunction &&
|
|
7271
|
+
!isAgentChatDurableBackgroundEnabled({
|
|
7272
|
+
appOptIn: options.durableBackgroundRuns === true,
|
|
7273
|
+
});
|
|
7274
|
+
const selfChainBudget = isSynchronousSelfChainContinuation
|
|
7275
|
+
? resolveSelfChainContinuationBudget(
|
|
7276
|
+
Date.now() - setupT0,
|
|
7277
|
+
resolveRunSoftTimeoutMs(options.runSoftTimeoutMs, {
|
|
7278
|
+
useHostedDefault: true,
|
|
7279
|
+
backgroundFunction: false,
|
|
7280
|
+
}),
|
|
7281
|
+
)
|
|
7282
|
+
: null;
|
|
7283
|
+
|
|
7140
7284
|
startRun(
|
|
7141
7285
|
runId,
|
|
7142
7286
|
effectiveThreadId,
|
|
@@ -7146,6 +7290,25 @@ export function createProductionAgentHandler(
|
|
|
7146
7290
|
updateTrackedProgressFromEvent(event);
|
|
7147
7291
|
};
|
|
7148
7292
|
|
|
7293
|
+
if (selfChainBudget?.skipToBoundary) {
|
|
7294
|
+
// Not enough wall-clock left in this synchronous function
|
|
7295
|
+
// invocation to safely start real agent-loop work — doing so
|
|
7296
|
+
// anyway risks Netlify's ~60s hard kill cutting it off mid-stream
|
|
7297
|
+
// (the exact incident `resolveSelfChainContinuationBudget` hardens
|
|
7298
|
+
// against). Skip straight to the same `run_timeout` boundary a
|
|
7299
|
+
// normal soft-timeout produces so the existing continuation
|
|
7300
|
+
// machinery (`chainServerDrivenContinuation` below, plus the
|
|
7301
|
+
// client's `auto_continue` re-POST fallback) hands the turn off to
|
|
7302
|
+
// a fresh invocation instead.
|
|
7303
|
+
await recordRunDiagnostic(
|
|
7304
|
+
runId,
|
|
7305
|
+
RUN_DIAG_STAGE.workerSetupStep,
|
|
7306
|
+
`self_chain_budget_exhausted elapsed=${Date.now() - setupT0}ms`,
|
|
7307
|
+
).catch(() => {});
|
|
7308
|
+
send({ type: "auto_continue", reason: "run_timeout" });
|
|
7309
|
+
return;
|
|
7310
|
+
}
|
|
7311
|
+
|
|
7149
7312
|
send({ type: "activity", label: "Starting agent" });
|
|
7150
7313
|
|
|
7151
7314
|
// DIAGNOSTIC: the agent loop body actually started running. For a
|
|
@@ -7595,7 +7758,17 @@ export function createProductionAgentHandler(
|
|
|
7595
7758
|
},
|
|
7596
7759
|
handleRunComplete,
|
|
7597
7760
|
{
|
|
7598
|
-
|
|
7761
|
+
// A synchronous self-chain chunk with enough remaining budget gets
|
|
7762
|
+
// that REDUCED budget (ceiling minus setup already spent) instead of
|
|
7763
|
+
// a fresh `options.runSoftTimeoutMs`/hosted-default window — see
|
|
7764
|
+
// `resolveSelfChainContinuationBudget`. Every other caller
|
|
7765
|
+
// (durable-background worker, plain foreground POST) is unaffected:
|
|
7766
|
+
// `selfChainBudget` is `null` for them and this falls through to the
|
|
7767
|
+
// exact prior value.
|
|
7768
|
+
softTimeoutMs:
|
|
7769
|
+
selfChainBudget && !selfChainBudget.skipToBoundary
|
|
7770
|
+
? selfChainBudget.softTimeoutMs
|
|
7771
|
+
: options.runSoftTimeoutMs,
|
|
7599
7772
|
useHostedSoftTimeoutDefault: true,
|
|
7600
7773
|
// Lift the soft-timeout clamp to ~13min ONLY when this run is actually
|
|
7601
7774
|
// executing inside a real Netlify `-background` function (15-min budget,
|
|
@@ -291,7 +291,15 @@ export interface ResolveRunSoftTimeoutOptions {
|
|
|
291
291
|
backgroundFunction?: boolean;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
/**
|
|
295
|
+
* True on hosted/serverless runtimes where the soft-timeout regime applies
|
|
296
|
+
* (see `resolveRunSoftTimeoutMs`, which resolves to 0 — disabled — off these
|
|
297
|
+
* runtimes). Exported so production-agent.ts can gate its foreground
|
|
298
|
+
* first-model-event cap (`FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS`) on the
|
|
299
|
+
* SAME predicate that selects the 40s clamp: the cap only makes sense where
|
|
300
|
+
* that clamp (and the platform wall behind it) exists.
|
|
301
|
+
*/
|
|
302
|
+
export function isHostedRuntime(): boolean {
|
|
295
303
|
if (
|
|
296
304
|
process.env.NETLIFY &&
|
|
297
305
|
process.env.NETLIFY !== "false" &&
|
|
@@ -1516,6 +1524,44 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1516
1524
|
const memRun = getActiveRunForThread(threadId);
|
|
1517
1525
|
if (memRun && (memRun.status === "running" || memRun.events.length > 0)) {
|
|
1518
1526
|
const sqlSnapshot = await fetchRunThreadSnapshot(memRun.runId, threadId);
|
|
1527
|
+
|
|
1528
|
+
// FIX 1 (durable-background incident): a terminal in-memory run (chunk
|
|
1529
|
+
// completed at a soft-timeout/no-progress/loop-limit boundary, or any
|
|
1530
|
+
// other terminal outcome) never clears `threadToRun` — it stays this
|
|
1531
|
+
// thread's resident in-memory candidate for up to CLEANUP_DELAY_MS
|
|
1532
|
+
// (5 min), and `fetchRunThreadSnapshot` above returns null the instant
|
|
1533
|
+
// SQL's newest row for the thread is no longer THIS run (i.e. a
|
|
1534
|
+
// successor already exists). Left alone, every poll that lands on this
|
|
1535
|
+
// warm isolate would keep falling back to `memRun.status` below and
|
|
1536
|
+
// never discover that a newer, still-running successor already exists
|
|
1537
|
+
// in SQL — exactly the "stale terminal run masks a live successor" bug
|
|
1538
|
+
// that produced the mid-sentence dead turn. Only pay for the extra SQL
|
|
1539
|
+
// read here, in the terminal-candidate branch; the common "still
|
|
1540
|
+
// running" poll (the vast majority) never reaches it.
|
|
1541
|
+
if (!sqlSnapshot && memRun.status !== "running") {
|
|
1542
|
+
const successor = await fetchNewerNonTerminalRunForSameTurn(
|
|
1543
|
+
threadId,
|
|
1544
|
+
memRun,
|
|
1545
|
+
);
|
|
1546
|
+
if (successor) {
|
|
1547
|
+
return {
|
|
1548
|
+
runId: successor.id,
|
|
1549
|
+
threadId: successor.threadId,
|
|
1550
|
+
turnId: successor.turnId ?? successor.id,
|
|
1551
|
+
status: successor.status,
|
|
1552
|
+
heartbeatAt: successor.heartbeatAt ?? successor.startedAt,
|
|
1553
|
+
lastProgressAt: successor.lastProgressAt,
|
|
1554
|
+
dispatchMode: successor.dispatchMode,
|
|
1555
|
+
terminalReason: successor.terminalReason,
|
|
1556
|
+
diagStage: successor.diagStage,
|
|
1557
|
+
// Definitionally non-terminal and freshly read from SQL above —
|
|
1558
|
+
// never the stale in-memory candidate's own state.
|
|
1559
|
+
awaitingRedispatch: false,
|
|
1560
|
+
hasInFlightWork: successor.inFlightSince != null,
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1519
1565
|
const status = sqlSnapshot?.status ?? memRun.status;
|
|
1520
1566
|
const heartbeatAt =
|
|
1521
1567
|
status === "running"
|
|
@@ -1686,6 +1732,40 @@ async function fetchRunThreadSnapshot(runId: string, threadId: string) {
|
|
|
1686
1732
|
}
|
|
1687
1733
|
}
|
|
1688
1734
|
|
|
1735
|
+
/**
|
|
1736
|
+
* FIX 1 (durable-background incident): find a genuinely newer, still-running
|
|
1737
|
+
* SQL row for the SAME turn as a terminal in-memory `ActiveRun` — used only
|
|
1738
|
+
* when `fetchRunThreadSnapshot` found no SQL row matching the in-memory
|
|
1739
|
+
* run's own id (i.e. SQL's newest row for the thread is a different run).
|
|
1740
|
+
* `getRunByThread` always returns the thread's newest row by `started_at`,
|
|
1741
|
+
* so this is the same read `fetchRunThreadSnapshot` already made; we just
|
|
1742
|
+
* don't throw its result away when the id doesn't match.
|
|
1743
|
+
*
|
|
1744
|
+
* Scoped to the SAME `turnId` (not just the same thread) so an unrelated,
|
|
1745
|
+
* later user turn on the same thread is never mistaken for a continuation
|
|
1746
|
+
* successor of this one.
|
|
1747
|
+
*/
|
|
1748
|
+
async function fetchNewerNonTerminalRunForSameTurn(
|
|
1749
|
+
threadId: string,
|
|
1750
|
+
memRun: ActiveRun,
|
|
1751
|
+
): Promise<Awaited<ReturnType<typeof getRunByThread>> | null> {
|
|
1752
|
+
try {
|
|
1753
|
+
const latest = await getRunByThread(threadId, { includeTerminal: true });
|
|
1754
|
+
if (
|
|
1755
|
+
latest &&
|
|
1756
|
+
latest.id !== memRun.runId &&
|
|
1757
|
+
latest.status === "running" &&
|
|
1758
|
+
latest.startedAt > memRun.startedAt &&
|
|
1759
|
+
(latest.turnId ?? latest.id) === memRun.turnId
|
|
1760
|
+
) {
|
|
1761
|
+
return latest;
|
|
1762
|
+
}
|
|
1763
|
+
return null;
|
|
1764
|
+
} catch {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1689
1769
|
/** Get a run by ID */
|
|
1690
1770
|
export function getRun(runId: string): ActiveRun | null {
|
|
1691
1771
|
return activeRuns.get(runId) ?? null;
|