@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +6 -2
- package/corpus/templates/clips/changelog/2026-08-07-full-screen-recordings-on-a-multi-monitor-mac-now-let-you-pi.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/styles.css +13 -4
- package/corpus/templates/clips/desktop/src/app.tsx +38 -1
- package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +123 -104
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +165 -2
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/monitor-picker.tsx +70 -0
- package/corpus/templates/clips/desktop/src/styles.css +70 -11
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +163 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +141 -22
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +27 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +12 -1
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/registry.d.ts +15 -0
- package/dist/agent/engine/registry.js +39 -10
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.js +81 -40
- package/dist/agent/run-manager.d.ts +44 -1
- package/dist/agent/run-manager.js +70 -8
- package/dist/chat-threads/store.js +6 -4
- package/dist/client/use-db-sync.js +9 -1
- package/dist/db/ddl-guard.d.ts +8 -0
- package/dist/db/ddl-guard.js +148 -0
- package/dist/deploy/build.d.ts +36 -0
- package/dist/deploy/build.js +78 -5
- package/dist/integrations/webhook-handler.d.ts +3 -2
- package/dist/integrations/webhook-handler.js +8 -38
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/accept-pending.js +2 -2
- package/dist/org/auth-policy.js +4 -0
- package/dist/org/auto-join-domain.d.ts +9 -0
- package/dist/org/auto-join-domain.js +53 -4
- package/dist/org/context.js +12 -3
- package/dist/org/handlers.js +17 -5
- package/dist/org/request-org-cache.d.ts +24 -2
- package/dist/org/request-org-cache.js +57 -2
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/resources/store.d.ts +1 -1
- package/dist/resources/store.js +96 -7
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat-plugin.js +83 -37
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts +3 -0
- package/dist/server/auth.js +22 -1
- package/dist/server/collab-plugin.js +10 -4
- package/dist/server/complete-text.js +14 -7
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/request-context.d.ts +7 -1
- package/dist/server/session-email-cache.d.ts +14 -0
- package/dist/server/session-email-cache.js +48 -0
- package/dist/server/sweep-runtime.d.ts +22 -0
- package/dist/server/sweep-runtime.js +28 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.js +63 -7
- package/dist/shared/ttl-cache.d.ts +59 -0
- package/dist/shared/ttl-cache.js +43 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +3 -3
- package/dist/sharing/actions/list-resource-shares.js +4 -1
- package/dist/triggers/condition-evaluator.js +11 -27
- package/docs/content/deployment.mdx +48 -0
- package/docs/content/environment-variables.mdx +11 -0
- package/package.json +1 -1
|
@@ -57,6 +57,7 @@ import { createPollHandler } from "./poll.js";
|
|
|
57
57
|
import { createRealtimeTokenHandler } from "./realtime-token.js";
|
|
58
58
|
import { getRequestContext, hasRequestContext, runWithRequestContext, } from "./request-context.js";
|
|
59
59
|
import { findUnsupportedScopedKeyNames, saveKeyValuesToScopedSecrets, ScopedKeyStorageError, } from "./scoped-key-storage.js";
|
|
60
|
+
import { shouldDisableInProcessSweeps } from "./sweep-runtime.js";
|
|
60
61
|
import { createTranscribeVoiceHandler } from "./transcribe-voice.js";
|
|
61
62
|
import { createVoiceProvidersStatusHandler } from "./voice-providers-status.js";
|
|
62
63
|
import { createWorkspaceProviderOAuthHandler } from "./workspace-provider-oauth.js";
|
|
@@ -1260,6 +1261,8 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
1260
1261
|
// poll-time drain in run-code covers deployments where warm-instance
|
|
1261
1262
|
// timers rarely fire.
|
|
1262
1263
|
(() => {
|
|
1264
|
+
if (shouldDisableInProcessSweeps())
|
|
1265
|
+
return;
|
|
1263
1266
|
let lastSweep = 0;
|
|
1264
1267
|
const SWEEP_INTERVAL_MS = 2 * 60 * 1000;
|
|
1265
1268
|
setTimeout(() => {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } fro
|
|
|
14
14
|
export { verifyCaptcha, type CaptchaVerifyResult } from "./captcha.js";
|
|
15
15
|
export { getLocaleInitScript, parseAcceptLanguage, resolveLocaleFromRequest, type LocaleInitScriptOptions, type ResolveLocaleFromRequestOptions, type ResolvedRequestLocale, } from "../localization/server.js";
|
|
16
16
|
export { createProductionAgentHandler, type ActionEntry, type ScriptEntry, type ProductionAgentOptions, type ActionTool, type ScriptTool, type AgentMessage, type AgentChatRequest, type AgentChatEvent, type AgentChatAttachment, type AgentChatReference, type MentionProvider, type MentionProviderItem, type AgentLoopFinalResponseGuard, type AgentLoopFinalResponseGuardContext, type AgentLoopFinalResponseGuardResult, type AgentLoopToolCallSummary, type AgentLoopToolResultSummary, } from "../agent/index.js";
|
|
17
|
-
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, runAgentLoop, type AgentToolCallExecutionResult, type ExecuteAgentToolCallOptions, } from "../agent/production-agent.js";
|
|
17
|
+
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, getOwnerApiKeyForEngine, resolveOwnerEngineApiKey, runAgentLoop, type AgentToolCallExecutionResult, type ExecuteAgentToolCallOptions, type ResolvedOwnerApiKey, } from "../agent/production-agent.js";
|
|
18
18
|
export { mountRealtimeVoiceRoutes, realtimeVoiceSafetyIdentifier, REALTIME_VOICE_MAX_SDP_BYTES, REALTIME_VOICE_MAX_TOOL_BODY_BYTES, REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS, REALTIME_VOICE_SESSION_PATH, REALTIME_VOICE_TOOL_PATH, type MountRealtimeVoiceRoutesOptions, type RealtimeVoiceRequestContext, type RealtimeVoiceToolExecutionRequest, type RealtimeVoiceToolExecutionResult, } from "./realtime-voice.js";
|
|
19
19
|
export { getStoredModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
20
20
|
export { completeText, type CompleteTextMessage, type CompleteTextOptions, type CompleteTextResult, type CompleteTextUsage, } from "./complete-text.js";
|
package/dist/server/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } fro
|
|
|
14
14
|
export { verifyCaptcha } from "./captcha.js";
|
|
15
15
|
export { getLocaleInitScript, parseAcceptLanguage, resolveLocaleFromRequest, } from "../localization/server.js";
|
|
16
16
|
export { createProductionAgentHandler, } from "../agent/index.js";
|
|
17
|
-
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, runAgentLoop, } from "../agent/production-agent.js";
|
|
17
|
+
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, getOwnerApiKeyForEngine, resolveOwnerEngineApiKey, runAgentLoop, } from "../agent/production-agent.js";
|
|
18
18
|
export { mountRealtimeVoiceRoutes, realtimeVoiceSafetyIdentifier, REALTIME_VOICE_MAX_SDP_BYTES, REALTIME_VOICE_MAX_TOOL_BODY_BYTES, REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS, REALTIME_VOICE_SESSION_PATH, REALTIME_VOICE_TOOL_PATH, } from "./realtime-voice.js";
|
|
19
19
|
export { getStoredModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
20
20
|
export { completeText, } from "./complete-text.js";
|
|
@@ -41,8 +41,14 @@ export interface RequestRunContext {
|
|
|
41
41
|
} | null;
|
|
42
42
|
/** Resolved owner email (set by prepareRun). */
|
|
43
43
|
owner?: string;
|
|
44
|
-
/** Owner's
|
|
44
|
+
/** Owner's API key for this run's engine (set by prepareRun). */
|
|
45
45
|
userApiKey?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Env var `userApiKey` was issued for. Anything that hands the key to a
|
|
48
|
+
* fixed provider must check this first — the owner's active engine is not
|
|
49
|
+
* always Anthropic, and an unchecked key reaches the wrong endpoint.
|
|
50
|
+
*/
|
|
51
|
+
userApiKeyEnvVar?: string;
|
|
46
52
|
/** Thread ID for the current run (set by onRunStart). */
|
|
47
53
|
threadId?: string;
|
|
48
54
|
/** Run ID for the current run (set by onRunStart). */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function getCachedSessionEmail(token: string): string | undefined;
|
|
2
|
+
export declare function setCachedSessionEmail(token: string, email: string): void;
|
|
3
|
+
export declare function forgetCachedSessionEmail(token: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* Drop cached session resolutions. Call after ANY write to `sessions`.
|
|
6
|
+
*
|
|
7
|
+
* Clears every token rather than one: the bulk sign-out paths delete by email
|
|
8
|
+
* (`DELETE FROM sessions WHERE email = ?`) and do not know which tokens they
|
|
9
|
+
* removed, so a per-token eviction there would leave a signed-out session
|
|
10
|
+
* resolving for the rest of the TTL.
|
|
11
|
+
*/
|
|
12
|
+
export declare function invalidateSessionEmailCache(): void;
|
|
13
|
+
/** Test seam — the cache is module state, so suites must be able to clear it. */
|
|
14
|
+
export declare function __resetSessionEmailCacheForTests(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createTtlCache } from "../shared/ttl-cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-request cache for resolved legacy session tokens.
|
|
4
|
+
*
|
|
5
|
+
* A session token is immutable for its (30-day) lifetime, yet every
|
|
6
|
+
* authenticated request re-read it: production showed 494,848 of these for 3
|
|
7
|
+
* users, 1:1 with the request count. The TTL is far shorter than the session
|
|
8
|
+
* lifetime, so expiry is enforced at most `SESSION_EMAIL_TTL_MS` late — a 30-day
|
|
9
|
+
* boundary observed up to 15s late changes nothing.
|
|
10
|
+
*
|
|
11
|
+
* ONLY successful resolutions belong in here. A `null` — no such token, expired,
|
|
12
|
+
* revoked — must stay uncached so a sign-out or a token that has just been
|
|
13
|
+
* written is never answered from a stale negative, and so an auth failure can
|
|
14
|
+
* never be made sticky by a cache.
|
|
15
|
+
*
|
|
16
|
+
* Lives in its own module rather than in `auth.ts` because `auth.ts` imports
|
|
17
|
+
* `org/auth-policy.ts`, which also revokes sessions and so needs to invalidate
|
|
18
|
+
* this. Importing back into `auth.ts` from there would be a cycle.
|
|
19
|
+
*/
|
|
20
|
+
const SESSION_EMAIL_TTL_MS = 15_000;
|
|
21
|
+
const sessionEmailCache = createTtlCache({
|
|
22
|
+
ttlMs: SESSION_EMAIL_TTL_MS,
|
|
23
|
+
maxEntries: 4_096,
|
|
24
|
+
});
|
|
25
|
+
export function getCachedSessionEmail(token) {
|
|
26
|
+
return sessionEmailCache.get(token);
|
|
27
|
+
}
|
|
28
|
+
export function setCachedSessionEmail(token, email) {
|
|
29
|
+
sessionEmailCache.set(token, email);
|
|
30
|
+
}
|
|
31
|
+
export function forgetCachedSessionEmail(token) {
|
|
32
|
+
sessionEmailCache.delete(token);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Drop cached session resolutions. Call after ANY write to `sessions`.
|
|
36
|
+
*
|
|
37
|
+
* Clears every token rather than one: the bulk sign-out paths delete by email
|
|
38
|
+
* (`DELETE FROM sessions WHERE email = ?`) and do not know which tokens they
|
|
39
|
+
* removed, so a per-token eviction there would leave a signed-out session
|
|
40
|
+
* resolving for the rest of the TTL.
|
|
41
|
+
*/
|
|
42
|
+
export function invalidateSessionEmailCache() {
|
|
43
|
+
sessionEmailCache.clear();
|
|
44
|
+
}
|
|
45
|
+
/** Test seam — the cache is module state, so suites must be able to clear it. */
|
|
46
|
+
export function __resetSessionEmailCacheForTests() {
|
|
47
|
+
sessionEmailCache.clear();
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type SweepRuntimeEnvKey = "AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS";
|
|
2
|
+
type SweepRuntimeEnv = Partial<Record<SweepRuntimeEnvKey, string | undefined>>;
|
|
3
|
+
/**
|
|
4
|
+
* Kill switch for the `setInterval` backstop sweeps.
|
|
5
|
+
*
|
|
6
|
+
* These timers are correct on a long-lived Node server: one process, one timer
|
|
7
|
+
* set, one query per tick. On a serverless host they are billed per WARM
|
|
8
|
+
* CONTAINER, not per deployment — every warm instance independently runs the
|
|
9
|
+
* whole timer set, so the sweep query rate scales with instance count, which no
|
|
10
|
+
* in-process backoff or `lastSweep` guard can observe. A production sample
|
|
11
|
+
* showed one 2-minute sweep issuing 237k queries, implying ~11 containers each
|
|
12
|
+
* ticking forever against a database that then never gets to autosuspend.
|
|
13
|
+
*
|
|
14
|
+
* Default OFF (sweeps run), because on every host where the timers are the only
|
|
15
|
+
* thing driving recovery, disabling them silently strands queued work. Turn this
|
|
16
|
+
* on ONLY where a durable platform scheduler already drives the same recovery —
|
|
17
|
+
* the shape `isNetlifyRecurringJobsRuntime` + the emitted scheduled sweep
|
|
18
|
+
* already implement for recurring jobs, and which these three sweeps should
|
|
19
|
+
* eventually adopt instead of needing this switch at all.
|
|
20
|
+
*/
|
|
21
|
+
export declare function shouldDisableInProcessSweeps(env?: SweepRuntimeEnv): boolean;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Gating for the in-process backstop sweep timers (automation redispatch,
|
|
3
|
+
// agent-teams orphan reconciliation, durable sandbox executions).
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
function isTruthyEnv(value) {
|
|
6
|
+
return /^(1|true|yes|on)$/i.test(value?.trim() ?? "");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Kill switch for the `setInterval` backstop sweeps.
|
|
10
|
+
*
|
|
11
|
+
* These timers are correct on a long-lived Node server: one process, one timer
|
|
12
|
+
* set, one query per tick. On a serverless host they are billed per WARM
|
|
13
|
+
* CONTAINER, not per deployment — every warm instance independently runs the
|
|
14
|
+
* whole timer set, so the sweep query rate scales with instance count, which no
|
|
15
|
+
* in-process backoff or `lastSweep` guard can observe. A production sample
|
|
16
|
+
* showed one 2-minute sweep issuing 237k queries, implying ~11 containers each
|
|
17
|
+
* ticking forever against a database that then never gets to autosuspend.
|
|
18
|
+
*
|
|
19
|
+
* Default OFF (sweeps run), because on every host where the timers are the only
|
|
20
|
+
* thing driving recovery, disabling them silently strands queued work. Turn this
|
|
21
|
+
* on ONLY where a durable platform scheduler already drives the same recovery —
|
|
22
|
+
* the shape `isNetlifyRecurringJobsRuntime` + the emitted scheduled sweep
|
|
23
|
+
* already implement for recurring jobs, and which these three sweeps should
|
|
24
|
+
* eventually adopt instead of needing this switch at all.
|
|
25
|
+
*/
|
|
26
|
+
export function shouldDisableInProcessSweeps(env = process.env) {
|
|
27
|
+
return isTruthyEnv(env.AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS);
|
|
28
|
+
}
|
package/dist/settings/store.js
CHANGED
|
@@ -25,6 +25,24 @@ function requestSettingsCache() {
|
|
|
25
25
|
}
|
|
26
26
|
return cache;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-request memo of the WHOLE settings table for `getAllSettings`.
|
|
30
|
+
*
|
|
31
|
+
* The single-key path above was already request-cached; the full-table read was
|
|
32
|
+
* not, and production showed 98,479 of them — an entire `SELECT key, value FROM
|
|
33
|
+
* settings` per call, several times per request via the MCP client routes and
|
|
34
|
+
* org settings.
|
|
35
|
+
*
|
|
36
|
+
* Holds raw JSON strings, like the single-key cache, so callers can't mutate a
|
|
37
|
+
* shared parsed object. Dropped by every write path, because a request that
|
|
38
|
+
* writes a setting and then re-reads all of them must see its own write.
|
|
39
|
+
*/
|
|
40
|
+
const _requestAllSettingsCache = new WeakMap();
|
|
41
|
+
function invalidateRequestAllSettings() {
|
|
42
|
+
const ctx = getRequestContext();
|
|
43
|
+
if (ctx && typeof ctx === "object")
|
|
44
|
+
_requestAllSettingsCache.delete(ctx);
|
|
45
|
+
}
|
|
28
46
|
// Created lazily so this module can be evaluated in the browser dev graph
|
|
29
47
|
// without a top-level `new EventEmitter()` tripping Vite's externalized
|
|
30
48
|
// `node:events` stub. The emitter drives server-side SSE fan-out only.
|
|
@@ -152,6 +170,7 @@ export async function mutateSetting(key, updater, options) {
|
|
|
152
170
|
if (result.rowsAffected === 0)
|
|
153
171
|
continue;
|
|
154
172
|
requestSettingsCache()?.set(key, nextRaw);
|
|
173
|
+
invalidateRequestAllSettings();
|
|
155
174
|
settingsEmitter().emit("settings", {
|
|
156
175
|
source: "settings",
|
|
157
176
|
type: "change",
|
|
@@ -173,6 +192,7 @@ export async function putSetting(key, value, options) {
|
|
|
173
192
|
args: [key, JSON.stringify(value), Date.now()],
|
|
174
193
|
});
|
|
175
194
|
requestSettingsCache()?.set(key, JSON.stringify(value));
|
|
195
|
+
invalidateRequestAllSettings();
|
|
176
196
|
settingsEmitter().emit("settings", {
|
|
177
197
|
source: "settings",
|
|
178
198
|
type: "change",
|
|
@@ -189,6 +209,7 @@ export async function deleteSetting(key, options) {
|
|
|
189
209
|
args: [key],
|
|
190
210
|
});
|
|
191
211
|
requestSettingsCache()?.set(key, null);
|
|
212
|
+
invalidateRequestAllSettings();
|
|
192
213
|
if (result.rowsAffected > 0) {
|
|
193
214
|
settingsEmitter().emit("settings", {
|
|
194
215
|
source: "settings",
|
|
@@ -215,6 +236,7 @@ export async function deleteSettingsByPrefix(prefix, options) {
|
|
|
215
236
|
args: [`${escaped}%`],
|
|
216
237
|
});
|
|
217
238
|
requestSettingsCache()?.clear();
|
|
239
|
+
invalidateRequestAllSettings();
|
|
218
240
|
if (result.rowsAffected > 0) {
|
|
219
241
|
settingsEmitter().emit("settings", {
|
|
220
242
|
source: "settings",
|
|
@@ -226,13 +248,47 @@ export async function deleteSettingsByPrefix(prefix, options) {
|
|
|
226
248
|
return result.rowsAffected;
|
|
227
249
|
}
|
|
228
250
|
export async function getAllSettings() {
|
|
229
|
-
await
|
|
230
|
-
const client = getDbExec();
|
|
231
|
-
const table = settingsTable();
|
|
232
|
-
const { rows } = await client.execute(`SELECT key, value FROM ${table}`);
|
|
251
|
+
const raw = await loadAllSettingsRaw();
|
|
233
252
|
const result = {};
|
|
234
|
-
for (const
|
|
235
|
-
result[
|
|
236
|
-
}
|
|
253
|
+
for (const [key, value] of raw)
|
|
254
|
+
result[key] = JSON.parse(value);
|
|
237
255
|
return result;
|
|
238
256
|
}
|
|
257
|
+
async function loadAllSettingsRaw() {
|
|
258
|
+
const ctx = getRequestContext();
|
|
259
|
+
const cached = ctx && typeof ctx === "object"
|
|
260
|
+
? _requestAllSettingsCache.get(ctx)
|
|
261
|
+
: undefined;
|
|
262
|
+
if (cached)
|
|
263
|
+
return cached;
|
|
264
|
+
const load = (async () => {
|
|
265
|
+
await ensureTable();
|
|
266
|
+
const client = getDbExec();
|
|
267
|
+
const table = settingsTable();
|
|
268
|
+
const { rows } = await client.execute(`SELECT key, value FROM ${table}`);
|
|
269
|
+
const raw = new Map();
|
|
270
|
+
for (const row of rows)
|
|
271
|
+
raw.set(row.key, row.value);
|
|
272
|
+
// Seed the single-key cache so a `getSetting` after `getAllSettings` in the
|
|
273
|
+
// same request is free rather than another round trip. Only fills keys it
|
|
274
|
+
// does not already hold: a value written earlier in this request is already
|
|
275
|
+
// written through there and must win over this snapshot.
|
|
276
|
+
const perKey = requestSettingsCache();
|
|
277
|
+
if (perKey) {
|
|
278
|
+
for (const [key, value] of raw) {
|
|
279
|
+
if (!perKey.has(key))
|
|
280
|
+
perKey.set(key, value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return raw;
|
|
284
|
+
})();
|
|
285
|
+
if (ctx && typeof ctx === "object") {
|
|
286
|
+
// Evicted on failure so one transient error is not memoized as the answer
|
|
287
|
+
// for the rest of the request.
|
|
288
|
+
_requestAllSettingsCache.set(ctx, load.catch((err) => {
|
|
289
|
+
_requestAllSettingsCache.delete(ctx);
|
|
290
|
+
throw err;
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
return load;
|
|
294
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded process-level TTL cache for answers that are expensive to re-fetch
|
|
3
|
+
* and safe to serve slightly stale.
|
|
4
|
+
*
|
|
5
|
+
* ## Which cache do I want?
|
|
6
|
+
*
|
|
7
|
+
* This repo has exactly two general caching patterns. Reach for an existing one
|
|
8
|
+
* rather than hand-rolling a third `Map` with timestamps in it:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Request-scoped** — `WeakMap<RequestContext, Map<...>>`, freed with the
|
|
11
|
+
* request. Use when repeated reads within ONE request must collapse but no
|
|
12
|
+
* answer may outlive it. See `settings/store.ts` (`requestSettingsCache`),
|
|
13
|
+
* `org/request-org-cache.ts`, `secrets/storage.ts`.
|
|
14
|
+
* 2. **Process-scoped with a TTL** — this module. Use when every request
|
|
15
|
+
* re-reads the same value and staleness bounded by the TTL is acceptable.
|
|
16
|
+
* Every consumer must also wire an invalidation path for the writes that
|
|
17
|
+
* change the answer; the TTL is a backstop, not the correctness story.
|
|
18
|
+
*
|
|
19
|
+
* Two caches deliberately do NOT use this and should stay as they are, because
|
|
20
|
+
* flattening them into this shape would lose behavior they depend on:
|
|
21
|
+
*
|
|
22
|
+
* - `server/poll.ts`'s collab access cache — different TTL per outcome
|
|
23
|
+
* (allow vs deny), a per-resource invalidation epoch, and in-flight request
|
|
24
|
+
* dedup.
|
|
25
|
+
* - `demo/redact.ts` — a bidirectional memo (forward map plus a produced-value
|
|
26
|
+
* reverse index) with refresh-on-read recency, needed for round-trip
|
|
27
|
+
* stability.
|
|
28
|
+
*
|
|
29
|
+
* The per-request memos elsewhere in this codebase (`event.context` caches,
|
|
30
|
+
* `requestSettingsCache`) collapse repeated reads WITHIN one request. They do
|
|
31
|
+
* nothing for a value every request re-reads identically — a session token's
|
|
32
|
+
* email, an account's org memberships — which on a remote Postgres is a round
|
|
33
|
+
* trip per request per value, forever.
|
|
34
|
+
*
|
|
35
|
+
* ## Only cache successful reads
|
|
36
|
+
*
|
|
37
|
+
* Never `set` a value that stands for "I could not read this". A cached
|
|
38
|
+
* "unreadable" is indistinguishable from a cached "absent" to every later
|
|
39
|
+
* caller, which is exactly how a transient blip turns into a
|
|
40
|
+
* permanent-looking wrong answer. Leave failures on the uncached path so they
|
|
41
|
+
* stay loud and retryable.
|
|
42
|
+
*
|
|
43
|
+
* ## Bounded on purpose
|
|
44
|
+
*
|
|
45
|
+
* Keys here are user-supplied (session tokens, emails), so an unbounded map is
|
|
46
|
+
* a memory leak that grows with traffic. `maxEntries` evicts oldest-written
|
|
47
|
+
* first once the cap is reached.
|
|
48
|
+
*/
|
|
49
|
+
export interface TtlCache<V> {
|
|
50
|
+
get(key: string): V | undefined;
|
|
51
|
+
set(key: string, value: V): void;
|
|
52
|
+
delete(key: string): void;
|
|
53
|
+
clear(): void;
|
|
54
|
+
readonly size: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function createTtlCache<V>(options: {
|
|
57
|
+
ttlMs: number;
|
|
58
|
+
maxEntries: number;
|
|
59
|
+
}): TtlCache<V>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function createTtlCache(options) {
|
|
2
|
+
const { ttlMs, maxEntries } = options;
|
|
3
|
+
if (!(ttlMs > 0)) {
|
|
4
|
+
throw new Error(`createTtlCache: ttlMs must be positive, got ${ttlMs}`);
|
|
5
|
+
}
|
|
6
|
+
if (!(maxEntries > 0)) {
|
|
7
|
+
throw new Error(`createTtlCache: maxEntries must be positive, got ${maxEntries}`);
|
|
8
|
+
}
|
|
9
|
+
const entries = new Map();
|
|
10
|
+
return {
|
|
11
|
+
get(key) {
|
|
12
|
+
const hit = entries.get(key);
|
|
13
|
+
if (!hit)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (hit.expiresAt <= Date.now()) {
|
|
16
|
+
entries.delete(key);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return hit.value;
|
|
20
|
+
},
|
|
21
|
+
set(key, value) {
|
|
22
|
+
// Delete before insert so Map insertion order tracks the newest write,
|
|
23
|
+
// which is what the eviction loop below reads as "oldest".
|
|
24
|
+
entries.delete(key);
|
|
25
|
+
entries.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
26
|
+
while (entries.size > maxEntries) {
|
|
27
|
+
const oldest = entries.keys().next();
|
|
28
|
+
if (oldest.done)
|
|
29
|
+
break;
|
|
30
|
+
entries.delete(oldest.value);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
delete(key) {
|
|
34
|
+
entries.delete(key);
|
|
35
|
+
},
|
|
36
|
+
clear() {
|
|
37
|
+
entries.clear();
|
|
38
|
+
},
|
|
39
|
+
get size() {
|
|
40
|
+
return entries.size;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -12,9 +12,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
12
12
|
orgId?: undefined;
|
|
13
13
|
role?: undefined;
|
|
14
14
|
} | {
|
|
15
|
-
ownerEmail:
|
|
16
|
-
orgId:
|
|
17
|
-
visibility:
|
|
15
|
+
ownerEmail: string;
|
|
16
|
+
orgId: string;
|
|
17
|
+
visibility: import("../schema.js").Visibility;
|
|
18
18
|
role: "owner" | import("../schema.js").ShareRole;
|
|
19
19
|
shares: any;
|
|
20
20
|
policy: {
|
|
@@ -43,7 +43,10 @@ export default defineAction({
|
|
|
43
43
|
allowPublic: reg.allowPublic !== false,
|
|
44
44
|
requireOrgMemberForUserShares: reg.requireOrgMemberForUserShares === true,
|
|
45
45
|
};
|
|
46
|
-
|
|
46
|
+
// Only `ownerEmail`/`orgId`/`visibility` are read below, all of which the
|
|
47
|
+
// projected load carries — so the share dialog never pulls a resource's
|
|
48
|
+
// body blob just to render who it is shared with.
|
|
49
|
+
const access = await resolveAccess(args.resourceType, args.resourceId, undefined, { skipResourceBody: true });
|
|
47
50
|
if (!access)
|
|
48
51
|
return { ownerEmail: null, visibility: null, shares: [], policy };
|
|
49
52
|
const db = reg.getDb();
|
|
@@ -14,16 +14,22 @@
|
|
|
14
14
|
* invalidated wholesale by bumping CONDITION_EVAL_VERSION.
|
|
15
15
|
*/
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
|
+
import { createTtlCache } from "../shared/ttl-cache.js";
|
|
17
18
|
/**
|
|
18
19
|
* Bumped whenever the prompt template, model, or hardening logic changes.
|
|
19
20
|
* Included in the cache key so cached "yes" answers from a previous
|
|
20
21
|
* (potentially weaker) prompt don't satisfy conditions in the new prompt.
|
|
21
22
|
*/
|
|
22
23
|
const CONDITION_EVAL_VERSION = "v2";
|
|
23
|
-
//
|
|
24
|
-
|
|
24
|
+
// Bounded TTL cache: hash → classifier result. Uses the shared primitive so
|
|
25
|
+
// there is one implementation of "expire and cap an in-memory map" rather than
|
|
26
|
+
// one per call site; see `shared/ttl-cache.ts` for which pattern to use where.
|
|
25
27
|
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
26
28
|
const MAX_CACHE_SIZE = 500;
|
|
29
|
+
const _cache = createTtlCache({
|
|
30
|
+
ttlMs: CACHE_TTL_MS,
|
|
31
|
+
maxEntries: MAX_CACHE_SIZE,
|
|
32
|
+
});
|
|
27
33
|
function cacheKey(condition, payload) {
|
|
28
34
|
// Salt the cache key with the prompt version + a separate hash of the
|
|
29
35
|
// payload so two callers can't cross-pollute each other's cache via
|
|
@@ -41,26 +47,6 @@ function cacheKey(condition, payload) {
|
|
|
41
47
|
const raw = `${CONDITION_EVAL_VERSION}|${condition}|${payloadHash}`;
|
|
42
48
|
return createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
43
49
|
}
|
|
44
|
-
function pruneCache() {
|
|
45
|
-
if (_cache.size <= MAX_CACHE_SIZE)
|
|
46
|
-
return;
|
|
47
|
-
const now = Date.now();
|
|
48
|
-
for (const [key, entry] of _cache) {
|
|
49
|
-
if (entry.expiresAt < now)
|
|
50
|
-
_cache.delete(key);
|
|
51
|
-
}
|
|
52
|
-
// If still too large, drop oldest entries
|
|
53
|
-
if (_cache.size > MAX_CACHE_SIZE) {
|
|
54
|
-
const excess = _cache.size - MAX_CACHE_SIZE;
|
|
55
|
-
let deleted = 0;
|
|
56
|
-
for (const key of _cache.keys()) {
|
|
57
|
-
if (deleted >= excess)
|
|
58
|
-
break;
|
|
59
|
-
_cache.delete(key);
|
|
60
|
-
deleted++;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
50
|
/**
|
|
65
51
|
* Evaluate whether a natural-language condition matches an event payload.
|
|
66
52
|
* Returns true if the condition is empty/undefined (unconditional trigger).
|
|
@@ -70,12 +56,10 @@ export async function evaluateCondition(condition, payload, apiKey) {
|
|
|
70
56
|
return true;
|
|
71
57
|
const key = cacheKey(condition, payload);
|
|
72
58
|
const cached = _cache.get(key);
|
|
73
|
-
if (cached
|
|
74
|
-
return cached
|
|
75
|
-
}
|
|
59
|
+
if (cached !== undefined)
|
|
60
|
+
return cached;
|
|
76
61
|
const result = await callHaikuClassifier(condition, payload, apiKey);
|
|
77
|
-
|
|
78
|
-
_cache.set(key, { result, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
62
|
+
_cache.set(key, result);
|
|
79
63
|
return result;
|
|
80
64
|
}
|
|
81
65
|
async function callHaikuClassifier(condition, payload, apiKey) {
|
|
@@ -433,6 +433,49 @@ npx @agent-native/core@latest deploy --preset netlify
|
|
|
433
433
|
|
|
434
434
|
The workspace build writes static assets under `dist/_workspace_static/` and routes each app to its own Netlify function without forced asset redirects, so files like `/mail/assets/...` are served statically before the server function handles app routes.
|
|
435
435
|
|
|
436
|
+
### Keep-warm and scale-to-zero databases {#netlify-keep-warm}
|
|
437
|
+
|
|
438
|
+
The Netlify build emits a scheduled function that requests `/_agent-native/health` **every minute**. That keeps the server function and its database warm so the next visitor after an idle period doesn't pay a cold start (1–5s on Neon). On provisioned compute this is the right default.
|
|
439
|
+
|
|
440
|
+
**On a metered scale-to-zero database it is the wrong default.** A once-a-minute wake means autosuspend never fires, so the database bills and quota-limits as though it were awake continuously with nobody using the app. On a free-tier Neon this exhausts the compute quota, and the database then rejects **all** reads mid-session — the symptom is a dead app, not a slow one.
|
|
441
|
+
|
|
442
|
+
Three knobs, all opt-in, defaults unchanged:
|
|
443
|
+
|
|
444
|
+
| Variable | Effect |
|
|
445
|
+
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
446
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM` | Skips the scheduled function entirely. The database is free to autosuspend; the next visitor after an idle period pays its cold start. |
|
|
447
|
+
| `AGENT_NATIVE_KEEP_WARM_SCHEDULE` | Overrides the cadence with a 5-field cron expression, e.g. `*/5 * * * *`. An unparseable value **fails the build** rather than silently reverting to once a minute. |
|
|
448
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND` | Drops only the background-function warm, keeping the server warm. Warming `server` is one request; warming `-background` starts a fresh container that re-probes the schema. |
|
|
449
|
+
|
|
450
|
+
If you are on a free or otherwise quota-limited database tier, set `AGENT_NATIVE_DISABLE_KEEP_WARM=1` or pick a slower cadence.
|
|
451
|
+
|
|
452
|
+
### On-demand schema DDL {#skip-ensure-tables}
|
|
453
|
+
|
|
454
|
+
Each store calls `ensureTable()` on its first database touch, which probes
|
|
455
|
+
`information_schema` / `pg_indexes` and issues `CREATE TABLE` / `ADD COLUMN` /
|
|
456
|
+
`CREATE INDEX` only for what is genuinely missing. On a long-lived Node server
|
|
457
|
+
that cost is paid once per boot and is invisible. On serverless, "first touch" is
|
|
458
|
+
**every cold start**, across ~57 stores — so a cold request can pay a few hundred
|
|
459
|
+
serial round trips before it does any real work.
|
|
460
|
+
|
|
461
|
+
Two things reduce it:
|
|
462
|
+
|
|
463
|
+
- The probes are answered from **one batched introspection pass per database**
|
|
464
|
+
(two queries), instead of one query per table, column, and index. This is
|
|
465
|
+
automatic and needs no configuration.
|
|
466
|
+
- `AGENT_NATIVE_SKIP_ENSURE_TABLES=1` skips the probe-and-DDL machinery
|
|
467
|
+
altogether, which is the largest cold-start latency win available.
|
|
468
|
+
|
|
469
|
+
Set `AGENT_NATIVE_SKIP_ENSURE_TABLES=1` when your database schema is already
|
|
470
|
+
migrated and you do not want application boot creating or altering tables — the
|
|
471
|
+
normal case for a production deployment with a real migration step.
|
|
472
|
+
|
|
473
|
+
<Callout tone="risk">
|
|
474
|
+
|
|
475
|
+
**This flag does not and cannot fail closed.** If the schema is in fact missing, the first real query fails loudly rather than being repaired on the fly — which is the point, but it means you own the migration story. Do not set it on a deployment that relies on application boot to provision its tables.
|
|
476
|
+
|
|
477
|
+
</Callout>
|
|
478
|
+
|
|
436
479
|
## Cloudflare Pages {#cloudflare-pages}
|
|
437
480
|
|
|
438
481
|
```ts filename="vite.config.ts"
|
|
@@ -532,6 +575,11 @@ hosting behavior.
|
|
|
532
575
|
| `APP_URL` | Optional canonical public origin. Checked ahead of `BETTER_AUTH_URL` when resolving the A2A JWT audience, the OAuth public origin, self-dispatch webhook targets, and generated resource links. Set it explicitly on preview/staging deploys where the request host is unreliable. |
|
|
533
576
|
| `AGENT_PROD_CODE_EXECUTION` | Optional production code-execution mode: `off` (default), `sandboxed`, or `trusted`. See [Production Code Execution](#production-code-execution). |
|
|
534
577
|
| `AGENT_NATIVE_SSR_CACHE` | Deployment-wide SSR shell cache policy: unset/`on` keeps the default hard-cache, `off` sends `no-store`, or a duration such as `30s` / `5m` sets a shorter freshness. See [SSR Caching](#ssr-caching). |
|
|
578
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM` | Skip the Netlify keep-warm scheduled function so a scale-to-zero database can autosuspend. See [Keep-warm](#netlify-keep-warm). |
|
|
579
|
+
| `AGENT_NATIVE_KEEP_WARM_SCHEDULE` | Override the keep-warm cadence with a 5-field cron expression (default `* * * * *`). An unparseable value fails the build. See [Keep-warm](#netlify-keep-warm). |
|
|
580
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND` | Drop only the background-function warm and keep the server warm. See [Keep-warm](#netlify-keep-warm). |
|
|
581
|
+
| `AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS` | Turn off the in-process backstop sweep timers (automation redispatch, agent-teams reconciliation, sandbox-execution recovery). On serverless these run per warm container, so their query rate scales with instance count. Set it **only** where a durable scheduler already drives the same recovery, or queued work will not be retried. |
|
|
582
|
+
| `AGENT_NATIVE_SKIP_ENSURE_TABLES` | Skip on-demand schema DDL entirely. Removes the schema-probe round trips every cold start pays before doing any work. Requires that your schema is already migrated — see [On-demand schema DDL](#skip-ensure-tables). |
|
|
535
583
|
| `AGENT_NATIVE_BUILDER_RELAY_SECRET` | Dedicated 32+ char shared HMAC secret for preview-safe Builder authorization relay. Set the same value on the approved corporate callback deployment and preview deployments only when authorization must land in an isolated preview database. |
|
|
536
584
|
| `AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS` | Corporate callback-only, comma-separated allowlist of exact trusted preview origins (for example, `https://0123456789abcdef01234567--content.netlify.app`). Set it only on the corporate callback deployment. Netlify origins must use an immutable, deploy-specific 24-hex permalink; mutable `deploy-preview-*` aliases, wildcards, and domain suffixes are rejected. |
|
|
537
585
|
|
|
@@ -40,6 +40,8 @@ deployment-specific values in the environment or scoped secret store.
|
|
|
40
40
|
| `WORKSPACE_SECRETS_ENCRYPTION_KEY` | Stable encryption key for a workspace-shared secrets vault. | The same stable key in every app that reads the vault. |
|
|
41
41
|
| `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` | Previous workspace key during rotation. | The old key only; remove it after migration. |
|
|
42
42
|
| `ANTHROPIC_API_KEY` | Optional deploy-level fallback for the embedded production agent. | An API key; prefer scoped credentials for multi-tenant apps. |
|
|
43
|
+
| `AGENT_ENGINE` | Explicitly selects the agent engine instead of auto-detecting from provider keys. | A registered engine name such as `anthropic`, `ai-sdk:openai`, `builder`, or `codex-cli`. |
|
|
44
|
+
| `AGENT_ENGINE_PREFER_BYO_KEY` | Prefers a provider key you set over the managed Builder gateway during auto-detection. | `true` or `1`; Builder still applies as the fallback when no provider key is set. |
|
|
43
45
|
| `AUTH_MODE` | Selects the local authentication behavior used by CLI and agent flows. | Unset or `local`; this is not a browser login bypass. |
|
|
44
46
|
| `AUTH_DISABLED` | Disables auth for local or preview-only work. | `true` or `1`; never use on a public production app. |
|
|
45
47
|
| `AUTH_MAGIC_LINK` | Controls the password-first versus magic-link sign-in path. | `0` keeps password-first sign-in; otherwise a configured email transport can enable magic links. |
|
|
@@ -47,6 +49,11 @@ deployment-specific values in the environment or scoped secret store.
|
|
|
47
49
|
| `ACCESS_TOKEN` / `ACCESS_TOKENS` | Static bearer fallback for MCP and Connect clients. | One token or a delimited token list; these are not browser auth. |
|
|
48
50
|
| `AGENT_PROD_CODE_EXECUTION` | Production code-execution policy. | `off`, `sandboxed`, or `trusted`. See [Production Code Execution](/docs/deployment#production-code-execution). |
|
|
49
51
|
| `AGENT_NATIVE_SSR_CACHE` | Deployment-wide public SSR shell cache policy. | Unset/`on`, `off`, or a duration such as `30s` or `5m`. See [SSR Caching](/docs/deployment#ssr-caching). |
|
|
52
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM` | Lets a scale-to-zero database autosuspend instead of being woken every minute. | `1`; see [Keep-warm](/docs/deployment#netlify-keep-warm). |
|
|
53
|
+
| `AGENT_NATIVE_KEEP_WARM_SCHEDULE` | Keep-warm cadence override. | A 5-field cron expression such as `*/5 * * * *`; an unparseable value fails the build. |
|
|
54
|
+
| `AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND` | Drops only the background-function warm and keeps the server warm. | `1`; see [Keep-warm](/docs/deployment#netlify-keep-warm). |
|
|
55
|
+
| `AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS` | Turns off the in-process backstop sweep timers where a durable scheduler drives the same work. | `1`; queued work is not retried without another driver. |
|
|
56
|
+
| `AGENT_NATIVE_SKIP_ENSURE_TABLES` | Skips on-demand schema DDL for an already-migrated database. | `1`; see [On-demand schema DDL](/docs/deployment#skip-ensure-tables). You own migrations when this is set. |
|
|
50
57
|
| `COOKIE_DOMAIN` | Shares auth cookies across same-database subdomains when explicitly configured. | A cookie domain. |
|
|
51
58
|
| `CORS_ALLOWED_ORIGINS` | Cross-origin request allowlist. | A comma-separated list of exact origins. |
|
|
52
59
|
| `WEBHOOK_BASE_URL` | Optional public base URL for self-callback and webhook targets. | An absolute public `http(s)` origin. |
|
|
@@ -90,8 +97,12 @@ specific Google API integrations; they are not required for browser sign-in.
|
|
|
90
97
|
These are deploy-level fallbacks for single-tenant or local use. Hosted,
|
|
91
98
|
multi-user apps should prefer the scoped credential and connection store.
|
|
92
99
|
|
|
100
|
+
Every provider key below selects its engine on its own: dropping one in is
|
|
101
|
+
enough, and `AGENT_ENGINE` is only needed to pick between several.
|
|
102
|
+
|
|
93
103
|
| Variable | What it controls | Supported values or format |
|
|
94
104
|
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------- |
|
|
105
|
+
| `ANTHROPIC_API_KEY` | Claude model fallback for the built-in AI engine. | An Anthropic API key. |
|
|
95
106
|
| `OPENAI_API_KEY` | OpenAI model, realtime voice, or transcription fallback. | An OpenAI API key. |
|
|
96
107
|
| `OPENAI_BASE_URL` | OpenAI-compatible endpoint used with `OPENAI_API_KEY`. | An absolute `http(s)` URL without embedded credentials. |
|
|
97
108
|
| `GOOGLE_GENERATIVE_AI_API_KEY` | Gemini model fallback for the built-in AI engine. | A Google AI API key. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.2",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|