@k2b/cloud 0.6.0 → 0.8.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 +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- 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/readiness.ts +4 -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 +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -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 +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- 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 +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -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 +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -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 +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -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 +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -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-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- 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 +67 -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 +6 -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 +116 -35
- 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 +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- 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/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -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/notification-sender.ts +12 -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/app-approval.ts +11 -10
- 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 +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- 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 +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -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 +131 -72
- package/src/services/pdf/index.ts +11 -8
- 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 +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- 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 +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- 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 +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- 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 +11 -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/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- 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 +4 -2
- 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/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ai/runtime.ts
CHANGED
|
@@ -6,12 +6,16 @@ import { lazySync } from "../_internal/process-sync";
|
|
|
6
6
|
import type { RequestActor } from "../server";
|
|
7
7
|
import { logger } from "../services/logging";
|
|
8
8
|
import { superviseRuntimeTask } from "../services/runtime-lifecycle";
|
|
9
|
-
|
|
9
|
+
import { coreSettings } from "../services/settings/api";
|
|
10
10
|
import { type AiToolApprovalContext, aiTurnAllowsRememberedApprovals, rememberAiToolApproval } from "./approvals";
|
|
11
11
|
import { aiChatAccessSubject, selectAssistantAiModelId } from "./assistant-models";
|
|
12
|
+
import { startAiDictationRuntime } from "./dictation-runtime";
|
|
12
13
|
import { AiTurnExecutor } from "./executor";
|
|
13
14
|
import { canonicalizeAiConversationAttachments, snapshotAiConversationFiles } from "./file-context";
|
|
14
15
|
import { startAiInvalidationRuntime, stopAiInvalidationRuntime } from "./live-outbox";
|
|
16
|
+
import { drainQueuedMessages } from "./message-queue";
|
|
17
|
+
import { aiQuotas } from "./quotas";
|
|
18
|
+
import { parseAiResourceMarker } from "./resource-markers";
|
|
15
19
|
import { isAiVisionModelConfigured } from "./settings";
|
|
16
20
|
import { aiConversations } from "./store";
|
|
17
21
|
import { publishAiTurnAbort, publishAiWireEvent } from "./stream";
|
|
@@ -44,7 +48,7 @@ const AI_TURN_LEASE_MS = 45_000;
|
|
|
44
48
|
const AI_TURN_HEARTBEAT_MS = 3_000;
|
|
45
49
|
const AI_TURN_WORKER_CONCURRENCY = 8;
|
|
46
50
|
const AI_TURN_MAX_ATTEMPTS = 5;
|
|
47
|
-
|
|
51
|
+
|
|
48
52
|
const AI_SWEEP_INTERVAL_MS = 15_000;
|
|
49
53
|
|
|
50
54
|
// ---------------------------------------------------------------------------
|
|
@@ -65,7 +69,14 @@ const aiTurnQueue = lazySync((sync) => {
|
|
|
65
69
|
|
|
66
70
|
// No idempotency key: the DB claim is the only gate, so re-enqueues (recovery,
|
|
67
71
|
// continuation, stale-sweep) are always allowed and never silently swallowed.
|
|
68
|
-
const enqueueAiTurn = (job: AiTurnJob): Promise<unknown> => aiTurnQueue().send({ data: job, orderingKey: job.
|
|
72
|
+
const enqueueAiTurn = (job: AiTurnJob): Promise<unknown> => aiTurnQueue().send({ data: job, orderingKey: job.turnId });
|
|
73
|
+
|
|
74
|
+
/** Wake the durable queue promptly; the periodic sweep retries a failed wake-up. */
|
|
75
|
+
export const wakeAiMessageQueue = (conversationId: string): void => {
|
|
76
|
+
void drainQueuedMessages(enqueueAiTurn, conversationId).catch(() =>
|
|
77
|
+
log.warn("AI message queue wake-up failed", { code: "queue_wakeup_failed", conversationId }),
|
|
78
|
+
);
|
|
79
|
+
};
|
|
69
80
|
|
|
70
81
|
/** Enqueue a turn created atomically by another durable AI workflow. */
|
|
71
82
|
export const enqueueExistingAiTurn = (input: AiTurnJob): Promise<unknown> => enqueueAiTurn(input);
|
|
@@ -103,7 +114,7 @@ export type SubmitAiChatTurnInput = {
|
|
|
103
114
|
retrySourceTurnId?: string;
|
|
104
115
|
};
|
|
105
116
|
|
|
106
|
-
export const
|
|
117
|
+
export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
|
|
107
118
|
if (input.clientToolIds?.length && input.toolSource?.kind !== "default") {
|
|
108
119
|
throw new Error("Optional client tools require the default tool source.");
|
|
109
120
|
}
|
|
@@ -128,8 +139,18 @@ export const submitAiChatTurn = async (input: SubmitAiChatTurnInput): Promise<{
|
|
|
128
139
|
modelPolicy: input.modelPolicy,
|
|
129
140
|
requestedModelId,
|
|
130
141
|
});
|
|
142
|
+
const quotaSubject = aiChatAccessSubject(input.actor);
|
|
143
|
+
if (input.assistantChat && quotaSubject) await aiQuotas.assertAllowed(quotaSubject, resolved.profile.id);
|
|
131
144
|
const runConfig: AiChatTurnRunConfig = {
|
|
132
145
|
kind: "chat",
|
|
146
|
+
selectedSkillIds: [
|
|
147
|
+
...new Set(
|
|
148
|
+
(userMessage.role === "user" ? userMessage.content : []).flatMap((part) => {
|
|
149
|
+
const marker = parseAiResourceMarker(typeof part === "string" ? part : part.type === "text" ? part.text : "");
|
|
150
|
+
return marker?.ref.type === "core.ai.skill" ? [marker.ref.id] : [];
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
],
|
|
133
154
|
...(input.assistantChat ? { assistantChat: true } : {}),
|
|
134
155
|
input: canonicalInput,
|
|
135
156
|
chatId: input.chatId,
|
|
@@ -146,7 +167,7 @@ export const submitAiChatTurn = async (input: SubmitAiChatTurnInput): Promise<{
|
|
|
146
167
|
toolApprovalContext: input.toolApprovalContext,
|
|
147
168
|
};
|
|
148
169
|
|
|
149
|
-
|
|
170
|
+
return {
|
|
150
171
|
conversationId: input.conversationId,
|
|
151
172
|
modelProfileId: resolved.profile.id,
|
|
152
173
|
runConfig,
|
|
@@ -156,8 +177,11 @@ export const submitAiChatTurn = async (input: SubmitAiChatTurnInput): Promise<{
|
|
|
156
177
|
expectedProjectId: input.expectedProjectId,
|
|
157
178
|
resources: input.resources,
|
|
158
179
|
retrySourceTurnId: input.retrySourceTurnId,
|
|
159
|
-
}
|
|
180
|
+
};
|
|
181
|
+
};
|
|
160
182
|
|
|
183
|
+
export const submitAiChatTurn = async (input: SubmitAiChatTurnInput): Promise<{ turn: AiTurn; message: AiStoredMessage }> => {
|
|
184
|
+
const submitted = await aiConversations.submitChatTurn(await prepareAiChatTurn(input));
|
|
161
185
|
await enqueueAiTurn({ conversationId: input.conversationId, turnId: submitted.turn.id });
|
|
162
186
|
return submitted;
|
|
163
187
|
};
|
|
@@ -242,7 +266,8 @@ export const abortAiTurn = async (input: { conversationId: string; turnId: strin
|
|
|
242
266
|
|
|
243
267
|
if (request.ownerless) {
|
|
244
268
|
const finalized = await aiConversations.completeTurn({ ...input, status: "aborted", error: null });
|
|
245
|
-
|
|
269
|
+
const config = await aiConversations.getTurnRunConfig(input);
|
|
270
|
+
if (finalized === "completed" && !(config?.kind === "chat" && config.background)) {
|
|
246
271
|
const attempt = active?.turn.id === input.turnId ? active.turn.attempt : 1;
|
|
247
272
|
const seq = (active?.turn.id === input.turnId ? active.liveSeq : 0) + 1;
|
|
248
273
|
await publishAiWireEvent({
|
|
@@ -343,9 +368,7 @@ export const submitAiTurnAction = async (input: {
|
|
|
343
368
|
return { ok: false, status: 409, message: "AI action was already resolved with a different response." };
|
|
344
369
|
}
|
|
345
370
|
}
|
|
346
|
-
await aiToolAudit
|
|
347
|
-
.noteToolCompleted({ turnId: input.turnId, callId: input.callId, result: input.action.result, isError: false })
|
|
348
|
-
.catch(() => undefined);
|
|
371
|
+
await aiToolAudit.noteToolCompleted({ turnId: input.turnId, callId: input.callId, isError: false }).catch(() => undefined);
|
|
349
372
|
}
|
|
350
373
|
|
|
351
374
|
await enqueueAiTurn({ conversationId: input.conversationId, turnId: input.turnId });
|
|
@@ -362,6 +385,8 @@ const runClaimedTurn = async (
|
|
|
362
385
|
leaseOwner: string,
|
|
363
386
|
onTurnFinalized?: (event: AiTurnFinalizedEvent) => Promise<void>,
|
|
364
387
|
): Promise<void> => {
|
|
388
|
+
const timeoutMinutes = await coreSettings.get<number>("ai.turn_timeout_minutes");
|
|
389
|
+
const runBudgetMs = Math.max(0, Number(timeoutMinutes ?? 30)) * 60_000;
|
|
365
390
|
const claim =
|
|
366
391
|
(await aiConversations.claimTurn({
|
|
367
392
|
...job,
|
|
@@ -369,7 +394,7 @@ const runClaimedTurn = async (
|
|
|
369
394
|
leaseMs: AI_TURN_LEASE_MS,
|
|
370
395
|
from: "queue",
|
|
371
396
|
maxAttempts: AI_TURN_MAX_ATTEMPTS,
|
|
372
|
-
runBudgetMs
|
|
397
|
+
runBudgetMs,
|
|
373
398
|
})) ??
|
|
374
399
|
(await aiConversations.claimTurn({
|
|
375
400
|
...job,
|
|
@@ -377,7 +402,7 @@ const runClaimedTurn = async (
|
|
|
377
402
|
leaseMs: AI_TURN_LEASE_MS,
|
|
378
403
|
from: "waiting",
|
|
379
404
|
maxAttempts: AI_TURN_MAX_ATTEMPTS,
|
|
380
|
-
runBudgetMs
|
|
405
|
+
runBudgetMs,
|
|
381
406
|
}));
|
|
382
407
|
|
|
383
408
|
if (!claim) return; // Already owned, done, cancelled, or attempt-capped.
|
|
@@ -392,12 +417,29 @@ const runClaimedTurn = async (
|
|
|
392
417
|
};
|
|
393
418
|
|
|
394
419
|
const processMessage = async (
|
|
395
|
-
message: QueueMessage<AiTurnJob>,
|
|
420
|
+
message: Pick<QueueMessage<AiTurnJob>, "messageId" | "data" | "heartbeat">,
|
|
396
421
|
signal: AbortSignal,
|
|
397
422
|
onTurnFinalized?: (event: AiTurnFinalizedEvent) => Promise<void>,
|
|
398
423
|
): Promise<void> => {
|
|
399
424
|
const leaseOwner = `${AI_WORKER_ID}:${message.messageId}:${crypto.randomUUID()}`;
|
|
400
|
-
|
|
425
|
+
let heartbeatFailed = false;
|
|
426
|
+
const touch = setInterval(() => {
|
|
427
|
+
void message
|
|
428
|
+
.heartbeat()
|
|
429
|
+
.then(() => {
|
|
430
|
+
heartbeatFailed = false;
|
|
431
|
+
})
|
|
432
|
+
.catch(() => {
|
|
433
|
+
if (!heartbeatFailed && !signal.aborted) {
|
|
434
|
+
log.warn("AI queue heartbeat failed", {
|
|
435
|
+
code: "queue_heartbeat_failed",
|
|
436
|
+
conversationId: message.data.conversationId,
|
|
437
|
+
turnId: message.data.turnId,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
heartbeatFailed = true;
|
|
441
|
+
});
|
|
442
|
+
}, AI_TURN_HEARTBEAT_MS);
|
|
401
443
|
if (typeof touch === "object" && "unref" in touch) touch.unref();
|
|
402
444
|
try {
|
|
403
445
|
await runClaimedTurn(message.data, signal, leaseOwner, onTurnFinalized);
|
|
@@ -409,10 +451,13 @@ const processMessage = async (
|
|
|
409
451
|
});
|
|
410
452
|
} finally {
|
|
411
453
|
clearInterval(touch);
|
|
454
|
+
if (!signal.aborted) wakeAiMessageQueue(message.data.conversationId);
|
|
412
455
|
}
|
|
413
456
|
};
|
|
414
457
|
|
|
415
458
|
const publishSweepFinished = async (turn: AiTurnFinalizedAction & { error?: string }, status: "failed" | "aborted"): Promise<void> => {
|
|
459
|
+
const config = await aiConversations.getTurnRunConfig(turn);
|
|
460
|
+
if (config?.kind === "chat" && config.background) return;
|
|
416
461
|
await publishAiWireEvent({
|
|
417
462
|
v: 1,
|
|
418
463
|
conversationId: turn.conversationId,
|
|
@@ -422,13 +467,23 @@ const publishSweepFinished = async (turn: AiTurnFinalizedAction & { error?: stri
|
|
|
422
467
|
type: "turn_finished",
|
|
423
468
|
status,
|
|
424
469
|
error: status === "failed" ? (turn.error ?? "AI turn failed.") : null,
|
|
425
|
-
}).catch(() =>
|
|
470
|
+
}).catch(() =>
|
|
471
|
+
log.warn("AI completion notification failed", {
|
|
472
|
+
code: "completion_publish_failed",
|
|
473
|
+
conversationId: turn.conversationId,
|
|
474
|
+
turnId: turn.turnId,
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
426
477
|
};
|
|
427
478
|
|
|
428
479
|
export const sweepAiRuntime = async (onTurnFinalized?: (event: AiTurnFinalizedEvent) => Promise<void>): Promise<void> => {
|
|
480
|
+
await aiQuotas.prune().catch(() => log.warn("Quota retention failed", { code: "quota_retention_failed" }));
|
|
481
|
+
await drainQueuedMessages(enqueueAiTurn);
|
|
429
482
|
const sweep = await aiConversations.sweepTurns({ maxAttempts: AI_TURN_MAX_ATTEMPTS });
|
|
430
483
|
await Promise.all([
|
|
431
|
-
...sweep.requeued.map((job) =>
|
|
484
|
+
...sweep.requeued.map((job) =>
|
|
485
|
+
enqueueAiTurn(job).catch(() => log.warn("AI recovery enqueue failed", { code: "recovery_enqueue_failed", ...job })),
|
|
486
|
+
),
|
|
432
487
|
...sweep.failed.map((turn) => publishSweepFinished(turn, "failed")),
|
|
433
488
|
...sweep.aborted.map((turn) => publishSweepFinished(turn, "aborted")),
|
|
434
489
|
]);
|
|
@@ -508,11 +563,13 @@ export const startAiRuntime = (
|
|
|
508
563
|
const sweepTimer = setInterval(runSweep, AI_SWEEP_INTERVAL_MS);
|
|
509
564
|
if (typeof sweepTimer === "object" && "unref" in sweepTimer) sweepTimer.unref();
|
|
510
565
|
|
|
566
|
+
const stopDictations = startAiDictationRuntime();
|
|
511
567
|
const state: AiRuntimeState = {
|
|
512
568
|
listeners,
|
|
513
569
|
stop: () => {
|
|
514
570
|
if (running !== state) return;
|
|
515
571
|
controller.abort();
|
|
572
|
+
stopDictations();
|
|
516
573
|
clearInterval(sweepTimer);
|
|
517
574
|
listeners.clear();
|
|
518
575
|
running = null;
|
|
@@ -541,4 +598,4 @@ const releaseAiRuntime = (listener: ((event: AiTurnFinalizedEvent) => Promise<vo
|
|
|
541
598
|
};
|
|
542
599
|
};
|
|
543
600
|
|
|
544
|
-
export const __aiRuntimeTest = { actionMatchesResolvedEvent };
|
|
601
|
+
export const __aiRuntimeTest = { actionMatchesResolvedEvent, processMessage, publishSweepFinished };
|
package/src/ai/settings.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { coreSettings } from "../services";
|
|
3
|
+
import { AiModelPricingSchema } from "../shared/ai-costs";
|
|
3
4
|
import { getAiCredential, listAiCredentialProfileIds } from "./credentials";
|
|
4
5
|
import { AI_FIRECRAWL_API_KEY_SETTING_KEY } from "./firecrawl-tools";
|
|
5
6
|
import { createAiProvider } from "./provider";
|
|
@@ -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,13 +14,12 @@ export const withAiShortId = async <T>(
|
|
|
14
14
|
try {
|
|
15
15
|
return await insert(allocate());
|
|
16
16
|
} catch (error) {
|
|
17
|
-
const
|
|
17
|
+
const uniqueViolation =
|
|
18
18
|
typeof error === "object" &&
|
|
19
19
|
error !== null &&
|
|
20
|
-
"code" in error &&
|
|
21
|
-
|
|
22
|
-
"constraint" in error &&
|
|
23
|
-
error.constraint === constraint;
|
|
20
|
+
(("code" in error && error.code === "23505") || ("errno" in error && error.errno === "23505"));
|
|
21
|
+
const collision =
|
|
22
|
+
typeof error === "object" && error !== null && uniqueViolation && "constraint" in error && error.constraint === constraint;
|
|
24
23
|
if (!collision) throw error;
|
|
25
24
|
}
|
|
26
25
|
}
|
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,58 @@
|
|
|
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 =
|
|
20
|
+
typeof error === "object" &&
|
|
21
|
+
error !== null &&
|
|
22
|
+
["code", "errno"].some((key) => key in error && ["0A000", "42704", "42883", "55000"].includes(String(Reflect.get(error, key))));
|
|
23
|
+
if (!bm25 || !capabilityLost) throw error;
|
|
24
|
+
bm25Available = Promise.resolve(false);
|
|
25
|
+
return run(false);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function aiSkillSearchSql(query: string, bm25: boolean) {
|
|
30
|
+
const terms = [...new Set(query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])];
|
|
31
|
+
// Match every term in either field. Short prefixes stay exact; longer terms
|
|
32
|
+
// tolerate typos without matching unrelated short words throughout the catalog.
|
|
33
|
+
const matches = !query
|
|
34
|
+
? sql`TRUE`
|
|
35
|
+
: !terms.length
|
|
36
|
+
? sql`FALSE`
|
|
37
|
+
: sql`
|
|
38
|
+
NOT EXISTS (SELECT 1 FROM unnest(${toPgTextArray(terms)}::text[]) AS term
|
|
39
|
+
WHERE strpos(lower(skill.name), term) = 0 AND strpos(lower(skill.description), term) = 0
|
|
40
|
+
AND (length(term) < 4 OR greatest(word_similarity(term, skill.name),
|
|
41
|
+
word_similarity(term, skill.description)) < 0.35))
|
|
42
|
+
`;
|
|
43
|
+
// Rank typos against whole words: a partial-word extent such as "invi…" in a
|
|
44
|
+
// description must not outscore the typo'd name "invoices" it was meant for.
|
|
45
|
+
const rank = !query
|
|
46
|
+
? sql`0::real`
|
|
47
|
+
: sql`
|
|
48
|
+
CASE WHEN lower(skill.name) = ${query.toLowerCase()} THEN 100 ELSE 0 END
|
|
49
|
+
+ CASE WHEN strpos(lower(skill.name), ${query.toLowerCase()}) > 0 THEN 10 ELSE 0 END
|
|
50
|
+
+ 2 * strict_word_similarity(${query}, skill.name) + strict_word_similarity(${query}, skill.description)
|
|
51
|
+
`;
|
|
52
|
+
const textRank = !query
|
|
53
|
+
? sql`0::real`
|
|
54
|
+
: bm25
|
|
55
|
+
? sql`-(skill.search_text <@> to_bm25query(${query}, ${BM25_INDEX}))`
|
|
56
|
+
: sql`ts_rank_cd(to_tsvector('simple', skill.search_text), plainto_tsquery('simple', ${query}))`;
|
|
57
|
+
return { matches, rank, textRank };
|
|
58
|
+
}
|
package/src/ai/skill-seeds.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ASSISTANT_CODE_MODE_SKILL } from "./code-mode-skill";
|
|
2
|
+
import { ASSISTANT_DATA_ANALYSIS_SKILL } from "./data-analysis-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,83 @@ 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. Code Mode can run unattended on the server:
|
|
300
|
+
load its Skill for calculations, chat-file processing, AI tasks and exports.
|
|
301
|
+
Code capability calls inherit this task's grants, including fixed inputs.
|
|
302
|
+
HTTP and RSQL use the same grants list: kind http with fixedInput fields
|
|
303
|
+
origin, url and/or method; kind database with resourceId, operation and/or
|
|
304
|
+
table. Empty fixedInput grants all supported targets/operations within current
|
|
305
|
+
access. Prefer a resourceId-only database grant when a task needs connect and
|
|
306
|
+
multiple operations; a fixed operation also requires a separate connect grant.
|
|
307
|
+
HTTP secrets must already be configured in the chat or app. Interactive dialogs,
|
|
308
|
+
secret entry and capability binary streams are unavailable in scheduled code.
|
|
309
|
+
Explain those requirements instead of promising unattended interaction.
|
|
310
|
+
5. Create the task. Confirm its actual saved timing, timezone and destination only
|
|
311
|
+
after success. Queued or manually started does not mean completed.
|
|
312
|
+
|
|
313
|
+
Examples: “Remind me tomorrow at 09:00 to call Anna” is one once task with no
|
|
314
|
+
capability grants. “Every weekday summarize my mail, compare it with calendar X,
|
|
315
|
+
and update note Y” is a recurring task with discovered read and write grants,
|
|
316
|
+
using verified calendar and note IDs where the respective schemas allow them.
|
|
317
|
+
|
|
318
|
+
## Changes, errors and missing rights
|
|
319
|
+
|
|
320
|
+
Read the task and relevant failed run before proposing a repair. Distinguish
|
|
321
|
+
missing task grants from lost resource access and ordinary execution errors.
|
|
322
|
+
Update the same task's prompt, future schedule or grants; do not recreate it.
|
|
323
|
+
Omitted grants remain unchanged; supplied grants replace the complete list.
|
|
324
|
+
Expanded authority needs the normal user review and cannot restore lost resource
|
|
325
|
+
access. Check writes already performed before retrying with task.run to avoid
|
|
326
|
+
duplicates; use task.resume when the user wants future scheduled runs restored.
|
|
327
|
+
|
|
328
|
+
Each run works independently from a snapshot of the chat history at its start.
|
|
329
|
+
Available memories, Skills, Project context and chat files remain accessible
|
|
330
|
+
under runtime rules; files are live, not frozen copies. The run returns its result
|
|
331
|
+
or problem to the parent chat, reopening it and updating activity. The user can
|
|
332
|
+
keep chatting meanwhile. Do not load this management Skill on every background
|
|
333
|
+
run unless managing a schedule is itself needed; use the domain Skills for the
|
|
334
|
+
actual work.`;
|
|
252
335
|
|
|
253
336
|
const CLOUD_NOTEBOOKS_REFERENCE = `## Flexible data and automatic page lists
|
|
254
337
|
|
|
@@ -351,13 +434,11 @@ Mail attachment inputs require base64: encode the exact returned calendar string
|
|
|
351
434
|
|
|
352
435
|
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
436
|
|
|
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
|
-
},
|
|
437
|
+
const BUILTIN_CLOUD_AI_SKILLS: AiSkillTemplate[] = [
|
|
438
|
+
ASSISTANT_CODE_MODE_SKILL,
|
|
439
|
+
ASSISTANT_DATA_ANALYSIS_SKILL,
|
|
360
440
|
{
|
|
441
|
+
version: 5,
|
|
361
442
|
key: "grids:cloud-grids",
|
|
362
443
|
name: "cloud-grids",
|
|
363
444
|
description:
|
|
@@ -366,6 +447,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
366
447
|
references: [{ path: "references/query-tasks.md", content: CLOUD_GRIDS_QUERY_REFERENCE }],
|
|
367
448
|
},
|
|
368
449
|
{
|
|
450
|
+
version: 2,
|
|
369
451
|
key: "assistant:cloud-assistant",
|
|
370
452
|
name: "cloud-assistant",
|
|
371
453
|
description:
|
|
@@ -373,6 +455,15 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
373
455
|
instructions: CLOUD_ASSISTANT_INSTRUCTIONS,
|
|
374
456
|
},
|
|
375
457
|
{
|
|
458
|
+
version: 2,
|
|
459
|
+
key: "assistant:scheduled-tasks",
|
|
460
|
+
name: "scheduled-tasks",
|
|
461
|
+
description:
|
|
462
|
+
"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.",
|
|
463
|
+
instructions: SCHEDULED_TASKS_INSTRUCTIONS,
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
version: 2,
|
|
376
467
|
key: "core:skill-creator",
|
|
377
468
|
name: "skill-creator",
|
|
378
469
|
description:
|
|
@@ -380,6 +471,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
380
471
|
instructions: SKILL_CREATOR_INSTRUCTIONS,
|
|
381
472
|
},
|
|
382
473
|
{
|
|
474
|
+
version: 1,
|
|
383
475
|
key: "mail:cloud-mail",
|
|
384
476
|
name: "cloud-mail",
|
|
385
477
|
description:
|
|
@@ -387,6 +479,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
387
479
|
instructions: CLOUD_MAIL_INSTRUCTIONS,
|
|
388
480
|
},
|
|
389
481
|
{
|
|
482
|
+
version: 1,
|
|
390
483
|
key: "notebooks:cloud-notebooks",
|
|
391
484
|
name: "cloud-notebooks",
|
|
392
485
|
description:
|
|
@@ -395,6 +488,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
395
488
|
references: [{ path: "references/structured-pages.md", content: CLOUD_NOTEBOOKS_REFERENCE }],
|
|
396
489
|
},
|
|
397
490
|
{
|
|
491
|
+
version: 1,
|
|
398
492
|
key: "contacts:cloud-contacts",
|
|
399
493
|
name: "cloud-contacts",
|
|
400
494
|
description:
|
|
@@ -402,6 +496,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
402
496
|
instructions: CLOUD_CONTACTS_INSTRUCTIONS,
|
|
403
497
|
},
|
|
404
498
|
{
|
|
499
|
+
version: 1,
|
|
405
500
|
key: "spaces:cloud-spaces",
|
|
406
501
|
name: "cloud-spaces",
|
|
407
502
|
description:
|
|
@@ -410,6 +505,7 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
410
505
|
references: [{ path: "references/calendar-mail.md", content: CLOUD_SPACES_CALENDAR_REFERENCE }],
|
|
411
506
|
},
|
|
412
507
|
{
|
|
508
|
+
version: 1,
|
|
413
509
|
key: "weather:cloud-weather",
|
|
414
510
|
name: "cloud-weather",
|
|
415
511
|
description:
|
|
@@ -418,13 +514,11 @@ const BUILTIN_CLOUD_AI_SKILLS: Array<Parameters<typeof aiSkills.seedOnce>[0]> =
|
|
|
418
514
|
},
|
|
419
515
|
];
|
|
420
516
|
|
|
517
|
+
export const getBuiltinAiSkillTemplates = (): readonly AiSkillTemplate[] => BUILTIN_CLOUD_AI_SKILLS;
|
|
518
|
+
|
|
421
519
|
export const getBuiltinAiSkillTemplate = (name: string) => {
|
|
422
520
|
const seed = BUILTIN_CLOUD_AI_SKILLS.find((entry) => entry.name === name);
|
|
423
521
|
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);
|
|
522
|
+
const { key, ...template } = seed;
|
|
523
|
+
return { ...template, templateId: key };
|
|
430
524
|
};
|