@k2b/cloud 0.40.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.
- package/package.json +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- 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
|
-
|
|
231
|
-
|
|
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
|
|
234
|
-
const effectivePermission = (skillId: SQLQuery, subject: AccessSubject | null, roles:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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(
|
|
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(
|
|
739
|
-
|
|
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(
|
|
743
|
-
|
|
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(
|
|
747
|
-
|
|
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(
|
|
802
|
-
|
|
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(
|
|
842
|
-
|
|
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(
|
|
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(
|
|
1004
|
-
|
|
1005
|
-
|
|
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(
|
|
1113
|
-
|
|
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(
|
|
1117
|
-
|
|
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;
|