@agent-native/core 0.131.7 → 0.132.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 +123 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
- package/corpus/core/docs/design/durable-agent-runs.md +15 -11
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +127 -10
- package/corpus/core/src/a2a/index.ts +3 -0
- package/corpus/core/src/agent/durable-background.ts +38 -28
- package/corpus/core/src/agent/harness/background.ts +12 -2
- package/corpus/core/src/agent/production-agent.ts +18 -15
- package/corpus/core/src/agent/run-store.ts +45 -3
- package/corpus/core/src/application-state/handlers.ts +45 -0
- package/corpus/core/src/application-state/index.ts +3 -0
- package/corpus/core/src/application-state/store.ts +38 -34
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
- package/corpus/core/src/client/agent-chat.ts +4 -9
- package/corpus/core/src/client/application-state.ts +115 -12
- package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/client/guided-questions.tsx +12 -23
- package/corpus/core/src/db/client.ts +148 -28
- package/corpus/core/src/db/create-get-db.ts +39 -6
- package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
- package/corpus/core/src/deploy/build.ts +26 -33
- package/corpus/core/src/feature-flags/store.ts +10 -5
- package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
- package/corpus/core/src/integrations/config-store.ts +13 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
- package/corpus/core/src/mcp-client/routes.ts +33 -2
- package/corpus/core/src/onboarding/plugin.ts +31 -25
- package/corpus/core/src/org/accept-pending.ts +2 -0
- package/corpus/core/src/org/auto-join-domain.ts +2 -0
- package/corpus/core/src/org/context.ts +35 -10
- package/corpus/core/src/org/handlers.ts +6 -0
- package/corpus/core/src/org/request-org-cache.ts +71 -0
- package/corpus/core/src/resources/store.ts +10 -9
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/secrets/storage.ts +97 -5
- package/corpus/core/src/server/action-change.ts +33 -0
- package/corpus/core/src/server/action-routes.ts +11 -9
- package/corpus/core/src/server/agent-teams.ts +10 -2
- package/corpus/core/src/server/better-auth-instance.ts +39 -12
- package/corpus/core/src/server/core-routes-plugin.ts +19 -2
- package/corpus/core/src/server/credential-provider.ts +57 -14
- package/corpus/core/src/server/http-response-telemetry.ts +1 -0
- package/corpus/core/src/server/poll.ts +95 -57
- package/corpus/core/src/server/voice-providers-status.ts +19 -4
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
- package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +25 -1
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
- package/dist/a2a/activity.d.ts +22 -4
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +97 -11
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/durable-background.d.ts +13 -8
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +36 -28
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/harness/background.d.ts.map +1 -1
- package/dist/agent/harness/background.js +12 -2
- package/dist/agent/harness/background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +2 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +44 -3
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/handlers.d.ts +15 -0
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +42 -2
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/store.d.ts +11 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +37 -40
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/redact.d.ts +25 -2
- package/dist/audit/redact.d.ts.map +1 -1
- package/dist/audit/redact.js +85 -21
- package/dist/audit/redact.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +11 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +20 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +4 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/application-state.d.ts +11 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +87 -9
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +4 -13
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -22
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +57 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +122 -31
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +31 -7
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/runtime-diagnostics.d.ts +1 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -1
- package/dist/db/runtime-diagnostics.js +31 -6
- package/dist/db/runtime-diagnostics.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -20
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +18 -30
- package/dist/deploy/build.js.map +1 -1
- package/dist/feature-flags/store.d.ts.map +1 -1
- package/dist/feature-flags/store.js +10 -5
- package/dist/feature-flags/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +12 -0
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +11 -0
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +19 -2
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +30 -2
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +12 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +2 -0
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +2 -0
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +29 -9
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +5 -0
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/request-org-cache.d.ts +16 -0
- package/dist/org/request-org-cache.d.ts.map +1 -0
- package/dist/org/request-org-cache.js +62 -0
- package/dist/org/request-org-cache.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +11 -9
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +3 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +82 -5
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +20 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +29 -0
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +7 -8
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +31 -14
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +18 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +16 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +49 -13
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +1 -0
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/poll.d.ts +6 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +84 -47
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +12 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +3 -3
- package/src/a2a/activity.ts +127 -10
- package/src/a2a/index.ts +3 -0
- package/src/agent/durable-background.ts +38 -28
- package/src/agent/harness/background.ts +12 -2
- package/src/agent/production-agent.ts +18 -15
- package/src/agent/run-store.ts +45 -3
- package/src/application-state/handlers.ts +45 -0
- package/src/application-state/index.ts +3 -0
- package/src/application-state/store.ts +38 -34
- package/src/audit/redact.ts +104 -18
- package/src/cli/create.ts +115 -16
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/MultiTabAssistantChat.tsx +3 -0
- package/src/client/RunStuckBanner.tsx +29 -6
- package/src/client/agent-chat.ts +4 -9
- package/src/client/application-state.ts +115 -12
- package/src/client/dynamic-suggestions.ts +4 -12
- package/src/client/frame-protocol.ts +5 -1
- package/src/client/guided-questions.tsx +12 -23
- package/src/db/client.ts +148 -28
- package/src/db/create-get-db.ts +39 -6
- package/src/db/runtime-diagnostics.ts +41 -6
- package/src/deploy/build.ts +26 -33
- package/src/feature-flags/store.ts +10 -5
- package/src/integrations/a2a-continuations-store.ts +11 -0
- package/src/integrations/config-store.ts +13 -0
- package/src/integrations/google-docs-poller.ts +26 -2
- package/src/mcp-client/routes.ts +33 -2
- package/src/onboarding/plugin.ts +31 -25
- package/src/org/accept-pending.ts +2 -0
- package/src/org/auto-join-domain.ts +2 -0
- package/src/org/context.ts +35 -10
- package/src/org/handlers.ts +6 -0
- package/src/org/request-org-cache.ts +71 -0
- package/src/resources/store.ts +10 -9
- package/src/scripts/runner.ts +6 -3
- package/src/secrets/storage.ts +97 -5
- package/src/server/action-change.ts +33 -0
- package/src/server/action-routes.ts +11 -9
- package/src/server/agent-teams.ts +10 -2
- package/src/server/better-auth-instance.ts +39 -12
- package/src/server/core-routes-plugin.ts +19 -2
- package/src/server/credential-provider.ts +57 -14
- package/src/server/http-response-telemetry.ts +1 -0
- package/src/server/poll.ts +95 -57
- package/src/server/voice-providers-status.ts +19 -4
- package/src/workspace-files/tool.ts +18 -11
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
MCP_EMBED_CORS_ALLOW_HEADERS,
|
|
22
22
|
shouldAllowMcpEmbedCredentials,
|
|
23
23
|
} from "../shared/mcp-embed-headers.js";
|
|
24
|
-
import { notifyActionChange } from "./action-change.js";
|
|
24
|
+
import { actionCallIsReadOnly, notifyActionChange } from "./action-change.js";
|
|
25
25
|
import {
|
|
26
26
|
seedAgentRunOwnerContext,
|
|
27
27
|
type AgentRunOwnerContext,
|
|
@@ -603,14 +603,16 @@ export function mountActionRoutes(
|
|
|
603
603
|
// their action queries. The calling tab already refetches via
|
|
604
604
|
// useActionMutation's onSuccess, so this is mainly cross-tab
|
|
605
605
|
// sync (and parity with the agent's tool-call path).
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
606
|
+
// A per-call Plan-mode effect wins over entry.readOnly, which
|
|
607
|
+
// wins (true OR false) over the method heuristic. defineAction
|
|
608
|
+
// already auto-infers GET → readOnly=true, so for actions
|
|
609
|
+
// registered through that path entry.readOnly is always set and
|
|
610
|
+
// the fallback just guards legacy wrap paths.
|
|
611
|
+
const isReadOnly = actionCallIsReadOnly(
|
|
612
|
+
entry,
|
|
613
|
+
params,
|
|
614
|
+
method === "GET",
|
|
615
|
+
);
|
|
614
616
|
if (!isReadOnly) {
|
|
615
617
|
try {
|
|
616
618
|
await notifyActionChange({
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
listAppState,
|
|
58
58
|
deleteAppState,
|
|
59
59
|
} from "../application-state/script-helpers.js";
|
|
60
|
+
import { redactArgsToValue, redactTextToSummary } from "../audit/redact.js";
|
|
60
61
|
import { createThread } from "../chat-threads/store.js";
|
|
61
62
|
import type {
|
|
62
63
|
BackgroundAgentRun,
|
|
@@ -1112,13 +1113,20 @@ function summarizeAgentChatEvent(event: RunEvent): {
|
|
|
1112
1113
|
return {
|
|
1113
1114
|
kind: "status",
|
|
1114
1115
|
message: `Running ${payload.tool}`,
|
|
1115
|
-
metadata: {
|
|
1116
|
+
metadata: {
|
|
1117
|
+
tool: payload.tool,
|
|
1118
|
+
input: redactArgsToValue(payload.input),
|
|
1119
|
+
},
|
|
1116
1120
|
};
|
|
1117
1121
|
case "tool_done":
|
|
1118
1122
|
return {
|
|
1119
1123
|
kind: "artifact",
|
|
1120
1124
|
message: payload.result,
|
|
1121
|
-
metadata: {
|
|
1125
|
+
metadata: {
|
|
1126
|
+
tool: payload.tool,
|
|
1127
|
+
input: redactArgsToValue(payload.input),
|
|
1128
|
+
result: redactTextToSummary(payload.result),
|
|
1129
|
+
},
|
|
1122
1130
|
};
|
|
1123
1131
|
case "agent_task":
|
|
1124
1132
|
return {
|
|
@@ -38,6 +38,9 @@ import {
|
|
|
38
38
|
pgPoolOptions,
|
|
39
39
|
neonPoolMax,
|
|
40
40
|
attachNeonPoolErrorLogger,
|
|
41
|
+
sharedDbPool,
|
|
42
|
+
onSharedDbPoolsClosed,
|
|
43
|
+
onSharedDbPoolReplaced,
|
|
41
44
|
} from "../db/client.js";
|
|
42
45
|
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
43
46
|
import { saveOAuthTokens } from "../oauth-tokens/store.js";
|
|
@@ -808,15 +811,34 @@ export async function getBetterAuthInternalAdapter(
|
|
|
808
811
|
export async function resetBetterAuth(): Promise<void> {
|
|
809
812
|
_auth = undefined;
|
|
810
813
|
_initPromise = undefined;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
814
|
+
// The Postgres pool belongs to the process (see `sharedDbPool`), not to Better
|
|
815
|
+
// Auth — ending it here would take the framework's and every store's database
|
|
816
|
+
// access down with it. `closeDbExec()` owns that.
|
|
817
|
+
_neonAuthPool = undefined;
|
|
818
|
+
await closePgliteClients();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// A `closeDbExec()` releases the pool this instance's adapter is bound to, so
|
|
822
|
+
// the next `getAuth()` must build a fresh one. Registered from the pooled
|
|
823
|
+
// branches rather than at module load: core's specs widely mock
|
|
824
|
+
// `db/client.js`, and an import-time call into the mock breaks every one of
|
|
825
|
+
// them that doesn't stub this export.
|
|
826
|
+
let _poolCloseHookRegistered = false;
|
|
827
|
+
function resetAuthOnPoolClose(driver?: string, url?: string): void {
|
|
828
|
+
if (_poolCloseHookRegistered) return;
|
|
829
|
+
_poolCloseHookRegistered = true;
|
|
830
|
+
onSharedDbPoolsClosed(() => {
|
|
831
|
+
_auth = undefined;
|
|
832
|
+
_initPromise = undefined;
|
|
817
833
|
_neonAuthPool = undefined;
|
|
834
|
+
});
|
|
835
|
+
if (driver && url) {
|
|
836
|
+
onSharedDbPoolReplaced(driver, url, () => {
|
|
837
|
+
_auth = undefined;
|
|
838
|
+
_initPromise = undefined;
|
|
839
|
+
_neonAuthPool = undefined;
|
|
840
|
+
});
|
|
818
841
|
}
|
|
819
|
-
await closePgliteClients();
|
|
820
842
|
}
|
|
821
843
|
|
|
822
844
|
// ---------------------------------------------------------------------------
|
|
@@ -1160,10 +1182,12 @@ async function buildDatabaseConfig(
|
|
|
1160
1182
|
// session lookup on essentially every authenticated request, so an
|
|
1161
1183
|
// un-capped pool here is a primary contributor to "Max client
|
|
1162
1184
|
// connections reached" across concurrent serverless instances.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1185
|
+
resetAuthOnPoolClose("neon", url);
|
|
1186
|
+
_neonAuthPool = sharedDbPool(
|
|
1187
|
+
"neon",
|
|
1188
|
+
url,
|
|
1189
|
+
() => new Pool({ connectionString: url, max: neonPoolMax() }),
|
|
1190
|
+
);
|
|
1167
1191
|
attachNeonPoolErrorLogger(_neonAuthPool, "db/neon-auth");
|
|
1168
1192
|
const { drizzle } = await import("drizzle-orm/neon-serverless");
|
|
1169
1193
|
const db = drizzle(buildResilientNeonPool(_neonAuthPool), {
|
|
@@ -1182,7 +1206,10 @@ async function buildDatabaseConfig(
|
|
|
1182
1206
|
// un-capped pool here is a primary contributor to "Max client connections
|
|
1183
1207
|
// reached" across concurrent serverless instances.
|
|
1184
1208
|
const { default: postgres } = await import("postgres");
|
|
1185
|
-
|
|
1209
|
+
resetAuthOnPoolClose("postgres-js", url);
|
|
1210
|
+
const sql = sharedDbPool("postgres-js", url, () =>
|
|
1211
|
+
postgres(url, pgPoolOptions(url)),
|
|
1212
|
+
);
|
|
1186
1213
|
const { drizzle } = await import("drizzle-orm/postgres-js");
|
|
1187
1214
|
const db = drizzle(buildResilientPostgresJsClient(sql), {
|
|
1188
1215
|
schema: pgAuthSchema,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
putComposeDraft,
|
|
47
47
|
deleteComposeDraft,
|
|
48
48
|
deleteAllComposeDrafts,
|
|
49
|
+
getStateMany,
|
|
49
50
|
} from "../application-state/handlers.js";
|
|
50
51
|
import { mountBrowserSessionRoutes } from "../browser-sessions/routes.js";
|
|
51
52
|
import { mountDbAdminRoutes } from "../db-admin/routes.js";
|
|
@@ -152,6 +153,7 @@ import {
|
|
|
152
153
|
import type { EnvKeyConfig } from "./create-server.js";
|
|
153
154
|
import {
|
|
154
155
|
canUseDeployCredentialFallbackForRequest,
|
|
156
|
+
prefetchSecrets,
|
|
155
157
|
readDeployCredentialEnv,
|
|
156
158
|
resolveSecret,
|
|
157
159
|
} from "./credential-provider.js";
|
|
@@ -1286,6 +1288,7 @@ export async function readLegacyCoreRouteInitSettings(
|
|
|
1286
1288
|
* GET /_agent-native/health — DB liveness probe + scale-to-zero warmup
|
|
1287
1289
|
* GET /_agent-native/env-status — env key configuration status (when envKeys provided)
|
|
1288
1290
|
* POST /_agent-native/env-vars — compatibility route that saves keys to scoped DB secrets
|
|
1291
|
+
* GET /_agent-native/application-state?keys=a,b,c — batched read of many keys
|
|
1289
1292
|
* GET /_agent-native/application-state/:key — read application state
|
|
1290
1293
|
* PUT /_agent-native/application-state/:key — write application state
|
|
1291
1294
|
* DELETE /_agent-native/application-state/:key — delete application state
|
|
@@ -3322,10 +3325,17 @@ export function createCoreRoutesPlugin(
|
|
|
3322
3325
|
/* org module not present in this template */
|
|
3323
3326
|
}
|
|
3324
3327
|
}
|
|
3328
|
+
// One context for the whole sweep so the per-request secret memo is
|
|
3329
|
+
// shared, and one batched read per scope to fill it. Without this
|
|
3330
|
+
// every key pays its own four-scope waterfall.
|
|
3331
|
+
const requestContext = { userEmail, orgId };
|
|
3332
|
+
await runWithRequestContext(requestContext, () =>
|
|
3333
|
+
prefetchSecrets(allowedEnvKeyNames),
|
|
3334
|
+
);
|
|
3325
3335
|
return Promise.all(
|
|
3326
3336
|
envKeys.map(async (cfg) => {
|
|
3327
3337
|
const configured = await runWithRequestContext(
|
|
3328
|
-
|
|
3338
|
+
requestContext,
|
|
3329
3339
|
() => resolveSecret(cfg.key).then(Boolean),
|
|
3330
3340
|
);
|
|
3331
3341
|
return {
|
|
@@ -4245,7 +4255,14 @@ export function createCoreRoutesPlugin(
|
|
|
4245
4255
|
(event.url?.pathname || "").replace(/^\/+/, "").split("/")[0] ||
|
|
4246
4256
|
"";
|
|
4247
4257
|
// Skip — compose handler above already handled it
|
|
4248
|
-
if (key === "compose"
|
|
4258
|
+
if (key === "compose") return;
|
|
4259
|
+
// Collection root: `GET ?keys=a,b,c` batches many single-key reads
|
|
4260
|
+
// into one request (and one identity resolution) — the chat rail
|
|
4261
|
+
// alone reads ~6 keys on every mount.
|
|
4262
|
+
if (key === "") {
|
|
4263
|
+
if (getMethod(event) === "GET") return getStateMany(event);
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4249
4266
|
if (event.context) {
|
|
4250
4267
|
event.context.params = { ...event.context.params, key };
|
|
4251
4268
|
}
|
|
@@ -1276,11 +1276,50 @@ export async function deleteBuilderCredentials(
|
|
|
1276
1276
|
// ANTHROPIC_API_KEY, but Builder identity keys keep the stricter scoped policy.
|
|
1277
1277
|
// ---------------------------------------------------------------------------
|
|
1278
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* Warm this request's secret memo for many keys with one read per scope.
|
|
1281
|
+
*
|
|
1282
|
+
* `resolveSecret` walks four scopes per key, so a status endpoint asking about
|
|
1283
|
+
* a dozen keys costs ~50 round trips against a remote database. Reading each
|
|
1284
|
+
* scope once for the whole key set collapses that to four, and the subsequent
|
|
1285
|
+
* `resolveSecret` calls answer from the per-request memo — same precedence,
|
|
1286
|
+
* same identity scoping, no new cache to invalidate.
|
|
1287
|
+
*
|
|
1288
|
+
* Best-effort on purpose: `readAppSecrets` only memoizes keys a statement
|
|
1289
|
+
* actually covered, so a failure here leaves each key's own lookup to run and
|
|
1290
|
+
* report the failure. It must never turn an unreadable store into "not set".
|
|
1291
|
+
*/
|
|
1292
|
+
export async function prefetchSecrets(keys: readonly string[]): Promise<void> {
|
|
1293
|
+
const email = getRequestUserEmail();
|
|
1294
|
+
if (!email || keys.length === 0) return;
|
|
1295
|
+
const { readAppSecrets } = await import("../secrets/storage.js");
|
|
1296
|
+
const orgId =
|
|
1297
|
+
getRequestOrgId() || (await resolveOrgIdForRequestEmail(email)).orgId;
|
|
1298
|
+
const scopes: Array<{
|
|
1299
|
+
scope: "user" | "org" | "workspace";
|
|
1300
|
+
scopeId: string;
|
|
1301
|
+
}> = [
|
|
1302
|
+
{ scope: "user", scopeId: email },
|
|
1303
|
+
...(orgId
|
|
1304
|
+
? ([
|
|
1305
|
+
{ scope: "org", scopeId: orgId },
|
|
1306
|
+
{ scope: "workspace", scopeId: orgId },
|
|
1307
|
+
] as const)
|
|
1308
|
+
: []),
|
|
1309
|
+
{ scope: "workspace", scopeId: `solo:${email}` },
|
|
1310
|
+
];
|
|
1311
|
+
await Promise.all(
|
|
1312
|
+
scopes.map((s) => readAppSecrets({ keys, ...s }).catch(() => undefined)),
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1279
1316
|
/**
|
|
1280
1317
|
* Resolve a request-scoped secret. Reads from `app_secrets` first (current
|
|
1281
1318
|
* user override, active org, workspace row for that org, then the solo
|
|
1282
1319
|
* workspace row); falls back to `process.env` only when the deploy fallback
|
|
1283
1320
|
* policy allows it.
|
|
1321
|
+
*
|
|
1322
|
+
* Resolving several keys in one request? Call `prefetchSecrets` first.
|
|
1284
1323
|
*/
|
|
1285
1324
|
export async function resolveSecret(key: string): Promise<string | null> {
|
|
1286
1325
|
const resolved = await resolveSecretDetailed(key);
|
|
@@ -1307,6 +1346,7 @@ export async function resolveSecretDetailed(
|
|
|
1307
1346
|
if (email) {
|
|
1308
1347
|
try {
|
|
1309
1348
|
const { readAppSecret } = await import("../secrets/storage.js");
|
|
1349
|
+
|
|
1310
1350
|
// Per-user override first.
|
|
1311
1351
|
const userSecret = await readAppSecret({
|
|
1312
1352
|
key,
|
|
@@ -1333,14 +1373,25 @@ export async function resolveSecretDetailed(
|
|
|
1333
1373
|
orgId = resolved.orgId;
|
|
1334
1374
|
}
|
|
1335
1375
|
|
|
1376
|
+
if (lookupFailed) {
|
|
1377
|
+
return { value: null, lookupFailed: true, cause };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1336
1380
|
if (orgId) {
|
|
1381
|
+
// These rows are independent once the org is known, so read them in
|
|
1382
|
+
// parallel while still applying precedence below in the same order.
|
|
1383
|
+
const [orgRead, workspaceRead] = await Promise.allSettled([
|
|
1384
|
+
readAppSecret({ key, scope: "org", scopeId: orgId }),
|
|
1385
|
+
readAppSecret({ key, scope: "workspace", scopeId: orgId }),
|
|
1386
|
+
]);
|
|
1387
|
+
const unwrap = <T>(settled: PromiseSettledResult<T>): T => {
|
|
1388
|
+
if (settled.status === "rejected") throw settled.reason;
|
|
1389
|
+
return settled.value;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1337
1392
|
// Fall back to the active org's shared row, when present. Builder
|
|
1338
1393
|
// Connect uses this first-class org scope.
|
|
1339
|
-
const orgSecret =
|
|
1340
|
-
key,
|
|
1341
|
-
scope: "org",
|
|
1342
|
-
scopeId: orgId,
|
|
1343
|
-
});
|
|
1394
|
+
const orgSecret = unwrap(orgRead);
|
|
1344
1395
|
if (orgSecret?.value) {
|
|
1345
1396
|
if (traceLookup) {
|
|
1346
1397
|
console.log(
|
|
@@ -1353,11 +1404,7 @@ export async function resolveSecretDetailed(
|
|
|
1353
1404
|
// Registered secrets historically used "workspace" scope for
|
|
1354
1405
|
// org-shared configuration. Keep reading it so Settings status and
|
|
1355
1406
|
// runtime resolution agree.
|
|
1356
|
-
const workspaceSecret =
|
|
1357
|
-
key,
|
|
1358
|
-
scope: "workspace",
|
|
1359
|
-
scopeId: orgId,
|
|
1360
|
-
});
|
|
1407
|
+
const workspaceSecret = unwrap(workspaceRead);
|
|
1361
1408
|
if (workspaceSecret?.value) {
|
|
1362
1409
|
if (traceLookup) {
|
|
1363
1410
|
console.log(
|
|
@@ -1368,10 +1415,6 @@ export async function resolveSecretDetailed(
|
|
|
1368
1415
|
}
|
|
1369
1416
|
}
|
|
1370
1417
|
|
|
1371
|
-
if (lookupFailed) {
|
|
1372
|
-
return { value: null, lookupFailed: true, cause };
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
1418
|
// Solo-workspace fallback: always checked, even when an org id was found
|
|
1376
1419
|
// above. A secret written before the user joined/created an org lives
|
|
1377
1420
|
// here, and must not become unreachable once that org exists. It stays
|
|
@@ -367,6 +367,7 @@ export function installHttpResponseTelemetryHooks(nitroApp: any): void {
|
|
|
367
367
|
);
|
|
368
368
|
}
|
|
369
369
|
if (state.db.operationCount > 0) {
|
|
370
|
+
appendServerTiming(response, event, "db-ops", state.db.operationCount);
|
|
370
371
|
appendServerTiming(response, event, "db", state.db.operationWallMs);
|
|
371
372
|
appendServerTiming(
|
|
372
373
|
response,
|
package/src/server/poll.ts
CHANGED
|
@@ -442,6 +442,12 @@ export class AppSyncState {
|
|
|
442
442
|
private lastExtensionsUpdatedAt: string | number | undefined;
|
|
443
443
|
private lastExtensionMarkerTs = 0;
|
|
444
444
|
private lastActionMarkerTs = 0;
|
|
445
|
+
/**
|
|
446
|
+
* Set by the seed when a durable action marker predates this process, so the
|
|
447
|
+
* first `doCheckExternalDbChanges` reads the marker rows even though the
|
|
448
|
+
* MAX(updated_at) probe cannot distinguish that row from the seeded max.
|
|
449
|
+
*/
|
|
450
|
+
private replayActionMarkerOnce = false;
|
|
445
451
|
|
|
446
452
|
/**
|
|
447
453
|
* Tracks the latest updated_at seen on the `__screen_refresh__` key.
|
|
@@ -1355,6 +1361,7 @@ export class AppSyncState {
|
|
|
1355
1361
|
// performed by a separate action process. Do not baseline past an existing
|
|
1356
1362
|
// marker on cold start, or the first poll after the action will miss it.
|
|
1357
1363
|
this.lastActionMarkerTs = 0;
|
|
1364
|
+
this.replayActionMarkerOnce = actionMarkerTs > 0;
|
|
1358
1365
|
this.lastScreenRefreshTs = refreshTs;
|
|
1359
1366
|
this.lastScreenRefreshTsBySession.clear();
|
|
1360
1367
|
for (const row of refreshResult.rows) {
|
|
@@ -1416,28 +1423,54 @@ export class AppSyncState {
|
|
|
1416
1423
|
// concurrently to shave stacked latency; results are still processed in
|
|
1417
1424
|
// the original sequential order, and conditional follow-up queries stay
|
|
1418
1425
|
// sequential within their branch.
|
|
1426
|
+
//
|
|
1427
|
+
// `lastAppStateTs` is maintained as MAX(updated_at) over the whole
|
|
1428
|
+
// `application_state` table (seeded in `seedVersionFromDb`, advanced by
|
|
1429
|
+
// the scan below over every key, not just the ones that emit events).
|
|
1430
|
+
// The application-state row scan and screen/extension marker reads only
|
|
1431
|
+
// report rows strictly newer than a watermark that is itself <=
|
|
1432
|
+
// `lastAppStateTs`, so an unadvanced MAX proves those reads are no-ops.
|
|
1433
|
+
// The action marker keeps its own max probe because its watermark must
|
|
1434
|
+
// remain independent under cross-process clock skew.
|
|
1419
1435
|
const [
|
|
1420
|
-
|
|
1436
|
+
appStateMaxTs,
|
|
1421
1437
|
actionMarkerTs,
|
|
1422
|
-
refreshResult,
|
|
1423
|
-
extensionMarkerTs,
|
|
1424
1438
|
settingsTs,
|
|
1425
1439
|
extensionsMaxUpdatedAt,
|
|
1426
1440
|
] = await Promise.all([
|
|
1427
|
-
db
|
|
1428
|
-
sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
|
|
1429
|
-
args: [this.lastAppStateTs],
|
|
1430
|
-
}),
|
|
1441
|
+
readMaxUpdatedAt(db, "application_state"),
|
|
1431
1442
|
readActionMarkerMaxUpdatedAt(db),
|
|
1432
|
-
db.execute({
|
|
1433
|
-
sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
|
|
1434
|
-
args: [SCREEN_REFRESH_KEY],
|
|
1435
|
-
}),
|
|
1436
|
-
readExtensionMarkerMaxUpdatedAt(db),
|
|
1437
1443
|
readMaxUpdatedAt(db, "settings"),
|
|
1438
1444
|
readMaxUpdatedAtRaw(db, "tools"),
|
|
1439
1445
|
]);
|
|
1440
1446
|
|
|
1447
|
+
// The seed deliberately rewinds `lastActionMarkerTs` to 0 so a marker
|
|
1448
|
+
// written before this process booted still reaches the first poll. That
|
|
1449
|
+
// replay is invisible to the MAX probe (the row is already counted in the
|
|
1450
|
+
// seeded max), so it gets one forced full read.
|
|
1451
|
+
const replayActionMarker = this.replayActionMarkerOnce;
|
|
1452
|
+
this.replayActionMarkerOnce = false;
|
|
1453
|
+
const appStateChanged =
|
|
1454
|
+
appStateMaxTs > this.lastAppStateTs || replayActionMarker;
|
|
1455
|
+
|
|
1456
|
+
const [appResult, refreshResult, extensionMarkerTs] = appStateChanged
|
|
1457
|
+
? await Promise.all([
|
|
1458
|
+
db.execute({
|
|
1459
|
+
sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
|
|
1460
|
+
args: [this.lastAppStateTs],
|
|
1461
|
+
}),
|
|
1462
|
+
db.execute({
|
|
1463
|
+
sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
|
|
1464
|
+
args: [SCREEN_REFRESH_KEY],
|
|
1465
|
+
}),
|
|
1466
|
+
readExtensionMarkerMaxUpdatedAt(db),
|
|
1467
|
+
])
|
|
1468
|
+
: ([
|
|
1469
|
+
{ rows: [] as Record<string, unknown>[] },
|
|
1470
|
+
null,
|
|
1471
|
+
this.lastExtensionMarkerTs,
|
|
1472
|
+
] as const);
|
|
1473
|
+
|
|
1441
1474
|
// Check application_state for external writes. Preserve the changed key so
|
|
1442
1475
|
// clients can invalidate one-shot command queries (`navigate`, `__set_url__`)
|
|
1443
1476
|
// only when those command rows actually change; noisy keys such as
|
|
@@ -1497,54 +1530,59 @@ export class AppSyncState {
|
|
|
1497
1530
|
// tool writes to application_state under a well-known key; when its
|
|
1498
1531
|
// updated_at bumps, emit a distinct event so the client invalidates
|
|
1499
1532
|
// all queries (not just the ones matching its default queryKey prefix).
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
)
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
// advanced, scoped with `owner` so canSeeChangeForUser delivers it only
|
|
1518
|
-
// to that user — not every authenticated poller.
|
|
1519
|
-
for (const row of refreshResult.rows) {
|
|
1520
|
-
const owner =
|
|
1521
|
-
typeof row.session_id === "string" ? row.session_id : undefined;
|
|
1522
|
-
if (!owner) continue;
|
|
1523
|
-
const rowTs = timestampValue(row.updated_at);
|
|
1524
|
-
if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
|
|
1525
|
-
continue;
|
|
1533
|
+
// `refreshResult` is null exactly when the MAX probe proved no
|
|
1534
|
+
// application_state row moved, which is also exactly when this block
|
|
1535
|
+
// could not emit anything.
|
|
1536
|
+
if (refreshResult) {
|
|
1537
|
+
const refreshTs = refreshResult.rows.reduce(
|
|
1538
|
+
(max, row) => Math.max(max, timestampValue(row.updated_at)),
|
|
1539
|
+
0,
|
|
1540
|
+
);
|
|
1541
|
+
if (!this.screenRefreshInitialized) {
|
|
1542
|
+
this.lastScreenRefreshTs = refreshTs;
|
|
1543
|
+
for (const row of refreshResult.rows) {
|
|
1544
|
+
if (typeof row.session_id === "string") {
|
|
1545
|
+
this.lastScreenRefreshTsBySession.set(
|
|
1546
|
+
row.session_id,
|
|
1547
|
+
timestampValue(row.updated_at),
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1526
1550
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1551
|
+
this.screenRefreshInitialized = true;
|
|
1552
|
+
} else if (refreshTs > this.lastScreenRefreshTs) {
|
|
1553
|
+
// Emit a per-user event only for the session(s) whose row actually
|
|
1554
|
+
// advanced, scoped with `owner` so canSeeChangeForUser delivers it only
|
|
1555
|
+
// to that user — not every authenticated poller.
|
|
1556
|
+
for (const row of refreshResult.rows) {
|
|
1557
|
+
const owner =
|
|
1558
|
+
typeof row.session_id === "string" ? row.session_id : undefined;
|
|
1559
|
+
if (!owner) continue;
|
|
1560
|
+
const rowTs = timestampValue(row.updated_at);
|
|
1561
|
+
if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
|
|
1562
|
+
continue;
|
|
1533
1563
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1564
|
+
let scope: string | undefined;
|
|
1565
|
+
try {
|
|
1566
|
+
const raw = row.value;
|
|
1567
|
+
if (typeof raw === "string") {
|
|
1568
|
+
const parsed = JSON.parse(raw);
|
|
1569
|
+
if (typeof parsed?.scope === "string") scope = parsed.scope;
|
|
1570
|
+
}
|
|
1571
|
+
} catch {}
|
|
1572
|
+
this.recordChange(
|
|
1573
|
+
{
|
|
1574
|
+
source: "screen-refresh",
|
|
1575
|
+
type: "change",
|
|
1576
|
+
key: SCREEN_REFRESH_KEY,
|
|
1577
|
+
owner,
|
|
1578
|
+
...(scope ? { scope } : {}),
|
|
1579
|
+
},
|
|
1580
|
+
{ dedupeKey: `screen-refresh|${rowTs}` },
|
|
1581
|
+
);
|
|
1582
|
+
this.lastScreenRefreshTsBySession.set(owner, rowTs);
|
|
1583
|
+
}
|
|
1584
|
+
this.lastScreenRefreshTs = refreshTs;
|
|
1546
1585
|
}
|
|
1547
|
-
this.lastScreenRefreshTs = refreshTs;
|
|
1548
1586
|
}
|
|
1549
1587
|
|
|
1550
1588
|
// Extension mutations write a durable marker row so delete and hide/unhide
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { getOrgContext } from "../org/context.js";
|
|
23
23
|
import { getSession } from "./auth.js";
|
|
24
24
|
import {
|
|
25
|
+
prefetchSecrets,
|
|
25
26
|
resolveHasCompleteBuilderConnection,
|
|
26
27
|
resolveSecret,
|
|
27
28
|
} from "./credential-provider.js";
|
|
@@ -74,10 +75,14 @@ export function createVoiceProvidersStatusHandler() {
|
|
|
74
75
|
async function hasKey(key: string): Promise<boolean> {
|
|
75
76
|
try {
|
|
76
77
|
if (key === "GOOGLE_APPLICATION_CREDENTIALS") {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// Same identity, passed explicitly — the context is here only so this
|
|
79
|
+
// scope sweep shares the prefetched per-request memo below.
|
|
80
|
+
const resolved = await withRequestContext(() =>
|
|
81
|
+
resolveGoogleRealtimeCredentials({
|
|
82
|
+
userEmail: session?.email,
|
|
83
|
+
orgId: orgCtx?.orgId ?? undefined,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
81
86
|
return typeof resolved === "string" && resolved.length > 0;
|
|
82
87
|
}
|
|
83
88
|
const resolved = await withRequestContext(() => resolveSecret(key));
|
|
@@ -87,6 +92,16 @@ export function createVoiceProvidersStatusHandler() {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
// One read per scope for every key below, instead of one per key per scope.
|
|
96
|
+
await withRequestContext(() =>
|
|
97
|
+
prefetchSecrets([
|
|
98
|
+
"GEMINI_API_KEY",
|
|
99
|
+
"OPENAI_API_KEY",
|
|
100
|
+
"GROQ_API_KEY",
|
|
101
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
102
|
+
]),
|
|
103
|
+
);
|
|
104
|
+
|
|
90
105
|
let builder = false;
|
|
91
106
|
try {
|
|
92
107
|
builder =
|
|
@@ -86,13 +86,16 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
86
86
|
'MIME type for new files. Default: "text/plain". Use "application/json" for JSON, "text/markdown" for Markdown.',
|
|
87
87
|
},
|
|
88
88
|
offset: {
|
|
89
|
-
type: "
|
|
89
|
+
type: "integer",
|
|
90
|
+
minimum: 0,
|
|
90
91
|
description:
|
|
91
|
-
"
|
|
92
|
+
"Non-negative character offset to start reading from (for paging large files). Default: 0.",
|
|
92
93
|
},
|
|
93
94
|
maxChars: {
|
|
94
|
-
type: "
|
|
95
|
-
|
|
95
|
+
type: "integer",
|
|
96
|
+
minimum: 1,
|
|
97
|
+
maximum: MAX_READ_CHARS,
|
|
98
|
+
description: `Positive maximum characters to return when reading. Default: ${DEFAULT_READ_CHARS}. Max: ${MAX_READ_CHARS}.`,
|
|
96
99
|
},
|
|
97
100
|
pattern: {
|
|
98
101
|
type: "string",
|
|
@@ -168,16 +171,19 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
168
171
|
if (!path) return "Error: path is required for read.";
|
|
169
172
|
const rawOffset = Number(args.offset);
|
|
170
173
|
const offset =
|
|
171
|
-
Number.isFinite(rawOffset) && rawOffset > 0
|
|
174
|
+
Number.isFinite(rawOffset) && rawOffset > 0
|
|
175
|
+
? Math.floor(rawOffset)
|
|
176
|
+
: 0;
|
|
172
177
|
const rawMax = Number(args.maxChars);
|
|
173
178
|
const maxChars =
|
|
174
179
|
Number.isFinite(rawMax) && rawMax > 0
|
|
175
|
-
? Math.min(rawMax, MAX_READ_CHARS)
|
|
180
|
+
? Math.min(Math.max(1, Math.floor(rawMax)), MAX_READ_CHARS)
|
|
176
181
|
: DEFAULT_READ_CHARS;
|
|
177
182
|
|
|
183
|
+
// The sentinel character distinguishes an exact page from a truncated one.
|
|
178
184
|
const file = await readWorkspaceFile(scope, path, {
|
|
179
185
|
offset,
|
|
180
|
-
maxChars,
|
|
186
|
+
maxChars: maxChars + 1,
|
|
181
187
|
});
|
|
182
188
|
if (!file) {
|
|
183
189
|
return JSON.stringify({
|
|
@@ -186,19 +192,20 @@ export function createWorkspaceFilesTool(): Record<string, ActionEntry> {
|
|
|
186
192
|
});
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
const truncated = file.content.length
|
|
195
|
+
const truncated = file.content.length > maxChars;
|
|
196
|
+
const content = file.content.slice(0, maxChars);
|
|
190
197
|
return JSON.stringify({
|
|
191
198
|
ok: true,
|
|
192
199
|
path: file.path,
|
|
193
200
|
contentType: file.contentType,
|
|
194
201
|
sizeBytes: file.sizeBytes,
|
|
195
202
|
updatedAt: file.updatedAt,
|
|
196
|
-
content
|
|
203
|
+
content,
|
|
197
204
|
...(truncated
|
|
198
205
|
? {
|
|
199
206
|
truncated: true,
|
|
200
|
-
nextOffset: offset +
|
|
201
|
-
hint: `File has more content. Call again with offset: ${offset +
|
|
207
|
+
nextOffset: offset + content.length,
|
|
208
|
+
hint: `File has more content. Call again with offset: ${offset + content.length}`,
|
|
202
209
|
}
|
|
203
210
|
: {}),
|
|
204
211
|
});
|