@agent-native/core 0.145.0 → 0.145.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/brain/server/lib/capture-sanitization.ts +6 -2
  3. package/corpus/templates/clips/changelog/2026-08-07-full-screen-recordings-on-a-multi-monitor-mac-now-let-you-pi.md +6 -0
  4. package/corpus/templates/clips/chrome-extension/src/styles.css +13 -4
  5. package/corpus/templates/clips/desktop/src/app.tsx +38 -1
  6. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +123 -104
  7. package/corpus/templates/clips/desktop/src/lib/recorder.ts +165 -2
  8. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  9. package/corpus/templates/clips/desktop/src/overlays/monitor-picker.tsx +70 -0
  10. package/corpus/templates/clips/desktop/src/styles.css +70 -11
  11. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +163 -1
  13. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -1
  14. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +141 -22
  15. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +27 -1
  16. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +12 -1
  17. package/dist/agent/engine/index.d.ts +1 -1
  18. package/dist/agent/engine/index.js +1 -1
  19. package/dist/agent/engine/registry.d.ts +15 -0
  20. package/dist/agent/engine/registry.js +39 -10
  21. package/dist/agent/production-agent.d.ts +40 -1
  22. package/dist/agent/production-agent.js +81 -40
  23. package/dist/agent/run-manager.d.ts +44 -1
  24. package/dist/agent/run-manager.js +70 -8
  25. package/dist/chat-threads/store.js +6 -4
  26. package/dist/client/use-db-sync.js +9 -1
  27. package/dist/db/ddl-guard.d.ts +8 -0
  28. package/dist/db/ddl-guard.js +148 -0
  29. package/dist/deploy/build.d.ts +36 -0
  30. package/dist/deploy/build.js +78 -5
  31. package/dist/integrations/webhook-handler.d.ts +3 -2
  32. package/dist/integrations/webhook-handler.js +8 -38
  33. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  34. package/dist/notifications/routes.d.ts +3 -3
  35. package/dist/observability/routes.d.ts +5 -5
  36. package/dist/org/accept-pending.js +2 -2
  37. package/dist/org/auth-policy.js +4 -0
  38. package/dist/org/auto-join-domain.d.ts +9 -0
  39. package/dist/org/auto-join-domain.js +53 -4
  40. package/dist/org/context.js +12 -3
  41. package/dist/org/handlers.js +17 -5
  42. package/dist/org/request-org-cache.d.ts +24 -2
  43. package/dist/org/request-org-cache.js +57 -2
  44. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  45. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  46. package/dist/resources/store.d.ts +1 -1
  47. package/dist/resources/store.js +96 -7
  48. package/dist/secrets/routes.d.ts +6 -6
  49. package/dist/server/agent-chat-plugin.js +83 -37
  50. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  51. package/dist/server/auth.d.ts +3 -0
  52. package/dist/server/auth.js +22 -1
  53. package/dist/server/collab-plugin.js +10 -4
  54. package/dist/server/complete-text.js +14 -7
  55. package/dist/server/core-routes-plugin.js +3 -0
  56. package/dist/server/index.d.ts +1 -1
  57. package/dist/server/index.js +1 -1
  58. package/dist/server/request-context.d.ts +7 -1
  59. package/dist/server/session-email-cache.d.ts +14 -0
  60. package/dist/server/session-email-cache.js +48 -0
  61. package/dist/server/sweep-runtime.d.ts +22 -0
  62. package/dist/server/sweep-runtime.js +28 -0
  63. package/dist/server/transcribe-voice.d.ts +1 -1
  64. package/dist/settings/store.js +63 -7
  65. package/dist/shared/ttl-cache.d.ts +59 -0
  66. package/dist/shared/ttl-cache.js +43 -0
  67. package/dist/sharing/actions/list-resource-shares.d.ts +3 -3
  68. package/dist/sharing/actions/list-resource-shares.js +4 -1
  69. package/dist/triggers/condition-evaluator.js +11 -27
  70. package/docs/content/deployment.mdx +48 -0
  71. package/docs/content/environment-variables.mdx +11 -0
  72. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  use std::sync::Mutex;
2
2
  use std::time::Instant;
3
- use tauri::Rect;
3
+ use tauri::{AppHandle, Manager, Rect};
4
4
 
5
5
  use crate::tray_meetings::MeetingItem;
6
6
 
@@ -63,3 +63,29 @@ pub struct VoiceTargetBundle(pub Mutex<Option<String>>);
63
63
  /// Last dictation result for "paste last".
64
64
  #[derive(Default)]
65
65
  pub struct LastTranscript(pub Mutex<Option<String>>);
