@k2b/cloud 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/package.json +6 -5
  2. package/src/_internal/bounded-json.ts +14 -2
  3. package/src/_internal/capabilities.ts +87 -8
  4. package/src/_internal/capability-reviews.ts +6 -0
  5. package/src/_internal/capability-timeout.ts +71 -0
  6. package/src/_internal/define-app.ts +145 -89
  7. package/src/_internal/http-drain.ts +56 -0
  8. package/src/_internal/registry-validation.ts +3 -0
  9. package/src/_internal/runtime-context.ts +1 -0
  10. package/src/_internal/status-preserving-ssr.ts +2 -0
  11. package/src/access/PermissionEditor.tsx +16 -17
  12. package/src/access/PrincipalPicker.tsx +45 -10
  13. package/src/access/messages.ts +8 -0
  14. package/src/access/principal-label.ts +18 -0
  15. package/src/access/ui.ts +1 -0
  16. package/src/account/EntitySearch.tsx +5 -4
  17. package/src/ai/action-review.ts +2 -1
  18. package/src/ai/app-skill-store.ts +1 -1
  19. package/src/ai/capabilities.ts +21 -3
  20. package/src/ai/capability-execution.ts +14 -12
  21. package/src/ai/chat/action-card.ts +7 -1
  22. package/src/ai/chat/action-messages.ts +14 -0
  23. package/src/ai/chat/action-question.browser-harness.tsx +12 -0
  24. package/src/ai/chat/action-question.tsx +202 -22
  25. package/src/ai/chat/action-text.ts +1 -1
  26. package/src/ai/chat-tasks.ts +2 -1
  27. package/src/ai/client/controller.ts +150 -32
  28. package/src/ai/client/transport.ts +14 -2
  29. package/src/ai/code-capability-routes.ts +27 -23
  30. package/src/ai/code-runtime-tools.ts +14 -16
  31. package/src/ai/code-source-tools.ts +16 -18
  32. package/src/ai/executor.ts +56 -21
  33. package/src/ai/fetch-file-tool.ts +67 -76
  34. package/src/ai/http.ts +1 -0
  35. package/src/ai/migrate.ts +13 -0
  36. package/src/ai/open-questions.ts +4 -8
  37. package/src/ai/projects.ts +1 -2
  38. package/src/ai/protocol.ts +2 -2
  39. package/src/ai/recent-resources.ts +12 -5
  40. package/src/ai/review-content.ts +77 -0
  41. package/src/ai/routes.ts +69 -31
  42. package/src/ai/runtime.ts +1 -0
  43. package/src/ai/skill-tool.ts +17 -6
  44. package/src/ai/skills-routes.ts +72 -27
  45. package/src/ai/skills.ts +139 -60
  46. package/src/ai/store.ts +168 -33
  47. package/src/ai/types.ts +10 -3
  48. package/src/ai/usage.ts +2 -2
  49. package/src/api/admin-core-settings.ts +15 -2
  50. package/src/api/admin-geo.ts +54 -0
  51. package/src/api/admin-linux-identities.ts +6 -3
  52. package/src/api/admin-outgoing-mail.ts +29 -15
  53. package/src/api/approvals-loader.ts +3 -3
  54. package/src/api/approvals.ts +51 -0
  55. package/src/api/apps.ts +4 -2
  56. package/src/api/capabilities.ts +258 -181
  57. package/src/api/capability-streams.ts +21 -13
  58. package/src/api/cli-plugins.ts +4 -1
  59. package/src/api/geo-glyphs.ts +11 -0
  60. package/src/api/geo.ts +128 -0
  61. package/src/api/help.ts +12 -4
  62. package/src/api/index.ts +5 -0
  63. package/src/api/mcp.ts +46 -38
  64. package/src/api/me-panel-consent.ts +8 -5
  65. package/src/api/search.ts +21 -14
  66. package/src/api/sync-ops.ts +19 -13
  67. package/src/api/widgets.ts +42 -18
  68. package/src/approvals/Approvals.tsx +5 -3
  69. package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
  70. package/src/browser/CloudResourceSearch.tsx +3 -13
  71. package/src/browser/PanelLeaveNotice.tsx +18 -0
  72. package/src/browser/navigation-search.ts +78 -25
  73. package/src/browser/panel-leave.ts +69 -0
  74. package/src/browser/panel-messages.ts +12 -0
  75. package/src/browser/panel-slot.ts +173 -24
  76. package/src/browser/panel.ts +3 -0
  77. package/src/browser/resource-search-messages.ts +2 -0
  78. package/src/capabilities/client.ts +28 -1
  79. package/src/capabilities/executions.ts +22 -3
  80. package/src/capabilities/geo-client.ts +57 -0
  81. package/src/capabilities/response.ts +2 -1
  82. package/src/capabilities/review-storage.ts +24 -0
  83. package/src/capabilities/reviews.ts +17 -3
  84. package/src/capabilities/server.ts +36 -16
  85. package/src/cli/access.ts +15 -23
  86. package/src/cli/admin/linux.ts +13 -2
  87. package/src/cli/admin/outgoing-mail.ts +26 -4
  88. package/src/cli/capabilities.ts +19 -0
  89. package/src/config/env.ts +0 -6
  90. package/src/contracts/app-access.ts +48 -0
  91. package/src/contracts/app.ts +4 -1
  92. package/src/contracts/approvals.ts +2 -2
  93. package/src/contracts/avatar.ts +186 -0
  94. package/src/contracts/capabilities.ts +148 -54
  95. package/src/contracts/geo-admin.ts +127 -0
  96. package/src/contracts/geo-map.ts +19 -0
  97. package/src/contracts/geo-route.ts +168 -0
  98. package/src/contracts/geo.ts +248 -0
  99. package/src/contracts/index.ts +4 -0
  100. package/src/contracts/outgoing-mail.ts +17 -4
  101. package/src/contracts/registry.ts +2 -0
  102. package/src/events/app-access.ts +68 -0
  103. package/src/events/live-delivery.ts +128 -0
  104. package/src/events/live.ts +14 -130
  105. package/src/geo/CloudMap.tsx +95 -0
  106. package/src/geo/map-messages.ts +17 -0
  107. package/src/geo/ui.ts +1 -0
  108. package/src/index.ts +2 -0
  109. package/src/server/client-ip.ts +36 -0
  110. package/src/server/index.ts +3 -4
  111. package/src/server/middleware/app-access.ts +312 -0
  112. package/src/server/middleware/auth.ts +1 -1
  113. package/src/server/middleware/invocation.ts +11 -0
  114. package/src/server/middleware/rate-limit.ts +4 -4
  115. package/src/server/services/access.ts +13 -9
  116. package/src/server/services/index.ts +0 -2
  117. package/src/server/services/services.ts +0 -2
  118. package/src/services/accounts/posix.ts +21 -7
  119. package/src/services/allowed-network-fetch.ts +16 -10
  120. package/src/services/app-access-metrics.ts +24 -0
  121. package/src/services/app-access.ts +123 -0
  122. package/src/services/app-discovery.ts +27 -0
  123. package/src/services/approvals/content.ts +40 -0
  124. package/src/services/approvals/index.ts +14 -6
  125. package/src/services/approvals/list.ts +6 -11
  126. package/src/services/approvals/question.ts +11 -7
  127. package/src/services/downstream-budget.ts +87 -0
  128. package/src/services/geo/admin.ts +262 -0
  129. package/src/services/geo/bucket.ts +67 -0
  130. package/src/services/geo/cache.ts +57 -0
  131. package/src/services/geo/costs.ts +5 -0
  132. package/src/services/geo/errors.ts +41 -0
  133. package/src/services/geo/fixtures/README.md +17 -0
  134. package/src/services/geo/fixtures/along.json +355 -0
  135. package/src/services/geo/fixtures/autocomplete.json +218 -0
  136. package/src/services/geo/fixtures/geocode.json +129 -0
  137. package/src/services/geo/fixtures/places.json +355 -0
  138. package/src/services/geo/fixtures/reverse.json +61 -0
  139. package/src/services/geo/fixtures/routing.json +57 -0
  140. package/src/services/geo/geoapify-route.ts +93 -0
  141. package/src/services/geo/geoapify.ts +281 -0
  142. package/src/services/geo/ledger.ts +122 -0
  143. package/src/services/geo/map.ts +160 -0
  144. package/src/services/geo/migrate.ts +25 -0
  145. package/src/services/geo/polyline.ts +184 -0
  146. package/src/services/geo/route-chain.ts +145 -0
  147. package/src/services/geo/route-costs.ts +9 -0
  148. package/src/services/geo/route-geometry.ts +39 -0
  149. package/src/services/geo/route-plan.ts +172 -0
  150. package/src/services/geo/route-service.ts +433 -0
  151. package/src/services/geo/service.ts +300 -0
  152. package/src/services/geo/settings.ts +49 -0
  153. package/src/services/help/index.ts +16 -7
  154. package/src/services/help/types.ts +6 -1
  155. package/src/services/identity/invocation-actor.ts +20 -2
  156. package/src/services/identity/issue-invocation.ts +67 -0
  157. package/src/services/identity/key-ring.ts +5 -3
  158. package/src/services/identity/runtime-config.ts +23 -0
  159. package/src/services/index.ts +9 -0
  160. package/src/services/mandates/index.ts +40 -3
  161. package/src/services/network-security.ts +6 -3
  162. package/src/services/network-transport.ts +99 -0
  163. package/src/services/notifications/catalog.ts +3 -2
  164. package/src/services/notifications/runtime.ts +1 -0
  165. package/src/services/notifications/web-push-transport.ts +45 -38
  166. package/src/services/outgoing-mail/dispatcher.ts +6 -1
  167. package/src/services/outgoing-mail/index.ts +55 -1
  168. package/src/services/outgoing-mail/messages.ts +82 -3
  169. package/src/services/outgoing-mail/resolve-host.ts +2 -2
  170. package/src/services/outgoing-mail/retention.ts +13 -1
  171. package/src/services/outgoing-mail/store.ts +36 -6
  172. package/src/services/outgoing-mail/test-send.ts +34 -4
  173. package/src/services/outgoing-mail/transport.ts +1 -1
  174. package/src/services/pdf/gotenberg.ts +11 -38
  175. package/src/services/pdf/template-preview.ts +1 -1
  176. package/src/services/public-http.ts +28 -40
  177. package/src/services/settings/app.ts +4 -1
  178. package/src/services/settings/core-settings.ts +88 -0
  179. package/src/services/settings/defaults.ts +8 -2
  180. package/src/services/settings/errors.ts +11 -0
  181. package/src/services/settings/geo-budget.ts +40 -0
  182. package/src/services/settings/index.ts +4 -3
  183. package/src/services/settings/store.ts +25 -17
  184. package/src/services/weather/geo.ts +29 -111
  185. package/src/services/weather/index.ts +4 -3
  186. package/src/services/weather/locations.ts +8 -5
  187. package/src/services/weather/migrate.ts +1 -0
  188. package/src/services/weather/settings.ts +0 -16
  189. package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
  190. package/src/shared/avatar-art.ts +400 -0
  191. package/src/shared/index.ts +2 -0
  192. package/src/shared/network-address.ts +22 -3
  193. package/src/shared/template-rendering.ts +36 -4
  194. package/src/shared-text/documents.ts +1 -1
  195. package/src/shared-text/index.ts +2 -0
  196. package/src/shared-text/log.ts +51 -3
  197. package/src/shared-text/snapshots.ts +340 -0
  198. package/src/ssr/AdminLayout.tsx +4 -2
  199. package/src/ssr/CloudShell.island.tsx +6 -0
  200. package/src/ssr/Layout.tsx +65 -15
  201. package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
  202. package/src/ssr/LayoutHelp.tsx +3 -2
  203. package/src/ssr/PanelRuntime.island.tsx +11 -7
  204. package/src/ssr/PwaLayout.tsx +2 -2
  205. package/src/ssr/RailApps.tsx +6 -0
  206. package/src/ssr/ShellRuntime.island.tsx +23 -7
  207. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
  208. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +6 -6
  209. package/src/ssr/admin-navigation.ts +1 -0
  210. package/src/ssr/app-badges.ts +6 -1
  211. package/src/ssr/compact-shell.browser-harness.ts +44 -5
  212. package/src/ssr/help.ts +3 -2
  213. package/src/ssr/index.ts +1 -1
  214. package/src/ssr/layout-context.ts +2 -0
  215. package/src/ssr/mobile-menu-history.ts +4 -0
  216. package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
  217. package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
  218. package/src/ssr/page-swap.browser-harness.tsx +177 -0
  219. package/src/ssr/page-swap.ts +28 -0
  220. package/src/ssr/page-trail-runtime.ts +32 -13
  221. package/src/ssr/panel-consent.ts +2 -2
  222. package/src/ssr/panel-slot.browser-harness.tsx +3 -0
  223. package/src/ssr/platform-messages.ts +8 -0
  224. package/src/ssr/runtime.ts +17 -0
  225. package/src/ssr/shell-data.tsx +10 -2
  226. package/src/styles/global.css +70 -6
  227. package/src/server/services/geo.ts +0 -215
