@agent-native/core 0.26.9 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/run-ownership.d.ts +12 -0
- package/dist/agent/run-ownership.d.ts.map +1 -0
- package/dist/agent/run-ownership.js +39 -0
- package/dist/agent/run-ownership.js.map +1 -0
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +1 -0
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +9 -6
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/db-admin/DataGrid.d.ts +42 -0
- package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
- package/dist/client/db-admin/DataGrid.js +204 -0
- package/dist/client/db-admin/DataGrid.js.map +1 -0
- package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
- package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
- package/dist/client/db-admin/DbAdminPage.js +72 -0
- package/dist/client/db-admin/DbAdminPage.js.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
- package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
- package/dist/client/db-admin/DevDatabaseLink.js +25 -0
- package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
- package/dist/client/db-admin/EditableCell.d.ts +26 -0
- package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
- package/dist/client/db-admin/EditableCell.js +150 -0
- package/dist/client/db-admin/EditableCell.js.map +1 -0
- package/dist/client/db-admin/FilterBar.d.ts +8 -0
- package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
- package/dist/client/db-admin/FilterBar.js +68 -0
- package/dist/client/db-admin/FilterBar.js.map +1 -0
- package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
- package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
- package/dist/client/db-admin/ResultsGrid.js +41 -0
- package/dist/client/db-admin/ResultsGrid.js.map +1 -0
- package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
- package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
- package/dist/client/db-admin/RowSidePanel.js +104 -0
- package/dist/client/db-admin/RowSidePanel.js.map +1 -0
- package/dist/client/db-admin/SqlEditor.d.ts +8 -0
- package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
- package/dist/client/db-admin/SqlEditor.js +350 -0
- package/dist/client/db-admin/SqlEditor.js.map +1 -0
- package/dist/client/db-admin/TableBrowser.d.ts +10 -0
- package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
- package/dist/client/db-admin/TableBrowser.js +61 -0
- package/dist/client/db-admin/TableBrowser.js.map +1 -0
- package/dist/client/db-admin/TableEditor.d.ts +9 -0
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
- package/dist/client/db-admin/TableEditor.js +254 -0
- package/dist/client/db-admin/TableEditor.js.map +1 -0
- package/dist/client/db-admin/cell-format.d.ts +55 -0
- package/dist/client/db-admin/cell-format.d.ts.map +1 -0
- package/dist/client/db-admin/cell-format.js +223 -0
- package/dist/client/db-admin/cell-format.js.map +1 -0
- package/dist/client/db-admin/changeset.d.ts +74 -0
- package/dist/client/db-admin/changeset.d.ts.map +1 -0
- package/dist/client/db-admin/changeset.js +169 -0
- package/dist/client/db-admin/changeset.js.map +1 -0
- package/dist/client/db-admin/export-utils.d.ts +15 -0
- package/dist/client/db-admin/export-utils.d.ts.map +1 -0
- package/dist/client/db-admin/export-utils.js +62 -0
- package/dist/client/db-admin/export-utils.js.map +1 -0
- package/dist/client/db-admin/index.d.ts +7 -0
- package/dist/client/db-admin/index.d.ts.map +1 -0
- package/dist/client/db-admin/index.js +8 -0
- package/dist/client/db-admin/index.js.map +1 -0
- package/dist/client/db-admin/sql-storage.d.ts +35 -0
- package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
- package/dist/client/db-admin/sql-storage.js +117 -0
- package/dist/client/db-admin/sql-storage.js.map +1 -0
- package/dist/client/db-admin/storage.d.ts +24 -0
- package/dist/client/db-admin/storage.d.ts.map +1 -0
- package/dist/client/db-admin/storage.js +50 -0
- package/dist/client/db-admin/storage.js.map +1 -0
- package/dist/client/db-admin/useAgentSync.d.ts +22 -0
- package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
- package/dist/client/db-admin/useAgentSync.js +120 -0
- package/dist/client/db-admin/useAgentSync.js.map +1 -0
- package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
- package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
- package/dist/client/db-admin/useDbAdmin.js +154 -0
- package/dist/client/db-admin/useDbAdmin.js.map +1 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +8 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-dev-mode.d.ts +20 -2
- package/dist/client/use-dev-mode.d.ts.map +1 -1
- package/dist/client/use-dev-mode.js +49 -14
- package/dist/client/use-dev-mode.js.map +1 -1
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +25 -5
- package/dist/credentials/index.js.map +1 -1
- package/dist/db-admin/agent-tools.d.ts +15 -0
- package/dist/db-admin/agent-tools.d.ts.map +1 -0
- package/dist/db-admin/agent-tools.js +147 -0
- package/dist/db-admin/agent-tools.js.map +1 -0
- package/dist/db-admin/operations.d.ts +17 -0
- package/dist/db-admin/operations.d.ts.map +1 -0
- package/dist/db-admin/operations.js +541 -0
- package/dist/db-admin/operations.js.map +1 -0
- package/dist/db-admin/routes.d.ts +5 -0
- package/dist/db-admin/routes.d.ts.map +1 -0
- package/dist/db-admin/routes.js +134 -0
- package/dist/db-admin/routes.js.map +1 -0
- package/dist/db-admin/types.d.ts +85 -0
- package/dist/db-admin/types.d.ts.map +1 -0
- package/dist/db-admin/types.js +9 -0
- package/dist/db-admin/types.js.map +1 -0
- package/dist/extensions/url-safety.d.ts +20 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +43 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
- package/dist/file-upload/actions/upload-image.js +6 -1
- package/dist/file-upload/actions/upload-image.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +112 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/types.d.ts +11 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/scripts/db/exec.d.ts.map +1 -1
- package/dist/scripts/db/exec.js +2 -1
- package/dist/scripts/db/exec.js.map +1 -1
- package/dist/scripts/db/index.d.ts.map +1 -1
- package/dist/scripts/db/index.js +1 -0
- package/dist/scripts/db/index.js.map +1 -1
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
- package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
- package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
- package/dist/scripts/db/query.d.ts.map +1 -1
- package/dist/scripts/db/query.js +2 -1
- package/dist/scripts/db/query.js.map +1 -1
- package/dist/scripts/db/safety.d.ts +1 -0
- package/dist/scripts/db/safety.d.ts.map +1 -1
- package/dist/scripts/db/safety.js +32 -0
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/scripts/db/scoping.d.ts.map +1 -1
- package/dist/scripts/db/scoping.js +11 -1
- package/dist/scripts/db/scoping.js.map +1 -1
- package/dist/secrets/crypto.d.ts +28 -0
- package/dist/secrets/crypto.d.ts.map +1 -0
- package/dist/secrets/crypto.js +81 -0
- package/dist/secrets/crypto.js.map +1 -0
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +3 -61
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -2
- 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 +24 -7
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +49 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.js +3 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +5 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +9 -1
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/design-token-utils.d.ts +8 -1
- package/dist/server/design-token-utils.d.ts.map +1 -1
- package/dist/server/design-token-utils.js +12 -4
- package/dist/server/design-token-utils.js.map +1 -1
- package/dist/templates/default/AGENTS.md +4 -4
- package/dist/templates/default/app/routes/database.tsx +13 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +4 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.md +2 -2
- package/docs/content/actions.md +2 -54
- package/docs/content/agent-mentions.md +1 -1
- package/docs/content/agent-teams.md +1 -1
- package/docs/content/authentication.md +2 -2
- package/docs/content/cli-adapters.md +33 -17
- package/docs/content/client.md +11 -20
- package/docs/content/code-agents-ui.md +19 -6
- package/docs/content/context-awareness.md +36 -20
- package/docs/content/database.md +3 -3
- package/docs/content/deployment.md +8 -8
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +5 -1
- package/docs/content/faq.md +1 -0
- package/docs/content/frames.md +116 -30
- package/docs/content/getting-started.md +15 -14
- package/docs/content/mcp-clients.md +1 -1
- package/docs/content/mcp-protocol.md +11 -88
- package/docs/content/messaging.md +1 -1
- package/docs/content/migration-workbench.md +13 -87
- package/docs/content/multi-app-workspace.md +2 -38
- package/docs/content/multi-tenancy.md +3 -26
- package/docs/content/onboarding.md +10 -3
- package/docs/content/recurring-jobs.md +2 -2
- package/docs/content/security.md +33 -1
- package/docs/content/server.md +1 -1
- package/docs/content/template-assets.md +9 -9
- package/docs/content/template-brain.md +114 -388
- package/docs/content/template-clips.md +42 -2
- package/docs/content/template-content.md +1 -1
- package/docs/content/template-design.md +27 -0
- package/docs/content/template-dispatch.md +3 -3
- package/docs/content/template-forms.md +6 -6
- package/docs/content/template-starter.md +2 -2
- package/docs/content/using-your-agent.md +56 -0
- package/docs/content/workspace-management.md +6 -6
- package/docs/content/workspace.md +28 -9
- package/package.json +10 -3
- package/src/templates/default/AGENTS.md +4 -4
- package/src/templates/default/app/routes/database.tsx +13 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Whether the agent is in "Code mode" — the capability toggle that lets the
|
|
3
|
+
* agent run shell/file/raw-DB tools and edit the app's own source code. This is
|
|
4
|
+
* distinct from environment dev mode (NODE_ENV / Vite).
|
|
5
|
+
*
|
|
6
|
+
* Fetches `/_agent-native/agent-chat/mode` on first call, then stays in sync via
|
|
7
|
+
* `setCodeMode`. The endpoint, its `devMode` payload key, the `AGENT_MODE` env
|
|
8
|
+
* var, and the `agent-chat.mode` settings key are unchanged for back-compat.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useCodeMode(apiBase?: string): {
|
|
11
|
+
isCodeMode: boolean;
|
|
12
|
+
canToggle: boolean;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
setCodeMode: (codeMode: boolean) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use {@link useCodeMode} instead. The agent-capability "dev mode"
|
|
18
|
+
* was renamed to "Code mode" to disambiguate it from environment/NODE_ENV dev
|
|
19
|
+
* mode. This alias preserves the old `{ isDevMode, canToggle, isLoading,
|
|
20
|
+
* setDevMode }` shape so existing callers keep working; it delegates to the same
|
|
21
|
+
* shared internal state as `useCodeMode`.
|
|
4
22
|
*/
|
|
5
23
|
export declare function useDevMode(apiBase?: string): {
|
|
6
24
|
isDevMode: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-dev-mode.d.ts","sourceRoot":"","sources":["../../src/client/use-dev-mode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-dev-mode.d.ts","sourceRoot":"","sources":["../../src/client/use-dev-mode.ts"],"names":[],"mappings":"AAqHA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,SAA+C,GACrD;IACD,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAIA;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,SAA+C,GACrD;IACD,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD,CASA"}
|
|
@@ -13,7 +13,7 @@ function isLocalhostHostname() {
|
|
|
13
13
|
const h = window.location.hostname;
|
|
14
14
|
return h === "localhost" || h === "127.0.0.1" || h === "::1";
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function fetchCodeMode(apiBase) {
|
|
17
17
|
if (!fetchPromise) {
|
|
18
18
|
fetchPromise = fetch(`${apiBase}/mode`)
|
|
19
19
|
.then((res) => {
|
|
@@ -27,9 +27,9 @@ function fetchDevMode(apiBase) {
|
|
|
27
27
|
})
|
|
28
28
|
.catch(() => {
|
|
29
29
|
// If the server isn't reachable (503 during boot, connection refused,
|
|
30
|
-
// etc.) but we're clearly on localhost, assume
|
|
31
|
-
// tab and
|
|
32
|
-
// error permanently disables
|
|
30
|
+
// etc.) but we're clearly on localhost, assume Code mode is on so the
|
|
31
|
+
// CLI tab and Code mode toggle still work. Without this, a transient
|
|
32
|
+
// server error permanently disables code features in the sidebar.
|
|
33
33
|
cached = isLocalhostHostname()
|
|
34
34
|
? { devMode: true, canToggle: true }
|
|
35
35
|
: { devMode: false, canToggle: false };
|
|
@@ -42,10 +42,15 @@ function fetchDevMode(apiBase) {
|
|
|
42
42
|
return fetchPromise;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Shared internal state machine backing both `useCodeMode` (primary) and the
|
|
46
|
+
* deprecated `useDevMode` alias. Returns the raw `{ codeMode, canToggle,
|
|
47
|
+
* isLoading, setCodeMode }` shape; the public hooks adapt the field names.
|
|
48
|
+
*
|
|
49
|
+
* The `/mode` endpoint and its `devMode` payload key are unchanged for
|
|
50
|
+
* back-compat — only the user-facing concept name moved from "dev mode" to
|
|
51
|
+
* "Code mode".
|
|
47
52
|
*/
|
|
48
|
-
|
|
53
|
+
function useCodeModeInternal(apiBase) {
|
|
49
54
|
const [state, setState] = useState(cached ?? { devMode: false, canToggle: false });
|
|
50
55
|
const [isLoading, setIsLoading] = useState(cached === null);
|
|
51
56
|
useEffect(() => {
|
|
@@ -61,18 +66,19 @@ export function useDevMode(apiBase = agentNativePath("/_agent-native/agent-chat"
|
|
|
61
66
|
setIsLoading(false);
|
|
62
67
|
return;
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
fetchCodeMode(apiBase).then((val) => {
|
|
65
70
|
setState(val);
|
|
66
71
|
setIsLoading(false);
|
|
67
72
|
});
|
|
68
73
|
}, [apiBase]);
|
|
69
|
-
const
|
|
70
|
-
// Optimistic update — apply immediately, then confirm with server
|
|
71
|
-
|
|
74
|
+
const setCodeMode = useCallback(async (codeMode) => {
|
|
75
|
+
// Optimistic update — apply immediately, then confirm with server.
|
|
76
|
+
// The endpoint still speaks `devMode` for back-compat.
|
|
77
|
+
notifyListeners({ devMode: codeMode, canToggle: true });
|
|
72
78
|
const res = await fetch(`${apiBase}/mode`, {
|
|
73
79
|
method: "POST",
|
|
74
80
|
headers: { "Content-Type": "application/json" },
|
|
75
|
-
body: JSON.stringify({ devMode }),
|
|
81
|
+
body: JSON.stringify({ devMode: codeMode }),
|
|
76
82
|
});
|
|
77
83
|
if (res.ok) {
|
|
78
84
|
const data = await res.json();
|
|
@@ -80,10 +86,39 @@ export function useDevMode(apiBase = agentNativePath("/_agent-native/agent-chat"
|
|
|
80
86
|
}
|
|
81
87
|
}, [apiBase]);
|
|
82
88
|
return {
|
|
83
|
-
|
|
89
|
+
codeMode: state.devMode,
|
|
84
90
|
canToggle: state.canToggle,
|
|
85
91
|
isLoading,
|
|
86
|
-
|
|
92
|
+
setCodeMode,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether the agent is in "Code mode" — the capability toggle that lets the
|
|
97
|
+
* agent run shell/file/raw-DB tools and edit the app's own source code. This is
|
|
98
|
+
* distinct from environment dev mode (NODE_ENV / Vite).
|
|
99
|
+
*
|
|
100
|
+
* Fetches `/_agent-native/agent-chat/mode` on first call, then stays in sync via
|
|
101
|
+
* `setCodeMode`. The endpoint, its `devMode` payload key, the `AGENT_MODE` env
|
|
102
|
+
* var, and the `agent-chat.mode` settings key are unchanged for back-compat.
|
|
103
|
+
*/
|
|
104
|
+
export function useCodeMode(apiBase = agentNativePath("/_agent-native/agent-chat")) {
|
|
105
|
+
const { codeMode, canToggle, isLoading, setCodeMode } = useCodeModeInternal(apiBase);
|
|
106
|
+
return { isCodeMode: codeMode, canToggle, isLoading, setCodeMode };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Use {@link useCodeMode} instead. The agent-capability "dev mode"
|
|
110
|
+
* was renamed to "Code mode" to disambiguate it from environment/NODE_ENV dev
|
|
111
|
+
* mode. This alias preserves the old `{ isDevMode, canToggle, isLoading,
|
|
112
|
+
* setDevMode }` shape so existing callers keep working; it delegates to the same
|
|
113
|
+
* shared internal state as `useCodeMode`.
|
|
114
|
+
*/
|
|
115
|
+
export function useDevMode(apiBase = agentNativePath("/_agent-native/agent-chat")) {
|
|
116
|
+
const { codeMode, canToggle, isLoading, setCodeMode } = useCodeModeInternal(apiBase);
|
|
117
|
+
return {
|
|
118
|
+
isDevMode: codeMode,
|
|
119
|
+
canToggle,
|
|
120
|
+
isLoading,
|
|
121
|
+
setDevMode: setCodeMode,
|
|
87
122
|
};
|
|
88
123
|
}
|
|
89
124
|
//# sourceMappingURL=use-dev-mode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-dev-mode.js","sourceRoot":"","sources":["../../src/client/use-dev-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAOhD,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"use-dev-mode.js","sourceRoot":"","sources":["../../src/client/use-dev-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\ninterface CodeModeState {\n devMode: boolean;\n canToggle: boolean;\n}\n\nlet cached: CodeModeState | null = null;\nlet fetchPromise: Promise<CodeModeState> | null = null;\nlet listeners: Set<(state: CodeModeState) => void> = new Set();\n\nfunction notifyListeners(state: CodeModeState) {\n cached = state;\n listeners.forEach((fn) => fn(state));\n}\n\nfunction isLocalhostHostname(): boolean {\n if (typeof window === \"undefined\") return false;\n const h = window.location.hostname;\n return h === \"localhost\" || h === \"127.0.0.1\" || h === \"::1\";\n}\n\nfunction fetchCodeMode(apiBase: string): Promise<CodeModeState> {\n if (!fetchPromise) {\n fetchPromise = fetch(`${apiBase}/mode`)\n .then((res) => {\n if (!res.ok) throw new Error(`${res.status}`);\n return res.json();\n })\n .then((data: CodeModeState) => {\n cached = data;\n return cached;\n })\n .catch(() => {\n // If the server isn't reachable (503 during boot, connection refused,\n // etc.) but we're clearly on localhost, assume Code mode is on so the\n // CLI tab and Code mode toggle still work. Without this, a transient\n // server error permanently disables code features in the sidebar.\n cached = isLocalhostHostname()\n ? { devMode: true, canToggle: true }\n : { devMode: false, canToggle: false };\n // Null the in-flight promise so the next call retries the fetch\n // and we can pick up the real answer once the server is back.\n fetchPromise = null;\n return cached;\n });\n }\n return fetchPromise;\n}\n\n/**\n * Shared internal state machine backing both `useCodeMode` (primary) and the\n * deprecated `useDevMode` alias. Returns the raw `{ codeMode, canToggle,\n * isLoading, setCodeMode }` shape; the public hooks adapt the field names.\n *\n * The `/mode` endpoint and its `devMode` payload key are unchanged for\n * back-compat — only the user-facing concept name moved from \"dev mode\" to\n * \"Code mode\".\n */\nfunction useCodeModeInternal(apiBase: string): {\n codeMode: boolean;\n canToggle: boolean;\n isLoading: boolean;\n setCodeMode: (codeMode: boolean) => Promise<void>;\n} {\n const [state, setState] = useState<CodeModeState>(\n cached ?? { devMode: false, canToggle: false },\n );\n const [isLoading, setIsLoading] = useState(cached === null);\n\n useEffect(() => {\n // Subscribe to changes from other hook instances\n listeners.add(setState);\n return () => {\n listeners.delete(setState);\n };\n }, []);\n\n useEffect(() => {\n if (cached !== null) {\n setState(cached);\n setIsLoading(false);\n return;\n }\n fetchCodeMode(apiBase).then((val) => {\n setState(val);\n setIsLoading(false);\n });\n }, [apiBase]);\n\n const setCodeMode = useCallback(\n async (codeMode: boolean) => {\n // Optimistic update — apply immediately, then confirm with server.\n // The endpoint still speaks `devMode` for back-compat.\n notifyListeners({ devMode: codeMode, canToggle: true });\n const res = await fetch(`${apiBase}/mode`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ devMode: codeMode }),\n });\n if (res.ok) {\n const data: CodeModeState = await res.json();\n notifyListeners(data);\n }\n },\n [apiBase],\n );\n\n return {\n codeMode: state.devMode,\n canToggle: state.canToggle,\n isLoading,\n setCodeMode,\n };\n}\n\n/**\n * Whether the agent is in \"Code mode\" — the capability toggle that lets the\n * agent run shell/file/raw-DB tools and edit the app's own source code. This is\n * distinct from environment dev mode (NODE_ENV / Vite).\n *\n * Fetches `/_agent-native/agent-chat/mode` on first call, then stays in sync via\n * `setCodeMode`. The endpoint, its `devMode` payload key, the `AGENT_MODE` env\n * var, and the `agent-chat.mode` settings key are unchanged for back-compat.\n */\nexport function useCodeMode(\n apiBase = agentNativePath(\"/_agent-native/agent-chat\"),\n): {\n isCodeMode: boolean;\n canToggle: boolean;\n isLoading: boolean;\n setCodeMode: (codeMode: boolean) => Promise<void>;\n} {\n const { codeMode, canToggle, isLoading, setCodeMode } =\n useCodeModeInternal(apiBase);\n return { isCodeMode: codeMode, canToggle, isLoading, setCodeMode };\n}\n\n/**\n * @deprecated Use {@link useCodeMode} instead. The agent-capability \"dev mode\"\n * was renamed to \"Code mode\" to disambiguate it from environment/NODE_ENV dev\n * mode. This alias preserves the old `{ isDevMode, canToggle, isLoading,\n * setDevMode }` shape so existing callers keep working; it delegates to the same\n * shared internal state as `useCodeMode`.\n */\nexport function useDevMode(\n apiBase = agentNativePath(\"/_agent-native/agent-chat\"),\n): {\n isDevMode: boolean;\n canToggle: boolean;\n isLoading: boolean;\n setDevMode: (devMode: boolean) => Promise<void>;\n} {\n const { codeMode, canToggle, isLoading, setCodeMode } =\n useCodeModeInternal(apiBase);\n return {\n isDevMode: codeMode,\n canToggle,\n isLoading,\n setDevMode: setCodeMode,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,KAAK,CAAC;AA6BpD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAc7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSetting, putSetting, deleteSetting } from "../settings/store.js";
|
|
2
|
+
import { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "../secrets/crypto.js";
|
|
2
3
|
const SETTING_PREFIX = "credential:";
|
|
3
4
|
function userCredentialSettingKey(email, key) {
|
|
4
5
|
return `u:${email.toLowerCase()}:${SETTING_PREFIX}${key}`;
|
|
@@ -8,9 +9,22 @@ function orgCredentialSettingKey(orgId, key) {
|
|
|
8
9
|
}
|
|
9
10
|
async function readCredentialSetting(settingKey) {
|
|
10
11
|
const setting = await getSetting(settingKey);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (!setting || typeof setting.value !== "string")
|
|
13
|
+
return undefined;
|
|
14
|
+
const stored = setting.value;
|
|
15
|
+
// Values written by saveCredential are AES-256-GCM encrypted at rest.
|
|
16
|
+
// Rows that predate encryption are plaintext — read them transparently
|
|
17
|
+
// (the migrate-encrypt-credentials script re-encrypts them in place).
|
|
18
|
+
if (!isEncryptedSecretValue(stored))
|
|
19
|
+
return stored;
|
|
20
|
+
try {
|
|
21
|
+
return decryptSecretValue(stored);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Key rotated, corrupt, or tampered row — treat as not set rather than
|
|
25
|
+
// surfacing ciphertext or throwing into every credential lookup.
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
14
28
|
}
|
|
15
29
|
/**
|
|
16
30
|
* Resolve a credential from one explicit legacy SQL credential scope.
|
|
@@ -69,15 +83,21 @@ export async function saveCredential(key, value, ctx) {
|
|
|
69
83
|
if (!ctx?.userEmail) {
|
|
70
84
|
throw new Error("saveCredential requires CredentialContext with userEmail");
|
|
71
85
|
}
|
|
86
|
+
// Encrypt at rest (AES-256-GCM) so a leaked DB backup / pg_dump / read
|
|
87
|
+
// replica doesn't expose plaintext keys. resolveCredential decrypts
|
|
88
|
+
// transparently on read.
|
|
89
|
+
const encrypted = encryptSecretValue(value);
|
|
72
90
|
if (ctx.scope === "org") {
|
|
73
91
|
if (!ctx.orgId) {
|
|
74
92
|
throw new Error("saveCredential scope='org' requires orgId");
|
|
75
93
|
}
|
|
76
|
-
await putSetting(orgCredentialSettingKey(ctx.orgId, key), {
|
|
94
|
+
await putSetting(orgCredentialSettingKey(ctx.orgId, key), {
|
|
95
|
+
value: encrypted,
|
|
96
|
+
});
|
|
77
97
|
return;
|
|
78
98
|
}
|
|
79
99
|
await putSetting(userCredentialSettingKey(ctx.userEmail, key), {
|
|
80
|
-
value,
|
|
100
|
+
value: encrypted,
|
|
81
101
|
});
|
|
82
102
|
}
|
|
83
103
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,cAAc,GAAG,aAAa,CAAC;AASrC,SAAS,wBAAwB,CAAC,KAAa,EAAE,GAAW;IAC1D,OAAO,KAAK,KAAK,CAAC,WAAW,EAAE,IAAI,cAAc,GAAG,GAAG,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAa,EAAE,GAAW;IACzD,OAAO,KAAK,KAAK,IAAI,cAAc,GAAG,GAAG,EAAE,CAAC;AAC9C,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,UAAkB;IAElB,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACpE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,sEAAsE;IACtE,uEAAuE;IACvE,sEAAsE;IACtE,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;QACvE,iEAAiE;QACjE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,GAAW,EACX,GAA0D;IAE1D,IAAI,CAAC,GAAG,EAAE,SAAS;QAAE,OAAO,SAAS,CAAC;IACtC,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QACjC,OAAO,qBAAqB,CAAC,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,qBAAqB,CAAC,wBAAwB,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAW,EACX,GAAsB;IAEtB,IAAI,CAAC,GAAG,EAAE,SAAS;QAAE,OAAO,SAAS,CAAC;IAEtC,MAAM,WAAW,GAAG,MAAM,yBAAyB,CAAC,GAAG,EAAE;QACvD,GAAG,GAAG;QACN,KAAK,EAAE,MAAM;KACd,CAAC,CAAC;IACH,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,yBAAyB,CAAC,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,GAAsB;IAEtB,OAAO,CAAC,MAAM,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,SAAS,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,KAAa,EACb,GAAmD;IAEnD,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,uEAAuE;IACvE,oEAAoE;IACpE,yBAAyB;IACzB,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,UAAU,CAAC,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;YACxD,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,MAAM,UAAU,CAAC,wBAAwB,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE;QAC7D,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,GAAmD;IAEnD,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,aAAa,CAAC,uBAAuB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IACD,MAAM,aAAa,CAAC,wBAAwB,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC","sourcesContent":["import { getSetting, putSetting, deleteSetting } from \"../settings/store.js\";\nimport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\n\nconst SETTING_PREFIX = \"credential:\";\n\nexport interface CredentialContext {\n userEmail: string;\n orgId?: string | null;\n}\n\nexport type CredentialStorageScope = \"user\" | \"org\";\n\nfunction userCredentialSettingKey(email: string, key: string): string {\n return `u:${email.toLowerCase()}:${SETTING_PREFIX}${key}`;\n}\n\nfunction orgCredentialSettingKey(orgId: string, key: string): string {\n return `o:${orgId}:${SETTING_PREFIX}${key}`;\n}\n\nasync function readCredentialSetting(\n settingKey: string,\n): Promise<string | undefined> {\n const setting = await getSetting(settingKey);\n if (!setting || typeof setting.value !== \"string\") return undefined;\n const stored = setting.value;\n // Values written by saveCredential are AES-256-GCM encrypted at rest.\n // Rows that predate encryption are plaintext — read them transparently\n // (the migrate-encrypt-credentials script re-encrypts them in place).\n if (!isEncryptedSecretValue(stored)) return stored;\n try {\n return decryptSecretValue(stored);\n } catch {\n // Key rotated, corrupt, or tampered row — treat as not set rather than\n // surfacing ciphertext or throwing into every credential lookup.\n return undefined;\n }\n}\n\n/**\n * Resolve a credential from one explicit legacy SQL credential scope.\n *\n * Prefer `resolveCredential()` for normal app-local credential lookup. This\n * helper exists for workspace connection refs, where a ref can explicitly say\n * \"use the org-scoped key\" and must not accidentally read a user override.\n */\nexport async function resolveCredentialForScope(\n key: string,\n ctx: CredentialContext & { scope: CredentialStorageScope },\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) return undefined;\n return readCredentialSetting(orgCredentialSettingKey(ctx.orgId, key));\n }\n return readCredentialSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n\n/**\n * Resolve a credential, scoped to the caller's user (and falling back to\n * the active org's shared credential, if any).\n *\n * SECURITY: NEVER reads from process.env. Env vars are global to the\n * deployment and would leak across users in a multi-tenant app. The only\n * sources are per-user / per-org rows in the SQL `settings` table.\n *\n * Storage keys (priority order):\n * 1. u:<email>:credential:<KEY> — per-user override\n * 2. o:<orgId>:credential:<KEY> — per-org shared credential (if orgId given)\n */\nexport async function resolveCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n\n const userSetting = await resolveCredentialForScope(key, {\n ...ctx,\n scope: \"user\",\n });\n if (userSetting) return userSetting;\n\n if (ctx.orgId) {\n return resolveCredentialForScope(key, { ...ctx, scope: \"org\" });\n }\n\n return undefined;\n}\n\n/**\n * Check if a credential is available for the given context.\n */\nexport async function hasCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<boolean> {\n return (await resolveCredential(key, ctx)) !== undefined;\n}\n\n/**\n * Save a credential. By default writes to the per-user store; pass\n * `scope: \"org\"` to write to the active org's shared credentials.\n */\nexport async function saveCredential(\n key: string,\n value: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\"saveCredential requires CredentialContext with userEmail\");\n }\n // Encrypt at rest (AES-256-GCM) so a leaked DB backup / pg_dump / read\n // replica doesn't expose plaintext keys. resolveCredential decrypts\n // transparently on read.\n const encrypted = encryptSecretValue(value);\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"saveCredential scope='org' requires orgId\");\n }\n await putSetting(orgCredentialSettingKey(ctx.orgId, key), {\n value: encrypted,\n });\n return;\n }\n await putSetting(userCredentialSettingKey(ctx.userEmail, key), {\n value: encrypted,\n });\n}\n\n/**\n * Delete a credential from the per-user (default) or per-org store.\n */\nexport async function deleteCredential(\n key: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\n \"deleteCredential requires CredentialContext with userEmail\",\n );\n }\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"deleteCredential scope='org' requires orgId\");\n }\n await deleteSetting(orgCredentialSettingKey(ctx.orgId, key));\n return;\n }\n await deleteSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only agent tools for the database admin.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the HTTP routes and call the SAME `operations.ts` functions, so
|
|
5
|
+
* the agent can introspect and edit the full database during local development.
|
|
6
|
+
* They are spread into the DEV actions surface ONLY (never `prodActions`) and
|
|
7
|
+
* the dev surface itself is gated to dev + localhost upstream.
|
|
8
|
+
*
|
|
9
|
+
* Complex params (sort / filters / inserts / updates / deletes / params) are
|
|
10
|
+
* accepted as JSON strings and parsed here, matching how the framework's other
|
|
11
|
+
* structured CLI-style tools accept JSON-encoded arguments.
|
|
12
|
+
*/
|
|
13
|
+
import type { ActionEntry } from "../agent/production-agent.js";
|
|
14
|
+
export declare function createDbAdminAgentTools(): Record<string, ActionEntry>;
|
|
15
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/db-admin/agent-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAuBhE,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAgKrE"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { applyMutations, getRows, getTableSchema, listTables, runSql, DbAdminConfirmRequiredError, } from "./operations.js";
|
|
2
|
+
function parseJson(value, field) {
|
|
3
|
+
if (value == null || value === "")
|
|
4
|
+
return undefined;
|
|
5
|
+
if (typeof value !== "string")
|
|
6
|
+
return value;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(value);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error(`Invalid JSON for "${field}": ${String(value).slice(0, 200)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function createDbAdminAgentTools() {
|
|
15
|
+
return {
|
|
16
|
+
"db-admin-tables": {
|
|
17
|
+
tool: {
|
|
18
|
+
description: "DEV ONLY. List every table and view in the full database (unscoped) with row counts. Use to see the whole data model before reading or editing rows.",
|
|
19
|
+
parameters: { type: "object", properties: {} },
|
|
20
|
+
},
|
|
21
|
+
readOnly: true,
|
|
22
|
+
run: async () => JSON.stringify(await listTables()),
|
|
23
|
+
},
|
|
24
|
+
"db-admin-schema": {
|
|
25
|
+
tool: {
|
|
26
|
+
description: "DEV ONLY. Get the full schema for one table or view: columns, types, nullability, primary key, foreign keys, indexes, and row count.",
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
table: { type: "string", description: "Table or view name" },
|
|
31
|
+
},
|
|
32
|
+
required: ["table"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
readOnly: true,
|
|
36
|
+
run: async (args) => JSON.stringify(await getTableSchema(String(args.table))),
|
|
37
|
+
},
|
|
38
|
+
"db-admin-rows": {
|
|
39
|
+
tool: {
|
|
40
|
+
description: "DEV ONLY. Read rows from one table (unscoped) with pagination, sorting, and filters. Returns column metadata, the page of rows, and the total count.",
|
|
41
|
+
parameters: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
table: { type: "string", description: "Table or view name" },
|
|
45
|
+
page: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "1-based page number (default 1)",
|
|
48
|
+
},
|
|
49
|
+
pageSize: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Rows per page (default 50, max 1000)",
|
|
52
|
+
},
|
|
53
|
+
sort: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: 'JSON array of sort specs, e.g. \'[{"column":"created_at","dir":"desc"}]\'',
|
|
56
|
+
},
|
|
57
|
+
filters: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: 'JSON array of filters, e.g. \'[{"column":"status","op":"eq","value":"draft"}]\'. Ops: eq, neq, lt, lte, gt, gte, like, ilike, in, is_null, not_null.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["table"],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
readOnly: true,
|
|
66
|
+
run: async (args) => {
|
|
67
|
+
const result = await getRows(String(args.table), {
|
|
68
|
+
page: Number(args.page) || 1,
|
|
69
|
+
pageSize: Number(args.pageSize) || 50,
|
|
70
|
+
sort: parseJson(args.sort, "sort"),
|
|
71
|
+
filters: parseJson(args.filters, "filters"),
|
|
72
|
+
});
|
|
73
|
+
return JSON.stringify(result);
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
"db-admin-mutate": {
|
|
77
|
+
tool: {
|
|
78
|
+
description: 'DEV ONLY. Insert, update, and/or delete rows in one table (unscoped — full database access). PREFER THIS over db-exec/db-patch for any database-admin edit, and ALWAYS use it (not db-exec) for tables without owner_email/org_id columns — db-exec auto-scopes to the current user and will match 0 rows on unscoped tables. Pass `dryRun: "true"` to get the SQL without executing. Each update/delete must include a where clause.',
|
|
79
|
+
parameters: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
table: { type: "string", description: "Table name" },
|
|
83
|
+
inserts: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: 'JSON array of row objects to insert, e.g. \'[{"id":"1","title":"Hi"}]\'',
|
|
86
|
+
},
|
|
87
|
+
updates: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: 'JSON array of {where, set} objects, e.g. \'[{"where":{"id":"1"},"set":{"title":"Bye"}}]\'',
|
|
90
|
+
},
|
|
91
|
+
deletes: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: 'JSON array of where objects, e.g. \'[{"id":"1"}]\'',
|
|
94
|
+
},
|
|
95
|
+
dryRun: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: 'Set to "true" to return SQL without executing',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
required: ["table"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
run: async (args) => {
|
|
104
|
+
const mutation = {
|
|
105
|
+
inserts: parseJson(args.inserts, "inserts"),
|
|
106
|
+
updates: parseJson(args.updates, "updates"),
|
|
107
|
+
deletes: parseJson(args.deletes, "deletes"),
|
|
108
|
+
dryRun: String(args.dryRun) === "true",
|
|
109
|
+
};
|
|
110
|
+
return JSON.stringify(await applyMutations(String(args.table), mutation));
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
"db-admin-query": {
|
|
114
|
+
tool: {
|
|
115
|
+
description: 'DEV ONLY. Run arbitrary SQL against the full database (unscoped). PREFER THIS over db-query/db-exec for database-admin work and for any table without owner_email/org_id scoping (the scoped tools match 0 rows on those). Bare SELECTs are auto-limited to 100 rows. Destructive statements (DROP / TRUNCATE / unscoped DELETE or UPDATE) require confirmDestructive: "true".',
|
|
116
|
+
parameters: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
sql: { type: "string", description: "SQL statement to run" },
|
|
120
|
+
params: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Optional JSON array of positional bind args for ? placeholders",
|
|
123
|
+
},
|
|
124
|
+
confirmDestructive: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: 'Set to "true" to allow destructive statements',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ["sql"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
run: async (args) => {
|
|
133
|
+
try {
|
|
134
|
+
const result = await runSql(String(args.sql), parseJson(args.params, "params"), { confirmDestructive: String(args.confirmDestructive) === "true" });
|
|
135
|
+
return JSON.stringify(result);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
if (err instanceof DbAdminConfirmRequiredError) {
|
|
139
|
+
return JSON.stringify({ needsConfirm: true, error: err.message });
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=agent-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.js","sourceRoot":"","sources":["../../src/db-admin/agent-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Dev-only agent tools for the database admin.\n *\n * These mirror the HTTP routes and call the SAME `operations.ts` functions, so\n * the agent can introspect and edit the full database during local development.\n * They are spread into the DEV actions surface ONLY (never `prodActions`) and\n * the dev surface itself is gated to dev + localhost upstream.\n *\n * Complex params (sort / filters / inserts / updates / deletes / params) are\n * accepted as JSON strings and parsed here, matching how the framework's other\n * structured CLI-style tools accept JSON-encoded arguments.\n */\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n applyMutations,\n getRows,\n getTableSchema,\n listTables,\n runSql,\n DbAdminConfirmRequiredError,\n} from \"./operations.js\";\nimport type { DbAdminFilter, DbAdminMutation, DbAdminSort } from \"./types.js\";\n\nfunction parseJson<T>(value: unknown, field: string): T | undefined {\n if (value == null || value === \"\") return undefined;\n if (typeof value !== \"string\") return value as T;\n try {\n return JSON.parse(value) as T;\n } catch {\n throw new Error(\n `Invalid JSON for \"${field}\": ${String(value).slice(0, 200)}`,\n );\n }\n}\n\nexport function createDbAdminAgentTools(): Record<string, ActionEntry> {\n return {\n \"db-admin-tables\": {\n tool: {\n description:\n \"DEV ONLY. List every table and view in the full database (unscoped) with row counts. Use to see the whole data model before reading or editing rows.\",\n parameters: { type: \"object\", properties: {} },\n },\n readOnly: true,\n run: async () => JSON.stringify(await listTables()),\n },\n\n \"db-admin-schema\": {\n tool: {\n description:\n \"DEV ONLY. Get the full schema for one table or view: columns, types, nullability, primary key, foreign keys, indexes, and row count.\",\n parameters: {\n type: \"object\",\n properties: {\n table: { type: \"string\", description: \"Table or view name\" },\n },\n required: [\"table\"],\n },\n },\n readOnly: true,\n run: async (args: Record<string, string>) =>\n JSON.stringify(await getTableSchema(String(args.table))),\n },\n\n \"db-admin-rows\": {\n tool: {\n description:\n \"DEV ONLY. Read rows from one table (unscoped) with pagination, sorting, and filters. Returns column metadata, the page of rows, and the total count.\",\n parameters: {\n type: \"object\",\n properties: {\n table: { type: \"string\", description: \"Table or view name\" },\n page: {\n type: \"string\",\n description: \"1-based page number (default 1)\",\n },\n pageSize: {\n type: \"string\",\n description: \"Rows per page (default 50, max 1000)\",\n },\n sort: {\n type: \"string\",\n description:\n 'JSON array of sort specs, e.g. \\'[{\"column\":\"created_at\",\"dir\":\"desc\"}]\\'',\n },\n filters: {\n type: \"string\",\n description:\n 'JSON array of filters, e.g. \\'[{\"column\":\"status\",\"op\":\"eq\",\"value\":\"draft\"}]\\'. Ops: eq, neq, lt, lte, gt, gte, like, ilike, in, is_null, not_null.',\n },\n },\n required: [\"table\"],\n },\n },\n readOnly: true,\n run: async (args: Record<string, string>) => {\n const result = await getRows(String(args.table), {\n page: Number(args.page) || 1,\n pageSize: Number(args.pageSize) || 50,\n sort: parseJson<DbAdminSort[]>(args.sort, \"sort\"),\n filters: parseJson<DbAdminFilter[]>(args.filters, \"filters\"),\n });\n return JSON.stringify(result);\n },\n },\n\n \"db-admin-mutate\": {\n tool: {\n description:\n 'DEV ONLY. Insert, update, and/or delete rows in one table (unscoped — full database access). PREFER THIS over db-exec/db-patch for any database-admin edit, and ALWAYS use it (not db-exec) for tables without owner_email/org_id columns — db-exec auto-scopes to the current user and will match 0 rows on unscoped tables. Pass `dryRun: \"true\"` to get the SQL without executing. Each update/delete must include a where clause.',\n parameters: {\n type: \"object\",\n properties: {\n table: { type: \"string\", description: \"Table name\" },\n inserts: {\n type: \"string\",\n description:\n 'JSON array of row objects to insert, e.g. \\'[{\"id\":\"1\",\"title\":\"Hi\"}]\\'',\n },\n updates: {\n type: \"string\",\n description:\n 'JSON array of {where, set} objects, e.g. \\'[{\"where\":{\"id\":\"1\"},\"set\":{\"title\":\"Bye\"}}]\\'',\n },\n deletes: {\n type: \"string\",\n description: 'JSON array of where objects, e.g. \\'[{\"id\":\"1\"}]\\'',\n },\n dryRun: {\n type: \"string\",\n description: 'Set to \"true\" to return SQL without executing',\n },\n },\n required: [\"table\"],\n },\n },\n run: async (args: Record<string, string>) => {\n const mutation: DbAdminMutation = {\n inserts: parseJson<Record<string, unknown>[]>(\n args.inserts,\n \"inserts\",\n ),\n updates: parseJson<\n { where: Record<string, unknown>; set: Record<string, unknown> }[]\n >(args.updates, \"updates\"),\n deletes: parseJson<Record<string, unknown>[]>(\n args.deletes,\n \"deletes\",\n ),\n dryRun: String(args.dryRun) === \"true\",\n };\n return JSON.stringify(\n await applyMutations(String(args.table), mutation),\n );\n },\n },\n\n \"db-admin-query\": {\n tool: {\n description:\n 'DEV ONLY. Run arbitrary SQL against the full database (unscoped). PREFER THIS over db-query/db-exec for database-admin work and for any table without owner_email/org_id scoping (the scoped tools match 0 rows on those). Bare SELECTs are auto-limited to 100 rows. Destructive statements (DROP / TRUNCATE / unscoped DELETE or UPDATE) require confirmDestructive: \"true\".',\n parameters: {\n type: \"object\",\n properties: {\n sql: { type: \"string\", description: \"SQL statement to run\" },\n params: {\n type: \"string\",\n description:\n \"Optional JSON array of positional bind args for ? placeholders\",\n },\n confirmDestructive: {\n type: \"string\",\n description: 'Set to \"true\" to allow destructive statements',\n },\n },\n required: [\"sql\"],\n },\n },\n run: async (args: Record<string, string>) => {\n try {\n const result = await runSql(\n String(args.sql),\n parseJson<unknown[]>(args.params, \"params\"),\n { confirmDestructive: String(args.confirmDestructive) === \"true\" },\n );\n return JSON.stringify(result);\n } catch (err) {\n if (err instanceof DbAdminConfirmRequiredError) {\n return JSON.stringify({ needsConfirm: true, error: err.message });\n }\n throw err;\n }\n },\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DbAdminDialect, DbAdminMutation, DbAdminMutationResult, DbAdminQueryResult, DbAdminRowsRequest, DbAdminRowsResult, DbAdminTableSchema, DbAdminTableSummary } from "./types.js";
|
|
2
|
+
export declare function listTables(): Promise<{
|
|
3
|
+
dialect: DbAdminDialect;
|
|
4
|
+
tables: DbAdminTableSummary[];
|
|
5
|
+
}>;
|
|
6
|
+
export declare function getTableSchema(table: string): Promise<DbAdminTableSchema>;
|
|
7
|
+
export declare function getRows(table: string, req: DbAdminRowsRequest): Promise<DbAdminRowsResult>;
|
|
8
|
+
export declare function applyMutations(table: string, m: DbAdminMutation): Promise<DbAdminMutationResult>;
|
|
9
|
+
/** Error thrown when a destructive statement is run without confirmation. */
|
|
10
|
+
export declare class DbAdminConfirmRequiredError extends Error {
|
|
11
|
+
readonly needsConfirm = true;
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare function runSql(sql: string, params: unknown[] | undefined, opts?: {
|
|
15
|
+
confirmDestructive?: boolean;
|
|
16
|
+
}): Promise<DbAdminQueryResult>;
|
|
17
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/db-admin/operations.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,cAAc,EAId,eAAe,EACf,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAyCpB,wBAAsB,UAAU,IAAI,OAAO,CAAC;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B,CAAC,CAwCD;AAoBD,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,CAAC,CAK7B;AAwRD,wBAAsB,OAAO,CAC3B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CA+B5B;AAyDD,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,CAAC,EAAE,eAAe,GACjB,OAAO,CAAC,qBAAqB,CAAC,CA+ChC;AAMD,6EAA6E;AAC7E,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,YAAY,QAAQ;gBACjB,OAAO,EAAE,MAAM;CAI5B;AAmCD,wBAAsB,MAAM,CAC1B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAC7B,IAAI,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,OAAO,CAAC,kBAAkB,CAAC,CAuC7B"}
|