66
+
67
+ /// CGDirectDisplayID the user picked in the multi-monitor screen picker,
68
+ /// applying for the whole lifetime of the recording it was picked for (not
69
+ /// just the first read) — `tray_display_id` and `tray_monitor_physical_rect`
70
+ /// both read it via `get`. Cleared via `set(app, None)` by `show_monitor_picker`
71
+ /// (next pick), `hide_recording_chrome` (stop), and
72
+ /// `native_fullscreen_recording_cancel` (aborted start) — a stale pick must
73
+ /// never leak into a later recording that skipped the picker (e.g. only one
74
+ /// monitor connected).
75
+ #[derive(Default)]
76
+ pub struct SelectedRecordingDisplay(pub Mutex<Option<u32>>);
77
+
78
+ impl SelectedRecordingDisplay {
79
+ pub fn get(app: &AppHandle) -> Option<u32> {
80
+ app.try_state::<Self>()
81
+ .and_then(|s| s.0.lock().ok().and_then(|g| *g))
82
+ }
83
+
84
+ pub fn set(app: &AppHandle, display_id: Option<u32>) {
85
+ if let Some(state) = app.try_state::<Self>() {
86
+ if let Ok(mut guard) = state.0.lock() {
87
+ *guard = display_id;
88
+ }
89
+ }
90
+ }
91
+ }
@@ -2,7 +2,8 @@ use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindow, WebviewWindo
2
2
 
3
3
  use crate::dlog;
4
4
  use crate::state::{
5
- DictationActive, PopoverShownAt, RecordingActive, TrayAnchor, VoiceWakePopover,
5
+ DictationActive, PopoverShownAt, RecordingActive, SelectedRecordingDisplay, TrayAnchor,
6
+ VoiceWakePopover,
6
7
  };
7
8
 
8
9
  const POPOVER_SHADOW_GUTTER_LOGICAL: f64 = 24.0;
