@agent-native/core 0.39.2 → 0.40.1

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 (154) hide show
  1. package/README.md +1 -1
  2. package/dist/action.js +12 -0
  3. package/dist/action.js.map +1 -1
  4. package/dist/cli/create.d.ts.map +1 -1
  5. package/dist/cli/create.js +5 -1
  6. package/dist/cli/create.js.map +1 -1
  7. package/dist/cli/skills.d.ts +4 -3
  8. package/dist/cli/skills.d.ts.map +1 -1
  9. package/dist/cli/skills.js +756 -694
  10. package/dist/cli/skills.js.map +1 -1
  11. package/dist/client/blocks/AiEditableField.d.ts +8 -0
  12. package/dist/client/blocks/AiEditableField.d.ts.map +1 -0
  13. package/dist/client/blocks/AiEditableField.js +10 -0
  14. package/dist/client/blocks/AiEditableField.js.map +1 -0
  15. package/dist/client/blocks/BlockView.d.ts +3 -3
  16. package/dist/client/blocks/BlockView.d.ts.map +1 -1
  17. package/dist/client/blocks/BlockView.js +15 -3
  18. package/dist/client/blocks/BlockView.js.map +1 -1
  19. package/dist/client/blocks/SchemaBlockEditor.js +2 -2
  20. package/dist/client/blocks/SchemaBlockEditor.js.map +1 -1
  21. package/dist/client/blocks/index.d.ts +5 -2
  22. package/dist/client/blocks/index.d.ts.map +1 -1
  23. package/dist/client/blocks/index.js +6 -3
  24. package/dist/client/blocks/index.js.map +1 -1
  25. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.js +20 -6
  27. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  28. package/dist/client/blocks/library/DiffBlock.d.ts +29 -0
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +190 -30
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  33. package/dist/client/blocks/library/FileTreeBlock.js +46 -7
  34. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  35. package/dist/client/blocks/library/HighlightedCode.d.ts +10 -0
  36. package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -0
  37. package/dist/client/blocks/library/HighlightedCode.js +92 -0
  38. package/dist/client/blocks/library/HighlightedCode.js.map +1 -0
  39. package/dist/client/blocks/library/JsonExplorerBlock.d.ts +9 -4
  40. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  41. package/dist/client/blocks/library/JsonExplorerBlock.js +66 -30
  42. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  43. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  44. package/dist/client/blocks/library/MermaidBlock.js +73 -44
  45. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  46. package/dist/client/blocks/library/OpenApiSpecBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/OpenApiSpecBlock.js +3 -2
  48. package/dist/client/blocks/library/OpenApiSpecBlock.js.map +1 -1
  49. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  50. package/dist/client/blocks/library/checklist.js +1 -0
  51. package/dist/client/blocks/library/checklist.js.map +1 -1
  52. package/dist/client/blocks/library/code-tabs.d.ts.map +1 -1
  53. package/dist/client/blocks/library/code-tabs.js +183 -102
  54. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  55. package/dist/client/blocks/library/columns.config.d.ts +60 -0
  56. package/dist/client/blocks/library/columns.config.d.ts.map +1 -0
  57. package/dist/client/blocks/library/columns.config.js +37 -0
  58. package/dist/client/blocks/library/columns.config.js.map +1 -0
  59. package/dist/client/blocks/library/columns.d.ts +25 -0
  60. package/dist/client/blocks/library/columns.d.ts.map +1 -0
  61. package/dist/client/blocks/library/columns.js +199 -0
  62. package/dist/client/blocks/library/columns.js.map +1 -0
  63. package/dist/client/blocks/library/dev-doc-ui.d.ts +2 -1
  64. package/dist/client/blocks/library/dev-doc-ui.d.ts.map +1 -1
  65. package/dist/client/blocks/library/dev-doc-ui.js +2 -1
  66. package/dist/client/blocks/library/dev-doc-ui.js.map +1 -1
  67. package/dist/client/blocks/library/html.d.ts +1 -1
  68. package/dist/client/blocks/library/html.d.ts.map +1 -1
  69. package/dist/client/blocks/library/html.js +34 -4
  70. package/dist/client/blocks/library/html.js.map +1 -1
  71. package/dist/client/blocks/library/json-explorer.config.d.ts +3 -1
  72. package/dist/client/blocks/library/json-explorer.config.d.ts.map +1 -1
  73. package/dist/client/blocks/library/json-explorer.config.js +30 -1
  74. package/dist/client/blocks/library/json-explorer.config.js.map +1 -1
  75. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  76. package/dist/client/blocks/library/server-specs.js +13 -3
  77. package/dist/client/blocks/library/server-specs.js.map +1 -1
  78. package/dist/client/blocks/library/specs.d.ts +4 -4
  79. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  80. package/dist/client/blocks/library/specs.js +21 -16
  81. package/dist/client/blocks/library/specs.js.map +1 -1
  82. package/dist/client/blocks/library/table.config.d.ts +3 -0
  83. package/dist/client/blocks/library/table.config.d.ts.map +1 -1
  84. package/dist/client/blocks/library/table.config.js +13 -1
  85. package/dist/client/blocks/library/table.config.js.map +1 -1
  86. package/dist/client/blocks/library/table.d.ts.map +1 -1
  87. package/dist/client/blocks/library/table.js +90 -9
  88. package/dist/client/blocks/library/table.js.map +1 -1
  89. package/dist/client/blocks/library/tabs.config.d.ts +16 -8
  90. package/dist/client/blocks/library/tabs.config.d.ts.map +1 -1
  91. package/dist/client/blocks/library/tabs.config.js +10 -4
  92. package/dist/client/blocks/library/tabs.config.js.map +1 -1
  93. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  94. package/dist/client/blocks/library/tabs.js +146 -21
  95. package/dist/client/blocks/library/tabs.js.map +1 -1
  96. package/dist/client/blocks/server.d.ts +2 -1
  97. package/dist/client/blocks/server.d.ts.map +1 -1
  98. package/dist/client/blocks/server.js +1 -0
  99. package/dist/client/blocks/server.js.map +1 -1
  100. package/dist/client/blocks/types.d.ts +99 -9
  101. package/dist/client/blocks/types.d.ts.map +1 -1
  102. package/dist/client/blocks/types.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +2 -2
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts.map +1 -1
  108. package/dist/client/rich-markdown-editor/BubbleToolbar.js +13 -3
  109. package/dist/client/rich-markdown-editor/BubbleToolbar.js.map +1 -1
  110. package/dist/client/rich-markdown-editor/DragHandle.d.ts +49 -4
  111. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  112. package/dist/client/rich-markdown-editor/DragHandle.js +656 -88
  113. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  114. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +10 -1
  115. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  116. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +180 -15
  117. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  118. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +2 -1
  119. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  120. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  121. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  122. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts +5 -0
  123. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts.map +1 -1
  124. package/dist/client/rich-markdown-editor/SlashCommandMenu.js +33 -5
  125. package/dist/client/rich-markdown-editor/SlashCommandMenu.js.map +1 -1
  126. package/dist/client/rich-markdown-editor/index.d.ts +3 -3
  127. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  128. package/dist/client/rich-markdown-editor/index.js +2 -2
  129. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  130. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts +14 -0
  131. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts.map +1 -1
  132. package/dist/client/rich-markdown-editor/registrySlashCommands.js +38 -0
  133. package/dist/client/rich-markdown-editor/registrySlashCommands.js.map +1 -1
  134. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +1 -0
  135. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  136. package/dist/client/rich-markdown-editor/useCollabReconcile.js +4 -0
  137. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  138. package/dist/db/client.d.ts.map +1 -1
  139. package/dist/db/client.js +17 -1
  140. package/dist/db/client.js.map +1 -1
  141. package/dist/deploy/build.js +68 -0
  142. package/dist/deploy/build.js.map +1 -1
  143. package/dist/sharing/access.d.ts +4 -2
  144. package/dist/sharing/access.d.ts.map +1 -1
  145. package/dist/sharing/access.js +8 -3
  146. package/dist/sharing/access.js.map +1 -1
  147. package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
  148. package/dist/sharing/actions/set-resource-visibility.js +2 -3
  149. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  150. package/dist/sharing/registry.d.ts +13 -0
  151. package/dist/sharing/registry.d.ts.map +1 -1
  152. package/dist/sharing/registry.js.map +1 -1
  153. package/dist/styles/rich-markdown-editor.css +15 -0
  154. package/package.json +16 -1
