@capybara-hq/kernel 0.1.0-rc.12 → 0.1.0-rc.14
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/admin-catalog.controller.d.ts +13 -2
- package/dist/catalog/admin-catalog.controller.js +24 -3
- package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
- package/dist/catalog/admin-catalog.schemas.js +7 -0
- package/dist/catalog/catalog-content.d.ts +9 -1
- package/dist/catalog/catalog-content.js +2 -1
- package/dist/catalog/catalog-files.d.ts +3 -2
- package/dist/catalog/catalog-files.js +8 -5
- package/dist/catalog/component-scope.service.d.ts +14 -1
- package/dist/catalog/component-scope.service.js +12 -2
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.controller.d.ts +5 -4
- package/dist/catalog/org-catalog.controller.js +5 -4
- package/dist/catalog/org-catalog.schemas.d.ts +18 -2
- package/dist/catalog/org-catalog.schemas.js +16 -2
- package/dist/catalog/org-catalog.service.js +8 -2
- package/dist/catalog/plugin-import.js +1 -1
- package/dist/catalog/plugin-package.js +37 -9
- package/dist/catalog/plugin-upgrade.d.ts +15 -1
- package/dist/catalog/plugin-upgrade.js +18 -5
- package/dist/channels/channels.module.js +2 -2
- package/dist/channels/connections.controller.d.ts +1 -1
- package/dist/channels/connections.controller.js +1 -1
- package/dist/channels/connections.service.d.ts +22 -5
- package/dist/channels/connections.service.js +56 -10
- 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/channels/webhook.d.ts +21 -1
- package/dist/channels/webhook.js +39 -4
- 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 +26 -3
- package/dist/ctx/create-ctx.js +134 -26
- package/dist/db/kernel-schema.d.ts +37 -25
- 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 +29 -25
- package/dist/errors.js +29 -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/guide-places.d.ts +66 -0
- package/dist/extensions/guide-places.js +152 -0
- package/dist/extensions/host.d.ts +13 -2
- package/dist/extensions/host.js +27 -6
- package/dist/extensions/instance.d.ts +17 -2
- package/dist/extensions/instance.js +21 -0
- package/dist/extensions/ledger.d.ts +1 -1
- package/dist/extensions/permission-set.d.ts +16 -2
- package/dist/extensions/permission-set.js +17 -3
- package/dist/extensions/permission-view.d.ts +10 -1
- package/dist/extensions/plugin-guides.d.ts +207 -54
- package/dist/extensions/plugin-guides.js +436 -165
- package/dist/files/previewers.d.ts +1 -1
- package/dist/files/previewers.js +1 -1
- package/dist/http/api.module.js +16 -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/infra/lock/lock.service.d.ts +8 -0
- package/dist/infra/lock/lock.service.js +27 -0
- package/dist/infra/throttler/named-throttlers.d.ts +4 -1
- package/dist/infra/throttler/named-throttlers.js +16 -1
- package/dist/infra/throttler/throttler.guard.js +1 -1
- 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/overrides.d.ts +3 -1
- package/dist/overrides.js +2 -0
- package/dist/platform/exclusions.js +48 -8
- package/dist/platform/operation.js +10 -2
- package/dist/platform/ops/admin.ops.js +8 -2
- package/dist/platform/ops/connectors.ops.js +2 -0
- 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/setup.ops.d.ts +24 -0
- package/dist/platform/ops/setup.ops.js +212 -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 +4 -2
- package/dist/platform/platform-ops.module.js +13 -5
- package/dist/platform/route-schemas.js +11 -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/setup/kernel-checks.d.ts +91 -0
- package/dist/setup/kernel-checks.js +295 -0
- package/dist/setup/oauth-token-probe.d.ts +47 -0
- package/dist/setup/oauth-token-probe.js +108 -0
- package/dist/setup/redact-check.d.ts +18 -0
- package/dist/setup/redact-check.js +46 -0
- package/dist/setup/setup-check-registry.d.ts +86 -0
- package/dist/setup/setup-check-registry.js +75 -0
- package/dist/setup/setup-checks.controller.d.ts +28 -0
- package/dist/setup/setup-checks.controller.js +78 -0
- package/dist/setup/setup-checks.schemas.d.ts +38 -0
- package/dist/setup/setup-checks.schemas.js +43 -0
- package/dist/setup/setup-checks.service.d.ts +144 -0
- package/dist/setup/setup-checks.service.js +683 -0
- package/dist/setup/setup-guides.controller.d.ts +18 -0
- package/dist/setup/setup-guides.controller.js +48 -0
- package/dist/setup/setup-guides.schemas.d.ts +10 -0
- package/dist/setup/setup-guides.schemas.js +6 -0
- package/dist/setup/setup-guides.service.d.ts +126 -0
- package/dist/setup/setup-guides.service.js +315 -0
- package/dist/setup/setup.module.d.ts +8 -0
- package/dist/setup/setup.module.js +31 -0
- 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/connectors.controller.d.ts +28 -11
- package/dist/tools/connectors.controller.js +49 -14
- package/dist/tools/connectors.schemas.d.ts +4 -0
- package/dist/tools/connectors.schemas.js +4 -0
- package/dist/tools/connectors.service.d.ts +30 -2
- package/dist/tools/connectors.service.js +98 -3
- package/dist/tools/credentials.d.ts +20 -0
- package/dist/tools/credentials.js +90 -1
- package/dist/tools/oauth/tokens.d.ts +20 -0
- package/dist/tools/oauth/tokens.js +49 -24
- 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/src/migrations/0106_setup_guides_v2.sql +10 -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
package/dist/ui/views.js
CHANGED
|
@@ -7,21 +7,27 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { UI_ARRANGEABLE_SLOTS, validateUiView, } from "@capybara-hq/plugin-sdk";
|
|
11
11
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
12
|
import { sql } from "kysely";
|
|
13
|
+
import { isAgentVisibleIn } from "../agents/agent-lookup.js";
|
|
13
14
|
import { ConversationsService } from "../conversations/conversations.service.js";
|
|
14
15
|
import { ApiError } from "../errors.js";
|
|
15
16
|
import { EffectiveConfigs, NEEDS_CONFIG_REASON } from "../extensions/effective-config.js";
|
|
16
17
|
import { ExtensionHost } from "../extensions/host.js";
|
|
17
18
|
import { PreviewerRegistry } from "../files/previewers.js";
|
|
19
|
+
import { readUiArrangement } from "../identity/settings.js";
|
|
18
20
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
19
21
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
22
|
+
import { readOrgSettings } from "../orgs/settings.service.js";
|
|
23
|
+
import { arrange, arrangeBySlot, effectiveEntry, ORG_ARRANGEABLE, orderItems, } from "./arrangement.js";
|
|
20
24
|
import { callExtension } from "./call-extension.js";
|
|
21
|
-
import {
|
|
25
|
+
import { canUseSlot, UUID_RE } from "./registry.js";
|
|
22
26
|
export { callExtension } from "./call-extension.js";
|
|
23
27
|
/** 插件 render / action 的时限(spec §4.2:慢的插件只让那个标签失败)。 */
|
|
24
28
|
export const UI_CALL_TIMEOUT_MS = 5_000;
|
|
29
|
+
/** The log field naming where a target is: its org, or the platform scope. */
|
|
30
|
+
const whereOf = (t) => t.scope === "platform" ? { scope: "platform" } : { orgId: t.orgId };
|
|
25
31
|
/**
|
|
26
32
|
* The card a `tool_result` of the conversation recorded for `callId`, or null (the inline-card
|
|
27
33
|
* attestation of {@link UiViewsDeps.recordedCard}). Scans one conversation's tool results.
|
|
@@ -66,6 +72,37 @@ export function dropForeignOpens(view, panels) {
|
|
|
66
72
|
const { open, ...rest } = view;
|
|
67
73
|
return { ...rest, ...(open !== undefined && ok(open) && { open }), blocks };
|
|
68
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Lists contributions for a listing route: no alias, a view's render registered; base order
|
|
77
|
+
* (spec §5): extension name, then manifest order (stable).
|
|
78
|
+
*/
|
|
79
|
+
function listed(entries) {
|
|
80
|
+
return entries
|
|
81
|
+
.filter((c) => c.contribution.aliasOf === undefined && (c.contribution.kind === "embed" || c.render))
|
|
82
|
+
.map((c, i) => ({ c, i }))
|
|
83
|
+
.sort((x, y) => (x.c.plugin === y.c.plugin ? x.i - y.i : x.c.plugin < y.c.plugin ? -1 : 1))
|
|
84
|
+
.map(({ c: { plugin, contribution: c, slot: s, after } }) => ({
|
|
85
|
+
plugin,
|
|
86
|
+
id: c.id,
|
|
87
|
+
title: c.title,
|
|
88
|
+
slot: s.id,
|
|
89
|
+
...(after !== undefined && { after }),
|
|
90
|
+
kind: c.kind,
|
|
91
|
+
...(c.kind === "embed" && c.path !== undefined && { path: c.path }),
|
|
92
|
+
icon: c.icon ?? "puzzle",
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** The items in effective order, each marked hidden or not (hidden ones stay in place). */
|
|
96
|
+
function withHidden(items, entry) {
|
|
97
|
+
const hidden = new Set(entry.hidden);
|
|
98
|
+
return orderItems(items, entry).map((i) => ({ ...i, hidden: hidden.has(`${i.plugin}/${i.id}`) }));
|
|
99
|
+
}
|
|
100
|
+
/** A platform route's contribution: found, and in `platform.page`; else 404. */
|
|
101
|
+
function platformEntry(entry) {
|
|
102
|
+
if (entry === undefined || entry.slot.id !== "platform.page")
|
|
103
|
+
throw notFound();
|
|
104
|
+
return entry;
|
|
105
|
+
}
|
|
69
106
|
const TOAST_MAX = 500;
|
|
70
107
|
const MAX_LOGGED_PROBLEMS = 10;
|
|
71
108
|
const notFound = () => new ApiError("ui.not_found", "UI contribution not found");
|
|
@@ -91,9 +128,11 @@ function actionResultProblems(r) {
|
|
|
91
128
|
/**
|
|
92
129
|
* 插件视图(第一档:渲染 schema):列出贡献、调用插件 `render` / `action`。
|
|
93
130
|
* - 只认在该组织已启用、且已在本副本加载的插件(启用状态每次查库);
|
|
94
|
-
* -
|
|
95
|
-
*
|
|
96
|
-
*
|
|
131
|
+
* - every request goes through one gate (`resolve`): the slot's `minRole` (never `platform.page`),
|
|
132
|
+
* then its scope rule (conversation: readable, like the SSE stream; agent: readable and in this
|
|
133
|
+
* org; org: the role alone); refused is 404 `ui.not_found`, the same as not existing;
|
|
134
|
+
* - a view takes only the context id of its scope (`conversationId` / `agentId`): a missing one or
|
|
135
|
+
* one of another scope is 400 `validation_failed`;
|
|
97
136
|
* - 调用带 5 秒时限与中止信号:render 抛错 502 `ui.render_failed`、action 抛错 502 `ui.action_failed`、
|
|
98
137
|
* 超时 504 `ui.timeout`、返回非法视图 502
|
|
99
138
|
* `ui.invalid_view`,都记告警日志、不写对话日志(这些不是模型可见的内容)。
|
|
@@ -108,57 +147,125 @@ let UiViews = class UiViews {
|
|
|
108
147
|
// org config rows plus the required bootstrap plugins
|
|
109
148
|
enabledPlugins: (orgId) => host.enabledPlugins(orgId),
|
|
110
149
|
canAccessConversation: (orgId, id, accountId) => conversations.isAccessible(orgId, id, accountId),
|
|
150
|
+
canReadAgent: (orgId, agentId, accountId) => isAgentVisibleIn(db.db, orgId, agentId, accountId),
|
|
111
151
|
checkWritable: async (orgId, id, accountId) => {
|
|
112
152
|
await conversations.writable(orgId, id, accountId);
|
|
113
153
|
},
|
|
114
154
|
previewEmbedAllowed: (t) => previewers.embedAllowed(t),
|
|
115
155
|
recordedCard: (conversationId, callId) => recordedCard(db.db, conversationId, callId),
|
|
156
|
+
orgArrangement: async (orgId) => (await readOrgSettings(db.db, orgId)).uiArrangement,
|
|
157
|
+
platformArrangement: () => readUiArrangement(db.db),
|
|
116
158
|
log,
|
|
117
159
|
};
|
|
118
160
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
161
|
+
/**
|
|
162
|
+
* The contributions the role may use in the org, of one slot or of every org-reachable slot:
|
|
163
|
+
* the extension enabled and loaded, a view's render registered, the slot's `minRole`
|
|
164
|
+
* ({@link canUseSlot}, so never `platform.page`); no alias, no `file.preview` contribution (the
|
|
165
|
+
* file previewers list those). Base order (spec §5): extension name, then manifest order.
|
|
166
|
+
*/
|
|
167
|
+
async contributions(orgId, role, slot) {
|
|
168
|
+
const items = await this.orgBase(orgId, role, slot);
|
|
169
|
+
if (items.length === 0)
|
|
170
|
+
return items;
|
|
171
|
+
const [org, platform] = await Promise.all([
|
|
172
|
+
this.d.orgArrangement(orgId),
|
|
173
|
+
this.d.platformArrangement(),
|
|
174
|
+
]);
|
|
175
|
+
return arrangeBySlot(items, (s) => effectiveEntry(org, platform, s).entry);
|
|
176
|
+
}
|
|
177
|
+
/** {@link contributions} before the arrangement: what the role may use, in base order. */
|
|
178
|
+
async orgBase(orgId, role, slot) {
|
|
121
179
|
const all = this.d.host.uiContributions();
|
|
122
180
|
if (all.length === 0)
|
|
123
181
|
return [];
|
|
124
182
|
const enabled = await this.d.enabledPlugins(orgId);
|
|
125
|
-
return all
|
|
126
|
-
.
|
|
127
|
-
c.
|
|
128
|
-
c.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
183
|
+
return listed(all.filter((c) => enabled.has(c.plugin) &&
|
|
184
|
+
c.slot.id !== "file.preview" &&
|
|
185
|
+
(slot === undefined || c.slot.id === slot) &&
|
|
186
|
+
canUseSlot(c.slot, role)));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The arrangement page of an org: every org-arrangeable slot with the contributions the role may
|
|
190
|
+
* use, in effective order with the hidden ones in place, and where the entry comes from.
|
|
191
|
+
*/
|
|
192
|
+
async arrangementView(orgId, role) {
|
|
193
|
+
// the page is for people who arrange or at least see the org's UI, not for a guest
|
|
194
|
+
if (role === "guest")
|
|
195
|
+
throw notFound();
|
|
196
|
+
const [base, org, platform] = await Promise.all([
|
|
197
|
+
this.orgBase(orgId, role, undefined),
|
|
198
|
+
this.d.orgArrangement(orgId),
|
|
199
|
+
this.d.platformArrangement(),
|
|
200
|
+
]);
|
|
201
|
+
return ORG_ARRANGEABLE.map((slot) => {
|
|
202
|
+
const { entry, source } = effectiveEntry(org, platform, slot);
|
|
203
|
+
return {
|
|
204
|
+
slot,
|
|
205
|
+
source,
|
|
206
|
+
items: withHidden(base.filter((i) => i.slot === slot), entry),
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The platform's arrangement page: every arrangeable slot with the contributions of every running
|
|
212
|
+
* extension (whatever its org enablement), ordered by the platform entry, hidden ones in place.
|
|
213
|
+
*/
|
|
214
|
+
async platformArrangementView() {
|
|
215
|
+
const [base, platform] = await Promise.all([
|
|
216
|
+
listed(this.d.host.uiContributions().filter((c) => c.slot.id !== "file.preview")),
|
|
217
|
+
this.d.platformArrangement(),
|
|
218
|
+
]);
|
|
219
|
+
return UI_ARRANGEABLE_SLOTS.map((slot) => {
|
|
220
|
+
const { entry, source } = effectiveEntry(undefined, platform, slot);
|
|
221
|
+
return {
|
|
222
|
+
slot,
|
|
223
|
+
source,
|
|
224
|
+
items: withHidden(base.filter((i) => i.slot === slot), entry),
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The `platform.page` contributions of the running extensions, for the admin console (super
|
|
230
|
+
* admins; no org enablement applies): no alias, a view's render registered. Base order like
|
|
231
|
+
* {@link contributions}.
|
|
232
|
+
*/
|
|
233
|
+
async platformContributions() {
|
|
234
|
+
const items = listed(this.d.host.uiContributions().filter((c) => c.slot.id === "platform.page"));
|
|
235
|
+
const { entry } = effectiveEntry(undefined, await this.d.platformArrangement(), "platform.page");
|
|
236
|
+
return arrange(items, entry);
|
|
141
237
|
}
|
|
142
238
|
/**
|
|
143
239
|
* 嵌入应用:核对贡献是 embed、插件在该组织启用;对话嵌入要求调用者能访问该对话(否则 404,与视图一致),
|
|
144
240
|
* 组织页面不收 `conversationId`。返回签发令牌所需的目标(令牌由调用方签)。
|
|
145
241
|
*/
|
|
146
242
|
async embed(t) {
|
|
147
|
-
|
|
148
|
-
|
|
243
|
+
let fileId;
|
|
244
|
+
let target;
|
|
245
|
+
if (t.scope === "platform")
|
|
246
|
+
target = { ...t, locale: "en" };
|
|
247
|
+
else {
|
|
248
|
+
const { fileId: f, ...org } = t;
|
|
249
|
+
fileId = f;
|
|
250
|
+
target = { ...org, locale: "en" };
|
|
251
|
+
}
|
|
252
|
+
const { entry, args } = await this.resolve(target, "embed");
|
|
149
253
|
const path = entry.contribution.path;
|
|
150
254
|
if (path === undefined)
|
|
151
255
|
throw notFound();
|
|
152
|
-
const preview = entry.
|
|
256
|
+
const preview = entry.slot.id === "file.preview";
|
|
153
257
|
if (preview !== (fileId !== undefined)) {
|
|
154
258
|
throw new ApiError("validation_failed", preview
|
|
155
259
|
? "fileId is required for a file-preview embed"
|
|
156
260
|
: "fileId is only allowed for a file-preview embed");
|
|
157
261
|
}
|
|
158
262
|
if (fileId !== undefined) {
|
|
263
|
+
// a file-preview embed is an org's (a platform target never carries a fileId)
|
|
264
|
+
if (args.orgId === undefined)
|
|
265
|
+
throw notFound();
|
|
159
266
|
const allowed = this.d.previewEmbedAllowed !== undefined &&
|
|
160
267
|
(await this.d.previewEmbedAllowed({
|
|
161
|
-
orgId:
|
|
268
|
+
orgId: args.orgId,
|
|
162
269
|
accountId: t.accountId,
|
|
163
270
|
conversationId: args.conversationId,
|
|
164
271
|
fileId,
|
|
@@ -173,6 +280,7 @@ let UiViews = class UiViews {
|
|
|
173
280
|
contribution: t.id,
|
|
174
281
|
path,
|
|
175
282
|
...(args.conversationId !== undefined && { conversationId: args.conversationId }),
|
|
283
|
+
...(args.agentId !== undefined && { agentId: args.agentId }),
|
|
176
284
|
...(fileId !== undefined && { fileId }),
|
|
177
285
|
};
|
|
178
286
|
}
|
|
@@ -205,10 +313,20 @@ let UiViews = class UiViews {
|
|
|
205
313
|
panels(plugin) {
|
|
206
314
|
return new Set(this.d.host
|
|
207
315
|
.uiContributions()
|
|
208
|
-
.filter((c) => c.plugin === plugin &&
|
|
316
|
+
.filter((c) => c.plugin === plugin && c.slot.id === "conversation.panel")
|
|
209
317
|
.map((c) => c.contribution.id));
|
|
210
318
|
}
|
|
211
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* The single access gate of the org UI routes (UI slots spec §7): finds the contribution (a
|
|
321
|
+
* retired alias answers with the one it names), then checks, in order, that its slot is reachable
|
|
322
|
+
* from an org (not `platform`), the role meets the slot's `minRole`, the extension is enabled in
|
|
323
|
+
* the org, its effective config is valid, and the slot's scope rule (conversation: readable;
|
|
324
|
+
* agent: readable and in this org; org: the role alone; file: a conversation the caller reads,
|
|
325
|
+
* the previewer's grant is checked by `embed`). A platform target (the admin routes) takes the
|
|
326
|
+
* platform gate instead ({@link platformArgs}): only a `platform.page` contribution. Returns the
|
|
327
|
+
* args passed to the extension (no signal). Anything refused for a contribution that exists is
|
|
328
|
+
* 404 `ui.not_found`.
|
|
329
|
+
*/
|
|
212
330
|
async resolve(t, need) {
|
|
213
331
|
const loaded = this.d.host.uiContributions();
|
|
214
332
|
const named = loaded.find((c) => c.plugin === t.plugin && c.contribution.id === t.id);
|
|
@@ -221,8 +339,13 @@ let UiViews = class UiViews {
|
|
|
221
339
|
(need === "embed"
|
|
222
340
|
? c.contribution.kind === "embed"
|
|
223
341
|
: c.contribution.kind === "view" && c[need] !== undefined));
|
|
224
|
-
if (
|
|
342
|
+
if (t.scope === "platform") {
|
|
343
|
+
const found = platformEntry(entry);
|
|
344
|
+
return { entry: found, args: this.platformArgs(t, found) };
|
|
345
|
+
}
|
|
346
|
+
if (!entry || entry.slot.scope === "platform" || !canUseSlot(entry.slot, t.role)) {
|
|
225
347
|
throw notFound();
|
|
348
|
+
}
|
|
226
349
|
if (!(await this.d.enabledPlugins(t.orgId)).has(t.plugin))
|
|
227
350
|
throw notFound();
|
|
228
351
|
// spec A §6: the extension is not called for an org whose effective config is not valid
|
|
@@ -230,25 +353,46 @@ let UiViews = class UiViews {
|
|
|
230
353
|
if (!effective.ok) {
|
|
231
354
|
throw new ApiError("extension.needs_config", `Extension ${t.plugin} ${NEEDS_CONFIG_REASON}`);
|
|
232
355
|
}
|
|
356
|
+
const scope = entry.slot.scope;
|
|
233
357
|
const base = {
|
|
234
358
|
orgId: t.orgId,
|
|
235
359
|
accountId: t.accountId,
|
|
236
360
|
role: t.role,
|
|
361
|
+
slot: entry.slot.id,
|
|
362
|
+
scope,
|
|
237
363
|
locale: t.locale,
|
|
238
364
|
config: effective.config,
|
|
239
365
|
};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
366
|
+
const what = need === "embed" ? "embed" : "view";
|
|
367
|
+
const conversational = scope === "conversation" || scope === "file";
|
|
368
|
+
if (!conversational && (t.item !== undefined || t.card !== undefined)) {
|
|
369
|
+
throw new ApiError("validation_failed", "item and callId are for conversation views");
|
|
370
|
+
}
|
|
371
|
+
if (scope !== "agent" && t.agentId !== undefined) {
|
|
372
|
+
throw new ApiError("validation_failed", `agentId is only allowed for an agent ${what}`);
|
|
373
|
+
}
|
|
374
|
+
if (scope === "agent") {
|
|
375
|
+
if (t.conversationId !== undefined) {
|
|
376
|
+
throw new ApiError("validation_failed", `conversationId is not allowed for an agent ${what}`);
|
|
377
|
+
}
|
|
378
|
+
const agentId = t.agentId;
|
|
379
|
+
if (agentId === undefined) {
|
|
380
|
+
throw new ApiError("validation_failed", `agentId is required for an agent ${what}`);
|
|
381
|
+
}
|
|
382
|
+
if (!UUID_RE.test(agentId) || !(await this.d.canReadAgent(t.orgId, agentId, t.accountId))) {
|
|
383
|
+
throw notFound();
|
|
243
384
|
}
|
|
385
|
+
return { entry, args: { ...base, agentId } };
|
|
386
|
+
}
|
|
387
|
+
if (!conversational) {
|
|
244
388
|
if (t.conversationId !== undefined) {
|
|
245
|
-
throw new ApiError("validation_failed", `conversationId is not allowed for an organization page ${
|
|
389
|
+
throw new ApiError("validation_failed", `conversationId is not allowed for an organization page ${what}`);
|
|
246
390
|
}
|
|
247
391
|
return { entry, args: base };
|
|
248
392
|
}
|
|
249
393
|
const conversationId = t.conversationId;
|
|
250
394
|
if (conversationId === undefined) {
|
|
251
|
-
throw new ApiError("validation_failed", `conversationId is required for a conversation ${
|
|
395
|
+
throw new ApiError("validation_failed", `conversationId is required for a conversation ${what}`);
|
|
252
396
|
}
|
|
253
397
|
if (!UUID_RE.test(conversationId) ||
|
|
254
398
|
!(await this.d.canAccessConversation(t.orgId, conversationId, t.accountId))) {
|
|
@@ -260,7 +404,7 @@ let UiViews = class UiViews {
|
|
|
260
404
|
if (t.card !== undefined) {
|
|
261
405
|
// args.card is attested: an inline view, and a tool_result of this conversation recorded this
|
|
262
406
|
// very card (plugin, the requested id or its alias target, key); else it is not found
|
|
263
|
-
const recorded =
|
|
407
|
+
const recorded = entry.slot.id === "conversation.tool-result.card" && this.d.recordedCard !== undefined
|
|
264
408
|
? await this.d.recordedCard(conversationId, t.card.callId)
|
|
265
409
|
: null;
|
|
266
410
|
if (recorded === null ||
|
|
@@ -280,13 +424,32 @@ let UiViews = class UiViews {
|
|
|
280
424
|
},
|
|
281
425
|
};
|
|
282
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The platform gate (UI slots spec §7): the contribution is a `platform.page` of a running
|
|
429
|
+
* extension (no org enablement applies), given the platform's deployment config. The caller is a
|
|
430
|
+
* super admin (`@SuperAdmin` on the admin routes); anything else is 404 `ui.not_found`.
|
|
431
|
+
*/
|
|
432
|
+
platformArgs(t, entry) {
|
|
433
|
+
const effective = this.d.effective.platform(t.plugin);
|
|
434
|
+
// `platform()` fails only when the extension is not running here: not found
|
|
435
|
+
if (!effective.ok)
|
|
436
|
+
throw notFound();
|
|
437
|
+
return {
|
|
438
|
+
scope: "platform",
|
|
439
|
+
accountId: t.accountId,
|
|
440
|
+
role: "superAdmin",
|
|
441
|
+
slot: entry.slot.id,
|
|
442
|
+
locale: t.locale,
|
|
443
|
+
config: effective.config,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
283
446
|
/** 带时限调用插件:超时中止信号并 504;抛错 502(render / action 各一个码,不回显插件的异常信息)。 */
|
|
284
447
|
call(t, what, fn) {
|
|
285
448
|
return callExtension({
|
|
286
449
|
log: this.d.log,
|
|
287
450
|
plugin: t.plugin,
|
|
288
451
|
what,
|
|
289
|
-
fields: { plugin: t.plugin, contribution: t.id,
|
|
452
|
+
fields: { plugin: t.plugin, contribution: t.id, ...whereOf(t) },
|
|
290
453
|
timeoutMs: UI_CALL_TIMEOUT_MS,
|
|
291
454
|
failCode: what === "render" ? "ui.render_failed" : "ui.action_failed",
|
|
292
455
|
failMessage: `Plugin ${t.plugin} failed to ${what} ${t.id}`,
|
|
@@ -300,7 +463,7 @@ let UiViews = class UiViews {
|
|
|
300
463
|
this.d.log.warn(`plugin ui ${what} returned an invalid view`, {
|
|
301
464
|
plugin: t.plugin,
|
|
302
465
|
contribution: t.id,
|
|
303
|
-
|
|
466
|
+
...whereOf(t),
|
|
304
467
|
problems: problems.slice(0, MAX_LOGGED_PROBLEMS),
|
|
305
468
|
});
|
|
306
469
|
throw new ApiError("ui.invalid_view", `Plugin ${t.plugin} returned an invalid view`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.14",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"yauzl": "3.4.0",
|
|
67
67
|
"zod": "^4.6.7",
|
|
68
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.10"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@types/ws": "^8.18.2",
|
|
75
75
|
"@types/yauzl": "3.4.0",
|
|
76
76
|
"ws": "^8.22.0",
|
|
77
|
-
"@capybara-hq/testing": "0.1.0-rc.
|
|
77
|
+
"@capybara-hq/testing": "0.1.0-rc.13"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build": "tsc -b"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
-- One name: devices (spec 2026-11-07-environments-design §15.2). Every environment table, column,
|
|
2
|
+
-- index and constraint takes the device name; a connected machine's source 'device' becomes
|
|
3
|
+
-- 'local', the kernel-internal provider '@device' becomes '@local', and the log event
|
|
4
|
+
-- 'environment_paths' becomes 'device_paths' (in the log and in eval case snapshots). The kernel
|
|
5
|
+
-- is renamed in the same release; before 0.1.0, so no compatibility layer and no rolling deploy
|
|
6
|
+
-- across this migration. Every statement is guarded, so a rerun changes nothing.
|
|
7
|
+
|
|
8
|
+
-- capybara:allow-destructive table rename; the kernel is renamed in the same release (not released, no compatibility layer)
|
|
9
|
+
ALTER TABLE IF EXISTS environments RENAME TO devices;
|
|
10
|
+
-- capybara:allow-destructive table rename; the kernel is renamed in the same release (not released, no compatibility layer)
|
|
11
|
+
ALTER TABLE IF EXISTS environment_instances RENAME TO device_instances;
|
|
12
|
+
-- capybara:allow-destructive table rename; the kernel is renamed in the same release (not released, no compatibility layer)
|
|
13
|
+
ALTER TABLE IF EXISTS environment_defaults RENAME TO device_defaults;
|
|
14
|
+
-- capybara:allow-destructive table rename; the kernel is renamed in the same release (not released, no compatibility layer)
|
|
15
|
+
ALTER TABLE IF EXISTS environment_machines RENAME TO device_machines;
|
|
16
|
+
|
|
17
|
+
-- RENAME COLUMN has no IF EXISTS: checked against information_schema first.
|
|
18
|
+
-- capybara:allow-destructive column rename environment_id -> device_id; the kernel is renamed in the same release (not released)
|
|
19
|
+
DO $$
|
|
20
|
+
DECLARE
|
|
21
|
+
t text;
|
|
22
|
+
BEGIN
|
|
23
|
+
FOREACH t IN ARRAY ARRAY['device_instances', 'device_defaults', 'device_machines',
|
|
24
|
+
'conversations', 'background_tasks'] LOOP
|
|
25
|
+
IF EXISTS (
|
|
26
|
+
SELECT 1 FROM information_schema.columns
|
|
27
|
+
WHERE table_schema = 'capybara' AND table_name = t AND column_name = 'environment_id'
|
|
28
|
+
) THEN
|
|
29
|
+
EXECUTE format('ALTER TABLE capybara.%I RENAME COLUMN environment_id TO device_id', t);
|
|
30
|
+
END IF;
|
|
31
|
+
END LOOP;
|
|
32
|
+
END
|
|
33
|
+
$$;
|
|
34
|
+
|
|
35
|
+
-- Indexes, including those behind the primary keys and unique constraints (renaming such an
|
|
36
|
+
-- index renames its constraint too).
|
|
37
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
38
|
+
ALTER INDEX IF EXISTS environments_pkey RENAME TO devices_pkey;
|
|
39
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
40
|
+
ALTER INDEX IF EXISTS environments_default_uq RENAME TO devices_default_uq;
|
|
41
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
42
|
+
ALTER INDEX IF EXISTS environments_name_uq RENAME TO devices_name_uq;
|
|
43
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
44
|
+
ALTER INDEX IF EXISTS environment_instances_pkey RENAME TO device_instances_pkey;
|
|
45
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
46
|
+
ALTER INDEX IF EXISTS environment_instances_running_idx RENAME TO device_instances_running_idx;
|
|
47
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
48
|
+
ALTER INDEX IF EXISTS environment_instances_live_idx RENAME TO device_instances_live_idx;
|
|
49
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
50
|
+
ALTER INDEX IF EXISTS environment_instances_org_idx RENAME TO device_instances_org_idx;
|
|
51
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
52
|
+
ALTER INDEX IF EXISTS environment_defaults_pkey RENAME TO device_defaults_pkey;
|
|
53
|
+
-- capybara:allow-destructive index rename; names follow the column rename (not released)
|
|
54
|
+
ALTER INDEX IF EXISTS background_tasks_environment_running_idx RENAME TO background_tasks_device_running_idx;
|
|
55
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
56
|
+
ALTER INDEX IF EXISTS environment_machines_pkey RENAME TO device_machines_pkey;
|
|
57
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
58
|
+
ALTER INDEX IF EXISTS environment_machines_credential_hash_key RENAME TO device_machines_credential_hash_key;
|
|
59
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
60
|
+
ALTER INDEX IF EXISTS environment_machines_active_uq RENAME TO device_machines_active_uq;
|
|
61
|
+
-- capybara:allow-destructive index rename; names follow the table rename (not released)
|
|
62
|
+
ALTER INDEX IF EXISTS environment_machines_org_idx RENAME TO device_machines_org_idx;
|
|
63
|
+
|
|
64
|
+
-- NOT NULL constraints are unnamed on PostgreSQL 17 (the target), so nothing to rename there.
|
|
65
|
+
-- The other CHECK and foreign key constraints: RENAME CONSTRAINT has no IF EXISTS, so each is
|
|
66
|
+
-- checked against pg_constraint first. The source CHECK is not renamed here: it is replaced
|
|
67
|
+
-- below with the new value list.
|
|
68
|
+
-- capybara:allow-destructive constraint rename; names follow the table and column renames (not released)
|
|
69
|
+
DO $$
|
|
70
|
+
DECLARE
|
|
71
|
+
r record;
|
|
72
|
+
BEGIN
|
|
73
|
+
FOR r IN
|
|
74
|
+
SELECT * FROM (VALUES
|
|
75
|
+
('devices', 'environments_org_id_fkey', 'devices_org_id_fkey'),
|
|
76
|
+
('devices', 'environments_owner_account_id_fkey', 'devices_owner_account_id_fkey'),
|
|
77
|
+
('devices', 'environments_owner_kind_check', 'devices_owner_kind_check'),
|
|
78
|
+
('device_instances', 'environment_instances_fs_status_check', 'device_instances_fs_status_check'),
|
|
79
|
+
('device_instances', 'environment_instances_status_check', 'device_instances_status_check'),
|
|
80
|
+
('device_instances', 'environment_instances_environment_id_fkey', 'device_instances_device_id_fkey'),
|
|
81
|
+
('device_defaults', 'environment_defaults_environment_id_fkey', 'device_defaults_device_id_fkey'),
|
|
82
|
+
('conversations', 'conversations_environment_id_fkey', 'conversations_device_id_fkey'),
|
|
83
|
+
('device_machines', 'environment_machines_mode_check', 'device_machines_mode_check'),
|
|
84
|
+
('device_machines', 'environment_machines_org_id_fkey', 'device_machines_org_id_fkey'),
|
|
85
|
+
('device_machines', 'environment_machines_environment_id_fkey', 'device_machines_device_id_fkey')
|
|
86
|
+
) AS v(tbl, old_name, new_name)
|
|
87
|
+
LOOP
|
|
88
|
+
IF EXISTS (
|
|
89
|
+
SELECT 1 FROM pg_constraint c
|
|
90
|
+
JOIN pg_namespace s ON s.oid = c.connamespace
|
|
91
|
+
WHERE s.nspname = 'capybara' AND c.conname = r.old_name
|
|
92
|
+
) THEN
|
|
93
|
+
EXECUTE format('ALTER TABLE capybara.%I RENAME CONSTRAINT %I TO %I',
|
|
94
|
+
r.tbl, r.old_name, r.new_name);
|
|
95
|
+
END IF;
|
|
96
|
+
END LOOP;
|
|
97
|
+
END
|
|
98
|
+
$$;
|
|
99
|
+
|
|
100
|
+
-- A connected machine's source 'device' becomes 'local': the old CHECK goes, the rows change, and
|
|
101
|
+
-- the CHECK comes back under the new name with the new list.
|
|
102
|
+
ALTER TABLE devices DROP CONSTRAINT IF EXISTS environments_source_check;
|
|
103
|
+
ALTER TABLE devices DROP CONSTRAINT IF EXISTS devices_source_check;
|
|
104
|
+
UPDATE devices SET source = 'local' WHERE source = 'device';
|
|
105
|
+
ALTER TABLE devices
|
|
106
|
+
ADD CONSTRAINT devices_source_check CHECK (source IN ('cloud', 'local'));
|
|
107
|
+
|
|
108
|
+
-- The kernel-internal provider '@device' becomes '@local' wherever a provider name is stored.
|
|
109
|
+
UPDATE devices SET provider = '@local' WHERE provider = '@device';
|
|
110
|
+
UPDATE device_instances SET provider = '@local' WHERE provider = '@device';
|
|
111
|
+
UPDATE device_instances SET prev_provider = '@local' WHERE prev_provider = '@device';
|
|
112
|
+
|
|
113
|
+
-- The log event 'environment_paths' becomes 'device_paths' (its idempotency key prefix too); the
|
|
114
|
+
-- rows are updated first, so the old partial index (0094) finds them, then the index follows.
|
|
115
|
+
UPDATE conversation_events
|
|
116
|
+
SET type = 'device_paths',
|
|
117
|
+
idem_key = CASE
|
|
118
|
+
WHEN idem_key LIKE 'environment_paths:%' THEN 'device_paths:' || substr(idem_key, 19)
|
|
119
|
+
ELSE idem_key
|
|
120
|
+
END
|
|
121
|
+
WHERE type = 'environment_paths';
|
|
122
|
+
DROP INDEX IF EXISTS conversation_events_environment_paths_idx;
|
|
123
|
+
CREATE INDEX IF NOT EXISTS conversation_events_device_paths_idx
|
|
124
|
+
ON conversation_events (conversation_id, seq)
|
|
125
|
+
WHERE type = 'device_paths';
|
|
126
|
+
|
|
127
|
+
-- Eval case snapshots carry the events of their run: the same event type change, order kept.
|
|
128
|
+
-- snapshot_bytes is not recomputed: only type names change (a few bytes shorter), harmless.
|
|
129
|
+
UPDATE eval_cases
|
|
130
|
+
SET snapshot = jsonb_set(snapshot, '{events}', (
|
|
131
|
+
SELECT jsonb_agg(
|
|
132
|
+
CASE WHEN e->>'type' = 'environment_paths' THEN jsonb_set(e, '{type}', '"device_paths"')
|
|
133
|
+
ELSE e END
|
|
134
|
+
ORDER BY ord)
|
|
135
|
+
FROM jsonb_array_elements(snapshot->'events') WITH ORDINALITY AS t(e, ord)))
|
|
136
|
+
WHERE snapshot @? '$.events[*] ? (@.type == "environment_paths")';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Setup guides v2 (spec 2026-11-09-setup-guides-v2-design.md §11, §15).
|
|
2
|
+
-- A connector made from a connector template records the setup guide it was set up with:
|
|
3
|
+
-- { guideId, version, hash, mode } (mode null: the place has no modes), recorded when the
|
|
4
|
+
-- connector is created from the template and when an org admin confirms the third-party app was
|
|
5
|
+
-- updated. NULL = unknown (connectors before this column, custom ones): no drift notice for those.
|
|
6
|
+
ALTER TABLE connectors ADD COLUMN IF NOT EXISTS guide_ack jsonb;
|
|
7
|
+
-- The v1 acks of channel connections hold v1 template hashes that no v2 hash can equal; keeping
|
|
8
|
+
-- them would show every existing connection a false update notice. Cleared ("no ack, no notice"):
|
|
9
|
+
-- those connections start tracking again at their next mode switch or ack.
|
|
10
|
+
UPDATE channel_connections SET guide_ack = NULL WHERE guide_ack IS NOT NULL;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Device sign-in (environments spec §13.3): {@link DeviceAuthService} behind
|
|
3
|
-
* `DeviceAuthController`. Kept apart from {@link DevicesModule}, which `EnvironmentsModule`
|
|
4
|
-
* imports (deleting an environment revokes its machine), while sign-in needs both.
|
|
5
|
-
*/
|
|
6
|
-
export declare class DeviceAuthModule {
|
|
7
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { ApiError } from "../errors.js";
|
|
2
|
-
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
3
|
-
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
4
|
-
import { SandboxProviders } from "../sandbox/providers.js";
|
|
5
|
-
import { EnvironmentAccess } from "./access.js";
|
|
6
|
-
import { EnvironmentsService } from "./environments.service.js";
|
|
7
|
-
/** `details[0].reason` of the error an anonymous IM conversation's sandbox call gets. */
|
|
8
|
-
export declare const ANONYMOUS_NO_ENVIRONMENT = "anonymous";
|
|
9
|
-
/** Anonymous IM senders have no environment (spec §4.2 item 8): their sandbox tools are off. */
|
|
10
|
-
export declare const anonymousNoEnvironment: () => ApiError;
|
|
11
|
-
/** How a new conversation picks its environment. */
|
|
12
|
-
export interface NewConversationEnvironment {
|
|
13
|
-
/** The environment the person chose (the composer's picker); checked with `usable`. */
|
|
14
|
-
environmentId?: string;
|
|
15
|
-
/** An IM thread: the person's IM default (`environment_defaults`) when still usable. */
|
|
16
|
-
im?: boolean;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Which environment a conversation works in (spec 2026-11-07-environments-design §4.1 items 1, 3;
|
|
20
|
-
* §4.2 items 8, 9, 12). A conversation is bound once, to an environment of its owner
|
|
21
|
-
* (`conversations.account_id`): the one chosen at its first send, else the owner's IM default
|
|
22
|
-
* (IM threads), else the owner's default environment. Its sub-agents use the root's. Access is
|
|
23
|
-
* checked when it is bound and again on every sandbox tool call ({@link forTool}); only the owner
|
|
24
|
-
* may use a personal environment, so a conversation never reaches another person's.
|
|
25
|
-
*/
|
|
26
|
-
export declare class ConversationEnvironments {
|
|
27
|
-
private readonly db;
|
|
28
|
-
readonly txHost: TransactionHost;
|
|
29
|
-
private readonly providers;
|
|
30
|
-
private readonly access;
|
|
31
|
-
private readonly environments;
|
|
32
|
-
constructor(db: KernelDb, txHost: TransactionHost, providers: Pick<SandboxProviders, "cloud"> & Partial<Pick<SandboxProviders, "forName">>, access: Pick<EnvironmentAccess, "usable">, environments: Pick<EnvironmentsService, "ensureDefault">);
|
|
33
|
-
/**
|
|
34
|
-
* The environment a new conversation of `accountId` is bound to: the chosen one when it is
|
|
35
|
-
* usable by the person (else `environment.not_found` / `environment.unavailable`, and nothing
|
|
36
|
-
* is created); without a choice the IM default (IM only) or the person's default environment.
|
|
37
|
-
* Null (unbound) for anonymous IM senders, and without a cloud sandbox provider when no usable
|
|
38
|
-
* IM default (a connected machine's environment, say) applies.
|
|
39
|
-
*/
|
|
40
|
-
forNew(orgId: string, accountId: string, pick?: NewConversationEnvironment): Promise<string | null>;
|
|
41
|
-
/**
|
|
42
|
-
* The environment a sandbox tool call of the conversation tree rooted at `rootConversationId`
|
|
43
|
-
* enters, checked for its owner now. A conversation still unbound (created before
|
|
44
|
-
* environments, or while no provider existed) is bound first, to the owner's IM default when it
|
|
45
|
-
* is an IM thread, else to the owner's default environment; a racing call binds it once.
|
|
46
|
-
* Throws `sandbox.unavailable` for a cloud environment (or none to bind) without a cloud
|
|
47
|
-
* provider and for an anonymous IM conversation, and `environment.not_found` /
|
|
48
|
-
* `environment.unavailable` when the owner may not use it now. A conversation bound to a
|
|
49
|
-
* connected machine's environment (`@device`) does not need a cloud provider.
|
|
50
|
-
*/
|
|
51
|
-
forTool(orgId: string, rootConversationId: string): Promise<string>;
|
|
52
|
-
/**
|
|
53
|
-
* Whether a sandbox tool call of the tree rooted at `rootConversationId` has a provider to run
|
|
54
|
-
* on, for tool listings (ruling R8; no binding, no access check, never throws): a cloud
|
|
55
|
-
* provider is loaded, or the environment the conversation is bound to (an unbound IM thread:
|
|
56
|
-
* its owner's IM default) runs on a registered kernel provider (`@device`). A forbidden or gone
|
|
57
|
-
* environment still counts: the call then says why.
|
|
58
|
-
*/
|
|
59
|
-
usableNow(orgId: string, rootConversationId: string): Promise<boolean>;
|
|
60
|
-
/**
|
|
61
|
-
* The person's IM default when `im` and it is still theirs, live, in this org and usable (the
|
|
62
|
-
* table has no such guard), else their default environment (created when missing); null when
|
|
63
|
-
* that is needed and there is no cloud provider to make or use it. The owner having left the org
|
|
64
|
-
* reads as `environment.not_found`.
|
|
65
|
-
*/
|
|
66
|
-
private defaultFor;
|
|
67
|
-
/** Binds an unbound conversation to `environmentId`; returns what it is bound to now. */
|
|
68
|
-
private bind;
|
|
69
|
-
/** The environment runs on a kernel-internal provider (`@device`). */
|
|
70
|
-
private isInternal;
|
|
71
|
-
/** The account is the org's anonymous IM guest (no person behind it). */
|
|
72
|
-
private isAnonymous;
|
|
73
|
-
/** The conversation is (or was) an IM thread's. */
|
|
74
|
-
private isImThread;
|
|
75
|
-
}
|