@@ -369,6 +370,16 @@ pub fn present_interactive_window(window: &WebviewWindow) {
369
370
  /// instead of `primary_monitor_physical_size` for any overlay that should appear
370
371
  /// on the same screen as the recording.
371
372
  pub fn tray_monitor_physical_rect(app: &AppHandle) -> (i32, i32, u32, u32) {
373
+ // A monitor picked in the multi-monitor screen picker wins over the tray
374
+ // icon's monitor for every overlay shown during that recording
375
+ // (countdown, toolbar, finalizing, region guides, ...) — see
376
+ // `SelectedRecordingDisplay`'s doc comment for its lifecycle.
377
+ if let Some(id) = SelectedRecordingDisplay::get(app) {
378
+ if let Some(rect) = crate::native_screen::monitor_rect_for_display_id(app, id) {
379
+ return rect;
380
+ }
381
+ }
382
+
372
383
  let tray_rect = app
373
384
  .try_state::<TrayAnchor>()
374
385
  .and_then(|a| a.0.lock().ok().and_then(|g| *g));
@@ -2,7 +2,7 @@
2
2
  * Public exports for the pluggable agent engine system.
3
3
  */
4
4
  export type { AgentEngine, EngineCapabilities, EngineTool, EngineMessage, EngineContentPart, EngineTextPart, EngineImagePart, EngineToolCallPart, EngineToolResultPart, EngineThinkingPart, EngineEvent, EngineStreamOptions, } from "./types.js";
5
- export { registerAgentEngine, getAgentEngineEntry, listAgentEngines, resolveEngine, getConfiguredEngineNameForRequest, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, resolveEnginePreservesCustomModels, type NormalizeModelOptions, detectEngineFromEnv, detectEngineFromEnvForRequest, detectEngineFromUserSecrets, isAgentEngineSettingConfigured, isAgentEnginePackageInstalled, isStoredEngineUsable, isStoredEngineUsableForRequest, isResolvedEngineUsableForRequest, type AgentEngineEntry, type ResolveEngineConfig, } from "./registry.js";
5
+ export { registerAgentEngine, getAgentEngineEntry, listAgentEngines, resolveEngine, explicitEngineName, getConfiguredEngineNameForRequest, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, resolveEnginePreservesCustomModels, type NormalizeModelOptions, detectEngineFromEnv, detectEngineFromEnvForRequest, detectEngineFromUserSecrets, isAgentEngineSettingConfigured, isAgentEnginePackageInstalled, isStoredEngineUsable, isStoredEngineUsableForRequest, isResolvedEngineUsableForRequest, type AgentEngineEntry, type ResolveEngineConfig, } from "./registry.js";
6
6
  export { createBuilderEngine, BUILDER_DEFAULT_MODEL, BUILDER_SUPPORTED_MODELS, BUILDER_CAPABILITIES, } from "./builder-engine.js";
7
7
  export { createAnthropicEngine, ANTHROPIC_DEFAULT_MODEL, ANTHROPIC_SUPPORTED_MODELS, ANTHROPIC_CAPABILITIES, } from "./anthropic-engine.js";
8
8
  export { createAISDKEngine, type AISDKProvider } from "./ai-sdk-engine.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Public exports for the pluggable agent engine system.
3
3
  */
4
- export { registerAgentEngine, getAgentEngineEntry, listAgentEngines, resolveEngine, getConfiguredEngineNameForRequest, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, resolveEnginePreservesCustomModels, detectEngineFromEnv, detectEngineFromEnvForRequest, detectEngineFromUserSecrets, isAgentEngineSettingConfigured, isAgentEnginePackageInstalled, isStoredEngineUsable, isStoredEngineUsableForRequest, isResolvedEngineUsableForRequest, } from "./registry.js";
4
+ export { registerAgentEngine, getAgentEngineEntry, listAgentEngines, resolveEngine, explicitEngineName, getConfiguredEngineNameForRequest, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, resolveEnginePreservesCustomModels, detectEngineFromEnv, detectEngineFromEnvForRequest, detectEngineFromUserSecrets, isAgentEngineSettingConfigured, isAgentEnginePackageInstalled, isStoredEngineUsable, isStoredEngineUsableForRequest, isResolvedEngineUsableForRequest, } from "./registry.js";
5
5
  export { createBuilderEngine, BUILDER_DEFAULT_MODEL, BUILDER_SUPPORTED_MODELS, BUILDER_CAPABILITIES, } from "./builder-engine.js";
6
6
  export { createAnthropicEngine, ANTHROPIC_DEFAULT_MODEL, ANTHROPIC_SUPPORTED_MODELS, ANTHROPIC_CAPABILITIES, } from "./anthropic-engine.js";
7
7
  export { createAISDKEngine } from "./ai-sdk-engine.js";
@@ -174,11 +174,26 @@ export interface ResolveEngineConfig {
174
174
  };
175
175
  /** API key (used as config for the resolved engine) */
176
176
  apiKey?: string;
177
+ /**
178
+ * Env var name `apiKey` was issued for, when the caller knows it. Declaring
179
+ * it keeps a provider-specific credential from reaching a different
180
+ * provider's engine; omit it for opaque keys.
181
+ */
182
+ apiKeyEnvVar?: string;
177
183
  /** Model override (used as part of engine config) */
178
184
  model?: string;
179
185
  /** App/template id used for org-scoped per-app model defaults. */
180
186
  appId?: string;
181
187
  }
188
+ /**
189
+ * Engine name a caller explicitly selected, when {@link resolveEngine} will
190
+ * honor it as a name. Callers resolve the API key before they call
191
+ * `resolveEngine`, so they need the same answer the registry will reach:
192
+ * an untagged "active" key resolved against a different provider's setting
193
+ * would otherwise ride along to whichever engine this names. Returns
194
+ * `undefined` for an engine instance, which carries its own credential.
195
+ */
196
+ export declare function explicitEngineName(engineOption: ResolveEngineConfig["engineOption"]): string | undefined;
182
197
  /**
183
198
  * Return the usable engine explicitly selected by the current user/org.
184
199
  *
@@ -559,9 +559,18 @@ async function apiKeyBelongsToDifferentProvider(apiKey, selectedEntry) {
559
559
  }
560
560
  return false;
561
561
  }
562
- async function engineCreateConfigForEntry(entry, apiKey, extra, preferResolvedCredential = false) {
562
+ async function engineCreateConfigForEntry(entry, apiKey, extra, preferResolvedCredential = false, apiKeyEnvVar) {
563
563
  const safeExtra = { ...(extra ?? {}) };
564
564
  let matchingApiKey = apiKey;
565
+ // A declared provenance settles the question without inspecting values: a
566
+ // credential issued for another provider's env var is never this entry's
567
+ // key, so drop it on explicit branches too. Value comparison below cannot
568
+ // cover this — a host-supplied key (plugin `options.apiKey`) matches no
569
+ // stored secret, so it would otherwise reach whichever provider was picked.
570
+ if (apiKeyEnvVar !== undefined &&
571
+ !entry.requiredEnvVars.includes(apiKeyEnvVar)) {
572
+ matchingApiKey = undefined;
573
+ }
565
574
  // Automatic engine selection must also select that engine's credential.
566
575
  // Callers historically passed one untagged "active" key before the registry
567
576
  // chose an engine, which could hand an Anthropic key to an app-default
@@ -687,6 +696,26 @@ export async function isResolvedEngineUsableForRequest(engine, options = {}) {
687
696
  }
688
697
  return true;
689
698
  }
699
+ /**
700
+ * Engine name a caller explicitly selected, when {@link resolveEngine} will
701
+ * honor it as a name. Callers resolve the API key before they call
702
+ * `resolveEngine`, so they need the same answer the registry will reach:
703
+ * an untagged "active" key resolved against a different provider's setting
704
+ * would otherwise ride along to whichever engine this names. Returns
705
+ * `undefined` for an engine instance, which carries its own credential.
706
+ */
707
+ export function explicitEngineName(engineOption) {
708
+ if (!engineOption)
709
+ return undefined;
710
+ if (typeof engineOption === "string")
711
+ return engineOption;
712
+ if (typeof engineOption === "object" &&
713
+ !("stream" in engineOption) &&
714
+ typeof engineOption.name === "string") {
715
+ return engineOption.name;
716
+ }
717
+ return undefined;
718
+ }
690
719
  /**
691
720
  * Return the usable engine explicitly selected by the current user/org.
692
721
  *
@@ -734,7 +763,7 @@ export async function getConfiguredEngineNameForRequest(options = {}) {
734
763
  * 7. Default "anthropic" (requires ANTHROPIC_API_KEY)
735
764
  */
736
765
  export async function resolveEngine(config) {
737
- const { engineOption, apiKey, model: _model, appId } = config;
766
+ const { engineOption, apiKey, apiKeyEnvVar, model: _model, appId } = config;
738
767
  // 1. Explicit instance passed directly
739
768
  if (engineOption &&
740
769
  typeof engineOption === "object" &&
@@ -750,7 +779,7 @@ export async function resolveEngine(config) {
750
779
  if (!entry)
751
780
  throw new Error(`[agent-engine] Unknown engine: "${name}". Registered: ${[..._registry.keys()].join(", ")}`);
752
781
  assertAgentEnginePackageInstalled(entry);
753
- return entry.create(await engineCreateConfigForEntry(entry, apiKey, engineConfig));
782
+ return entry.create(await engineCreateConfigForEntry(entry, apiKey, engineConfig, false, apiKeyEnvVar));
754
783
  }
755
784
  // 3. Explicit string name from options
756
785
  if (typeof engineOption === "string") {
@@ -758,7 +787,7 @@ export async function resolveEngine(config) {
758
787
  if (!entry)
759
788
  throw new Error(`[agent-engine] Unknown engine: "${engineOption}". Registered: ${[..._registry.keys()].join(", ")}`);
760
789
  assertAgentEnginePackageInstalled(entry);
761
- return entry.create(await engineCreateConfigForEntry(entry, apiKey));
790
+ return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, false, apiKeyEnvVar));
762
791
  }
763
792
  // 4. Env var — explicit engine name override
764
793
  const envEngine = process.env.AGENT_ENGINE;
@@ -766,14 +795,14 @@ export async function resolveEngine(config) {
766
795
  const entry = _registry.get(envEngine);
767
796
  if (entry) {
768
797
  assertAgentEnginePackageInstalled(entry);
769
- return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true));
798
+ return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true, apiKeyEnvVar));
770
799
  }
