@agent-native/core 0.131.6 → 0.131.9
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/core/CHANGELOG.md +31 -0
- package/corpus/core/docs/content/deployment.mdx +12 -10
- package/corpus/core/docs/content/security.mdx +2 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/invoke.ts +4 -2
- package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
- package/corpus/core/src/agent/engine/registry.ts +61 -9
- package/corpus/core/src/application-state/store.ts +18 -31
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/guards/no-env-credentials.ts +2 -0
- package/corpus/core/src/scripts/call-agent.ts +4 -2
- package/corpus/core/src/secrets/crypto.ts +44 -15
- package/corpus/core/src/secrets/storage.ts +6 -4
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +2 -0
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/AGENTS.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +35 -0
- package/corpus/templates/slides/actions/create-deck.ts +1 -1
- package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +4 -2
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
- package/dist/agent/action-continuation-guidance.js +2 -1
- package/dist/agent/action-continuation-guidance.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +59 -12
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +20 -36
- package/dist/application-state/store.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/guards/no-env-credentials.d.ts.map +1 -1
- package/dist/guards/no-env-credentials.js +2 -0
- package/dist/guards/no-env-credentials.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/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +4 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/crypto.d.ts +14 -9
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +42 -16
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts +6 -4
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +6 -4
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +2 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/deployment.mdx +12 -10
- package/docs/content/security.mdx +2 -2
- package/package.json +1 -1
- package/src/a2a/invoke.ts +4 -2
- package/src/agent/action-continuation-guidance.ts +2 -1
- package/src/agent/engine/registry.ts +61 -9
- package/src/application-state/store.ts +18 -31
- package/src/cli/create.ts +115 -16
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/frame-protocol.ts +5 -1
- package/src/guards/no-env-credentials.ts +2 -0
- package/src/scripts/call-agent.ts +4 -2
- package/src/secrets/crypto.ts +44 -15
- package/src/secrets/storage.ts +6 -4
- package/src/server/agent-chat/action-filters-a2a.ts +2 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +14 -9
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -9
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -9
- package/src/workspace-files/tool.ts +18 -11
|
@@ -227,15 +227,20 @@ Core routes plugin mounts these under `/_agent-native/secrets/` automatically:
|
|
|
227
227
|
prefer `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (for example,
|
|
228
228
|
`ANALYTICS_SECRETS_ENCRYPTION_KEY`), then `SECRETS_ENCRYPTION_KEY`, then
|
|
229
229
|
`BETTER_AUTH_SECRET`.
|
|
230
|
-
- Workspace-shared `app_secrets` prefer
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
- Workspace-shared `app_secrets` prefer
|
|
231
|
+
`WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy shared
|
|
232
|
+
`SECRETS_ENCRYPTION_KEY`, then a purpose-derived key from `A2A_SECRET`.
|
|
233
|
+
Better Auth and app-scoped keys remain legacy read candidates; a successful
|
|
234
|
+
legacy decrypt is compare-and-swap migrated to the preferred shared key
|
|
235
|
+
without changing the row timestamp.
|
|
236
|
+
- Set the same stable `WORKSPACE_SECRETS_ENCRYPTION_KEY` in every app that
|
|
237
|
+
reads a shared vault. It is intentionally separate from app-local OAuth
|
|
238
|
+
encryption. If the vault still relies on `A2A_SECRET`, rotating A2A material
|
|
239
|
+
also rotates its encryption key. Add the stable vault key everywhere and
|
|
240
|
+
read/migrate existing rows before rotating A2A.
|
|
241
|
+
- To rotate the dedicated workspace key itself, deploy the new value together
|
|
242
|
+
with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS=<old value>`, let reads
|
|
243
|
+
migrate ciphertext, then remove the previous key after the migration window.
|
|
239
244
|
- If no configured key material exists, development uses a machine-local
|
|
240
245
|
fallback and logs a one-time warning. Production fails closed.
|
|
241
246
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/workspace-files/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA2BhE,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/workspace-files/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA2BhE,wBAAgB,wBAAwB,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA6OtE"}
|
|
@@ -69,12 +69,15 @@ export function createWorkspaceFilesTool() {
|
|
|
69
69
|
description: 'MIME type for new files. Default: "text/plain". Use "application/json" for JSON, "text/markdown" for Markdown.',
|
|
70
70
|
},
|
|
71
71
|
offset: {
|
|
72
|
-
type: "
|
|
73
|
-
|
|
72
|
+
type: "integer",
|
|
73
|
+
minimum: 0,
|
|
74
|
+
description: "Non-negative character offset to start reading from (for paging large files). Default: 0.",
|
|
74
75
|
},
|
|
75
76
|
maxChars: {
|
|
76
|
-
type: "
|
|
77
|
-
|
|
77
|
+
type: "integer",
|
|
78
|
+
minimum: 1,
|
|
79
|
+
maximum: MAX_READ_CHARS,
|
|
80
|
+
description: `Positive maximum characters to return when reading. Default: ${DEFAULT_READ_CHARS}. Max: ${MAX_READ_CHARS}.`,
|
|
78
81
|
},
|
|
79
82
|
pattern: {
|
|
80
83
|
type: "string",
|
|
@@ -135,14 +138,17 @@ export function createWorkspaceFilesTool() {
|
|
|
135
138
|
if (!path)
|
|
136
139
|
return "Error: path is required for read.";
|
|
137
140
|
const rawOffset = Number(args.offset);
|
|
138
|
-
const offset = Number.isFinite(rawOffset) && rawOffset > 0
|
|
141
|
+
const offset = Number.isFinite(rawOffset) && rawOffset > 0
|
|
142
|
+
? Math.floor(rawOffset)
|
|
143
|
+
: 0;
|
|
139
144
|
const rawMax = Number(args.maxChars);
|
|
140
145
|
const maxChars = Number.isFinite(rawMax) && rawMax > 0
|
|
141
|
-
? Math.min(rawMax, MAX_READ_CHARS)
|
|
146
|
+
? Math.min(Math.max(1, Math.floor(rawMax)), MAX_READ_CHARS)
|
|
142
147
|
: DEFAULT_READ_CHARS;
|
|
148
|
+
// The sentinel character distinguishes an exact page from a truncated one.
|
|
143
149
|
const file = await readWorkspaceFile(scope, path, {
|
|
144
150
|
offset,
|
|
145
|
-
maxChars,
|
|
151
|
+
maxChars: maxChars + 1,
|
|
146
152
|
});
|
|
147
153
|
if (!file) {
|
|
148
154
|
return JSON.stringify({
|
|
@@ -150,19 +156,20 @@ export function createWorkspaceFilesTool() {
|
|
|
150
156
|
error: `File not found: "${path}"`,
|
|
151
157
|
});
|
|
152
158
|
}
|
|
153
|
-
const truncated = file.content.length
|
|
159
|
+
const truncated = file.content.length > maxChars;
|
|
160
|
+
const content = file.content.slice(0, maxChars);
|
|
154
161
|
return JSON.stringify({
|
|
155
162
|
ok: true,
|
|
156
163
|
path: file.path,
|
|
157
164
|
contentType: file.contentType,
|
|
158
165
|
sizeBytes: file.sizeBytes,
|
|
159
166
|
updatedAt: file.updatedAt,
|
|
160
|
-
content
|
|
167
|
+
content,
|
|
161
168
|
...(truncated
|
|
162
169
|
? {
|
|
163
170
|
truncated: true,
|
|
164
|
-
nextOffset: offset +
|
|
165
|
-
hint: `File has more content. Call again with offset: ${offset +
|
|
171
|
+
nextOffset: offset + content.length,
|
|
172
|
+
hint: `File has more content. Call again with offset: ${offset + content.length}`,
|
|
166
173
|
}
|
|
167
174
|
: {}),
|
|
168
175
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/workspace-files/tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `workspace-files` bridge tool.\n *\n * A single tool with an `action` discriminator covering write, append, read,\n * list, delete, and grep. It is kept for `run-code` workspaceRead/workspaceWrite\n * compatibility and delegates storage to the Resources table.\n *\n * Scope is automatically resolved from the active request context:\n * - org scope when a request orgId is present (shared across users in the org)\n * - user scope otherwise (personal to the requesting user's email)\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport {\n writeWorkspaceFile,\n appendWorkspaceFile,\n readWorkspaceFile,\n listWorkspaceFiles,\n deleteWorkspaceFile,\n grepWorkspaceFiles,\n type WorkspaceFilesScope,\n} from \"./store.js\";\n\nconst MAX_READ_CHARS = 100_000;\nconst DEFAULT_READ_CHARS = 40_000;\n\n/** Resolve scope from the current request context (org-preferred). */\nfunction resolveScope(): WorkspaceFilesScope | null {\n const orgId = getRequestOrgId();\n if (orgId) return { scope: \"org\", scopeId: orgId };\n const email = getRequestUserEmail();\n if (email) return { scope: \"user\", scopeId: email };\n return null;\n}\n\nexport function createWorkspaceFilesTool(): Record<string, ActionEntry> {\n return {\n \"workspace-files\": {\n agentTool: false,\n readOnly: false,\n tool: {\n description: [\n \"Bridge-only workspace file storage backed by Resources. Files are scoped to the current org/user and appear in the Resources workspace.\",\n \"Use scratch/... for temporary intermediate results; scratch files are hidden from the Resources view by default and expire. Use durable folder names for files the user explicitly wants to keep/manage.\",\n \"Use this to stage large intermediate results (fetched pages, per-item analysis memos, API payloads) so they don't consume context window, then read them back selectively for synthesis.\",\n \"\",\n \"Typical fusion-style workflow:\",\n \" 1. Fan out: for each item, fetch data and `write` a per-item memo file.\",\n \" 2. Synthesize: `list` files, then `read` each memo (with offset/maxChars to page large ones).\",\n \" 3. Optionally `grep` across all memos to find patterns.\",\n \" 4. `delete` temp files when no longer needed.\",\n \"\",\n \"Actions:\",\n \" write — create or overwrite a file. Max 2 MB per file.\",\n \" append — append text to a file (or create if absent).\",\n \" read — read content, optionally with offset/maxChars for paging large files.\",\n \" list — list files (with optional path prefix filter) showing name, size, updated.\",\n \" delete — delete a file by path.\",\n \" grep — search content across files for a substring or regex.\",\n ].join(\"\\n\"),\n parameters: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"write\", \"append\", \"read\", \"list\", \"delete\", \"grep\"],\n description: \"Operation to perform.\",\n },\n path: {\n type: \"string\",\n description:\n 'File path relative to the scope root, e.g. \"scratch/analysis/q2-memos/acme.md\". Required for write/append/read/delete. Optional for list/grep (acts as prefix filter).',\n },\n content: {\n type: \"string\",\n description:\n \"Text content to write or append. Required for write/append.\",\n },\n contentType: {\n type: \"string\",\n description:\n 'MIME type for new files. Default: \"text/plain\". Use \"application/json\" for JSON, \"text/markdown\" for Markdown.',\n },\n offset: {\n type: \"number\",\n description:\n \"Character offset to start reading from (for paging large files). Default: 0.\",\n },\n maxChars: {\n type: \"number\",\n description: `Maximum characters to return when reading. Default: ${DEFAULT_READ_CHARS}. Max: ${MAX_READ_CHARS}.`,\n },\n pattern: {\n type: \"string\",\n description:\n \"Search pattern for grep. Required for grep action. A plain substring by default; set useRegex to true for a regex.\",\n },\n useRegex: {\n type: \"boolean\",\n description:\n \"When true, treat `pattern` as a JavaScript regex (case-insensitive). Default: false.\",\n },\n },\n required: [\"action\"],\n },\n },\n\n run: async (args: Record<string, unknown>): Promise<string> => {\n const scope = resolveScope();\n if (!scope) {\n return \"Error: workspace-files requires an authenticated request context.\";\n }\n\n const action = String(args.action ?? \"\").trim();\n\n try {\n switch (action) {\n case \"write\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for write.\";\n const content = String(args.content ?? \"\");\n const contentType = String(args.contentType ?? \"text/plain\");\n const meta = await writeWorkspaceFile(\n scope,\n path,\n content,\n contentType,\n );\n return JSON.stringify({\n ok: true,\n action: \"write\",\n resourceId: meta.id,\n path: meta.path,\n contentType: meta.contentType,\n sizeBytes: meta.sizeBytes,\n updatedAt: meta.updatedAt,\n });\n }\n\n case \"append\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for append.\";\n const content = String(args.content ?? \"\");\n const contentType = String(args.contentType ?? \"text/plain\");\n const meta = await appendWorkspaceFile(\n scope,\n path,\n content,\n contentType,\n );\n return JSON.stringify({\n ok: true,\n action: \"append\",\n resourceId: meta.id,\n path: meta.path,\n contentType: meta.contentType,\n sizeBytes: meta.sizeBytes,\n updatedAt: meta.updatedAt,\n });\n }\n\n case \"read\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for read.\";\n const rawOffset = Number(args.offset);\n const offset =\n Number.isFinite(rawOffset) && rawOffset > 0 ? rawOffset : 0;\n const rawMax = Number(args.maxChars);\n const maxChars =\n Number.isFinite(rawMax) && rawMax > 0\n ? Math.min(rawMax, MAX_READ_CHARS)\n : DEFAULT_READ_CHARS;\n\n const file = await readWorkspaceFile(scope, path, {\n offset,\n maxChars,\n });\n if (!file) {\n return JSON.stringify({\n ok: false,\n error: `File not found: \"${path}\"`,\n });\n }\n\n const truncated = file.content.length >= maxChars;\n return JSON.stringify({\n ok: true,\n path: file.path,\n contentType: file.contentType,\n sizeBytes: file.sizeBytes,\n updatedAt: file.updatedAt,\n content: file.content,\n ...(truncated\n ? {\n truncated: true,\n nextOffset: offset + file.content.length,\n hint: `File has more content. Call again with offset: ${offset + file.content.length}`,\n }\n : {}),\n });\n }\n\n case \"list\": {\n const prefix = args.path ? String(args.path).trim() : undefined;\n const files = await listWorkspaceFiles(\n scope,\n prefix || undefined,\n );\n return JSON.stringify({\n ok: true,\n count: files.length,\n files: files.map((f) => ({\n path: f.path,\n sizeBytes: f.sizeBytes,\n contentType: f.contentType,\n updatedAt: f.updatedAt,\n })),\n });\n }\n\n case \"delete\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for delete.\";\n const deleted = await deleteWorkspaceFile(scope, path);\n return JSON.stringify({\n ok: true,\n deleted,\n path,\n });\n }\n\n case \"grep\": {\n const pattern = String(args.pattern ?? \"\").trim();\n if (!pattern) return \"Error: pattern is required for grep.\";\n const prefix = args.path ? String(args.path).trim() : undefined;\n const useRegex =\n args.useRegex === true || String(args.useRegex) === \"true\";\n const matches = await grepWorkspaceFiles(scope, pattern, {\n pathPrefix: prefix || undefined,\n useRegex,\n maxMatchesPerFile: 20,\n maxFiles: 50,\n });\n return JSON.stringify({\n ok: true,\n pattern,\n matchCount: matches.length,\n matches: matches.map((m) => ({\n path: m.path,\n line: m.lineNumber,\n text: m.line,\n })),\n });\n }\n\n default:\n return `Error: unknown action \"${action}\". Valid actions: write, append, read, list, delete, grep.`;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return JSON.stringify({ ok: false, error: msg });\n }\n },\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/workspace-files/tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `workspace-files` bridge tool.\n *\n * A single tool with an `action` discriminator covering write, append, read,\n * list, delete, and grep. It is kept for `run-code` workspaceRead/workspaceWrite\n * compatibility and delegates storage to the Resources table.\n *\n * Scope is automatically resolved from the active request context:\n * - org scope when a request orgId is present (shared across users in the org)\n * - user scope otherwise (personal to the requesting user's email)\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport {\n writeWorkspaceFile,\n appendWorkspaceFile,\n readWorkspaceFile,\n listWorkspaceFiles,\n deleteWorkspaceFile,\n grepWorkspaceFiles,\n type WorkspaceFilesScope,\n} from \"./store.js\";\n\nconst MAX_READ_CHARS = 100_000;\nconst DEFAULT_READ_CHARS = 40_000;\n\n/** Resolve scope from the current request context (org-preferred). */\nfunction resolveScope(): WorkspaceFilesScope | null {\n const orgId = getRequestOrgId();\n if (orgId) return { scope: \"org\", scopeId: orgId };\n const email = getRequestUserEmail();\n if (email) return { scope: \"user\", scopeId: email };\n return null;\n}\n\nexport function createWorkspaceFilesTool(): Record<string, ActionEntry> {\n return {\n \"workspace-files\": {\n agentTool: false,\n readOnly: false,\n tool: {\n description: [\n \"Bridge-only workspace file storage backed by Resources. Files are scoped to the current org/user and appear in the Resources workspace.\",\n \"Use scratch/... for temporary intermediate results; scratch files are hidden from the Resources view by default and expire. Use durable folder names for files the user explicitly wants to keep/manage.\",\n \"Use this to stage large intermediate results (fetched pages, per-item analysis memos, API payloads) so they don't consume context window, then read them back selectively for synthesis.\",\n \"\",\n \"Typical fusion-style workflow:\",\n \" 1. Fan out: for each item, fetch data and `write` a per-item memo file.\",\n \" 2. Synthesize: `list` files, then `read` each memo (with offset/maxChars to page large ones).\",\n \" 3. Optionally `grep` across all memos to find patterns.\",\n \" 4. `delete` temp files when no longer needed.\",\n \"\",\n \"Actions:\",\n \" write — create or overwrite a file. Max 2 MB per file.\",\n \" append — append text to a file (or create if absent).\",\n \" read — read content, optionally with offset/maxChars for paging large files.\",\n \" list — list files (with optional path prefix filter) showing name, size, updated.\",\n \" delete — delete a file by path.\",\n \" grep — search content across files for a substring or regex.\",\n ].join(\"\\n\"),\n parameters: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"write\", \"append\", \"read\", \"list\", \"delete\", \"grep\"],\n description: \"Operation to perform.\",\n },\n path: {\n type: \"string\",\n description:\n 'File path relative to the scope root, e.g. \"scratch/analysis/q2-memos/acme.md\". Required for write/append/read/delete. Optional for list/grep (acts as prefix filter).',\n },\n content: {\n type: \"string\",\n description:\n \"Text content to write or append. Required for write/append.\",\n },\n contentType: {\n type: \"string\",\n description:\n 'MIME type for new files. Default: \"text/plain\". Use \"application/json\" for JSON, \"text/markdown\" for Markdown.',\n },\n offset: {\n type: \"integer\",\n minimum: 0,\n description:\n \"Non-negative character offset to start reading from (for paging large files). Default: 0.\",\n },\n maxChars: {\n type: \"integer\",\n minimum: 1,\n maximum: MAX_READ_CHARS,\n description: `Positive maximum characters to return when reading. Default: ${DEFAULT_READ_CHARS}. Max: ${MAX_READ_CHARS}.`,\n },\n pattern: {\n type: \"string\",\n description:\n \"Search pattern for grep. Required for grep action. A plain substring by default; set useRegex to true for a regex.\",\n },\n useRegex: {\n type: \"boolean\",\n description:\n \"When true, treat `pattern` as a JavaScript regex (case-insensitive). Default: false.\",\n },\n },\n required: [\"action\"],\n },\n },\n\n run: async (args: Record<string, unknown>): Promise<string> => {\n const scope = resolveScope();\n if (!scope) {\n return \"Error: workspace-files requires an authenticated request context.\";\n }\n\n const action = String(args.action ?? \"\").trim();\n\n try {\n switch (action) {\n case \"write\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for write.\";\n const content = String(args.content ?? \"\");\n const contentType = String(args.contentType ?? \"text/plain\");\n const meta = await writeWorkspaceFile(\n scope,\n path,\n content,\n contentType,\n );\n return JSON.stringify({\n ok: true,\n action: \"write\",\n resourceId: meta.id,\n path: meta.path,\n contentType: meta.contentType,\n sizeBytes: meta.sizeBytes,\n updatedAt: meta.updatedAt,\n });\n }\n\n case \"append\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for append.\";\n const content = String(args.content ?? \"\");\n const contentType = String(args.contentType ?? \"text/plain\");\n const meta = await appendWorkspaceFile(\n scope,\n path,\n content,\n contentType,\n );\n return JSON.stringify({\n ok: true,\n action: \"append\",\n resourceId: meta.id,\n path: meta.path,\n contentType: meta.contentType,\n sizeBytes: meta.sizeBytes,\n updatedAt: meta.updatedAt,\n });\n }\n\n case \"read\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for read.\";\n const rawOffset = Number(args.offset);\n const offset =\n Number.isFinite(rawOffset) && rawOffset > 0\n ? Math.floor(rawOffset)\n : 0;\n const rawMax = Number(args.maxChars);\n const maxChars =\n Number.isFinite(rawMax) && rawMax > 0\n ? Math.min(Math.max(1, Math.floor(rawMax)), MAX_READ_CHARS)\n : DEFAULT_READ_CHARS;\n\n // The sentinel character distinguishes an exact page from a truncated one.\n const file = await readWorkspaceFile(scope, path, {\n offset,\n maxChars: maxChars + 1,\n });\n if (!file) {\n return JSON.stringify({\n ok: false,\n error: `File not found: \"${path}\"`,\n });\n }\n\n const truncated = file.content.length > maxChars;\n const content = file.content.slice(0, maxChars);\n return JSON.stringify({\n ok: true,\n path: file.path,\n contentType: file.contentType,\n sizeBytes: file.sizeBytes,\n updatedAt: file.updatedAt,\n content,\n ...(truncated\n ? {\n truncated: true,\n nextOffset: offset + content.length,\n hint: `File has more content. Call again with offset: ${offset + content.length}`,\n }\n : {}),\n });\n }\n\n case \"list\": {\n const prefix = args.path ? String(args.path).trim() : undefined;\n const files = await listWorkspaceFiles(\n scope,\n prefix || undefined,\n );\n return JSON.stringify({\n ok: true,\n count: files.length,\n files: files.map((f) => ({\n path: f.path,\n sizeBytes: f.sizeBytes,\n contentType: f.contentType,\n updatedAt: f.updatedAt,\n })),\n });\n }\n\n case \"delete\": {\n const path = String(args.path ?? \"\").trim();\n if (!path) return \"Error: path is required for delete.\";\n const deleted = await deleteWorkspaceFile(scope, path);\n return JSON.stringify({\n ok: true,\n deleted,\n path,\n });\n }\n\n case \"grep\": {\n const pattern = String(args.pattern ?? \"\").trim();\n if (!pattern) return \"Error: pattern is required for grep.\";\n const prefix = args.path ? String(args.path).trim() : undefined;\n const useRegex =\n args.useRegex === true || String(args.useRegex) === \"true\";\n const matches = await grepWorkspaceFiles(scope, pattern, {\n pathPrefix: prefix || undefined,\n useRegex,\n maxMatchesPerFile: 20,\n maxFiles: 50,\n });\n return JSON.stringify({\n ok: true,\n pattern,\n matchCount: matches.length,\n matches: matches.map((m) => ({\n path: m.path,\n line: m.lineNumber,\n text: m.line,\n })),\n });\n }\n\n default:\n return `Error: unknown action \"${action}\". Valid actions: write, append, read, list, delete, grep.`;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return JSON.stringify({ ok: false, error: msg });\n }\n },\n },\n };\n}\n"]}
|
|
@@ -536,14 +536,16 @@ Database connection variables (`DATABASE_URL`, `DATABASE_AUTH_TOKEN`, per-app `<
|
|
|
536
536
|
|
|
537
537
|
These must be set before promoting an app to a real prod deploy. Missing values either fail-closed (the framework refuses to start / refuses to handle requests) or fall back to weaker behavior with a loud warning.
|
|
538
538
|
|
|
539
|
-
| Variable
|
|
540
|
-
|
|
|
541
|
-
| `BETTER_AUTH_SECRET`
|
|
542
|
-
| `BETTER_AUTH_URL`
|
|
543
|
-
| `ANTHROPIC_API_KEY`
|
|
544
|
-
| `OAUTH_STATE_SECRET`
|
|
545
|
-
| `A2A_SECRET`
|
|
546
|
-
| `
|
|
539
|
+
| Variable | Description |
|
|
540
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
541
|
+
| `BETTER_AUTH_SECRET` | 32+ char random string. Signs session cookies and is the fallback for `OAUTH_STATE_SECRET` and app-local encrypted values. It is also a legacy read candidate for existing shared vault ciphertext. Hard-required: the framework throws on startup if missing in production. |
|
|
542
|
+
| `BETTER_AUTH_URL` | Public origin of this app (e.g. `https://mail.example.com`). Used for cookie domain and OAuth redirect construction. |
|
|
543
|
+
| `ANTHROPIC_API_KEY` | API key for the embedded production agent. **In multi-tenant deploys**, the framework refuses to fall back to this when the user has no per-user key — bring-your-own-key is required. Single-tenant self-hosted installs use it as a global key. |
|
|
544
|
+
| `OAUTH_STATE_SECRET` | Dedicated HMAC key for OAuth state envelopes (Google, Atlassian, Zoom). Falls back to `BETTER_AUTH_SECRET` when unset, but a dedicated value is recommended so rotating one doesn't invalidate the other. Generate via `openssl rand -hex 32`. |
|
|
545
|
+
| `A2A_SECRET` | Shared HMAC for inter-app A2A JSON-RPC. Without it, every A2A endpoint and the `/_agent-native/integrations/process-task` self-fire endpoint return 503 in production. When no dedicated workspace vault key is set, shared vault ciphertext uses a purpose-derived key from this value, so rotating it also rotates the vault key. |
|
|
546
|
+
| `WORKSPACE_SECRETS_ENCRYPTION_KEY` | Preferred stable AES-256-GCM key for the shared encrypted-at-rest secrets vault. Set the same value in every app that reads the vault. It is separate from app-local OAuth/credential encryption, so A2A trust can rotate without stranding vault rows. Existing A2A-, Better Auth-, and app-key ciphertext is migrated on successful reads. |
|
|
547
|
+
| `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` | Optional previous workspace vault key used only during a rotation window. Deploy it alongside the new `WORKSPACE_SECRETS_ENCRYPTION_KEY`, let successful reads re-encrypt rows, then remove it after migration. |
|
|
548
|
+
| `SECRETS_ENCRYPTION_KEY` | Legacy combined key for app-local encryption and the shared vault. It remains supported, but new multi-app deployments should use `WORKSPACE_SECRETS_ENCRYPTION_KEY` for the shared vault so changing workspace encryption does not change app-local OAuth ciphertext. |
|
|
547
549
|
|
|
548
550
|
### Auth & Identity {#env-auth}
|
|
549
551
|
|
|
@@ -563,13 +565,13 @@ Inside a workspace, the root `.env` is loaded into every app automatically, so s
|
|
|
563
565
|
|
|
564
566
|
### Generating Strong Secrets {#env-generate-secrets}
|
|
565
567
|
|
|
566
|
-
For any secret marked "32+ char random" (`BETTER_AUTH_SECRET`, `OAUTH_STATE_SECRET`, `A2A_SECRET`, `SECRETS_ENCRYPTION_KEY`, `AGENT_NATIVE_BUILDER_RELAY_SECRET`), generate fresh values with:
|
|
568
|
+
For any secret marked "32+ char random" (`BETTER_AUTH_SECRET`, `OAUTH_STATE_SECRET`, `A2A_SECRET`, `WORKSPACE_SECRETS_ENCRYPTION_KEY`, `SECRETS_ENCRYPTION_KEY`, `AGENT_NATIVE_BUILDER_RELAY_SECRET`), generate fresh values with:
|
|
567
569
|
|
|
568
570
|
```bash
|
|
569
571
|
openssl rand -hex 32
|
|
570
572
|
```
|
|
571
573
|
|
|
572
|
-
Rotate
|
|
574
|
+
Rotate signing/session keys by replacing the env var on every instance and redeploying — sessions / OAuth state envelopes signed under the old key become invalid, so users may need to sign in again. Rotate `WORKSPACE_SECRETS_ENCRYPTION_KEY` with the `..._PREVIOUS` window described above so encrypted vault rows remain readable while they migrate.
|
|
573
575
|
|
|
574
576
|
## Production Agent Tools {#production-agent-tools}
|
|
575
577
|
|
|
@@ -320,7 +320,7 @@ pnpm action db-check-scoping --require-org # Also require org_id
|
|
|
320
320
|
| OAuth tokens (Google, GitHub) | `oauth_tokens` store via `saveOAuthTokens()` |
|
|
321
321
|
| Session tokens | Automatic (Better Auth handles this) |
|
|
322
322
|
|
|
323
|
-
Per-user/per-org credentials and the vault are encrypted at rest with AES-256-GCM. For a per-user/per-org credential, the key precedence is `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (app-scoped — useful in a multi-app workspace where apps shouldn't share one key) → `SECRETS_ENCRYPTION_KEY` → `BETTER_AUTH_SECRET`; production refuses to start without one of these set. The shared workspace vault (`app_secrets`, meant to decrypt the same in sibling apps)
|
|
323
|
+
Per-user/per-org credentials and the vault are encrypted at rest with AES-256-GCM. For a per-user/per-org credential, the key precedence is `<APP_NAME>_SECRETS_ENCRYPTION_KEY` (app-scoped — useful in a multi-app workspace where apps shouldn't share one key) → `SECRETS_ENCRYPTION_KEY` → `BETTER_AUTH_SECRET`; production refuses to start without one of these set. The shared workspace vault (`app_secrets`, meant to decrypt the same in sibling apps) prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy combined `SECRETS_ENCRYPTION_KEY`, then material derived from `A2A_SECRET`, with `BETTER_AUTH_SECRET` and the app-scoped key retained as compatibility candidates. Successful fallback reads compare-and-swap re-encrypt the shared column under the preferred key. During a workspace-key rotation, set `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` to the old value until reads have migrated existing rows. To encrypt any pre-existing plaintext credential rows in place, run `pnpm action db-migrate-encrypt-credentials` (idempotent, non-destructive).
|
|
324
324
|
|
|
325
325
|
Never store secrets in `settings`, `application_state`, source code, or action responses. Use the credential / vault APIs above — they handle both encryption and per-user scoping.
|
|
326
326
|
|
|
@@ -365,7 +365,7 @@ Google Docs Pub/Sub push notifications don't go through that generic adapter gat
|
|
|
365
365
|
- [ ] `BETTER_AUTH_SECRET` set to a random 32+ char string (`openssl rand -hex 32`), unless this is a hosted workspace deploy deriving it from `A2A_SECRET`
|
|
366
366
|
- [ ] `OAUTH_STATE_SECRET` set to a separate random 32+ char string (don't reuse `BETTER_AUTH_SECRET`) — see [OAuth State Signing](#oauth-state)
|
|
367
367
|
- [ ] `A2A_SECRET` set on every app that calls or receives A2A traffic — see [A2A Identity Verification](#a2a-identity)
|
|
368
|
-
- [ ] `
|
|
368
|
+
- [ ] `WORKSPACE_SECRETS_ENCRYPTION_KEY` set consistently for multi-app shared vaults; app-local encryption still has `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` fallbacks — see [Secrets Management](#secrets)
|
|
369
369
|
- [ ] `AUTH_SKIP_EMAIL_VERIFICATION` is **not** set in production (or set only on QA preview deploys)
|
|
370
370
|
|
|
371
371
|
### Webhook secrets (set the ones for integrations you use)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.131.
|
|
3
|
+
"version": "0.131.9",
|
|
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": {
|
package/src/a2a/invoke.ts
CHANGED
|
@@ -263,9 +263,11 @@ export function buildAgentInvocationPrompt(
|
|
|
263
263
|
): string {
|
|
264
264
|
return (
|
|
265
265
|
`${prompt}\n\n` +
|
|
266
|
-
|
|
266
|
+
`<a2a-caller-hint>\n` +
|
|
267
|
+
`This request comes from another app via A2A. The caller cannot see your local UI, resource list, or navigation - only the literal text you put in your reply. ` +
|
|
267
268
|
`If you create or reference a deck/document/design/dashboard/resource, include its FULLY-QUALIFIED URL (e.g. ${agentUrl.replace(/\/$/, "")}/<path>/<id>) in your reply, not a relative path. ` +
|
|
268
|
-
`Use only artifact IDs and URL paths returned by successful actions - never invent slugs, IDs, or hosts
|
|
269
|
+
`Use only artifact IDs and URL paths returned by successful actions - never invent slugs, IDs, or hosts.\n` +
|
|
270
|
+
`</a2a-caller-hint>`
|
|
269
271
|
);
|
|
270
272
|
}
|
|
271
273
|
|
|
@@ -21,8 +21,9 @@ export function incrementalActionGuidance(tool: string): string | undefined {
|
|
|
21
21
|
case "create-prototype-plan":
|
|
22
22
|
return "create the plan with its core sections or first screen, then expand it with `update-visual-plan`/`patch-visual-plan-source` follow-up edits";
|
|
23
23
|
case "update-visual-plan":
|
|
24
|
+
return "apply one or a few small `update-visual-plan` `contentPatches` such as `append-block`, `update-rich-text`, or a text patch; do not resend the full plan or switch to a source-control workflow";
|
|
24
25
|
case "patch-visual-plan-source":
|
|
25
|
-
return "apply
|
|
26
|
+
return "apply small targeted `patch-visual-plan-source` MDX AST edits; do not use `replace-file`, and do not use this source action for a live plan edit";
|
|
26
27
|
case "update-dashboard":
|
|
27
28
|
return "save a small dashboard first, then add panels one at a time with `update-dashboard` incremental `ops` edits instead of authoring the whole config in one call";
|
|
28
29
|
default:
|
|
@@ -600,6 +600,41 @@ async function resolveUsableProviderSecret(
|
|
|
600
600
|
return authFailure ? null : value;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Return true only when the supplied key can be positively identified as a
|
|
605
|
+
* usable credential for a different registered provider.
|
|
606
|
+
*
|
|
607
|
+
* `ResolveEngineConfig.apiKey` is public and may be an opaque caller-provided
|
|
608
|
+
* key, so automatic selection must not silently replace it merely because a
|
|
609
|
+
* stored credential also exists. Delegated/internal callers, however,
|
|
610
|
+
* historically pass the current "active" provider key before the registry
|
|
611
|
+
* selects an app-default engine. Exact in-memory comparison lets us correct
|
|
612
|
+
* that proven mismatch without guessing from provider-specific key prefixes
|
|
613
|
+
* or exposing either value.
|
|
614
|
+
*/
|
|
615
|
+
async function apiKeyBelongsToDifferentProvider(
|
|
616
|
+
apiKey: string,
|
|
617
|
+
selectedEntry: AgentEngineEntry,
|
|
618
|
+
): Promise<boolean> {
|
|
619
|
+
const selectedEnvVars = new Set(selectedEntry.requiredEnvVars);
|
|
620
|
+
const checkedEnvVars = new Set<string>();
|
|
621
|
+
for (const entry of _registry.values()) {
|
|
622
|
+
if (entry === selectedEntry || entry.name === "builder") continue;
|
|
623
|
+
for (const key of entry.requiredEnvVars) {
|
|
624
|
+
if (selectedEnvVars.has(key) || checkedEnvVars.has(key)) continue;
|
|
625
|
+
checkedEnvVars.add(key);
|
|
626
|
+
try {
|
|
627
|
+
if ((await resolveUsableProviderSecret(key)) === apiKey) return true;
|
|
628
|
+
} catch {
|
|
629
|
+
// An unrelated provider store failure is not proof that the explicit
|
|
630
|
+
// key belongs elsewhere. Preserve the caller's key and let the
|
|
631
|
+
// selected provider's own preflight report any relevant failure.
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
603
638
|
async function engineCreateConfigForEntry(
|
|
604
639
|
entry: AgentEngineEntry,
|
|
605
640
|
apiKey: string | undefined,
|
|
@@ -612,27 +647,44 @@ async function engineCreateConfigForEntry(
|
|
|
612
647
|
// Callers historically passed one untagged "active" key before the registry
|
|
613
648
|
// chose an engine, which could hand an Anthropic key to an app-default
|
|
614
649
|
// OpenAI engine (or vice versa). Explicit engineOption branches retain their
|
|
615
|
-
// paired key
|
|
650
|
+
// paired key. Automatic branches replace only a key proven to belong to a
|
|
651
|
+
// different configured provider; opaque caller-supplied keys keep the public
|
|
652
|
+
// `ResolveEngineConfig.apiKey` contract.
|
|
616
653
|
if (
|
|
617
654
|
preferResolvedCredential &&
|
|
618
655
|
entry.name !== "builder" &&
|
|
619
656
|
entry.requiredEnvVars.length > 0
|
|
620
657
|
) {
|
|
621
|
-
let
|
|
658
|
+
let resolvedMatchingCredential: string | undefined;
|
|
659
|
+
let matchingCredentialUsesDeployFallback = false;
|
|
622
660
|
for (const key of entry.requiredEnvVars) {
|
|
623
661
|
const resolved = (await resolveUsableProviderSecret(key)) ?? undefined;
|
|
624
662
|
if (!resolved) continue;
|
|
625
|
-
|
|
626
|
-
|
|
663
|
+
resolvedMatchingCredential = resolved;
|
|
664
|
+
matchingCredentialUsesDeployFallback =
|
|
627
665
|
canUseDeployCredentialFallbackForRequest(key) &&
|
|
628
666
|
readDeployCredentialEnv(key) === resolved;
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const suppliedKeyMatchesSelected =
|
|
671
|
+
matchingApiKey !== undefined &&
|
|
672
|
+
matchingApiKey === resolvedMatchingCredential;
|
|
673
|
+
const suppliedKeyBelongsElsewhere =
|
|
674
|
+
matchingApiKey !== undefined &&
|
|
675
|
+
!suppliedKeyMatchesSelected &&
|
|
676
|
+
(await apiKeyBelongsToDifferentProvider(matchingApiKey, entry));
|
|
677
|
+
|
|
678
|
+
if (matchingApiKey === undefined || suppliedKeyBelongsElsewhere) {
|
|
629
679
|
// Keep deploy-only credentials implicit so provider SDKs retain their
|
|
630
680
|
// established env-fallback behavior. Scoped credentials must be passed
|
|
631
|
-
// explicitly
|
|
632
|
-
|
|
633
|
-
|
|
681
|
+
// explicitly. A proven different-provider key is replaced or cleared;
|
|
682
|
+
// an opaque caller-supplied key is preserved by the branch above.
|
|
683
|
+
matchingApiKey =
|
|
684
|
+
resolvedMatchingCredential && !matchingCredentialUsesDeployFallback
|
|
685
|
+
? resolvedMatchingCredential
|
|
686
|
+
: undefined;
|
|
634
687
|
}
|
|
635
|
-
if (!foundMatchingCredential) matchingApiKey = undefined;
|
|
636
688
|
}
|
|
637
689
|
if (entry.name === "ai-sdk:openai") {
|
|
638
690
|
if (typeof safeExtra.baseURL === "string" && safeExtra.baseUrl == null) {
|
|
@@ -924,7 +976,7 @@ export async function resolveEngine(
|
|
|
924
976
|
);
|
|
925
977
|
}
|
|
926
978
|
return anthropicEntry.create(
|
|
927
|
-
await engineCreateConfigForEntry(anthropicEntry, apiKey),
|
|
979
|
+
await engineCreateConfigForEntry(anthropicEntry, apiKey, undefined, true),
|
|
928
980
|
);
|
|
929
981
|
}
|
|
930
982
|
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
getDbExec,
|
|
3
3
|
getDialect,
|
|
4
4
|
isLocalDatabase,
|
|
5
|
-
isConnectionError,
|
|
6
5
|
isPostgres,
|
|
7
6
|
intType,
|
|
8
7
|
type DbExec,
|
|
@@ -102,21 +101,14 @@ export async function appStateGet(
|
|
|
102
101
|
sessionId: string,
|
|
103
102
|
key: string,
|
|
104
103
|
): Promise<Record<string, unknown> | null> {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return JSON.parse(rows[0].value as string);
|
|
114
|
-
} catch (err) {
|
|
115
|
-
// Transient WS / connection drops (Neon serverless) — caller polls every
|
|
116
|
-
// 2s and will see the value on the next tick. Swallow rather than 500.
|
|
117
|
-
if (isConnectionError(err)) return null;
|
|
118
|
-
throw err;
|
|
119
|
-
}
|
|
104
|
+
await ensureTable();
|
|
105
|
+
const client = getDbExec();
|
|
106
|
+
const { rows } = await client.execute({
|
|
107
|
+
sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,
|
|
108
|
+
args: [sessionId, key],
|
|
109
|
+
});
|
|
110
|
+
if (rows.length === 0) return null;
|
|
111
|
+
return JSON.parse(rows[0].value as string);
|
|
120
112
|
}
|
|
121
113
|
|
|
122
114
|
/**
|
|
@@ -133,22 +125,17 @@ export async function appStateGetMany(
|
|
|
133
125
|
for (const key of uniqueKeys) values[key] = null;
|
|
134
126
|
if (uniqueKeys.length === 0) return values;
|
|
135
127
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
values[row.key as string] = JSON.parse(row.value as string);
|
|
146
|
-
}
|
|
147
|
-
return values;
|
|
148
|
-
} catch (err) {
|
|
149
|
-
if (isConnectionError(err)) return values;
|
|
150
|
-
throw err;
|
|
128
|
+
await ensureTable();
|
|
129
|
+
const client = getDbExec();
|
|
130
|
+
const placeholders = uniqueKeys.map(() => "?").join(", ");
|
|
131
|
+
const { rows } = await client.execute({
|
|
132
|
+
sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
|
|
133
|
+
args: [sessionId, ...uniqueKeys],
|
|
134
|
+
});
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
values[row.key as string] = JSON.parse(row.value as string);
|
|
151
137
|
}
|
|
138
|
+
return values;
|
|
152
139
|
}
|
|
153
140
|
|
|
154
141
|
export async function appStatePut(
|