@agent-native/core 0.7.1 → 0.7.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/dist/action.d.ts +8 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +18 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +148 -36
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +20 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +14 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +63 -6
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/setup-agents.d.ts.map +1 -1
- package/dist/cli/setup-agents.js +0 -2
- package/dist/cli/setup-agents.js.map +1 -1
- package/dist/cli/templates-meta.d.ts +52 -0
- package/dist/cli/templates-meta.d.ts.map +1 -0
- package/dist/cli/templates-meta.js +165 -0
- package/dist/cli/templates-meta.js.map +1 -0
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +279 -30
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +172 -40
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts +21 -0
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -0
- package/dist/client/ConnectBuilderCard.js +196 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -0
- package/dist/client/FeedbackButton.d.ts +15 -0
- package/dist/client/FeedbackButton.d.ts.map +1 -0
- package/dist/client/FeedbackButton.js +72 -0
- package/dist/client/FeedbackButton.js.map +1 -0
- package/dist/client/IframeEmbed.d.ts +17 -0
- package/dist/client/IframeEmbed.d.ts.map +1 -0
- package/dist/client/IframeEmbed.js +108 -0
- package/dist/client/IframeEmbed.js.map +1 -0
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +34 -7
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +34 -15
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +7 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/MentionPopover.d.ts.map +1 -1
- package/dist/client/composer/MentionPopover.js +27 -24
- package/dist/client/composer/MentionPopover.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +21 -4
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/embed.d.ts +28 -0
- package/dist/client/embed.d.ts.map +1 -0
- package/dist/client/embed.js +50 -0
- package/dist/client/embed.js.map +1 -0
- package/dist/client/index.d.ts +4 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +2 -2
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/onboarding/OnboardingBanner.js +2 -2
- package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.d.ts.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +139 -52
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/onboarding/SetupButton.d.ts.map +1 -1
- package/dist/client/onboarding/SetupButton.js +13 -3
- package/dist/client/onboarding/SetupButton.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +12 -7
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/client/resources/ResourceEditor.js +57 -2
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +5 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +17 -10
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +12 -10
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/AgentsSection.d.ts.map +1 -1
- package/dist/client/settings/AgentsSection.js +6 -3
- package/dist/client/settings/AgentsSection.js.map +1 -1
- package/dist/client/settings/ComingSoonSection.js +1 -1
- package/dist/client/settings/ComingSoonSection.js.map +1 -1
- package/dist/client/settings/LLMSection.d.ts.map +1 -1
- package/dist/client/settings/LLMSection.js +1 -1
- package/dist/client/settings/LLMSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +16 -23
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.js +2 -2
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/settings/UsageSection.d.ts +2 -0
- package/dist/client/settings/UsageSection.d.ts.map +1 -0
- package/dist/client/settings/UsageSection.js +70 -0
- package/dist/client/settings/UsageSection.js.map +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +67 -4
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +25 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +62 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-dev-mode.d.ts.map +1 -1
- package/dist/client/use-dev-mode.js +16 -1
- package/dist/client/use-dev-mode.js.map +1 -1
- package/dist/db/client.d.ts +12 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +89 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts +11 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +47 -3
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +62 -5
- package/dist/db/migrations.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +7 -2
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/oauth-tokens/google-refresh.d.ts +31 -0
- package/dist/oauth-tokens/google-refresh.d.ts.map +1 -0
- package/dist/oauth-tokens/google-refresh.js +115 -0
- package/dist/oauth-tokens/google-refresh.js.map +1 -0
- package/dist/oauth-tokens/index.d.ts +1 -0
- package/dist/oauth-tokens/index.d.ts.map +1 -1
- package/dist/oauth-tokens/index.js +1 -0
- package/dist/oauth-tokens/index.js.map +1 -1
- package/dist/onboarding/default-steps.d.ts.map +1 -1
- package/dist/onboarding/default-steps.js +57 -18
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/org/context.js +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +35 -10
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.d.ts.map +1 -1
- package/dist/org/plugin.js +37 -22
- package/dist/org/plugin.js.map +1 -1
- package/dist/resources/handlers.js +1 -1
- package/dist/resources/handlers.js.map +1 -1
- package/dist/resources/metadata.d.ts.map +1 -1
- package/dist/resources/metadata.js +2 -2
- package/dist/resources/metadata.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +27 -1
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/db/patch.d.ts.map +1 -1
- package/dist/scripts/db/patch.js +273 -11
- package/dist/scripts/db/patch.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +26 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +12 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +495 -48
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.js +2 -2
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +21 -58
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +284 -41
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +70 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/builder-browser.d.ts +21 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +67 -4
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +122 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/desktop-sso.d.ts +30 -0
- package/dist/server/desktop-sso.d.ts.map +1 -0
- package/dist/server/desktop-sso.js +74 -0
- package/dist/server/desktop-sso.js.map +1 -0
- package/dist/server/email.d.ts +23 -0
- package/dist/server/email.d.ts.map +1 -0
- package/dist/server/email.js +105 -0
- package/dist/server/email.js.map +1 -0
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +29 -0
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +19 -3
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/local-migration.d.ts +9 -0
- package/dist/server/local-migration.d.ts.map +1 -1
- package/dist/server/local-migration.js +44 -14
- package/dist/server/local-migration.js.map +1 -1
- package/dist/server/oauth-helpers.d.ts +2 -0
- package/dist/server/oauth-helpers.d.ts.map +1 -1
- package/dist/server/oauth-helpers.js +2 -0
- package/dist/server/oauth-helpers.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +6 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +229 -25
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +48 -0
- package/dist/server/poll.js.map +1 -1
- package/dist/templates/default/.agents/skills/inline-embeds/SKILL.md +88 -0
- package/dist/usage/store.d.ts +74 -12
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +210 -44
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +55 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/deployment.md +59 -2
- package/docs/content/resources.md +9 -9
- package/docs/content/workspace-management.md +2 -2
- package/package.json +3 -3
- package/src/templates/default/.agents/skills/inline-embeds/SKILL.md +88 -0
- /package/dist/templates/workspace-core/{skills → .agents/skills}/company-policies/SKILL.md +0 -0
- /package/src/templates/workspace-core/{skills → .agents/skills}/company-policies/SKILL.md +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runWithRequestContext, getRequestOrgId } from "./request-context.js";
|
|
2
|
+
import { getSetting, putSetting } from "../settings/store.js";
|
|
2
3
|
import { getH3App } from "./framework-request-handler.js";
|
|
3
4
|
import { createProductionAgentHandler, runAgentLoop, actionsToEngineTools, getActiveRunForThreadAsync, abortRun, subscribeToRun, } from "../agent/production-agent.js";
|
|
4
5
|
import { resolveEngine, createAnthropicEngine } from "../agent/engine/index.js";
|
|
@@ -28,9 +29,10 @@ async function lazyFs() {
|
|
|
28
29
|
* Wraps a core CLI script (that writes to console.log) as a ActionEntry
|
|
29
30
|
* by capturing stdout.
|
|
30
31
|
*/
|
|
31
|
-
function wrapCliScript(tool, cliDefault) {
|
|
32
|
+
function wrapCliScript(tool, cliDefault, opts) {
|
|
32
33
|
return {
|
|
33
34
|
tool,
|
|
35
|
+
...(opts?.readOnly ? { readOnly: true } : {}),
|
|
34
36
|
run: async (args) => {
|
|
35
37
|
const cliArgs = [];
|
|
36
38
|
for (const [k, v] of Object.entries(args)) {
|
|
@@ -72,6 +74,254 @@ function wrapCliScript(tool, cliDefault) {
|
|
|
72
74
|
},
|
|
73
75
|
};
|
|
74
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates the `refresh-screen` tool. Writes a bump to `application_state`
|
|
79
|
+
* under a well-known key; the client's `useDbSync` watches for this and
|
|
80
|
+
* invalidates react-query caches so the on-screen UI re-fetches its data
|
|
81
|
+
* without a full page reload.
|
|
82
|
+
*
|
|
83
|
+
* This is the standard way for the agent to say "the data on the screen
|
|
84
|
+
* just changed, please refresh it" — e.g. after editing a dashboard config,
|
|
85
|
+
* updating a form schema, or mutating a row that the current view renders.
|
|
86
|
+
*/
|
|
87
|
+
function createRefreshScreenEntry() {
|
|
88
|
+
return {
|
|
89
|
+
"refresh-screen": {
|
|
90
|
+
// Writes __screen_refresh__ to application_state, which emits its own
|
|
91
|
+
// distinct `screen-refresh` poll event. Don't double-emit a generic
|
|
92
|
+
// `action` event on top of that.
|
|
93
|
+
readOnly: true,
|
|
94
|
+
tool: {
|
|
95
|
+
description: "Manually refresh the user's current screen. The framework ALREADY auto-refreshes after any successful mutating action tool call (template actions, db-exec, db-patch) — you do NOT need to call this after a normal action. Use it only when (a) you mutated data via a path the framework can't detect (e.g. a direct write to an external system the app mirrors), or (b) you want to pass a `scope` hint so the UI narrows which queries to refetch. The UI re-fetches its queries without a full page reload.",
|
|
96
|
+
parameters: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
scope: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Optional hint describing what changed (e.g. 'dashboard', 'form', 'settings'). Templates may use it to narrow which queries to invalidate; if omitted, all queries are invalidated.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
run: async (args) => {
|
|
107
|
+
const { writeAppState } = await import("../application-state/script-helpers.js");
|
|
108
|
+
const nonce = Date.now();
|
|
109
|
+
const scope = typeof args?.scope === "string" ? args.scope : undefined;
|
|
110
|
+
await writeAppState(SCREEN_REFRESH_KEY, {
|
|
111
|
+
nonce,
|
|
112
|
+
...(scope ? { scope } : {}),
|
|
113
|
+
});
|
|
114
|
+
return `refreshed${scope ? ` (scope: ${scope})` : ""}`;
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Well-known application-state key used by the refresh-screen tool. */
|
|
120
|
+
const SCREEN_REFRESH_KEY = "__screen_refresh__";
|
|
121
|
+
/**
|
|
122
|
+
* Creates the `set-search-params` / `set-url-path` tools. Writes a one-shot
|
|
123
|
+
* URL command to application_state; the client's URLSync component applies
|
|
124
|
+
* it via react-router (no full page reload) and then deletes the command.
|
|
125
|
+
*
|
|
126
|
+
* This is how the agent edits URL state — filter query params, route
|
|
127
|
+
* changes, hash — without needing a per-template navigate action. The
|
|
128
|
+
* current URL is visible to the agent via the auto-injected `<current-url>`
|
|
129
|
+
* block, which includes parsed search params.
|
|
130
|
+
*/
|
|
131
|
+
function createUrlTools() {
|
|
132
|
+
return {
|
|
133
|
+
"set-search-params": {
|
|
134
|
+
// Writes __set_url__ to application_state, which the app-state watcher
|
|
135
|
+
// already surfaces as a poll event. No need to double-emit.
|
|
136
|
+
readOnly: true,
|
|
137
|
+
tool: {
|
|
138
|
+
description: "Update the URL query string on the user's current page. Use this to change dashboard/list filters, search terms, or any other state the app stores in `?foo=bar` style query params. One-shot — the UI applies it in ~1s without a page reload. See the current URL + parsed search params in the auto-injected `<current-url>` block. Keys are the exact query param names as they appear in the URL (e.g. `f_pubDateStart`, not just `pubDateStart`). Set a value to null or empty string to clear that param. By default merges over existing params — pass `merge: false` to replace them all.",
|
|
139
|
+
parameters: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
params: {
|
|
143
|
+
type: "object",
|
|
144
|
+
description: 'Map of query param → value. Each value is a string, or null/"" to clear. Example: {"f_pubDateStart": null, "f_cadence": "MONTH"}.',
|
|
145
|
+
},
|
|
146
|
+
merge: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: '"true" (default) merges over existing params; "false" replaces them entirely.',
|
|
149
|
+
enum: ["true", "false"],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ["params"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
run: async (args) => {
|
|
156
|
+
const params = (args?.params ?? {});
|
|
157
|
+
const merge = args?.merge !== "false";
|
|
158
|
+
const { writeAppState } = await import("../application-state/script-helpers.js");
|
|
159
|
+
await writeAppState("__set_url__", {
|
|
160
|
+
searchParams: params,
|
|
161
|
+
mergeSearchParams: merge,
|
|
162
|
+
});
|
|
163
|
+
const keys = Object.keys(params);
|
|
164
|
+
return `set-search-params: ${keys.length} key${keys.length === 1 ? "" : "s"}${merge ? "" : " (replace)"}`;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
"set-url-path": {
|
|
168
|
+
// Same as set-search-params — writes application_state, already emits
|
|
169
|
+
// via the app-state watcher.
|
|
170
|
+
readOnly: true,
|
|
171
|
+
tool: {
|
|
172
|
+
description: "Navigate the user to a different pathname, optionally also setting search params. For most template-specific routing prefer the template's `navigate` action if it exists — this is the generic fallback. One-shot, applied by the client without a page reload.",
|
|
173
|
+
parameters: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
pathname: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "New URL pathname (e.g. '/adhoc/weekly').",
|
|
179
|
+
},
|
|
180
|
+
params: {
|
|
181
|
+
type: "object",
|
|
182
|
+
description: 'Optional query params to set alongside the path change. String values set, null/"" clears.',
|
|
183
|
+
},
|
|
184
|
+
merge: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: '"true" (default) merges over existing params; "false" starts fresh.',
|
|
187
|
+
enum: ["true", "false"],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ["pathname"],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
run: async (args) => {
|
|
194
|
+
const pathname = String(args?.pathname ?? "");
|
|
195
|
+
if (!pathname.startsWith("/")) {
|
|
196
|
+
return "Error: pathname must start with '/'.";
|
|
197
|
+
}
|
|
198
|
+
const params = (args?.params ?? {});
|
|
199
|
+
const merge = args?.merge !== "false";
|
|
200
|
+
const { writeAppState } = await import("../application-state/script-helpers.js");
|
|
201
|
+
await writeAppState("__set_url__", {
|
|
202
|
+
pathname,
|
|
203
|
+
searchParams: params,
|
|
204
|
+
mergeSearchParams: merge,
|
|
205
|
+
});
|
|
206
|
+
return `set-url-path: ${pathname}`;
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Creates db-* tools (db-query, db-exec, db-patch, db-schema) as native tools.
|
|
213
|
+
* These let the agent read and write the app's own SQL database. Scoping to
|
|
214
|
+
* the current user/org is enforced automatically in production via temp views.
|
|
215
|
+
*
|
|
216
|
+
* In dev mode template actions are invoked via shell and the agent can call
|
|
217
|
+
* `pnpm action db-query ...` — but in production there is no shell, so these
|
|
218
|
+
* must be registered as native tools for the agent to reach the app DB at all.
|
|
219
|
+
*/
|
|
220
|
+
async function createDbScriptEntries() {
|
|
221
|
+
try {
|
|
222
|
+
const [schemaMod, queryMod, execMod, patchMod] = await Promise.all([
|
|
223
|
+
import("../scripts/db/schema.js"),
|
|
224
|
+
import("../scripts/db/query.js"),
|
|
225
|
+
import("../scripts/db/exec.js"),
|
|
226
|
+
import("../scripts/db/patch.js"),
|
|
227
|
+
]);
|
|
228
|
+
return {
|
|
229
|
+
"db-schema": wrapCliScript({
|
|
230
|
+
description: "Show the app's SQL schema — all tables, columns, types, indexes, and foreign keys. Use this to understand the data model before querying.",
|
|
231
|
+
parameters: {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
format: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description: 'Output format: "json" or "text" (default: text)',
|
|
237
|
+
enum: ["json", "text"],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
}, schemaMod.default, { readOnly: true }),
|
|
242
|
+
"db-query": wrapCliScript({
|
|
243
|
+
description: "Read from the app's SQL database. Runs a SELECT (or WITH/EXPLAIN/PRAGMA) against the app's own tables — settings, application_state, and all template tables. Results are automatically scoped to the current user/org; DO NOT add `WHERE owner_email = ...` yourself. This queries the APP DATABASE — not any external data source.",
|
|
244
|
+
parameters: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
sql: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "SELECT query to run, e.g. \"SELECT key, value FROM settings WHERE key LIKE 'sql-dashboard-%'\"",
|
|
250
|
+
},
|
|
251
|
+
format: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description: 'Output format: "json" or "text" (default: text)',
|
|
254
|
+
enum: ["json", "text"],
|
|
255
|
+
},
|
|
256
|
+
limit: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "Append LIMIT N if the query doesn't already have one",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
required: ["sql"],
|
|
262
|
+
},
|
|
263
|
+
}, queryMod.default, { readOnly: true }),
|
|
264
|
+
"db-exec": wrapCliScript({
|
|
265
|
+
description: "Write to the app's SQL database. Runs INSERT / UPDATE / DELETE against the app's own tables. Writes are automatically scoped to the current user/org, and `owner_email` / `org_id` are auto-injected on INSERT. Use this to update rows in the settings table (e.g. edit a dashboard config stored under `o:<orgId>:sql-dashboard-<id>`). This writes to the APP DATABASE — not any external data source.",
|
|
266
|
+
parameters: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
sql: {
|
|
270
|
+
type: "string",
|
|
271
|
+
description: "INSERT / UPDATE / DELETE statement. Use parameterized placeholders (?) if possible.",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
required: ["sql"],
|
|
275
|
+
},
|
|
276
|
+
}, execMod.default),
|
|
277
|
+
"db-patch": wrapCliScript({
|
|
278
|
+
description: "Surgical patch on a large text/JSON column in the app's SQL database. Two modes: (1) text find/replace via `find`/`replace`/`edits` — best for small edits to documents, slide HTML, etc. (2) structural JSON ops via `json-ops` — STRONGLY PREFERRED when the column is JSON (dashboard configs, form schemas, slide decks) because it avoids all the brace/quote/comma surgery that text find/replace requires. Use `json-ops` to set/remove values at a JSON Pointer path, or to move/insert array items — e.g. reorder dashboard panels, add a filter, rename a field. Targets exactly one row (narrow `where` by primary key). Same per-user/org scoping as db-exec.",
|
|
279
|
+
parameters: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
table: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Table name (e.g. 'settings')",
|
|
285
|
+
},
|
|
286
|
+
column: {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Text/JSON column to patch (e.g. 'value' for settings)",
|
|
289
|
+
},
|
|
290
|
+
where: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "WHERE clause that matches exactly one row (e.g. \"key = 'o:org1:sql-dashboard-foo'\")",
|
|
293
|
+
},
|
|
294
|
+
find: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Text mode: substring to find. Must match EXACTLY ONE occurrence by default (like Claude Code's Edit tool). If 0 matches, you get 'NOT FOUND'. If >1 matches, you get surrounding context for each match — widen `find` with unique context and retry. Use `all: \"true\"` to replace every occurrence.",
|
|
297
|
+
},
|
|
298
|
+
replace: {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: "Text mode: replacement substring",
|
|
301
|
+
},
|
|
302
|
+
edits: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: 'Text mode batch: JSON array of {find, replace} pairs. Same uniqueness rule applies to each `find`. Example: \'[{"find":"a","replace":"b"}]\'',
|
|
305
|
+
},
|
|
306
|
+
"json-ops": {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: 'JSON mode: JSON array of structural ops. Each op is {op, path, value?, from?}. `op` is one of "set", "remove", "insert", "move", "move-before". `path` / `from` use JSON Pointer ("/panels/3/title"). Examples — reorder: \'[{"op":"move","from":"/panels/7","path":"/panels/1"}]\'; edit field: \'[{"op":"set","path":"/panels/0/title","value":"New"}]\'; delete filter: \'[{"op":"remove","path":"/filters/2"}]\'; add panel: \'[{"op":"insert","path":"/panels/0","value":{"id":"p","title":"..."}}]\'. Much safer than text find/replace for JSON columns.',
|
|
309
|
+
},
|
|
310
|
+
all: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: 'Text mode: set to "true" to replace every occurrence of each `find` (default requires exactly one match)',
|
|
313
|
+
enum: ["true"],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ["table", "column", "where"],
|
|
317
|
+
},
|
|
318
|
+
}, patchMod.default),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return {};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
75
325
|
/**
|
|
76
326
|
* Creates the docs-search tool so agents can look up framework documentation.
|
|
77
327
|
* Docs are bundled in @agent-native/core and read via fs at runtime.
|
|
@@ -100,7 +350,7 @@ async function createDocsScriptEntries() {
|
|
|
100
350
|
},
|
|
101
351
|
},
|
|
102
352
|
},
|
|
103
|
-
}, mod.default),
|
|
353
|
+
}, mod.default, { readOnly: true }),
|
|
104
354
|
};
|
|
105
355
|
}
|
|
106
356
|
catch {
|
|
@@ -342,6 +592,32 @@ async function createCallAgentScriptEntry(selfAppId) {
|
|
|
342
592
|
}
|
|
343
593
|
function createBuilderBrowserTool(deps) {
|
|
344
594
|
return {
|
|
595
|
+
"connect-builder": {
|
|
596
|
+
tool: {
|
|
597
|
+
description: "Render a Builder.io card inline in the chat. Call this IMMEDIATELY — no exploration, no planning — whenever (a) the user asks to add a feature, change the UI, edit code, create a component, add a route, add an integration, fix a bug in the app itself, or anything else that requires source-file edits while in hosted/production mode, OR (b) the user needs Builder for LLM access, browser automation, or any other Builder-gated capability. If Builder is already connected, the card shows a 'Send to Builder' button that hands the work off to Builder's cloud agent and returns a branch URL. When you call this for a code-change request, pass the user's request verbatim as the `prompt` arg so the card can forward it to Builder unchanged.",
|
|
598
|
+
parameters: {
|
|
599
|
+
type: "object",
|
|
600
|
+
properties: {
|
|
601
|
+
prompt: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "The user's feature / change request, verbatim. Forwarded to Builder's cloud agent when the user clicks Send. Omit only for generic 'connect Builder' requests that aren't tied to a specific code change.",
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
run: async (args) => {
|
|
609
|
+
const configured = !!(process.env.BUILDER_PRIVATE_KEY && process.env.BUILDER_PUBLIC_KEY);
|
|
610
|
+
const prompt = typeof args?.prompt === "string" ? args.prompt : "";
|
|
611
|
+
return JSON.stringify({
|
|
612
|
+
kind: "connect-builder-card",
|
|
613
|
+
configured,
|
|
614
|
+
builderEnabled: !!process.env.ENABLE_BUILDER,
|
|
615
|
+
connectUrl: getBuilderBrowserConnectUrl(deps.getOrigin()),
|
|
616
|
+
orgName: process.env.BUILDER_ORG_NAME || null,
|
|
617
|
+
prompt,
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
},
|
|
345
621
|
"get-browser-connection": {
|
|
346
622
|
tool: {
|
|
347
623
|
description: "Provision a Builder-backed browser session and return browser websocket connection details. If Builder browser access is not configured yet, this returns setup guidance instead.",
|
|
@@ -616,11 +892,12 @@ const FRAMEWORK_CORE = `
|
|
|
616
892
|
### Core Rules
|
|
617
893
|
|
|
618
894
|
1. **Data lives in SQL** — All app state is in a SQL database (could be SQLite, Postgres, Turso, or Cloudflare D1 — never assume which). Use the available database tools.
|
|
619
|
-
2. **Context awareness** — The user's current screen state is automatically included in each message as a \`<current-screen>\` block. Use
|
|
895
|
+
2. **Context awareness** — The user's current screen state is automatically included in each message as a \`<current-screen>\` block, and the current URL (path + search params) as a \`<current-url>\` block. Use both to understand what the user is looking at — filters, search terms, and other URL-driven state live in \`<current-url>\`'s \`searchParams\`, NOT in the settings table. To change URL state (e.g. toggle a filter, clear a query string), use the \`set-search-params\` or \`set-url-path\` tools — never try to edit URL state by writing to settings or application_state directly.
|
|
620
896
|
3. **Navigate the UI** — Use the \`navigate\` tool to switch views, open items, or focus elements for the user.
|
|
621
897
|
4. **Application state** — Ephemeral UI state (drafts, selections, navigation) lives in \`application_state\`. Use \`readAppState\`/\`writeAppState\` to read and write it. When you write state, the UI updates automatically.
|
|
622
|
-
5. **
|
|
623
|
-
6. **
|
|
898
|
+
5. **Screen refresh is automatic after action calls** — The framework auto-emits a refresh event after any successful mutating tool call (template actions like \`log-meal\`, \`update-form\`, \`edit-document\`, and the \`db-exec\` / \`db-patch\` tools). The UI re-fetches its queries without a full page reload. You do NOT need to call \`refresh-screen\` after an action — it's already handled. Only call \`refresh-screen\` explicitly when (a) you mutated data via a path the framework can't detect (e.g. writing directly to an external system whose results the app mirrors), or (b) you want to pass a \`scope\` hint so the UI narrows which queries to refetch. Do NOT tell the user to reload the page.
|
|
899
|
+
6. **Memory** — Use the structured memory system to persist knowledge across sessions. Use \`save-memory\` proactively when you learn preferences, corrections, or project context. Update shared AGENTS.md for instructions that should apply to all users.
|
|
900
|
+
7. **Security** — Always use \`defineAction\` with a Zod \`schema:\` for input validation. Never construct SQL with string concatenation — use parameterized queries via db-query/db-exec. Never use \`dangerouslySetInnerHTML\`, \`innerHTML\`, or \`eval()\`. Never expose secrets in responses or source code. Every table with user data must have \`owner_email\`.
|
|
624
901
|
|
|
625
902
|
### Resources
|
|
626
903
|
|
|
@@ -634,6 +911,37 @@ When the user gives instructions that should apply to all users/sessions, update
|
|
|
634
911
|
|
|
635
912
|
When the user says "show me", "go to", "open", "switch to", or similar navigation language, ALWAYS use the \`navigate\` action to update the UI. The user expects to SEE the result in the main app, not just read it in chat. Navigate first, then fetch/display data.
|
|
636
913
|
|
|
914
|
+
### Inline Embeds
|
|
915
|
+
|
|
916
|
+
You can embed an interactive view inline in your chat reply by writing an \`embed\` fenced code block. The chat renderer swaps the fence for a sandboxed iframe pointing at a route inside this app.
|
|
917
|
+
|
|
918
|
+
Syntax:
|
|
919
|
+
|
|
920
|
+
\`\`\`\`
|
|
921
|
+
\`\`\`embed
|
|
922
|
+
src: /some/path?param=value
|
|
923
|
+
aspect: 16/9
|
|
924
|
+
title: Optional label
|
|
925
|
+
\`\`\`
|
|
926
|
+
\`\`\`\`
|
|
927
|
+
|
|
928
|
+
Keys:
|
|
929
|
+
- \`src\` (required) — **must be a same-origin path starting with \`/\`**. Cross-origin URLs are blocked by the renderer. No \`javascript:\` or \`data:\` URLs.
|
|
930
|
+
- \`aspect\` (optional) — one of \`16/9\` (default), \`4/3\`, \`3/2\`, \`2/1\`, \`21/9\`, \`1/1\`.
|
|
931
|
+
- \`title\` (optional) — accessible label / hover tooltip.
|
|
932
|
+
- \`height\` (optional) — fixed pixel height when aspect ratio isn't a good fit.
|
|
933
|
+
|
|
934
|
+
**When to reach for it:**
|
|
935
|
+
- Showing a chart, visualization, or map that benefits from being live/interactive.
|
|
936
|
+
- Previewing a specific item (a thread, a doc, a record) inline with your explanation.
|
|
937
|
+
- Anything where a screenshot-sized static image would undersell the result.
|
|
938
|
+
|
|
939
|
+
**When NOT to use it:**
|
|
940
|
+
- For simple prose answers, tables, or plain data — those should stay as markdown.
|
|
941
|
+
- For external sites — the renderer blocks cross-origin iframes.
|
|
942
|
+
|
|
943
|
+
Which routes are renderable as embeds is template-specific — the app's \`AGENTS.md\` will list them. If no embeddable routes exist in this template, don't emit \`embed\` fences.
|
|
944
|
+
|
|
637
945
|
### Chat History
|
|
638
946
|
|
|
639
947
|
You can search and restore previous chat conversations:
|
|
@@ -682,11 +990,15 @@ When the user asks for something recurring ("every morning", "daily at 9am", "we
|
|
|
682
990
|
|
|
683
991
|
Job instructions should be self-contained — include which actions to call, what conditions to check, and what to do with results. The agent executing the job has access to all the same tools you do.
|
|
684
992
|
|
|
993
|
+
### Connecting Builder.io
|
|
994
|
+
|
|
995
|
+
When the user asks to connect Builder.io, needs Builder for LLM access / browser automation, or you hit a "Builder not configured" error, call the \`connect-builder\` tool. It renders a one-click Connect card inline in the chat — do NOT write out multi-step setup instructions yourself (no "Option 1 / Option 2", no terminal commands). Just call the tool and let the card handle the rest.
|
|
996
|
+
|
|
685
997
|
### Browser Access
|
|
686
998
|
|
|
687
999
|
Use \`get-browser-connection\` when you need a real browser session backed by Builder. It returns websocket connection details for a provisioned browser session.
|
|
688
1000
|
|
|
689
|
-
- If the tool says Builder is not configured,
|
|
1001
|
+
- If the tool says Builder is not configured, call \`connect-builder\` to show the user a Connect card.
|
|
690
1002
|
- Reuse a stable \`sessionId\` when you want to reconnect to the same browser session.
|
|
691
1003
|
- Include proxy parameters when you need the browser to reach a local dev server through Builder's browser connection flow.
|
|
692
1004
|
|
|
@@ -743,20 +1055,22 @@ The agent and the UI are equal partners — everything the UI can do, you can do
|
|
|
743
1055
|
|
|
744
1056
|
**In production mode, you operate through registered actions exposed as tools.** These are your capabilities — use them to read data, take actions, and help the user. You cannot edit source code or access the filesystem directly. Your tools are the app's API.
|
|
745
1057
|
|
|
746
|
-
### Code Changes Not Available
|
|
1058
|
+
### Code Changes Not Available — Call \`connect-builder\` Immediately
|
|
747
1059
|
|
|
748
|
-
When the user asks you to change the UI, modify code, add
|
|
1060
|
+
When the user asks you to change the UI, modify code, add a feature, fix a bug in the app itself, change styles, add a hook, create a component, add a route, add an integration, or anything else that requires editing source files — you MUST take exactly these steps, in order:
|
|
749
1061
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
> **Option 1:** Connect to [Builder.io](https://www.builder.io/m/agent-native) for a full development environment
|
|
753
|
-
>
|
|
754
|
-
> **Option 2:** Clone and run locally:
|
|
755
|
-
> \`\`\`
|
|
756
|
-
> npx agent-native create APP_NAME
|
|
757
|
-
> \`\`\`
|
|
1062
|
+
1. Call the \`connect-builder\` tool, passing the user's full request verbatim as the \`prompt\` argument. This renders an inline card. If Builder is connected, the card hands the prompt off to Builder's cloud agent on one click and returns a branch URL; otherwise it shows a connect / waitlist flow. Either way, Builder writes the code — no local setup, no clone, no install.
|
|
1063
|
+
2. After the card renders, write a single short sentence telling the user to click Connect Builder to have this built for them in the cloud, and mention they can also clone the app locally with \`npx agent-native create <app-name>\` if they'd rather work offline.
|
|
758
1064
|
|
|
759
|
-
|
|
1065
|
+
**Hard rules — do NOT break these:**
|
|
1066
|
+
- Do NOT read source files, list directories, or explore the codebase. You have no filesystem tools and don't need to look at code to recommend Builder.
|
|
1067
|
+
- Do NOT write an implementation plan. Do NOT write code in your response. Do NOT describe which files to create or modify. Builder will figure that out in its sandbox.
|
|
1068
|
+
- Do NOT save plans, specs, or code to \`resource-write\`. Resources are for app data, not implementation plans the user didn't ask for.
|
|
1069
|
+
- Do NOT spawn sub-agents (\`spawn-task\`) to design, plan, or research the feature. The answer is always: call \`connect-builder\`, say one sentence, stop. Sub-agents inherit these same rules and have no code-editing tools either.
|
|
1070
|
+
- Do NOT say "I don't have a request-code-change tool" or list what tools you lack. Just call \`connect-builder\`.
|
|
1071
|
+
- Do NOT wait for the user to ask "how" — call \`connect-builder\` the moment a code change is requested.
|
|
1072
|
+
|
|
1073
|
+
Builder.io is the recommended path because it's one-click and runs in the cloud. Local dev is a fallback for users who want to work offline.
|
|
760
1074
|
${FRAMEWORK_CORE}`;
|
|
761
1075
|
const DEV_FRAMEWORK_PROMPT = `## Agent-Native Framework — Development Mode
|
|
762
1076
|
|
|
@@ -850,12 +1164,15 @@ async function loadResourcesForPrompt(owner) {
|
|
|
850
1164
|
* from the environment so scheduler/A2A/HTTP call sites all see whatever
|
|
851
1165
|
* org was just resolved for this request.
|
|
852
1166
|
*/
|
|
853
|
-
async function buildSchemaBlock(owner,
|
|
1167
|
+
async function buildSchemaBlock(owner, _legacyHasRawDbTools) {
|
|
1168
|
+
// db-* tools are always registered (see createDbScriptEntries), in both dev
|
|
1169
|
+
// and prod. The legacy boolean is kept for call-site compatibility but
|
|
1170
|
+
// ignored — always advertise the tools to the agent.
|
|
854
1171
|
try {
|
|
855
1172
|
return await loadSchemaPromptBlock({
|
|
856
1173
|
owner,
|
|
857
1174
|
orgId: getRequestOrgId() ?? null,
|
|
858
|
-
hasRawDbTools,
|
|
1175
|
+
hasRawDbTools: true,
|
|
859
1176
|
});
|
|
860
1177
|
}
|
|
861
1178
|
catch {
|
|
@@ -1045,6 +1362,29 @@ export function createAgentChatPlugin(options) {
|
|
|
1045
1362
|
const canToggle = (env === "development" || env === "test") &&
|
|
1046
1363
|
process.env.AGENT_MODE !== "production";
|
|
1047
1364
|
const routePath = options?.path ?? "/_agent-native/agent-chat";
|
|
1365
|
+
// Mutable mode flag — persisted to the `settings` table so a user who
|
|
1366
|
+
// toggles to "Production" stays in prod mode across server restarts.
|
|
1367
|
+
// Hoisted here (before any tool-registry / handler closures are built)
|
|
1368
|
+
// so every runtime decision point can close over it and see live changes
|
|
1369
|
+
// when the user toggles the Environment dropdown.
|
|
1370
|
+
const AGENT_MODE_SETTING_KEY = "agent-chat.mode";
|
|
1371
|
+
let currentDevMode = canToggle;
|
|
1372
|
+
if (canToggle) {
|
|
1373
|
+
try {
|
|
1374
|
+
const persisted = await getSetting(AGENT_MODE_SETTING_KEY);
|
|
1375
|
+
if (persisted && typeof persisted.devMode === "boolean") {
|
|
1376
|
+
currentDevMode = persisted.devMode;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
catch {
|
|
1380
|
+
// Settings table may not be ready yet — fall back to default.
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
// Every closure that picks between dev/prod tools, prompts, or handlers
|
|
1384
|
+
// at request time should call this getter instead of reading `canToggle`.
|
|
1385
|
+
// `canToggle` means "this environment allows toggling" (static); this
|
|
1386
|
+
// function means "the user currently has dev mode ON" (live).
|
|
1387
|
+
const isDevMode = () => currentDevMode;
|
|
1048
1388
|
// Initialize MCP client (connects to user-configured local MCP servers).
|
|
1049
1389
|
// Graceful-degrade: any failure yields zero MCP tools and agent-chat keeps
|
|
1050
1390
|
// working as before. No-op outside Node runtimes.
|
|
@@ -1092,9 +1432,12 @@ export function createAgentChatPlugin(options) {
|
|
|
1092
1432
|
const templateScripts = typeof rawActions === "function"
|
|
1093
1433
|
? await rawActions()
|
|
1094
1434
|
: (rawActions ?? {});
|
|
1095
|
-
// Resource, chat, docs, and cross-agent scripts are available in both prod and dev modes
|
|
1435
|
+
// Resource, chat, docs, db, and cross-agent scripts are available in both prod and dev modes
|
|
1096
1436
|
const resourceScripts = await createResourceScriptEntries();
|
|
1097
1437
|
const docsScripts = await createDocsScriptEntries();
|
|
1438
|
+
const dbScripts = await createDbScriptEntries();
|
|
1439
|
+
const refreshScreenTool = createRefreshScreenEntry();
|
|
1440
|
+
const urlTools = createUrlTools();
|
|
1098
1441
|
const engineScripts = await createAgentEngineScriptEntries();
|
|
1099
1442
|
const chatScripts = {
|
|
1100
1443
|
...(await createChatScriptEntries()),
|
|
@@ -1253,6 +1596,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1253
1596
|
...templateScripts,
|
|
1254
1597
|
...resourceScripts,
|
|
1255
1598
|
...docsScripts,
|
|
1599
|
+
...dbScripts,
|
|
1600
|
+
...refreshScreenTool,
|
|
1601
|
+
...urlTools,
|
|
1256
1602
|
...chatScripts,
|
|
1257
1603
|
...callAgentScript,
|
|
1258
1604
|
...browserTools,
|
|
@@ -1328,12 +1674,13 @@ export function createAgentChatPlugin(options) {
|
|
|
1328
1674
|
apiKey: options?.apiKey,
|
|
1329
1675
|
});
|
|
1330
1676
|
// Use the same handler (dev or prod) that the interactive chat uses
|
|
1331
|
-
const
|
|
1677
|
+
const devActive = isDevMode();
|
|
1678
|
+
const handler = devActive && devHandler ? devHandler : prodHandler;
|
|
1332
1679
|
// Build the same system prompt the interactive agent uses
|
|
1333
1680
|
const owner = userEmail || "local@localhost";
|
|
1334
1681
|
const resources = await loadResourcesForPrompt(owner);
|
|
1335
|
-
const schemaBlock = await buildSchemaBlock(owner,
|
|
1336
|
-
const systemPrompt =
|
|
1682
|
+
const schemaBlock = await buildSchemaBlock(owner, devActive);
|
|
1683
|
+
const systemPrompt = devActive
|
|
1337
1684
|
? devPrompt + resources + schemaBlock
|
|
1338
1685
|
: basePrompt + resources + schemaBlock;
|
|
1339
1686
|
const model = options?.model ??
|
|
@@ -1342,7 +1689,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1342
1689
|
// to prevent infinite recursive A2A loops (agent calling itself).
|
|
1343
1690
|
// In dev mode, template actions are invoked via shell (not native tools),
|
|
1344
1691
|
// so they're omitted from the tool registry — see allScripts comment.
|
|
1345
|
-
const a2aActions =
|
|
1692
|
+
const a2aActions = devActive
|
|
1346
1693
|
? {
|
|
1347
1694
|
...resourceScripts,
|
|
1348
1695
|
...docsScripts,
|
|
@@ -1354,6 +1701,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1354
1701
|
...templateScripts,
|
|
1355
1702
|
...resourceScripts,
|
|
1356
1703
|
...docsScripts,
|
|
1704
|
+
...dbScripts,
|
|
1705
|
+
...refreshScreenTool,
|
|
1706
|
+
...urlTools,
|
|
1357
1707
|
...chatScripts,
|
|
1358
1708
|
...browserTools,
|
|
1359
1709
|
};
|
|
@@ -1438,7 +1788,8 @@ export function createAgentChatPlugin(options) {
|
|
|
1438
1788
|
(canToggle ? "claude-sonnet-4-6" : "claude-haiku-4-5-20251001");
|
|
1439
1789
|
// Same actions as A2A — without call-agent to prevent loops.
|
|
1440
1790
|
// In dev mode, template actions go through shell, not native tools.
|
|
1441
|
-
const
|
|
1791
|
+
const devActiveMcp = isDevMode();
|
|
1792
|
+
const mcpActions = devActiveMcp
|
|
1442
1793
|
? {
|
|
1443
1794
|
...resourceScripts,
|
|
1444
1795
|
...docsScripts,
|
|
@@ -1449,12 +1800,15 @@ export function createAgentChatPlugin(options) {
|
|
|
1449
1800
|
...templateScripts,
|
|
1450
1801
|
...resourceScripts,
|
|
1451
1802
|
...docsScripts,
|
|
1803
|
+
...dbScripts,
|
|
1804
|
+
...refreshScreenTool,
|
|
1805
|
+
...urlTools,
|
|
1452
1806
|
...chatScripts,
|
|
1453
1807
|
};
|
|
1454
1808
|
const mcpTools = actionsToEngineTools(mcpActions);
|
|
1455
1809
|
const resources = await loadResourcesForPrompt("local@localhost");
|
|
1456
|
-
const schemaBlock = await buildSchemaBlock("local@localhost",
|
|
1457
|
-
const systemPrompt =
|
|
1810
|
+
const schemaBlock = await buildSchemaBlock("local@localhost", devActiveMcp);
|
|
1811
|
+
const systemPrompt = devActiveMcp
|
|
1458
1812
|
? devPrompt + resources + schemaBlock
|
|
1459
1813
|
: basePrompt + resources + schemaBlock;
|
|
1460
1814
|
let accumulatedText = "";
|
|
@@ -1560,6 +1914,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1560
1914
|
// requests for different threads don't clobber each other.
|
|
1561
1915
|
const _runSendByThread = new Map();
|
|
1562
1916
|
let _currentRunOwner = "local@localhost";
|
|
1917
|
+
let _currentRunUserApiKey;
|
|
1563
1918
|
let _currentRunThreadId = "";
|
|
1564
1919
|
let _currentRunSystemPrompt = basePrompt;
|
|
1565
1920
|
// Default to Haiku in production mode to manage costs for hosted apps
|
|
@@ -1568,7 +1923,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1568
1923
|
const teamTools = createTeamTools({
|
|
1569
1924
|
getOwner: () => _currentRunOwner,
|
|
1570
1925
|
getSystemPrompt: () => _currentRunSystemPrompt,
|
|
1571
|
-
getActions: () =>
|
|
1926
|
+
getActions: () => isDevMode()
|
|
1572
1927
|
? {
|
|
1573
1928
|
// Sub-agents spawned in dev mode also invoke template actions
|
|
1574
1929
|
// via shell, so omit them from the native tool registry.
|
|
@@ -1581,10 +1936,18 @@ export function createAgentChatPlugin(options) {
|
|
|
1581
1936
|
...templateScripts,
|
|
1582
1937
|
...resourceScripts,
|
|
1583
1938
|
...docsScripts,
|
|
1939
|
+
...dbScripts,
|
|
1940
|
+
...refreshScreenTool,
|
|
1941
|
+
...urlTools,
|
|
1584
1942
|
...chatScripts,
|
|
1585
1943
|
},
|
|
1586
1944
|
getEngine: () => createAnthropicEngine({
|
|
1587
|
-
|
|
1945
|
+
// Sub-agents must inherit the parent run's resolved key so a
|
|
1946
|
+
// BYO-key user can't bypass the free-tier check on the parent
|
|
1947
|
+
// run and then have spawn-task delegations bill the platform key.
|
|
1948
|
+
apiKey: _currentRunUserApiKey ??
|
|
1949
|
+
options?.apiKey ??
|
|
1950
|
+
process.env.ANTHROPIC_API_KEY,
|
|
1588
1951
|
}),
|
|
1589
1952
|
getModel: () => resolvedModel,
|
|
1590
1953
|
getParentThreadId: () => _currentRunThreadId,
|
|
@@ -1606,6 +1969,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1606
1969
|
...templateScripts,
|
|
1607
1970
|
...resourceScripts,
|
|
1608
1971
|
...docsScripts,
|
|
1972
|
+
...dbScripts,
|
|
1973
|
+
...refreshScreenTool,
|
|
1974
|
+
...urlTools,
|
|
1609
1975
|
...chatScripts,
|
|
1610
1976
|
...callAgentScript,
|
|
1611
1977
|
...teamTools,
|
|
@@ -1616,15 +1982,30 @@ export function createAgentChatPlugin(options) {
|
|
|
1616
1982
|
// Always build the production handler (includes resource tools + call-agent + team tools)
|
|
1617
1983
|
// In production mode (!canToggle), enable usage tracking and limits
|
|
1618
1984
|
const isHostedProd = !canToggle;
|
|
1985
|
+
const resolveExtraContext = async (event, owner) => {
|
|
1986
|
+
if (!options?.extraContext)
|
|
1987
|
+
return "";
|
|
1988
|
+
try {
|
|
1989
|
+
const extra = await options.extraContext(event, owner);
|
|
1990
|
+
return extra ? `\n\n${extra}` : "";
|
|
1991
|
+
}
|
|
1992
|
+
catch (err) {
|
|
1993
|
+
console.warn("[agent-chat] extraContext threw:", err instanceof Error ? err.message : err);
|
|
1994
|
+
return "";
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1619
1997
|
const prodHandler = createProductionAgentHandler({
|
|
1620
1998
|
actions: prodActions,
|
|
1621
1999
|
systemPrompt: async (event) => {
|
|
1622
2000
|
_currentRequestOrigin = getOrigin(event);
|
|
1623
2001
|
const owner = await getOwnerFromEvent(event);
|
|
1624
2002
|
_currentRunOwner = owner;
|
|
2003
|
+
const { getOwnerAnthropicApiKey } = await import("../agent/production-agent.js");
|
|
2004
|
+
_currentRunUserApiKey = await getOwnerAnthropicApiKey(owner);
|
|
1625
2005
|
const resources = await loadResourcesForPrompt(owner);
|
|
1626
2006
|
const schemaBlock = await buildSchemaBlock(owner, false);
|
|
1627
|
-
|
|
2007
|
+
const extra = await resolveExtraContext(event, owner);
|
|
2008
|
+
_currentRunSystemPrompt = basePrompt + resources + schemaBlock + extra;
|
|
1628
2009
|
return _currentRunSystemPrompt;
|
|
1629
2010
|
},
|
|
1630
2011
|
model: options?.model ??
|
|
@@ -1670,9 +2051,12 @@ export function createAgentChatPlugin(options) {
|
|
|
1670
2051
|
_currentRequestOrigin = getOrigin(event);
|
|
1671
2052
|
const owner = await getOwnerFromEvent(event);
|
|
1672
2053
|
_currentRunOwner = owner;
|
|
2054
|
+
const { getOwnerAnthropicApiKey } = await import("../agent/production-agent.js");
|
|
2055
|
+
_currentRunUserApiKey = await getOwnerAnthropicApiKey(owner);
|
|
1673
2056
|
const resources = await loadResourcesForPrompt(owner);
|
|
1674
2057
|
const schemaBlock = await buildSchemaBlock(owner, true);
|
|
1675
|
-
|
|
2058
|
+
const extra = await resolveExtraContext(event, owner);
|
|
2059
|
+
_currentRunSystemPrompt = devPrompt + resources + schemaBlock + extra;
|
|
1676
2060
|
return _currentRunSystemPrompt;
|
|
1677
2061
|
},
|
|
1678
2062
|
model: options?.model,
|
|
@@ -1693,8 +2077,8 @@ export function createAgentChatPlugin(options) {
|
|
|
1693
2077
|
const mentionProviders = typeof rawProviders === "function"
|
|
1694
2078
|
? await rawProviders()
|
|
1695
2079
|
: (rawProviders ?? {});
|
|
1696
|
-
//
|
|
1697
|
-
|
|
2080
|
+
// currentDevMode + persistence were hoisted to the top of this function
|
|
2081
|
+
// so every closure built below can close over the live flag.
|
|
1698
2082
|
// Mount mode endpoint — GET returns current mode, POST toggles it (localhost only)
|
|
1699
2083
|
getH3App(nitroApp).use(`${routePath}/mode`, defineEventHandler(async (event) => {
|
|
1700
2084
|
if (getMethod(event) === "POST") {
|
|
@@ -1713,12 +2097,24 @@ export function createAgentChatPlugin(options) {
|
|
|
1713
2097
|
else {
|
|
1714
2098
|
currentDevMode = !currentDevMode;
|
|
1715
2099
|
}
|
|
2100
|
+
try {
|
|
2101
|
+
await putSetting(AGENT_MODE_SETTING_KEY, {
|
|
2102
|
+
devMode: currentDevMode,
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
catch {
|
|
2106
|
+
// Persistence is best-effort — in-memory flag still applies for
|
|
2107
|
+
// the lifetime of this process even if the settings write fails.
|
|
2108
|
+
}
|
|
1716
2109
|
return { devMode: currentDevMode, canToggle };
|
|
1717
2110
|
}
|
|
1718
2111
|
return { devMode: currentDevMode, canToggle };
|
|
1719
2112
|
}));
|
|
1720
|
-
// Mount save-key BEFORE the prefix handler so it isn't shadowed
|
|
1721
|
-
//
|
|
2113
|
+
// Mount save-key BEFORE the prefix handler so it isn't shadowed.
|
|
2114
|
+
// Persists the user's key per-owner in the SQL settings table so it
|
|
2115
|
+
// survives across serverless invocations (where mutating process.env
|
|
2116
|
+
// and writing .env are both no-ops). Also updates process.env and
|
|
2117
|
+
// .env when running locally for fast pickup by other handlers.
|
|
1722
2118
|
getH3App(nitroApp).use(`${routePath}/save-key`, defineEventHandler(async (event) => {
|
|
1723
2119
|
if (getMethod(event) !== "POST") {
|
|
1724
2120
|
setResponseStatus(event, 405);
|
|
@@ -1731,19 +2127,66 @@ export function createAgentChatPlugin(options) {
|
|
|
1731
2127
|
return { error: "API key is required" };
|
|
1732
2128
|
}
|
|
1733
2129
|
const trimmedKey = key.trim();
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2130
|
+
// Persist per-owner so the key survives cold starts in serverless
|
|
2131
|
+
// and so the user's key isn't shared across users on multi-tenant
|
|
2132
|
+
// hosted deployments. We require a real authenticated owner here —
|
|
2133
|
+
// `local@localhost` is the unauthenticated fallback and must never
|
|
2134
|
+
// become the shared key bucket on hosted deployments.
|
|
2135
|
+
const ownerEmail = await getOwnerFromEvent(event);
|
|
2136
|
+
if (isHostedProd && (!ownerEmail || ownerEmail === "local@localhost")) {
|
|
2137
|
+
setResponseStatus(event, 401);
|
|
2138
|
+
return { error: "Authentication required" };
|
|
1741
2139
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
2140
|
+
if (ownerEmail && ownerEmail !== "local@localhost") {
|
|
2141
|
+
try {
|
|
2142
|
+
await putSetting(`user-anthropic-api-key:${ownerEmail}`, {
|
|
2143
|
+
key: trimmedKey,
|
|
2144
|
+
});
|
|
2145
|
+
// Verify the write actually landed — some managed DB drivers
|
|
2146
|
+
// swallow errors on degraded connections. Without this the
|
|
2147
|
+
// client sees "saved", reloads, and the usage-limit card
|
|
2148
|
+
// re-appears on the next message because the key isn't
|
|
2149
|
+
// really persisted.
|
|
2150
|
+
const check = await getSetting(`user-anthropic-api-key:${ownerEmail}`);
|
|
2151
|
+
if (!check ||
|
|
2152
|
+
typeof check.key !== "string" ||
|
|
2153
|
+
check.key !== trimmedKey) {
|
|
2154
|
+
throw new Error("settings write did not persist");
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
catch (err) {
|
|
2158
|
+
if (isHostedProd) {
|
|
2159
|
+
console.error("[agent-chat] save-key persistence failed:", err instanceof Error ? err.message : err);
|
|
2160
|
+
setResponseStatus(event, 500);
|
|
2161
|
+
return {
|
|
2162
|
+
error: "Failed to persist API key. Please try again or contact support.",
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
// Local dev falls through to the env-file path below.
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
// In hosted/multi-tenant mode we deliberately do NOT touch
|
|
2169
|
+
// process.env or .env: the per-owner SQL lookup above is the
|
|
2170
|
+
// single source of truth, and overwriting the shared env key
|
|
2171
|
+
// would leak one tenant's credentials into every subsequent
|
|
2172
|
+
// request that hit the same warm instance without its own key.
|
|
2173
|
+
if (!isHostedProd) {
|
|
2174
|
+
try {
|
|
2175
|
+
const path = await import("path");
|
|
2176
|
+
const { upsertEnvFile } = await import("./create-server.js");
|
|
2177
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
2178
|
+
await upsertEnvFile(envPath, [
|
|
2179
|
+
{ key: "ANTHROPIC_API_KEY", value: trimmedKey },
|
|
2180
|
+
]);
|
|
2181
|
+
}
|
|
2182
|
+
catch {
|
|
2183
|
+
// Edge runtime — can't write .env, but can still update process.env
|
|
2184
|
+
}
|
|
2185
|
+
// Update process.env so the agent works immediately in the
|
|
2186
|
+
// current local-dev invocation; the SQL persist above covers
|
|
2187
|
+
// future invocations.
|
|
2188
|
+
process.env.ANTHROPIC_API_KEY = trimmedKey;
|
|
1744
2189
|
}
|
|
1745
|
-
// Update process.env so the agent works immediately
|
|
1746
|
-
process.env.ANTHROPIC_API_KEY = trimmedKey;
|
|
1747
2190
|
return { ok: true };
|
|
1748
2191
|
}));
|
|
1749
2192
|
// Mount file search endpoint
|
|
@@ -2069,7 +2512,7 @@ export function createAgentChatPlugin(options) {
|
|
|
2069
2512
|
setResponseStatus(event, 405);
|
|
2070
2513
|
return { error: "Method not allowed" };
|
|
2071
2514
|
}
|
|
2072
|
-
await getOwnerFromEvent(event);
|
|
2515
|
+
const ownerEmail = await getOwnerFromEvent(event);
|
|
2073
2516
|
const body = await readBody(event);
|
|
2074
2517
|
const message = body?.message;
|
|
2075
2518
|
if (!message || typeof message !== "string") {
|
|
@@ -2078,7 +2521,11 @@ export function createAgentChatPlugin(options) {
|
|
|
2078
2521
|
}
|
|
2079
2522
|
// Strip mention markup: @[Name|type] → @Name
|
|
2080
2523
|
const cleanMessage = message.replace(/@\[([^\]|]+)\|[^\]]*\]/g, "@$1");
|
|
2081
|
-
|
|
2524
|
+
// Mirror the chat-run resolution so BYO-key users have title
|
|
2525
|
+
// generation billed to their own key instead of the platform key.
|
|
2526
|
+
const { getOwnerAnthropicApiKey } = await import("../agent/production-agent.js");
|
|
2527
|
+
const userApiKey = await getOwnerAnthropicApiKey(ownerEmail);
|
|
2528
|
+
const apiKey = userApiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
2082
2529
|
if (!apiKey) {
|
|
2083
2530
|
// Fallback: truncate the message
|
|
2084
2531
|
return { title: cleanMessage.trim().slice(0, 60) };
|