771
800
  }
772
801
  const appDefault = await getAgentAppModelDefaultForCurrentRequest(appId);
773
802
  if (appDefault?.engine) {
774
803
  const entry = _registry.get(appDefault.engine);
775
804
  if (entry && (await isStoredEngineUsableForRequest(appDefault, entry))) {
776
- return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true));
805
+ return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true, apiKeyEnvVar));
777
806
  }
778
807
  }
779
808
  let stored = null;
@@ -797,25 +826,25 @@ export async function resolveEngine(config) {
797
826
  if (storedRaw && typeof storedEngine === "string") {
798
827
  const entry = _registry.get(storedEngine);
799
828
  if (entry && (await isStoredEngineUsableForRequest(storedRaw, entry))) {
800
- return entry.create(await engineCreateConfigForEntry(entry, apiKey, stripInlineApiKeyConfig(storedConfig), true));
829
+ return entry.create(await engineCreateConfigForEntry(entry, apiKey, stripInlineApiKeyConfig(storedConfig), true, apiKeyEnvVar));
801
830
  }
802
831
  }
803
832
  if (detectedFromUser) {
804
- return detectedFromUser.create(await engineCreateConfigForEntry(detectedFromUser, apiKey, undefined, true));
833
+ return detectedFromUser.create(await engineCreateConfigForEntry(detectedFromUser, apiKey, undefined, true, apiKeyEnvVar));
805
834
  }
806
835
  // 8. Auto-detect from any provider env var — so just dropping a key in
807
836
  // .env works without also setting AGENT_ENGINE. Skip keys with active
808
837
  // auth-failure markers so a rejected deploy key cannot permanently win.
809
838
  const detected = await detectEngineFromEnvForRequest();
810
839
  if (detected) {
811
- return detected.create(await engineCreateConfigForEntry(detected, apiKey, undefined, true));
840
+ return detected.create(await engineCreateConfigForEntry(detected, apiKey, undefined, true, apiKeyEnvVar));
812
841
  }
813
842
  // 9. Default: anthropic
814
843
  const anthropicEntry = _registry.get("anthropic");
815
844
  if (!anthropicEntry) {
816
845
  throw new Error("[agent-engine] Default Anthropic engine is not registered. Did builtin.ts fail to load?");
817
846
  }
818
- return anthropicEntry.create(await engineCreateConfigForEntry(anthropicEntry, apiKey, undefined, true));
847
+ return anthropicEntry.create(await engineCreateConfigForEntry(anthropicEntry, apiKey, undefined, true, apiKeyEnvVar));
819
848
  }
