@agent-native/core 0.228.2-nightly-20261113032548 → 0.228.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/application-state/handlers.d.ts +2 -2
  2. package/dist/cli/skills-content/an-skill.d.ts +1 -1
  3. package/dist/cli/skills-content/an-skill.js +16 -0
  4. package/dist/collab/routes.d.ts +1 -1
  5. package/dist/db/schema-snapshot.generated.js +2 -1
  6. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  7. package/dist/file-upload/builder-gateway.d.ts +42 -0
  8. package/dist/file-upload/builder-gateway.js +185 -0
  9. package/dist/file-upload/builder.js +8 -11
  10. package/dist/file-upload/index.d.ts +1 -0
  11. package/dist/file-upload/index.js +1 -0
  12. package/dist/framework-package-versions.json +11 -11
  13. package/dist/identity/rekey.js +2 -0
  14. package/dist/mcp/embed-app.js +259 -3
  15. package/dist/mcp/oauth-route.js +7 -0
  16. package/dist/mcp/screen-memory-stdio.d.ts +3 -3
  17. package/dist/notifications/routes.d.ts +3 -3
  18. package/dist/observability/routes.d.ts +5 -5
  19. package/dist/org/index.d.ts +1 -1
  20. package/dist/secrets/api-keys.js +8 -7
  21. package/dist/secrets/index.d.ts +2 -1
  22. package/dist/secrets/index.js +2 -1
  23. package/dist/secrets/routes.d.ts +3 -3
  24. package/dist/secrets/routes.js +7 -8
  25. package/dist/secrets/schema.d.ts +51 -0
  26. package/dist/secrets/schema.js +3 -0
  27. package/dist/secrets/storage.d.ts +83 -1
  28. package/dist/secrets/storage.js +194 -6
  29. package/dist/secrets/usage.js +2 -4
  30. package/dist/secrets/vault-managed.d.ts +17 -0
  31. package/dist/secrets/vault-managed.js +19 -0
  32. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  33. package/dist/server/builder-signup-experiment.d.ts +4 -4
  34. package/dist/server/credential-provider.js +7 -7
  35. package/dist/server/first-touch-route.d.ts +1 -1
  36. package/dist/server/transcribe-voice.d.ts +1 -1
  37. package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +11 -0
  38. package/docs/content/security.mdx +1 -1
  39. package/docs/content/template-dispatch-developers.mdx +7 -7
  40. package/docs/content/template-dispatch-reference.mdx +16 -10
  41. package/package.json +4 -4
  42. package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +11 -0