package/src/ai/skills.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { type SQL, type SQLQuery, sql } from "bun";
2
+ import { listApps } from "../_internal/registry";
2
3
  import { AuthenticatedPrincipalSchema, type ServiceAccountKind } from "../contracts/shared";
3
4
  import type { AccessSubject } from "../server";
5
+ import type { RequestAuthority } from "../server/middleware/auth";
4
6
  import {
5
7
  buildAccessPrincipalCondition,
6
8
  createAccess,
@@ -11,6 +13,8 @@ import {
11
13
  } from "../server/services/access";
12
14
  import { accessRevision } from "../server/services/access-revision";
13
15
  import { getRoles } from "../services/accounts/users";
16
+ import { usableAppIds } from "../services/app-access";
17
+ import { logger } from "../services/logging";
14
18
  import { toPgTextArray, toPgUuidArray } from "../services/postgres";
15
19
  import { mountAiSkillFilePath } from "./file-mount";
16
20
  import { aiProjects } from "./projects";
@@ -227,11 +231,29 @@ const getRowByName = async (name: string, db: SQL = sql): Promise<SkillRow | nul
227
231
  * Resolve before opening a transaction: role lookup uses its own pool connection, and a transaction that waits for a
228
232
  * second connection can exhaust the pool under concurrent requests.
229
233
  */
230
- const subjectRoles = async (subject: AccessSubject | null): Promise<string[]> =>
231
- subject?.type === "user" ? await getRoles(subject.userId) : [];
234
+ type SkillAudience = { roles: readonly string[]; usableAppIds: readonly string[]; declaredAppIds: readonly string[] };
235
+ const subjectRoles = async (subject: AccessSubject | null, authority: RequestAuthority | null = null): Promise<SkillAudience> => {
236
+ const actor = authority?.actor;
237
+ const roles = actor
238
+ ? actor.kind === "user"
239
+ ? actor.user.roles
240
+ : (actor.delegatedUser?.roles ?? [])
241
+ : subject?.type === "user"
242
+ ? await getRoles(subject.userId)
243
+ : [];
244
+ const apps = await listApps().catch(() => {
245
+ logger("ai:skills").warn("App registry unavailable; app Skills are unavailable");
246
+ return [];
247
+ });
248
+ return {
249
+ roles,
250
+ usableAppIds: [...(await usableAppIds(authority, apps))],
251
+ declaredAppIds: apps.filter((app) => app.access !== undefined).map((app) => app.id),
252
+ };
253
+ };
232
254
 
233
- // App availability applies to every grant. Only explicit skill grants bypass its audience.
234
- const effectivePermission = (skillId: SQLQuery, subject: AccessSubject | null, roles: readonly string[]) => sql`
255
+ // App access applies to every grant; the older navigation audience keeps its legacy grant behavior.
256
+ const effectivePermission = (skillId: SQLQuery, subject: AccessSubject | null, roles: SkillAudience) => sql`
235
257
  SELECT permission FROM (
236
258
  SELECT access.permission, (access.user_id IS NOT NULL OR access.group_id IS NOT NULL OR access.service_account_id IS NOT NULL) AS direct
237
259
  FROM ai.skill_access link JOIN auth.access access ON access.id=link.access_id
@@ -243,7 +265,7 @@ const effectivePermission = (skillId: SQLQuery, subject: AccessSubject | null, r
243
265
  WHERE link.skill_id=${skillId} AND access.permission <> 'none' AND ${accessMatch(subject)}
244
266
  ) grants WHERE NOT EXISTS (
245
267
  SELECT 1 FROM ai.app_skills app WHERE app.skill_id=${skillId}
246
- AND (NOT app.available OR (NOT grants.direct AND app.required_roles IS NOT NULL AND NOT(app.required_roles && ${toPgTextArray([...roles])}::text[])))
268
+ AND (NOT app.available OR NOT(app.app_id = ANY(${toPgTextArray([...roles.usableAppIds])}::text[])) OR (NOT (app.app_id = ANY(${toPgTextArray([...roles.declaredAppIds])}::text[])) AND NOT grants.direct AND app.required_roles IS NOT NULL AND NOT(app.required_roles && ${toPgTextArray([...roles.roles])}::text[])))
247
269
  ) ORDER BY CASE permission WHEN 'admin' THEN 3 WHEN 'write' THEN 2 WHEN 'read' THEN 1 ELSE 0 END DESC LIMIT 1
248
270
  `;
249
271
 
@@ -251,7 +273,7 @@ const permissionFor = async (
251
273
  skillId: string,
252
274
  subject: AccessSubject | null,
253
275
  db: SQL,
254
- roles: readonly string[],
276
+ roles: SkillAudience,
255
277
  ): Promise<AiSkillPermission | "none"> => {
256
278
  const [row] = await db<
257
279
  { permission: AiSkillPermission | null }[]
@@ -264,7 +286,7 @@ const canMutate = async (
264
286
  subject: AccessSubject | null,
265
287
  required: AiSkillPermission,
266
288
  db: SQL,
267
- roles: readonly string[],
289
+ roles: SkillAudience,
268
290
  ): Promise<boolean> => {
269
291
  const permission = await permissionFor(skillId, subject, db, roles);
270
292
  if (hasPermission(permission, required)) return true;
@@ -292,7 +314,7 @@ export const listReferences = async (skillId: string, db: SQL = sql): Promise<Ai
292
314
  ORDER BY path
293
315
  `;
294
316
 
295
- const toSkill = async (row: SkillRow, subject: AccessSubject | null, db: SQL, roles: readonly string[]): Promise<AiSkill | null> => {
317
+ const toSkill = async (row: SkillRow, subject: AccessSubject | null, db: SQL, roles: SkillAudience): Promise<AiSkill | null> => {
296
318
  const permission = await permissionFor(row.id, subject, db, roles);
297
319
  if (permission === "none") return null;
298
320
  const references = await listReferences(row.id, db);
@@ -324,7 +346,7 @@ const requireSkill = async (
324
346
  subject: AccessSubject | null,
325
347
  required: AiSkillPermission,
326
348
  db: SQL,
327
- roles: readonly string[],
349
+ roles: SkillAudience,
328
350
  ): Promise<AiSkill | null> => {
329
351
  if (!row) return null;
330
352
  const skill = await toSkill(row, subject, db, roles);
@@ -337,8 +359,7 @@ const listSkillAccess = async (skillId: string, db: SQL = sql): Promise<AiSkillA
337
359
  const rows = await db<SkillAccessRow[]>`
338
360
  SELECT skill_access.short_id, access.user_id, access.group_id, access.service_account_id, access.authenticated_only,
339
361
  access.permission, access.created_at,
340
- COALESCE(NULLIF(users.display_name, ''), users.uid, groups.name, service_accounts.name,
341
- CASE WHEN access.authenticated_only THEN 'All authenticated users' ELSE 'Public' END) AS display_name,
362
+ COALESCE(NULLIF(users.display_name, ''), users.uid, groups.name, service_accounts.name) AS display_name,
342
363
  users.avatar_hash, service_accounts.kind AS service_account_kind
343
364
  FROM ai.skill_access skill_access
344
365
  JOIN auth.access access ON access.id = skill_access.access_id
@@ -523,6 +544,7 @@ const skillSnapshotFiles = (skill: AiSkill): { path: string; content: string }[]
523
544
  ];
524
545
 
525
546
  type SkillListOptions = {
547
+ authority?: RequestAuthority | null;
526
548
  name?: string;
527
549
  /** Only these Skills, by short ID, such as the ones a person recently sent. */
528
550
  shortIds?: readonly string[];
@@ -539,7 +561,7 @@ async function listSkillSummaries(
539
561
  offset?: number;
540
562
  } = {},
541
563
  ): Promise<AiSkillSummary[]> {
542
- const roles = await subjectRoles(subject);
564
+ const roles = await subjectRoles(subject, options.authority);
543
565
  const userId = subject?.type === "user" ? subject.userId : null;
544
566
  const query = (options.query ?? "").trim().slice(0, 200);
545
567
  const limit = Math.max(1, Math.min(201, options.limit ?? 200));
@@ -599,11 +621,7 @@ async function linkedSkillProjects(skillId: string, subject: AccessSubject | nul
599
621
 
600
622
  export type AiConversationSkillUse = { name: string; description: string; turns: number; lastLoadedAt: string };
601
623
 
602
- const listSkillTurnFiles = async (
603
- turnId: string,
604
- subject: AccessSubject | null,
605
- roles: readonly string[],
606
- ): Promise<AiSkillFileToolStat[]> => {
624
+ const listSkillTurnFiles = async (turnId: string, subject: AccessSubject | null, roles: SkillAudience): Promise<AiSkillFileToolStat[]> => {
607
625
  const userId = subject?.type === "user" ? subject.userId : null;
608
626
  const rows = await sql<SnapshotRow[]>`
609
627
  SELECT DISTINCT snapshot.skill_name, snapshot.description, snapshot.revision, snapshot.instructions,
@@ -651,16 +669,19 @@ export const aiSkills = {
651
669
  }));
652
670
  },
653
671
 
654
- async create(input: {
655
- subject: AccessSubject;
656
- name: string;
657
- description: string;
658
- instructions: string;
659
- extraFrontmatter?: AiSkillExtraFrontmatter;
660
- references?: readonly AiSkillReferenceInput[];
661
- }): Promise<AiSkill> {
672
+ async create(
673
+ input: {
674
+ subject: AccessSubject;
675
+ name: string;
676
+ description: string;
677
+ instructions: string;
678
+ extraFrontmatter?: AiSkillExtraFrontmatter;
679
+ references?: readonly AiSkillReferenceInput[];
680
+ },
681
+ authority: RequestAuthority | null = null,
682
+ ): Promise<AiSkill> {
662
683
  const fields = validatedFields(input);
663
- const roles = await subjectRoles(input.subject);
684
+ const roles = await subjectRoles(input.subject, authority);
664
685
  return sql.begin(async (tx) => {
665
686
  const rows = await withAiShortIdForDb(
666
687
  tx,
@@ -692,12 +713,13 @@ export const aiSkills = {
692
713
  async projectSkills(
693
714
  projectId: string,
694
715
  subject: AccessSubject | null,
695
- options: { query?: string; page?: number; available?: boolean } = {},
716
+ options: { query?: string; page?: number; available?: boolean; authority?: RequestAuthority | null } = {},
696
717
  ) {
697
718
  if (!(await aiProjects.get(projectId, subject, options.available ? "admin" : "read"))) return null;
698
719
  const page = Math.max(1, Math.min(100000, Math.trunc(options.page ?? 1)));
699
720
  const skills = await listSkillSummaries(subject, {
700
721
  query: options.query,
722
+ authority: options.authority,
701
723
  limit: 31,
702
724
  offset: (page - 1) * 30,
703
725
  project: { id: projectId, linked: !options.available },
@@ -706,9 +728,15 @@ export const aiSkills = {
706
728
  return { items: skills.slice(0, 30), hasNext: skills.length > 30, page };
707
729
  },
708
730
 
709
- async linkProject(skillId: string, projectId: string, linked: boolean, subject: AccessSubject | null): Promise<boolean> {
731
+ async linkProject(
732
+ skillId: string,
733
+ projectId: string,
734
+ linked: boolean,
735
+ subject: AccessSubject | null,
736
+ authority: RequestAuthority | null = null,
737
+ ): Promise<boolean> {
710
738
  if (!(await aiProjects.get(projectId, subject, "admin"))) return false;
711
- const roles = await subjectRoles(subject);
739
+ const roles = await subjectRoles(subject, authority);
712
740
  return sql.begin(async (tx) => {
713
741
  const [row] = await tx`SELECT id FROM ai.skills WHERE id=${skillId}::uuid FOR UPDATE`;
714
742
  if (!row || !(await canMutate(skillId, subject, "admin", tx, roles))) return false;
@@ -719,8 +747,8 @@ export const aiSkills = {
719
747
  });
720
748
  },
721
749
 
722
- async linkedProjects(skillId: string, subject: AccessSubject | null) {
723
- if (!(await this.get(skillId, subject, "admin"))) return null;
750
+ async linkedProjects(skillId: string, subject: AccessSubject | null, authority: RequestAuthority | null = null) {
751
+ if (!(await this.get(skillId, subject, "admin", authority))) return null;
724
752
  return linkedSkillProjects(skillId, subject);
725
753
  },
726
754
 
@@ -728,23 +756,38 @@ export const aiSkills = {
728
756
  subject: AccessSubject | null,
729
757
  query: string,
730
758
  limit = 30,
731
- options: { modelInvocableOnly?: boolean } = {},
759
+ options: { modelInvocableOnly?: boolean; authority?: RequestAuthority | null } = {},
732
760
  ): Promise<{ skills: AiSkillSummary[]; more: boolean }> {
733
761
  const size = Math.max(1, Math.min(200, limit));
734
762
  const skills = await aiSkills.list(subject, { ...options, query, enabledOnly: true, limit: size + 1 });
735
763
  return { skills: skills.slice(0, size), more: skills.length > size };
736
764
  },
737
765
 
738
- async get(skillId: string, subject: AccessSubject | null, required: AiSkillPermission = "read"): Promise<AiSkill | null> {
739
- return requireSkill(await getRow(skillId), subject, required, sql, await subjectRoles(subject));
766
+ async get(
767
+ skillId: string,
768
+ subject: AccessSubject | null,
769
+ required: AiSkillPermission = "read",
770
+ authority: RequestAuthority | null = null,
771
+ ): Promise<AiSkill | null> {
772
+ return requireSkill(await getRow(skillId), subject, required, sql, await subjectRoles(subject, authority));
740
773
  },
741
774
 
742
- async getByShortId(shortId: string, subject: AccessSubject | null, required: AiSkillPermission = "read"): Promise<AiSkill | null> {
743
- return requireSkill(await getRowByShortId(shortId), subject, required, sql, await subjectRoles(subject));
775
+ async getByShortId(
776
+ shortId: string,
777
+ subject: AccessSubject | null,
778
+ required: AiSkillPermission = "read",
779
+ authority: RequestAuthority | null = null,
780
+ ): Promise<AiSkill | null> {
781
+ return requireSkill(await getRowByShortId(shortId), subject, required, sql, await subjectRoles(subject, authority));
744
782
  },
745
783
 
746
- async getByName(name: string, subject: AccessSubject | null, required: AiSkillPermission = "read"): Promise<AiSkill | null> {
747
- return requireSkill(await getRowByName(name), subject, required, sql, await subjectRoles(subject));
784
+ async getByName(
785
+ name: string,
786
+ subject: AccessSubject | null,
787
+ required: AiSkillPermission = "read",
788
+ authority: RequestAuthority | null = null,
789
+ ): Promise<AiSkill | null> {
790
+ return requireSkill(await getRowByName(name), subject, required, sql, await subjectRoles(subject, authority));
748
791
  },
749
792
 
750
793
  async update(
@@ -758,9 +801,10 @@ export const aiSkills = {
758
801
  extraFrontmatter?: AiSkillExtraFrontmatter;
759
802
  references?: readonly AiSkillReferenceInput[];
760
803
  },
804
+ authority: RequestAuthority | null = null,
761
805
  ): Promise<AiSkill | null> {
762
806
  const fields = validatedFields(input);
763
- const roles = await subjectRoles(subject);
807
+ const roles = await subjectRoles(subject, authority);
764
808
  return sql.begin(async (tx) => {
765
809
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
766
810
  if (!row || !(await canMutate(row.id, subject, "write", tx, roles))) return null;
@@ -784,8 +828,8 @@ export const aiSkills = {
784
828
  });
785
829
  },
786
830
 
787
- async delete(skillId: string, subject: AccessSubject | null): Promise<boolean> {
788
- const roles = await subjectRoles(subject);
831
+ async delete(skillId: string, subject: AccessSubject | null, authority: RequestAuthority | null = null): Promise<boolean> {
832
+ const roles = await subjectRoles(subject, authority);
789
833
  return sql.begin(async (tx) => {
790
834
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
791
835
  if (!row || !(await canMutate(row.id, subject, "admin", tx, roles))) return false;
@@ -798,8 +842,12 @@ export const aiSkills = {
798
842
  });
799
843
  },
800
844
 
801
- async listAccess(skillId: string, subject: AccessSubject | null): Promise<AiSkillAccess[] | null> {
802
- const roles = await subjectRoles(subject);
845
+ async listAccess(
846
+ skillId: string,
847
+ subject: AccessSubject | null,
848
+ authority: RequestAuthority | null = null,
849
+ ): Promise<AiSkillAccess[] | null> {
850
+ const roles = await subjectRoles(subject, authority);
803
851
  return sql.begin(async (tx) => {
804
852
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id=${skillId}::uuid FOR UPDATE`;
805
853
  if (!row || !(await canMutate(row.id, subject, "admin", tx, roles))) return null;
@@ -812,8 +860,9 @@ export const aiSkills = {
812
860
  subject: AccessSubject | null,
813
861
  input: { principal: Principal; permission: AiSkillPermission },
814
862
  expectedAccessRevision?: string,
863
+ authority: RequestAuthority | null = null,
815
864
  ): Promise<AiSkillAccess | null> {
816
- const roles = await subjectRoles(subject);
865
+ const roles = await subjectRoles(subject, authority);
817
866
  return sql.begin(async (tx) => {
818
867
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
819
868
  if (!row || !(await canMutate(row.id, subject, "admin", tx, roles))) return null;
@@ -828,8 +877,9 @@ export const aiSkills = {
828
877
  subject: AccessSubject | null,
829
878
  permission: AiSkillPermission,
830
879
  expectedAccessRevision?: string,
880
+ authority: RequestAuthority | null = null,
831
881
  ): Promise<boolean> {
832
- const roles = await subjectRoles(subject);
882
+ const roles = await subjectRoles(subject, authority);
833
883
  return sql.begin(async (tx) => {
834
884
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
835
885
  if (!row || !(await canMutate(row.id, subject, "admin", tx, roles))) return false;
@@ -838,8 +888,14 @@ export const aiSkills = {
838
888
  });
839
889
  },
840
890
 
841
- async revokeAccess(skillId: string, accessId: string, subject: AccessSubject | null, expectedAccessRevision?: string): Promise<boolean> {
842
- const roles = await subjectRoles(subject);
891
+ async revokeAccess(
892
+ skillId: string,
893
+ accessId: string,
894
+ subject: AccessSubject | null,
895
+ expectedAccessRevision?: string,
896
+ authority: RequestAuthority | null = null,
897
+ ): Promise<boolean> {
898
+ const roles = await subjectRoles(subject, authority);
843
899
  return sql.begin(async (tx) => {
844
900
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
845
901
  if (!row || !(await canMutate(row.id, subject, "admin", tx, roles))) return false;
@@ -974,9 +1030,14 @@ export const aiSkills = {
974
1030
  },
975
1031
  },
976
1032
 
977
- async setEnabled(skillId: string, subject: AccessSubject | null, enabled: boolean): Promise<boolean | null> {
1033
+ async setEnabled(
1034
+ skillId: string,
1035
+ subject: AccessSubject | null,
1036
+ enabled: boolean,
1037
+ authority: RequestAuthority | null = null,
1038
+ ): Promise<boolean | null> {
978
1039
  if (subject?.type !== "user") return null;
979
- const roles = await subjectRoles(subject);
1040
+ const roles = await subjectRoles(subject, authority);
980
1041
  return sql.begin(async (tx) => {
981
1042
  if (!(await requireSkill(await getRow(skillId, tx), subject, "read", tx, roles))) return null;
982
1043
  if (enabled) {
@@ -999,21 +1060,28 @@ export const aiSkills = {
999
1060
  skillId: string,
1000
1061
  subject: AccessSubject | null,
1001
1062
  input: { expectedRevision: number; path: string; content: string },
1063
+ authority: RequestAuthority | null = null,
1002
1064
  ): Promise<AiSkill | null> {
1003
- return this.setReferences(skillId, subject, {
1004
- expectedRevision: input.expectedRevision,
1005
- references: [{ path: input.path, content: input.content }],
1006
- });
1065
+ return this.setReferences(
1066
+ skillId,
1067
+ subject,
1068
+ {
1069
+ expectedRevision: input.expectedRevision,
1070
+ references: [{ path: input.path, content: input.content }],
1071
+ },
1072
+ authority,
1073
+ );
1007
1074
  },
1008
1075
 
1009
1076
  async setReferences(
1010
1077
  skillId: string,
1011
1078
  subject: AccessSubject | null,
1012
1079
  input: { expectedRevision: number; references: readonly AiSkillReferenceInput[] },
1080
+ authority: RequestAuthority | null = null,
1013
1081
  ): Promise<AiSkill | null> {
1014
1082
  if (input.references.length === 0) throw new AiSkillInputError("Set at least one Skill reference.");
1015
1083
  const references = validateAiSkillReferences(input.references);
1016
- const roles = await subjectRoles(subject);
1084
+ const roles = await subjectRoles(subject, authority);
1017
1085
  return sql.begin(async (tx) => {
1018
1086
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
1019
1087
  if (!row || !(await canMutate(row.id, subject, "write", tx, roles))) return null;
@@ -1043,9 +1111,10 @@ export const aiSkills = {
1043
1111
  skillId: string,
1044
1112
  subject: AccessSubject | null,
1045
1113
  input: { expectedRevision: number; path: string },
1114
+ authority: RequestAuthority | null = null,
1046
1115
  ): Promise<AiSkill | null> {
1047
1116
  const [reference] = validateAiSkillReferences([{ path: input.path, content: "" }]);
1048
- const roles = await subjectRoles(subject);
1117
+ const roles = await subjectRoles(subject, authority);
1049
1118
  return sql.begin(async (tx) => {
1050
1119
  const [row] = await tx<SkillRow[]>`SELECT * FROM ai.skills WHERE id = ${skillId}::uuid FOR UPDATE`;
1051
1120
  if (!row || !(await canMutate(row.id, subject, "write", tx, roles))) return null;
@@ -1067,11 +1136,12 @@ export const aiSkills = {
1067
1136
  turnId: string,
1068
1137
  selector: string | { id: string },
1069
1138
  subject: AccessSubject | null,
1139
+ authority: RequestAuthority | null = null,
1070
1140
  ): Promise<AiLoadedSkillSnapshot | null> {
1071
1141
  const name = typeof selector === "string" ? validateAiSkillName(selector) : null;
1072
1142
  const shortId = typeof selector === "string" ? null : selector.id;
1073
1143
  if (shortId !== null && !AI_SHORT_ID_PATTERN.test(shortId)) throw new AiSkillInputError("Invalid Skill ID.");
1074
- const roles = await subjectRoles(subject);
1144
+ const roles = await subjectRoles(subject, authority);
1075
1145
  return sql.begin(async (tx) => {
1076
1146
  const [row] = await tx<SkillRow[]>`
1077
1147
  SELECT * FROM ai.skills WHERE name = ${name} OR short_id = ${shortId} FOR SHARE
@@ -1109,12 +1179,21 @@ export const aiSkills = {
1109
1179
  });
1110
1180
  },
1111
1181
 
1112
- async listTurnFiles(turnId: string, subject: AccessSubject | null): Promise<AiSkillFileToolStat[]> {
1113
- return listSkillTurnFiles(turnId, subject, await subjectRoles(subject));
1182
+ async listTurnFiles(
1183
+ turnId: string,
1184
+ subject: AccessSubject | null,
1185
+ authority: RequestAuthority | null = null,
1186
+ ): Promise<AiSkillFileToolStat[]> {
1187
+ return listSkillTurnFiles(turnId, subject, await subjectRoles(subject, authority));
1114
1188
  },
1115
1189
 
1116
- async readTurnFile(turnId: string, path: string, subject: AccessSubject | null): Promise<AiSkillFileToolContent | null> {
1117
- const roles = await subjectRoles(subject);
1190
+ async readTurnFile(
1191
+ turnId: string,
1192
+ path: string,
1193
+ subject: AccessSubject | null,
1194
+ authority: RequestAuthority | null = null,
1195
+ ): Promise<AiSkillFileToolContent | null> {
1196
+ const roles = await subjectRoles(subject, authority);
1118
1197
  const files = await listSkillTurnFiles(turnId, subject, roles);
1119
1198
  const stat = files.find((file) => file.path === path);
1120
1199
  if (!stat) return null;