@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
|
@@ -14,16 +14,16 @@ import { ApiError } from "../errors.js";
|
|
|
14
14
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
15
15
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
16
16
|
import { KERNEL_BUS, KERNEL_LOGGER, REDIS } from "../infra/tokens.js";
|
|
17
|
-
import {
|
|
17
|
+
import { deviceNotFound } from "../sandbox/sandbox.service.js";
|
|
18
18
|
import { deviceLeaseKey, deviceReqKey, deviceReqTopic, deviceResKey, deviceResTopic, } from "./keys.js";
|
|
19
19
|
import { DeviceTargets } from "./targets.js";
|
|
20
|
-
/** The
|
|
21
|
-
export const
|
|
20
|
+
/** The device's connected machine is not connected (no replica holds its lease). */
|
|
21
|
+
export const deviceOffline = () => new ApiError("device.offline", "The machine of this local device is offline. Start capybara-device on that machine, or pick another device.");
|
|
22
22
|
/**
|
|
23
|
-
* The
|
|
23
|
+
* The device's machine was disconnected (revoked, signed out): 403 `device.forbidden`
|
|
24
24
|
* with `details: [{ reason: "machine_revoked" }]`, so the tools tell the model what to ask for.
|
|
25
25
|
*/
|
|
26
|
-
export const machineRevoked = () => new ApiError("
|
|
26
|
+
export const machineRevoked = () => new ApiError("device.forbidden", "The machine of this local device was disconnected. Run capybara-device logout, then capybara-device login on it, or pick another device.", [{ reason: "machine_revoked" }]);
|
|
27
27
|
/** The text a machine's `error` result reaches the model with (ruling R28). */
|
|
28
28
|
export const ON_MACHINE_PREFIX = "On the connected machine: ";
|
|
29
29
|
/** The longest one lease check of a waiting caller may take before it counts as "unknown". */
|
|
@@ -68,8 +68,8 @@ const GRACE_EXPIRED = Symbol("grace expired");
|
|
|
68
68
|
const errMsg = (e) => (e instanceof Error ? e.message : String(e));
|
|
69
69
|
/**
|
|
70
70
|
* The caller side of the device relay (environments spec §13.4). A request goes onto the
|
|
71
|
-
*
|
|
72
|
-
* `device.req.<
|
|
71
|
+
* device's Redis list (`capk:device-req:<deviceId>`) and is announced on
|
|
72
|
+
* `device.req.<deviceId>`; whichever gateway replica holds the machine's lease drains the
|
|
73
73
|
* list and forwards the frames to the machine.
|
|
74
74
|
*
|
|
75
75
|
* **The holder's half of the wire** (Task 6 follows it): items on the list are `KernelReq` or
|
|
@@ -82,13 +82,13 @@ const errMsg = (e) => (e instanceof Error ? e.message : String(e));
|
|
|
82
82
|
* The caller subscribes to its result topic before pushing and reads the result key once after,
|
|
83
83
|
* and once more before giving up (the run-abort pattern; a publish lost while the subscriber
|
|
84
84
|
* reconnects is still found). It waits within its own call for at most `deadlineMs` plus
|
|
85
|
-
* `
|
|
85
|
+
* `devices.local.reconnectGraceSeconds`; when the lease disappears meanwhile (checked every
|
|
86
86
|
* {@link HOLDER_POLL_MS}) the wait ends at most one grace later. A result produced while the
|
|
87
87
|
* machine was disconnected arrives after it reconnects. Giving up (grace, abort, a bad result)
|
|
88
88
|
* removes the request from the list, or queues a `cancel` when the machine already took it
|
|
89
89
|
* (ruling R15). Nothing is kept in process memory beyond the call.
|
|
90
90
|
*/
|
|
91
|
-
let
|
|
91
|
+
let LocalDeviceRelay = class LocalDeviceRelay {
|
|
92
92
|
redis;
|
|
93
93
|
bus;
|
|
94
94
|
locks;
|
|
@@ -104,35 +104,35 @@ let DeviceRelay = class DeviceRelay {
|
|
|
104
104
|
this.targets = targets;
|
|
105
105
|
}
|
|
106
106
|
/** Whether a gateway replica holds the machine's connection now. */
|
|
107
|
-
async holderLive(
|
|
108
|
-
return (await this.locks.holderOf(deviceLeaseKey(
|
|
107
|
+
async holderLive(deviceId) {
|
|
108
|
+
return (await this.locks.holderOf(deviceLeaseKey(deviceId))) !== null;
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
111
|
* Queues `op` for the machine without waiting for an answer (ruling R14: `stop`). The caller
|
|
112
112
|
* checks {@link holderLive} first; the frame waits on the list for the grace like any request.
|
|
113
113
|
*/
|
|
114
|
-
async send(
|
|
114
|
+
async send(deviceId, op, args, deadlineMs) {
|
|
115
115
|
const id = randomUUID();
|
|
116
116
|
const d = Math.max(1, Math.ceil(deadlineMs));
|
|
117
117
|
const req = { type: "req", id, op, args, deadlineMs: d };
|
|
118
|
-
await this.push(
|
|
119
|
-
await this.bus.publish(deviceReqTopic(
|
|
118
|
+
await this.push(deviceId, JSON.stringify(req), this.graceMs() + d + LIST_TTL_MARGIN_MS);
|
|
119
|
+
await this.bus.publish(deviceReqTopic(deviceId), { id });
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
* Runs `op` on the
|
|
123
|
-
* `
|
|
124
|
-
* out, but `
|
|
125
|
-
* `
|
|
122
|
+
* Runs `op` on the device's machine and returns its validated result. Throws
|
|
123
|
+
* `device.offline` when no replica holds the machine at send time or when the wait ran
|
|
124
|
+
* out, but `device.forbidden` ({@link machineRevoked}) when the machine was revoked and
|
|
125
|
+
* `device.not_found` when the device was deleted (checked whenever the connection is
|
|
126
126
|
* missing, ruling R27); the machine's failures map to `SandboxGoneError` (`gone`),
|
|
127
127
|
* `FileSystemGoneError` (`fs_gone`) or a `ToolError` (`policy`, and `error` prefixed
|
|
128
128
|
* {@link ON_MACHINE_PREFIX}); an invalid answer throws a `DeviceProtocolError`. On abort the
|
|
129
129
|
* abort reason is thrown.
|
|
130
130
|
*/
|
|
131
|
-
async call(
|
|
131
|
+
async call(deviceId, op, args, opts) {
|
|
132
132
|
const { signal } = opts;
|
|
133
133
|
signal.throwIfAborted();
|
|
134
|
-
if (!(await this.holderLive(
|
|
135
|
-
throw this.goneError(await this.targetState(
|
|
134
|
+
if (!(await this.holderLive(deviceId))) {
|
|
135
|
+
throw this.goneError(await this.targetState(deviceId)) ?? deviceOffline();
|
|
136
136
|
}
|
|
137
137
|
signal.throwIfAborted();
|
|
138
138
|
const id = randomUUID();
|
|
@@ -164,8 +164,8 @@ let DeviceRelay = class DeviceRelay {
|
|
|
164
164
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
165
165
|
// aborted while subscribing: nothing was sent yet
|
|
166
166
|
signal.throwIfAborted();
|
|
167
|
-
await this.push(
|
|
168
|
-
this.nudge(
|
|
167
|
+
await this.push(deviceId, reqJson, listTtlMs);
|
|
168
|
+
this.nudge(deviceId, id);
|
|
169
169
|
onStored(await this.redis.get(deviceResKey(id)));
|
|
170
170
|
// wait until the result, the abort, or `giveUpAt`. The lease is checked every poll: its loss
|
|
171
171
|
// caps `giveUpAt` at one grace from then (never extending it); a reconnect within that grace
|
|
@@ -178,7 +178,7 @@ let DeviceRelay = class DeviceRelay {
|
|
|
178
178
|
return;
|
|
179
179
|
try {
|
|
180
180
|
// a stalled Redis must not hold the give-up back: no answer in time means "unknown"
|
|
181
|
-
const live = await this.leaseCheck(
|
|
181
|
+
const live = await this.leaseCheck(deviceId);
|
|
182
182
|
if (live === true && capped) {
|
|
183
183
|
capped = false;
|
|
184
184
|
giveUpAt = fullGiveUpAt;
|
|
@@ -188,8 +188,8 @@ let DeviceRelay = class DeviceRelay {
|
|
|
188
188
|
giveUpAt = Math.min(giveUpAt, Date.now() + graceMs);
|
|
189
189
|
}
|
|
190
190
|
if (live === false) {
|
|
191
|
-
// a revoked machine or a deleted
|
|
192
|
-
const gone = this.goneError(await this.targetState(
|
|
191
|
+
// a revoked machine or a deleted device never comes back: fail at once
|
|
192
|
+
const gone = this.goneError(await this.targetState(deviceId));
|
|
193
193
|
if (gone) {
|
|
194
194
|
settle({ error: gone });
|
|
195
195
|
return;
|
|
@@ -197,7 +197,7 @@ let DeviceRelay = class DeviceRelay {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
catch (e) {
|
|
200
|
-
this.log.warn("device relay lease check failed", {
|
|
200
|
+
this.log.warn("device relay lease check failed", { deviceId, error: errMsg(e) });
|
|
201
201
|
}
|
|
202
202
|
if (stopped)
|
|
203
203
|
return;
|
|
@@ -219,31 +219,31 @@ let DeviceRelay = class DeviceRelay {
|
|
|
219
219
|
stopped = true;
|
|
220
220
|
clearTimeout(timer);
|
|
221
221
|
if ("res" in r) {
|
|
222
|
-
await this.quietly("delete the result",
|
|
222
|
+
await this.quietly("delete the result", deviceId, () => this.redis.del(deviceResKey(id)));
|
|
223
223
|
return this.resultOf(op, r.res);
|
|
224
224
|
}
|
|
225
225
|
if (r.error instanceof Error) {
|
|
226
226
|
// a bad answer: drop it, give the request up, report the protocol error
|
|
227
|
-
await this.quietly("delete the result",
|
|
227
|
+
await this.quietly("delete the result", deviceId, () => this.redis.del(deviceResKey(id)));
|
|
228
228
|
}
|
|
229
|
-
await this.giveUp(
|
|
229
|
+
await this.giveUp(deviceId, id, reqJson, listTtlMs);
|
|
230
230
|
if (r.error === ABORTED)
|
|
231
231
|
throw signal.reason;
|
|
232
232
|
if (r.error === GRACE_EXPIRED)
|
|
233
|
-
throw
|
|
233
|
+
throw deviceOffline();
|
|
234
234
|
throw r.error;
|
|
235
235
|
}
|
|
236
236
|
finally {
|
|
237
237
|
stopped = true;
|
|
238
238
|
clearTimeout(timer);
|
|
239
239
|
signal.removeEventListener("abort", onAbort);
|
|
240
|
-
await this.quietly("unsubscribe",
|
|
240
|
+
await this.quietly("unsubscribe", deviceId, unsubscribe);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
/** {@link holderLive}, or undefined when it takes longer than {@link LEASE_CHECK_TIMEOUT_MS}. */
|
|
244
|
-
async leaseCheck(
|
|
244
|
+
async leaseCheck(deviceId) {
|
|
245
245
|
let timer;
|
|
246
|
-
const check = this.holderLive(
|
|
246
|
+
const check = this.holderLive(deviceId);
|
|
247
247
|
check.catch(() => { }); // a late failure after the timeout is not unhandled
|
|
248
248
|
try {
|
|
249
249
|
return await Promise.race([
|
|
@@ -258,12 +258,12 @@ let DeviceRelay = class DeviceRelay {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
/**
|
|
261
|
-
* The
|
|
261
|
+
* The device's state ({@link DeviceTargets}), or undefined when it cannot be read within
|
|
262
262
|
* {@link LEASE_CHECK_TIMEOUT_MS} (a failure only logs: the caller keeps its other rules).
|
|
263
263
|
*/
|
|
264
|
-
async targetState(
|
|
264
|
+
async targetState(deviceId) {
|
|
265
265
|
let timer;
|
|
266
|
-
const read = this.targets.state(
|
|
266
|
+
const read = this.targets.state(deviceId);
|
|
267
267
|
read.catch(() => { });
|
|
268
268
|
try {
|
|
269
269
|
return await Promise.race([
|
|
@@ -274,8 +274,8 @@ let DeviceRelay = class DeviceRelay {
|
|
|
274
274
|
]);
|
|
275
275
|
}
|
|
276
276
|
catch (e) {
|
|
277
|
-
this.log.warn("device relay could not read the
|
|
278
|
-
|
|
277
|
+
this.log.warn("device relay could not read the device state", {
|
|
278
|
+
deviceId,
|
|
279
279
|
error: errMsg(e),
|
|
280
280
|
});
|
|
281
281
|
return undefined;
|
|
@@ -287,39 +287,39 @@ let DeviceRelay = class DeviceRelay {
|
|
|
287
287
|
/** The error of a state that never comes back; undefined for `active` or unknown. */
|
|
288
288
|
goneError(state) {
|
|
289
289
|
if (state === "deleted")
|
|
290
|
-
return
|
|
290
|
+
return deviceNotFound();
|
|
291
291
|
if (state === "revoked")
|
|
292
292
|
return machineRevoked();
|
|
293
293
|
return undefined;
|
|
294
294
|
}
|
|
295
295
|
graceMs() {
|
|
296
|
-
return this.config.get().
|
|
296
|
+
return this.config.get().devices.local.reconnectGraceSeconds * 1000;
|
|
297
297
|
}
|
|
298
|
-
async push(
|
|
299
|
-
await this.redis.eval(PUSH_WITH_TTL, 1, deviceReqKey(
|
|
298
|
+
async push(deviceId, frame, ttlMs) {
|
|
299
|
+
await this.redis.eval(PUSH_WITH_TTL, 1, deviceReqKey(deviceId), frame, String(ttlMs));
|
|
300
300
|
}
|
|
301
301
|
/** Announces new frames to the holder; a lost nudge is picked up on the holder's next drain. */
|
|
302
|
-
nudge(
|
|
303
|
-
this.bus.publish(deviceReqTopic(
|
|
304
|
-
this.log.warn("device relay nudge failed", {
|
|
302
|
+
nudge(deviceId, id) {
|
|
303
|
+
this.bus.publish(deviceReqTopic(deviceId), { id }).catch((e) => {
|
|
304
|
+
this.log.warn("device relay nudge failed", { deviceId, error: errMsg(e) });
|
|
305
305
|
});
|
|
306
306
|
}
|
|
307
307
|
/** Removes the request from the list, or queues a cancel when the machine took it (R15). */
|
|
308
|
-
async giveUp(
|
|
308
|
+
async giveUp(deviceId, id, reqJson, ttlMs) {
|
|
309
309
|
const cancel = JSON.stringify({ type: "cancel", id });
|
|
310
|
-
await this.quietly("give the request up",
|
|
311
|
-
const queued = await this.redis.eval(GIVE_UP, 1, deviceReqKey(
|
|
310
|
+
await this.quietly("give the request up", deviceId, async () => {
|
|
311
|
+
const queued = await this.redis.eval(GIVE_UP, 1, deviceReqKey(deviceId), reqJson, cancel, String(ttlMs));
|
|
312
312
|
if (queued === 1)
|
|
313
|
-
this.nudge(
|
|
313
|
+
this.nudge(deviceId, id);
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
/** Runs a side step whose failure must not replace the call's outcome: logged, swallowed. */
|
|
317
|
-
async quietly(what,
|
|
317
|
+
async quietly(what, deviceId, fn) {
|
|
318
318
|
try {
|
|
319
319
|
await fn();
|
|
320
320
|
}
|
|
321
321
|
catch (e) {
|
|
322
|
-
this.log.warn(`device relay could not ${what}`, {
|
|
322
|
+
this.log.warn(`device relay could not ${what}`, { deviceId, error: errMsg(e) });
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
/** A stored / published `res` frame for request `id`, or the protocol error it carries. */
|
|
@@ -361,7 +361,7 @@ let DeviceRelay = class DeviceRelay {
|
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
363
|
};
|
|
364
|
-
|
|
364
|
+
LocalDeviceRelay = __decorate([
|
|
365
365
|
Injectable(),
|
|
366
366
|
__param(0, Inject(REDIS)),
|
|
367
367
|
__param(1, Inject(KERNEL_BUS)),
|
|
@@ -369,5 +369,5 @@ DeviceRelay = __decorate([
|
|
|
369
369
|
__param(3, Inject(KernelConfigService)),
|
|
370
370
|
__param(4, Inject(KERNEL_LOGGER)),
|
|
371
371
|
__param(5, Inject(DeviceTargets))
|
|
372
|
-
],
|
|
373
|
-
export {
|
|
372
|
+
], LocalDeviceRelay);
|
|
373
|
+
export { LocalDeviceRelay };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SandboxRoots } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
3
3
|
/**
|
|
4
|
-
* Whether a device
|
|
4
|
+
* Whether a local device can still be served: `active` (live and an unrevoked machine),
|
|
5
5
|
* `revoked` (live, no active machine: disconnected or signed out) or `deleted` (missing or
|
|
6
6
|
* deleted, or its org deleted).
|
|
7
7
|
*/
|
|
@@ -9,20 +9,20 @@ export type DeviceTargetState = "active" | "revoked" | "deleted";
|
|
|
9
9
|
/** Whether two sets of roots name the same three directories. */
|
|
10
10
|
export declare const sameRoots: (a: SandboxRoots, b: SandboxRoots) => boolean;
|
|
11
11
|
/**
|
|
12
|
-
* Reads a device
|
|
12
|
+
* Reads a local device and its machine from PG for the relay and the `@local` provider.
|
|
13
13
|
* The relay asks {@link state} when a machine has no connection, so a revoked machine or a deleted
|
|
14
|
-
*
|
|
14
|
+
* device fails a waiting call at once instead of after the reconnect grace (ruling R27).
|
|
15
15
|
* Kept apart from `MachinesService` (which depends on the relay) so there is no dependency cycle.
|
|
16
16
|
*/
|
|
17
17
|
export declare class DeviceTargets {
|
|
18
18
|
private readonly db;
|
|
19
19
|
constructor(db: KernelDb);
|
|
20
|
-
state(
|
|
21
|
-
/** The org of a live
|
|
22
|
-
orgOf(
|
|
20
|
+
state(deviceId: string): Promise<DeviceTargetState>;
|
|
21
|
+
/** The org of a live device, or null (missing or deleted). */
|
|
22
|
+
orgOf(deviceId: string): Promise<string | null>;
|
|
23
23
|
/**
|
|
24
|
-
* The roots the
|
|
24
|
+
* The roots the device's active machine last reported in its hello frame, or null (no
|
|
25
25
|
* active machine, or none reported yet).
|
|
26
26
|
*/
|
|
27
|
-
machineRoots(
|
|
27
|
+
machineRoots(deviceId: string): Promise<SandboxRoots | null>;
|
|
28
28
|
}
|
|
@@ -13,9 +13,9 @@ const ROOT_KEYS = ["workspace", "skills", "tmp"];
|
|
|
13
13
|
/** Whether two sets of roots name the same three directories. */
|
|
14
14
|
export const sameRoots = (a, b) => ROOT_KEYS.every((k) => a[k] === b[k]);
|
|
15
15
|
/**
|
|
16
|
-
* Reads a device
|
|
16
|
+
* Reads a local device and its machine from PG for the relay and the `@local` provider.
|
|
17
17
|
* The relay asks {@link state} when a machine has no connection, so a revoked machine or a deleted
|
|
18
|
-
*
|
|
18
|
+
* device fails a waiting call at once instead of after the reconnect grace (ruling R27).
|
|
19
19
|
* Kept apart from `MachinesService` (which depends on the relay) so there is no dependency cycle.
|
|
20
20
|
*/
|
|
21
21
|
let DeviceTargets = class DeviceTargets {
|
|
@@ -23,13 +23,13 @@ let DeviceTargets = class DeviceTargets {
|
|
|
23
23
|
constructor(db) {
|
|
24
24
|
this.db = db;
|
|
25
25
|
}
|
|
26
|
-
async state(
|
|
26
|
+
async state(deviceId) {
|
|
27
27
|
const row = await this.db
|
|
28
|
-
.selectFrom("
|
|
28
|
+
.selectFrom("devices as e")
|
|
29
29
|
.innerJoin("orgs as o", "o.id", "e.org_id")
|
|
30
|
-
.leftJoin("
|
|
30
|
+
.leftJoin("device_machines as m", (j) => j.onRef("m.device_id", "=", "e.id").on("m.revoked_at", "is", null))
|
|
31
31
|
.select(["e.id", "m.id as machine_id"])
|
|
32
|
-
.where("e.id", "=",
|
|
32
|
+
.where("e.id", "=", deviceId)
|
|
33
33
|
.where("e.deleted_at", "is", null)
|
|
34
34
|
.where("o.deleted_at", "is", null)
|
|
35
35
|
.executeTakeFirst();
|
|
@@ -37,25 +37,25 @@ let DeviceTargets = class DeviceTargets {
|
|
|
37
37
|
return "deleted";
|
|
38
38
|
return row.machine_id === null ? "revoked" : "active";
|
|
39
39
|
}
|
|
40
|
-
/** The org of a live
|
|
41
|
-
async orgOf(
|
|
40
|
+
/** The org of a live device, or null (missing or deleted). */
|
|
41
|
+
async orgOf(deviceId) {
|
|
42
42
|
const row = await this.db
|
|
43
|
-
.selectFrom("
|
|
43
|
+
.selectFrom("devices")
|
|
44
44
|
.select("org_id")
|
|
45
|
-
.where("id", "=",
|
|
45
|
+
.where("id", "=", deviceId)
|
|
46
46
|
.where("deleted_at", "is", null)
|
|
47
47
|
.executeTakeFirst();
|
|
48
48
|
return row?.org_id ?? null;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* The roots the
|
|
51
|
+
* The roots the device's active machine last reported in its hello frame, or null (no
|
|
52
52
|
* active machine, or none reported yet).
|
|
53
53
|
*/
|
|
54
|
-
async machineRoots(
|
|
54
|
+
async machineRoots(deviceId) {
|
|
55
55
|
const row = await this.db
|
|
56
|
-
.selectFrom("
|
|
56
|
+
.selectFrom("device_machines")
|
|
57
57
|
.select("roots")
|
|
58
|
-
.where("
|
|
58
|
+
.where("device_id", "=", deviceId)
|
|
59
59
|
.where("revoked_at", "is", null)
|
|
60
60
|
.executeTakeFirst();
|
|
61
61
|
const r = row?.roots;
|
|
@@ -30,8 +30,8 @@ export interface OffboardResult {
|
|
|
30
30
|
runs: InFlightRun[];
|
|
31
31
|
/** 负责的 agent 转交给的所有者;没有转交时为 null。 */
|
|
32
32
|
newOwnerId: string | null;
|
|
33
|
-
/** The person's
|
|
34
|
-
|
|
33
|
+
/** The person's devices in the org, soft-deleted here; destroyed by a job after commit. */
|
|
34
|
+
devices: string[];
|
|
35
35
|
/** Conversations whose queued messages were deleted (`changed: queue` after commit). */
|
|
36
36
|
queueConversations: string[];
|
|
37
37
|
}
|
|
@@ -66,7 +66,7 @@ export declare class Offboarding {
|
|
|
66
66
|
private readonly redis;
|
|
67
67
|
private readonly registry;
|
|
68
68
|
private readonly eventLog;
|
|
69
|
-
/** The departed member's
|
|
69
|
+
/** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
|
|
70
70
|
private readonly sandboxJobs;
|
|
71
71
|
/** Stops the departed member's runs (`by: null`, a system action). */
|
|
72
72
|
private readonly runs;
|
|
@@ -75,7 +75,7 @@ export declare class Offboarding {
|
|
|
75
75
|
private readonly questions;
|
|
76
76
|
readonly log: KernelLogger;
|
|
77
77
|
constructor(db: KernelDb, txHost: TransactionHost, sessions: Pick<SessionService, "markRevoked">, redis: Pick<Redis, "set" | "exists">, registry: Pick<ExtensionRegistry, "list">, eventLog: Pick<EventLog, "notify" | "publishChanged">,
|
|
78
|
-
/** The departed member's
|
|
78
|
+
/** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
|
|
79
79
|
sandboxJobs: Pick<SandboxJobs, "destroy">,
|
|
80
80
|
/** Stops the departed member's runs (`by: null`, a system action). */
|
|
81
81
|
runs: OffboardingRunControl, credentials: Pick<Credentials, "clearAccount">, approvals: Pick<ApprovalsService, "cancelPendingFor">, questions: Pick<QuestionsService, "cancelPendingFor">, log: KernelLogger);
|
|
@@ -95,7 +95,7 @@ export declare class Offboarding {
|
|
|
95
95
|
* 以及成员关系自身的 `directory:<插件>` 来源,不论此人当初是不是由通讯录加入(手动重新加入后再被移出也不会被同步加回);
|
|
96
96
|
* 同步不再把此人加回,直到有人手动重新加入({@link MembersService.add} 删除排除);
|
|
97
97
|
* 8. 审计 `org.member.offboarded`;
|
|
98
|
-
* 9. Soft-deletes the person's
|
|
98
|
+
* 9. Soft-deletes the person's devices in the org and clears their default row (the
|
|
99
99
|
* instances and files are destroyed after the commit, best effort; the hourly sweep finishes
|
|
100
100
|
* any left).
|
|
101
101
|
*/
|
|
@@ -108,7 +108,7 @@ export declare class Offboarding {
|
|
|
108
108
|
/**
|
|
109
109
|
* After the commit (callers register it with the infra `afterCommit` inside their transaction):
|
|
110
110
|
* writes the session revocation markers, publishes the cancelled events, stops the person's
|
|
111
|
-
* runs in the org and schedules destroying their
|
|
111
|
+
* runs in the org and schedules destroying their devices. A failing step only logs a
|
|
112
112
|
* warning.
|
|
113
113
|
*/
|
|
114
114
|
afterOffboard(r: OffboardResult): Promise<void>;
|
package/dist/orgs/offboarding.js
CHANGED
|
@@ -63,7 +63,7 @@ let Offboarding = class Offboarding {
|
|
|
63
63
|
questions;
|
|
64
64
|
log;
|
|
65
65
|
constructor(db, txHost, sessions, redis, registry, eventLog,
|
|
66
|
-
/** The departed member's
|
|
66
|
+
/** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
|
|
67
67
|
sandboxJobs,
|
|
68
68
|
/** Stops the departed member's runs (`by: null`, a system action). */
|
|
69
69
|
runs, credentials, approvals, questions, log) {
|
|
@@ -96,7 +96,7 @@ let Offboarding = class Offboarding {
|
|
|
96
96
|
* 以及成员关系自身的 `directory:<插件>` 来源,不论此人当初是不是由通讯录加入(手动重新加入后再被移出也不会被同步加回);
|
|
97
97
|
* 同步不再把此人加回,直到有人手动重新加入({@link MembersService.add} 删除排除);
|
|
98
98
|
* 8. 审计 `org.member.offboarded`;
|
|
99
|
-
* 9. Soft-deletes the person's
|
|
99
|
+
* 9. Soft-deletes the person's devices in the org and clears their default row (the
|
|
100
100
|
* instances and files are destroyed after the commit, best effort; the hourly sweep finishes
|
|
101
101
|
* any left).
|
|
102
102
|
*/
|
|
@@ -259,8 +259,8 @@ let Offboarding = class Offboarding {
|
|
|
259
259
|
.where("id", "in", queueConversations)
|
|
260
260
|
.execute();
|
|
261
261
|
}
|
|
262
|
-
const
|
|
263
|
-
.updateTable("
|
|
262
|
+
const devices = await this.db
|
|
263
|
+
.updateTable("devices")
|
|
264
264
|
.set({ deleted_at: sql `now()`, updated_at: sql `now()` })
|
|
265
265
|
.where("org_id", "=", orgId)
|
|
266
266
|
.where("owner_kind", "=", "personal")
|
|
@@ -269,7 +269,7 @@ let Offboarding = class Offboarding {
|
|
|
269
269
|
.returning("id")
|
|
270
270
|
.execute();
|
|
271
271
|
await this.db
|
|
272
|
-
.deleteFrom("
|
|
272
|
+
.deleteFrom("device_defaults")
|
|
273
273
|
.where("org_id", "=", orgId)
|
|
274
274
|
.where("account_id", "=", accountId)
|
|
275
275
|
.execute();
|
|
@@ -293,7 +293,7 @@ let Offboarding = class Offboarding {
|
|
|
293
293
|
channelThreadsArchived: Number(threads.numUpdatedRows),
|
|
294
294
|
toolGrantsDeleted: Number(grants.numDeletedRows),
|
|
295
295
|
...(excluded.length > 0 && { directoryExcluded: excluded }),
|
|
296
|
-
...(
|
|
296
|
+
...(devices.length > 0 && { devicesDeleted: devices.length }),
|
|
297
297
|
...(queued.length > 0 && { queuedMessagesDeleted: queued.length }),
|
|
298
298
|
},
|
|
299
299
|
});
|
|
@@ -307,7 +307,7 @@ let Offboarding = class Offboarding {
|
|
|
307
307
|
status: r.status,
|
|
308
308
|
})),
|
|
309
309
|
newOwnerId: agents.length > 0 ? owner.account_id : null,
|
|
310
|
-
|
|
310
|
+
devices: devices.map((r) => r.id),
|
|
311
311
|
queueConversations,
|
|
312
312
|
};
|
|
313
313
|
}
|
|
@@ -336,7 +336,7 @@ let Offboarding = class Offboarding {
|
|
|
336
336
|
/**
|
|
337
337
|
* After the commit (callers register it with the infra `afterCommit` inside their transaction):
|
|
338
338
|
* writes the session revocation markers, publishes the cancelled events, stops the person's
|
|
339
|
-
* runs in the org and schedules destroying their
|
|
339
|
+
* runs in the org and schedules destroying their devices. A failing step only logs a
|
|
340
340
|
* warning.
|
|
341
341
|
*/
|
|
342
342
|
async afterOffboard(r) {
|
|
@@ -365,13 +365,13 @@ let Offboarding = class Offboarding {
|
|
|
365
365
|
}
|
|
366
366
|
for (const run of r.runs)
|
|
367
367
|
await this.stopRun(run);
|
|
368
|
-
if (r.
|
|
368
|
+
if (r.devices.length > 0) {
|
|
369
369
|
try {
|
|
370
|
-
await this.sandboxJobs.destroy(r.
|
|
370
|
+
await this.sandboxJobs.destroy(r.devices);
|
|
371
371
|
}
|
|
372
372
|
catch (err) {
|
|
373
|
-
log.warn("offboarding: cannot schedule
|
|
374
|
-
count: r.
|
|
373
|
+
log.warn("offboarding: cannot schedule device destruction", {
|
|
374
|
+
count: r.devices.length,
|
|
375
375
|
err,
|
|
376
376
|
});
|
|
377
377
|
}
|
|
@@ -21,6 +21,8 @@ import { OrgLock } from "./org-lock.js";
|
|
|
21
21
|
* own (run context spec §3.3); an invalid stored value reads as absent.
|
|
22
22
|
* - `theme`: the org's default theme and whether members may pick their own (themes spec §7);
|
|
23
23
|
* absent (or an invalid stored value) = follow the platform, personal picks allowed.
|
|
24
|
+
* - `uiArrangement`: per slot, the extension contributions the org hides and their order (UI slots
|
|
25
|
+
* spec §5); a slot with no entry follows the platform's; an invalid stored value reads as absent.
|
|
24
26
|
*/
|
|
25
27
|
/** 组织沙箱出站白名单的主机数上限。 */
|
|
26
28
|
export declare const ORG_SANDBOX_ALLOW_HOSTS_MAX = 200;
|
|
@@ -85,6 +87,19 @@ export declare const OrgSettingsSchema: z.ZodObject<{
|
|
|
85
87
|
locked: "locked";
|
|
86
88
|
}>;
|
|
87
89
|
}, z.core.$strict>>>;
|
|
90
|
+
uiArrangement: z.ZodCatch<z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
91
|
+
"agent.detail.section": "agent.detail.section";
|
|
92
|
+
"conversation.panel": "conversation.panel";
|
|
93
|
+
"conversation.properties.section": "conversation.properties.section";
|
|
94
|
+
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
95
|
+
"file.preview": "file.preview";
|
|
96
|
+
"org.page": "org.page";
|
|
97
|
+
"org.settings.section": "org.settings.section";
|
|
98
|
+
"platform.page": "platform.page";
|
|
99
|
+
}> & z.core.$partial, z.ZodObject<{
|
|
100
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
101
|
+
order: z.ZodArray<z.ZodString>;
|
|
102
|
+
}, z.core.$strict>>>>;
|
|
88
103
|
}, z.core.$strip>;
|
|
89
104
|
export type OrgSettings = z.infer<typeof OrgSettingsSchema>;
|
|
90
105
|
/** PATCH 体:只改给出的字段;未知字段 → 400。 */
|
|
@@ -122,6 +137,19 @@ export declare const OrgSettingsPatchSchema: z.ZodObject<{
|
|
|
122
137
|
locked: "locked";
|
|
123
138
|
}>;
|
|
124
139
|
}, z.core.$strict>>>;
|
|
140
|
+
uiArrangement: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
141
|
+
"agent.detail.section": "agent.detail.section";
|
|
142
|
+
"conversation.panel": "conversation.panel";
|
|
143
|
+
"conversation.properties.section": "conversation.properties.section";
|
|
144
|
+
"conversation.tool-result.card": "conversation.tool-result.card";
|
|
145
|
+
"file.preview": "file.preview";
|
|
146
|
+
"org.page": "org.page";
|
|
147
|
+
"org.settings.section": "org.settings.section";
|
|
148
|
+
"platform.page": "platform.page";
|
|
149
|
+
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
150
|
+
hidden: z.ZodArray<z.ZodString>;
|
|
151
|
+
order: z.ZodArray<z.ZodString>;
|
|
152
|
+
}, z.core.$strict>>>>;
|
|
125
153
|
}, z.core.$strict>;
|
|
126
154
|
export type OrgSettingsPatch = z.infer<typeof OrgSettingsPatchSchema>;
|
|
127
155
|
/** 部署的沙箱出站(`sandbox.limits.egress` + `allowHosts`)。 */
|
|
@@ -19,6 +19,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
19
19
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
20
20
|
import { assertThemeOnIn } from "../themes/theme-state.js";
|
|
21
21
|
import { IanaTimeZone } from "../time-zone.js";
|
|
22
|
+
import { mergeArrangement, OrgUiArrangementPatchSchema, UiArrangementSchema, } from "../ui/arrangement.js";
|
|
22
23
|
import { OrgLock } from "./org-lock.js";
|
|
23
24
|
/**
|
|
24
25
|
* 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
|
|
@@ -36,6 +37,8 @@ import { OrgLock } from "./org-lock.js";
|
|
|
36
37
|
* own (run context spec §3.3); an invalid stored value reads as absent.
|
|
37
38
|
* - `theme`: the org's default theme and whether members may pick their own (themes spec §7);
|
|
38
39
|
* absent (or an invalid stored value) = follow the platform, personal picks allowed.
|
|
40
|
+
* - `uiArrangement`: per slot, the extension contributions the org hides and their order (UI slots
|
|
41
|
+
* spec §5); a slot with no entry follows the platform's; an invalid stored value reads as absent.
|
|
39
42
|
*/
|
|
40
43
|
/** 组织沙箱出站白名单的主机数上限。 */
|
|
41
44
|
export const ORG_SANDBOX_ALLOW_HOSTS_MAX = 200;
|
|
@@ -87,6 +90,8 @@ export const OrgSettingsSchema = z.object({
|
|
|
87
90
|
timeZone: IanaTimeZone.optional().catch(undefined),
|
|
88
91
|
/** The org's default theme and personal lock (themes spec §7); absent = {@link DEFAULT_ORG_THEME}. */
|
|
89
92
|
theme: OrgThemeSettingSchema.optional().catch(undefined),
|
|
93
|
+
/** Hidden and ordered extension contributions per slot; absent = follow the platform. */
|
|
94
|
+
uiArrangement: UiArrangementSchema.optional().catch(undefined),
|
|
90
95
|
});
|
|
91
96
|
/** PATCH 体:只改给出的字段;未知字段 → 400。 */
|
|
92
97
|
export const OrgSettingsPatchSchema = z
|
|
@@ -106,6 +111,11 @@ export const OrgSettingsPatchSchema = z
|
|
|
106
111
|
* follows the platform) and whether members may pick their own; null clears it.
|
|
107
112
|
*/
|
|
108
113
|
theme: OrgThemeSettingSchema.nullable().optional(),
|
|
114
|
+
/**
|
|
115
|
+
* Per slot, the entry to hide / order contributions with (replaces that slot's stored entry);
|
|
116
|
+
* null removes a slot's entry (it follows the platform again). `platform.page` is refused.
|
|
117
|
+
*/
|
|
118
|
+
uiArrangement: OrgUiArrangementPatchSchema.optional(),
|
|
109
119
|
})
|
|
110
120
|
.strict();
|
|
111
121
|
/**
|
|
@@ -220,7 +230,14 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
220
230
|
.filter(([, v]) => v !== undefined)
|
|
221
231
|
// null clears sandboxEgress / wakeOnBackgroundTask: follow the deployment default again
|
|
222
232
|
.map(([k, v]) => [k, v === null ? undefined : v]));
|
|
233
|
+
const arrangementPatch = patch.uiArrangement;
|
|
234
|
+
delete changes.uiArrangement;
|
|
223
235
|
const after = { ...before, ...changes };
|
|
236
|
+
if (arrangementPatch !== undefined) {
|
|
237
|
+
after.uiArrangement = mergeArrangement(before.uiArrangement, arrangementPatch);
|
|
238
|
+
if (Object.keys(after.uiArrangement).length === 0)
|
|
239
|
+
delete after.uiArrangement;
|
|
240
|
+
}
|
|
224
241
|
if (after.sandboxEgress === undefined)
|
|
225
242
|
delete after.sandboxEgress;
|
|
226
243
|
if (after.wakeOnBackgroundTask === undefined)
|
|
@@ -229,6 +246,8 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
229
246
|
delete after.timeZone;
|
|
230
247
|
if (after.theme === undefined)
|
|
231
248
|
delete after.theme;
|
|
249
|
+
if (after.uiArrangement === undefined)
|
|
250
|
+
delete after.uiArrangement;
|
|
232
251
|
if (sameSettings(before, after))
|
|
233
252
|
return before;
|
|
234
253
|
// Lock order: the org row (orgLock above), then the theme's catalog item and its row FOR
|
|
@@ -237,7 +256,7 @@ let OrgSettingsService = class OrgSettingsService {
|
|
|
237
256
|
await assertThemeOnIn(this.db.db, orgId, patch.theme.id);
|
|
238
257
|
}
|
|
239
258
|
// 与存量合并:保留本版本不认识的键(清除的 sandboxEgress 不留在存量里)
|
|
240
|
-
const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, ...rest } = stored;
|
|
259
|
+
const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, ...rest } = stored;
|
|
241
260
|
await this.db
|
|
242
261
|
.updateTable("orgs")
|
|
243
262
|
.set({ settings: JSON.stringify({ ...rest, ...after }) })
|
package/dist/overrides.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { RUN_HOOKS, type RunHooks } from "./runtime/run-worker.js";
|
|
|
12
12
|
import { SUBAGENT_MESSAGES_HOOKS, type SubagentMessagesHooks } from "./runtime/subagents/messages.js";
|
|
13
13
|
import { USAGE_REPORT_RETRY, type UsageReportRetry } from "./runtime/usage-report.js";
|
|
14
14
|
import { SANDBOX_LEASE_TIMINGS, type SandboxLeaseTimings } from "./sandbox/leases.js";
|
|
15
|
+
import { SETUP_CHECK_TIMEOUT } from "./setup/setup-checks.service.js";
|
|
15
16
|
import { TOOL_PIPELINE_HOOKS, type ToolPipelineHooks } from "./tools/pipeline.js";
|
|
16
17
|
import { EMBED_TOKEN_CLOCK } from "./ui/embed-token.js";
|
|
17
18
|
/**
|
|
@@ -24,7 +25,7 @@ import { EMBED_TOKEN_CLOCK } from "./ui/embed-token.js";
|
|
|
24
25
|
* - `TOOL_PIPELINE_HOOKS`: the tool pipeline's hooks and policy decision;
|
|
25
26
|
* - `SUBAGENT_MESSAGES_HOOKS`: the sub-agent message tools' hooks (a send raced by a run's end);
|
|
26
27
|
* - `BOOTSTRAP_LOCK_TTL`, `FILE_PREVIEW_TIMEOUT`, `LIST_MODELS_TIMEOUT`, `USAGE_REPORT_RETRY`,
|
|
27
|
-
* `SANDBOX_LEASE_TIMINGS`, `CHANNEL_WEBHOOK_TIMINGS`: shorter timings;
|
|
28
|
+
* `SANDBOX_LEASE_TIMINGS`, `CHANNEL_WEBHOOK_TIMINGS`, `SETUP_CHECK_TIMEOUT`: shorter timings;
|
|
28
29
|
* - `EMBED_TOKEN_CLOCK`: the clock embed tokens are issued and checked with;
|
|
29
30
|
* - `RUN_CONTEXT_CLOCK`: the clock run start reads the run context's date from, and the
|
|
30
31
|
* `CurrentTime` builtin the time;
|
|
@@ -42,6 +43,7 @@ interface KernelSeams {
|
|
|
42
43
|
[USAGE_REPORT_RETRY]: UsageReportRetry;
|
|
43
44
|
[SANDBOX_LEASE_TIMINGS]: Partial<SandboxLeaseTimings>;
|
|
44
45
|
[CHANNEL_WEBHOOK_TIMINGS]: Partial<ChannelWebhookTimings>;
|
|
46
|
+
[SETUP_CHECK_TIMEOUT]: number;
|
|
45
47
|
[EMBED_TOKEN_CLOCK]: () => number;
|
|
46
48
|
[RUN_CONTEXT_CLOCK]: () => number;
|
|
47
49
|
[APP_INTERCEPTOR]: NestInterceptor;
|