820
849
  /**
821
850
  * Read the user-selected model for an engine from the `agent-engine` setting.
@@ -3,6 +3,7 @@ import type { Task } from "../a2a/types.js";
3
3
  import { type ActionAutomationContext, type ActionCaller } from "../action.js";
4
4
  import { fireInternalDispatch } from "../server/self-dispatch.js";
5
5
  import { type ReasoningEffort } from "../shared/reasoning-effort.js";
6
+ import { type ResolveEngineConfig } from "./engine/index.js";
6
7
  import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
7
8
  import type { AgentEngine, EngineTool, EngineMessage, EngineContentPart } from "./engine/types.js";
8
9
  import { type Processor } from "./processors.js";
@@ -99,18 +100,56 @@ export declare function getOwnerApiKey(provider: string, ownerEmail: string | nu
99
100
  * "ai-sdk:openai" → "openai", "anthropic" → "anthropic"
100
101
  */
101
102
  export declare function engineToProvider(engineName: string): string;
103
+ /** An API key together with the env var it was issued for. */
104
+ export interface ResolvedOwnerApiKey {
105
+ apiKey: string | undefined;
106
+ /** Undefined when no key was found, or when the key's provider is unknown. */
107
+ apiKeyEnvVar: string | undefined;
108
+ }
102
109
  /**
103
- * Resolve the active engine's provider and look up the user's API key for it.
110
+ * Resolve the owner's key for one named engine, tagged with the env var it was
111
+ * issued for.
104
112
  *
105
113
  * If the owner has no scoped key, fall back to provider keys supplied by the
106
114
  * hosting environment only when the current request can safely use deploy-level
107
115
  * credentials. This is a read from process-level config, not a request-scoped
108
116
  * write to `process.env`.
117
+ */
118
+ export declare function getOwnerApiKeyForEngine(engineName: string, ownerEmail: string | null | undefined): Promise<ResolvedOwnerApiKey>;
119
+ /**
120
+ * Resolve the active engine's provider and look up the user's API key for it.
109
121
  *
110
122
  * Callers that layer another deployment-key fallback after this should keep the
111
123
  * same precedence: scoped key first, host-provided env key second.
124
+ *
125
+ * The returned key is untagged, so it is only safe to hand to `resolveEngine`
126
+ * when the caller has no explicit engine of its own — otherwise the active
127
+ * setting and the selected engine can name different providers. Prefer
128
+ * {@link resolveOwnerEngineApiKey}, which decides that per call site.
112
129
  */
113
130
  export declare function getOwnerActiveApiKey(ownerEmail: string | null | undefined): Promise<string | undefined>;
131
+ /**
132
+ * Resolve the credential to hand `resolveEngine` alongside `engineOption`.
133
+ *
134
+ * The provenance is the point. An explicitly named engine skips the registry's
135
+ * value-comparison path, so an untagged key resolved from the saved
136
+ * `agent-engine` setting would ride along to whatever provider the caller
137
+ * named — shipping, say, a live Anthropic secret to OpenAI's endpoint and
138
+ * making the resulting 401 blame a key the user never saved. Resolving the key
139
+ * for the named engine keeps the two in step; only the no-explicit-engine case
140
+ * may stay untagged, because the registry's automatic branches re-derive the
141
+ * credential themselves.
142
+ */
143
+ export declare function resolveOwnerEngineApiKey(input: {
144
+ engineOption?: ResolveEngineConfig["engineOption"];
145
+ ownerEmail: string | null | undefined;
146
+ /**
147
+ * Host-provided credential, which is Anthropic by contract
148
+ * (`AgentChatPluginOptions.apiKey` / `WebhookHandlerOptions.apiKey`). Used
149
+ * only when no owner key was found.
150
+ */
151
+ anthropicFallback?: string;
152
+ }): Promise<ResolvedOwnerApiKey>;
114
153
  /** @deprecated Use getOwnerApiKey("anthropic", ownerEmail) instead */
115
154
  export declare function getOwnerAnthropicApiKey(ownerEmail: string | null | undefined): Promise<string | undefined>;
