@k2b/cloud 0.6.0-rc.0 → 0.7.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/README.md +2 -2
- package/package.json +30 -17
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/solid.tsx +3 -3
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/scripts/app-favicon.test.ts +0 -49
- package/src/ai/chat/blocks.render.test.tsx +0 -1108
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
- package/src/ssr/Layout.render.test.tsx +0 -148
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ai/settings.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AiModelPricingSchema } from "../shared/ai-costs";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { coreSettings } from "../services";
|
|
3
4
|
import { getAiCredential, listAiCredentialProfileIds } from "./credentials";
|
|
@@ -94,10 +95,22 @@ const ModelProfileSchema = z
|
|
|
94
95
|
maxOutputTokens: z.number().int().positive().optional(),
|
|
95
96
|
maxLoadedTools: z.number().int().optional(),
|
|
96
97
|
maxToolRounds: z.number().int().optional(),
|
|
97
|
-
|
|
98
|
-
creditsPerOutputToken: z.number().nonnegative().optional(),
|
|
98
|
+
pricing: AiModelPricingSchema.optional(),
|
|
99
99
|
})
|
|
100
100
|
.superRefine((profile, ctx) => {
|
|
101
|
+
if (profile.capabilities?.includes("transcription")) {
|
|
102
|
+
if (profile.pricing) ctx.addIssue({ code: "custom", path: ["pricing"], message: "Audio pricing is not supported." });
|
|
103
|
+
if (profile.capabilities.some((capability) => capability !== "transcription")) {
|
|
104
|
+
ctx.addIssue({ code: "custom", path: ["capabilities"], message: "Audio transcription cannot be combined with chat capabilities." });
|
|
105
|
+
}
|
|
106
|
+
if (profile.provider !== "openai" && profile.provider !== "openai-compatible") {
|
|
107
|
+
ctx.addIssue({
|
|
108
|
+
code: "custom",
|
|
109
|
+
path: ["provider"],
|
|
110
|
+
message: "Audio transcription requires OpenAI or an OpenAI-compatible endpoint.",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
101
114
|
if (profile.provider === "openai-compatible" && !profile.baseURL) {
|
|
102
115
|
ctx.addIssue({ code: "custom", path: ["baseURL"], message: "OpenAI-compatible profiles require a base URL." });
|
|
103
116
|
}
|
|
@@ -177,6 +190,7 @@ export const validateAiSettingsConfiguration = (input: {
|
|
|
177
190
|
defaultModelId: string;
|
|
178
191
|
backgroundModelId: string;
|
|
179
192
|
visionModelId?: string;
|
|
193
|
+
audioModelId?: string;
|
|
180
194
|
workflowModelId: string;
|
|
181
195
|
profiles: readonly AiModelProfile[];
|
|
182
196
|
credentialProfileIds: readonly string[];
|
|
@@ -186,13 +200,14 @@ export const validateAiSettingsConfiguration = (input: {
|
|
|
186
200
|
const errors: Record<string, string> = {};
|
|
187
201
|
const enabledProfiles = input.profiles.filter((profile) => profile.enabled);
|
|
188
202
|
const defaultProfile = input.profiles.find((profile) => profile.id === input.defaultModelId);
|
|
189
|
-
if (!input.defaultModelId || !defaultProfile?.enabled) {
|
|
203
|
+
if (!input.defaultModelId || !defaultProfile?.enabled || defaultProfile.capabilities.includes("transcription")) {
|
|
190
204
|
errors["ai.default_model_id"] = "Choose an enabled model profile.";
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
if (input.backgroundModelId) {
|
|
194
208
|
const backgroundProfile = input.profiles.find((profile) => profile.id === input.backgroundModelId);
|
|
195
|
-
if (!backgroundProfile?.enabled
|
|
209
|
+
if (!backgroundProfile?.enabled || backgroundProfile.capabilities.includes("transcription"))
|
|
210
|
+
errors["ai.background_model_id"] = "Choose an enabled chat model profile or use the platform default.";
|
|
196
211
|
}
|
|
197
212
|
|
|
198
213
|
if (input.visionModelId) {
|
|
@@ -204,7 +219,15 @@ export const validateAiSettingsConfiguration = (input: {
|
|
|
204
219
|
|
|
205
220
|
if (input.workflowModelId) {
|
|
206
221
|
const workflowProfile = input.profiles.find((profile) => profile.id === input.workflowModelId);
|
|
207
|
-
if (!workflowProfile?.enabled
|
|
222
|
+
if (!workflowProfile?.enabled || workflowProfile.capabilities.includes("transcription"))
|
|
223
|
+
errors["ai.workflow_model_id"] = "Choose an enabled chat model profile or use the background model.";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (input.audioModelId) {
|
|
227
|
+
const audioProfile = input.profiles.find((profile) => profile.id === input.audioModelId);
|
|
228
|
+
if (!audioProfile?.enabled || !audioProfile.capabilities.includes("transcription")) {
|
|
229
|
+
errors["ai.audio_model_id"] = "Choose an enabled audio transcription profile or disable audio transcription.";
|
|
230
|
+
}
|
|
208
231
|
}
|
|
209
232
|
|
|
210
233
|
const configured = new Set(input.credentialProfileIds);
|
|
@@ -301,7 +324,7 @@ export const resolveAiSettingsStateFromRaw = async (input: {
|
|
|
301
324
|
};
|
|
302
325
|
}
|
|
303
326
|
|
|
304
|
-
if (!defaultProfile.enabled) {
|
|
327
|
+
if (!defaultProfile.enabled || defaultProfile.capabilities.includes("transcription")) {
|
|
305
328
|
return {
|
|
306
329
|
ok: false,
|
|
307
330
|
...baseState,
|
|
@@ -309,7 +332,7 @@ export const resolveAiSettingsStateFromRaw = async (input: {
|
|
|
309
332
|
profiles: parsed.profiles,
|
|
310
333
|
error: {
|
|
311
334
|
code: "default_model_disabled",
|
|
312
|
-
message: `Default AI model "${input.defaultModelId}"
|
|
335
|
+
message: `Default AI model "${input.defaultModelId}" must be an enabled text model.`,
|
|
313
336
|
fields: { "ai.default_model_id": "Choose an enabled model profile id." },
|
|
314
337
|
},
|
|
315
338
|
};
|
|
@@ -398,6 +421,7 @@ const hasAll = <T extends string>(values: readonly T[], required: readonly T[] |
|
|
|
398
421
|
|
|
399
422
|
const matchesPolicy = (profile: AiModelProfile, policy: AiModelPolicy): boolean => {
|
|
400
423
|
if (!profile.enabled) return false;
|
|
424
|
+
if (profile.capabilities.includes("transcription") !== Boolean(policy.requiredCapabilities?.includes("transcription"))) return false;
|
|
401
425
|
if ("allowedModelIds" in policy && policy.allowedModelIds && !policy.allowedModelIds.includes(profile.id)) return false;
|
|
402
426
|
if (policy.allowedDataBoundaries && !policy.allowedDataBoundaries.includes(profile.dataBoundary)) return false;
|
|
403
427
|
return hasAll(profile.capabilities, policy.requiredCapabilities);
|
|
@@ -521,7 +545,11 @@ export const toPublicAiSettingsState = async (allowedDataBoundaries?: AiDataBoun
|
|
|
521
545
|
firecrawlConfigured: state.firecrawlConfigured,
|
|
522
546
|
models:
|
|
523
547
|
state.ok && state.enabled
|
|
524
|
-
? state.profiles
|
|
548
|
+
? state.profiles
|
|
549
|
+
.filter(
|
|
550
|
+
(profile) => matchesPolicy(profile, { kind: "selectable", allowedDataBoundaries }) && isUsableProfile(profile, configured),
|
|
551
|
+
)
|
|
552
|
+
.map(profileToPublic)
|
|
525
553
|
: [],
|
|
526
554
|
};
|
|
527
555
|
};
|
package/src/ai/short-id.ts
CHANGED
|
@@ -14,11 +14,11 @@ export const withAiShortId = async <T>(
|
|
|
14
14
|
try {
|
|
15
15
|
return await insert(allocate());
|
|
16
16
|
} catch (error) {
|
|
17
|
+
const uniqueViolation = typeof error === "object" && error !== null && (("code" in error && error.code === "23505") || ("errno" in error && error.errno === "23505"));
|
|
17
18
|
const collision =
|
|
18
19
|
typeof error === "object" &&
|
|
19
20
|
error !== null &&
|
|
20
|
-
|
|
21
|
-
error.code === "23505" &&
|
|
21
|
+
uniqueViolation &&
|
|
22
22
|
"constraint" in error &&
|
|
23
23
|
error.constraint === constraint;
|
|
24
24
|
if (!collision) throw error;
|
package/src/ai/skill-catalog.ts
CHANGED
|
@@ -60,14 +60,4 @@ export const selectAiSkillCatalog = (
|
|
|
60
60
|
return { skills: selected, omitted: normalized.length - selected.length };
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
export const searchAiSkillCatalog = (
|
|
64
|
-
skills: readonly AiSkillCatalogEntry[],
|
|
65
|
-
query: string,
|
|
66
|
-
limit: number,
|
|
67
|
-
): { skills: AiSkillCatalogEntry[]; more: boolean } => {
|
|
68
|
-
const normalized = skills.map((skill) => ({ name: skill.name, description: skill.description }));
|
|
69
|
-
const matches = ranked(normalized, query).filter((skill) => relevance(skill, query) > 0);
|
|
70
|
-
return { skills: matches.slice(0, limit), more: matches.length > limit };
|
|
71
|
-
};
|
|
72
|
-
|
|
73
63
|
export const aiSkillCatalogChars = (skills: readonly AiSkillCatalogEntry[]): number => skills.map(catalogLine).join("\n").length;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { toPgTextArray } from "../services/postgres";
|
|
3
|
+
|
|
4
|
+
const BM25_INDEX = "ai.skills_search_bm25_idx";
|
|
5
|
+
let bm25Available: Promise<boolean> | undefined;
|
|
6
|
+
|
|
7
|
+
/** BM25 only ranks candidates; native matching and access checks always apply. */
|
|
8
|
+
export async function withAiSkillSearch<T>(query: string, run: (bm25: boolean) => Promise<T>): Promise<T> {
|
|
9
|
+
if (!query) return run(false);
|
|
10
|
+
bm25Available ??= sql<{ available: boolean }[]>`
|
|
11
|
+
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_textsearch')
|
|
12
|
+
AND EXISTS (SELECT 1 FROM pg_class c JOIN pg_am am ON am.oid = c.relam
|
|
13
|
+
WHERE c.oid = to_regclass(${BM25_INDEX}) AND am.amname = 'bm25') AS available
|
|
14
|
+
`.then(rows => rows[0]?.available ?? false);
|
|
15
|
+
const bm25 = await bm25Available;
|
|
16
|
+
try {
|
|
17
|
+
return await run(bm25);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const capabilityLost = typeof error === "object" && error !== null &&
|
|
20
|
+
["code", "errno"].some(key => key in error &&
|
|
21
|
+
["0A000", "42704", "42883", "55000"].includes(String(Reflect.get(error, key))));
|
|
22
|
+
if (!bm25 || !capabilityLost) throw error;
|
|
23
|
+
bm25Available = Promise.resolve(false);
|
|
24
|
+
return run(false);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function aiSkillSearchSql(query: string, bm25: boolean) {
|
|
29
|
+
const terms = [...new Set(query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])];
|
|
30
|
+
// Match every term in either field. Short prefixes stay exact; longer terms
|
|
31
|
+
// tolerate typos without matching unrelated short words throughout the catalog.
|
|
32
|
+
const matches = !query ? sql`TRUE` : !terms.length ? sql`FALSE` : sql`
|
|
33
|
+
NOT EXISTS (SELECT 1 FROM unnest(${toPgTextArray(terms)}::text[]) AS term
|
|
34
|
+
WHERE strpos(lower(skill.name), term) = 0 AND strpos(lower(skill.description), term) = 0
|
|
35
|
+
AND (length(term) < 4 OR greatest(word_similarity(term, skill.name),
|
|
36
|
+
word_similarity(term, skill.description)) < 0.35))
|
|
37
|
+
`;
|
|
38
|
+
const rank = !query ? sql`0::real` : sql`
|
|
39
|
+
CASE WHEN lower(skill.name) = ${query.toLowerCase()} THEN 100 ELSE 0 END
|
|
40
|
+
+ CASE WHEN strpos(lower(skill.name), ${query.toLowerCase()}) > 0 THEN 10 ELSE 0 END
|
|
41
|
+
+ 2 * word_similarity(${query}, skill.name) + word_similarity(${query}, skill.description)
|
|
42
|
+
`;
|
|
43
|
+
const textRank = !query ? sql`0::real` : bm25
|
|
44
|
+
? sql`-(skill.search_text <@> to_bm25query(${query}, ${BM25_INDEX}))`
|
|
45
|
+
: sql`ts_rank_cd(to_tsvector('simple', skill.search_text), plainto_tsquery('simple', ${query}))`;
|
|
46
|
+
return { matches, rank, textRank };
|
|
47
|
+
}
|
package/src/ai/skill-seeds.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ASSISTANT_DATA_ANALYSIS_SKILL } from "./data-analysis-skill";
|
|
2
|
+
import { ASSISTANT_CODE_MODE_SKILL } from "./code-mode-skill";
|
|
2
3
|
import { CLOUD_GRIDS_INSTRUCTIONS, CLOUD_GRIDS_QUERY_REFERENCE } from "./grids-skill";
|
|
3
|
-
import {
|
|
4
|
+
import type { AiSkillTemplate } from "./skills";
|
|
4
5
|
|
|
5
6
|
const SKILL_CREATOR_INSTRUCTIONS = `# Create and improve Skills
|
|
6
7
|
|
|
@@ -81,7 +82,15 @@ Pass the exact current revision to mutations and read again after a revision con
|
|
|
81
82
|
|
|
82
83
|
Creating, changing, and deleting Skills are reviewed mutations. Prepare the concrete content the user requested, then use the Capability review instead of asking for an extra confirmation that duplicates it.
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
## Optional App-backed workflows
|
|
86
|
+
|
|
87
|
+
A Skill can reference a reusable Studio App and its published actions instead of duplicating code. Load \`assistant-code-mode\` only when building, changing, or discovering such an App is useful. Apps may be stateless scripts, agent-only services with shared data, dashboards, or a combination. Many Skills need no code and many Apps need no Skill.
|
|
88
|
+
|
|
89
|
+
When referring to an App, include its exact ID, action names, input/output meaning, and a concrete workflow example. Read the action schemas instead of guessing APIs. Skill access and App access are independent: warn when intended recipients can use only one; never grant access implicitly.
|
|
90
|
+
|
|
91
|
+
For requested sharing, discover \`core.entities.search\` to resolve a recipient, then \`core.ai.skill.access.read\` and \`core.ai.skill.access.change\`. Read current grants and pass their exact accessRevision as expectedAccessRevision. Supply either a returned principal and permission (read/write/admin), or an existing accessId and permission (null revokes). Every mutation receives fresh review and preserves the last administrator. After a conflict, re-read and prepare a new review. For App permissions, read the Code Mode access reference; App Use/Manage is separate from Skill Read/Edit/Manage.
|
|
92
|
+
|
|
93
|
+
Imports and exports are not available through these capabilities. Do not invent a tool or bypass Cloud permissions.`;
|
|
85
94
|
|
|
86
95
|
const CLOUD_MAIL_INSTRUCTIONS = `# Work with Cloud Mail
|
|
87
96
|
|
|
@@ -196,7 +205,7 @@ Use these defaults unless the user asks otherwise or a more specific loaded Skil
|
|
|
196
205
|
- Simple subtasks use \`spaces.task.checklist.list\`, \`spaces.task.checklist.create\`, \`spaces.task.checklist.update\` and \`spaces.task.checklist.delete\`. Keep each entry to a label and completion state; do not turn checklist entries into independent assigned tasks.
|
|
197
206
|
- Use comments for discussion; read before replacing or deleting them. For tag replacement, preserve tags not included in the requested change.
|
|
198
207
|
- Use \`spaces.item.reference.find\` to find existing items for a source resource. Link on creation through references rather than a redundant second mutation. For an existing item use \`spaces.item.reference.add\`; use \`spaces.item.link-candidate.search\` when a writable target is unknown.
|
|
199
|
-
- Use \`spaces.event.create
|
|
208
|
+
- Use \`spaces.event.create\` for interactive creation. Never blindly retry an uncertain mutation.
|
|
200
209
|
|
|
201
210
|
## Calendar mail and cross-app work
|
|
202
211
|
|
|
@@ -237,7 +246,7 @@ Use these defaults unless the user asks otherwise or a more specific loaded Skil
|
|
|
237
246
|
|
|
238
247
|
- Conversations: \`core.ai.chats.search\`, \`core.ai.chat.read\`, \`core.ai.chat.search\`, \`core.ai.chat.resources\`, and \`core.ai.chats.resources\`.
|
|
239
248
|
- Messaging: \`core.ai.chat.message\`.
|
|
240
|
-
|
|
249
|
+
|
|
241
250
|
|
|
242
251
|
Load only the needed capabilities and reuse returned typed IDs unchanged.
|
|
243
252
|
|
|
@@ -246,9 +255,75 @@ Load only the needed capabilities and reuse returned typed IDs unchanged.
|
|
|
246
255
|
- The runtime Chat ID identifies the current conversation. Use \`core.ai.chat.search\` for earlier content in it. For another conversation, use \`core.ai.chats.search\`, then \`core.ai.chat.read\` or \`core.ai.chat.search\` with the returned ID.
|
|
247
256
|
- Use \`core.ai.chat.resources\` for resources from one known conversation and \`core.ai.chats.resources\` to search across conversations. Read a returned resource through its owning app rather than guessing its contents.
|
|
248
257
|
- Before \`core.ai.chat.message\`, identify the exact target and message. Report queued or delivered status accurately and do not claim the target completed the requested work.
|
|
249
|
-
- For reminders
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
- For one-time reminders, recurring background work, or changing and repairing a scheduled task, load the \`scheduled-tasks\` Skill.`;
|
|
259
|
+
|
|
260
|
+
const SCHEDULED_TASKS_INSTRUCTIONS = `# Schedule reminders and background work
|
|
261
|
+
|
|
262
|
+
Use this Skill to create or manage a task that runs later, once or repeatedly.
|
|
263
|
+
Use the current conversation unless the user names another owned chat. A simple
|
|
264
|
+
reminder is a valid task: its result is a normal message in that chat.
|
|
265
|
+
|
|
266
|
+
## Load the tools for the job
|
|
267
|
+
|
|
268
|
+
- Create: \`core.ai.task.create\`.
|
|
269
|
+
- Find and inspect: \`core.ai.tasks.list\`, then \`core.ai.task.read\`.
|
|
270
|
+
- Change instructions, schedule or grants: \`core.ai.task.update\`.
|
|
271
|
+
- Control an existing task: \`core.ai.task.pause\`, \`core.ai.task.resume\`,
|
|
272
|
+
\`core.ai.task.run\`, or \`core.ai.task.delete\`, as requested.
|
|
273
|
+
- Diagnose an occurrence: \`core.ai.task.run.read\`, using the task and occurrence
|
|
274
|
+
IDs returned by task.read.
|
|
275
|
+
|
|
276
|
+
Discover and load only the needed capabilities. Read their current input schemas;
|
|
277
|
+
reuse returned IDs. Read an existing task before changing it. If the request may
|
|
278
|
+
refer to an existing schedule, list tasks before creating a duplicate.
|
|
279
|
+
|
|
280
|
+
## Prepare the task
|
|
281
|
+
|
|
282
|
+
1. Resolve the user's timing with the exact runtime timezone (IANA). For a
|
|
283
|
+
one-time reminder or delayed job use kind once and localAt; for recurrence use
|
|
284
|
+
kind cron and cron. Resolve relative wording against the current runtime date
|
|
285
|
+
and time. Ask only if a missing time, timezone or target changes the outcome.
|
|
286
|
+
2. Write a self-contained prompt with the intended result, verified resource IDs
|
|
287
|
+
and relevant context. Use normal language for results and problems, without a
|
|
288
|
+
fixed return schema. A reminder prompt says what to remind the user about;
|
|
289
|
+
it needs no app capabilities merely to post its result to this chat.
|
|
290
|
+
3. For work across apps, load the relevant domain Skills and discover every
|
|
291
|
+
capability needed, including searches and reads before writes. Propose grants
|
|
292
|
+
for those capabilities. Choose fixedInput fields from their real schemas:
|
|
293
|
+
constrain a specific note when the task targets it; allow a whole notebook or
|
|
294
|
+
unrestricted inputs when that is the intended scope. Empty fixedInput allows
|
|
295
|
+
any inputs within current user access. Do not invent resource IDs or claim a
|
|
296
|
+
notebook constraint exists on an action that accepts only a note ID.
|
|
297
|
+
4. Explain the scope through the normal create/update review. Grants authorize
|
|
298
|
+
this task's capability use, not new access to resources. Always-approval
|
|
299
|
+
capabilities cannot be granted. Interactive browser and Code Mode are not
|
|
300
|
+
available in background runs; explain an unsupported requirement rather than
|
|
301
|
+
promising it will work unattended.
|
|
302
|
+
5. Create the task. Confirm its actual saved timing, timezone and destination only
|
|
303
|
+
after success. Queued or manually started does not mean completed.
|
|
304
|
+
|
|
305
|
+
Examples: “Remind me tomorrow at 09:00 to call Anna” is one once task with no
|
|
306
|
+
capability grants. “Every weekday summarize my mail, compare it with calendar X,
|
|
307
|
+
and update note Y” is a recurring task with discovered read and write grants,
|
|
308
|
+
using verified calendar and note IDs where the respective schemas allow them.
|
|
309
|
+
|
|
310
|
+
## Changes, errors and missing rights
|
|
311
|
+
|
|
312
|
+
Read the task and relevant failed run before proposing a repair. Distinguish
|
|
313
|
+
missing task grants from lost resource access and ordinary execution errors.
|
|
314
|
+
Update the same task's prompt, future schedule or grants; do not recreate it.
|
|
315
|
+
Omitted grants remain unchanged; supplied grants replace the complete list.
|
|
316
|
+
Expanded authority needs the normal user review and cannot restore lost resource
|
|
317
|
+
access. Check writes already performed before retrying with task.run to avoid
|
|
318
|
+
duplicates; use task.resume when the user wants future scheduled runs restored.
|
|
319
|
+
|
|
320
|
+
Each run works independently from a snapshot of the chat history at its start.
|
|
321
|
+
Available memories, Skills, Project context and chat files remain accessible
|
|
322
|
+
under runtime rules; files are live, not frozen copies. The run returns its result
|
|
323
|
+
or problem to the parent chat, reopening it and updating activity. The user can
|
|
324
|
+
keep chatting meanwhile. Do not load this management Skill on every background
|
|
325
|
+
run unless managing a schedule is itself needed; use the domain Skills for the
|
|
326
|
+
actual work.`;
|
|
252
327
|
|
|
253
328
|
const CLOUD_NOTEBOOKS_REFERENCE = `## Flexible data and automatic page lists
|
|
254
329
|
|
|
@@ -351,13 +426,11 @@ Mail attachment inputs require base64: encode the exact returned calendar string
|
|
|
351
426
|
|
|
352
427
|
Do not fabricate organizer/attendee addresses, regenerate the returned calendar text, commit after a failed attachment, or retry an unknown mutation result without reconciliation.`;
|
|
353
428
|
|
|
354
|
-
const BUILTIN_CLOUD_AI_SKILLS:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
description: "Use for Cloud Kit: programming or improving browser mini apps, file converters, JavaScript tools, workbench UIs, CSV processing or exact money calculations in an existing Kit app. Read current Kit Help before choosing SDK methods.",
|
|
358
|
-
instructions: CLOUD_KIT_INSTRUCTIONS,
|
|
359
|
-
},
|
|
429
|
+
const BUILTIN_CLOUD_AI_SKILLS: AiSkillTemplate[] = [
|
|
430
|
+
ASSISTANT_CODE_MODE_SKILL,
|
|
431
|
+
ASSISTANT_DATA_ANALYSIS_SKILL,
|
|
360
432
|
{
|
|
433
|
+
version: 4,
|
|
361
434
|
key: "grids:cloud-grids",
|
|
362
435
|
name: "cloud-grids",
|
|
363
436
|
description:
|
|
@@ -366,6 +439,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
366
439
|
references: [{ path: "references/query-tasks.md", content: CLOUD_GRIDS_QUERY_REFERENCE }],
|
|
367
440
|
},
|
|
368
441
|
{
|
|
442
|
+
version: 2,
|
|
369
443
|
key: "assistant:cloud-assistant",
|
|
370
444
|
name: "cloud-assistant",
|
|
371
445
|
description:
|
|
@@ -373,6 +447,14 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
373
447
|
instructions: CLOUD_ASSISTANT_INSTRUCTIONS,
|
|
374
448
|
},
|
|
375
449
|
{
|
|
450
|
+
version: 1,
|
|
451
|
+
key: "assistant:scheduled-tasks",
|
|
452
|
+
name: "scheduled-tasks",
|
|
453
|
+
description: "Create and manage one-time reminders, delayed jobs and recurring scheduled background tasks in Cloud Assistant. Use for requests such as remind me tomorrow, check in an hour, every morning, or changing, pausing, resuming and repairing a scheduled task, including missing permissions or failed runs.",
|
|
454
|
+
instructions: SCHEDULED_TASKS_INSTRUCTIONS,
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
version: 2,
|
|
376
458
|
key: "core:skill-creator",
|
|
377
459
|
name: "skill-creator",
|
|
378
460
|
description:
|
|
@@ -380,6 +462,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
380
462
|
instructions: SKILL_CREATOR_INSTRUCTIONS,
|
|
381
463
|
},
|
|
382
464
|
{
|
|
465
|
+
version: 1,
|
|
383
466
|
key: "mail:cloud-mail",
|
|
384
467
|
name: "cloud-mail",
|
|
385
468
|
description:
|
|
@@ -387,6 +470,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
387
470
|
instructions: CLOUD_MAIL_INSTRUCTIONS,
|
|
388
471
|
},
|
|
389
472
|
{
|
|
473
|
+
version: 1,
|
|
390
474
|
key: "notebooks:cloud-notebooks",
|
|
391
475
|
name: "cloud-notebooks",
|
|
392
476
|
description:
|
|
@@ -395,6 +479,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
395
479
|
references: [{ path: "references/structured-pages.md", content: CLOUD_NOTEBOOKS_REFERENCE }],
|
|
396
480
|
},
|
|
397
481
|
{
|
|
482
|
+
version: 1,
|
|
398
483
|
key: "contacts:cloud-contacts",
|
|
399
484
|
name: "cloud-contacts",
|
|
400
485
|
description:
|
|
@@ -402,6 +487,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
402
487
|
instructions: CLOUD_CONTACTS_INSTRUCTIONS,
|
|
403
488
|
},
|
|
404
489
|
{
|
|
490
|
+
version: 1,
|
|
405
491
|
key: "spaces:cloud-spaces",
|
|
406
492
|
name: "cloud-spaces",
|
|
407
493
|
description:
|
|
@@ -410,6 +496,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
410
496
|
references: [{ path: "references/calendar-mail.md", content: CLOUD_SPACES_CALENDAR_REFERENCE }],
|
|
411
497
|
},
|
|
412
498
|
{
|
|
499
|
+
version: 1,
|
|
413
500
|
key: "weather:cloud-weather",
|
|
414
501
|
name: "cloud-weather",
|
|
415
502
|
description:
|
|
@@ -418,13 +505,11 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
418
505
|
},
|
|
419
506
|
];
|
|
420
507
|
|
|
508
|
+
export const getBuiltinAiSkillTemplates = (): readonly AiSkillTemplate[] => BUILTIN_CLOUD_AI_SKILLS;
|
|
509
|
+
|
|
421
510
|
export const getBuiltinAiSkillTemplate = (name: string) => {
|
|
422
511
|
const seed = BUILTIN_CLOUD_AI_SKILLS.find((entry) => entry.name === name);
|
|
423
512
|
if (!seed) return undefined;
|
|
424
|
-
const { key
|
|
425
|
-
return template;
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
export const seedCloudAiSkills = async (): Promise<void> => {
|
|
429
|
-
for (const seed of BUILTIN_CLOUD_AI_SKILLS) await aiSkills.seedOnce(seed);
|
|
513
|
+
const { key, ...template } = seed;
|
|
514
|
+
return { ...template, templateId: key };
|
|
430
515
|
};
|
package/src/ai/skill-tool.ts
CHANGED
|
@@ -2,11 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import type { AccessSubject } from "../server";
|
|
3
3
|
import { AI_SKILL_FILE_MOUNT } from "./file-mount";
|
|
4
4
|
import { AI_SHORT_ID_PATTERN } from "./short-id";
|
|
5
|
-
import { searchAiSkillCatalog } from "./skill-catalog";
|
|
6
5
|
import { AI_SKILL_NAME_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
|
|
7
6
|
import { aiSkills } from "./skills";
|
|
8
7
|
import { defineAiTool } from "./tools";
|
|
9
8
|
|
|
9
|
+
/** Explicit selections use the same permission check and turn snapshot as load_skill. */
|
|
10
|
+
export async function loadSelectedAiSkills(ids: readonly string[], turnId: string, subject: AccessSubject | null, allowed: boolean) {
|
|
11
|
+
const loaded = [];
|
|
12
|
+
for (const id of new Set(ids)) {
|
|
13
|
+
if (!subject || !allowed) throw new Error("The selected Skill requires a model and tool scope that allow Skills.");
|
|
14
|
+
const skill = await aiSkills.loadForTurn(turnId, { id }, subject);
|
|
15
|
+
if (!skill) throw new Error(`Selected Skill ${id} is unavailable or access was revoked.`);
|
|
16
|
+
loaded.push({ name: skill.name, revision: skill.revision, instructions: skill.instructions, files: skill.files.map(file => file.path) });
|
|
17
|
+
}
|
|
18
|
+
return loaded;
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
export const CloudAiLoadSkillInputSchema = z
|
|
11
22
|
.object({
|
|
12
23
|
name: z
|
|
@@ -58,7 +69,7 @@ export const createCloudAiLoadSkillTool = (subject: AccessSubject) =>
|
|
|
58
69
|
});
|
|
59
70
|
|
|
60
71
|
export const CloudAiSearchSkillsInputSchema = z.object({
|
|
61
|
-
query: z.string().trim().min(1).max(200).describe("
|
|
72
|
+
query: z.string().trim().min(1).max(200).describe("Words from the Skill name or description; minor typos are tolerated."),
|
|
62
73
|
limit: z.number().int().min(1).max(20).default(10),
|
|
63
74
|
});
|
|
64
75
|
|
|
@@ -71,11 +82,11 @@ export const createCloudAiSearchSkillsTool = (subject: AccessSubject) =>
|
|
|
71
82
|
defineAiTool({
|
|
72
83
|
name: "search_skills",
|
|
73
84
|
description:
|
|
74
|
-
"Search enabled Assistant Skills omitted from the bounded system-prompt catalog.
|
|
85
|
+
"Search enabled Assistant Skills omitted from the bounded system-prompt catalog. Search names and descriptions with short domain or workflow terms; minor typos are tolerated. Then load_skill with an exact returned name.",
|
|
75
86
|
inputSchema: CloudAiSearchSkillsInputSchema,
|
|
76
87
|
outputSchema: CloudAiSearchSkillsOutputSchema,
|
|
77
88
|
approval: "never",
|
|
78
89
|
}).server(async (input) => {
|
|
79
|
-
const
|
|
80
|
-
return
|
|
90
|
+
const result = await aiSkills.search(subject, input.query, input.limit);
|
|
91
|
+
return { skills: result.skills.map(({ name, description }) => ({ name, description })), more: result.more };
|
|
81
92
|
});
|
package/src/ai/skills-routes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { aiProjects } from "./projects";
|
|
1
2
|
import { Hono, type MiddlewareHandler } from "hono";
|
|
2
3
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
+
import { AuthenticatedPrincipalSchema } from "../contracts/shared";
|
|
4
5
|
import { type AuthContext, auth, err, fail, ok, rateLimit, respond, v } from "../server";
|
|
5
6
|
import {
|
|
6
7
|
AI_SKILL_DESCRIPTION_MAX_CHARS,
|
|
@@ -38,7 +39,7 @@ const SkillFieldsSchema = z.object({
|
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
const UpdateSkillSchema = SkillFieldsSchema.extend({ expectedRevision: z.number().int().positive() });
|
|
41
|
-
const SkillAccessSchema = z.object({ principal:
|
|
42
|
+
const SkillAccessSchema = z.object({ principal: AuthenticatedPrincipalSchema, permission: z.enum(["read", "write", "admin"]) });
|
|
42
43
|
const SkillAccessUpdateSchema = z.object({ permission: z.enum(["read", "write", "admin"]) });
|
|
43
44
|
const SkillEnabledSchema = z.object({ enabled: z.boolean() });
|
|
44
45
|
|
|
@@ -56,8 +57,35 @@ type AiSkillsRouteDependencies = {
|
|
|
56
57
|
const buildAiSkillsRoutes = (dependencies: AiSkillsRouteDependencies = {}) =>
|
|
57
58
|
new Hono<AuthContext>()
|
|
58
59
|
.use(dependencies.limit ?? rateLimit())
|
|
59
|
-
.use("*", dependencies.authenticate ?? auth.requireRole("
|
|
60
|
-
.get("/",
|
|
60
|
+
.use("*", dependencies.authenticate ?? auth.requireRole("authenticated"))
|
|
61
|
+
.get("/", v("query", z.object({ q: z.string().trim().max(200).optional() })), async (c) => {
|
|
62
|
+
const subject = c.get("accessSubject") ?? null;
|
|
63
|
+
const { q } = c.req.valid("query");
|
|
64
|
+
const skills = q === undefined ? await aiSkills.list(subject) : (await aiSkills.search(subject, q)).skills;
|
|
65
|
+
return respond(c, ok({ skills: skills.map(publicSummary) }));
|
|
66
|
+
})
|
|
67
|
+
.get("/project-links/:projectId", v("query", z.object({ q: z.string().max(200).default(""), page: z.coerce.number().int().min(1).max(100000).default(1), available: z.enum(["true","false"]).default("false") })), async c => {
|
|
68
|
+
const subject = c.get("accessSubject") ?? null, query = c.req.valid("query");
|
|
69
|
+
const project = await aiProjects.getByShortId(c.req.param("projectId")!, subject, query.available === "true" ? "admin" : "read");
|
|
70
|
+
const result = project ? await aiSkills.projectSkills(project.id, subject, { query:query.q, page:query.page, available:query.available === "true" }) : null;
|
|
71
|
+
return result ? respond(c, ok({ ...result, items:result.items.map(publicSummary) })) : respond(c, fail(err.notFound("Project")));
|
|
72
|
+
})
|
|
73
|
+
.get("/:skillId/projects", async c => {
|
|
74
|
+
const subject = c.get("accessSubject") ?? null;
|
|
75
|
+
const skill = await aiSkills.getByShortId(c.req.param("skillId")!, subject, "admin");
|
|
76
|
+
const projects = skill ? await aiSkills.linkedProjects(skill.id, subject) : null;
|
|
77
|
+
return projects ? respond(c, ok({ projects:projects.map(project => ({ id:project.shortId, name:project.name })) })) : respond(c, fail(err.notFound("Skill")));
|
|
78
|
+
})
|
|
79
|
+
.put("/:skillId/projects/:projectId", v("json", z.object({ linked:z.boolean() }).strict()), async c => {
|
|
80
|
+
const subject = c.get("accessSubject") ?? null;
|
|
81
|
+
const [skill,project] = await Promise.all([
|
|
82
|
+
aiSkills.getByShortId(c.req.param("skillId")!,subject,"admin"),
|
|
83
|
+
aiProjects.getByShortId(c.req.param("projectId")!,subject,"admin"),
|
|
84
|
+
]);
|
|
85
|
+
const linked = c.req.valid("json").linked;
|
|
86
|
+
return skill && project && await aiSkills.linkProject(skill.id,project.id,linked,subject)
|
|
87
|
+
? respond(c,ok({linked})) : respond(c,fail(err.notFound("Skill or Project")));
|
|
88
|
+
})
|
|
61
89
|
.get("/templates/:name", dependencies.authenticate ?? auth.requireRole("authenticated"), (c) => {
|
|
62
90
|
if (!c.get("accessSubject")) return respond(c, fail(err.forbidden("Skill templates require an authenticated access subject.")));
|
|
63
91
|
const template = getBuiltinAiSkillTemplate(c.req.param("name"));
|