@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
|
@@ -142,15 +142,15 @@ async function runInBackground(deps, ctx, input) {
|
|
|
142
142
|
const requested = input.timeout;
|
|
143
143
|
const seconds = Math.max(1, Math.ceil(Math.min(requested ?? maxMs, maxMs) / 1000));
|
|
144
144
|
const root = sandboxConversationOf(ctx);
|
|
145
|
-
// the
|
|
146
|
-
let
|
|
145
|
+
// the device the task will run in (the root conversation's, bound for good)
|
|
146
|
+
let deviceId;
|
|
147
147
|
try {
|
|
148
|
-
|
|
148
|
+
deviceId = await deps.devices.forTool(ctx.orgId, root);
|
|
149
149
|
}
|
|
150
150
|
catch (e) {
|
|
151
151
|
throw mapSandboxError(e, deps.log);
|
|
152
152
|
}
|
|
153
|
-
if (!(await sandbox.availableFor(
|
|
153
|
+
if (!(await sandbox.availableFor(deviceId)))
|
|
154
154
|
throw noSandboxError();
|
|
155
155
|
const maxTasks = cfg.sandbox.background.maxTasks;
|
|
156
156
|
const id = newTaskId();
|
|
@@ -161,7 +161,7 @@ async function runInBackground(deps, ctx, input) {
|
|
|
161
161
|
orgId: ctx.orgId,
|
|
162
162
|
conversationId: ctx.conversationId,
|
|
163
163
|
sandboxConversationId: root,
|
|
164
|
-
|
|
164
|
+
deviceId,
|
|
165
165
|
runId: ctx.runId,
|
|
166
166
|
callId: ctx.callId,
|
|
167
167
|
// the run's trigger account (the pipeline passes `runs.trigger_account_id` as `accountId`)
|
|
@@ -238,7 +238,7 @@ async function runInBackground(deps, ctx, input) {
|
|
|
238
238
|
const sid = out;
|
|
239
239
|
try {
|
|
240
240
|
// under the shared lease: no rebuild can come between the launch and this read
|
|
241
|
-
const instance = await currentInstance(
|
|
241
|
+
const instance = await currentInstance(deviceId);
|
|
242
242
|
if (instance === null)
|
|
243
243
|
throw new Error("the sandbox instance could not be read");
|
|
244
244
|
const marked = await background.markStarted(id, {
|
|
@@ -5,7 +5,7 @@ import type { BackgroundTaskStore } from "../../../background/store.js";
|
|
|
5
5
|
import type { KernelConfig } from "../../../config/schema.js";
|
|
6
6
|
import type { AttachmentsService } from "../../../conversations/attachments.service.js";
|
|
7
7
|
import type { EventLog } from "../../../conversations/event-log.js";
|
|
8
|
-
import { type
|
|
8
|
+
import { type ConversationDevices } from "../../../devices/conversation-devices.js";
|
|
9
9
|
import type { KernelConfigService } from "../../../infra/config/kernel-config.service.js";
|
|
10
10
|
import { type HeldProvider, type SandboxService } from "../../../sandbox/sandbox.service.js";
|
|
11
11
|
import type { StorageService } from "../../../storage/storage.service.js";
|
|
@@ -19,11 +19,11 @@ export interface SandboxToolDeps {
|
|
|
19
19
|
*/
|
|
20
20
|
sandbox: Pick<SandboxService, "cloudAvailable" | "availableFor" | "withSandbox"> & Partial<Pick<SandboxService, "pathLease" | "currentInstance" | "ifRunning">>;
|
|
21
21
|
/**
|
|
22
|
-
* The
|
|
22
|
+
* The device a call enters: the root conversation's, checked for its owner every call.
|
|
23
23
|
* `usableNow` (absent in test stubs: only the cloud provider counts) tells a listing whether a
|
|
24
|
-
* conversation's
|
|
24
|
+
* conversation's device can run without a cloud provider (ruling R8).
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
devices: Pick<ConversationDevices, "forTool"> & Partial<Pick<ConversationDevices, "usableNow">>;
|
|
27
27
|
/** Background `Bash` tasks; absent in test stubs, where `run_in_background` is refused. */
|
|
28
28
|
background?: Pick<BackgroundTaskStore, "reserve" | "markStarted" | "failLaunch" | "finishStoppedLaunch" | "releaseStop" | "get" | "advanceOffset">;
|
|
29
29
|
/**
|
|
@@ -38,7 +38,7 @@ export interface SandboxToolDeps {
|
|
|
38
38
|
storage: Pick<StorageService, "kernel" | "deployment" | "available">;
|
|
39
39
|
/**
|
|
40
40
|
* `publishChanged`: `Deliver` announces `changed: files` (UI refresh B). `latestOfType` reads the
|
|
41
|
-
* conversation's latest `
|
|
41
|
+
* conversation's latest `device_paths` (absent in test stubs, which record none).
|
|
42
42
|
*/
|
|
43
43
|
eventLog: Pick<EventLog, "append" | "latestFileState"> & Partial<Pick<EventLog, "publishChanged" | "latestOfType">>;
|
|
44
44
|
config: Pick<KernelConfigService, "get">;
|
|
@@ -51,22 +51,22 @@ export interface SandboxToolDeps {
|
|
|
51
51
|
* every provider call).
|
|
52
52
|
*/
|
|
53
53
|
export interface SandboxSession {
|
|
54
|
-
/** The
|
|
55
|
-
|
|
54
|
+
/** The device the call runs in (path leases are taken on it). */
|
|
55
|
+
deviceId: string;
|
|
56
56
|
h: SandboxHandle;
|
|
57
57
|
/** The instance's roots: every path the call builds or checks is under one of them. */
|
|
58
58
|
roots: SandboxRoots;
|
|
59
59
|
p: HeldProvider;
|
|
60
60
|
signal: AbortSignal;
|
|
61
61
|
}
|
|
62
|
-
/** This conversation's first use of its
|
|
62
|
+
/** This conversation's first use of its device (in the instance's workspace). */
|
|
63
63
|
export declare const sandboxStartedNotice: (roots: SandboxRoots) => string;
|
|
64
64
|
/** {@link sandboxStartedNotice} of an instance with the default roots. */
|
|
65
65
|
export declare const SANDBOX_STARTED_NOTICE: string;
|
|
66
|
-
/** A new instance came up on the
|
|
67
|
-
export declare const SANDBOX_RESTARTED_NOTICE = "The
|
|
66
|
+
/** A new instance came up on the device's kept files. */
|
|
67
|
+
export declare const SANDBOX_RESTARTED_NOTICE = "The device's instance was restarted; files are kept, running processes are gone and temporary files may be gone.";
|
|
68
68
|
/**
|
|
69
|
-
* The
|
|
69
|
+
* The device's files were reset (on request, or after they were lost) since this conversation
|
|
70
70
|
* last used it.
|
|
71
71
|
*/
|
|
72
72
|
export declare const sandboxFilesResetNotice: (roots: SandboxRoots) => string;
|
|
@@ -83,13 +83,13 @@ export declare function noSandboxError(): ToolError;
|
|
|
83
83
|
export declare const SANDBOX_UNREACHABLE_TEXT = "The sandbox provider can't be reached (the connection failed), so this call did nothing in the sandbox; retrying won't help until a super admin checks the sandbox extension's configuration (Admin console \u2192 Extensions). Continue without the sandbox, or ask the person to have a super admin check it.";
|
|
84
84
|
/** A provider connection that broke during the call: the provider may have acted before it broke. */
|
|
85
85
|
export declare const SANDBOX_CONNECTION_DROPPED_TEXT = "The connection to the sandbox provider broke during this call, so it may or may not have taken effect; check the sandbox state before retrying. If it fails again, continue without the sandbox and ask the person to have a super admin check the sandbox extension's configuration (Admin console \u2192 Extensions).";
|
|
86
|
-
/** The directory of attachments in the
|
|
86
|
+
/** The directory of attachments in the device (each conversation has a subdirectory). */
|
|
87
87
|
export declare const attachmentsRoot: (roots: SandboxRoots) => string;
|
|
88
88
|
/** {@link attachmentsRoot} of an instance with the default roots. */
|
|
89
89
|
export declare const ATTACHMENTS_ROOT: string;
|
|
90
90
|
/**
|
|
91
91
|
* One conversation's attachment directory: the first 12 hex digits of the root conversation's
|
|
92
|
-
* UUID, so conversations sharing
|
|
92
|
+
* UUID, so conversations sharing a device do not overwrite each other's files.
|
|
93
93
|
*/
|
|
94
94
|
export declare const attachmentsDir: (roots: SandboxRoots, rootConversationId: string) => string;
|
|
95
95
|
/** The conversation's marker of synced attachments: `{ <attachment id>: <file name> }`. */
|
|
@@ -144,7 +144,7 @@ export declare function clip(s: string, max: number): string;
|
|
|
144
144
|
export declare function withNotices(notices: readonly string[], text: string): string;
|
|
145
145
|
/**
|
|
146
146
|
* The root-independent checks of a path (`validateSandboxPathShape`: non-empty, absolute, no
|
|
147
|
-
* control characters, no empty or `..` segments), run before the call enters its
|
|
147
|
+
* control characters, no empty or `..` segments), run before the call enters its device, so
|
|
148
148
|
* a malformed path never creates or resumes an instance; root membership is checked inside
|
|
149
149
|
* ({@link checkPath}).
|
|
150
150
|
*/
|
|
@@ -176,41 +176,41 @@ export declare function assignAttachmentNames(items: readonly {
|
|
|
176
176
|
*/
|
|
177
177
|
export declare function mapSandboxError(e: unknown, log: Pick<Logger, "warn"> | undefined): unknown;
|
|
178
178
|
/**
|
|
179
|
-
* Runs `fn` in the
|
|
180
|
-
* checks the
|
|
179
|
+
* Runs `fn` in the device of the call's conversation (a sub-agent: its root's): resolves and
|
|
180
|
+
* checks the device for the conversation's owner (binding an unbound conversation to the
|
|
181
181
|
* owner's default first), then `SandboxService.withSandbox` (the instance created, resumed or
|
|
182
182
|
* rebuilt on the kept files as needed, every `exec` carrying the call's actor). Attachments are
|
|
183
183
|
* synced first. Returns the value and the notices to put before the result (first use, a new
|
|
184
184
|
* instance, newly copied attachments). When `withSandbox` rebuilds a gone instance and calls
|
|
185
185
|
* `fn` again, the notices are the last call's. The instance's roots are recorded for the model
|
|
186
|
-
* on the conversation's first call and whenever they change ({@link
|
|
186
|
+
* on the conversation's first call and whenever they change ({@link recordDevicePaths}).
|
|
187
187
|
*/
|
|
188
188
|
export declare function inSandbox<T>(deps: SandboxToolDeps, ctx: BuiltinCallContext, fn: (s: SandboxSession) => Promise<T>): Promise<{
|
|
189
189
|
value: T;
|
|
190
190
|
notices: string[];
|
|
191
191
|
}>;
|
|
192
|
-
/** The conversation whose
|
|
192
|
+
/** The conversation whose device a call uses (the root conversation's for a sub-agent). */
|
|
193
193
|
export declare const sandboxConversationOf: (ctx: BuiltinCallContext) => string;
|
|
194
194
|
/**
|
|
195
|
-
* Runs `fn` under the per-path lease of `path` in the session's
|
|
195
|
+
* Runs `fn` under the per-path lease of `path` in the session's device (spec §10.4: the
|
|
196
196
|
* kernel's own read-modify-write of one file never loses another holder's update, from any
|
|
197
|
-
* conversation sharing the
|
|
197
|
+
* conversation sharing the device). `fn` gets `signal` combined with the path lease's: pass
|
|
198
198
|
* it to every provider call, so a lost path lease stops the write. Without `pathLease` (test
|
|
199
199
|
* stubs) it just runs `fn` with `signal`.
|
|
200
200
|
*/
|
|
201
|
-
export declare function withPathLease<T>(deps: Pick<SandboxToolDeps, "sandbox">, s: Pick<SandboxSession, "
|
|
201
|
+
export declare function withPathLease<T>(deps: Pick<SandboxToolDeps, "sandbox">, s: Pick<SandboxSession, "deviceId">, path: string, signal: AbortSignal, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
202
202
|
/**
|
|
203
203
|
* Whether a sandbox tool call of the conversation tree rooted at `conversationId` can run: a
|
|
204
|
-
* cloud provider is loaded, or (ruling R8) the conversation's
|
|
205
|
-
* kernel provider (`@
|
|
204
|
+
* cloud provider is loaded, or (ruling R8) the conversation's device runs on a registered
|
|
205
|
+
* kernel provider (`@local`). Without a conversation only the cloud provider counts.
|
|
206
206
|
*/
|
|
207
|
-
export declare function sandboxUsable(deps: Pick<SandboxToolDeps, "sandbox" | "
|
|
207
|
+
export declare function sandboxUsable(deps: Pick<SandboxToolDeps, "sandbox" | "devices">, orgId: string, conversationId: string | undefined): Promise<boolean>;
|
|
208
208
|
/**
|
|
209
209
|
* The reason the registry reports the sandbox tools unavailable with, or null when they can run
|
|
210
210
|
* ({@link sandboxUsable}; the listing passes the root conversation of the run). An eval replay
|
|
211
211
|
* lists them as available (`replay`: its calls are never executed). `memo` (one per listing)
|
|
212
212
|
* makes every sandbox tool of a listing share one answer.
|
|
213
213
|
*/
|
|
214
|
-
export declare const unavailableWhenNoSandbox: (deps: Pick<SandboxToolDeps, "sandbox" | "
|
|
214
|
+
export declare const unavailableWhenNoSandbox: (deps: Pick<SandboxToolDeps, "sandbox" | "devices">) => (ctx: ToolListingScope) => Promise<string | null>;
|
|
215
215
|
/** 读出的字节是否像二进制(开头 8 KB 里有 NUL)。 */
|
|
216
216
|
export declare function looksBinary(data: Uint8Array): boolean;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { DEFAULT_SANDBOX_ROOTS, DeviceProtocolError, isFileSystemGoneError, isSandboxGoneError, ToolError, validateSandboxPath, validateSandboxPathShape, } from "@capybara-hq/plugin-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { ANONYMOUS_NO_DEVICE, } from "../../../devices/conversation-devices.js";
|
|
3
3
|
import { ApiError } from "../../../errors.js";
|
|
4
|
-
import {
|
|
4
|
+
import { devicePathsChange } from "../../../runtime/context/device-paths.js";
|
|
5
5
|
import { SandboxPathBusyError } from "../../../sandbox/leases.js";
|
|
6
6
|
import { SandboxLockLostError, } from "../../../sandbox/sandbox.service.js";
|
|
7
7
|
import { SandboxProviderUnreachableError } from "../../../sandbox/unreachable.js";
|
|
8
8
|
// ---- 模型可见文案(英文) ----
|
|
9
|
-
/** This conversation's first use of its
|
|
10
|
-
export const sandboxStartedNotice = (roots) => `
|
|
9
|
+
/** This conversation's first use of its device (in the instance's workspace). */
|
|
10
|
+
export const sandboxStartedNotice = (roots) => `Device started (first use in this conversation); the working directory is ${roots.workspace}, shared with the device's other conversations.`;
|
|
11
11
|
/** {@link sandboxStartedNotice} of an instance with the default roots. */
|
|
12
12
|
export const SANDBOX_STARTED_NOTICE = sandboxStartedNotice(DEFAULT_SANDBOX_ROOTS);
|
|
13
|
-
/** A new instance came up on the
|
|
14
|
-
export const SANDBOX_RESTARTED_NOTICE = "The
|
|
13
|
+
/** A new instance came up on the device's kept files. */
|
|
14
|
+
export const SANDBOX_RESTARTED_NOTICE = "The device's instance was restarted; files are kept, running processes are gone and temporary files may be gone.";
|
|
15
15
|
/**
|
|
16
|
-
* The
|
|
16
|
+
* The device's files were reset (on request, or after they were lost) since this conversation
|
|
17
17
|
* last used it.
|
|
18
18
|
*/
|
|
19
|
-
export const sandboxFilesResetNotice = (roots) => `The
|
|
19
|
+
export const sandboxFilesResetNotice = (roots) => `The device's files were reset; earlier files in ${roots.workspace} and running processes are gone and temporary files may be gone, so recreate anything you still need.`;
|
|
20
20
|
/** {@link sandboxFilesResetNotice} of an instance with the default roots. */
|
|
21
21
|
export const SANDBOX_FILES_RESET_NOTICE = sandboxFilesResetNotice(DEFAULT_SANDBOX_ROOTS);
|
|
22
|
-
const BUSY_TEXT = "The
|
|
22
|
+
const BUSY_TEXT = "The device is busy: all its command slots are taken by commands of this or another conversation sharing it; wait for one to finish and retry.";
|
|
23
23
|
const NO_SANDBOX_TEXT = "No sandbox is configured in this deployment; ask an administrator to install a sandbox plugin, or do the task without the sandbox.";
|
|
24
24
|
/** 没有沙箱提供方时「工具不可用」的原因(进提醒,内核在后面补句号):与调用时的错误同一句话。 */
|
|
25
25
|
export const SANDBOX_UNAVAILABLE_REASON = NO_SANDBOX_TEXT.slice(0, -1);
|
|
@@ -27,12 +27,12 @@ export const SANDBOX_UNAVAILABLE_REASON = NO_SANDBOX_TEXT.slice(0, -1);
|
|
|
27
27
|
export function noSandboxError() {
|
|
28
28
|
return new ToolError(NO_SANDBOX_TEXT);
|
|
29
29
|
}
|
|
30
|
-
const PATH_BUSY_TEXT = "Another sub-agent or another conversation
|
|
30
|
+
const PATH_BUSY_TEXT = "Another sub-agent or another conversation on this device is writing this file; wait a moment and retry.";
|
|
31
31
|
const ANONYMOUS_TEXT = "The sandbox is not available to anonymous IM senders; do the task without the sandbox, or ask the person to link their account and start a new conversation.";
|
|
32
|
-
const ENV_NOT_FOUND_TEXT = "This conversation's
|
|
33
|
-
const ENV_FORBIDDEN_TEXT = "This conversation's owner may no longer use its
|
|
34
|
-
const ENV_UNAVAILABLE_TEXT = "This conversation's
|
|
35
|
-
const ENV_FILES_LOST_TEXT = "This
|
|
32
|
+
const ENV_NOT_FOUND_TEXT = "This conversation's device was deleted or its owner can no longer use it; ask the user to start a new conversation on another device.";
|
|
33
|
+
const ENV_FORBIDDEN_TEXT = "This conversation's owner may no longer use its device; ask the user to start a new conversation on a device they can use.";
|
|
34
|
+
const ENV_UNAVAILABLE_TEXT = "This conversation's device is kept by a sandbox provider that is not active in this deployment; ask the user to start a new conversation on another device, or an administrator to re-enable that provider.";
|
|
35
|
+
const ENV_FILES_LOST_TEXT = "This device's files are gone. Ask the user to reset its files on the devices page, then retry.";
|
|
36
36
|
/**
|
|
37
37
|
* A provider call that never connected (refused, unknown host, unreachable network): the request
|
|
38
38
|
* did not reach the provider, so the call did nothing in the sandbox.
|
|
@@ -40,20 +40,20 @@ const ENV_FILES_LOST_TEXT = "This environment's files are gone. Ask the user to
|
|
|
40
40
|
export const SANDBOX_UNREACHABLE_TEXT = "The sandbox provider can't be reached (the connection failed), so this call did nothing in the sandbox; retrying won't help until a super admin checks the sandbox extension's configuration (Admin console → Extensions). Continue without the sandbox, or ask the person to have a super admin check it.";
|
|
41
41
|
/** A provider connection that broke during the call: the provider may have acted before it broke. */
|
|
42
42
|
export const SANDBOX_CONNECTION_DROPPED_TEXT = "The connection to the sandbox provider broke during this call, so it may or may not have taken effect; check the sandbox state before retrying. If it fails again, continue without the sandbox and ask the person to have a super admin check the sandbox extension's configuration (Admin console → Extensions).";
|
|
43
|
-
const DEVICE_FILES_LOST_TEXT = "This
|
|
44
|
-
const FILES_GONE_TEXT = "This
|
|
45
|
-
const DEVICE_OFFLINE_TEXT = "The
|
|
46
|
-
const DEVICE_OUTDATED_TEXT = "The
|
|
47
|
-
const DEVICE_REVOKED_TEXT = "The
|
|
48
|
-
const deviceProtocolText = (detail) => `The
|
|
43
|
+
const DEVICE_FILES_LOST_TEXT = "This device's directory is gone from the device's machine. Ask the person to run capybara-device logout, then capybara-device login into this device, on that machine, then retry.";
|
|
44
|
+
const FILES_GONE_TEXT = "This device's files are gone. Ask the user to reset its files on the devices page (for a local device: to run capybara-device logout, then capybara-device login, on it), then retry.";
|
|
45
|
+
const DEVICE_OFFLINE_TEXT = "The device's machine is offline. Ask the person to start capybara-device on it, or to start a new conversation on another device.";
|
|
46
|
+
const DEVICE_OUTDATED_TEXT = "The device's machine runs an outdated capybara-device. Ask the person to update it on that machine and start it again.";
|
|
47
|
+
const DEVICE_REVOKED_TEXT = "The device's machine was disconnected. Ask the person to run capybara-device logout, then capybara-device login on it, or to start a new conversation on another device.";
|
|
48
|
+
const deviceProtocolText = (detail) => `The device's machine sent an answer the platform cannot read (${detail}). Ask the person to update capybara-device on that machine, then retry.`;
|
|
49
49
|
const LOCK_LOST_TEXT = "The sandbox operation was interrupted because the sandbox lock was lost; check the sandbox state and retry.";
|
|
50
|
-
/** The directory of attachments in the
|
|
50
|
+
/** The directory of attachments in the device (each conversation has a subdirectory). */
|
|
51
51
|
export const attachmentsRoot = (roots) => `${roots.workspace}/attachments`;
|
|
52
52
|
/** {@link attachmentsRoot} of an instance with the default roots. */
|
|
53
53
|
export const ATTACHMENTS_ROOT = attachmentsRoot(DEFAULT_SANDBOX_ROOTS);
|
|
54
54
|
/**
|
|
55
55
|
* One conversation's attachment directory: the first 12 hex digits of the root conversation's
|
|
56
|
-
* UUID, so conversations sharing
|
|
56
|
+
* UUID, so conversations sharing a device do not overwrite each other's files.
|
|
57
57
|
*/
|
|
58
58
|
export const attachmentsDir = (roots, rootConversationId) => `${attachmentsRoot(roots)}/${rootConversationId.replaceAll("-", "").slice(0, 12)}`;
|
|
59
59
|
/** The conversation's marker of synced attachments: `{ <attachment id>: <file name> }`. */
|
|
@@ -122,7 +122,7 @@ export function withNotices(notices, text) {
|
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
124
|
* The root-independent checks of a path (`validateSandboxPathShape`: non-empty, absolute, no
|
|
125
|
-
* control characters, no empty or `..` segments), run before the call enters its
|
|
125
|
+
* control characters, no empty or `..` segments), run before the call enters its device, so
|
|
126
126
|
* a malformed path never creates or resumes an instance; root membership is checked inside
|
|
127
127
|
* ({@link checkPath}).
|
|
128
128
|
*/
|
|
@@ -320,28 +320,28 @@ export function mapSandboxError(e, log) {
|
|
|
320
320
|
return new ToolError(BUSY_TEXT);
|
|
321
321
|
if (e.code === "sandbox.unavailable") {
|
|
322
322
|
const reason = e.details?.[0]?.reason;
|
|
323
|
-
if (reason ===
|
|
323
|
+
if (reason === ANONYMOUS_NO_DEVICE)
|
|
324
324
|
return new ToolError(ANONYMOUS_TEXT);
|
|
325
325
|
return new ToolError(NO_SANDBOX_TEXT);
|
|
326
326
|
}
|
|
327
327
|
const detail = e.details?.[0];
|
|
328
|
-
if (e.code === "
|
|
328
|
+
if (e.code === "device.not_found")
|
|
329
329
|
return new ToolError(ENV_NOT_FOUND_TEXT);
|
|
330
|
-
if (e.code === "
|
|
330
|
+
if (e.code === "device.forbidden" && detail?.reason === "machine_revoked") {
|
|
331
331
|
return new ToolError(DEVICE_REVOKED_TEXT);
|
|
332
332
|
}
|
|
333
|
-
if (e.code === "
|
|
333
|
+
if (e.code === "device.forbidden")
|
|
334
334
|
return new ToolError(ENV_FORBIDDEN_TEXT);
|
|
335
|
-
if (e.code === "
|
|
335
|
+
if (e.code === "device.unavailable")
|
|
336
336
|
return new ToolError(ENV_UNAVAILABLE_TEXT);
|
|
337
|
-
if (e.code === "
|
|
337
|
+
if (e.code === "device.files_lost" && detail?.source === "local") {
|
|
338
338
|
return new ToolError(DEVICE_FILES_LOST_TEXT);
|
|
339
339
|
}
|
|
340
|
-
if (e.code === "
|
|
340
|
+
if (e.code === "device.files_lost")
|
|
341
341
|
return new ToolError(ENV_FILES_LOST_TEXT);
|
|
342
|
-
if (e.code === "
|
|
342
|
+
if (e.code === "device.offline")
|
|
343
343
|
return new ToolError(DEVICE_OFFLINE_TEXT);
|
|
344
|
-
if (e.code === "
|
|
344
|
+
if (e.code === "device.program_outdated")
|
|
345
345
|
return new ToolError(DEVICE_OUTDATED_TEXT);
|
|
346
346
|
}
|
|
347
347
|
// an operation outside withSandbox's own mapping (it records lost files itself)
|
|
@@ -356,27 +356,27 @@ export function mapSandboxError(e, log) {
|
|
|
356
356
|
return e;
|
|
357
357
|
}
|
|
358
358
|
/**
|
|
359
|
-
* Runs `fn` in the
|
|
360
|
-
* checks the
|
|
359
|
+
* Runs `fn` in the device of the call's conversation (a sub-agent: its root's): resolves and
|
|
360
|
+
* checks the device for the conversation's owner (binding an unbound conversation to the
|
|
361
361
|
* owner's default first), then `SandboxService.withSandbox` (the instance created, resumed or
|
|
362
362
|
* rebuilt on the kept files as needed, every `exec` carrying the call's actor). Attachments are
|
|
363
363
|
* synced first. Returns the value and the notices to put before the result (first use, a new
|
|
364
364
|
* instance, newly copied attachments). When `withSandbox` rebuilds a gone instance and calls
|
|
365
365
|
* `fn` again, the notices are the last call's. The instance's roots are recorded for the model
|
|
366
|
-
* on the conversation's first call and whenever they change ({@link
|
|
366
|
+
* on the conversation's first call and whenever they change ({@link recordDevicePaths}).
|
|
367
367
|
*/
|
|
368
368
|
export async function inSandbox(deps, ctx, fn) {
|
|
369
369
|
try {
|
|
370
|
-
const
|
|
370
|
+
const deviceId = await deps.devices.forTool(ctx.orgId, sandboxConversationOf(ctx));
|
|
371
371
|
return await deps.sandbox.withSandbox({
|
|
372
372
|
orgId: ctx.orgId,
|
|
373
|
-
|
|
373
|
+
deviceId,
|
|
374
374
|
// a sub-agent is told when another holder rebuilt the shared instance (final review m5)
|
|
375
375
|
holderConversationId: ctx.conversationId,
|
|
376
376
|
actor: { accountId: ctx.accountId, conversationId: ctx.conversationId },
|
|
377
377
|
}, async (h, p, info, signal) => {
|
|
378
|
-
const s = {
|
|
379
|
-
await
|
|
378
|
+
const s = { deviceId, h, roots: info.roots, p, signal };
|
|
379
|
+
await recordDevicePaths(deps, ctx, info.roots);
|
|
380
380
|
const notices = [];
|
|
381
381
|
if (info.filesReset)
|
|
382
382
|
notices.push(sandboxFilesResetNotice(info.roots));
|
|
@@ -398,32 +398,32 @@ export async function inSandbox(deps, ctx, fn) {
|
|
|
398
398
|
}
|
|
399
399
|
/**
|
|
400
400
|
* Environments spec §13.1, ruling R22: the calling conversation's log (the log its model reads; a
|
|
401
|
-
* sub-agent's own too) gets an `
|
|
401
|
+
* sub-agent's own too) gets an `device_paths` event on its first sandbox call, the default
|
|
402
402
|
* roots included, and again whenever the held instance's roots differ from the latest recorded
|
|
403
403
|
* ones; always before the call's result. One indexed read per call (migration 0094); parallel
|
|
404
404
|
* calls that saw the same latest event write one event (its idempotency key).
|
|
405
405
|
*/
|
|
406
|
-
async function
|
|
406
|
+
async function recordDevicePaths(deps, ctx, roots) {
|
|
407
407
|
if (!deps.eventLog.latestOfType)
|
|
408
408
|
return;
|
|
409
|
-
const latest = await deps.eventLog.latestOfType(ctx.conversationId, "
|
|
410
|
-
const change =
|
|
409
|
+
const latest = await deps.eventLog.latestOfType(ctx.conversationId, "device_paths");
|
|
410
|
+
const change = devicePathsChange(latest, roots, ctx.runId);
|
|
411
411
|
if (change)
|
|
412
412
|
await deps.eventLog.append(ctx.conversationId, [change]);
|
|
413
413
|
}
|
|
414
|
-
/** The conversation whose
|
|
414
|
+
/** The conversation whose device a call uses (the root conversation's for a sub-agent). */
|
|
415
415
|
export const sandboxConversationOf = (ctx) => ctx.sandboxConversationId ?? ctx.conversationId;
|
|
416
416
|
/**
|
|
417
|
-
* Runs `fn` under the per-path lease of `path` in the session's
|
|
417
|
+
* Runs `fn` under the per-path lease of `path` in the session's device (spec §10.4: the
|
|
418
418
|
* kernel's own read-modify-write of one file never loses another holder's update, from any
|
|
419
|
-
* conversation sharing the
|
|
419
|
+
* conversation sharing the device). `fn` gets `signal` combined with the path lease's: pass
|
|
420
420
|
* it to every provider call, so a lost path lease stops the write. Without `pathLease` (test
|
|
421
421
|
* stubs) it just runs `fn` with `signal`.
|
|
422
422
|
*/
|
|
423
423
|
export async function withPathLease(deps, s, path, signal, fn) {
|
|
424
424
|
if (!deps.sandbox.pathLease)
|
|
425
425
|
return fn(signal);
|
|
426
|
-
const lease = await deps.sandbox.pathLease(s.
|
|
426
|
+
const lease = await deps.sandbox.pathLease(s.deviceId, path, signal);
|
|
427
427
|
try {
|
|
428
428
|
return await fn(AbortSignal.any([signal, lease.signal]));
|
|
429
429
|
}
|
|
@@ -433,15 +433,15 @@ export async function withPathLease(deps, s, path, signal, fn) {
|
|
|
433
433
|
}
|
|
434
434
|
/**
|
|
435
435
|
* Whether a sandbox tool call of the conversation tree rooted at `conversationId` can run: a
|
|
436
|
-
* cloud provider is loaded, or (ruling R8) the conversation's
|
|
437
|
-
* kernel provider (`@
|
|
436
|
+
* cloud provider is loaded, or (ruling R8) the conversation's device runs on a registered
|
|
437
|
+
* kernel provider (`@local`). Without a conversation only the cloud provider counts.
|
|
438
438
|
*/
|
|
439
439
|
export async function sandboxUsable(deps, orgId, conversationId) {
|
|
440
440
|
if (deps.sandbox.cloudAvailable)
|
|
441
441
|
return true;
|
|
442
|
-
if (conversationId === undefined || !deps.
|
|
442
|
+
if (conversationId === undefined || !deps.devices.usableNow)
|
|
443
443
|
return false;
|
|
444
|
-
return deps.
|
|
444
|
+
return deps.devices.usableNow(orgId, conversationId);
|
|
445
445
|
}
|
|
446
446
|
/**
|
|
447
447
|
* The reason the registry reports the sandbox tools unavailable with, or null when they can run
|
|
@@ -57,10 +57,10 @@ export function sandboxEditTool(deps) {
|
|
|
57
57
|
throw new ToolError(SAME_TEXT);
|
|
58
58
|
checkPathShape(input.file_path, "file_path");
|
|
59
59
|
let path = "";
|
|
60
|
-
const { value: edit, notices } = await inSandbox(deps, ctx, ({
|
|
60
|
+
const { value: edit, notices } = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
|
|
61
61
|
path = checkWritable(input.file_path, roots);
|
|
62
62
|
// read, replace, write: one holder at a time per file
|
|
63
|
-
return withPathLease(deps, {
|
|
63
|
+
return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
|
|
64
64
|
const st = await p.stat(h, path, signal);
|
|
65
65
|
if (st === null)
|
|
66
66
|
throw new ToolError(notFound(path));
|
|
@@ -6,7 +6,7 @@ export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_R
|
|
|
6
6
|
export { compileGlob, GlobPatternError, MAX_GLOB_STARS } from "./glob-match.js";
|
|
7
7
|
/**
|
|
8
8
|
* The sandbox builtins (M5 T8): Bash / Read / Write / Edit / Glob / Grep / Deliver, all run in the
|
|
9
|
-
* conversation's
|
|
9
|
+
* conversation's device (`SandboxService.withSandbox`) after syncing attachments to
|
|
10
10
|
* `<workspace>/attachments` (the instance's roots, environments spec §13.1); unavailable without
|
|
11
11
|
* a provider for it. Allowed by default; with the org setting `sandboxToolPolicy` set to ask,
|
|
12
12
|
* every one but Read / Glob / Grep needs approval (see policy.ts).
|
|
@@ -13,7 +13,7 @@ export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_R
|
|
|
13
13
|
export { compileGlob, GlobPatternError, MAX_GLOB_STARS } from "./glob-match.js";
|
|
14
14
|
/**
|
|
15
15
|
* The sandbox builtins (M5 T8): Bash / Read / Write / Edit / Glob / Grep / Deliver, all run in the
|
|
16
|
-
* conversation's
|
|
16
|
+
* conversation's device (`SandboxService.withSandbox`) after syncing attachments to
|
|
17
17
|
* `<workspace>/attachments` (the instance's roots, environments spec §13.1); unavailable without
|
|
18
18
|
* a provider for it. Allowed by default; with the org setting `sandboxToolPolicy` set to ask,
|
|
19
19
|
* every one but Read / Glob / Grep needs approval (see policy.ts).
|
|
@@ -14,12 +14,12 @@ export interface TaskEnv {
|
|
|
14
14
|
store: NonNullable<SandboxToolDeps["background"]>;
|
|
15
15
|
tasks: NonNullable<SandboxToolDeps["tasks"]>;
|
|
16
16
|
ifRunning: SandboxService["ifRunning"];
|
|
17
|
-
|
|
17
|
+
devices: SandboxToolDeps["devices"];
|
|
18
18
|
log: SandboxToolDeps["log"];
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* The task tools' dependencies; throws the tool's error when they are missing, or when the
|
|
22
|
-
* conversation's
|
|
22
|
+
* conversation's device cannot run (no cloud provider, and not a registered `@local` one:
|
|
23
23
|
* ruling R8).
|
|
24
24
|
*/
|
|
25
25
|
export declare function taskEnv(deps: SandboxToolDeps, ctx: BuiltinCallContext): Promise<TaskEnv>;
|
|
@@ -29,14 +29,14 @@ export declare function taskEnv(deps: SandboxToolDeps, ctx: BuiltinCallContext):
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function ownedTask(env: TaskEnv, ctx: BuiltinCallContext, id: string): Promise<BackgroundTaskRow>;
|
|
31
31
|
/**
|
|
32
|
-
* The per-call access re-check of every sandbox tool (`inSandbox`): the conversation's
|
|
32
|
+
* The per-call access re-check of every sandbox tool (`inSandbox`): the conversation's device
|
|
33
33
|
* may still be used by its owner. The task tools read a task's own instance without `inSandbox`,
|
|
34
34
|
* so they check here first; errors become the tools' errors.
|
|
35
35
|
*/
|
|
36
|
-
export declare function
|
|
36
|
+
export declare function recheckDevice(env: TaskEnv, ctx: BuiltinCallContext): Promise<void>;
|
|
37
37
|
/**
|
|
38
|
-
* `ifRunning` on the task's own
|
|
39
|
-
* conversation); sandbox errors become the tools' errors. A task from before
|
|
38
|
+
* `ifRunning` on the task's own device instance (its execs act for the task's trigger in its
|
|
39
|
+
* conversation); sandbox errors become the tools' errors. A task from before devices has no
|
|
40
40
|
* instance (`not_running`).
|
|
41
41
|
*/
|
|
42
42
|
export declare function onTaskInstance<T>(env: TaskEnv, task: BackgroundTaskRow, fn: (p: HeldProvider, h: SandboxHandle, signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<IfRunningResult<T>>;
|
|
@@ -51,7 +51,7 @@ const SPEC = {
|
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
53
|
* The task tools' dependencies; throws the tool's error when they are missing, or when the
|
|
54
|
-
* conversation's
|
|
54
|
+
* conversation's device cannot run (no cloud provider, and not a registered `@local` one:
|
|
55
55
|
* ruling R8).
|
|
56
56
|
*/
|
|
57
57
|
export async function taskEnv(deps, ctx) {
|
|
@@ -65,7 +65,7 @@ export async function taskEnv(deps, ctx) {
|
|
|
65
65
|
store: background,
|
|
66
66
|
tasks,
|
|
67
67
|
ifRunning: deps.sandbox.ifRunning.bind(deps.sandbox),
|
|
68
|
-
|
|
68
|
+
devices: deps.devices,
|
|
69
69
|
log: deps.log,
|
|
70
70
|
};
|
|
71
71
|
}
|
|
@@ -83,28 +83,28 @@ export async function ownedTask(env, ctx, id) {
|
|
|
83
83
|
return task;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* The per-call access re-check of every sandbox tool (`inSandbox`): the conversation's
|
|
86
|
+
* The per-call access re-check of every sandbox tool (`inSandbox`): the conversation's device
|
|
87
87
|
* may still be used by its owner. The task tools read a task's own instance without `inSandbox`,
|
|
88
88
|
* so they check here first; errors become the tools' errors.
|
|
89
89
|
*/
|
|
90
|
-
export async function
|
|
90
|
+
export async function recheckDevice(env, ctx) {
|
|
91
91
|
try {
|
|
92
|
-
await env.
|
|
92
|
+
await env.devices.forTool(ctx.orgId, sandboxConversationOf(ctx));
|
|
93
93
|
}
|
|
94
94
|
catch (e) {
|
|
95
95
|
throw mapSandboxError(e, env.log);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
|
-
* `ifRunning` on the task's own
|
|
100
|
-
* conversation); sandbox errors become the tools' errors. A task from before
|
|
99
|
+
* `ifRunning` on the task's own device instance (its execs act for the task's trigger in its
|
|
100
|
+
* conversation); sandbox errors become the tools' errors. A task from before devices has no
|
|
101
101
|
* instance (`not_running`).
|
|
102
102
|
*/
|
|
103
103
|
export async function onTaskInstance(env, task, fn, signal) {
|
|
104
|
-
if (task.
|
|
104
|
+
if (task.deviceId === null)
|
|
105
105
|
return { kind: "not_running", status: "gone" };
|
|
106
106
|
try {
|
|
107
|
-
return await env.ifRunning(task.
|
|
107
|
+
return await env.ifRunning(task.deviceId, { createdAt: task.sandboxCreatedAt }, (p, h, sig) => fn(actingAs(p, taskActor(task)), h, sig), signal);
|
|
108
108
|
}
|
|
109
109
|
catch (e) {
|
|
110
110
|
throw mapSandboxError(e, env.log);
|
|
@@ -269,7 +269,7 @@ export function taskOutputTool(deps) {
|
|
|
269
269
|
const { task_id, block = true, timeout } = input;
|
|
270
270
|
const env = await taskEnv(deps, ctx);
|
|
271
271
|
let task = await ownedTask(env, ctx, task_id);
|
|
272
|
-
await
|
|
272
|
+
await recheckDevice(env, ctx);
|
|
273
273
|
if (block && task.status === "running") {
|
|
274
274
|
const wait = Math.max(0, Math.min(timeout ?? TASK_OUTPUT_DEFAULT_TIMEOUT_MS, TASK_OUTPUT_MAX_TIMEOUT_MS, runTimeLeftMs(ctx.runDeadline)));
|
|
275
275
|
const until = Date.now() + wait;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { rootsOf, ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { decodeTail, tailCommand } from "../../../background/session.js";
|
|
3
3
|
import { clip, display, mapSandboxError, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, } from "./common.js";
|
|
4
|
-
import { onTaskInstance, ownedTask,
|
|
4
|
+
import { onTaskInstance, ownedTask, recheckDevice, statusLine, taskEnv, } from "./task-output.js";
|
|
5
5
|
/** The time limit of reading a stopped task's last lines. */
|
|
6
6
|
const TAIL_EXEC_TIMEOUT_MS = 10_000;
|
|
7
7
|
const SPEC = {
|
|
@@ -84,7 +84,7 @@ export function taskStopTool(deps) {
|
|
|
84
84
|
const { task_id } = input;
|
|
85
85
|
const env = await taskEnv(deps, ctx);
|
|
86
86
|
const task = await ownedTask(env, ctx, task_id);
|
|
87
|
-
await
|
|
87
|
+
await recheckDevice(env, ctx);
|
|
88
88
|
if (task.status !== "running") {
|
|
89
89
|
return ended(env, task, "had already finished", ctx.signal);
|
|
90
90
|
}
|
|
@@ -46,10 +46,10 @@ export function sandboxWriteTool(deps) {
|
|
|
46
46
|
const data = new TextEncoder().encode(input.content);
|
|
47
47
|
checkPathShape(input.file_path, "file_path");
|
|
48
48
|
let path = "";
|
|
49
|
-
const { value, notices } = await inSandbox(deps, ctx, ({
|
|
49
|
+
const { value, notices } = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
|
|
50
50
|
path = checkWritable(input.file_path, roots);
|
|
51
51
|
// stat, read the previous content, write: one holder at a time per file
|
|
52
|
-
return withPathLease(deps, {
|
|
52
|
+
return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
|
|
53
53
|
const st = await p.stat(h, path, signal);
|
|
54
54
|
if (st?.isDir)
|
|
55
55
|
throw new ToolError(isDirectory(path));
|
|
@@ -8,7 +8,7 @@ import { type SandboxToolDeps } from "./sandbox/common.js";
|
|
|
8
8
|
* Builtin tool `Skill` (M5 T14, ruling M17): resolves the name against the latest skills catalog
|
|
9
9
|
* snapshot in the log to an id and version, and returns that version's SKILL.md body and file list
|
|
10
10
|
* (the result names the version). A skill with other files is copied whole into the conversation's
|
|
11
|
-
*
|
|
11
|
+
* device at `<skills root>/<name>@<digest>/` (the instance's skills root, `/skills` by default; the provider writes as root, commands read only; the
|
|
12
12
|
* digest is {@link skillDigest} of the version's file manifest, so versions of one skill coexist in
|
|
13
13
|
* the shared file system). Without a sandbox provider only the body is given, with a note that
|
|
14
14
|
* scripts are unavailable.
|
|
@@ -14,7 +14,7 @@ import { clip, display, inSandbox, sandboxBaseTimeoutMs, sandboxConversationOf,
|
|
|
14
14
|
* Builtin tool `Skill` (M5 T14, ruling M17): resolves the name against the latest skills catalog
|
|
15
15
|
* snapshot in the log to an id and version, and returns that version's SKILL.md body and file list
|
|
16
16
|
* (the result names the version). A skill with other files is copied whole into the conversation's
|
|
17
|
-
*
|
|
17
|
+
* device at `<skills root>/<name>@<digest>/` (the instance's skills root, `/skills` by default; the provider writes as root, commands read only; the
|
|
18
18
|
* digest is {@link skillDigest} of the version's file manifest, so versions of one skill coexist in
|
|
19
19
|
* the shared file system). Without a sandbox provider only the body is given, with a note that
|
|
20
20
|
* scripts are unavailable.
|
|
@@ -49,7 +49,7 @@ const SPEC = {
|
|
|
49
49
|
description: "Load a skill from the skills list: returns the skill's instructions " +
|
|
50
50
|
"(its SKILL.md) and the list of its files. Call it before starting a task that matches a " +
|
|
51
51
|
"skill's description, then follow the returned instructions. If the skill has scripts or other " +
|
|
52
|
-
"files, they are copied read-only to <name>@<version-id>/ in the skills directory of this
|
|
52
|
+
"files, they are copied read-only to <name>@<version-id>/ in the skills directory of this device; run scripts " +
|
|
53
53
|
"there with an interpreter (sh, python3, node). Call it again when the skills list changes. Pass args when the task gives the skill arguments (they replace " +
|
|
54
54
|
"$ARGUMENTS and $1, $2… in the instructions). Do not use it for skills that are not in the list.",
|
|
55
55
|
inputSchema: {
|