116
155
  /**
@@ -27,7 +27,7 @@ import { applyContextXrayTransformForIteration } from "./engine/context-directiv
27
27
  import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retry.js";
28
28
  import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
29
29
  import { isProviderConnectionErrorMessage } from "./engine/error-detail.js";
30
- import { resolveEngine, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
30
+ import { resolveEngine, explicitEngineName, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
31
31
  import { resolveEmptyResponseRetryMaxOutputTokens, resolveMainChatMaxOutputTokens, resolveMaxOutputTokensForEngine, } from "./engine/output-tokens.js";
32
32
  import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
33
33
  import { loadPriorTurnToolCallJournal } from "./engine/tool-call-journal-seed.js";
@@ -300,41 +300,92 @@ export async function getOwnerApiKey(provider, ownerEmail) {
300
300
  export function engineToProvider(engineName) {
301
301
  return engineName.startsWith("ai-sdk:") ? engineName.slice(7) : engineName;
302
302
  }
303
+ const NO_OWNER_API_KEY = {
304
+ apiKey: undefined,
305
+ apiKeyEnvVar: undefined,
306
+ };
303
307
  /**
304
- * Resolve the active engine's provider and look up the user's API key for it.
308
+ * Resolve the owner's key for one named engine, tagged with the env var it was
309
+ * issued for.
305
310
  *
306
311
  * If the owner has no scoped key, fall back to provider keys supplied by the
307
312
  * hosting environment only when the current request can safely use deploy-level
308
313
  * credentials. This is a read from process-level config, not a request-scoped
309
314
  * write to `process.env`.
310
- *
311
- * Callers that layer another deployment-key fallback after this should keep the
312
- * same precedence: scoped key first, host-provided env key second.
313
315
  */
