@k2b/cloud 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/scripts/README.md +10 -0
- package/scripts/build.ts +14 -1
- package/scripts/preload.ts +7 -0
- package/scripts/sync-recovery-smoke.ts +21 -6
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +35 -12
- package/src/_internal/define-app.ts +28 -11
- package/src/_internal/page-responses.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/runtime-context.ts +1 -1
- package/src/access/PermissionEditor.tsx +15 -9
- package/src/access/PrincipalPicker.tsx +9 -3
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -2
- package/src/ai/admin.ts +2 -2
- package/src/ai/assistant-models.ts +1 -1
- package/src/ai/audio-tool.ts +1 -1
- package/src/ai/browser-code-contracts.ts +174 -52
- package/src/ai/browser.ts +4 -2
- package/src/ai/capabilities.ts +3 -3
- package/src/ai/chat/blocks.tsx +127 -50
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-table.tsx +1 -1
- package/src/ai/chat/composer-adapter.ts +48 -13
- package/src/ai/chat/message-utils.ts +18 -5
- package/src/ai/chat/presentation.tsx +24 -11
- package/src/ai/chat/tool-disclosure.tsx +6 -2
- package/src/ai/chat/tool-groups.ts +8 -2
- package/src/ai/chat-quotas.ts +2 -2
- package/src/ai/chat-task-contracts.ts +9 -13
- package/src/ai/chat-tasks.ts +39 -18
- package/src/ai/client/controller.ts +87 -38
- package/src/ai/client/projection.ts +13 -6
- package/src/ai/client/transport.ts +4 -2
- package/src/ai/code-capability-routes.ts +22 -27
- package/src/ai/code-capability-transport.ts +2 -1
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +9 -5
- package/src/ai/code-runtime-tools.ts +16 -3
- package/src/ai/code-source-contracts.ts +100 -32
- package/src/ai/code-source-tools.ts +49 -6
- package/src/ai/compaction.ts +3 -3
- package/src/ai/default-tools.ts +87 -14
- package/src/ai/dictation-runtime.ts +1 -1
- package/src/ai/draft-content.ts +27 -12
- package/src/ai/enrich.ts +1 -1
- package/src/ai/executor.ts +8 -12
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +6 -1
- package/src/ai/file-tools.ts +8 -2
- package/src/ai/files-store.ts +55 -17
- package/src/ai/grids-skill.ts +2 -0
- package/src/ai/http.ts +18 -7
- package/src/ai/index.ts +24 -25
- package/src/ai/inference-calls.ts +3 -4
- 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 +5 -5
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +23 -12
- package/src/ai/migrate.ts +6 -4
- package/src/ai/model-pricing.ts +4 -4
- package/src/ai/pdf-render-worker.ts +3 -2
- package/src/ai/projects-routes.ts +15 -40
- package/src/ai/projects.ts +18 -13
- package/src/ai/quota-provider.ts +6 -5
- package/src/ai/quota-report.ts +5 -5
- package/src/ai/quotas.ts +3 -3
- package/src/ai/routes.ts +60 -34
- package/src/ai/run-timeout.ts +3 -1
- package/src/ai/runtime-tools.ts +6 -7
- package/src/ai/runtime.ts +38 -20
- package/src/ai/settings.ts +1 -1
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-search.ts +21 -10
- package/src/ai/skill-seeds.ts +16 -7
- package/src/ai/skill-tool.ts +6 -1
- package/src/ai/skills-routes.ts +32 -15
- package/src/ai/skills.ts +54 -28
- package/src/ai/store.ts +80 -47
- package/src/ai/structured.ts +6 -2
- package/src/ai/system-prompt.ts +7 -5
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/todo-contracts.ts +24 -12
- package/src/ai/todo-tool.ts +16 -13
- package/src/ai/tools.ts +1 -1
- package/src/ai/transcription.ts +4 -4
- package/src/ai/turn-timing.ts +1 -1
- package/src/ai/ui.tsx +2 -1
- package/src/ai/usage.ts +1 -1
- package/src/ai/vision-tool.ts +38 -17
- package/src/api/admin-ai-quotas.ts +14 -8
- package/src/api/admin-ai-skills.ts +3 -3
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +2 -2
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth/schemas.ts +7 -1
- package/src/api/auth.ts +4 -3
- package/src/api/capabilities.ts +45 -13
- package/src/api/index.ts +5 -5
- package/src/api/mcp.ts +5 -12
- package/src/browser/CloudResourceSearch.tsx +1 -1
- package/src/browser/command-bridge.ts +1 -1
- package/src/browser/command-shortcuts.ts +1 -1
- package/src/browser/commands.ts +8 -7
- package/src/browser/testing.ts +2 -1
- package/src/capabilities/client.ts +1 -1
- package/src/capabilities/command-link.ts +1 -1
- package/src/capabilities/server.ts +2 -1
- package/src/cli/admin/ai-quotas.ts +5 -5
- package/src/cli/admin/app-credentials.ts +7 -9
- package/src/cli/admin/index.ts +3 -3
- 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 -29
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -54
- package/src/config/index.ts +3 -1
- package/src/contracts/capabilities.ts +8 -1
- package/src/contracts/index.ts +5 -7
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +12 -3
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -2
- package/src/server/app-assets.ts +27 -0
- package/src/server/index.ts +3 -2
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +3 -3
- package/src/server/services/access.ts +3 -1
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/announcements/index.ts +4 -4
- package/src/services/app-approval.ts +11 -10
- package/src/services/auth-flows/magic-link.ts +13 -1
- package/src/services/identity/key-config.ts +6 -4
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/logging/index.ts +1 -1
- package/src/services/logging/trace.ts +1 -1
- package/src/services/mandates/index.ts +5 -2
- package/src/services/mandates/policy.ts +106 -6
- package/src/services/notifications/batches.ts +1 -0
- package/src/services/pdf/gotenberg.ts +50 -14
- package/src/services/pdf/index.ts +11 -11
- package/src/services/public-http.ts +1 -1
- package/src/services/rail-shortcuts.ts +1 -1
- package/src/services/rail-snapshot.ts +1 -1
- package/src/services/request-cache-redis.ts +2 -1
- package/src/services/session/user.ts +1 -1
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +6 -8
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +3 -3
- package/src/services/webauthn.ts +1 -1
- package/src/shared/index.ts +3 -6
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminSidebar.tsx +2 -2
- package/src/ssr/AppLaunchpad.island.tsx +12 -2
- package/src/ssr/AppLaunchpadPanel.tsx +4 -4
- package/src/ssr/GlobalSearchDialog.tsx +6 -6
- package/src/ssr/GlobalSearchTrigger.island.tsx +2 -2
- package/src/ssr/Layout.tsx +4 -3
- package/src/ssr/LayoutHeader.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +4 -6
- package/src/ssr/LayoutHelpTrigger.island.tsx +2 -2
- package/src/ssr/LayoutRail.tsx +3 -2
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/ProfilePreferences.island.tsx +1 -1
- package/src/ssr/RailApps.island.tsx +7 -7
- package/src/ssr/admin-navigation.ts +6 -2
- package/src/ssr/index.ts +1 -0
- package/src/ssr/islands/index.ts +2 -2
- package/src/ssr/mobile-menu-history.ts +1 -1
- package/src/ssr/rail-navigation.ts +3 -2
- package/src/ssr/request-path.ts +12 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +16 -91
- package/src/workflows/ai/store.ts +1 -1
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/store/index.ts +2 -2
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/session/test-fixture.ts +0 -18
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k2b/cloud",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Application platform library for independently deployed Hono and SolidJS services behind a dynamic gateway.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"!src/**/*.test.ts",
|
|
19
19
|
"!src/**/*.test.tsx",
|
|
20
20
|
"!src/**/*.integration.test.ts",
|
|
21
|
+
"!src/**/*.test-fixture.ts",
|
|
21
22
|
"!scripts/**/*.test.ts",
|
|
22
23
|
"!scripts/runtime-recovery-*.ts"
|
|
23
24
|
],
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
"scripts": {
|
|
86
87
|
"test": "bun test --isolate --path-ignore-patterns '**/*.behavior.test.tsx' --path-ignore-patterns '**/*.dom.test.tsx' && bun run test:dom",
|
|
87
88
|
"test:dom": "bun test --isolate --conditions=browser --preload ../ui/test/solid-dom-preload.ts behavior.test.tsx dom.test.tsx",
|
|
88
|
-
"typecheck": "tsc -p tsconfig.json --noEmit --pretty false
|
|
89
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --pretty false"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
92
|
"@firecrawl/anydoc": "0.1.9",
|
|
@@ -97,7 +98,7 @@
|
|
|
97
98
|
"@tailwindcss/typography": "0.5.20",
|
|
98
99
|
"@k2b/nessi": "0.12.0",
|
|
99
100
|
"@k2b/ssr": "0.14.0",
|
|
100
|
-
"@k2b/ui": "0.
|
|
101
|
+
"@k2b/ui": "0.4.1",
|
|
101
102
|
"@k2b/stdlib": "0.25.0",
|
|
102
103
|
"@k2b/sync": "6.4.0",
|
|
103
104
|
"@nats-io/transport-node": "3.4.0",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Cloud platform scripts
|
|
2
|
+
|
|
3
|
+
- `build.ts` — production build for one Cloud app (`APP_ID`); used by the Dockerfiles and `bun run build` targets.
|
|
4
|
+
- `preload.ts` — dev-mode preload that registers the SSR plugin and builds CSS before app code loads.
|
|
5
|
+
- `build-pdf-renderer.ts` — packages the PDF decoder and native assets for bundles that import PDF vision.
|
|
6
|
+
- `app-favicon.ts` — generates app favicons from Tabler icons; imported by `build.ts` and `preload.ts`.
|
|
7
|
+
- `browser-performance.ts` — bundles the web-vitals browser asset for core; imported by `build.ts` and `preload.ts`.
|
|
8
|
+
- `runtime-recovery-acceptance.ts` — shared-runtime recovery acceptance on disposable Docker containers: `bun packages/cloud/scripts/runtime-recovery-acceptance.ts` (also `bun run test:runtime-recovery`).
|
|
9
|
+
- `runtime-recovery-worker.ts` — the containerized fixture started by `runtime-recovery-acceptance.ts`; not run directly.
|
|
10
|
+
- `sync-recovery-smoke.ts` — two-phase NATS recovery smoke: `bun packages/cloud/scripts/sync-recovery-smoke.ts prepare --namespace cloud-recovery-smoke-<unique>`, restart the fleet, then `... recover --namespace <same>`.
|
package/scripts/build.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* _ssr/<island>.js hydration bundles (auto-emitted by the SSR plugin)
|
|
18
18
|
* public/<id>/app.css Tailwind, if the app has src/styles/app.css
|
|
19
19
|
* public/<id>/... anything from <appDir>/public/
|
|
20
|
+
* assets/... anything from <appDir>/src/assets/ (appAssetPath)
|
|
20
21
|
*
|
|
21
22
|
* If the app needs additional artefacts (core's global.css, logo, katex),
|
|
22
23
|
* it ships a `scripts/build-extras.ts` that this script runs at the end.
|
|
@@ -47,6 +48,10 @@ const release = process.env.CLOUD_RELEASE?.trim() || "local";
|
|
|
47
48
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(release)) {
|
|
48
49
|
throw new Error(`Invalid CLOUD_RELEASE: ${JSON.stringify(release)}`);
|
|
49
50
|
}
|
|
51
|
+
const version = process.env.CLOUD_VERSION?.trim() || "0.0.0-local";
|
|
52
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version)) {
|
|
53
|
+
throw new Error(`Invalid CLOUD_VERSION: ${JSON.stringify(version)}`);
|
|
54
|
+
}
|
|
50
55
|
const syncPackagePath = Bun.resolveSync("@k2b/sync/package.json", frameworkDir);
|
|
51
56
|
const syncPackage = JSON.parse(await readFile(syncPackagePath, "utf8")) as {
|
|
52
57
|
version?: unknown;
|
|
@@ -121,9 +126,11 @@ try {
|
|
|
121
126
|
minify: true,
|
|
122
127
|
metafile: true,
|
|
123
128
|
define: {
|
|
129
|
+
__CLOUD_VERSION__: JSON.stringify(version),
|
|
124
130
|
__CLOUD_RELEASE__: JSON.stringify(release),
|
|
125
131
|
__CLOUD_SYNC_VERSION__: JSON.stringify(syncVersion),
|
|
126
132
|
__CLOUD_PDF_RENDER_WORKER__: JSON.stringify("./pdf-render/worker.js"),
|
|
133
|
+
__CLOUD_APP_ASSETS__: JSON.stringify("./assets/"),
|
|
127
134
|
},
|
|
128
135
|
plugins: [(app?.plugin ?? plugin)()],
|
|
129
136
|
});
|
|
@@ -134,7 +141,7 @@ if (!server.success) {
|
|
|
134
141
|
for (const m of server.logs) console.error(m);
|
|
135
142
|
throw new Error("Server bundle failed");
|
|
136
143
|
}
|
|
137
|
-
if (Object.keys(server.metafile?.inputs ?? {}).some(path => path.endsWith("/ai/pdf-render.ts"))) {
|
|
144
|
+
if (Object.keys(server.metafile?.inputs ?? {}).some((path) => path.endsWith("/ai/pdf-render.ts"))) {
|
|
138
145
|
await buildPdfRenderer(dist);
|
|
139
146
|
}
|
|
140
147
|
|
|
@@ -162,6 +169,12 @@ if (existsSync(appPublic)) {
|
|
|
162
169
|
await cp(appPublic, resolve(distPublic, appId), { recursive: true });
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
// Server-side assets the bundle reads through appAssetPath().
|
|
173
|
+
const appAssets = resolve(appDir, "src/assets");
|
|
174
|
+
if (existsSync(appAssets)) {
|
|
175
|
+
await cp(appAssets, resolve(dist, "assets"), { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
|
|
165
178
|
if (appId !== "core" && app) {
|
|
166
179
|
await writeAppFavicon({
|
|
167
180
|
publicDir: distPublic,
|
package/scripts/preload.ts
CHANGED
|
@@ -132,6 +132,13 @@ if (appId === "core" && existsSync(globalCssEntry)) {
|
|
|
132
132
|
const appPublicDir = resolve(publicDir, appId);
|
|
133
133
|
await mkdir(appPublicDir, { recursive: true });
|
|
134
134
|
|
|
135
|
+
// Static assets from <appDir>/public, same as step 3 of the production build.
|
|
136
|
+
const appPublicSource = resolve(appDir, "public");
|
|
137
|
+
// A standalone application's public directory can be the serving root itself; never copy a tree into itself.
|
|
138
|
+
if (existsSync(appPublicSource) && appPublicSource !== publicDir && !appPublicDir.startsWith(`${appPublicSource}/`)) {
|
|
139
|
+
await cp(appPublicSource, appPublicDir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
135
142
|
if (appId !== "core" && app) {
|
|
136
143
|
await writeAppFavicon({
|
|
137
144
|
publicDir,
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Isolated two-process recovery acceptance for the local NATS cluster.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* bun packages/cloud/scripts/sync-recovery-smoke.ts prepare --namespace cloud-recovery-smoke-<unique>
|
|
5
5
|
* Stop the application fleet across the printed nextTick boundary, keeping NATS up.
|
|
6
6
|
* Restart the fleet, then run the same command with `recover`.
|
|
7
7
|
* No provider or application-domain effects; successful recovery deletes only
|
|
8
8
|
* the exact test streams after checking their broker identity metadata.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
11
12
|
import { createSync, type SyncResourceSummary, type Worker } from "@k2b/sync";
|
|
12
13
|
import { connect, type NatsConnection } from "@nats-io/transport-node";
|
|
14
|
+
import { env } from "../src/config/env";
|
|
13
15
|
|
|
14
16
|
const APPLICATION = "cloud-recovery-smoke";
|
|
15
17
|
const JOB_ID = "restart-job";
|
|
@@ -49,11 +51,24 @@ const removeTestStreams = async (connection: NatsConnection, namespace: string,
|
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
export const main = async (): Promise<void> => {
|
|
52
|
-
const
|
|
54
|
+
const { values: options, positionals } = parseArgs({
|
|
55
|
+
args: Bun.argv.slice(2),
|
|
56
|
+
allowPositionals: true,
|
|
57
|
+
options: { namespace: { type: "string" }, help: { type: "boolean", default: false } },
|
|
58
|
+
});
|
|
59
|
+
if (options.help) {
|
|
60
|
+
console.log(`Usage: bun packages/cloud/scripts/sync-recovery-smoke.ts <prepare|recover> --namespace cloud-recovery-smoke-<unique>
|
|
61
|
+
|
|
62
|
+
Two-phase recovery acceptance against the application's NATS servers (NATS_SERVERS, default nats://127.0.0.1:4222).
|
|
63
|
+
Run "prepare", restart the application fleet while NATS stays up, then run "recover" with the same namespace.
|
|
64
|
+
`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const phase = positionals[0];
|
|
53
68
|
check(phase === "prepare" || phase === "recover", "Pass prepare or recover");
|
|
54
|
-
const namespace =
|
|
55
|
-
check(/^cloud-recovery-smoke-[A-Za-z0-9_-]{6,60}$/.test(namespace), "
|
|
56
|
-
const servers = (
|
|
69
|
+
const namespace = options.namespace?.trim() ?? "";
|
|
70
|
+
check(/^cloud-recovery-smoke-[A-Za-z0-9_-]{6,60}$/.test(namespace), "Pass a unique --namespace cloud-recovery-smoke-<suffix>");
|
|
71
|
+
const servers = (env.NATS_SERVERS ?? "nats://127.0.0.1:4222")
|
|
57
72
|
.split(",")
|
|
58
73
|
.map((value) => value.trim())
|
|
59
74
|
.filter(Boolean);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { AppRuntimeMetadata } from "../contracts/registry";
|
|
2
2
|
|
|
3
|
+
declare const __CLOUD_VERSION__: string;
|
|
3
4
|
declare const __CLOUD_RELEASE__: string;
|
|
4
5
|
declare const __CLOUD_SYNC_VERSION__: string;
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
/** Cloud version and release label baked into the bundle by `scripts/build.ts`. */
|
|
8
|
+
export const buildMetadata = {
|
|
9
|
+
version: typeof __CLOUD_VERSION__ === "string" ? __CLOUD_VERSION__ : "0.0.0-local",
|
|
7
10
|
release: typeof __CLOUD_RELEASE__ === "string" ? __CLOUD_RELEASE__ : "development",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const appRuntimeMetadata: AppRuntimeMetadata = {
|
|
14
|
+
...buildMetadata,
|
|
8
15
|
syncVersion: typeof __CLOUD_SYNC_VERSION__ === "string" ? __CLOUD_SYNC_VERSION__ : "unknown",
|
|
9
16
|
};
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
type CapabilityActionManifest,
|
|
11
11
|
type CapabilityActionReviewResult,
|
|
12
12
|
CapabilityActionReviewSchema,
|
|
13
|
-
type CapabilityDefinitions,
|
|
14
13
|
type CapabilityCommandManifest,
|
|
15
14
|
CapabilityCommandManifestSchema,
|
|
15
|
+
type CapabilityDefinitions,
|
|
16
16
|
type CapabilityError,
|
|
17
17
|
CapabilityErrorSchema,
|
|
18
18
|
type CapabilityExecutionContext,
|
|
@@ -433,7 +433,11 @@ const compileOperationSchemas = (definition: CapabilityQueryDefinition | Capabil
|
|
|
433
433
|
inputSchema,
|
|
434
434
|
dataSchema,
|
|
435
435
|
resultZodSchema,
|
|
436
|
-
schemaHash: capabilityHash({
|
|
436
|
+
schemaHash: capabilityHash({
|
|
437
|
+
inputSchema,
|
|
438
|
+
dataSchema,
|
|
439
|
+
...(definition.stream ? { stream: { direction: definition.stream.direction, maxBytes: definition.stream.maxBytes } } : {}),
|
|
440
|
+
}),
|
|
437
441
|
};
|
|
438
442
|
};
|
|
439
443
|
|
|
@@ -469,8 +473,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
469
473
|
for (const [localId, definition] of Object.entries(definitions.queries ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
470
474
|
registerLocalId(localId, "Query");
|
|
471
475
|
const label = `Query ${localId}`;
|
|
472
|
-
if (
|
|
473
|
-
|
|
476
|
+
if (
|
|
477
|
+
definition.stream &&
|
|
478
|
+
((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
|
|
479
|
+
)
|
|
480
|
+
throw new Error(`${label}: write streams require an Action and a positive byte budget`);
|
|
481
|
+
if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
|
|
482
|
+
throw new Error(`${label}: write streams require idempotency`);
|
|
474
483
|
const schemas = compileOperationSchemas(definition, label);
|
|
475
484
|
if (definition.universalSearch) {
|
|
476
485
|
const expectedInput = projectSchema(UniversalSearchInputSchema, "Universal Search input", "input");
|
|
@@ -540,8 +549,13 @@ export const compileCapabilities = (appId: string, definitions: CapabilityDefini
|
|
|
540
549
|
if (definition.approval === "rememberable" && !definition.review) {
|
|
541
550
|
throw new Error(`${label} must provide a review before approval can be remembered`);
|
|
542
551
|
}
|
|
543
|
-
if (
|
|
544
|
-
|
|
552
|
+
if (
|
|
553
|
+
definition.stream &&
|
|
554
|
+
((definition.stream.direction === "write" && !("idempotency" in definition)) || definition.stream.maxBytes <= 0)
|
|
555
|
+
)
|
|
556
|
+
throw new Error(`${label}: write streams require an Action and a positive byte budget`);
|
|
557
|
+
if (definition.stream?.direction === "write" && "idempotency" in definition && definition.idempotency !== "required")
|
|
558
|
+
throw new Error(`${label}: write streams require idempotency`);
|
|
545
559
|
const schemas = compileOperationSchemas(definition, label);
|
|
546
560
|
const manifest = {
|
|
547
561
|
localId,
|
|
@@ -736,13 +750,14 @@ export const parseCapabilityManifest = (value: unknown, expectedAppId: string):
|
|
|
736
750
|
);
|
|
737
751
|
}
|
|
738
752
|
assertClosedObjectInput(inputSchema, `Operation ${operation.localId} input`);
|
|
739
|
-
if (
|
|
740
|
-
operation.stream?.direction === "write" &&
|
|
741
|
-
(!("idempotency" in operation) || operation.idempotency !== "required")
|
|
742
|
-
) {
|
|
753
|
+
if (operation.stream?.direction === "write" && (!("idempotency" in operation) || operation.idempotency !== "required")) {
|
|
743
754
|
throw new Error(`Operation ${operation.localId} write streams require an idempotent Action`);
|
|
744
755
|
}
|
|
745
|
-
const expectedSchemaHash = capabilityHash({
|
|
756
|
+
const expectedSchemaHash = capabilityHash({
|
|
757
|
+
inputSchema: operation.inputSchema,
|
|
758
|
+
dataSchema: operation.dataSchema,
|
|
759
|
+
...(operation.stream ? { stream: operation.stream } : {}),
|
|
760
|
+
});
|
|
746
761
|
if (operation.schemaHash !== expectedSchemaHash)
|
|
747
762
|
throw new Error(`Operation ${operation.localId} schemaHash does not match its schemas`);
|
|
748
763
|
if ("universalSearch" in operation && operation.universalSearch) {
|
|
@@ -1086,7 +1101,15 @@ export const invokeCompiledCapability = async (params: {
|
|
|
1086
1101
|
return normalizeProviderError(invoked.error, "Capability returned an invalid error", params.onUnexpectedError);
|
|
1087
1102
|
}
|
|
1088
1103
|
const stream = invoked.data.stream;
|
|
1089
|
-
if (
|
|
1104
|
+
if (
|
|
1105
|
+
stream &&
|
|
1106
|
+
(Buffer.byteLength(stream.id) > 2048 ||
|
|
1107
|
+
!operation.definition.stream ||
|
|
1108
|
+
stream.direction !== operation.definition.stream.direction ||
|
|
1109
|
+
stream.size > operation.definition.stream.maxBytes ||
|
|
1110
|
+
Date.parse(stream.expiresAt) <= Date.now() ||
|
|
1111
|
+
Date.parse(stream.expiresAt) > Date.now() + 86_400_000)
|
|
1112
|
+
)
|
|
1090
1113
|
return invalidProviderError("Stream is outside its declared policy or 24 hour lifetime");
|
|
1091
1114
|
const validated = validateCapabilityResult(params.compiled, operation, invoked.data, params.onUnexpectedError);
|
|
1092
1115
|
return validated.ok
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { invokeCapabilityStream } from "./capability-streams";
|
|
2
1
|
import { registerHelp } from "../services/help";
|
|
3
2
|
import { preloadLayoutHelp } from "../ssr/help";
|
|
3
|
+
import { invokeCapabilityStream } from "./capability-streams";
|
|
4
4
|
import { bindProcessApplicationId, clearProcessApplicationId } from "./process-identity";
|
|
5
5
|
/**
|
|
6
6
|
* defineApp() — The single entry point for every cloud app.
|
|
@@ -72,8 +72,8 @@ import { compileCapabilities, invokeCompiledCapability, reviewCompiledCapability
|
|
|
72
72
|
import { createHeartbeat } from "./heartbeat";
|
|
73
73
|
import { compileHelp } from "./help";
|
|
74
74
|
import { createPageResponses } from "./page-responses";
|
|
75
|
-
import { getProcessSync, startProcessSync } from "./process-sync";
|
|
76
75
|
import { configurePostgresApplicationName } from "./postgres-application-name";
|
|
76
|
+
import { getProcessSync, startProcessSync } from "./process-sync";
|
|
77
77
|
import { APP_READINESS_PATH, appReadinessResponse } from "./readiness";
|
|
78
78
|
import { appRegistry, type CapabilityRegistryRecord, capabilityRegistry } from "./registry";
|
|
79
79
|
import { ensureRuntimeWatcher, getCurrentRuntime, stopRuntimeWatcher } from "./runtime-watcher";
|
|
@@ -303,7 +303,7 @@ export const defineApp = <
|
|
|
303
303
|
opts: AppOptions<S, N, AppId>,
|
|
304
304
|
): AppDefinition<S, N, AppId> => {
|
|
305
305
|
configurePostgresApplicationName(opts.id);
|
|
306
|
-
const isDevelopment =
|
|
306
|
+
const isDevelopment = env.IS_DEVELOPMENT;
|
|
307
307
|
const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
|
|
308
308
|
|
|
309
309
|
// ── 0. Register declared settings into the runtime registry ──────────
|
|
@@ -321,7 +321,7 @@ export const defineApp = <
|
|
|
321
321
|
dev: isDevelopment,
|
|
322
322
|
verbose: true,
|
|
323
323
|
rootDir,
|
|
324
|
-
componentRoots: [resolve(
|
|
324
|
+
componentRoots: [resolve(env.APP_DIR ?? rootDir, "src"), fileURLToPath(new URL("../", import.meta.url))],
|
|
325
325
|
basePath: opts.basePath,
|
|
326
326
|
template: ({ body, scripts, title, description, theme, lang, performanceRoute }) => {
|
|
327
327
|
const themeFixed = theme !== undefined;
|
|
@@ -673,13 +673,30 @@ export const defineApp = <
|
|
|
673
673
|
};
|
|
674
674
|
});
|
|
675
675
|
for (const kind of ["queries", "actions"] as const) {
|
|
676
|
-
server.post(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
676
|
+
server.post(
|
|
677
|
+
`/api/_internal/capabilities/v1/streams/${kind}/:capabilityId/:verb`,
|
|
678
|
+
capabilityAuth(kind),
|
|
679
|
+
auth.requireOAuthScope(kind === "queries" ? "read" : "write", "admin"),
|
|
680
|
+
async (c) => {
|
|
681
|
+
const actor = c.get("actor");
|
|
682
|
+
return invokeCapabilityStream({
|
|
683
|
+
compiled: compiledCapabilities,
|
|
684
|
+
kind,
|
|
685
|
+
localId: c.req.param("capabilityId") ?? "",
|
|
686
|
+
verb: c.req.param("verb") ?? "",
|
|
687
|
+
request: c.req.raw,
|
|
688
|
+
context: {
|
|
689
|
+
actor,
|
|
690
|
+
accessSubject: c.get("accessSubject"),
|
|
691
|
+
user: actor.kind === "user" ? actor.user : actor.delegatedUser,
|
|
692
|
+
locale: resolveLocale(c.req.raw.headers, await get<string>("app.locale")),
|
|
693
|
+
requestId: normalizeInvocationRequestId(c.req.header("x-request-id")) ?? crypto.randomUUID(),
|
|
694
|
+
origin: CapabilityOriginSchema.catch("http").parse(c.req.header(CAPABILITY_ORIGIN_HEADER)),
|
|
695
|
+
signal: c.req.raw.signal,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
},
|
|
699
|
+
);
|
|
683
700
|
}
|
|
684
701
|
const capabilityReadScope = auth.requireOAuthScope("read", "admin");
|
|
685
702
|
const capabilityWriteScope = auth.requireOAuthScope("write", "admin");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { HtmlFn } from "@k2b/ssr";
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import type { ClientErrorStatusCode, ServerErrorStatusCode } from "hono/utils/http-status";
|
|
4
|
-
import { preloadLayoutAnnouncements } from "../server/middleware/settings";
|
|
5
4
|
import { getLocale } from "../server/locale";
|
|
5
|
+
import { preloadLayoutAnnouncements } from "../server/middleware/settings";
|
|
6
6
|
import { createLoginRedirectUrl } from "../shared/redirect";
|
|
7
7
|
import type { PageOptions } from "./define-app";
|
|
8
8
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { buildMetadata } from "./build-metadata";
|
|
2
|
+
|
|
1
3
|
export const APP_READINESS_PATH = "/_cloud/ready";
|
|
2
4
|
|
|
3
|
-
export const appReadinessResponse = (appId: string): Response =>
|
|
5
|
+
export const appReadinessResponse = (appId: string): Response =>
|
|
6
|
+
Response.json({ status: "ready", appId, version: buildMetadata.version, release: buildMetadata.release });
|
|
@@ -39,7 +39,7 @@ export const buildRuntimeFromRegistry = (entries: AppRegistryEntry[], capabiliti
|
|
|
39
39
|
appearance: e.appearance,
|
|
40
40
|
adminHref: e.nav?.adminHref,
|
|
41
41
|
adminNav: e.adminNav?.map((group) => ({
|
|
42
|
-
...(group.section ? {section:group.section} : {}),
|
|
42
|
+
...(group.section ? { section: group.section } : {}),
|
|
43
43
|
id: group.id,
|
|
44
44
|
label: group.label,
|
|
45
45
|
links: group.links.map((link) => ({ ...link })),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import PrincipalPicker from "./PrincipalPicker";
|
|
2
1
|
import { mutation } from "@k2b/stdlib/solid";
|
|
3
|
-
import {
|
|
2
|
+
import { IconButton, Placeholder, prompts, SelectChip, Tooltip, useLocale } from "@k2b/ui";
|
|
4
3
|
import { createSignal, For, Show } from "solid-js";
|
|
5
4
|
import { CloudAvatar } from "../account/Avatar";
|
|
6
5
|
import type { AccessEntry, PermissionLevel, Principal } from "../contracts/shared";
|
|
7
6
|
import { accessMessages } from "./messages";
|
|
7
|
+
import PrincipalPicker from "./PrincipalPicker";
|
|
8
8
|
|
|
9
9
|
// ─────────────────────────────────────────────────────────────────────────
|
|
10
10
|
// Public API
|
|
@@ -147,7 +147,8 @@ export default function PermissionEditor(props: PermissionEditorProps) {
|
|
|
147
147
|
const canEdit = () => props.canEdit !== false;
|
|
148
148
|
const allowPublic = () => props.allowPublic === true;
|
|
149
149
|
const allowAuthenticated = () => props.allowAuthenticated !== false;
|
|
150
|
-
const allowed = (principal: Principal) =>
|
|
150
|
+
const allowed = (principal: Principal) =>
|
|
151
|
+
resolveAllowedLevels(typeof props.allowedLevels === "function" ? props.allowedLevels(principal) : props.allowedLevels, t());
|
|
151
152
|
|
|
152
153
|
// Defensive dev-warning: an empty allowedLevels array makes the editor
|
|
153
154
|
// unable to grant anything.
|
|
@@ -229,12 +230,17 @@ export default function PermissionEditor(props: PermissionEditorProps) {
|
|
|
229
230
|
level on pick. The user upgrades via the row pill if they want
|
|
230
231
|
a higher level. KISS: one decision per step. */}
|
|
231
232
|
<Show when={canEdit()}>
|
|
232
|
-
<PrincipalPicker
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
233
|
+
<PrincipalPicker
|
|
234
|
+
existing={entries().map((e) => e.principal)}
|
|
235
|
+
allowPublic={allowPublic()}
|
|
236
|
+
allowAuthenticated={allowAuthenticated()}
|
|
237
|
+
allowServiceAccounts={props.allowServiceAccounts}
|
|
238
|
+
disabled={busy()}
|
|
239
|
+
onSelect={(principal, display) => {
|
|
240
|
+
const permission = allowed(principal)[0]?.level;
|
|
241
|
+
if (permission && !busy()) grantMut.mutate({ principal, permission, display });
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
238
244
|
</Show>
|
|
239
245
|
</div>
|
|
240
246
|
);
|
|
@@ -91,9 +91,15 @@ export default function PrincipalPicker(props: {
|
|
|
91
91
|
return (
|
|
92
92
|
<Combobox
|
|
93
93
|
disabled={props.disabled}
|
|
94
|
-
placeholder={
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
placeholder={
|
|
95
|
+
props.allowAuthenticated !== false || props.allowPublic
|
|
96
|
+
? props.allowServiceAccounts
|
|
97
|
+
? t().addAll
|
|
98
|
+
: t().addAudience
|
|
99
|
+
: props.allowServiceAccounts
|
|
100
|
+
? t().addService
|
|
101
|
+
: t().addBasic
|
|
102
|
+
}
|
|
97
103
|
fetchData={load}
|
|
98
104
|
onSelect={(option) => {
|
|
99
105
|
const principal = principals.get(option.id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dates } from "@k2b/stdlib";
|
|
2
2
|
import { mutation as mutations } from "@k2b/stdlib/solid";
|
|
3
|
-
import {
|
|
3
|
+
import { Button, CopyButton, DateTimePicker, NoticeCard, Placeholder, prompts, Select, Tag, TextInput, useLocale } from "@k2b/ui";
|
|
4
4
|
import { createEffect, createSignal, For, Show } from "solid-js";
|
|
5
5
|
import type { PermissionLevel, ServiceAccountCredential } from "../contracts/shared";
|
|
6
6
|
import { accessMessages } from "./messages";
|
package/src/access/ui.ts
CHANGED
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
export type { AllowedLevel, GrantableLevel } from "./PermissionEditor";
|
|
10
10
|
export { default as PermissionEditor } from "./PermissionEditor";
|
|
11
|
+
export { default as PrincipalPicker, principalKey } from "./PrincipalPicker";
|
|
11
12
|
export type {
|
|
12
13
|
ResourceApiKey,
|
|
13
14
|
ResourceApiKeyPermissionOption,
|
|
14
15
|
ResourceApiKeysProps,
|
|
15
16
|
} from "./ResourceApiKeys";
|
|
16
17
|
export { default as ResourceApiKeys } from "./ResourceApiKeys";
|
|
17
|
-
|
|
18
|
-
export { default as PrincipalPicker, principalKey } from "./PrincipalPicker";
|
package/src/ai/admin.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { quotaAdminConfig, quotaReport } from "./quota-report";
|
|
2
|
+
export { AiQuotaError, aiQuotas } from "./quotas";
|
|
3
3
|
/** Server-only AI administration and usage accounting. */
|
|
4
4
|
|
|
5
5
|
export { type AiModelAccessDraft, type AiModelAccessMap, type AiModelAccessState, aiModelAccess } from "./model-access";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { resolveAiAudioModel } from "./transcription";
|
|
2
1
|
import type { AccessSubject, RequestActor } from "../server";
|
|
3
2
|
import { aiModelAccess } from "./model-access";
|
|
4
3
|
import { personalAiModelPolicy } from "./personal-agent";
|
|
5
4
|
import { listAiModels, toPublicAiSettingsState } from "./settings";
|
|
5
|
+
import { resolveAiAudioModel } from "./transcription";
|
|
6
6
|
import type { AiChatTurnRunConfig, AiModelPolicy, AiSettingsError } from "./types";
|
|
7
7
|
|
|
8
8
|
export const aiChatAccessSubject = (actor: RequestActor | undefined): AccessSubject | null => {
|
package/src/ai/audio-tool.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { aiProjectFilePathFromMount } from "./file-mount";
|
|
|
4
4
|
import { aiFileStore, normalizeAiFilePath } from "./files-store";
|
|
5
5
|
import { aiModelAccess } from "./model-access";
|
|
6
6
|
import { defineAiTool } from "./tools";
|
|
7
|
-
import { resolveAiAudioModel, runAiTranscription
|
|
7
|
+
import { type AiResolvedAudioModel, resolveAiAudioModel, runAiTranscription } from "./transcription";
|
|
8
8
|
|
|
9
9
|
export const CloudAiTranscribeAudioInputSchema = z.object({
|
|
10
10
|
path: z.string().trim().min(1).describe("Absolute path of the stored audio file. Project files are below /project."),
|