@@ -13,6 +13,7 @@
13
13
  * callers who lack the required role.
14
14
  */
15
15
  import { type SQL } from "drizzle-orm";
16
+ import { type ShareableResourceRegistration } from "./registry.js";
16
17
  import { type ShareRole } from "./schema.js";
17
18
  export declare class ForbiddenError extends Error {
18
19
  statusCode: number;
@@ -24,6 +25,7 @@ export interface AccessContext {
24
25
  }
25
26
  /** Current request's access context. Pulls from request-context ALS. */
26
27
  export declare function currentAccess(): AccessContext;
28
+ export declare function resolveRegisteredAccessContext(reg: ShareableResourceRegistration | undefined, ctx: AccessContext): AccessContext;
27
29
  /**
28
30
  * Build a Drizzle `WHERE` clause that admits rows the current user can see.
29
31
  * Pass the ownable resource table and its shares table; optional min role
@@ -42,7 +44,7 @@ export declare function currentAccess(): AccessContext;
42
44
  * .from(schema.documents)
43
45
  * .where(accessFilter(schema.documents, schema.documentShares));
44
46
  */
45
- export declare function accessFilter(resourceTable: any, sharesTable: any, ctx?: AccessContext, minRole?: ShareRole, options?: {
47
+ export declare function accessFilter(resourceTable: any, sharesTable: any, rawCtx?: AccessContext, minRole?: ShareRole, options?: {
46
48
  includePublic?: boolean;
47
49
  }): SQL;
48
50
  export interface ResolvedAccess {
@@ -55,7 +57,7 @@ export interface ResolvedAccess {
55
57
  * Return the effective role the current user has on a specific resource, or
56
58
  * null if they have no access. Loads the resource and relevant share rows.
57
59
  */
58
- export declare function resolveAccess(resourceType: string, resourceId: string, ctx?: AccessContext): Promise<ResolvedAccess | null>;
60
+ export declare function resolveAccess(resourceType: string, resourceId: string, rawCtx?: AccessContext): Promise<ResolvedAccess | null>;
59
61
  /**
60
62
  * Throw ForbiddenError if the current user can't act on this resource with at
61
63
  * least the given role. Used at the top of update/delete actions.
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAUzD,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAmBxD,qBAAa,cAAe,SAAQ,KAAK;IACvC,UAAU,SAAO;gBACL,OAAO,SAAc;CAIlC;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,wBAAgB,aAAa,IAAI,aAAa,CAK7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,GAAG,EAClB,WAAW,EAAE,GAAG,EAChB,GAAG,GAAE,aAA+B,EACpC,OAAO,GAAE,SAAoB,EAC7B,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,GAAG,CAwDL;AAsDD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,EAAE,GAAG,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,aAA+B,GACnC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAkChC;AA8CD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,SAAS,GAAG,OAAkB,EACvC,GAAG,GAAE,aAA+B,GACnC,OAAO,CAAC,cAAc,CAAC,CAWzB"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAKzD,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAmBxD,qBAAa,cAAe,SAAQ,KAAK;IACvC,UAAU,SAAO;gBACL,OAAO,SAAc;CAIlC;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,wBAAgB,aAAa,IAAI,aAAa,CAK7C;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,6BAA6B,GAAG,SAAS,EAC9C,GAAG,EAAE,aAAa,GACjB,aAAa,CAEf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,GAAG,EAClB,WAAW,EAAE,GAAG,EAChB,MAAM,GAAE,aAA+B,EACvC,OAAO,GAAE,SAAoB,EAC7B,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,GAAG,CAyDL;AAsDD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,EAAE,GAAG,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,aAA+B,GACtC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAmChC;AA8CD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,SAAS,GAAG,OAAkB,EACvC,GAAG,GAAE,aAA+B,GACnC,OAAO,CAAC,cAAc,CAAC,CAWzB"}
@@ -45,6 +45,9 @@ export function currentAccess() {
45
45
  orgId: getRequestOrgId(),
46
46
  };
47
47
  }
48
+ export function resolveRegisteredAccessContext(reg, ctx) {
49
+ return reg?.resolveAccessContext ? reg.resolveAccessContext(ctx) : ctx;
50
+ }
48
51
  /**
49
52
  * Build a Drizzle `WHERE` clause that admits rows the current user can see.
50
53
  * Pass the ownable resource table and its shares table; optional min role
@@ -63,13 +66,14 @@ export function currentAccess() {
63
66
  * .from(schema.documents)
64
67
  * .where(accessFilter(schema.documents, schema.documentShares));
65
68
  */
66
- export function accessFilter(resourceTable, sharesTable, ctx = currentAccess(), minRole = "viewer", options = {}) {
67
- const { userEmail, orgId } = ctx;
69
+ export function accessFilter(resourceTable, sharesTable, rawCtx = currentAccess(), minRole = "viewer", options = {}) {
68
70
  // Defense in depth — resources registered with `allowPublic: false` must
69
71
  // never participate in cross-user "public" discovery, even if a caller
70
72
  // accidentally passes `includePublic: true` or if a stale public row sits
71
73
  // in the DB.
72
74
  const reg = findRegistrationByTable(resourceTable);
75
+ const ctx = resolveRegisteredAccessContext(reg, rawCtx);
76
+ const { userEmail, orgId } = ctx;
73
77
  const publicAllowed = reg?.allowPublic !== false;
74
78
  const includePublic = (options.includePublic ?? false) && publicAllowed;
75
79
  const clauses = [];
@@ -148,8 +152,9 @@ function explicitSharesAllowedForResource(reg, resource, ctx) {
148
152
  * Return the effective role the current user has on a specific resource, or
149
153
  * null if they have no access. Loads the resource and relevant share rows.
150
154
  */
151
- export async function resolveAccess(resourceType, resourceId, ctx = currentAccess()) {
155
+ export async function resolveAccess(resourceType, resourceId, rawCtx = currentAccess()) {
152
156
  const reg = requireShareableResource(resourceType);
157
+ const ctx = resolveRegisteredAccessContext(reg, rawCtx);
153
158
  const db = reg.getDb();
154
159
  const [resource] = await db
155
160
  .select()
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Access-control helpers for shareable resources.\n *\n * The access model combines:\n * 1. Direct ownership — `owner_email = currentUser`.\n * 2. Visibility — `'private' | 'org' | 'public'`. `org` grants read to anyone\n * in the same org; `public` grants read to any authenticated user.\n * 3. Share rows — per-user or per-org grants in the `{type}_shares` table\n * with a role (`viewer | editor | admin`).\n *\n * Use `applyAccessFilter()` on list/read queries to filter rows the current\n * user can see. Use `assertAccess()` at the top of write actions to reject\n * callers who lack the required role.\n */\n\nimport { and, eq, or, sql, type SQL } from \"drizzle-orm\";\nimport {\n getRequestUserEmail,\n getRequestOrgId,\n} from \"../server/request-context.js\";\nimport {\n listShareableResources,\n requireShareableResource,\n type ShareableResourceRegistration,\n} from \"./registry.js\";\nimport { ROLE_RANK, type ShareRole } from \"./schema.js\";\n\n/**\n * Find a registration by its drizzle table identity. Used to look up\n * per-resource policy flags (e.g. `allowPublic`) inside `accessFilter`,\n * which receives only the table — not the resource-type name.\n *\n * Identity is stable within a single bundle (Vite dedupes module instances);\n * the SSR/server side is the only caller, so per-bundle identity is fine.\n */\nfunction findRegistrationByTable(\n resourceTable: any,\n): ShareableResourceRegistration | undefined {\n for (const reg of listShareableResources()) {\n if (reg.resourceTable === resourceTable) return reg;\n }\n return undefined;\n}\n\nexport class ForbiddenError extends Error {\n statusCode = 403;\n constructor(message = \"Forbidden\") {\n super(message);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport interface AccessContext {\n userEmail?: string;\n orgId?: string;\n}\n\n/** Current request's access context. Pulls from request-context ALS. */\nexport function currentAccess(): AccessContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId(),\n };\n}\n\n/**\n * Build a Drizzle `WHERE` clause that admits rows the current user can see.\n * Pass the ownable resource table and its shares table; optional min role\n * (defaults to 'viewer') gates which share rows count.\n *\n * `visibility = 'public'` is intentionally NOT admitted by default. Public\n * means \"anyone with the link can view\" (still honoured by `resolveAccess`\n * for read-by-id), not \"appears in every signed-in user's list/sidebar.\"\n * Pass `{ includePublic: true }` for the rare list endpoint that wants\n * cross-user public discovery (a public template gallery, for example).\n *\n * Example:\n *\n * const rows = await db\n * .select()\n * .from(schema.documents)\n * .where(accessFilter(schema.documents, schema.documentShares));\n */\nexport function accessFilter(\n resourceTable: any,\n sharesTable: any,\n ctx: AccessContext = currentAccess(),\n minRole: ShareRole = \"viewer\",\n options: { includePublic?: boolean } = {},\n): SQL {\n const { userEmail, orgId } = ctx;\n // Defense in depth — resources registered with `allowPublic: false` must\n // never participate in cross-user \"public\" discovery, even if a caller\n // accidentally passes `includePublic: true` or if a stale public row sits\n // in the DB.\n const reg = findRegistrationByTable(resourceTable);\n const publicAllowed = reg?.allowPublic !== false;\n const includePublic = (options.includePublic ?? false) && publicAllowed;\n const clauses: SQL[] = [];\n\n if (userEmail) {\n clauses.push(\n and(\n eq(resourceTable.ownerEmail, userEmail),\n ownerScopeFilter(resourceTable, ctx),\n )!,\n );\n }\n if (minRole === \"viewer\") {\n if (includePublic) {\n clauses.push(eq(resourceTable.visibility, \"public\"));\n }\n if (orgId) {\n clauses.push(\n and(\n eq(resourceTable.visibility, \"org\"),\n eq(resourceTable.orgId, orgId),\n )!,\n );\n }\n }\n if (userEmail) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'user'\n and ${sharesTable.principalId} = ${userEmail}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n if (orgId) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'org'\n and ${sharesTable.principalId} = ${orgId}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n\n return or(...clauses) ?? sql`1=0`;\n}\n\nfunction ownerScopeFilter(resourceTable: any, ctx: AccessContext): SQL {\n if (ctx.orgId) {\n // Rows created before org-scoping, or in solo mode, have no org_id. Keep\n // them manageable by their owner after the owner joins or switches into an\n // organization, while still keeping rows from other orgs out of scope.\n return or(\n eq(resourceTable.orgId, ctx.orgId),\n sql`${resourceTable.orgId} IS NULL`,\n )!;\n }\n return sql`${resourceTable.orgId} IS NULL`;\n}\n\nfunction ownerMatchesActiveScope(resource: any, ctx: AccessContext): boolean {\n const resourceOrgId = resource?.orgId ?? null;\n if (!resourceOrgId) return true;\n return ctx.orgId === resourceOrgId;\n}\n\nfunction minRoleSql(minRole: ShareRole): SQL {\n if (minRole === \"viewer\") {\n // any role satisfies viewer\n return sql`1=1`;\n }\n if (minRole === \"editor\") {\n return sql`role in ('editor','admin')`;\n }\n return sql`role = 'admin'`;\n}\n\nfunction restrictedShareScopeSql(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n // Restricted resources (extensions) must stay inside their resource org even\n // if stale cross-org share rows already exist from older code or bad data.\n if (reg?.requireOrgMemberForUserShares !== true) return sql`1=1`;\n if (!ctx.orgId) return sql`1=0`;\n return eq(resourceTable.orgId, ctx.orgId);\n}\n\nfunction explicitSharesAllowedForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg.requireOrgMemberForUserShares !== true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n return !!resourceOrgId && !!ctx.orgId && resourceOrgId === ctx.orgId;\n}\n\nexport interface ResolvedAccess {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** The resource row (already loaded). */\n resource: any;\n}\n\n/**\n * Return the effective role the current user has on a specific resource, or\n * null if they have no access. Loads the resource and relevant share rows.\n */\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n ctx: AccessContext = currentAccess(),\n): Promise<ResolvedAccess | null> {\n const reg = requireShareableResource(resourceType);\n const db = reg.getDb() as any;\n\n const [resource] = await db\n .select()\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, resourceId));\n if (!resource) return null;\n\n const { userEmail, orgId } = ctx;\n\n if (\n userEmail &&\n resource.ownerEmail === userEmail &&\n ownerMatchesActiveScope(resource, ctx)\n ) {\n return { role: \"owner\", resource };\n }\n if (resource.visibility === \"public\" && reg.allowPublic !== false) {\n // No share row needed; default viewer unless upgraded below.\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n // `visibility === \"public\"` on an `allowPublic: false` resource is treated\n // as private: only owner + explicit shares grant access. Falls through to\n // the explicit-share lookup below.\n if (resource.visibility === \"org\" && orgId && resource.orgId === orgId) {\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n if (role) return { role, resource };\n return null;\n}\n\nasync function highestShareRole(\n reg: ShareableResourceRegistration,\n resourceId: string,\n ctx: AccessContext,\n resource: any,\n): Promise<ShareRole | null> {\n const { userEmail, orgId } = ctx;\n if (!userEmail && !orgId) return null;\n if (!explicitSharesAllowedForResource(reg, resource, ctx)) return null;\n const db = reg.getDb() as any;\n\n const principalClauses: ReturnType<typeof and>[] = [];\n if (userEmail) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"user\"),\n eq(reg.sharesTable.principalId, userEmail),\n ),\n );\n }\n if (orgId) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"org\"),\n eq(reg.sharesTable.principalId, orgId),\n ),\n );\n }\n\n const rows = await db\n .select({ role: reg.sharesTable.role })\n .from(reg.sharesTable)\n .where(\n and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)),\n )\n .limit(10);\n\n let best: ShareRole | null = null;\n for (const r of rows as Array<{ role: ShareRole }>) {\n if (!best || ROLE_RANK[r.role] > ROLE_RANK[best]) best = r.role;\n }\n return best;\n}\n\n/**\n * Throw ForbiddenError if the current user can't act on this resource with at\n * least the given role. Used at the top of update/delete actions.\n */\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" = \"viewer\",\n ctx: AccessContext = currentAccess(),\n): Promise<ResolvedAccess> {\n const access = await resolveAccess(resourceType, resourceId, ctx);\n if (!access) {\n throw new ForbiddenError(`No access to ${resourceType} ${resourceId}`);\n }\n if (ROLE_RANK[access.role] < ROLE_RANK[minRole]) {\n throw new ForbiddenError(\n `Requires ${minRole} role on ${resourceType} ${resourceId} (have ${access.role})`,\n );\n }\n return access;\n}\n"]}
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Access-control helpers for shareable resources.\n *\n * The access model combines:\n * 1. Direct ownership — `owner_email = currentUser`.\n * 2. Visibility — `'private' | 'org' | 'public'`. `org` grants read to anyone\n * in the same org; `public` grants read to any authenticated user.\n * 3. Share rows — per-user or per-org grants in the `{type}_shares` table\n * with a role (`viewer | editor | admin`).\n *\n * Use `applyAccessFilter()` on list/read queries to filter rows the current\n * user can see. Use `assertAccess()` at the top of write actions to reject\n * callers who lack the required role.\n */\n\nimport { and, eq, or, sql, type SQL } from \"drizzle-orm\";\nimport {\n getRequestUserEmail,\n getRequestOrgId,\n} from \"../server/request-context.js\";\nimport {\n listShareableResources,\n requireShareableResource,\n type ShareableResourceRegistration,\n} from \"./registry.js\";\nimport { ROLE_RANK, type ShareRole } from \"./schema.js\";\n\n/**\n * Find a registration by its drizzle table identity. Used to look up\n * per-resource policy flags (e.g. `allowPublic`) inside `accessFilter`,\n * which receives only the table — not the resource-type name.\n *\n * Identity is stable within a single bundle (Vite dedupes module instances);\n * the SSR/server side is the only caller, so per-bundle identity is fine.\n */\nfunction findRegistrationByTable(\n resourceTable: any,\n): ShareableResourceRegistration | undefined {\n for (const reg of listShareableResources()) {\n if (reg.resourceTable === resourceTable) return reg;\n }\n return undefined;\n}\n\nexport class ForbiddenError extends Error {\n statusCode = 403;\n constructor(message = \"Forbidden\") {\n super(message);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport interface AccessContext {\n userEmail?: string;\n orgId?: string;\n}\n\n/** Current request's access context. Pulls from request-context ALS. */\nexport function currentAccess(): AccessContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId(),\n };\n}\n\nexport function resolveRegisteredAccessContext(\n reg: ShareableResourceRegistration | undefined,\n ctx: AccessContext,\n): AccessContext {\n return reg?.resolveAccessContext ? reg.resolveAccessContext(ctx) : ctx;\n}\n\n/**\n * Build a Drizzle `WHERE` clause that admits rows the current user can see.\n * Pass the ownable resource table and its shares table; optional min role\n * (defaults to 'viewer') gates which share rows count.\n *\n * `visibility = 'public'` is intentionally NOT admitted by default. Public\n * means \"anyone with the link can view\" (still honoured by `resolveAccess`\n * for read-by-id), not \"appears in every signed-in user's list/sidebar.\"\n * Pass `{ includePublic: true }` for the rare list endpoint that wants\n * cross-user public discovery (a public template gallery, for example).\n *\n * Example:\n *\n * const rows = await db\n * .select()\n * .from(schema.documents)\n * .where(accessFilter(schema.documents, schema.documentShares));\n */\nexport function accessFilter(\n resourceTable: any,\n sharesTable: any,\n rawCtx: AccessContext = currentAccess(),\n minRole: ShareRole = \"viewer\",\n options: { includePublic?: boolean } = {},\n): SQL {\n // Defense in depth — resources registered with `allowPublic: false` must\n // never participate in cross-user \"public\" discovery, even if a caller\n // accidentally passes `includePublic: true` or if a stale public row sits\n // in the DB.\n const reg = findRegistrationByTable(resourceTable);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n const { userEmail, orgId } = ctx;\n const publicAllowed = reg?.allowPublic !== false;\n const includePublic = (options.includePublic ?? false) && publicAllowed;\n const clauses: SQL[] = [];\n\n if (userEmail) {\n clauses.push(\n and(\n eq(resourceTable.ownerEmail, userEmail),\n ownerScopeFilter(resourceTable, ctx),\n )!,\n );\n }\n if (minRole === \"viewer\") {\n if (includePublic) {\n clauses.push(eq(resourceTable.visibility, \"public\"));\n }\n if (orgId) {\n clauses.push(\n and(\n eq(resourceTable.visibility, \"org\"),\n eq(resourceTable.orgId, orgId),\n )!,\n );\n }\n }\n if (userEmail) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'user'\n and ${sharesTable.principalId} = ${userEmail}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n if (orgId) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'org'\n and ${sharesTable.principalId} = ${orgId}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n\n return or(...clauses) ?? sql`1=0`;\n}\n\nfunction ownerScopeFilter(resourceTable: any, ctx: AccessContext): SQL {\n if (ctx.orgId) {\n // Rows created before org-scoping, or in solo mode, have no org_id. Keep\n // them manageable by their owner after the owner joins or switches into an\n // organization, while still keeping rows from other orgs out of scope.\n return or(\n eq(resourceTable.orgId, ctx.orgId),\n sql`${resourceTable.orgId} IS NULL`,\n )!;\n }\n return sql`${resourceTable.orgId} IS NULL`;\n}\n\nfunction ownerMatchesActiveScope(resource: any, ctx: AccessContext): boolean {\n const resourceOrgId = resource?.orgId ?? null;\n if (!resourceOrgId) return true;\n return ctx.orgId === resourceOrgId;\n}\n\nfunction minRoleSql(minRole: ShareRole): SQL {\n if (minRole === \"viewer\") {\n // any role satisfies viewer\n return sql`1=1`;\n }\n if (minRole === \"editor\") {\n return sql`role in ('editor','admin')`;\n }\n return sql`role = 'admin'`;\n}\n\nfunction restrictedShareScopeSql(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n // Restricted resources (extensions) must stay inside their resource org even\n // if stale cross-org share rows already exist from older code or bad data.\n if (reg?.requireOrgMemberForUserShares !== true) return sql`1=1`;\n if (!ctx.orgId) return sql`1=0`;\n return eq(resourceTable.orgId, ctx.orgId);\n}\n\nfunction explicitSharesAllowedForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg.requireOrgMemberForUserShares !== true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n return !!resourceOrgId && !!ctx.orgId && resourceOrgId === ctx.orgId;\n}\n\nexport interface ResolvedAccess {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** The resource row (already loaded). */\n resource: any;\n}\n\n/**\n * Return the effective role the current user has on a specific resource, or\n * null if they have no access. Loads the resource and relevant share rows.\n */\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext = currentAccess(),\n): Promise<ResolvedAccess | null> {\n const reg = requireShareableResource(resourceType);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n const db = reg.getDb() as any;\n\n const [resource] = await db\n .select()\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, resourceId));\n if (!resource) return null;\n\n const { userEmail, orgId } = ctx;\n\n if (\n userEmail &&\n resource.ownerEmail === userEmail &&\n ownerMatchesActiveScope(resource, ctx)\n ) {\n return { role: \"owner\", resource };\n }\n if (resource.visibility === \"public\" && reg.allowPublic !== false) {\n // No share row needed; default viewer unless upgraded below.\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n // `visibility === \"public\"` on an `allowPublic: false` resource is treated\n // as private: only owner + explicit shares grant access. Falls through to\n // the explicit-share lookup below.\n if (resource.visibility === \"org\" && orgId && resource.orgId === orgId) {\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n if (role) return { role, resource };\n return null;\n}\n\nasync function highestShareRole(\n reg: ShareableResourceRegistration,\n resourceId: string,\n ctx: AccessContext,\n resource: any,\n): Promise<ShareRole | null> {\n const { userEmail, orgId } = ctx;\n if (!userEmail && !orgId) return null;\n if (!explicitSharesAllowedForResource(reg, resource, ctx)) return null;\n const db = reg.getDb() as any;\n\n const principalClauses: ReturnType<typeof and>[] = [];\n if (userEmail) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"user\"),\n eq(reg.sharesTable.principalId, userEmail),\n ),\n );\n }\n if (orgId) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"org\"),\n eq(reg.sharesTable.principalId, orgId),\n ),\n );\n }\n\n const rows = await db\n .select({ role: reg.sharesTable.role })\n .from(reg.sharesTable)\n .where(\n and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)),\n )\n .limit(10);\n\n let best: ShareRole | null = null;\n for (const r of rows as Array<{ role: ShareRole }>) {\n if (!best || ROLE_RANK[r.role] > ROLE_RANK[best]) best = r.role;\n }\n return best;\n}\n\n/**\n * Throw ForbiddenError if the current user can't act on this resource with at\n * least the given role. Used at the top of update/delete actions.\n */\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" = \"viewer\",\n ctx: AccessContext = currentAccess(),\n): Promise<ResolvedAccess> {\n const access = await resolveAccess(resourceType, resourceId, ctx);\n if (!access) {\n throw new ForbiddenError(`No access to ${resourceType} ${resourceId}`);\n }\n if (ROLE_RANK[access.role] < ROLE_RANK[minRole]) {\n throw new ForbiddenError(\n `Requires ${minRole} role on ${resourceType} ${resourceId} (have ${access.role})`,\n );\n }\n return access;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"set-resource-visibility.d.ts","sourceRoot":"","sources":["../../../src/sharing/actions/set-resource-visibility.ts"],"names":[],"mappings":";AAWA,wBAqDG"}
1
+ {"version":3,"file":"set-resource-visibility.d.ts","sourceRoot":"","sources":["../../../src/sharing/actions/set-resource-visibility.ts"],"names":[],"mappings":";AAeA,wBAwDG"}
@@ -1,8 +1,7 @@
1
1
  import { eq } from "drizzle-orm";
2
2
  import { z } from "zod";
3
3
  import { defineAction } from "../../action.js";
4
- import { getRequestOrgId } from "../../server/request-context.js";
5
- import { assertAccess, ForbiddenError } from "../access.js";
4
+ import { assertAccess, currentAccess, ForbiddenError, resolveRegisteredAccessContext, } from "../access.js";
6
5
  import { requireShareableResource } from "../registry.js";
7
6
  import { getExtensionShareChangeTargets, notifyExtensionShareChanged, } from "./extension-change.js";
8
7
  export default defineAction({
@@ -24,7 +23,7 @@ export default defineAction({
24
23
  const beforeExtensionTargets = await getExtensionShareChangeTargets(args.resourceType, args.resourceId);
25
24
  const db = reg.getDb();
26
25
  const update = { visibility: args.visibility };
27
- const currentOrgId = getRequestOrgId();
26
+ const currentOrgId = resolveRegisteredAccessContext(reg, currentAccess()).orgId;
28
27
  // Only the resource owner may bind an org to a previously unscoped resource.
29
28
  // If a non-owner admin did this, the resource would adopt the admin's org
30
29
  // and ownerMatchesActiveScope would then lock the real owner out of their
@@ -1 +1 @@
1
- {"version":3,"file":"set-resource-visibility.js","sourceRoot":"","sources":["../../../src/sharing/actions/set-resource-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,mHAAmH;IACrH,sEAAsE;IACtE,oEAAoE;IACpE,YAAY,EAAE,KAAK;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;KACjD,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,GAAG,GAAG,wBAAwB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC9D,MAAM,IAAI,cAAc,CACtB,GAAG,GAAG,CAAC,WAAW,mFAAmF,CACtG,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,OAAO,CACR,CAAC;QACF,MAAM,sBAAsB,GAAG,MAAM,8BAA8B,CACjE,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,EAAS,CAAC;QAC9B,MAAM,MAAM,GAA4B,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QACxE,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,6EAA6E;QAC7E,0EAA0E;QAC1E,0EAA0E;QAC1E,8EAA8E;QAC9E,IACE,IAAI,CAAC,UAAU,KAAK,KAAK;YACzB,YAAY;YACZ,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK;YACvB,MAAM,CAAC,IAAI,KAAK,OAAO,EACvB,CAAC;YACD,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC;QAC9B,CAAC;QACD,MAAM,EAAE;aACL,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;aACzB,GAAG,CAAC,MAAM,CAAC;aACX,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACpD,MAAM,2BAA2B,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,sBAAsB,CACvB,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IACnD,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport { z } from \"zod\";\nimport { defineAction } from \"../../action.js\";\nimport { getRequestOrgId } from \"../../server/request-context.js\";\nimport { assertAccess, ForbiddenError } from \"../access.js\";\nimport { requireShareableResource } from \"../registry.js\";\nimport {\n getExtensionShareChangeTargets,\n notifyExtensionShareChanged,\n} from \"./extension-change.js\";\n\nexport default defineAction({\n description:\n \"Change the coarse visibility of a shareable resource: 'private' | 'org' | 'public'. Owner or admin role required.\",\n // (audit H5) Visibility changes are admin-tier and can flip a private\n // resource org-wide or public. Refuse from the tools iframe bridge.\n toolCallable: false,\n schema: z.object({\n resourceType: z.string(),\n resourceId: z.string(),\n visibility: z.enum([\"private\", \"org\", \"public\"]),\n }),\n run: async (args) => {\n const reg = requireShareableResource(args.resourceType);\n if (args.visibility === \"public\" && reg.allowPublic === false) {\n throw new ForbiddenError(\n `${reg.displayName} cannot be made public — share with specific people or your organization instead.`,\n );\n }\n const access = await assertAccess(\n args.resourceType,\n args.resourceId,\n \"admin\",\n );\n const beforeExtensionTargets = await getExtensionShareChangeTargets(\n args.resourceType,\n args.resourceId,\n );\n const db = reg.getDb() as any;\n const update: Record<string, unknown> = { visibility: args.visibility };\n const currentOrgId = getRequestOrgId();\n // Only the resource owner may bind an org to a previously unscoped resource.\n // If a non-owner admin did this, the resource would adopt the admin's org\n // and ownerMatchesActiveScope would then lock the real owner out of their\n // own resource. Non-owner admins can still flip visibility once orgId is set.\n if (\n args.visibility === \"org\" &&\n currentOrgId &&\n !access.resource?.orgId &&\n access.role === \"owner\"\n ) {\n update.orgId = currentOrgId;\n }\n await db\n .update(reg.resourceTable)\n .set(update)\n .where(eq(reg.resourceTable.id, args.resourceId));\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n return { ok: true, visibility: args.visibility };\n },\n});\n"]}
1
+ {"version":3,"file":"set-resource-visibility.js","sourceRoot":"","sources":["../../../src/sharing/actions/set-resource-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,8BAA8B,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,mHAAmH;IACrH,sEAAsE;IACtE,oEAAoE;IACpE,YAAY,EAAE,KAAK;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;KACjD,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,GAAG,GAAG,wBAAwB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC9D,MAAM,IAAI,cAAc,CACtB,GAAG,GAAG,CAAC,WAAW,mFAAmF,CACtG,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,OAAO,CACR,CAAC;QACF,MAAM,sBAAsB,GAAG,MAAM,8BAA8B,CACjE,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,EAAS,CAAC;QAC9B,MAAM,MAAM,GAA4B,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QACxE,MAAM,YAAY,GAAG,8BAA8B,CACjD,GAAG,EACH,aAAa,EAAE,CAChB,CAAC,KAAK,CAAC;QACR,6EAA6E;QAC7E,0EAA0E;QAC1E,0EAA0E;QAC1E,8EAA8E;QAC9E,IACE,IAAI,CAAC,UAAU,KAAK,KAAK;YACzB,YAAY;YACZ,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK;YACvB,MAAM,CAAC,IAAI,KAAK,OAAO,EACvB,CAAC;YACD,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC;QAC9B,CAAC;QACD,MAAM,EAAE;aACL,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;aACzB,GAAG,CAAC,MAAM,CAAC;aACX,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACpD,MAAM,2BAA2B,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,sBAAsB,CACvB,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IACnD,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport { z } from \"zod\";\nimport { defineAction } from \"../../action.js\";\nimport {\n assertAccess,\n currentAccess,\n ForbiddenError,\n resolveRegisteredAccessContext,\n} from \"../access.js\";\nimport { requireShareableResource } from \"../registry.js\";\nimport {\n getExtensionShareChangeTargets,\n notifyExtensionShareChanged,\n} from \"./extension-change.js\";\n\nexport default defineAction({\n description:\n \"Change the coarse visibility of a shareable resource: 'private' | 'org' | 'public'. Owner or admin role required.\",\n // (audit H5) Visibility changes are admin-tier and can flip a private\n // resource org-wide or public. Refuse from the tools iframe bridge.\n toolCallable: false,\n schema: z.object({\n resourceType: z.string(),\n resourceId: z.string(),\n visibility: z.enum([\"private\", \"org\", \"public\"]),\n }),\n run: async (args) => {\n const reg = requireShareableResource(args.resourceType);\n if (args.visibility === \"public\" && reg.allowPublic === false) {\n throw new ForbiddenError(\n `${reg.displayName} cannot be made public — share with specific people or your organization instead.`,\n );\n }\n const access = await assertAccess(\n args.resourceType,\n args.resourceId,\n \"admin\",\n );\n const beforeExtensionTargets = await getExtensionShareChangeTargets(\n args.resourceType,\n args.resourceId,\n );\n const db = reg.getDb() as any;\n const update: Record<string, unknown> = { visibility: args.visibility };\n const currentOrgId = resolveRegisteredAccessContext(\n reg,\n currentAccess(),\n ).orgId;\n // Only the resource owner may bind an org to a previously unscoped resource.\n // If a non-owner admin did this, the resource would adopt the admin's org\n // and ownerMatchesActiveScope would then lock the real owner out of their\n // own resource. Non-owner admins can still flip visibility once orgId is set.\n if (\n args.visibility === \"org\" &&\n currentOrgId &&\n !access.resource?.orgId &&\n access.role === \"owner\"\n ) {\n update.orgId = currentOrgId;\n }\n await db\n .update(reg.resourceTable)\n .set(update)\n .where(eq(reg.resourceTable.id, args.resourceId));\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n return { ok: true, visibility: args.visibility };\n },\n});\n"]}
@@ -67,6 +67,19 @@ export interface ShareableResourceRegistration {
67
67
  * Default: `false` (matches the historical behavior — any email can be granted).
68
68
  */
69
69
  requireOrgMemberForUserShares?: boolean;
70
+ /**
71
+ * Optional per-resource access-context adapter. Most resources should use the
72
+ * request user/org unchanged. Templates with an intentional alternate local
73
+ * identity can normalize here so the generic framework sharing actions and
74
+ * access helpers stay in sync with template-owned actions.
75
+ */
76
+ resolveAccessContext?: (ctx: {
77
+ userEmail?: string;
78
+ orgId?: string;
79
+ }) => {
80
+ userEmail?: string;
81
+ orgId?: string;
82
+ };
70
83
  }
71
84
  export declare function registerShareableResource(entry: ShareableResourceRegistration): void;
72
85
  export declare function getShareableResource(type: string): ShareableResourceRegistration | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,aAAa,EAAE,GAAG,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,GAAG,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;OAUG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC;AAuBD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,6BAA6B,GACnC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,GACX,6BAA6B,GAAG,SAAS,CAE3C;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GACX,6BAA6B,CAS/B;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,EAAE,CAExE"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,aAAa,EAAE,GAAG,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,GAAG,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;OAUG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QACtE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAuBD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,6BAA6B,GACnC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,GACX,6BAA6B,GAAG,SAAS,CAE3C;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GACX,6BAA6B,CAS/B;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,EAAE,CAExE"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuDH,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEzD,MAAM,cAAc,GAClB,UAAiB,CAAC;AACpB,SAAS,WAAW;IAClB,IAAI,CAAC,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,IAAI,GAAG,EAAyC,CAAC;QACrD,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAoC;IAEpC,WAAW,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY;IAEZ,OAAO,WAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAY;IAEZ,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qCAAqC,IAAI,gDAAgD,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * Registry of shareable resources.\n *\n * Each template registers its ownable resource(s) once on module load so the\n * framework-level share actions (`share-resource`, `list-resource-shares`,\n * etc.) can dispatch to the correct tables.\n *\n * import { registerShareableResource } from \"@agent-native/core/sharing\";\n * import * as schema from \"./schema.js\";\n *\n * registerShareableResource({\n * type: \"document\",\n * resourceTable: schema.documents,\n * sharesTable: schema.documentShares,\n * displayName: \"Document\",\n * titleColumn: \"title\",\n * });\n */\n\nexport interface ShareableResourceRegistration {\n /** Stable identifier used across actions, UI, and analytics. e.g. \"document\". */\n type: string;\n /** Drizzle table for the parent resource (must have ownableColumns()). */\n resourceTable: any;\n /** Drizzle table produced by createSharesTable(). */\n sharesTable: any;\n /** Human-readable singular label shown in the share dialog. */\n displayName: string;\n /**\n * Column on the resource table that holds a human-readable title for\n * display in the share UI. Default: \"title\".\n */\n titleColumn?: string;\n /**\n * Optional app-relative path to this resource. Used by share notifications\n * when the caller does not pass a more specific resourceUrl.\n */\n getResourcePath?: (resource: any) => string | undefined;\n /**\n * Drizzle DB accessor from the template's server/db/index.ts. Required —\n * the framework-level share actions and access helpers call this to reach\n * the right DB instance (schema is template-specific).\n */\n getDb: () => any;\n /**\n * When `false`, `visibility: \"public\"` is rejected by `set-resource-visibility`,\n * and `accessFilter` / `resolveAccess` treat any stored public row as private\n * (defense in depth — only owner + explicit shares grant access).\n *\n * Use this for resources that execute code or expose privileged data and must\n * never be reachable by a random authenticated user. Extensions set this:\n * extension HTML runs inside an iframe that calls actions / DB / proxied APIs\n * as the *viewer*, so a public extension would be arbitrary code with the\n * viewer's credentials.\n *\n * Default: `true` (matches the historical behavior — most resources can be public).\n */\n allowPublic?: boolean;\n /**\n * When `true`, individual user shares (`principalType: \"user\"`) must target\n * an email that is already a member of the same org as the resource, OR has\n * a pending invitation to that org. Cross-org user shares are rejected.\n *\n * Pair with `allowPublic: false` for resources that need a hard \"this org\n * only\" trust boundary. Extensions set this so a malicious caller can't\n * widen reach by sharing a code-executing extension to an outsider email.\n *\n * Default: `false` (matches the historical behavior — any email can be granted).\n */\n requireOrgMemberForUserShares?: boolean;\n}\n\n// Stash the registry on globalThis so it survives SSR bundle duplication.\n// Vite SSR's `noExternal: /^(?!node:)/` policy means @agent-native/core gets\n// inlined into every server bundle that imports it — and each bundle gets its\n// own module-level state. A plain `new Map()` here would create one Map per\n// bundle, so the template's `registerShareableResource()` (called from the\n// Nitro plugin graph) wouldn't be visible to the framework's auto-mounted\n// share-resource action (loaded via `import(\"../sharing/actions/...js\")` in a\n// different module instance). Using globalThis collapses them back to one Map.\nconst REGISTRY_KEY = \"__agentNativeShareableResources__\";\ntype RegistryStore = Map<string, ShareableResourceRegistration>;\nconst globalRegistry: { [K in typeof REGISTRY_KEY]?: RegistryStore } =\n globalThis as any;\nfunction getRegistry(): RegistryStore {\n let r = globalRegistry[REGISTRY_KEY];\n if (!r) {\n r = new Map<string, ShareableResourceRegistration>();\n globalRegistry[REGISTRY_KEY] = r;\n }\n return r;\n}\n\nexport function registerShareableResource(\n entry: ShareableResourceRegistration,\n): void {\n getRegistry().set(entry.type, entry);\n}\n\nexport function getShareableResource(\n type: string,\n): ShareableResourceRegistration | undefined {\n return getRegistry().get(type);\n}\n\nexport function requireShareableResource(\n type: string,\n): ShareableResourceRegistration {\n const reg = getRegistry();\n const entry = reg.get(type);\n if (!entry) {\n throw new Error(\n `Unknown shareable resource type: \"${type}\". Did you forget registerShareableResource()?`,\n );\n }\n return entry;\n}\n\nexport function listShareableResources(): ShareableResourceRegistration[] {\n return Array.from(getRegistry().values());\n}\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiEH,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEzD,MAAM,cAAc,GAClB,UAAiB,CAAC;AACpB,SAAS,WAAW;IAClB,IAAI,CAAC,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,IAAI,GAAG,EAAyC,CAAC;QACrD,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAoC;IAEpC,WAAW,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY;IAEZ,OAAO,WAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAY;IAEZ,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qCAAqC,IAAI,gDAAgD,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * Registry of shareable resources.\n *\n * Each template registers its ownable resource(s) once on module load so the\n * framework-level share actions (`share-resource`, `list-resource-shares`,\n * etc.) can dispatch to the correct tables.\n *\n * import { registerShareableResource } from \"@agent-native/core/sharing\";\n * import * as schema from \"./schema.js\";\n *\n * registerShareableResource({\n * type: \"document\",\n * resourceTable: schema.documents,\n * sharesTable: schema.documentShares,\n * displayName: \"Document\",\n * titleColumn: \"title\",\n * });\n */\n\nexport interface ShareableResourceRegistration {\n /** Stable identifier used across actions, UI, and analytics. e.g. \"document\". */\n type: string;\n /** Drizzle table for the parent resource (must have ownableColumns()). */\n resourceTable: any;\n /** Drizzle table produced by createSharesTable(). */\n sharesTable: any;\n /** Human-readable singular label shown in the share dialog. */\n displayName: string;\n /**\n * Column on the resource table that holds a human-readable title for\n * display in the share UI. Default: \"title\".\n */\n titleColumn?: string;\n /**\n * Optional app-relative path to this resource. Used by share notifications\n * when the caller does not pass a more specific resourceUrl.\n */\n getResourcePath?: (resource: any) => string | undefined;\n /**\n * Drizzle DB accessor from the template's server/db/index.ts. Required —\n * the framework-level share actions and access helpers call this to reach\n * the right DB instance (schema is template-specific).\n */\n getDb: () => any;\n /**\n * When `false`, `visibility: \"public\"` is rejected by `set-resource-visibility`,\n * and `accessFilter` / `resolveAccess` treat any stored public row as private\n * (defense in depth — only owner + explicit shares grant access).\n *\n * Use this for resources that execute code or expose privileged data and must\n * never be reachable by a random authenticated user. Extensions set this:\n * extension HTML runs inside an iframe that calls actions / DB / proxied APIs\n * as the *viewer*, so a public extension would be arbitrary code with the\n * viewer's credentials.\n *\n * Default: `true` (matches the historical behavior — most resources can be public).\n */\n allowPublic?: boolean;\n /**\n * When `true`, individual user shares (`principalType: \"user\"`) must target\n * an email that is already a member of the same org as the resource, OR has\n * a pending invitation to that org. Cross-org user shares are rejected.\n *\n * Pair with `allowPublic: false` for resources that need a hard \"this org\n * only\" trust boundary. Extensions set this so a malicious caller can't\n * widen reach by sharing a code-executing extension to an outsider email.\n *\n * Default: `false` (matches the historical behavior — any email can be granted).\n */\n requireOrgMemberForUserShares?: boolean;\n /**\n * Optional per-resource access-context adapter. Most resources should use the\n * request user/org unchanged. Templates with an intentional alternate local\n * identity can normalize here so the generic framework sharing actions and\n * access helpers stay in sync with template-owned actions.\n */\n resolveAccessContext?: (ctx: { userEmail?: string; orgId?: string }) => {\n userEmail?: string;\n orgId?: string;\n };\n}\n\n// Stash the registry on globalThis so it survives SSR bundle duplication.\n// Vite SSR's `noExternal: /^(?!node:)/` policy means @agent-native/core gets\n// inlined into every server bundle that imports it — and each bundle gets its\n// own module-level state. A plain `new Map()` here would create one Map per\n// bundle, so the template's `registerShareableResource()` (called from the\n// Nitro plugin graph) wouldn't be visible to the framework's auto-mounted\n// share-resource action (loaded via `import(\"../sharing/actions/...js\")` in a\n// different module instance). Using globalThis collapses them back to one Map.\nconst REGISTRY_KEY = \"__agentNativeShareableResources__\";\ntype RegistryStore = Map<string, ShareableResourceRegistration>;\nconst globalRegistry: { [K in typeof REGISTRY_KEY]?: RegistryStore } =\n globalThis as any;\nfunction getRegistry(): RegistryStore {\n let r = globalRegistry[REGISTRY_KEY];\n if (!r) {\n r = new Map<string, ShareableResourceRegistration>();\n globalRegistry[REGISTRY_KEY] = r;\n }\n return r;\n}\n\nexport function registerShareableResource(\n entry: ShareableResourceRegistration,\n): void {\n getRegistry().set(entry.type, entry);\n}\n\nexport function getShareableResource(\n type: string,\n): ShareableResourceRegistration | undefined {\n return getRegistry().get(type);\n}\n\nexport function requireShareableResource(\n type: string,\n): ShareableResourceRegistration {\n const reg = getRegistry();\n const entry = reg.get(type);\n if (!entry) {\n throw new Error(\n `Unknown shareable resource type: \"${type}\". Did you forget registerShareableResource()?`,\n );\n }\n return entry;\n}\n\nexport function listShareableResources(): ShareableResourceRegistration[] {\n return Array.from(getRegistry().values());\n}\n"]}
@@ -346,6 +346,7 @@
346
346
 
347
347
  .an-rich-md-slash-menu {
348
348
  z-index: 9999;
349
+ width: min(420px, calc(100vw - 24px));
349
350
  min-width: 230px;
350
351
  max-height: 320px;
351
352
  overflow-y: auto;
@@ -370,6 +371,8 @@
370
371
 
371
372
  .an-rich-md-slash-item {
372
373
  width: 100%;
374
+ min-width: 0;
375
+ min-height: 44px;
373
376
  display: flex;
374
377
  align-items: center;
375
378
  gap: 8px;
@@ -402,14 +405,26 @@
402
405
  flex-shrink: 0;
403
406
  }
404
407
 
408
+ .an-rich-md-slash-copy {
409
+ display: block;
410
+ min-width: 0;
411
+ flex: 1;
412
+ }
413
+
405
414
  .an-rich-md-slash-title {
406
415
  display: block;
416
+ overflow: hidden;
417
+ text-overflow: ellipsis;
418
+ white-space: nowrap;
407
419
  font-weight: 500;
408
420
  font-size: 13px;
409
421
  }
410
422
 
411
423
  .an-rich-md-slash-description {
412
424
  display: block;
425
+ overflow: hidden;
426
+ text-overflow: ellipsis;
427
+ white-space: nowrap;
413
428
  font-size: 11px;
414
429
  color: hsl(var(--muted-foreground));
415
430
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.39.2",
3
+ "version": "0.40.1",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -211,6 +211,8 @@
211
211
  "@ai-sdk/groq": ">=3",
212
212
  "@ai-sdk/mistral": ">=3",
213
213
  "@ai-sdk/openai": ">=3",
214
+ "@excalidraw/excalidraw": ">=0.18",
215
+ "@excalidraw/mermaid-to-excalidraw": ">=2",
214
216
  "@openrouter/ai-sdk-provider": ">=2",
215
217
  "@supabase/supabase-js": ">=2",
216
218
  "@tabler/icons-react": ">=3",
@@ -224,6 +226,7 @@
224
226
  "ai-sdk-ollama": ">=3",
225
227
  "convex": ">=1",
226
228
  "drizzle-kit": ">=0.31",
229
+ "mermaid": ">=11",
227
230
  "node-pty": ">=1",
228
231
  "postgres": ">=3",
229
232
  "react": ">=18",
@@ -259,6 +262,12 @@
259
262
  "@ai-sdk/cohere": {
260
263
  "optional": true
261
264
  },
265
+ "@excalidraw/excalidraw": {
266
+ "optional": true
267
+ },
268
+ "@excalidraw/mermaid-to-excalidraw": {
269
+ "optional": true
270
+ },
262
271
  "ai-sdk-ollama": {
263
272
  "optional": true
264
273
  },
@@ -292,6 +301,9 @@
292
301
  "drizzle-kit": {
293
302
  "optional": true
294
303
  },
304
+ "mermaid": {
305
+ "optional": true
306
+ },
295
307
  "@xterm/xterm": {
296
308
  "optional": true
297
309
  },
@@ -318,6 +330,8 @@
318
330
  "@ai-sdk/openai": "^3.0.53",
319
331
  "@assistant-ui/react": "^0.12.19",
320
332
  "@assistant-ui/react-markdown": "^0.12.6",
333
+ "@excalidraw/excalidraw": "0.18.1",
334
+ "@excalidraw/mermaid-to-excalidraw": "2.2.2",
321
335
  "@react-router/dev": "^7.16.0",
322
336
  "@react-router/fs-routes": "^7.16.0",
323
337
  "@supabase/supabase-js": "^2.49.0",
@@ -340,6 +354,7 @@
340
354
  "autoprefixer": "^10.4.21",
341
355
  "drizzle-kit": "^0.31.10",
342
356
  "express": "^5.2.1",
357
+ "mermaid": "11.15.0",
343
358
  "node-pty": "^1.1.0",
344
359
  "playwright": "^1.60.0",
345
360
  "react": "^19.2.7",