@capybara-hq/kernel 0.1.0-rc.12 → 0.1.0-rc.13
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/dist/admin/admin.schemas.d.ts +13 -0
- package/dist/agents/agent-lookup.d.ts +5 -0
- package/dist/agents/agent-lookup.js +15 -0
- package/dist/background/background.module.js +2 -2
- package/dist/background/launch-lock.d.ts +1 -1
- package/dist/background/launch-lock.js +1 -1
- package/dist/background/poll.job.d.ts +2 -2
- package/dist/background/poll.job.js +10 -10
- package/dist/background/stop.d.ts +1 -1
- package/dist/background/stop.js +3 -3
- package/dist/background/store.d.ts +5 -5
- package/dist/background/store.js +5 -5
- package/dist/background/tasks.service.d.ts +6 -6
- package/dist/background/tasks.service.js +13 -13
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/channels/channels.module.js +2 -2
- package/dist/channels/inbound.d.ts +8 -8
- package/dist/channels/inbound.js +31 -31
- package/dist/channels/phrases.d.ts +27 -27
- package/dist/channels/phrases.js +27 -27
- package/dist/config/docs.js +1 -1
- package/dist/config/schema.d.ts +2 -2
- package/dist/config/schema.js +21 -21
- package/dist/conversations/conversations.controller.d.ts +11 -11
- package/dist/conversations/conversations.controller.js +15 -15
- package/dist/conversations/conversations.module.d.ts +1 -1
- package/dist/conversations/conversations.module.js +7 -7
- package/dist/conversations/conversations.schemas.js +1 -1
- package/dist/conversations/conversations.service.d.ts +14 -14
- package/dist/conversations/conversations.service.js +19 -19
- package/dist/conversations/{environment-status.d.ts → device-status.d.ts} +9 -9
- package/dist/conversations/{environment-status.js → device-status.js} +18 -18
- package/dist/conversations/event-log.d.ts +1 -1
- package/dist/conversations/event-log.js +1 -1
- package/dist/conversations/events.d.ts +1 -1
- package/dist/conversations/events.js +2 -2
- package/dist/ctx/create-ctx.d.ts +10 -1
- package/dist/ctx/create-ctx.js +86 -25
- package/dist/db/kernel-schema.d.ts +26 -23
- package/dist/{environments → devices}/access.d.ts +19 -19
- package/dist/{environments → devices}/access.js +28 -28
- package/dist/devices/conversation-devices.d.ts +75 -0
- package/dist/{environments/conversation-environments.js → devices/conversation-devices.js} +71 -72
- package/dist/devices/devices.controller.d.ts +36 -0
- package/dist/{environments/environments.controller.js → devices/devices.controller.js} +37 -37
- package/dist/devices/devices.module.d.ts +5 -13
- package/dist/devices/devices.module.js +16 -29
- package/dist/devices/devices.schemas.d.ts +3 -0
- package/dist/devices/devices.schemas.js +14 -0
- package/dist/{environments/environments.service.d.ts → devices/devices.service.d.ts} +70 -70
- package/dist/{environments/environments.service.js → devices/devices.service.js} +122 -122
- package/dist/devices/dto.d.ts +13 -54
- package/dist/devices/dto.js +7 -39
- package/dist/devices/label.d.ts +11 -0
- package/dist/{environments → devices}/label.js +7 -7
- package/dist/{environments → devices}/legacy-cleanup.job.d.ts +3 -3
- package/dist/{environments → devices}/legacy-cleanup.job.js +3 -3
- package/dist/errors.d.ts +25 -25
- package/dist/errors.js +25 -25
- package/dist/evals/recorded-tools.d.ts +2 -2
- package/dist/evals/recorded-tools.js +6 -6
- package/dist/extensions/admin-ops.js +1 -0
- package/dist/extensions/admin-view.js +2 -0
- package/dist/extensions/effective-config.d.ts +6 -0
- package/dist/extensions/effective-config.js +18 -7
- package/dist/extensions/host.d.ts +13 -2
- package/dist/extensions/host.js +24 -3
- package/dist/extensions/instance.d.ts +12 -1
- package/dist/extensions/instance.js +13 -0
- package/dist/extensions/permission-view.d.ts +10 -1
- package/dist/files/previewers.d.ts +1 -1
- package/dist/files/previewers.js +1 -1
- package/dist/http/api.module.js +10 -6
- package/dist/http/gateway-routes.d.ts +1 -1
- package/dist/http/gateway-routes.js +1 -1
- package/dist/identity/settings.d.ts +32 -0
- package/dist/identity/settings.js +26 -2
- package/dist/infra/config/kernel-config.service.js +4 -4
- package/dist/kernel.module.js +3 -3
- package/dist/{devices → local-devices}/device-auth.controller.d.ts +6 -6
- package/dist/{devices → local-devices}/device-auth.controller.js +5 -5
- package/dist/{devices → local-devices}/device-auth.d.ts +20 -20
- package/dist/{devices → local-devices}/device-auth.js +51 -53
- package/dist/local-devices/device-auth.module.d.ts +7 -0
- package/dist/{devices → local-devices}/device-auth.module.js +5 -5
- package/dist/{devices → local-devices}/device-routes.d.ts +3 -3
- package/dist/{devices → local-devices}/device-routes.js +2 -2
- package/dist/local-devices/dto.d.ts +66 -0
- package/dist/local-devices/dto.js +44 -0
- package/dist/{devices → local-devices}/keys.d.ts +11 -11
- package/dist/{devices → local-devices}/keys.js +11 -11
- package/dist/{devices/device-gateway.d.ts → local-devices/local-device-gateway.d.ts} +14 -14
- package/dist/{devices/device-gateway.js → local-devices/local-device-gateway.js} +44 -44
- package/dist/{devices/device-provider.d.ts → local-devices/local-device-provider.d.ts} +15 -15
- package/dist/{devices/device-provider.js → local-devices/local-device-provider.js} +23 -23
- package/dist/local-devices/local-devices.module.d.ts +16 -0
- package/dist/local-devices/local-devices.module.js +44 -0
- package/dist/{devices → local-devices}/machines.d.ts +26 -26
- package/dist/{devices → local-devices}/machines.js +71 -71
- package/dist/{devices → local-devices}/relay.d.ts +15 -15
- package/dist/{devices → local-devices}/relay.js +55 -55
- package/dist/{devices → local-devices}/targets.d.ts +8 -8
- package/dist/{devices → local-devices}/targets.js +14 -14
- package/dist/orgs/offboarding.d.ts +6 -6
- package/dist/orgs/offboarding.js +12 -12
- package/dist/orgs/settings.service.d.ts +28 -0
- package/dist/orgs/settings.service.js +20 -1
- package/dist/platform/exclusions.js +38 -3
- package/dist/platform/operation.js +6 -2
- package/dist/platform/ops/admin.ops.js +8 -2
- package/dist/platform/ops/conversations.ops.d.ts +3 -3
- package/dist/platform/ops/conversations.ops.js +9 -9
- package/dist/platform/ops/devices.ops.d.ts +26 -0
- package/dist/platform/ops/devices.ops.js +311 -0
- package/dist/platform/ops/org-settings.ops.js +2 -2
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/ui.ops.d.ts +8 -0
- package/dist/platform/ops/ui.ops.js +41 -0
- package/dist/platform/platform-ops.module.d.ts +3 -2
- package/dist/platform/platform-ops.module.js +8 -5
- package/dist/platform/route-schemas.js +6 -3
- package/dist/roles/gateway-role.d.ts +3 -3
- package/dist/roles/gateway-role.js +4 -4
- package/dist/roles/gateway.module.d.ts +1 -1
- package/dist/roles/gateway.module.js +5 -5
- package/dist/roles/worker.module.js +1 -1
- package/dist/runtime/context/build.d.ts +1 -1
- package/dist/runtime/context/build.js +6 -6
- package/dist/runtime/context/compaction.js +1 -1
- package/dist/runtime/context/{environment-paths.d.ts → device-paths.d.ts} +11 -11
- package/dist/runtime/context/{environment-paths.js → device-paths.js} +13 -13
- package/dist/runtime/maintenance.d.ts +1 -1
- package/dist/sandbox/jobs.d.ts +13 -13
- package/dist/sandbox/jobs.js +20 -20
- package/dist/sandbox/leases.d.ts +9 -9
- package/dist/sandbox/leases.js +14 -14
- package/dist/sandbox/providers.d.ts +8 -8
- package/dist/sandbox/providers.js +8 -8
- package/dist/sandbox/sandbox.module.d.ts +1 -1
- package/dist/sandbox/sandbox.module.js +1 -1
- package/dist/sandbox/sandbox.service.d.ts +76 -76
- package/dist/sandbox/sandbox.service.js +230 -230
- package/dist/tools/builtins/builtin-tools.d.ts +2 -2
- package/dist/tools/builtins/builtin-tools.js +5 -5
- package/dist/tools/builtins/platform.js +1 -1
- package/dist/tools/builtins/sandbox/bash.js +6 -6
- package/dist/tools/builtins/sandbox/common.d.ts +25 -25
- package/dist/tools/builtins/sandbox/common.js +51 -51
- package/dist/tools/builtins/sandbox/edit.js +2 -2
- package/dist/tools/builtins/sandbox/index.d.ts +1 -1
- package/dist/tools/builtins/sandbox/index.js +1 -1
- package/dist/tools/builtins/sandbox/task-output.d.ts +6 -6
- package/dist/tools/builtins/sandbox/task-output.js +10 -10
- package/dist/tools/builtins/sandbox/task-stop.js +2 -2
- package/dist/tools/builtins/sandbox/write.js +2 -2
- package/dist/tools/builtins/skill-load.d.ts +1 -1
- package/dist/tools/builtins/skill-load.js +2 -2
- package/dist/tools/pipeline.js +4 -3
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/tools.module.js +3 -3
- package/dist/ui/admin-ui.controller.d.ts +56 -0
- package/dist/ui/admin-ui.controller.js +192 -0
- package/dist/ui/arrangement.d.ts +74 -0
- package/dist/ui/arrangement.js +98 -0
- package/dist/ui/embed-token.d.ts +2 -2
- package/dist/ui/embed-token.js +30 -5
- package/dist/ui/registry.d.ts +29 -14
- package/dist/ui/registry.js +31 -10
- package/dist/ui/slots.controller.d.ts +11 -0
- package/dist/ui/slots.controller.js +25 -0
- package/dist/ui/ui-stream.d.ts +20 -3
- package/dist/ui/ui-stream.js +25 -7
- package/dist/ui/ui.schemas.d.ts +66 -0
- package/dist/ui/ui.schemas.js +65 -0
- package/dist/ui/views.controller.d.ts +35 -49
- package/dist/ui/views.controller.js +48 -70
- package/dist/ui/views.d.ts +84 -18
- package/dist/ui/views.js +201 -38
- package/package.json +3 -3
- package/src/migrations/0105_devices_rename.sql +136 -0
- package/dist/devices/device-auth.module.d.ts +0 -7
- package/dist/environments/conversation-environments.d.ts +0 -75
- package/dist/environments/dto.d.ts +0 -25
- package/dist/environments/dto.js +0 -12
- package/dist/environments/environments.controller.d.ts +0 -36
- package/dist/environments/environments.module.d.ts +0 -8
- package/dist/environments/environments.module.js +0 -31
- package/dist/environments/environments.schemas.d.ts +0 -3
- package/dist/environments/environments.schemas.js +0 -14
- package/dist/environments/label.d.ts +0 -11
- package/dist/platform/ops/environments.ops.d.ts +0 -26
- package/dist/platform/ops/environments.ops.js +0 -311
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { rootsOf, ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { decodeTail, tailCommand } from "../../../background/session.js";
|
|
3
3
|
import { clip, display, mapSandboxError, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, } from "./common.js";
|
|
4
|
-
import { onTaskInstance, ownedTask,
|
|
4
|
+
import { onTaskInstance, ownedTask, recheckDevice, statusLine, taskEnv, } from "./task-output.js";
|
|
5
5
|
/** The time limit of reading a stopped task's last lines. */
|
|
6
6
|
const TAIL_EXEC_TIMEOUT_MS = 10_000;
|
|
7
7
|
const SPEC = {
|
|
@@ -84,7 +84,7 @@ export function taskStopTool(deps) {
|
|
|
84
84
|
const { task_id } = input;
|
|
85
85
|
const env = await taskEnv(deps, ctx);
|
|
86
86
|
const task = await ownedTask(env, ctx, task_id);
|
|
87
|
-
await
|
|
87
|
+
await recheckDevice(env, ctx);
|
|
88
88
|
if (task.status !== "running") {
|
|
89
89
|
return ended(env, task, "had already finished", ctx.signal);
|
|
90
90
|
}
|
|
@@ -46,10 +46,10 @@ export function sandboxWriteTool(deps) {
|
|
|
46
46
|
const data = new TextEncoder().encode(input.content);
|
|
47
47
|
checkPathShape(input.file_path, "file_path");
|
|
48
48
|
let path = "";
|
|
49
|
-
const { value, notices } = await inSandbox(deps, ctx, ({
|
|
49
|
+
const { value, notices } = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
|
|
50
50
|
path = checkWritable(input.file_path, roots);
|
|
51
51
|
// stat, read the previous content, write: one holder at a time per file
|
|
52
|
-
return withPathLease(deps, {
|
|
52
|
+
return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
|
|
53
53
|
const st = await p.stat(h, path, signal);
|
|
54
54
|
if (st?.isDir)
|
|
55
55
|
throw new ToolError(isDirectory(path));
|
|
@@ -8,7 +8,7 @@ import { type SandboxToolDeps } from "./sandbox/common.js";
|
|
|
8
8
|
* Builtin tool `Skill` (M5 T14, ruling M17): resolves the name against the latest skills catalog
|
|
9
9
|
* snapshot in the log to an id and version, and returns that version's SKILL.md body and file list
|
|
10
10
|
* (the result names the version). A skill with other files is copied whole into the conversation's
|
|
11
|
-
*
|
|
11
|
+
* device at `<skills root>/<name>@<digest>/` (the instance's skills root, `/skills` by default; the provider writes as root, commands read only; the
|
|
12
12
|
* digest is {@link skillDigest} of the version's file manifest, so versions of one skill coexist in
|
|
13
13
|
* the shared file system). Without a sandbox provider only the body is given, with a note that
|
|
14
14
|
* scripts are unavailable.
|
|
@@ -14,7 +14,7 @@ import { clip, display, inSandbox, sandboxBaseTimeoutMs, sandboxConversationOf,
|
|
|
14
14
|
* Builtin tool `Skill` (M5 T14, ruling M17): resolves the name against the latest skills catalog
|
|
15
15
|
* snapshot in the log to an id and version, and returns that version's SKILL.md body and file list
|
|
16
16
|
* (the result names the version). A skill with other files is copied whole into the conversation's
|
|
17
|
-
*
|
|
17
|
+
* device at `<skills root>/<name>@<digest>/` (the instance's skills root, `/skills` by default; the provider writes as root, commands read only; the
|
|
18
18
|
* digest is {@link skillDigest} of the version's file manifest, so versions of one skill coexist in
|
|
19
19
|
* the shared file system). Without a sandbox provider only the body is given, with a note that
|
|
20
20
|
* scripts are unavailable.
|
|
@@ -49,7 +49,7 @@ const SPEC = {
|
|
|
49
49
|
description: "Load a skill from the skills list: returns the skill's instructions " +
|
|
50
50
|
"(its SKILL.md) and the list of its files. Call it before starting a task that matches a " +
|
|
51
51
|
"skill's description, then follow the returned instructions. If the skill has scripts or other " +
|
|
52
|
-
"files, they are copied read-only to <name>@<version-id>/ in the skills directory of this
|
|
52
|
+
"files, they are copied read-only to <name>@<version-id>/ in the skills directory of this device; run scripts " +
|
|
53
53
|
"there with an interpreter (sh, python3, node). Call it again when the skills list changes. Pass args when the task gives the skill arguments (they replace " +
|
|
54
54
|
"$ARGUMENTS and $1, $2… in the instructions). Do not use it for skills that are not in the list.",
|
|
55
55
|
inputSchema: {
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -207,9 +207,10 @@ let ToolPipeline = class ToolPipeline {
|
|
|
207
207
|
},
|
|
208
208
|
effective,
|
|
209
209
|
switches,
|
|
210
|
-
inlineViews: (plugin) => new Set((host?.current(plugin)?.
|
|
211
|
-
.filter((
|
|
212
|
-
.
|
|
210
|
+
inlineViews: (plugin) => new Set((host?.current(plugin)?.ui ?? [])
|
|
211
|
+
.filter((r) => r.slot.id === "conversation.tool-result.card" &&
|
|
212
|
+
r.contribution.aliasOf === undefined)
|
|
213
|
+
.map((r) => r.contribution.id)),
|
|
213
214
|
onToolsChanged: (orgId, connectorId) => {
|
|
214
215
|
void drift?.onToolsChanged(orgId, connectorId);
|
|
215
216
|
},
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { BUILTIN_TOOL_NAMES } from "./builtin-names.js";
|
|
|
14
14
|
export { canonicalJson } from "./canonical-json.js";
|
|
15
15
|
/**
|
|
16
16
|
* What a tool's `unavailable` is asked with: the org, and for a run's listing the root
|
|
17
|
-
* conversation (`conversationId`; the sandbox tools follow its
|
|
17
|
+
* conversation (`conversationId`; the sandbox tools follow its device, ruling R8), whether
|
|
18
18
|
* the run is an eval replay (`replay`: its tool calls are never executed), and a `memo` shared by
|
|
19
19
|
* the tools of one listing (one answer per question per listing).
|
|
20
20
|
*/
|
|
@@ -346,7 +346,7 @@ export declare class ToolRegistry {
|
|
|
346
346
|
forkChild?: boolean;
|
|
347
347
|
/**
|
|
348
348
|
* The root conversation of the run (a sub-agent's tree root): tools whose availability
|
|
349
|
-
* depends on the conversation's
|
|
349
|
+
* depends on the conversation's device (the sandbox tools) are listed by it.
|
|
350
350
|
*/
|
|
351
351
|
sandboxConversationId?: string;
|
|
352
352
|
/** An eval replay's listing: its calls are never executed (the sandbox tools are listed). */
|
|
@@ -9,7 +9,7 @@ import { BackgroundModule } from "../background/background.module.js";
|
|
|
9
9
|
import { ConversationAccessModule } from "../conversations/access.js";
|
|
10
10
|
import { AttachmentsModule } from "../conversations/attachments.module.js";
|
|
11
11
|
import { EventLogModule } from "../conversations/event-log.js";
|
|
12
|
-
import {
|
|
12
|
+
import { DevicesModule } from "../devices/devices.module.js";
|
|
13
13
|
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
14
14
|
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
15
15
|
import { ModelsModule } from "../models/models.module.js";
|
|
@@ -59,9 +59,9 @@ ToolsModule = __decorate([
|
|
|
59
59
|
OrgLockModule,
|
|
60
60
|
// the `Agent` tool's dispatch
|
|
61
61
|
SubagentsModule,
|
|
62
|
-
// the builtin sandbox and skill tools run in the conversation's
|
|
62
|
+
// the builtin sandbox and skill tools run in the conversation's device
|
|
63
63
|
SandboxModule,
|
|
64
|
-
|
|
64
|
+
DevicesModule,
|
|
65
65
|
// `Bash` with run_in_background records its task; TaskOutput / TaskStop finish and stop it
|
|
66
66
|
BackgroundModule,
|
|
67
67
|
// the conversation page's tool metadata reads only what the viewer may see
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { UiActionResult, UiView } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import { ApiTokenService } from "../identity/api-tokens.js";
|
|
5
|
+
import { type Principal } from "../identity/principal.js";
|
|
6
|
+
import { SessionService } from "../identity/sessions.js";
|
|
7
|
+
import { EmbedTokens } from "./embed-token.js";
|
|
8
|
+
import { ContributionParams, PlatformActionBody, PlatformViewQuery } from "./ui.schemas.js";
|
|
9
|
+
import { OrgUiStreams } from "./ui-stream.js";
|
|
10
|
+
import { type UiArrangementSlot, type UiContributionItem, UiViews } from "./views.js";
|
|
11
|
+
/**
|
|
12
|
+
* Extension UI in the admin console (UI slots spec §7, platform scope): the `platform.page`
|
|
13
|
+
* contributions, for super admins only (`@SuperAdmin`, a person: service tokens are refused), with
|
|
14
|
+
* no org. A platform contribution needs its extension running (no org enablement applies) and is
|
|
15
|
+
* given the platform's deployment config; its render args carry `scope: "platform"`, `role:
|
|
16
|
+
* "superAdmin"` and no `orgId`.
|
|
17
|
+
* - `GET /api/v1/admin/ui/contributions` → `{ items }` (`platform.page` only, base order);
|
|
18
|
+
* - `GET /api/v1/admin/ui/arrangement` → `{ slots }` over every arrangeable slot, the platform's
|
|
19
|
+
* entries only, with every running extension's contributions (hidden ones marked);
|
|
20
|
+
* - `GET /api/v1/admin/ui/views/:plugin/:id?locale=` → `{ view }`;
|
|
21
|
+
* - `POST /api/v1/admin/ui/actions/:plugin/:id` (`{ actionId, payload?, answer?, locale? }`) →
|
|
22
|
+
* `{ toast }` | `{ view }`;
|
|
23
|
+
* - `POST /api/v1/admin/ui/embeds/:plugin/:id/token` → `{ token, src, expiresAt, expiresIn }`: a
|
|
24
|
+
* platform embed token (`scope: "platform"`, `role: "superAdmin"`, no `orgId`), which
|
|
25
|
+
* `ctx.ui.verifyEmbedToken` accepts only for a `platform.page` contribution while the account is
|
|
26
|
+
* still an active super admin;
|
|
27
|
+
* - `GET /api/v1/admin/ui/stream`: the platform refresh SSE (`event: ui`, topic `platform-ui`),
|
|
28
|
+
* super admin re-checked on the `runtime.sseRecheckMs` interval.
|
|
29
|
+
* A contribution in any other slot is 404 `ui.not_found`, the same as one that does not exist.
|
|
30
|
+
*/
|
|
31
|
+
export declare class AdminUiController {
|
|
32
|
+
private readonly views;
|
|
33
|
+
private readonly streams;
|
|
34
|
+
private readonly sessions;
|
|
35
|
+
private readonly tokens;
|
|
36
|
+
private readonly embeds;
|
|
37
|
+
constructor(views: UiViews, streams: OrgUiStreams, sessions: SessionService, tokens: ApiTokenService, embeds: EmbedTokens);
|
|
38
|
+
contributions(principal: Principal): Promise<{
|
|
39
|
+
items: UiContributionItem[];
|
|
40
|
+
}>;
|
|
41
|
+
arrangement(principal: Principal): Promise<{
|
|
42
|
+
slots: UiArrangementSlot[];
|
|
43
|
+
}>;
|
|
44
|
+
view(principal: Principal, { plugin, id }: z.infer<typeof ContributionParams>, q: z.infer<typeof PlatformViewQuery>): Promise<{
|
|
45
|
+
view: UiView;
|
|
46
|
+
}>;
|
|
47
|
+
action(principal: Principal, { plugin, id }: z.infer<typeof ContributionParams>, body: z.infer<typeof PlatformActionBody>): Promise<UiActionResult>;
|
|
48
|
+
embedToken(principal: Principal, { plugin, id }: z.infer<typeof ContributionParams>): Promise<{
|
|
49
|
+
token: string;
|
|
50
|
+
src: string;
|
|
51
|
+
expiresAt: string;
|
|
52
|
+
expiresIn: number;
|
|
53
|
+
}>;
|
|
54
|
+
/** The platform refresh stream: written on the raw response (`reply.hijack()`) like the org one. */
|
|
55
|
+
stream(principal: Principal, req: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Controller, Get, HttpCode, Inject, Post, Req, Res } from "@nestjs/common";
|
|
11
|
+
import { ApiTokenService } from "../identity/api-tokens.js";
|
|
12
|
+
import { credentialCheck } from "../identity/credential-check.js";
|
|
13
|
+
import { CurrentPrincipal, requireUser, SuperAdmin, } from "../identity/principal.js";
|
|
14
|
+
import { SessionService } from "../identity/sessions.js";
|
|
15
|
+
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
16
|
+
import { ZBody, ZParam, ZQuery } from "../infra/validation/zod-params.js";
|
|
17
|
+
import { EMBED_TOKEN_TTL_SECONDS, EmbedTokens } from "./embed-token.js";
|
|
18
|
+
import { ContributionParams, PlatformActionBody, PlatformViewQuery } from "./ui.schemas.js";
|
|
19
|
+
import { OrgUiStreams } from "./ui-stream.js";
|
|
20
|
+
import { BEARER_RE, embedSrc } from "./views.controller.js";
|
|
21
|
+
import { UiViews } from "./views.js";
|
|
22
|
+
class AdminUiContributionParamsDto extends createZodDto(ContributionParams, "AdminUiContributionParams") {
|
|
23
|
+
}
|
|
24
|
+
class AdminUiViewQueryDto extends createZodDto(PlatformViewQuery, "AdminUiViewQuery") {
|
|
25
|
+
}
|
|
26
|
+
class AdminUiActionBodyDto extends createZodDto(PlatformActionBody, "AdminUiActionBody") {
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extension UI in the admin console (UI slots spec §7, platform scope): the `platform.page`
|
|
30
|
+
* contributions, for super admins only (`@SuperAdmin`, a person: service tokens are refused), with
|
|
31
|
+
* no org. A platform contribution needs its extension running (no org enablement applies) and is
|
|
32
|
+
* given the platform's deployment config; its render args carry `scope: "platform"`, `role:
|
|
33
|
+
* "superAdmin"` and no `orgId`.
|
|
34
|
+
* - `GET /api/v1/admin/ui/contributions` → `{ items }` (`platform.page` only, base order);
|
|
35
|
+
* - `GET /api/v1/admin/ui/arrangement` → `{ slots }` over every arrangeable slot, the platform's
|
|
36
|
+
* entries only, with every running extension's contributions (hidden ones marked);
|
|
37
|
+
* - `GET /api/v1/admin/ui/views/:plugin/:id?locale=` → `{ view }`;
|
|
38
|
+
* - `POST /api/v1/admin/ui/actions/:plugin/:id` (`{ actionId, payload?, answer?, locale? }`) →
|
|
39
|
+
* `{ toast }` | `{ view }`;
|
|
40
|
+
* - `POST /api/v1/admin/ui/embeds/:plugin/:id/token` → `{ token, src, expiresAt, expiresIn }`: a
|
|
41
|
+
* platform embed token (`scope: "platform"`, `role: "superAdmin"`, no `orgId`), which
|
|
42
|
+
* `ctx.ui.verifyEmbedToken` accepts only for a `platform.page` contribution while the account is
|
|
43
|
+
* still an active super admin;
|
|
44
|
+
* - `GET /api/v1/admin/ui/stream`: the platform refresh SSE (`event: ui`, topic `platform-ui`),
|
|
45
|
+
* super admin re-checked on the `runtime.sseRecheckMs` interval.
|
|
46
|
+
* A contribution in any other slot is 404 `ui.not_found`, the same as one that does not exist.
|
|
47
|
+
*/
|
|
48
|
+
let AdminUiController = class AdminUiController {
|
|
49
|
+
views;
|
|
50
|
+
streams;
|
|
51
|
+
sessions;
|
|
52
|
+
tokens;
|
|
53
|
+
embeds;
|
|
54
|
+
constructor(views, streams, sessions, tokens, embeds) {
|
|
55
|
+
this.views = views;
|
|
56
|
+
this.streams = streams;
|
|
57
|
+
this.sessions = sessions;
|
|
58
|
+
this.tokens = tokens;
|
|
59
|
+
this.embeds = embeds;
|
|
60
|
+
}
|
|
61
|
+
async contributions(principal) {
|
|
62
|
+
requireUser(principal);
|
|
63
|
+
return { items: await this.views.platformContributions() };
|
|
64
|
+
}
|
|
65
|
+
async arrangement(principal) {
|
|
66
|
+
requireUser(principal);
|
|
67
|
+
return { slots: await this.views.platformArrangementView() };
|
|
68
|
+
}
|
|
69
|
+
async view(principal, { plugin, id }, q) {
|
|
70
|
+
const p = requireUser(principal);
|
|
71
|
+
const view = await this.views.render({
|
|
72
|
+
scope: "platform",
|
|
73
|
+
accountId: p.account.id,
|
|
74
|
+
plugin,
|
|
75
|
+
id,
|
|
76
|
+
locale: q.locale,
|
|
77
|
+
});
|
|
78
|
+
return { view };
|
|
79
|
+
}
|
|
80
|
+
async action(principal, { plugin, id }, body) {
|
|
81
|
+
const p = requireUser(principal);
|
|
82
|
+
return this.views.act({ scope: "platform", accountId: p.account.id, plugin, id, locale: body.locale }, {
|
|
83
|
+
actionId: body.actionId,
|
|
84
|
+
...(body.payload !== undefined && { payload: body.payload }),
|
|
85
|
+
...(body.answer !== undefined && { answer: body.answer }),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async embedToken(principal, { plugin, id }) {
|
|
89
|
+
const p = requireUser(principal);
|
|
90
|
+
const target = await this.views.embed({
|
|
91
|
+
scope: "platform",
|
|
92
|
+
accountId: p.account.id,
|
|
93
|
+
plugin,
|
|
94
|
+
id,
|
|
95
|
+
});
|
|
96
|
+
const { token, expiresAt } = this.embeds.issue({
|
|
97
|
+
plugin: target.plugin,
|
|
98
|
+
scope: "platform",
|
|
99
|
+
accountId: p.account.id,
|
|
100
|
+
role: "superAdmin",
|
|
101
|
+
contribution: target.contribution,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
token,
|
|
105
|
+
src: embedSrc(target.plugin, target.path),
|
|
106
|
+
expiresAt: expiresAt.toISOString(),
|
|
107
|
+
expiresIn: EMBED_TOKEN_TTL_SECONDS,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The platform refresh stream: written on the raw response (`reply.hijack()`) like the org one. */
|
|
111
|
+
async stream(principal, req, reply) {
|
|
112
|
+
const p = requireUser(principal);
|
|
113
|
+
const res = reply.raw;
|
|
114
|
+
const handle = await this.streams.open({
|
|
115
|
+
scope: "platform",
|
|
116
|
+
viewerAccountId: p.account.id,
|
|
117
|
+
credential: credentialCheck(p, BEARER_RE.exec(req.headers.authorization ?? "")?.[1], {
|
|
118
|
+
sessions: this.sessions,
|
|
119
|
+
tokens: this.tokens,
|
|
120
|
+
}),
|
|
121
|
+
}, {
|
|
122
|
+
write: (chunk) => {
|
|
123
|
+
res.write(chunk);
|
|
124
|
+
},
|
|
125
|
+
backlog: () => res.writableLength,
|
|
126
|
+
end: () => {
|
|
127
|
+
res.end();
|
|
128
|
+
},
|
|
129
|
+
abort: () => {
|
|
130
|
+
res.destroy();
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
reply.hijack();
|
|
134
|
+
res.on("close", () => handle.close());
|
|
135
|
+
if (res.destroyed || !res.socket || res.socket.destroyed) {
|
|
136
|
+
handle.close();
|
|
137
|
+
res.destroy();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
res.writeHead(200, {
|
|
141
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
142
|
+
"cache-control": "no-cache",
|
|
143
|
+
"x-accel-buffering": "no",
|
|
144
|
+
connection: "keep-alive",
|
|
145
|
+
});
|
|
146
|
+
res.flushHeaders();
|
|
147
|
+
handle.begin();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
__decorate([
|
|
151
|
+
Get("contributions"),
|
|
152
|
+
__param(0, CurrentPrincipal())
|
|
153
|
+
], AdminUiController.prototype, "contributions", null);
|
|
154
|
+
__decorate([
|
|
155
|
+
Get("arrangement"),
|
|
156
|
+
__param(0, CurrentPrincipal())
|
|
157
|
+
], AdminUiController.prototype, "arrangement", null);
|
|
158
|
+
__decorate([
|
|
159
|
+
Get("views/:plugin/:id"),
|
|
160
|
+
__param(0, CurrentPrincipal()),
|
|
161
|
+
__param(1, ZParam(AdminUiContributionParamsDto)),
|
|
162
|
+
__param(2, ZQuery(AdminUiViewQueryDto))
|
|
163
|
+
], AdminUiController.prototype, "view", null);
|
|
164
|
+
__decorate([
|
|
165
|
+
Post("actions/:plugin/:id"),
|
|
166
|
+
HttpCode(200),
|
|
167
|
+
__param(0, CurrentPrincipal()),
|
|
168
|
+
__param(1, ZParam(AdminUiContributionParamsDto)),
|
|
169
|
+
__param(2, ZBody(AdminUiActionBodyDto))
|
|
170
|
+
], AdminUiController.prototype, "action", null);
|
|
171
|
+
__decorate([
|
|
172
|
+
Post("embeds/:plugin/:id/token"),
|
|
173
|
+
HttpCode(200),
|
|
174
|
+
__param(0, CurrentPrincipal()),
|
|
175
|
+
__param(1, ZParam(AdminUiContributionParamsDto))
|
|
176
|
+
], AdminUiController.prototype, "embedToken", null);
|
|
177
|
+
__decorate([
|
|
178
|
+
Get("stream"),
|
|
179
|
+
__param(0, CurrentPrincipal()),
|
|
180
|
+
__param(1, Req()),
|
|
181
|
+
__param(2, Res())
|
|
182
|
+
], AdminUiController.prototype, "stream", null);
|
|
183
|
+
AdminUiController = __decorate([
|
|
184
|
+
Controller("api/v1/admin/ui"),
|
|
185
|
+
SuperAdmin(),
|
|
186
|
+
__param(0, Inject(UiViews)),
|
|
187
|
+
__param(1, Inject(OrgUiStreams)),
|
|
188
|
+
__param(2, Inject(SessionService)),
|
|
189
|
+
__param(3, Inject(ApiTokenService)),
|
|
190
|
+
__param(4, Inject(EmbedTokens))
|
|
191
|
+
], AdminUiController);
|
|
192
|
+
export { AdminUiController };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type UiSlotId } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { UiContributionItem } from "./views.js";
|
|
4
|
+
export declare const UiArrangementEntrySchema: z.ZodObject<{
|
|
5
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
6
|
+
order: z.ZodArray<z.ZodString>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export type UiArrangementEntry = z.infer<typeof UiArrangementEntrySchema>;
|
|
9
|
+
export declare const UiArrangementSchema: z.ZodRecord<z.ZodEnum<{
|
|
10
|
+
"agent.detail.section": "agent.detail.section";
|
|
11
|
+
"conversation.panel": "conversation.panel";
|
|
12
|
+
"conversation.properties.section": "conversation.properties.section";
|
|
13
|
+
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
14
|
+
"file.preview": "file.preview";
|
|
15
|
+
"org.page": "org.page";
|
|
16
|
+
"org.settings.section": "org.settings.section";
|
|
17
|
+
"platform.page": "platform.page";
|
|
18
|
+
}> & z.core.$partial, z.ZodObject<{
|
|
19
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
20
|
+
order: z.ZodArray<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>>;
|
|
22
|
+
export type UiArrangement = z.infer<typeof UiArrangementSchema>;
|
|
23
|
+
/** The slots an org arranges: every arrangeable slot but the platform's own. */
|
|
24
|
+
export declare const ORG_ARRANGEABLE: readonly UiSlotId[];
|
|
25
|
+
/** An org's patch: a slot's entry replaces the stored one, null removes it. */
|
|
26
|
+
export declare const OrgUiArrangementPatchSchema: z.ZodRecord<z.ZodEnum<{
|
|
27
|
+
"agent.detail.section": "agent.detail.section";
|
|
28
|
+
"conversation.panel": "conversation.panel";
|
|
29
|
+
"conversation.properties.section": "conversation.properties.section";
|
|
30
|
+
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
31
|
+
"file.preview": "file.preview";
|
|
32
|
+
"org.page": "org.page";
|
|
33
|
+
"org.settings.section": "org.settings.section";
|
|
34
|
+
"platform.page": "platform.page";
|
|
35
|
+
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
36
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
37
|
+
order: z.ZodArray<z.ZodString>;
|
|
38
|
+
}, z.core.$strict>>>;
|
|
39
|
+
/** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
|
|
40
|
+
export declare const UiArrangementPatchSchema: z.ZodRecord<z.ZodEnum<{
|
|
41
|
+
"agent.detail.section": "agent.detail.section";
|
|
42
|
+
"conversation.panel": "conversation.panel";
|
|
43
|
+
"conversation.properties.section": "conversation.properties.section";
|
|
44
|
+
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
45
|
+
"file.preview": "file.preview";
|
|
46
|
+
"org.page": "org.page";
|
|
47
|
+
"org.settings.section": "org.settings.section";
|
|
48
|
+
"platform.page": "platform.page";
|
|
49
|
+
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
50
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
51
|
+
order: z.ZodArray<z.ZodString>;
|
|
52
|
+
}, z.core.$strict>>>;
|
|
53
|
+
/** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
|
|
54
|
+
export declare function mergeArrangement(current: UiArrangement | undefined, patch: Partial<Record<UiSlotId, UiArrangementEntry | null>>): UiArrangement;
|
|
55
|
+
/** The entry that applies to a slot: the org's if it arranged the slot, else the platform's. */
|
|
56
|
+
export declare function effectiveEntry(org: UiArrangement | undefined, platform: UiArrangement, slot: UiSlotId): {
|
|
57
|
+
entry: UiArrangementEntry;
|
|
58
|
+
source: "org" | "platform" | "none";
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
|
|
62
|
+
* order, also without an entry (the lists match what the page renders per anchor); within a group
|
|
63
|
+
* the keys of `entry.order` come first in that order, the rest in base order. Unknown keys are
|
|
64
|
+
* ignored, so an order never moves an item across anchors.
|
|
65
|
+
*/
|
|
66
|
+
export declare function orderItems(items: UiContributionItem[], entry: UiArrangementEntry): UiContributionItem[];
|
|
67
|
+
/** {@link orderItems} without the hidden keys. */
|
|
68
|
+
export declare function arrange(items: UiContributionItem[], entry: UiArrangementEntry): UiContributionItem[];
|
|
69
|
+
/**
|
|
70
|
+
* Applies the per-slot entries to a mixed list: each slot's items are arranged and written back
|
|
71
|
+
* into the positions that slot occupied (a slot without an entry: its base order, grouped by
|
|
72
|
+
* anchor).
|
|
73
|
+
*/
|
|
74
|
+
export declare function arrangeBySlot(items: UiContributionItem[], entryOf: (slot: UiSlotId) => UiArrangementEntry): UiContributionItem[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { UI_ARRANGEABLE_SLOTS, UI_CONTRIBUTION_KEY_RE, uiSlot, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* Arrangement of UI slots (UI slots spec §5): per slot, which contributions are hidden and in
|
|
5
|
+
* which order. "Hidden" means not listed: the access gate never reads an arrangement, so a hidden
|
|
6
|
+
* contribution's view, action and embed routes keep working for an allowed role.
|
|
7
|
+
*/
|
|
8
|
+
const Key = z.string().regex(UI_CONTRIBUTION_KEY_RE);
|
|
9
|
+
const LIST_MAX = 200;
|
|
10
|
+
export const UiArrangementEntrySchema = z
|
|
11
|
+
.object({ hidden: z.array(Key).max(LIST_MAX), order: z.array(Key).max(LIST_MAX) })
|
|
12
|
+
.strict();
|
|
13
|
+
export const UiArrangementSchema = z.partialRecord(z.enum(UI_ARRANGEABLE_SLOTS), UiArrangementEntrySchema);
|
|
14
|
+
/** The slots an org arranges: every arrangeable slot but the platform's own. */
|
|
15
|
+
export const ORG_ARRANGEABLE = UI_ARRANGEABLE_SLOTS.filter((s) => s !== "platform.page");
|
|
16
|
+
/** An org's patch: a slot's entry replaces the stored one, null removes it. */
|
|
17
|
+
export const OrgUiArrangementPatchSchema = z.partialRecord(z.enum(ORG_ARRANGEABLE), UiArrangementEntrySchema.nullable());
|
|
18
|
+
/** A deployment patch: like {@link OrgUiArrangementPatchSchema} over every arrangeable slot. */
|
|
19
|
+
export const UiArrangementPatchSchema = z.partialRecord(z.enum(UI_ARRANGEABLE_SLOTS), UiArrangementEntrySchema.nullable());
|
|
20
|
+
/** Merges a per-slot patch into the stored arrangement (null removes the slot's entry). */
|
|
21
|
+
export function mergeArrangement(current, patch) {
|
|
22
|
+
const merged = { ...current };
|
|
23
|
+
for (const [slot, entry] of Object.entries(patch)) {
|
|
24
|
+
if (entry === null)
|
|
25
|
+
delete merged[slot];
|
|
26
|
+
else if (entry !== undefined)
|
|
27
|
+
merged[slot] = entry;
|
|
28
|
+
}
|
|
29
|
+
return merged;
|
|
30
|
+
}
|
|
31
|
+
const NONE = { hidden: [], order: [] };
|
|
32
|
+
/** The entry that applies to a slot: the org's if it arranged the slot, else the platform's. */
|
|
33
|
+
export function effectiveEntry(org, platform, slot) {
|
|
34
|
+
const o = org?.[slot];
|
|
35
|
+
if (o !== undefined)
|
|
36
|
+
return { entry: o, source: "org" };
|
|
37
|
+
const p = platform[slot];
|
|
38
|
+
if (p !== undefined)
|
|
39
|
+
return { entry: p, source: "platform" };
|
|
40
|
+
return { entry: NONE, source: "none" };
|
|
41
|
+
}
|
|
42
|
+
const keyOf = (i) => `${i.plugin}/${i.id}`;
|
|
43
|
+
/**
|
|
44
|
+
* Orders the items of one slot (hidden ones stay in place): grouped by anchor in the slot's anchor
|
|
45
|
+
* order, also without an entry (the lists match what the page renders per anchor); within a group
|
|
46
|
+
* the keys of `entry.order` come first in that order, the rest in base order. Unknown keys are
|
|
47
|
+
* ignored, so an order never moves an item across anchors.
|
|
48
|
+
*/
|
|
49
|
+
export function orderItems(items, entry) {
|
|
50
|
+
const anchors = items[0] === undefined ? undefined : uiSlot(items[0].slot)?.anchors;
|
|
51
|
+
const groupOf = (i) => anchors === undefined ? 0 : Math.max(0, anchors.indexOf(i.after ?? anchors[0] ?? ""));
|
|
52
|
+
const rank = new Map(entry.order.map((k, n) => [k, n]));
|
|
53
|
+
return items
|
|
54
|
+
.map((item, base) => ({ item, base }))
|
|
55
|
+
.sort((a, b) => {
|
|
56
|
+
const g = groupOf(a.item) - groupOf(b.item);
|
|
57
|
+
if (g !== 0)
|
|
58
|
+
return g;
|
|
59
|
+
const ra = rank.get(keyOf(a.item));
|
|
60
|
+
const rb = rank.get(keyOf(b.item));
|
|
61
|
+
if (ra !== undefined && rb !== undefined)
|
|
62
|
+
return ra - rb;
|
|
63
|
+
if (ra !== undefined)
|
|
64
|
+
return -1;
|
|
65
|
+
if (rb !== undefined)
|
|
66
|
+
return 1;
|
|
67
|
+
return a.base - b.base;
|
|
68
|
+
})
|
|
69
|
+
.map((x) => x.item);
|
|
70
|
+
}
|
|
71
|
+
/** {@link orderItems} without the hidden keys. */
|
|
72
|
+
export function arrange(items, entry) {
|
|
73
|
+
const hidden = new Set(entry.hidden);
|
|
74
|
+
return orderItems(items, entry).filter((i) => !hidden.has(keyOf(i)));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Applies the per-slot entries to a mixed list: each slot's items are arranged and written back
|
|
78
|
+
* into the positions that slot occupied (a slot without an entry: its base order, grouped by
|
|
79
|
+
* anchor).
|
|
80
|
+
*/
|
|
81
|
+
export function arrangeBySlot(items, entryOf) {
|
|
82
|
+
const bySlot = new Map();
|
|
83
|
+
items.forEach((i, n) => {
|
|
84
|
+
const at = bySlot.get(i.slot);
|
|
85
|
+
if (at)
|
|
86
|
+
at.push(n);
|
|
87
|
+
else
|
|
88
|
+
bySlot.set(i.slot, [n]);
|
|
89
|
+
});
|
|
90
|
+
const out = [...items];
|
|
91
|
+
for (const [slot, at] of bySlot) {
|
|
92
|
+
const arranged = arrange(at.map((n) => items[n]), entryOf(slot));
|
|
93
|
+
at.forEach((n, j) => {
|
|
94
|
+
out[n] = arranged[j];
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return out.filter((i) => i !== undefined);
|
|
98
|
+
}
|
package/dist/ui/embed-token.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EmbedClaims } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { EmbedClaims, OrgEmbedClaims, PlatformEmbedClaims } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { Keyring } from "../ctx/secrets.js";
|
|
3
3
|
/** 嵌入令牌的有效期(秒)。网页在过期前 60 秒续期。 */
|
|
4
4
|
export declare const EMBED_TOKEN_TTL_SECONDS = 300;
|
|
@@ -22,7 +22,7 @@ export declare class EmbedTokens {
|
|
|
22
22
|
private readonly keys;
|
|
23
23
|
constructor(keyring: Keyring, now?: () => number);
|
|
24
24
|
/** 签发:`exp` = 现在 + {@link EMBED_TOKEN_TTL_SECONDS}。 */
|
|
25
|
-
issue(c: Omit<
|
|
25
|
+
issue(c: Omit<OrgEmbedClaims, "exp"> | Omit<PlatformEmbedClaims, "exp">): {
|
|
26
26
|
token: string;
|
|
27
27
|
claims: EmbedClaims;
|
|
28
28
|
expiresAt: Date;
|
package/dist/ui/embed-token.js
CHANGED
|
@@ -24,10 +24,12 @@ const B64URL = /^[A-Za-z0-9_-]+$/;
|
|
|
24
24
|
const PLUGIN_RE = /^[a-z][a-z0-9-]{1,40}$/;
|
|
25
25
|
const CLAIM_KEYS = new Set([
|
|
26
26
|
"plugin",
|
|
27
|
+
"scope",
|
|
27
28
|
"orgId",
|
|
28
29
|
"accountId",
|
|
29
30
|
"role",
|
|
30
31
|
"conversationId",
|
|
32
|
+
"agentId",
|
|
31
33
|
"fileId",
|
|
32
34
|
"contribution",
|
|
33
35
|
"exp",
|
|
@@ -44,7 +46,11 @@ function decodeStrict(s) {
|
|
|
44
46
|
const buf = Buffer.from(s, "base64url");
|
|
45
47
|
return buf.toString("base64url") === s ? buf : null;
|
|
46
48
|
}
|
|
47
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* 逐字段核对声明:只认已知字段、类型正确,返回只含已知字段的新对象。The scope decides the shape:
|
|
51
|
+
* `"platform"` exactly when `orgId` is absent and `role` is `"superAdmin"`, with no
|
|
52
|
+
* `conversationId` / `agentId` / `fileId`; `"org"` needs an `orgId` and one of the org roles.
|
|
53
|
+
*/
|
|
48
54
|
function parseClaims(v) {
|
|
49
55
|
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
50
56
|
return null;
|
|
@@ -53,22 +59,41 @@ function parseClaims(v) {
|
|
|
53
59
|
return null;
|
|
54
60
|
if (typeof c.plugin !== "string" || !PLUGIN_RE.test(c.plugin))
|
|
55
61
|
return null;
|
|
56
|
-
if (!isId(c.
|
|
62
|
+
if (!isId(c.accountId) || !isId(c.contribution))
|
|
57
63
|
return null;
|
|
58
|
-
if (!
|
|
64
|
+
if (typeof c.exp !== "number" || !Number.isSafeInteger(c.exp))
|
|
65
|
+
return null;
|
|
66
|
+
if (c.scope === "platform") {
|
|
67
|
+
if (c.orgId !== undefined || c.role !== "superAdmin")
|
|
68
|
+
return null;
|
|
69
|
+
if (c.conversationId !== undefined || c.agentId !== undefined || c.fileId !== undefined) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
plugin: c.plugin,
|
|
74
|
+
scope: "platform",
|
|
75
|
+
accountId: c.accountId,
|
|
76
|
+
role: "superAdmin",
|
|
77
|
+
contribution: c.contribution,
|
|
78
|
+
exp: c.exp,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (c.scope !== "org" || !isId(c.orgId) || !ROLES.has(c.role))
|
|
59
82
|
return null;
|
|
60
83
|
if (c.conversationId !== undefined && !isId(c.conversationId))
|
|
61
84
|
return null;
|
|
62
|
-
if (c.
|
|
85
|
+
if (c.agentId !== undefined && (!isId(c.agentId) || c.conversationId !== undefined))
|
|
63
86
|
return null;
|
|
64
|
-
if (
|
|
87
|
+
if (c.fileId !== undefined && (!isId(c.fileId) || c.conversationId === undefined))
|
|
65
88
|
return null;
|
|
66
89
|
return {
|
|
67
90
|
plugin: c.plugin,
|
|
91
|
+
scope: "org",
|
|
68
92
|
orgId: c.orgId,
|
|
69
93
|
accountId: c.accountId,
|
|
70
94
|
role: c.role,
|
|
71
95
|
...(c.conversationId !== undefined && { conversationId: c.conversationId }),
|
|
96
|
+
...(c.agentId !== undefined && { agentId: c.agentId }),
|
|
72
97
|
...(c.fileId !== undefined && { fileId: c.fileId }),
|
|
73
98
|
contribution: c.contribution,
|
|
74
99
|
exp: c.exp,
|