@agent-native/core 0.131.9 → 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 +99 -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 +28 -11
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- 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/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/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/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 +23 -10
- 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/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/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/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/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/observability/routes.d.ts +3 -3
- 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/transcribe-voice.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/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +2 -2
- 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 +28 -11
- package/src/audit/redact.ts +104 -18
- package/src/client/AgentPanel.tsx +2 -7
- 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/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/scripts/runner.ts
CHANGED
|
@@ -17,7 +17,10 @@ import { pathToFileURL } from "url";
|
|
|
17
17
|
|
|
18
18
|
import type { ActionEntry } from "../agent/production-agent.js";
|
|
19
19
|
import { closeDbExec } from "../db/client.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
actionCallIsReadOnly,
|
|
22
|
+
notifyActionChange,
|
|
23
|
+
} from "../server/action-change.js";
|
|
21
24
|
import {
|
|
22
25
|
runWithRequestContext,
|
|
23
26
|
getRequestOrgId,
|
|
@@ -442,7 +445,7 @@ async function dispatchAction(
|
|
|
442
445
|
) {
|
|
443
446
|
const parsed = parseActionArgs(args, { coerceBooleans: true });
|
|
444
447
|
const result = await handler.run(parsed, cliActionCtx(actionName));
|
|
445
|
-
if (handler
|
|
448
|
+
if (!actionCallIsReadOnly(handler, parsed, false)) {
|
|
446
449
|
await notifyActionChange({ actionName }).catch(() => {});
|
|
447
450
|
}
|
|
448
451
|
if (result) assertCliHandoffLaunched(printActionResult(result));
|
|
@@ -476,7 +479,7 @@ async function dispatchAction(
|
|
|
476
479
|
parsed as Record<string, string>,
|
|
477
480
|
cliActionCtx(actionName),
|
|
478
481
|
);
|
|
479
|
-
if (packageAction
|
|
482
|
+
if (!actionCallIsReadOnly(packageAction, parsed, false)) {
|
|
480
483
|
await notifyActionChange({ actionName }).catch(() => {});
|
|
481
484
|
}
|
|
482
485
|
if (result) assertCliHandoffLaunched(printActionResult(result));
|
package/src/secrets/storage.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { randomUUID } from "node:crypto";
|
|
|
16
16
|
|
|
17
17
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
18
18
|
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
19
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
19
20
|
import {
|
|
20
21
|
encryptSecretValue as encryptLegacyValue,
|
|
21
22
|
encryptSharedSecretValue as encryptValue,
|
|
@@ -217,6 +218,8 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
|
|
|
217
218
|
now,
|
|
218
219
|
];
|
|
219
220
|
|
|
221
|
+
invalidateRequestSecret(args);
|
|
222
|
+
|
|
220
223
|
if (isPostgres()) {
|
|
221
224
|
const { rows } = await client.execute({
|
|
222
225
|
sql: `${upsertSql} RETURNING id`,
|
|
@@ -339,6 +342,52 @@ async function populateSharedAppSecret(
|
|
|
339
342
|
}
|
|
340
343
|
}
|
|
341
344
|
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Per-request read memo
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Per-request memo of secret reads, keyed on the active AsyncLocalStorage
|
|
351
|
+
* RequestContext (WeakMap → freed with the request). Mirrors the settings
|
|
352
|
+
* store's cache, including its staleness rule: a write in THIS request is
|
|
353
|
+
* written through, while other in-flight requests keep their snapshot for
|
|
354
|
+
* their own (short) lifetime.
|
|
355
|
+
*
|
|
356
|
+
* The map key is `scope|scopeId|key`, which is the secret's full identity —
|
|
357
|
+
* `user:<email>`, `org:<id>`, `solo:<email>`, `workspace` — so a hit can never
|
|
358
|
+
* answer one caller with another caller's secret. Do NOT reduce the key to
|
|
359
|
+
* just `key`.
|
|
360
|
+
*
|
|
361
|
+
* This exists because one credential resolution is a WATERFALL: user scope,
|
|
362
|
+
* then org, then workspace, then solo. Callers re-resolve the same credential
|
|
363
|
+
* several times per request (engine detection, config resolution, usability
|
|
364
|
+
* checks), and against a remote database each probe is a network round trip.
|
|
365
|
+
*/
|
|
366
|
+
const _requestSecretsCache = new WeakMap<
|
|
367
|
+
object,
|
|
368
|
+
Map<string, ReadSecretResult | null>
|
|
369
|
+
>();
|
|
370
|
+
|
|
371
|
+
function requestSecretsCache(): Map<string, ReadSecretResult | null> | null {
|
|
372
|
+
const ctx = getRequestContext();
|
|
373
|
+
if (!ctx || typeof ctx !== "object") return null;
|
|
374
|
+
let cache = _requestSecretsCache.get(ctx);
|
|
375
|
+
if (!cache) {
|
|
376
|
+
cache = new Map();
|
|
377
|
+
_requestSecretsCache.set(ctx, cache);
|
|
378
|
+
}
|
|
379
|
+
return cache;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function secretCacheKey(ref: SecretRef): string {
|
|
383
|
+
return `${ref.scope}|${ref.scopeId}|${ref.key}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Drop this request's memo for a secret whose stored value just changed. */
|
|
387
|
+
function invalidateRequestSecret(ref: SecretRef): void {
|
|
388
|
+
requestSecretsCache()?.delete(secretCacheKey(ref));
|
|
389
|
+
}
|
|
390
|
+
|
|
342
391
|
type AppSecretsReadQuery = { sql: string; args: unknown[] };
|
|
343
392
|
|
|
344
393
|
/**
|
|
@@ -379,6 +428,17 @@ async function executeAppSecretsRead(query: AppSecretsReadQuery) {
|
|
|
379
428
|
*/
|
|
380
429
|
export async function readAppSecret(
|
|
381
430
|
ref: SecretRef,
|
|
431
|
+
): Promise<ReadSecretResult | null> {
|
|
432
|
+
const cache = requestSecretsCache();
|
|
433
|
+
const cacheKey = secretCacheKey(ref);
|
|
434
|
+
if (cache?.has(cacheKey)) return cache.get(cacheKey) ?? null;
|
|
435
|
+
const result = await readAppSecretUncached(ref);
|
|
436
|
+
cache?.set(cacheKey, result);
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function readAppSecretUncached(
|
|
441
|
+
ref: SecretRef,
|
|
382
442
|
): Promise<ReadSecretResult | null> {
|
|
383
443
|
const { key, scope, scopeId } = ref;
|
|
384
444
|
const { rows } = await executeAppSecretsRead({
|
|
@@ -417,15 +477,41 @@ export async function readAppSecrets(args: {
|
|
|
417
477
|
scope: SecretScope;
|
|
418
478
|
scopeId: string;
|
|
419
479
|
}): Promise<Map<string, ReadSecretResult>> {
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
480
|
+
const requested = [...new Set(args.keys.filter(Boolean))];
|
|
481
|
+
if (requested.length === 0) return new Map();
|
|
482
|
+
|
|
483
|
+
const cache = requestSecretsCache();
|
|
484
|
+
const results = new Map<string, ReadSecretResult>();
|
|
485
|
+
const keys: string[] = [];
|
|
486
|
+
for (const key of requested) {
|
|
487
|
+
const cacheKey = secretCacheKey({
|
|
488
|
+
key,
|
|
489
|
+
scope: args.scope,
|
|
490
|
+
scopeId: args.scopeId,
|
|
491
|
+
});
|
|
492
|
+
if (cache?.has(cacheKey)) {
|
|
493
|
+
const cached = cache.get(cacheKey);
|
|
494
|
+
if (cached) results.set(key, cached);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
keys.push(key);
|
|
498
|
+
}
|
|
499
|
+
if (keys.length === 0) return results;
|
|
422
500
|
|
|
423
501
|
const placeholders = keys.map(() => "?").join(", ");
|
|
424
502
|
const { rows } = await executeAppSecretsRead({
|
|
425
503
|
sql: `SELECT key, encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key IN (${placeholders})`,
|
|
426
504
|
args: [args.scope, args.scopeId, ...keys],
|
|
427
505
|
});
|
|
428
|
-
|
|
506
|
+
// The statement covered every uncached key in this scope, so a key missing
|
|
507
|
+
// from `rows` is genuinely absent — memo it as such rather than leaving a
|
|
508
|
+
// single-key read to go ask again.
|
|
509
|
+
for (const key of keys) {
|
|
510
|
+
cache?.set(
|
|
511
|
+
secretCacheKey({ key, scope: args.scope, scopeId: args.scopeId }),
|
|
512
|
+
null,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
429
515
|
for (const row of rows) {
|
|
430
516
|
const key = String(row.key ?? "");
|
|
431
517
|
if (!key) continue;
|
|
@@ -442,11 +528,16 @@ export async function readAppSecrets(args: {
|
|
|
442
528
|
decrypted.sharedCiphertextToReplace ?? null,
|
|
443
529
|
);
|
|
444
530
|
}
|
|
445
|
-
|
|
531
|
+
const result = {
|
|
446
532
|
value: decrypted.value,
|
|
447
533
|
last4: last4(decrypted.value),
|
|
448
534
|
updatedAt: Number(row.updated_at ?? 0),
|
|
449
|
-
}
|
|
535
|
+
};
|
|
536
|
+
results.set(key, result);
|
|
537
|
+
cache?.set(
|
|
538
|
+
secretCacheKey({ key, scope: args.scope, scopeId: args.scopeId }),
|
|
539
|
+
result,
|
|
540
|
+
);
|
|
450
541
|
} catch {
|
|
451
542
|
// Match readAppSecret: corrupted or stale ciphertext behaves as missing.
|
|
452
543
|
}
|
|
@@ -589,6 +680,7 @@ function parseAllowlist(raw: string | null): string[] | null {
|
|
|
589
680
|
|
|
590
681
|
export async function deleteAppSecret(ref: SecretRef): Promise<boolean> {
|
|
591
682
|
await ensureTable();
|
|
683
|
+
invalidateRequestSecret(ref);
|
|
592
684
|
const { key, scope, scopeId } = ref;
|
|
593
685
|
const client = getDbExec();
|
|
594
686
|
const { rowsAffected } = await client.execute({
|
|
@@ -4,10 +4,43 @@ import {
|
|
|
4
4
|
actionChangeMarkerValue,
|
|
5
5
|
type ActionChangeTarget,
|
|
6
6
|
} from "../action-change-marker.js";
|
|
7
|
+
import type { ActionPlanModeConfig } from "../action.js";
|
|
7
8
|
import { appStatePut } from "../application-state/store.js";
|
|
8
9
|
import { recordChange } from "./poll.js";
|
|
9
10
|
import { getRequestOrgId, getRequestUserEmail } from "./request-context.js";
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Whether THIS invocation should announce a change.
|
|
14
|
+
*
|
|
15
|
+
* `readOnly` describes an action name, but a tool that discriminates on an
|
|
16
|
+
* argument reads on some calls and writes on others — `manage-agent-engine`
|
|
17
|
+
* with `action: "list"` is a status poll, with `action: "set"` a write. Such
|
|
18
|
+
* actions already publish the per-call answer as a Plan-mode `effect`
|
|
19
|
+
* predicate, so read it here instead of treating every call as a write: a poll
|
|
20
|
+
* that bumps the `"action"` change version makes every query keyed on it
|
|
21
|
+
* refetch forever.
|
|
22
|
+
*
|
|
23
|
+
* `readOnly` stays authoritative otherwise. Widening the flag to cover mixed
|
|
24
|
+
* tools would also open them to read-only A2A peers and to the agent's
|
|
25
|
+
* read-result cache, where it would be false.
|
|
26
|
+
*/
|
|
27
|
+
export function actionCallIsReadOnly(
|
|
28
|
+
entry: { readOnly?: boolean; planMode?: ActionPlanModeConfig<any> },
|
|
29
|
+
params: unknown,
|
|
30
|
+
fallback: boolean,
|
|
31
|
+
): boolean {
|
|
32
|
+
const effect = entry.planMode?.effect;
|
|
33
|
+
if (typeof effect === "function") {
|
|
34
|
+
try {
|
|
35
|
+
return effect(params) === "read";
|
|
36
|
+
} catch {
|
|
37
|
+
// A predicate that throws says nothing; fall through to the flag.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (typeof entry.readOnly === "boolean") return entry.readOnly;
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
|
|
11
44
|
export interface NotifyActionChangeOptions {
|
|
12
45
|
actionName: string;
|
|
13
46
|
owner?: string;
|
|
@@ -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,
|