@agent-native/core 0.128.4 → 0.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +84 -0
- package/corpus/core/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
- package/corpus/core/package.json +6 -3
- package/corpus/core/src/a2a/client.ts +18 -4
- package/corpus/core/src/a2a/server.ts +19 -3
- package/corpus/core/src/a2a/types.ts +7 -0
- package/corpus/core/src/action.ts +43 -16
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -10
- package/corpus/core/src/agent/production-agent.ts +41 -3
- package/corpus/core/src/agent/thread-data-builder.ts +5 -0
- package/corpus/core/src/chat-threads/migrations.ts +53 -0
- package/corpus/core/src/cli/create.ts +4 -0
- package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
- package/corpus/core/src/cli/skills.ts +16 -0
- package/corpus/core/src/client/AgentPanel.tsx +9 -5
- package/corpus/core/src/client/AssistantChat.tsx +138 -46
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +16 -10
- package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
- package/corpus/core/src/client/analytics.ts +159 -102
- package/corpus/core/src/client/chat/message-components.tsx +12 -5
- package/corpus/core/src/client/chat/runtime.ts +2 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +25 -2
- package/corpus/core/src/client/chat-model-groups.ts +3 -0
- package/corpus/core/src/client/client-status-requests.ts +161 -0
- package/corpus/core/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
- package/corpus/core/src/client/sse-event-processor.ts +15 -3
- package/corpus/core/src/client/use-agent-engine-configured.ts +54 -50
- package/corpus/core/src/client/use-chat-models.ts +16 -11
- package/corpus/core/src/client/use-chat-threads.ts +32 -9
- package/corpus/core/src/client/use-session.ts +4 -5
- package/corpus/core/src/db/client.ts +83 -8
- package/corpus/core/src/db/ensure-additive-columns.ts +31 -20
- package/corpus/core/src/db/migrations.ts +22 -2
- package/corpus/core/src/mcp/approval-store.ts +99 -0
- package/corpus/core/src/mcp/build-server.ts +579 -303
- package/corpus/core/src/mcp/embed-app.ts +1 -1
- package/corpus/core/src/mcp/oauth-client-metadata.ts +380 -0
- package/corpus/core/src/mcp/oauth-route.ts +57 -52
- package/corpus/core/src/mcp/screen-memory-stdio.ts +400 -378
- package/corpus/core/src/mcp/server.ts +29 -145
- package/corpus/core/src/mcp/stdio.ts +72 -80
- package/corpus/core/src/mcp-client/manager.ts +6 -10
- package/corpus/core/src/mcp-client/oauth-client.ts +127 -36
- package/corpus/core/src/mcp-client/oauth-routes.ts +16 -6
- package/corpus/core/src/mcp-client/routes.ts +6 -5
- package/corpus/core/src/server/agent-capabilities.ts +20 -0
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +27 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +18 -0
- package/corpus/core/src/server/transcribe-voice.ts +14 -5
- package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/analytics/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -10
- package/corpus/templates/analytics/app/global.css +0 -31
- package/corpus/templates/analytics/app/lib/sql-query.ts +46 -20
- package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboard-loading-states-now-use-a-simpler-standar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboards-now-load-first-party-charts-reliably-in.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +42 -16
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +4 -2
- package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/chat/README.md +0 -2
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -6
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-07-28-copied-share-links-no-longer-leave-an-unnecessary-confirmati.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-28-playback-skip-controls-no-longer-show-overlapping-numbers.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-28-public-clips-share-buttons-now-use-a-clean-text-only-label.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +14 -17
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +3 -1
- package/corpus/templates/clips/desktop/src/styles.css +13 -10
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +36 -0
- package/corpus/templates/content/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +155 -34
- package/corpus/templates/content/actions/_builder-docs-client.ts +151 -30
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +5 -2
- package/corpus/templates/content/app/root.tsx +35 -14
- package/corpus/templates/content/changelog/2026-07-28-builder-backed-files-media-fields-now-accept-direct-file-url.md +6 -0
- package/corpus/templates/content/changelog/2026-07-28-builder-content-reads-now-negotiate-the-newest-mcp-protocol-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-28-public-content-pages-load-faster-before-the-optional-ask-sid.md +6 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +36 -16
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +42 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +2 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -15
- package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +314 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +88 -3
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +7 -6
- package/corpus/templates/design/app/i18n-data.ts +56 -11
- package/corpus/templates/design/app/i18n-responsive-interact.ts +183 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +60 -0
- package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +89 -0
- package/corpus/templates/design/changelog/2026-07-28-design-edits-can-be-applied-through-the-host-coding-agent-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-show-or-hide-design-editing-chrome-with-cmd-on-apple-devices.md +2 -2
- package/corpus/templates/design/changelog/2026-07-28-visual-edit-now-always-shows-the-live-running-app-in-the-can.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/macros/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/tasks/.agents/skills/security/SKILL.md +13 -2
- package/corpus/toolkit/CHANGELOG.md +13 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/chat-history.css +1 -2
- package/corpus/toolkit/src/composer/TiptapComposer.tsx +45 -0
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +8 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +18 -3
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/types.d.ts +7 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +15 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +39 -16
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +14 -10
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +35 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +2 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +3 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/chat-threads/migrations.d.ts +21 -0
- package/dist/chat-threads/migrations.d.ts.map +1 -0
- package/dist/chat-threads/migrations.js +45 -0
- package/dist/chat-threads/migrations.js.map +1 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +4 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.js +36 -16
- package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +15 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -5
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +37 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +41 -8
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +11 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +19 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +150 -101
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +2 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +11 -4
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/runtime.d.ts.map +1 -1
- package/dist/client/chat/runtime.js +2 -1
- package/dist/client/chat/runtime.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +4 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +14 -8
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +3 -0
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/client-status-requests.d.ts +14 -0
- package/dist/client/client-status-requests.d.ts.map +1 -0
- package/dist/client/client-status-requests.js +126 -0
- package/dist/client/client-status-requests.js.map +1 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +8 -3
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +29 -43
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +11 -11
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +23 -7
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-session.d.ts.map +1 -1
- package/dist/client/use-session.js +4 -4
- package/dist/client/use-session.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +48 -4
- package/dist/db/client.js.map +1 -1
- package/dist/db/ensure-additive-columns.d.ts.map +1 -1
- package/dist/db/ensure-additive-columns.js +32 -21
- package/dist/db/ensure-additive-columns.js.map +1 -1
- package/dist/db/migrations.d.ts +9 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +14 -3
- package/dist/db/migrations.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/approval-store.d.ts +16 -0
- package/dist/mcp/approval-store.d.ts.map +1 -0
- package/dist/mcp/approval-store.js +85 -0
- package/dist/mcp/approval-store.js.map +1 -0
- package/dist/mcp/build-server.d.ts +1 -34
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +220 -23
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.js +1 -1
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/oauth-client-metadata.d.ts +15 -0
- package/dist/mcp/oauth-client-metadata.d.ts.map +1 -0
- package/dist/mcp/oauth-client-metadata.js +297 -0
- package/dist/mcp/oauth-client-metadata.js.map +1 -0
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +39 -53
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp/screen-memory-stdio.js +304 -296
- package/dist/mcp/screen-memory-stdio.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -25
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +24 -133
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +36 -35
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +6 -6
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/oauth-client.d.ts +13 -17
- package/dist/mcp-client/oauth-client.d.ts.map +1 -1
- package/dist/mcp-client/oauth-client.js +74 -16
- package/dist/mcp-client/oauth-client.js.map +1 -1
- package/dist/mcp-client/oauth-routes.d.ts +2 -2
- package/dist/mcp-client/oauth-routes.d.ts.map +1 -1
- package/dist/mcp-client/oauth-routes.js +16 -6
- package/dist/mcp-client/oauth-routes.js.map +1 -1
- package/dist/mcp-client/routes.js +5 -5
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-capabilities.d.ts.map +1 -1
- package/dist/server/agent-capabilities.js +21 -0
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +16 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +11 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/transcribe-voice.js +12 -6
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/dist/templates/chat/README.md +0 -2
- package/dist/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
- package/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
- package/package.json +7 -4
- package/src/a2a/client.ts +18 -4
- package/src/a2a/server.ts +19 -3
- package/src/a2a/types.ts +7 -0
- package/src/action.ts +43 -16
- package/src/agent/engine/ai-sdk-engine.ts +14 -10
- package/src/agent/production-agent.ts +41 -3
- package/src/agent/thread-data-builder.ts +5 -0
- package/src/chat-threads/migrations.ts +53 -0
- package/src/cli/create.ts +4 -0
- package/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
- package/src/cli/skills.ts +16 -0
- package/src/client/AgentPanel.tsx +9 -5
- package/src/client/AssistantChat.tsx +138 -46
- package/src/client/MultiTabAssistantChat.tsx +16 -10
- package/src/client/agent-chat-adapter.ts +21 -3
- package/src/client/analytics.ts +159 -102
- package/src/client/chat/message-components.tsx +12 -5
- package/src/client/chat/runtime.ts +2 -1
- package/src/client/chat/tool-call-display.tsx +25 -2
- package/src/client/chat-model-groups.ts +3 -0
- package/src/client/client-status-requests.ts +161 -0
- package/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
- package/src/client/sse-event-processor.ts +15 -3
- package/src/client/use-agent-engine-configured.ts +54 -50
- package/src/client/use-chat-models.ts +16 -11
- package/src/client/use-chat-threads.ts +32 -9
- package/src/client/use-session.ts +4 -5
- package/src/db/client.ts +83 -8
- package/src/db/ensure-additive-columns.ts +31 -20
- package/src/db/migrations.ts +22 -2
- package/src/mcp/approval-store.ts +99 -0
- package/src/mcp/build-server.ts +579 -303
- package/src/mcp/embed-app.ts +1 -1
- package/src/mcp/oauth-client-metadata.ts +380 -0
- package/src/mcp/oauth-route.ts +57 -52
- package/src/mcp/screen-memory-stdio.ts +400 -378
- package/src/mcp/server.ts +29 -145
- package/src/mcp/stdio.ts +72 -80
- package/src/mcp-client/manager.ts +6 -10
- package/src/mcp-client/oauth-client.ts +127 -36
- package/src/mcp-client/oauth-routes.ts +16 -6
- package/src/mcp-client/routes.ts +6 -5
- package/src/server/agent-capabilities.ts +20 -0
- package/src/server/agent-chat/action-filters-a2a.ts +27 -0
- package/src/server/agent-chat-plugin.ts +18 -0
- package/src/server/transcribe-voice.ts +14 -5
- package/src/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/src/templates/chat/README.md +0 -2
- package/src/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { agentNativePath } from "./api-path.js";
|
|
2
|
+
|
|
3
|
+
export type ClientStatusResult<T> =
|
|
4
|
+
| { state: "available"; value: T }
|
|
5
|
+
| { state: "unavailable"; status?: number };
|
|
6
|
+
|
|
7
|
+
type CacheEntry = {
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
result: ClientStatusResult<unknown>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const RESULT_TTL_MS = 500;
|
|
13
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
14
|
+
const cache = new Map<string, CacheEntry>();
|
|
15
|
+
const requests = new Map<string, Promise<ClientStatusResult<unknown>>>();
|
|
16
|
+
const requestControllers = new Map<string, AbortController>();
|
|
17
|
+
const requestGenerations = new Map<string, number>();
|
|
18
|
+
let invalidationListenersInstalled = false;
|
|
19
|
+
let generation = 0;
|
|
20
|
+
|
|
21
|
+
function expireClientStatusCache(): void {
|
|
22
|
+
generation += 1;
|
|
23
|
+
cache.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function installInvalidationListeners(): void {
|
|
27
|
+
if (
|
|
28
|
+
invalidationListenersInstalled ||
|
|
29
|
+
typeof window === "undefined" ||
|
|
30
|
+
typeof document === "undefined"
|
|
31
|
+
) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
invalidationListenersInstalled = true;
|
|
35
|
+
|
|
36
|
+
if (typeof window.addEventListener === "function") {
|
|
37
|
+
window.addEventListener("focus", expireClientStatusCache);
|
|
38
|
+
window.addEventListener(
|
|
39
|
+
"agent-engine:configured-changed",
|
|
40
|
+
invalidateClientStatusRequests,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (typeof document.addEventListener === "function") {
|
|
44
|
+
document.addEventListener("visibilitychange", () => {
|
|
45
|
+
if (document.visibilityState === "visible") {
|
|
46
|
+
expireClientStatusCache();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function fetchClientStatus<T>(
|
|
53
|
+
path: string,
|
|
54
|
+
): Promise<ClientStatusResult<T>> {
|
|
55
|
+
installInvalidationListeners();
|
|
56
|
+
const url = agentNativePath(path);
|
|
57
|
+
const cached = cache.get(url);
|
|
58
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
59
|
+
return cached.result as ClientStatusResult<T>;
|
|
60
|
+
}
|
|
61
|
+
cache.delete(url);
|
|
62
|
+
|
|
63
|
+
const pending = requests.get(url);
|
|
64
|
+
if (pending) return pending as Promise<ClientStatusResult<T>>;
|
|
65
|
+
|
|
66
|
+
const requestGeneration = generation;
|
|
67
|
+
const requestUrlGeneration = requestGenerations.get(url) ?? 0;
|
|
68
|
+
const controller =
|
|
69
|
+
typeof AbortController === "undefined" ? null : new AbortController();
|
|
70
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
71
|
+
const timeout = new Promise<ClientStatusResult<unknown>>((resolve) => {
|
|
72
|
+
timeoutId = setTimeout(() => {
|
|
73
|
+
controller?.abort();
|
|
74
|
+
resolve({ state: "unavailable" });
|
|
75
|
+
}, REQUEST_TIMEOUT_MS);
|
|
76
|
+
});
|
|
77
|
+
const transport = fetch(url, {
|
|
78
|
+
cache: "no-store",
|
|
79
|
+
credentials: "same-origin",
|
|
80
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
81
|
+
})
|
|
82
|
+
.then(async (response): Promise<ClientStatusResult<unknown>> => {
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
return { state: "unavailable", status: response.status };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return { state: "available", value: await response.json() };
|
|
88
|
+
} catch {
|
|
89
|
+
return { state: "unavailable", status: response.status };
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.catch((): ClientStatusResult<unknown> => ({ state: "unavailable" }));
|
|
93
|
+
const request = Promise.race([transport, timeout])
|
|
94
|
+
.then((result) => {
|
|
95
|
+
if (
|
|
96
|
+
generation === requestGeneration &&
|
|
97
|
+
(requestGenerations.get(url) ?? 0) === requestUrlGeneration &&
|
|
98
|
+
result.state === "available"
|
|
99
|
+
) {
|
|
100
|
+
cache.set(url, {
|
|
101
|
+
expiresAt: Date.now() + RESULT_TTL_MS,
|
|
102
|
+
result,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
})
|
|
107
|
+
.finally(() => {
|
|
108
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
109
|
+
if (requests.get(url) === request) requests.delete(url);
|
|
110
|
+
if (requestControllers.get(url) === controller) {
|
|
111
|
+
requestControllers.delete(url);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
requests.set(url, request);
|
|
116
|
+
if (controller) requestControllers.set(url, controller);
|
|
117
|
+
return request as Promise<ClientStatusResult<T>>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function invalidateClientStatusRequest(path: string): void {
|
|
121
|
+
const url = agentNativePath(path);
|
|
122
|
+
requestGenerations.set(url, (requestGenerations.get(url) ?? 0) + 1);
|
|
123
|
+
cache.delete(url);
|
|
124
|
+
requestControllers.get(url)?.abort();
|
|
125
|
+
requestControllers.delete(url);
|
|
126
|
+
requests.delete(url);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function invalidateClientStatusRequests(): void {
|
|
130
|
+
generation += 1;
|
|
131
|
+
cache.clear();
|
|
132
|
+
for (const controller of requestControllers.values()) {
|
|
133
|
+
controller.abort();
|
|
134
|
+
}
|
|
135
|
+
requestControllers.clear();
|
|
136
|
+
requests.clear();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function fetchAgentEngineStatus<T = unknown>(): Promise<
|
|
140
|
+
ClientStatusResult<T>
|
|
141
|
+
> {
|
|
142
|
+
return fetchClientStatus<T>("/_agent-native/agent-engine/status");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function fetchEnvironmentStatus<T = unknown>(): Promise<
|
|
146
|
+
ClientStatusResult<T>
|
|
147
|
+
> {
|
|
148
|
+
return fetchClientStatus<T>("/_agent-native/env-status");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function fetchBuilderStatus<T = unknown>(): Promise<
|
|
152
|
+
ClientStatusResult<T>
|
|
153
|
+
> {
|
|
154
|
+
return fetchClientStatus<T>("/_agent-native/builder/status");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function fetchAuthSessionStatus<T = unknown>(): Promise<
|
|
158
|
+
ClientStatusResult<T>
|
|
159
|
+
> {
|
|
160
|
+
return fetchClientStatus<T>("/_agent-native/auth/session");
|
|
161
|
+
}
|
|
@@ -627,8 +627,8 @@ export function VoiceTranscriptionSection() {
|
|
|
627
627
|
id="openai"
|
|
628
628
|
selected={transcriptionMode === "batch" && provider === "openai"}
|
|
629
629
|
onSelect={() => chooseBatchProvider("openai")}
|
|
630
|
-
title="OpenAI
|
|
631
|
-
subtitle="Batch
|
|
630
|
+
title="OpenAI Transcribe"
|
|
631
|
+
subtitle="Batch transcription provider. Requires an OpenAI API key."
|
|
632
632
|
rightSlot={
|
|
633
633
|
openAiConfigured === null ? null : openAiConfigured ? (
|
|
634
634
|
<span className="flex items-center gap-1 text-[10px] text-green-500">
|
|
@@ -34,6 +34,13 @@ export type ContentPart =
|
|
|
34
34
|
args: Record<string, string>;
|
|
35
35
|
result?: string;
|
|
36
36
|
isError?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Set when the stream ended while this tool was still in flight. We know
|
|
39
|
+
* it started and NOT whether its side effect landed, so it is deliberately
|
|
40
|
+
* separate from `isError` — an email that WAS delivered must never render
|
|
41
|
+
* as a failure just because the transport dropped before `tool_done`.
|
|
42
|
+
*/
|
|
43
|
+
outcome?: "unknown";
|
|
37
44
|
completedSideEffect?: boolean;
|
|
38
45
|
mcpApp?: AgentMcpAppPayload;
|
|
39
46
|
chatUI?: ActionChatUIConfig;
|
|
@@ -155,7 +162,10 @@ export function settleInterruptedToolCalls(
|
|
|
155
162
|
part.activity === true
|
|
156
163
|
? (options?.activityResult ?? INTERRUPTED_ACTIVITY_RESULT)
|
|
157
164
|
: result;
|
|
158
|
-
|
|
165
|
+
// Interrupted is not failed: the side effect may well have landed. Never
|
|
166
|
+
// set `isError` here — that is reserved for a result the server told us
|
|
167
|
+
// failed.
|
|
168
|
+
part.outcome = "unknown";
|
|
159
169
|
changed = true;
|
|
160
170
|
}
|
|
161
171
|
}
|
|
@@ -1029,7 +1039,8 @@ function completedToolNamesAfterLastAssistantText(
|
|
|
1029
1039
|
part.type === "tool-call" &&
|
|
1030
1040
|
part.activity !== true &&
|
|
1031
1041
|
part.result !== undefined &&
|
|
1032
|
-
part.isError !== true
|
|
1042
|
+
part.isError !== true &&
|
|
1043
|
+
part.outcome !== "unknown"
|
|
1033
1044
|
) {
|
|
1034
1045
|
names.add(part.toolName);
|
|
1035
1046
|
}
|
|
@@ -1053,7 +1064,8 @@ function hasCompletedCustomUi(content: ContentPart[]): boolean {
|
|
|
1053
1064
|
part?.type !== "tool-call" ||
|
|
1054
1065
|
part.activity === true ||
|
|
1055
1066
|
part.result === undefined ||
|
|
1056
|
-
part.isError === true
|
|
1067
|
+
part.isError === true ||
|
|
1068
|
+
part.outcome === "unknown"
|
|
1057
1069
|
) {
|
|
1058
1070
|
continue;
|
|
1059
1071
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { PROVIDER_ENV_VARS } from "../agent/engine/provider-env-vars.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
fetchAgentEngineStatus,
|
|
6
|
+
fetchBuilderStatus,
|
|
7
|
+
fetchEnvironmentStatus,
|
|
8
|
+
invalidateClientStatusRequest,
|
|
9
|
+
type ClientStatusResult,
|
|
10
|
+
} from "./client-status-requests.js";
|
|
5
11
|
|
|
6
12
|
const PROVIDER_ENV_VAR_SET = new Set(PROVIDER_ENV_VARS);
|
|
7
13
|
|
|
@@ -39,43 +45,32 @@ export interface UseAgentEngineConfiguredOptions {
|
|
|
39
45
|
threadId?: string | null;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
// Abort ceiling for a hung request, NOT a deadline the probes race against.
|
|
43
|
-
// `/agent-engine/status` resolves session, org context, a scoped secret and a
|
|
44
|
-
// settings row — several DB round-trips that measured ~4.7s on a warm local
|
|
45
|
-
// dev server, so any budget short enough to "feel fast" just manufactures a
|
|
46
|
-
// fake answer. Losing this ceiling is treated as "don't know", never "missing".
|
|
47
|
-
const DEFAULT_STATUS_CHECK_TIMEOUT_MS = 15000;
|
|
48
48
|
const RETRY_BASE_MS = 2000;
|
|
49
49
|
const RETRY_MAX_MS = 30000;
|
|
50
50
|
|
|
51
|
-
async function
|
|
51
|
+
async function waitForStatus<T>(
|
|
52
|
+
request: Promise<ClientStatusResult<T>>,
|
|
52
53
|
path: string,
|
|
53
|
-
timeoutMs: number,
|
|
54
|
-
): Promise<
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
timeoutMs: number | undefined,
|
|
55
|
+
): Promise<ClientStatusResult<T>> {
|
|
56
|
+
if (timeoutMs === undefined) return request;
|
|
57
|
+
|
|
57
58
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
58
|
-
const timeout = new Promise<
|
|
59
|
+
const timeout = new Promise<ClientStatusResult<T>>((resolve) => {
|
|
59
60
|
timeoutId = setTimeout(() => {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// A request that loses this race may never settle. Evict and abort the
|
|
62
|
+
// shared probe so the scheduled retry starts a genuinely new request.
|
|
63
|
+
invalidateClientStatusRequest(path);
|
|
64
|
+
resolve({ state: "unavailable" });
|
|
62
65
|
}, timeoutMs);
|
|
63
66
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
73
|
-
.catch(() => null)
|
|
74
|
-
.finally(() => {
|
|
75
|
-
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return Promise.race([request, timeout]);
|
|
67
|
+
try {
|
|
68
|
+
return await Promise.race([request, timeout]);
|
|
69
|
+
} finally {
|
|
70
|
+
if (timeoutId !== undefined) {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
function hasConfiguredFlag(value: unknown): value is { configured: boolean } {
|
|
@@ -117,22 +112,38 @@ export async function fetchAgentEngineConfiguredState(
|
|
|
117
112
|
const timeoutMs =
|
|
118
113
|
typeof options?.timeoutMs === "number" && options.timeoutMs > 0
|
|
119
114
|
? options.timeoutMs
|
|
120
|
-
:
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return "
|
|
115
|
+
: undefined;
|
|
116
|
+
const engineResult = await waitForStatus(
|
|
117
|
+
fetchAgentEngineStatus(),
|
|
118
|
+
"/_agent-native/agent-engine/status",
|
|
119
|
+
timeoutMs,
|
|
120
|
+
);
|
|
121
|
+
if (
|
|
122
|
+
engineResult.state === "available" &&
|
|
123
|
+
hasConfiguredFlag(engineResult.value)
|
|
124
|
+
) {
|
|
125
|
+
return engineResult.value.configured ? "configured" : "missing";
|
|
131
126
|
}
|
|
132
127
|
|
|
128
|
+
// Older hosts may not expose the canonical route. Only then pay for the two
|
|
129
|
+
// legacy probes; current hosts answer readiness with one request.
|
|
130
|
+
const [envResult, builderResult] = await Promise.all([
|
|
131
|
+
waitForStatus(
|
|
132
|
+
fetchEnvironmentStatus(),
|
|
133
|
+
"/_agent-native/env-status",
|
|
134
|
+
timeoutMs,
|
|
135
|
+
),
|
|
136
|
+
waitForStatus(
|
|
137
|
+
fetchBuilderStatus(),
|
|
138
|
+
"/_agent-native/builder/status",
|
|
139
|
+
timeoutMs,
|
|
140
|
+
),
|
|
141
|
+
]);
|
|
142
|
+
const envKeys = envResult.state === "available" ? envResult.value : undefined;
|
|
143
|
+
const builderStatus =
|
|
144
|
+
builderResult.state === "available" ? builderResult.value : undefined;
|
|
133
145
|
const envKeysKnown = Array.isArray(envKeys);
|
|
134
146
|
const builderStatusKnown = hasConfiguredFlag(builderStatus);
|
|
135
|
-
const engineStatusKnown = hasConfiguredFlag(engineStatus);
|
|
136
147
|
const keys = envKeysKnown
|
|
137
148
|
? (envKeys as Array<{
|
|
138
149
|
key: string;
|
|
@@ -142,16 +153,9 @@ export async function fetchAgentEngineConfiguredState(
|
|
|
142
153
|
const llmKeys = keys.filter((k) => PROVIDER_ENV_VAR_SET.has(k.key));
|
|
143
154
|
const anyConfigured =
|
|
144
155
|
llmKeys.some((k) => k.configured) ||
|
|
145
|
-
(builderStatusKnown && builderStatus.configured)
|
|
146
|
-
(engineStatusKnown && engineStatus.configured);
|
|
156
|
+
(builderStatusKnown && builderStatus.configured);
|
|
147
157
|
if (anyConfigured) return "configured";
|
|
148
158
|
|
|
149
|
-
// The engine status route is the canonical readiness check: it resolves
|
|
150
|
-
// Builder, scoped BYOK secrets, deployment credentials, and custom engines.
|
|
151
|
-
// Once it has answered `configured: false`, a slow legacy env/Builder status
|
|
152
|
-
// request must not leave the composer permissively stuck in `unknown`.
|
|
153
|
-
if (engineStatusKnown) return "missing";
|
|
154
|
-
|
|
155
159
|
// Compatibility fallback for older hosts without the canonical route.
|
|
156
160
|
return envKeysKnown && builderStatusKnown ? "missing" : "unavailable";
|
|
157
161
|
}
|
|
@@ -7,11 +7,14 @@ import {
|
|
|
7
7
|
resolveReasoningEffortSelection,
|
|
8
8
|
type ReasoningEffort,
|
|
9
9
|
} from "../shared/reasoning-effort.js";
|
|
10
|
-
import { agentNativePath } from "./api-path.js";
|
|
11
10
|
import {
|
|
12
11
|
buildChatModelGroups,
|
|
13
12
|
type EngineModelGroup,
|
|
14
13
|
} from "./chat-model-groups.js";
|
|
14
|
+
import {
|
|
15
|
+
fetchBuilderStatus,
|
|
16
|
+
fetchEnvironmentStatus,
|
|
17
|
+
} from "./client-status-requests.js";
|
|
15
18
|
import { callAction } from "./use-action.js";
|
|
16
19
|
|
|
17
20
|
export type { EngineModelGroup } from "./chat-model-groups.js";
|
|
@@ -192,14 +195,10 @@ export function useChatModels({
|
|
|
192
195
|
callAction("manage-agent-engine" as any, { action: "list" } as any).catch(
|
|
193
196
|
() => null,
|
|
194
197
|
),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
.catch(() => []),
|
|
198
|
-
fetch(agentNativePath("/_agent-native/builder/status"))
|
|
199
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
200
|
-
.catch(() => null),
|
|
198
|
+
fetchEnvironmentStatus<Array<{ key: string; configured: boolean }>>(),
|
|
199
|
+
fetchBuilderStatus<{ configured?: boolean }>(),
|
|
201
200
|
])
|
|
202
|
-
.then(([enginesData,
|
|
201
|
+
.then(([enginesData, envResult, builderResult]) => {
|
|
203
202
|
if (!enginesData?.engines) {
|
|
204
203
|
// Without a catalog the picker keeps an unvalidated DEFAULT_MODEL,
|
|
205
204
|
// which is indistinguishable from a real selection unless we say so.
|
|
@@ -208,10 +207,16 @@ export function useChatModels({
|
|
|
208
207
|
);
|
|
209
208
|
return;
|
|
210
209
|
}
|
|
210
|
+
if (
|
|
211
|
+
envResult.state !== "available" ||
|
|
212
|
+
builderResult.state !== "available"
|
|
213
|
+
) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const envKeys = envResult.value;
|
|
217
|
+
const builderStatus = builderResult.value;
|
|
211
218
|
const configuredKeys = new Set(
|
|
212
|
-
(
|
|
213
|
-
.filter((k) => k.configured)
|
|
214
|
-
.map((k) => k.key),
|
|
219
|
+
envKeys.filter((k) => k.configured).map((k) => k.key),
|
|
215
220
|
);
|
|
216
221
|
const builderConnected = builderStatus?.configured === true;
|
|
217
222
|
const currentEngineName: string | undefined =
|
|
@@ -78,6 +78,35 @@ const ACTIVE_THREAD_KEY = "agent-chat-active-thread";
|
|
|
78
78
|
const THREADS_UPDATED_EVENT = "agent-chat:threads-updated";
|
|
79
79
|
const THREADS_PAGE_SIZE = 50;
|
|
80
80
|
|
|
81
|
+
const sharedThreadListRequests = new Map<
|
|
82
|
+
string,
|
|
83
|
+
Promise<ChatThreadSummary[] | undefined>
|
|
84
|
+
>();
|
|
85
|
+
|
|
86
|
+
async function fetchSharedThreadListPage(
|
|
87
|
+
apiUrl: string,
|
|
88
|
+
offset: number,
|
|
89
|
+
): Promise<ChatThreadSummary[] | undefined> {
|
|
90
|
+
const requestKey = `${apiUrl}\n${offset}`;
|
|
91
|
+
const pending = sharedThreadListRequests.get(requestKey);
|
|
92
|
+
if (pending) return pending;
|
|
93
|
+
|
|
94
|
+
const request = fetch(
|
|
95
|
+
offset > 0 ? `${apiUrl}/threads?offset=${offset}` : `${apiUrl}/threads`,
|
|
96
|
+
)
|
|
97
|
+
.then(async (res) => {
|
|
98
|
+
if (!res.ok) return undefined;
|
|
99
|
+
const data = await res.json();
|
|
100
|
+
const threads = (data.threads ?? []) as ChatThreadSummary[];
|
|
101
|
+
return threads;
|
|
102
|
+
})
|
|
103
|
+
.finally(() => {
|
|
104
|
+
sharedThreadListRequests.delete(requestKey);
|
|
105
|
+
});
|
|
106
|
+
sharedThreadListRequests.set(requestKey, request);
|
|
107
|
+
return request;
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
function emitThreadsUpdated() {
|
|
82
111
|
if (typeof window === "undefined") return;
|
|
83
112
|
window.dispatchEvent(new CustomEvent(THREADS_UPDATED_EVENT));
|
|
@@ -441,20 +470,14 @@ export function useChatThreads(
|
|
|
441
470
|
async (options?: { append?: boolean }) => {
|
|
442
471
|
try {
|
|
443
472
|
const offset = options?.append ? nextThreadsOffsetRef.current : 0;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
? `${apiUrl}/threads?offset=${offset}`
|
|
447
|
-
: `${apiUrl}/threads`,
|
|
448
|
-
);
|
|
449
|
-
if (!res.ok) {
|
|
473
|
+
const loaded = await fetchSharedThreadListPage(apiUrl, offset);
|
|
474
|
+
if (!loaded) {
|
|
450
475
|
if (!options?.append) {
|
|
451
476
|
setThreadsLoadError("Could not load chat history.");
|
|
452
477
|
}
|
|
453
478
|
return;
|
|
454
479
|
}
|
|
455
|
-
const data = await res.json();
|
|
456
480
|
setThreadsLoadError(null);
|
|
457
|
-
const loaded = (data.threads ?? []) as ChatThreadSummary[];
|
|
458
481
|
if (!options?.append) {
|
|
459
482
|
nextThreadsOffsetRef.current = loaded.length;
|
|
460
483
|
} else {
|
|
@@ -526,7 +549,7 @@ export function useChatThreads(
|
|
|
526
549
|
}
|
|
527
550
|
return [...optimisticOnly, ...merged];
|
|
528
551
|
});
|
|
529
|
-
return
|
|
552
|
+
return loaded;
|
|
530
553
|
} catch {
|
|
531
554
|
if (!options?.append) {
|
|
532
555
|
setThreadsLoadError("Could not load chat history.");
|
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|
|
2
2
|
|
|
3
3
|
import type { AuthSession } from "../server/auth.js";
|
|
4
4
|
import { setSentryUser, trackSessionStatus } from "./analytics.js";
|
|
5
|
-
import {
|
|
5
|
+
import { fetchAuthSessionStatus } from "./client-status-requests.js";
|
|
6
6
|
|
|
7
7
|
export type { AuthSession };
|
|
8
8
|
|
|
@@ -51,10 +51,9 @@ function fetchSharedSession(): Promise<AuthSession | null | undefined> {
|
|
|
51
51
|
|
|
52
52
|
sessionRequest = (async () => {
|
|
53
53
|
try {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
const data = await res.json();
|
|
54
|
+
const result = await fetchAuthSessionStatus();
|
|
55
|
+
if (result.state === "unavailable") return undefined;
|
|
56
|
+
const data = result.value as AuthSession & { error?: unknown };
|
|
58
57
|
const session = data.error ? null : (data as AuthSession);
|
|
59
58
|
cachedSession = session;
|
|
60
59
|
cachedSessionAt = Date.now();
|
package/src/db/client.ts
CHANGED
|
@@ -638,6 +638,27 @@ function sqlAndArgs(sql: DbExecStatement): {
|
|
|
638
638
|
: { rawSql: sql.sql, args: sql.args || [] };
|
|
639
639
|
}
|
|
640
640
|
|
|
641
|
+
const POSTGRES_STATEMENT_TIMEOUT_HEADROOM_MS = 250;
|
|
642
|
+
const POSTGRES_MAX_INT = 2_147_483_647;
|
|
643
|
+
|
|
644
|
+
function hasExplicitDbTimeout(statement: DbExecStatement): boolean {
|
|
645
|
+
if (typeof statement === "string") return false;
|
|
646
|
+
const timeoutMs = Number(statement.timeoutMs);
|
|
647
|
+
return Number.isFinite(timeoutMs) && timeoutMs > 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function postgresStatementTimeoutMs(clientTimeoutMs: number): number {
|
|
651
|
+
const safeTimeoutMs = Math.max(
|
|
652
|
+
1,
|
|
653
|
+
Math.min(POSTGRES_MAX_INT, Math.floor(clientTimeoutMs)),
|
|
654
|
+
);
|
|
655
|
+
const headroomMs = Math.min(
|
|
656
|
+
POSTGRES_STATEMENT_TIMEOUT_HEADROOM_MS,
|
|
657
|
+
Math.max(1, Math.floor(safeTimeoutMs * 0.1)),
|
|
658
|
+
);
|
|
659
|
+
return Math.max(1, safeTimeoutMs - headroomMs);
|
|
660
|
+
}
|
|
661
|
+
|
|
641
662
|
export function dbExecQueryBudget(statement: DbExecStatement): {
|
|
642
663
|
timeoutMs: number;
|
|
643
664
|
maxAttempts: number;
|
|
@@ -1229,6 +1250,36 @@ async function createDbExecInternal(
|
|
|
1229
1250
|
rowsAffected: result.rowCount ?? 0,
|
|
1230
1251
|
};
|
|
1231
1252
|
}
|
|
1253
|
+
async function queryNeonClientWithStatementTimeout(
|
|
1254
|
+
client: any,
|
|
1255
|
+
sql: Parameters<DbExec["execute"]>[0],
|
|
1256
|
+
remainingMs: () => number,
|
|
1257
|
+
markClientForDiscard: () => void,
|
|
1258
|
+
) {
|
|
1259
|
+
const statementTimeoutMs = postgresStatementTimeoutMs(remainingMs());
|
|
1260
|
+
await queryNeonClient(
|
|
1261
|
+
client,
|
|
1262
|
+
`SET statement_timeout = ${statementTimeoutMs}`,
|
|
1263
|
+
remainingMs(),
|
|
1264
|
+
);
|
|
1265
|
+
try {
|
|
1266
|
+
return await queryNeonClient(client, sql, remainingMs());
|
|
1267
|
+
} finally {
|
|
1268
|
+
try {
|
|
1269
|
+
await queryNeonClient(
|
|
1270
|
+
client,
|
|
1271
|
+
"RESET statement_timeout",
|
|
1272
|
+
remainingMs(),
|
|
1273
|
+
);
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
markClientForDiscard();
|
|
1276
|
+
console.warn(
|
|
1277
|
+
"[db/neon] statement timeout reset failed; discarding connection:",
|
|
1278
|
+
err instanceof Error ? err.message : err,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1232
1283
|
return {
|
|
1233
1284
|
async execute(sql) {
|
|
1234
1285
|
const { timeoutMs, maxAttempts } = dbExecQueryBudget(sql);
|
|
@@ -1276,17 +1327,25 @@ async function createDbExecInternal(
|
|
|
1276
1327
|
released = true;
|
|
1277
1328
|
client.release(err);
|
|
1278
1329
|
};
|
|
1330
|
+
let discardClient = false;
|
|
1279
1331
|
|
|
1280
1332
|
try {
|
|
1281
|
-
const result =
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1333
|
+
const result = hasExplicitDbTimeout(sql)
|
|
1334
|
+
? await queryNeonClientWithStatementTimeout(
|
|
1335
|
+
client,
|
|
1336
|
+
sql,
|
|
1337
|
+
remainingAttemptMs,
|
|
1338
|
+
() => {
|
|
1339
|
+
discardClient = true;
|
|
1340
|
+
},
|
|
1341
|
+
)
|
|
1342
|
+
: await queryNeonClient(client, sql, remainingAttemptMs());
|
|
1343
|
+
releaseClient(discardClient ? true : undefined);
|
|
1287
1344
|
return result;
|
|
1288
1345
|
} catch (err) {
|
|
1289
|
-
releaseClient(
|
|
1346
|
+
releaseClient(
|
|
1347
|
+
discardClient || isConnectionError(err) ? true : undefined,
|
|
1348
|
+
);
|
|
1290
1349
|
throw err;
|
|
1291
1350
|
}
|
|
1292
1351
|
}, maxAttempts);
|
|
@@ -1317,7 +1376,23 @@ async function createDbExecInternal(
|
|
|
1317
1376
|
client.release(err);
|
|
1318
1377
|
};
|
|
1319
1378
|
const tx: DbExec = {
|
|
1320
|
-
execute: (sql) =>
|
|
1379
|
+
execute: async (sql) => {
|
|
1380
|
+
if (!hasExplicitDbTimeout(sql)) {
|
|
1381
|
+
return queryNeonClient(client, sql);
|
|
1382
|
+
}
|
|
1383
|
+
const { timeoutMs } = dbExecQueryBudget(sql);
|
|
1384
|
+
const startedAt = Date.now();
|
|
1385
|
+
const remainingMs = () =>
|
|
1386
|
+
Math.max(1, timeoutMs - (Date.now() - startedAt));
|
|
1387
|
+
const statementTimeoutMs =
|
|
1388
|
+
postgresStatementTimeoutMs(remainingMs());
|
|
1389
|
+
await queryNeonClient(
|
|
1390
|
+
client,
|
|
1391
|
+
`SET LOCAL statement_timeout = ${statementTimeoutMs}`,
|
|
1392
|
+
remainingMs(),
|
|
1393
|
+
);
|
|
1394
|
+
return queryNeonClient(client, sql, remainingMs());
|
|
1395
|
+
},
|
|
1321
1396
|
};
|
|
1322
1397
|
try {
|
|
1323
1398
|
await queryNeonClient(client, "BEGIN");
|