@@ -31,6 +31,9 @@ export function embedApp(options = {}) {
31
31
  return {
32
32
  title,
33
33
  requiresEmbedSession: true,
34
+ // Claude reads this from the resources/read content item and moves the
35
+ // view to its side panel; hosts that do not know the field ignore it.
36
+ _meta: { ui: { preferredDisplayMode: "fullscreen" } },
34
37
  ...(options.description ? { description: options.description } : {}),
35
38
  html: (ctx) => {
36
39
  const remoteBridgeFallbackEnabled = ctx.catalogMode !== "directory";
@@ -1363,6 +1366,7 @@ export function embedApp(options = {}) {
1363
1366
  window.history.replaceState(window.history.state, "", localPathFromUrl(appUrl, false));
1364
1367
  } catch {}
1365
1368
  await mountTransplantedHtml(html, appUrl);
1369
+ watchPageTools();
1366
1370
  embedSessionRefreshAttempts = 0;
1367
1371
  notifyHostHeightRepeatedly();
1368
1372
  }
@@ -1756,6 +1760,7 @@ export function embedApp(options = {}) {
1756
1760
  ${ctx.catalogMode === "directory" ? "" : 'frame.allow = "clipboard-read; clipboard-write";'}
1757
1761
  appFrame = frame;
1758
1762
  appFrameReady = false;
1763
+ notifyViewToolsChanged();
1759
1764
  lastFrameSrc = src;
1760
1765
  appFrameDocumentState.loadGeneration += 1;
1761
1766
  frame.addEventListener("load", () => {
@@ -2642,6 +2647,226 @@ export function embedApp(options = {}) {
2642
2647
  if (bridge && (!appFrame || openAiBridge)) syncOpenAiBridge(bridge);
2643
2648
  }, { passive: true });
2644
2649
 
2650
+ // App tools: the host calls these on this open view, so a "filter it" or
2651
+ // "open that one" turn changes the panel in place instead of opening a new view.
2652
+ const readViewToolName = "read_view";
2653
+ const navigateViewToolName = "navigate_view";
2654
+ const maxViewTextChars = 6000;
2655
+ const maxPageTools = 60;
2656
+ const pageToolWaitMs = 40000;
2657
+ const pageToolOrigins = new Map();
2658
+ let pageToolWatchStarted = false;
2659
+
2660
+ function appPageToolHelper() {
2661
+ if (appFrame) return null;
2662
+ const helper = window.__agentNativeWebMcp;
2663
+ return helper && typeof helper.tools === "function" && typeof helper.call === "function"
2664
+ ? helper
2665
+ : null;
2666
+ }
2667
+
2668
+ function currentViewUrl() {
2669
+ if (appFrame) {
2670
+ try {
2671
+ return new URL(appFrame.src, window.location.href);
2672
+ // coercion-ok: null means "no readable view URL"; read_view and navigate_view report it as an error
2673
+ } catch {
2674
+ return null;
2675
+ }
2676
+ }
2677
+ return lastFrameSrc ? new URL(window.location.href) : null;
2678
+ }
2679
+
2680
+ function readViewState() {
2681
+ // A nested app frame is cross-origin and moves on its own, so its src is
2682
+ // only the launch URL; reporting it would describe the wrong page.
2683
+ if (appFrame) throw new Error("This host shows the app in a nested frame, so its current page can't be read.");
2684
+ const url = currentViewUrl();
2685
+ if (!url) throw new Error("No app page has loaded in this view yet.");
2686
+ const state = {
2687
+ path: localPathFromUrl(url, false),
2688
+ title: document.title
2689
+ };
2690
+ {
2691
+ const selection = typeof window.getSelection === "function"
2692
+ ? String(window.getSelection() || "").trim()
2693
+ : "";
2694
+ if (selection) state.selectedText = selection.slice(0, maxViewTextChars);
2695
+ const root = document.querySelector("main") || document.body;
2696
+ state.visibleText = root
2697
+ ? String(root.innerText || "").replace(/\\s+/g, " ").trim().slice(0, maxViewTextChars)
2698
+ : "";
2699
+ }
2700
+ return state;
2701
+ }
2702
+
2703
+ function navigateView(rawPath) {
2704
+ const path = typeof rawPath === "string" ? rawPath.trim() : "";
2705
+ if (!path.startsWith("/") || path.startsWith("//")) {
2706
+ throw new Error("path must start with / and stay inside the open app, e.g. the path read_view returns.");
2707
+ }
2708
+ // The nested frame was launched on a session scoped to its start URL;
2709
+ // pointing it at a bare app route would drop that session.
2710
+ if (appFrame) {
2711
+ throw new Error("This host shows the app in a nested frame, so it can't be moved from here.");
2712
+ }
2713
+ const base = currentViewUrl();
2714
+ if (!base) throw new Error("No app page has loaded in this view yet.");
2715
+ const target = new URL(path, base);
2716
+ if (target.origin !== base.origin) throw new Error("path must stay inside the open app.");
2717
+ const nextPath = localPathFromUrl(target, false);
2718
+ // React Router reads the location on popstate, so a pushed entry plus a
2719
+ // popstate event moves the transplanted app without reloading it.
2720
+ const current = window.history.state;
2721
+ const idx = current && typeof current.idx === "number" ? current.idx + 1 : 0;
2722
+ window.history.pushState(
2723
+ { usr: null, key: Math.random().toString(36).slice(2, 10), idx },
2724
+ "",
2725
+ nextPath
2726
+ );
2727
+ window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
2728
+ return { path: nextPath };
2729
+ }
2730
+
2731
+ async function listViewTools() {
2732
+ // A nested cross-origin frame can be neither read nor moved from here.
2733
+ if (appFrame) return [];
2734
+ const tools = [
2735
+ {
2736
+ name: readViewToolName,
2737
+ title: "Read open app view",
2738
+ description: "Read the app page open beside the chat: its path, title, selected text and visible text. Call it before acting on what the user is looking at.",
2739
+ inputSchema: { type: "object", properties: {} },
2740
+ annotations: { readOnlyHint: true }
2741
+ },
2742
+ {
2743
+ name: navigateViewToolName,
2744
+ title: "Navigate open app view",
2745
+ description: "Show another page of the open app in place, such as a filtered list or one record. Pass a path in the form read_view returns.",
2746
+ inputSchema: {
2747
+ type: "object",
2748
+ properties: {
2749
+ path: {
2750
+ type: "string",
2751
+ description: "App path starting with /, in the form read_view returns, e.g. a list with a search query."
2752
+ }
2753
+ },
2754
+ required: ["path"]
2755
+ }
2756
+ }
2757
+ ];
2758
+ const helper = appPageToolHelper();
2759
+ if (!helper) return tools;
2760
+ pageToolOrigins.clear();
2761
+ try {
2762
+ const summaries = await helper.tools();
2763
+ for (const summary of (Array.isArray(summaries) ? summaries : []).slice(0, maxPageTools)) {
2764
+ if (!summary || typeof summary.name !== "string") continue;
2765
+ if (tools.some((tool) => tool.name === summary.name)) continue;
2766
+ const full = typeof helper.describe === "function"
2767
+ ? await helper.describe(summary.name, summary.origin)
2768
+ : null;
2769
+ const schema = full && full.inputSchema && full.inputSchema.type === "object"
2770
+ ? full.inputSchema
2771
+ : { type: "object", properties: {} };
2772
+ tools.push({
2773
+ name: summary.name,
2774
+ ...(summary.title ? { title: summary.title } : {}),
2775
+ description: String((full && full.description) || summary.description || summary.name),
2776
+ inputSchema: schema,
2777
+ ...(summary.readOnly ? { annotations: { readOnlyHint: true } } : {})
2778
+ });
2779
+ pageToolOrigins.set(summary.name, summary.origin);
2780
+ }
2781
+ } catch (err) {
2782
+ console.warn("[agent-native] page tools unavailable to the MCP host", err);
2783
+ }
2784
+ return tools;
2785
+ }
2786
+
2787
+ async function callViewTool(params) {
2788
+ const record = objectValue(params);
2789
+ const name = typeof record.name === "string" ? record.name : "";
2790
+ const args = objectValue(record.arguments);
2791
+ try {
2792
+ let result;
2793
+ if (name === readViewToolName) {
2794
+ result = readViewState();
2795
+ } else if (name === navigateViewToolName) {
2796
+ result = navigateView(args.path);
2797
+ } else {
2798
+ const helper = appPageToolHelper();
2799
+ if (!helper) throw new Error("Unknown view tool: " + name);
2800
+ const origin = pageToolOrigins.get(name);
2801
+ const outcome = await helper.call(name, args, {
2802
+ waitMs: pageToolWaitMs,
2803
+ ...(origin ? { origin } : {})
2804
+ });
2805
+ if (!outcome || outcome.state !== "done") {
2806
+ throw new Error("Page tool " + name + " did not finish.");
2807
+ }
2808
+ if (!outcome.ok) throw new Error(outcome.error || "Page tool " + name + " failed.");
2809
+ result = outcome.result;
2810
+ }
2811
+ const structured = result && typeof result === "object" && !Array.isArray(result)
2812
+ ? result
2813
+ : undefined;
2814
+ return {
2815
+ content: [{
2816
+ type: "text",
2817
+ text: typeof result === "string" ? result : JSON.stringify(result === undefined ? null : result)
2818
+ }],
2819
+ ...(structured ? { structuredContent: structured } : {})
2820
+ };
2821
+ } catch (err) {
2822
+ return {
2823
+ isError: true,
2824
+ content: [{ type: "text", text: err && err.message ? err.message : String(err) }]
2825
+ };
2826
+ }
2827
+ }
2828
+
2829
+ function notifyViewToolsChanged() {
2830
+ if (app && typeof app.sendToolListChanged === "function") {
2831
+ try {
2832
+ app.sendToolListChanged();
2833
+ } catch (error) {
2834
+ console.warn("[agent-native] tools/list_changed notification failed", error);
2835
+ }
2836
+ }
2837
+ }
2838
+
2839
+ // The app registers its WebMCP tools from module scripts that keep running
2840
+ // after the transplant mounts, so the first list_changed can arrive before
2841
+ // they exist. Re-announce when the helper appears and on every toolchange.
2842
+ function watchPageTools() {
2843
+ notifyViewToolsChanged();
2844
+ if (pageToolWatchStarted) return;
2845
+ pageToolWatchStarted = true;
2846
+ let timer = null;
2847
+ const schedule = () => {
2848
+ clearTimeout(timer);
2849
+ timer = setTimeout(notifyViewToolsChanged, 250);
2850
+ };
2851
+ let watchedContext = null;
2852
+ let helperSeen = false;
2853
+ let ticks = 0;
2854
+ const check = () => {
2855
+ const context = document.modelContext;
2856
+ if (context && context !== watchedContext && typeof context.addEventListener === "function") {
2857
+ context.addEventListener("toolchange", schedule);
2858
+ watchedContext = context;
2859
+ }
2860
+ if (!helperSeen && appPageToolHelper()) {
2861
+ helperSeen = true;
2862
+ schedule();
2863
+ }
2864
+ if ((helperSeen && watchedContext) || ++ticks >= 20) clearInterval(poll);
2865
+ };
2866
+ const poll = setInterval(check, 500);
2867
+ check();
2868
+ }
2869
+
2645
2870
  function createNativeMcpAppsBridge() {
2646
2871
  let rpcId = 0;
2647
2872
  let connectPromise = null;
@@ -2745,7 +2970,10 @@ export function embedApp(options = {}) {
2745
2970
  "ui/initialize",
2746
2971
  {
2747
2972
  appInfo: { name: "Agent-Native Embed", version: "1.0.0" },
2748
- appCapabilities: {},
2973
+ appCapabilities: {
2974
+ availableDisplayModes: ["inline", "fullscreen"],
2975
+ tools: { listChanged: true }
2976
+ },
2749
2977
  protocolVersion: "2026-01-26"
2750
2978
  },
2751
2979
  nativeBridgeInitializeTimeoutMs
@@ -2791,17 +3019,43 @@ export function embedApp(options = {}) {
2791
3019
  sendSizeChanged(params) {
2792
3020
  rpcNotify("ui/notifications/size-changed", objectValue(params));
2793
3021
  },
3022
+ sendToolListChanged() {
3023
+ if (connectPromise) rpcNotify("notifications/tools/list_changed", {});
3024
+ },
2794
3025
  async sendMessage(params) {
2795
3026
  await nativeApp.connect();
2796
3027
  return await rpcRequest("ui/message", objectValue(params));
2797
3028
  }
2798
3029
  };
2799
3030
 
3031
+ async function answerHostRequest(message) {
3032
+ let reply;
3033
+ try {
3034
+ if (message.method === "tools/list") {
3035
+ reply = { result: { tools: await listViewTools() } };
3036
+ } else if (message.method === "tools/call") {
3037
+ reply = { result: await callViewTool(message.params) };
3038
+ } else if (message.method === "ping" || message.method === "ui/resource-teardown") {
3039
+ reply = { result: {} };
3040
+ } else {
3041
+ reply = { error: { code: -32601, message: "Method not found: " + message.method } };
3042
+ }
3043
+ } catch (err) {
3044
+ reply = { error: { code: -32603, message: err && err.message ? err.message : String(err) } };
3045
+ }
3046
+ window.parent.postMessage({ jsonrpc: "2.0", id: message.id, ...reply }, "*");
3047
+ }
3048
+
2800
3049
  function onMessage(event) {
2801
3050
  if (event.source !== window.parent) return;
2802
3051
  const message = event.data;
2803
3052
  if (!message || message.jsonrpc !== "2.0") return;
2804
- if (typeof message.id === "number" || typeof message.id === "string") {
3053
+ const hasId = typeof message.id === "number" || typeof message.id === "string";
3054
+ if (hasId && typeof message.method === "string") {
3055
+ void answerHostRequest(message);
3056
+ return;
3057
+ }
3058
+ if (hasId) {
2805
3059
  settleRpcResponse(message);
2806
3060
  return;
2807
3061
  }
@@ -2881,9 +3135,11 @@ export function embedApp(options = {}) {
2881
3135
  const { App } = await import("${MCP_APP_IMPORT}");
2882
3136
  app = new App(
2883
3137
  { name: "Agent-Native Embed", version: "1.0.0" },
2884
- {},
3138
+ { availableDisplayModes: ["inline", "fullscreen"], tools: { listChanged: true } },
2885
3139
  { autoResize: false }
2886
3140
  );
3141
+ app.onlisttools = async () => ({ tools: await listViewTools() });
3142
+ app.oncalltool = async (params) => await callViewTool(params);
2887
3143
  appConnectPromise = null;
2888
3144
  app.ontoolinput = (params) => {
2889
3145
  toolInput = params.arguments || {};
@@ -310,6 +310,13 @@ function protectedResourcePathFromRequest(event) {
310
310
  pathname = pathname.replace(/\/+$/, "");
311
311
  if (!pathname || pathname === "/")
312
312
  return undefined;
313
+ // RFC 9728 inserts the whole resource path after the well-known segment, so
314
+ // a mounted app (resource /dispatch/mcp) is asked about /dispatch/mcp.
315
+ const issuer = getMcpOAuthIssuer(event);
316
+ const basePath = issuer ? new URL(issuer).pathname.replace(/\/+$/, "") : "";
317
+ if (basePath && pathname.startsWith(`${basePath}/`)) {
318
+ pathname = pathname.slice(basePath.length);
319
+ }
313
320
  return ([
314
321
  MCP_PUBLIC_ROUTE_PREFIX,
315
322
  MCP_LEGACY_ROUTE_PREFIX,
@@ -283,20 +283,20 @@ export declare function screenMemoryMcpToolDefinitions(): ({
283
283
  type: string;
284
284
  };
285
285
  startAt: {
286
- description?: undefined;
287
286
  type: string;
287
+ description?: undefined;
288
288
  };
289
289
  endAt: {
290
- description?: undefined;
291
290
  type: string;
291
+ description?: undefined;
292
292
  };
293
293
  count: {
294
294
  type: string;
295
295
  description: string;
296
296
  };
297
297
  reason: {
298
- description?: undefined;
299
298
  type: string;
299
+ description?: undefined;
300
300
  };
301
301
  includeMicrophone?: undefined;
302
302
  includeSystemAudio?: undefined;
@@ -1,18 +1,18 @@
1
1
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
2
2
  error?: undefined;
3
- ok?: undefined;
4
3
  count: number;
5
4
  updated?: undefined;
5
+ ok?: undefined;
6
6
  } | {
7
7
  error?: undefined;
8
- ok?: undefined;
9
8
  count?: undefined;
10
9
  updated: number;
11
- } | {
12
10
  ok?: undefined;
11
+ } | {
13
12
  count?: undefined;
14
13
  updated?: undefined;
15
14
  error: string;
15
+ ok?: undefined;
16
16
  } | {
17
17
  error?: undefined;
18
18
  count?: undefined;
@@ -27,10 +27,10 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
27
27
  thumbsUpRate: number;
28
28
  avgEvalScore: number;
29
29
  } | {
30
- summary?: undefined;
31
30
  ok?: undefined;
32
31
  error: string;
33
32
  message: string;
33
+ summary?: undefined;
34
34
  spans?: undefined;
35
35
  } | {
36
36
  error?: undefined;
@@ -40,30 +40,30 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
40
40
  spans: import("./types.js").TraceSpan[];
41
41
  } | {
42
42
  error?: undefined;
43
- summary?: undefined;
44
43
  ok?: undefined;
45
44
  message?: undefined;
45
+ summary?: undefined;
46
46
  spans?: undefined;
47
47
  traceMissing: true;
48
48
  id: string;
49
49
  } | {
50
50
  error?: undefined;
51
- summary?: undefined;
52
51
  ok?: undefined;
53
52
  message?: undefined;
53
+ summary?: undefined;
54
54
  spans?: undefined;
55
55
  traceMissing?: undefined;
56
56
  id: string;
57
57
  } | {
58
- summary?: undefined;
59
58
  ok?: undefined;
60
59
  message?: undefined;
60
+ summary?: undefined;
61
61
  spans?: undefined;
62
62
  error: any;
63
63
  } | {
64
64
  error?: undefined;
65
- summary?: undefined;
66
65
  message?: undefined;
66
+ summary?: undefined;
67
67
  spans?: undefined;
68
68
  ok: boolean;
69
69
  }>>;
@@ -180,9 +180,9 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
180
180
  soloDeploymentAdmin: boolean | undefined;
181
181
  }>>;
182
182
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
183
- success: boolean;
184
183
  id?: undefined;
185
184
  name?: undefined;
185
+ success: boolean;
186
186
  role?: undefined;
187
187
  } | {
188
188
  success?: undefined;
@@ -10,8 +10,9 @@ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
10
10
  import { canonicalSecretKey, secretKeyNames } from "./key-aliases.js";
11
11
  import { resolveSecretManagedBy } from "./managed-keys.js";
12
12
  import { getRequiredSecret, listRequiredSecrets, } from "./register.js";
13
- import { deleteAppSecret, listAppSecretsForScope, VAULT_SYNC_DESCRIPTION_PREFIX, } from "./storage.js";
13
+ import { deleteAppSecret, listAppSecretsForScope, } from "./storage.js";
14
14
  import { describeSecretUsage } from "./usage.js";
15
+ import { isVaultManagedSecret } from "./vault-managed.js";
15
16
  /**
16
17
  * The rows the resolver reads for the caller, in its precedence order:
17
18
  * personal, the pre-organization solo row, then the organization's.
@@ -63,7 +64,7 @@ function toEntry(caller, meta, owner, manages) {
63
64
  const registered = getRequiredSecret(meta.key);
64
65
  const managedBy = resolveSecretManagedBy(meta.key);
65
66
  const provider = providerForName(meta.key);
66
- const vault = isVaultSynced(meta.scope, meta.description);
67
+ const vault = isVaultSynced(meta.scope, meta);
67
68
  const mayChange = !managedBy && !vault && (owner === "user" || manages);
68
69
  // Replace writes through the routes' own target, so it only applies when
69
70
  // that target is this row; otherwise it would add a second row instead.
@@ -155,9 +156,9 @@ export async function listApiKeys(caller) {
155
156
  canManageOrg: manages,
156
157
  };
157
158
  }
158
- /** Vault writes shared rows with this description; a user's own rows are never synced. */
159
- function isVaultSynced(scope, description) {
160
- return (scope !== "user" && !!description?.startsWith(VAULT_SYNC_DESCRIPTION_PREFIX));
159
+ /** The Vault owns the shared rows it writes; a user's own rows never belong to it. */
160
+ function isVaultSynced(scope, meta) {
161
+ return scope !== "user" && isVaultManagedSecret(meta);
161
162
  }
162
163
  /**
163
164
  * Delete one saved key row. A provider key also takes its endpoint and older
@@ -181,7 +182,7 @@ export async function deleteApiKey(caller, input) {
181
182
  const row = rows.find((meta) => meta.key === input.name);
182
183
  if (!row)
183
184
  return { status: "not-found" };
184
- if (isVaultSynced(storedScope, row.description))
185
+ if (isVaultSynced(storedScope, row))
185
186
  return { status: "vault" };
186
187
  const provider = AGENT_PROVIDER_CATALOG.find((option) => option.id === providerForName(input.name));
187
188
  const names = provider
@@ -198,7 +199,7 @@ export async function deleteApiKey(caller, input) {
198
199
  continue;
199
200
  // Vault owns its rows, and a managed name is never the user's to delete,
200
201
  // even when it sits beside the key being removed.
201
- if (isVaultSynced(storedScope, meta.description))
202
+ if (isVaultSynced(storedScope, meta))
202
203
  continue;
203
204
  if (resolveSecretManagedBy(name))
204
205
  continue;
@@ -1,5 +1,6 @@
1
1
  export { registerRequiredSecret, listRequiredSecrets, getRequiredSecret, registerSecretUsage, getRegisteredSecretUsage, __resetSecretsRegistry, type RegisteredSecret, type SecretUsage, type SecretManagedBy, type SecretScope, type SecretKind, type SecretValidator, type ValidatorResult, } from "./register.js";
2
- export { writeAppSecret, readAppSecret, hasAppSecret, readAppSecretMeta, deleteAppSecret, getAppSecretMeta, listAppSecretsForScope, last4, VAULT_SYNC_DESCRIPTION_PREFIX, type SecretRef, type WriteSecretArgs, type ReadSecretResult, type SecretMeta, } from "./storage.js";
2
+ export { writeAppSecret, readAppSecret, hasAppSecret, readAppSecretMeta, readAppSecretMetaById, deleteAppSecret, deleteAppSecretById, getAppSecretMeta, listAppSecretsForScope, listAppSecretSummaries, updateAppSecretById, AppSecretKeyConflictError, last4, type SecretRef, type WriteSecretArgs, type ReadSecretResult, type SecretMeta, type AppSecretSummary, type UpdateAppSecretPatch, } from "./storage.js";
3
+ export { VAULT_SYNC_DESCRIPTION_PREFIX, isVaultManagedSecret, } from "./vault-managed.js";
3
4
  export { APP_SECRETS_CREATE_SQL, appSecrets } from "./schema.js";
4
5
  export { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "./crypto.js";
5
6
  export { createListSecretsHandler, createWriteSecretHandler, createTestSecretHandler, createAdHocSecretHandler, type SecretStatusPayload, type AdHocSecretPayload, } from "./routes.js";
@@ -1,5 +1,6 @@
1
1
  export { registerRequiredSecret, listRequiredSecrets, getRequiredSecret, registerSecretUsage, getRegisteredSecretUsage, __resetSecretsRegistry, } from "./register.js";
2
- export { writeAppSecret, readAppSecret, hasAppSecret, readAppSecretMeta, deleteAppSecret, getAppSecretMeta, listAppSecretsForScope, last4, VAULT_SYNC_DESCRIPTION_PREFIX, } from "./storage.js";
2
+ export { writeAppSecret, readAppSecret, hasAppSecret, readAppSecretMeta, readAppSecretMetaById, deleteAppSecret, deleteAppSecretById, getAppSecretMeta, listAppSecretsForScope, listAppSecretSummaries, updateAppSecretById, AppSecretKeyConflictError, last4, } from "./storage.js";
3
+ export { VAULT_SYNC_DESCRIPTION_PREFIX, isVaultManagedSecret, } from "./vault-managed.js";
3
4
  export { APP_SECRETS_CREATE_SQL, appSecrets } from "./schema.js";
4
5
  export { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "./crypto.js";
5
6
  export { createListSecretsHandler, createWriteSecretHandler, createTestSecretHandler, createAdHocSecretHandler, } from "./routes.js";
@@ -55,14 +55,14 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
55
55
  errorCode: "secret_managed_elsewhere";
56
56
  managedBy: SecretManagedBy;
57
57
  } | {
58
- errorCode?: undefined;
59
- error: string;
60
58
  ok?: undefined;
59
+ error: string;
60
+ errorCode?: undefined;
61
61
  status?: undefined;
62
62
  } | {
63
+ ok?: undefined;
63
64
  error: string;
64
65
  errorCode: string;
65
- ok?: undefined;
66
66
  status?: undefined;
67
67
  } | {
68
68
  error?: undefined;
@@ -51,17 +51,16 @@ import { PERSONAL_PROVIDER_KEYS_RESTRICTED_ERROR_CODE, resolvePersonalProviderKe
51
51
  import { canonicalSecretKey, secretKeyNames } from "./key-aliases.js";
52
52
  import { isManagedDeleteAllowed, managedDeleteRefusal, resolveSecretManagedBy, } from "./managed-keys.js";
53
53
  import { listRequiredSecrets, getRequiredSecret, } from "./register.js";
54
- import { writeAppSecret, updateAppSecretUrlAllowlist, deleteAppSecret, last4, listAppSecretsForScope, readAppSecretMeta, VAULT_SYNC_DESCRIPTION_PREFIX, } from "./storage.js";
54
+ import { writeAppSecret, updateAppSecretUrlAllowlist, deleteAppSecret, last4, listAppSecretsForScope, readAppSecretMeta, } from "./storage.js";
55
55
  import { registeredUrlAllowlist, validateUrlAllowlist, } from "./substitution.js";
56
56
  import { describeSecretUsage } from "./usage.js";
57
+ import { isVaultManagedSecret } from "./vault-managed.js";
57
58
  export const SECRET_REJECTED_ERROR_CODE = "secret_rejected";
58
59
  export const SECRET_VALIDATION_UNAVAILABLE_ERROR_CODE = "secret_validation_unavailable";
59
- function secretSource(scope, description) {
60
+ function secretSource(scope, meta) {
60
61
  if (scope === "user")
61
62
  return "personal";
62
- return description?.startsWith(VAULT_SYNC_DESCRIPTION_PREFIX)
63
- ? "vault"
64
- : "workspace";
63
+ return isVaultManagedSecret(meta) ? "vault" : "workspace";
65
64
  }
66
65
  const NOT_RESOLVED = {
67
66
  value: null,
@@ -216,7 +215,7 @@ export function createListSecretsHandler() {
216
215
  const meta = await readAppSecretMeta(hit);
217
216
  base.last4 = meta?.last4 || last4(effective.value);
218
217
  base.updatedAt = meta?.updatedAt;
219
- base.source = secretSource(hit.scope, meta?.description);
218
+ base.source = secretSource(hit.scope, meta);
220
219
  base.managedHere = hit.scope === secret.scope && hit.scopeId === scopeId;
221
220
  if (base.managedHere && secret.scope === "user") {
222
221
  const shared = await asRequestUser(event, () => resolveSecretWithAliasesDetailed(secret.key, {
@@ -233,7 +232,7 @@ export function createListSecretsHandler() {
233
232
  scopeId: shared.scopeId,
234
233
  })
235
234
  : null;
236
- const sharedSource = secretSource(shared.source, sharedMeta?.description);
235
+ const sharedSource = secretSource(shared.source, sharedMeta);
237
236
  base.overrides = sharedSource === "vault" ? "vault" : "workspace";
238
237
  }
239
238
  }
@@ -513,7 +512,7 @@ function metaToPayload(meta) {
513
512
  name: meta.key,
514
513
  scope: meta.scope,
515
514
  scopeId: meta.scopeId,
516
- source: secretSource(meta.scope, meta.description),
515
+ source: secretSource(meta.scope, meta),
517
516
  description: meta.description,
518
517
  last4: meta.last4,
519
518
  urlAllowlist: meta.urlAllowlist,
@@ -138,6 +138,57 @@ export declare const appSecrets: import("drizzle-orm/pg-core").PgTableWithColumn
138
138
  identity: undefined;
139
139
  generated: undefined;
140
140
  }, {}, {}>;
141
+ label: import("drizzle-orm/pg-core").PgColumn<{
142
+ name: "label";
143
+ tableName: "app_secrets";
144
+ dataType: "string";
145
+ columnType: "PgText";
146
+ data: string;
147
+ driverParam: string;
148
+ notNull: false;
149
+ hasDefault: false;
150
+ isPrimaryKey: false;
151
+ isAutoincrement: false;
152
+ hasRuntimeDefault: false;
153
+ enumValues: [string, ...string[]];
154
+ baseColumn: never;
155
+ identity: undefined;
156
+ generated: undefined;
157
+ }, {}, {}>;
158
+ provider: import("drizzle-orm/pg-core").PgColumn<{
159
+ name: "provider";
160
+ tableName: "app_secrets";
161
+ dataType: "string";
162
+ columnType: "PgText";
163
+ data: string;
164
+ driverParam: string;
165
+ notNull: false;
166
+ hasDefault: false;
167
+ isPrimaryKey: false;
168
+ isAutoincrement: false;
169
+ hasRuntimeDefault: false;
170
+ enumValues: [string, ...string[]];
171
+ baseColumn: never;
172
+ identity: undefined;
173
+ generated: undefined;
174
+ }, {}, {}>;
175
+ createdBy: import("drizzle-orm/pg-core").PgColumn<{
176
+ name: "created_by";
177
+ tableName: "app_secrets";
178
+ dataType: "string";
179
+ columnType: "PgText";
180
+ data: string;
181
+ driverParam: string;
182
+ notNull: false;
183
+ hasDefault: false;
184
+ isPrimaryKey: false;
185
+ isAutoincrement: false;
186
+ hasRuntimeDefault: false;
187
+ enumValues: [string, ...string[]];
188
+ baseColumn: never;
189
+ identity: undefined;
190
+ generated: undefined;
191
+ }, {}, {}>;
141
192
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
142
193
  name: "created_at";
143
194
  tableName: "app_secrets";
@@ -8,6 +8,9 @@ export const appSecrets = table("app_secrets", {
8
8
  sharedEncryptedValue: text("shared_encrypted_value"),
9
9
  description: text("description"),
10
10
  urlAllowlist: text("url_allowlist"),
11
+ label: text("label"),
12
+ provider: text("provider"),
13
+ createdBy: text("created_by"),
11
14
  createdAt: bigint("created_at", { mode: "number" }).notNull(),
12
15
  updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
13
16
  });