314
- export async function getOwnerActiveApiKey(ownerEmail) {
316
+ export async function getOwnerApiKeyForEngine(engineName, ownerEmail) {
315
317
  try {
316
- const { getSetting } = await import("../settings/store.js");
317
- const engineSetting = await getSetting("agent-engine");
318
- const activeEngine = engineSetting?.engine ?? "anthropic";
319
- const provider = engineToProvider(activeEngine);
318
+ const provider = engineToProvider(engineName);
319
+ const envVar = PROVIDER_TO_ENV[provider];
320
320
  const userKey = await getOwnerApiKey(provider, ownerEmail);
321
321
  if (userKey)
322
- return userKey;
323
- const envVar = PROVIDER_TO_ENV[provider];
322
+ return { apiKey: userKey, apiKeyEnvVar: envVar };
324
323
  if (!envVar || !canUseDeployCredentialFallbackForRequest(envVar)) {
325
- return undefined;
324
+ return NO_OWNER_API_KEY;
326
325
  }
327
326
  const envKey = readDeployCredentialEnv(envVar);
328
327
  if (envKey &&
329
328
  !(await getProviderCredentialAuthFailure({ key: envVar, value: envKey }))) {
330
- return envKey;
329
+ return { apiKey: envKey, apiKeyEnvVar: envVar };
331
330
  }
332
- return undefined;
331
+ return NO_OWNER_API_KEY;
332
+ }
333
+ catch {
334
+ return NO_OWNER_API_KEY;
335
+ }
336
+ }
337
+ /**
338
+ * Resolve the active engine's provider and look up the user's API key for it.
339
+ *
340
+ * Callers that layer another deployment-key fallback after this should keep the
341
+ * same precedence: scoped key first, host-provided env key second.
342
+ *
343
+ * The returned key is untagged, so it is only safe to hand to `resolveEngine`
344
+ * when the caller has no explicit engine of its own — otherwise the active
345
+ * setting and the selected engine can name different providers. Prefer
346
+ * {@link resolveOwnerEngineApiKey}, which decides that per call site.
347
+ */
348
+ export async function getOwnerActiveApiKey(ownerEmail) {
349
+ try {
350
+ const { getSetting } = await import("../settings/store.js");
351
+ const engineSetting = await getSetting("agent-engine");
352
+ const activeEngine = engineSetting?.engine ?? "anthropic";
353
+ return (await getOwnerApiKeyForEngine(activeEngine, ownerEmail)).apiKey;
333
354
  }
334
355
  catch {
335
356
  return undefined;
336
357
  }
337
358
  }
359
+ /**
360
+ * Resolve the credential to hand `resolveEngine` alongside `engineOption`.
361
+ *
362
+ * The provenance is the point. An explicitly named engine skips the registry's
363
+ * value-comparison path, so an untagged key resolved from the saved
364
+ * `agent-engine` setting would ride along to whatever provider the caller
365
+ * named — shipping, say, a live Anthropic secret to OpenAI's endpoint and
366
+ * making the resulting 401 blame a key the user never saved. Resolving the key
367
+ * for the named engine keeps the two in step; only the no-explicit-engine case
368
+ * may stay untagged, because the registry's automatic branches re-derive the
369
+ * credential themselves.
370
+ */
371
+ export async function resolveOwnerEngineApiKey(input) {
372
+ const engineName = explicitEngineName(input.engineOption);
373
+ if (engineName) {
374
+ const resolved = await getOwnerApiKeyForEngine(engineName, input.ownerEmail);
375
+ if (resolved.apiKey)
376
+ return resolved;
377
+ }
378
+ else {
379
+ const activeKey = await getOwnerActiveApiKey(input.ownerEmail);
380
+ if (activeKey)
381
+ return { apiKey: activeKey, apiKeyEnvVar: undefined };
382
+ }
383
+ const fallback = input.anthropicFallback?.trim();
384
+ return fallback &&
385
+ canUseDeployCredentialFallbackForRequest("ANTHROPIC_API_KEY")
386
+ ? { apiKey: fallback, apiKeyEnvVar: "ANTHROPIC_API_KEY" }
387
+ : NO_OWNER_API_KEY;
388
+ }
338
389
  /** @deprecated Use getOwnerApiKey("anthropic", ownerEmail) instead */
339
390
  export async function getOwnerAnthropicApiKey(ownerEmail) {
340
391
  return getOwnerApiKey("anthropic", ownerEmail);
@@ -5884,34 +5935,22 @@ export function createProductionAgentHandler(options) {
5884
5935
  }
5885
5936
  // DIAGNOSTIC-ONLY: attachment upload + persistence finished.
5886
5937
  workerStep("attach_done");
5887
- // When a per-request engine override is specified, resolve the API key
5888
- // for that provider instead of the global active engine's provider.
5938
+ // Resolve the key for the engine this request will actually select — the
5939
+ // per-request override when there is one, otherwise the plugin's own
5940
+ // engine — instead of the global active engine's provider.
5941
+ // `options.apiKey` is the value the template constructed the plugin with
5942
+ // (often wired from a deployment env var). Honor it as host-provided
5943
+ // read-only configuration after scoped keys.
5889
5944
  // DIAGNOSTIC-ONLY: bracket per-owner API-key resolution (settings/app_secrets reads).
5890
5945
  workerStep("apikey_start");
5891
- let userApiKey;
5892
- if (requestEngine) {
5893
- const provider = engineToProvider(requestEngine);
5894
- userApiKey = await getOwnerApiKey(provider, ownerEmail);
5895
- const envVar = PROVIDER_TO_ENV[provider];
5896
- if (!userApiKey &&
5897
- envVar &&
5898
- canUseDeployCredentialFallbackForRequest(envVar)) {
5899
- // Read-only env fallback for the requested provider.
5900
- userApiKey = envVar ? readDeployCredentialEnv(envVar) : undefined;
5901
- }
5902
- }
5903
- else {
5904
- userApiKey = await getOwnerActiveApiKey(ownerEmail);
5905
- }
5946
+ const engineOption = requestEngine ?? options.engine;
5947
+ const { apiKey: effectiveApiKey, apiKeyEnvVar: effectiveApiKeyEnvVar } = await resolveOwnerEngineApiKey({
5948
+ engineOption,
5949
+ ownerEmail,
5950
+ anthropicFallback: options.apiKey ?? readDeployCredentialEnv("ANTHROPIC_API_KEY"),
5951
+ });
5906
5952
  // DIAGNOSTIC-ONLY: API-key resolution finished.
5907
5953
  workerStep("apikey_done");
5908
- // `options.apiKey` is the value the template constructed the plugin with
5909
- // (often wired from a deployment env var). Honor it as host-provided
5910
- // read-only configuration after scoped keys when deploy fallback is safe.
5911
- const hostApiKey = canUseDeployCredentialFallbackForRequest("ANTHROPIC_API_KEY")
5912
- ? (options.apiKey ?? readDeployCredentialEnv("ANTHROPIC_API_KEY"))
5913
- : undefined;
5914
- const effectiveApiKey = userApiKey ?? hostApiKey;
5915
5954
  // Resolve engine — per-request engine override takes priority
5916
5955
  // DIAGNOSTIC-ONLY: bracket engine resolution (Builder credential / app-default
5917
5956
  // settings reads inside resolveEngine).
@@ -5919,8 +5958,9 @@ export function createProductionAgentHandler(options) {
5919
5958
  let engine;
5920
5959
  try {
5921
5960
  engine = await resolveEngine({
5922
- engineOption: requestEngine ?? options.engine,
5961
+ engineOption,
5923
5962
  apiKey: effectiveApiKey,
5963
+ apiKeyEnvVar: effectiveApiKeyEnvVar,
5924
5964
  model: configuredModel,
5925
5965
  appId: options.appId,
5926
5966
  });
@@ -5928,6 +5968,7 @@ export function createProductionAgentHandler(options) {
5928
5968
  catch {
5929
5969
  engine = await resolveEngine({
5930
5970
  apiKey: effectiveApiKey,
5971
+ apiKeyEnvVar: effectiveApiKeyEnvVar,
5931
5972
  appId: options.appId,
5932
5973
  });
5933
5974
  }
@@ -181,13 +181,56 @@ export declare const SQL_SUBSCRIPTION_IDLE_POLL_MS = 500;
181
181
  export declare const SQL_SUBSCRIPTION_ACTIVE_GRACE_MS = 2000;
182
182
  /** Keep terminal/status probes at the historical cadence to bound DB work. */
183
183
  export declare const SQL_SUBSCRIPTION_STATUS_POLL_MS = 500;
184
+ /**
185
+ * Consecutive empty polls before the IDLE cadence starts decaying toward
186
+ * `SQL_SUBSCRIPTION_IDLE_MAX_POLL_MS`.
187
+ *
188
+ * The active grace already covers a streaming producer, so decay only ever
189
+ * applies to a subscriber watching a run that is producing nothing: a long tool
190
+ * call, a slow first token, or a wedged producer. Those cost one poll per
191
+ * 500ms each, forever, per subscriber — the single largest source of idle
192
+ * `agent_run_events` reads.
193
+ */
194
+ export declare const SQL_SUBSCRIPTION_IDLE_DECAY_AFTER_POLLS = 4;
195
+ /**
196
+ * Ceiling for the decayed idle cadence. Bounds the WORST-CASE added latency to
197
+ * the next token a quiet run eventually produces; a row already waiting when the
198
+ * timer fires is delivered immediately, so this is not added to a streaming run.
199
+ */
200
+ export declare const SQL_SUBSCRIPTION_IDLE_MAX_POLL_MS = 2000;
201
+ /**
202
+ * Cadence for the opportunistic `reapIfStale` probe inside the SSE poll loop,
203
+ * kept SEPARATE from (and much slower than) the status probe next to it.
204
+ *
205
+ * A reap can only ever act on a run whose liveness basis is older than the
206
+ * tightest staleness window any sweep enforces — `RUN_STALE_MS`, 15s — so
207
+ * running it on the 500ms status cadence issued ~30 rounds of
208
+ * `reconcileTerminalRunFromEvents` + reap-eligibility queries (2-4 round trips
209
+ * each) before the first one could possibly match a row. This cadence still
210
+ * detects a stale producer well inside the client's own idle timeout while
211
+ * cutting that probe traffic ~10x. `getRunById` deliberately stays on the fast
212
+ * status cadence: it is one indexed read and it is what closes the stream on a
213
+ * NORMAL finish, which must stay prompt.
214
+ */
215
+ export declare const SQL_SUBSCRIPTION_REAP_POLL_MS = 5000;
184
216
  /** Initial retry delay after a transient cross-isolate SQL polling failure. */
185
217
  export declare const SQL_SUBSCRIPTION_RETRY_BASE_MS = 250;
186
218
  /** Bound consecutive SQL polling failures so a dead subscription fails loud. */
187
219
  export declare const SQL_SUBSCRIPTION_MAX_CONSECUTIVE_FAILURES = 4;
188
220
  /** Cap retry delay even if the failure bound changes independently. */
189
221
  export declare const SQL_SUBSCRIPTION_RETRY_MAX_MS = 2000;
190
- export declare function resolveSqlSubscriptionPollMs(now: number, activePollUntil: number): number;
222
+ export declare function resolveSqlSubscriptionPollMs(now: number, activePollUntil: number, consecutiveEmptyPolls?: number): number;
223
+ /**
224
+ * Advance the empty-poll counter that drives `resolveSqlSubscriptionPollMs`'s
225
+ * decay.
226
+ *
227
+ * Only IDLE polls count. Counting the ~16 fast polls inside the active grace
228
+ * window would push the ladder to its cap the instant the grace expired, so a
229
+ * producer that pauses a few seconds between tokens would resume at the 2s cap
230
+ * instead of 500ms — a visible mid-stream stutter rather than the intended
231
+ * "this run has gone quiet" backoff.
232
+ */
233
+ export declare function nextSqlSubscriptionEmptyPolls(current: number, hadEvents: boolean, now: number, activePollUntil: number): number;
191
234
  export declare function resolveSqlSubscriptionRetryMs(consecutiveFailures: number): number;
192
235
  export interface StartRunOptions {
193
236
  /** Keep a request-scoped serverless invocation alive for this run. */