@capybara-hq/kernel 0.1.0-rc.8 → 0.1.0-rc.9
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/background/finish.js +2 -1
- package/dist/background/launch.d.ts +13 -7
- package/dist/background/launch.js +15 -8
- package/dist/background/poll.job.d.ts +1 -1
- package/dist/background/poll.job.js +8 -3
- package/dist/background/session.d.ts +2 -2
- package/dist/background/session.js +3 -2
- package/dist/background/status.d.ts +1 -1
- package/dist/background/status.js +1 -1
- package/dist/background/stop.js +4 -3
- package/dist/background/store.d.ts +6 -1
- package/dist/background/store.js +5 -1
- package/dist/background/tasks.service.d.ts +1 -1
- package/dist/background/tasks.service.js +4 -4
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/config/docs.js +2 -0
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +45 -0
- package/dist/conversations/event-log.d.ts +4 -3
- package/dist/conversations/event-log.js +4 -3
- package/dist/conversations/events.d.ts +9 -0
- package/dist/conversations/events.js +14 -0
- package/dist/db/kernel-schema.d.ts +26 -1
- package/dist/devices/device-auth.controller.d.ts +42 -0
- package/dist/devices/device-auth.controller.js +116 -0
- package/dist/devices/device-auth.d.ts +119 -0
- package/dist/devices/device-auth.js +403 -0
- package/dist/devices/device-auth.module.d.ts +7 -0
- package/dist/devices/device-auth.module.js +25 -0
- package/dist/devices/device-gateway.d.ts +128 -0
- package/dist/devices/device-gateway.js +530 -0
- package/dist/devices/device-provider.d.ts +89 -0
- package/dist/devices/device-provider.js +151 -0
- package/dist/devices/device-routes.d.ts +36 -0
- package/dist/devices/device-routes.js +90 -0
- package/dist/devices/devices.module.d.ts +16 -0
- package/dist/devices/devices.module.js +44 -0
- package/dist/devices/dto.d.ts +66 -0
- package/dist/devices/dto.js +44 -0
- package/dist/devices/keys.d.ts +25 -0
- package/dist/devices/keys.js +25 -0
- package/dist/devices/machines.d.ts +106 -0
- package/dist/devices/machines.js +301 -0
- package/dist/devices/relay.d.ts +100 -0
- package/dist/devices/relay.js +373 -0
- package/dist/devices/targets.d.ts +28 -0
- package/dist/devices/targets.js +73 -0
- package/dist/environments/access.d.ts +11 -6
- package/dist/environments/access.js +23 -8
- package/dist/environments/conversation-environments.d.ts +20 -6
- package/dist/environments/conversation-environments.js +70 -9
- package/dist/environments/environments.controller.d.ts +4 -1
- package/dist/environments/environments.controller.js +15 -1
- package/dist/environments/environments.module.js +3 -1
- package/dist/environments/environments.service.d.ts +54 -7
- package/dist/environments/environments.service.js +104 -20
- package/dist/environments/legacy-cleanup.job.d.ts +1 -1
- package/dist/environments/legacy-cleanup.job.js +4 -4
- package/dist/errors.d.ts +24 -0
- package/dist/errors.js +24 -0
- package/dist/evals/recorded-tools.d.ts +9 -0
- package/dist/evals/recorded-tools.js +27 -0
- package/dist/http/api.module.js +4 -0
- package/dist/http/gateway-routes.d.ts +7 -3
- package/dist/http/gateway-routes.js +6 -3
- package/dist/infra/config/kernel-config.service.js +2 -0
- package/dist/infra/throttler/named-throttlers.d.ts +5 -0
- package/dist/infra/throttler/named-throttlers.js +38 -0
- package/dist/kernel.module.js +3 -0
- package/dist/lifecycle/kernel-lifecycle.js +5 -0
- package/dist/platform/exclusions.js +33 -0
- package/dist/platform/ops/environments.ops.js +21 -3
- package/dist/roles/gateway-role.d.ts +14 -3
- package/dist/roles/gateway-role.js +25 -5
- package/dist/roles/gateway.module.d.ts +2 -1
- package/dist/roles/gateway.module.js +6 -3
- package/dist/runtime/context/build.d.ts +2 -0
- package/dist/runtime/context/build.js +14 -0
- package/dist/runtime/context/compaction.js +1 -0
- package/dist/runtime/context/environment-paths.d.ts +40 -0
- package/dist/runtime/context/environment-paths.js +48 -0
- package/dist/runtime/model-call.js +2 -0
- package/dist/sandbox/providers.d.ts +29 -7
- package/dist/sandbox/providers.js +43 -8
- package/dist/sandbox/sandbox.service.d.ts +70 -18
- package/dist/sandbox/sandbox.service.js +176 -59
- package/dist/tools/builtins/sandbox/bash.d.ts +2 -2
- package/dist/tools/builtins/sandbox/bash.js +39 -27
- package/dist/tools/builtins/sandbox/common.d.ts +60 -25
- package/dist/tools/builtins/sandbox/common.js +121 -30
- package/dist/tools/builtins/sandbox/deliver.js +9 -6
- package/dist/tools/builtins/sandbox/edit.js +40 -37
- package/dist/tools/builtins/sandbox/glob.js +13 -8
- package/dist/tools/builtins/sandbox/grep.js +10 -6
- package/dist/tools/builtins/sandbox/index.d.ts +6 -4
- package/dist/tools/builtins/sandbox/index.js +6 -4
- package/dist/tools/builtins/sandbox/read.js +9 -6
- package/dist/tools/builtins/sandbox/task-output.d.ts +7 -3
- package/dist/tools/builtins/sandbox/task-output.js +14 -10
- package/dist/tools/builtins/sandbox/task-stop.js +3 -3
- package/dist/tools/builtins/sandbox/write.js +33 -30
- package/dist/tools/builtins/skill-load.d.ts +8 -5
- package/dist/tools/builtins/skill-load.js +21 -15
- package/dist/tools/builtins/skill-write.js +2 -2
- package/dist/tools/pipeline.js +2 -0
- package/dist/tools/registry.d.ts +21 -4
- package/dist/tools/registry.js +10 -2
- package/package.json +5 -2
- package/src/migrations/0093_connected_environments.sql +37 -0
- package/src/migrations/0094_environment_paths_idx.sql +7 -0
|
@@ -4,6 +4,7 @@ import { renderBackgroundFinished } from "../../background/render.js";
|
|
|
4
4
|
import { isAbsorbable, } from "../../conversations/events.js";
|
|
5
5
|
import { catalogText, isAlwaysLoaded } from "../../tools/builtins/tool-search.js";
|
|
6
6
|
import { renderSubagentDelivery } from "../subagents/render.js";
|
|
7
|
+
import { environmentPathsAt, environmentPathsText } from "./environment-paths.js";
|
|
7
8
|
import { LEGACY_CATALOG_HEADER, LEGACY_SKILL_TEXTS, usesLegacyToolNames } from "./legacy-text.js";
|
|
8
9
|
import { currentRunContext, runContextEvents, runContextFullText, runContextText, senderLines, } from "./run-context.js";
|
|
9
10
|
import { invokedSkillNames, invokedSkillsText, SKILL_TEXTS, skillsCatalogEvents, snapshotChangeText, snapshotCurrentText, snapshotSystemText, systemSkillsCatalog, } from "./skills.js";
|
|
@@ -336,6 +337,8 @@ export function summaryText(summary) {
|
|
|
336
337
|
* `user_message` / `assistant_message`(content 原样,含 opaque;aborted 的也回放;媒体引用按
|
|
337
338
|
* {@link resolveMedia} 换成 base64 或文字占位)/ `reminder`
|
|
338
339
|
* (`role: "system"`,放在它所在的位置)/ `tools_changed` (`role: "system"`, the text of {@link toolsChangedText}, at its position) /
|
|
340
|
+
* `environment_paths` (`role: "system"` at its position, `environmentPathsText`; covered by the
|
|
341
|
+
* summary: the latest one follows the summary) /
|
|
339
342
|
* `run_context` (`role: "system"` at its position, `runContextText`; covered by the summary: the
|
|
340
343
|
* current context follows the summary whole; a user message's sender line by `senderLines`) /
|
|
341
344
|
* `background_task_finished`(`role: "system"`, the text of
|
|
@@ -547,6 +550,12 @@ export function buildModelRequest(input) {
|
|
|
547
550
|
content: runContextFullText(currentRunContext(events, after)),
|
|
548
551
|
});
|
|
549
552
|
}
|
|
553
|
+
// environments spec §13.1: the instance's directories told before the boundary follow the
|
|
554
|
+
// summary again (the model keeps using the real paths)
|
|
555
|
+
const paths = environmentPathsAt(events, after);
|
|
556
|
+
if (paths) {
|
|
557
|
+
messages.push({ role: "system", content: environmentPathsText(paths.payload.roots) });
|
|
558
|
+
}
|
|
550
559
|
// 被摘要覆盖的变更提醒不再发送:摘要时生效的目录与 system 里的不同 → 紧随摘要补上完整的当前目录
|
|
551
560
|
const covered = skillSnaps.filter((e) => e.seq <= after).at(-1);
|
|
552
561
|
// (phase B: plus the current pinned skills and tool-name map when they differ from system's)
|
|
@@ -620,6 +629,11 @@ export function buildModelRequest(input) {
|
|
|
620
629
|
case "run_context":
|
|
621
630
|
messages.push({ role: "system", content: runContextText(events, e) });
|
|
622
631
|
break;
|
|
632
|
+
// the instance's directories (environments spec §13.1): logged during a step's tool call,
|
|
633
|
+
// so rendered after that step's tool results
|
|
634
|
+
case "environment_paths":
|
|
635
|
+
messages.push({ role: "system", content: environmentPathsText(e.payload.roots) });
|
|
636
|
+
break;
|
|
623
637
|
// hook_denied is not model-visible: the model sees only the denied tool call's tool_result
|
|
624
638
|
// (and nothing for a denied model request), so request building skips it
|
|
625
639
|
case "hook_denied":
|
|
@@ -134,6 +134,7 @@ function transcriptLine(e) {
|
|
|
134
134
|
// phase D: a forked skill's report is user content of the request (build.ts)
|
|
135
135
|
case "skill_fork_result":
|
|
136
136
|
return `User: ${e.payload.text}`;
|
|
137
|
+
// environment_paths is left out too: the latest one follows the summary (build.ts)
|
|
137
138
|
// run_context is left out: its current values follow the summary anyway (build.ts), and
|
|
138
139
|
// stale names should not be summarised as current
|
|
139
140
|
case "tool_result":
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SandboxRoots } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { ConversationEvent } from "../../conversations/events.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `environment_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
|
|
5
|
+
* of every conversation records the instance's roots (the defaults included), and a later call
|
|
6
|
+
* records them again whenever they differ from the conversation's latest recorded ones (a
|
|
7
|
+
* connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
|
|
8
|
+
* its position as a system message; in the log, so it needs no `DriftReason`.
|
|
9
|
+
*/
|
|
10
|
+
/** The system line of an `environment_paths` event (model-visible, English). */
|
|
11
|
+
export declare function environmentPathsText(r: SandboxRoots): string;
|
|
12
|
+
/** Whether two sets of roots are the same paths. */
|
|
13
|
+
export declare const sameRoots: (a: SandboxRoots, b: SandboxRoots) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The idempotency key of an `environment_paths` event for `r` that follows the conversation's
|
|
16
|
+
* event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
|
|
17
|
+
* calls that saw the same latest event write it once.
|
|
18
|
+
*/
|
|
19
|
+
export declare const environmentPathsIdemKey: (r: SandboxRoots, prevSeq: number) => string;
|
|
20
|
+
/**
|
|
21
|
+
* The `environment_paths` event to append when a sandbox call holds an instance with `roots` and
|
|
22
|
+
* the conversation's latest such event is `latest`; null when nothing changed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function environmentPathsChange(latest: {
|
|
25
|
+
seq: number;
|
|
26
|
+
payload: {
|
|
27
|
+
roots: SandboxRoots;
|
|
28
|
+
};
|
|
29
|
+
} | undefined, roots: SandboxRoots, runId: string | undefined): {
|
|
30
|
+
type: "environment_paths";
|
|
31
|
+
runId?: string;
|
|
32
|
+
idemKey: string;
|
|
33
|
+
payload: {
|
|
34
|
+
roots: SandboxRoots;
|
|
35
|
+
};
|
|
36
|
+
} | null;
|
|
37
|
+
/** The latest `environment_paths` event at or before `seq`; undefined without one. */
|
|
38
|
+
export declare function environmentPathsAt(events: readonly ConversationEvent[], seq: number): Extract<ConversationEvent, {
|
|
39
|
+
type: "environment_paths";
|
|
40
|
+
}> | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* The `environment_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
|
|
4
|
+
* of every conversation records the instance's roots (the defaults included), and a later call
|
|
5
|
+
* records them again whenever they differ from the conversation's latest recorded ones (a
|
|
6
|
+
* connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
|
|
7
|
+
* its position as a system message; in the log, so it needs no `DriftReason`.
|
|
8
|
+
*/
|
|
9
|
+
/** The system line of an `environment_paths` event (model-visible, English). */
|
|
10
|
+
export function environmentPathsText(r) {
|
|
11
|
+
return `This environment's directories: workspace ${r.workspace}, skills ${r.skills} (read only), temporary files ${r.tmp}. Use these paths in commands and tool calls.`;
|
|
12
|
+
}
|
|
13
|
+
/** Whether two sets of roots are the same paths. */
|
|
14
|
+
export const sameRoots = (a, b) => a.workspace === b.workspace && a.skills === b.skills && a.tmp === b.tmp;
|
|
15
|
+
/**
|
|
16
|
+
* The idempotency key of an `environment_paths` event for `r` that follows the conversation's
|
|
17
|
+
* event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
|
|
18
|
+
* calls that saw the same latest event write it once.
|
|
19
|
+
*/
|
|
20
|
+
export const environmentPathsIdemKey = (r, prevSeq) => `environment_paths:${createHash("sha256")
|
|
21
|
+
.update(JSON.stringify([r.workspace, r.skills, r.tmp]))
|
|
22
|
+
.digest("hex")
|
|
23
|
+
.slice(0, 16)}:${prevSeq}`;
|
|
24
|
+
/**
|
|
25
|
+
* The `environment_paths` event to append when a sandbox call holds an instance with `roots` and
|
|
26
|
+
* the conversation's latest such event is `latest`; null when nothing changed.
|
|
27
|
+
*/
|
|
28
|
+
export function environmentPathsChange(latest, roots, runId) {
|
|
29
|
+
if (latest && sameRoots(latest.payload.roots, roots))
|
|
30
|
+
return null;
|
|
31
|
+
return {
|
|
32
|
+
type: "environment_paths",
|
|
33
|
+
...(runId !== undefined && { runId }),
|
|
34
|
+
idemKey: environmentPathsIdemKey(roots, latest?.seq ?? 0),
|
|
35
|
+
payload: { roots: { workspace: roots.workspace, skills: roots.skills, tmp: roots.tmp } },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** The latest `environment_paths` event at or before `seq`; undefined without one. */
|
|
39
|
+
export function environmentPathsAt(events, seq) {
|
|
40
|
+
let found;
|
|
41
|
+
for (const e of events) {
|
|
42
|
+
if (e.seq > seq)
|
|
43
|
+
break;
|
|
44
|
+
if (e.type === "environment_paths")
|
|
45
|
+
found = e;
|
|
46
|
+
}
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
@@ -163,6 +163,8 @@ wrapUpReason) {
|
|
|
163
163
|
subagents: input.agent.subagents,
|
|
164
164
|
child: input.parent !== undefined || input.parentRunId !== undefined || input.subagent === true,
|
|
165
165
|
forkChild: input.parent?.forSkill === true,
|
|
166
|
+
sandboxConversationId: input.parent?.rootConversationId ?? input.conversationId,
|
|
167
|
+
replay: input.conversationKind === "eval",
|
|
166
168
|
skillLoad: skillLoadOffered(events.findLast((e) => e.type === "skills_catalog"), hasInvocation(events)),
|
|
167
169
|
});
|
|
168
170
|
const tools = {
|
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import { type SandboxProvider } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
3
|
-
/**
|
|
3
|
+
/** The kernel-internal provider of connected machines (`device` environments). */
|
|
4
|
+
export declare const DEVICE_PROVIDER = "@device";
|
|
5
|
+
/**
|
|
6
|
+
* A kernel-internal provider name: it starts with `@`, which no extension name can
|
|
7
|
+
* (`EXTENSION_NAME_RE`), so it never collides with a loaded `sandbox@2` plugin.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isInternalProvider: (name: string) => boolean;
|
|
10
|
+
/** A sandbox provider: its name (plugin name, or an internal `@…` name) and implementation. */
|
|
4
11
|
export interface SandboxProviderEntry {
|
|
5
12
|
plugin: string;
|
|
6
13
|
impl: SandboxProvider;
|
|
7
14
|
}
|
|
8
15
|
/**
|
|
9
|
-
* The
|
|
10
|
-
* call
|
|
16
|
+
* The sandbox providers of this process. The cloud provider is the loaded `sandbox@2` extension,
|
|
17
|
+
* read from the {@link ExtensionRegistry} at every call (providers are ready only after the
|
|
18
|
+
* extension load, and change with installs). Kernel-internal providers (`@device`) are registered
|
|
19
|
+
* by their module on init, in every replica: they are code, not shared state.
|
|
11
20
|
*/
|
|
12
21
|
export declare class SandboxProviders {
|
|
13
22
|
private readonly registry;
|
|
23
|
+
private readonly internal;
|
|
14
24
|
constructor(registry: Pick<ExtensionRegistry, "list">);
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
25
|
+
/** Registers a kernel-internal provider (an `@…` name); a later registration replaces it. */
|
|
26
|
+
register(entry: SandboxProviderEntry): void;
|
|
27
|
+
/** The cloud provider (the loaded `sandbox@2` extension); undefined without one. */
|
|
28
|
+
cloud(): SandboxProviderEntry | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* A provider by name: a registered internal provider, else a still-loaded `sandbox@2` extension
|
|
31
|
+
* of that plugin name (to destroy an earlier provider's resources).
|
|
32
|
+
*/
|
|
33
|
+
forName(name: string): SandboxProviderEntry | undefined;
|
|
19
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The provider an environment works with, from its `environments.provider`: an internal one
|
|
37
|
+
* (`@device`) by that name only, never falling back to the cloud provider; any other environment
|
|
38
|
+
* is a cloud environment and uses the current cloud provider (files kept by another provider are
|
|
39
|
+
* refused by the sandbox service, `environment.unavailable`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function providerForEnvironment(providers: Pick<SandboxProviders, "cloud" | "forName">, provider: string | null): SandboxProviderEntry | undefined;
|
|
@@ -10,25 +10,49 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
10
10
|
import { SANDBOX_EXTENSION_POINT } from "@capybara-hq/plugin-sdk";
|
|
11
11
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
12
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
13
|
+
/** The kernel-internal provider of connected machines (`device` environments). */
|
|
14
|
+
export const DEVICE_PROVIDER = "@device";
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* A kernel-internal provider name: it starts with `@`, which no extension name can
|
|
17
|
+
* (`EXTENSION_NAME_RE`), so it never collides with a loaded `sandbox@2` plugin.
|
|
18
|
+
*/
|
|
19
|
+
export const isInternalProvider = (name) => name.startsWith("@");
|
|
20
|
+
/**
|
|
21
|
+
* The sandbox providers of this process. The cloud provider is the loaded `sandbox@2` extension,
|
|
22
|
+
* read from the {@link ExtensionRegistry} at every call (providers are ready only after the
|
|
23
|
+
* extension load, and change with installs). Kernel-internal providers (`@device`) are registered
|
|
24
|
+
* by their module on init, in every replica: they are code, not shared state.
|
|
16
25
|
*/
|
|
17
26
|
let SandboxProviders = class SandboxProviders {
|
|
18
27
|
registry;
|
|
28
|
+
internal = new Map();
|
|
19
29
|
constructor(registry) {
|
|
20
30
|
this.registry = registry;
|
|
21
31
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
32
|
+
/** Registers a kernel-internal provider (an `@…` name); a later registration replaces it. */
|
|
33
|
+
register(entry) {
|
|
34
|
+
if (!isInternalProvider(entry.plugin)) {
|
|
35
|
+
throw new Error(`an internal sandbox provider name starts with @: ${entry.plugin}`);
|
|
36
|
+
}
|
|
37
|
+
this.internal.set(entry.plugin, entry);
|
|
38
|
+
}
|
|
39
|
+
/** The cloud provider (the loaded `sandbox@2` extension); undefined without one. */
|
|
40
|
+
cloud() {
|
|
24
41
|
const e = this.registry.list("sandbox").find((x) => x.capability === SANDBOX_EXTENSION_POINT);
|
|
25
42
|
return e ? { plugin: e.plugin, impl: e.impl } : undefined;
|
|
26
43
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
/**
|
|
45
|
+
* A provider by name: a registered internal provider, else a still-loaded `sandbox@2` extension
|
|
46
|
+
* of that plugin name (to destroy an earlier provider's resources).
|
|
47
|
+
*/
|
|
48
|
+
forName(name) {
|
|
49
|
+
const own = this.internal.get(name);
|
|
50
|
+
if (own)
|
|
51
|
+
return own;
|
|
52
|
+
const e = this.registry
|
|
30
53
|
.list("sandbox")
|
|
31
|
-
.find((x) => x.capability === SANDBOX_EXTENSION_POINT && x.plugin ===
|
|
54
|
+
.find((x) => x.capability === SANDBOX_EXTENSION_POINT && x.plugin === name);
|
|
55
|
+
return e ? { plugin: e.plugin, impl: e.impl } : undefined;
|
|
32
56
|
}
|
|
33
57
|
};
|
|
34
58
|
SandboxProviders = __decorate([
|
|
@@ -36,3 +60,14 @@ SandboxProviders = __decorate([
|
|
|
36
60
|
__param(0, Inject(ExtensionRegistry))
|
|
37
61
|
], SandboxProviders);
|
|
38
62
|
export { SandboxProviders };
|
|
63
|
+
/**
|
|
64
|
+
* The provider an environment works with, from its `environments.provider`: an internal one
|
|
65
|
+
* (`@device`) by that name only, never falling back to the cloud provider; any other environment
|
|
66
|
+
* is a cloud environment and uses the current cloud provider (files kept by another provider are
|
|
67
|
+
* refused by the sandbox service, `environment.unavailable`).
|
|
68
|
+
*/
|
|
69
|
+
export function providerForEnvironment(providers, provider) {
|
|
70
|
+
if (provider !== null && isInternalProvider(provider))
|
|
71
|
+
return providers.forName(provider);
|
|
72
|
+
return providers.cloud();
|
|
73
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExecResult, type SandboxActor, type SandboxHandle, type SandboxLimits, type SandboxProvider } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type ExecResult, type SandboxActor, type SandboxHandle, type SandboxLimits, type SandboxProvider, type SandboxRoots } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { Redis } from "ioredis";
|
|
3
3
|
import { type Selectable } from "kysely";
|
|
4
4
|
import { BackgroundTaskStore } from "../background/store.js";
|
|
@@ -21,6 +21,11 @@ export interface SandboxUseInfo {
|
|
|
21
21
|
* new instance on the kept files, so earlier processes are gone.
|
|
22
22
|
*/
|
|
23
23
|
restarted: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The instance's roots ({@link rootsOf} of its handle): every model-visible path is built from
|
|
26
|
+
* them (environments spec §13.1; a connected machine's real paths).
|
|
27
|
+
*/
|
|
28
|
+
roots: SandboxRoots;
|
|
24
29
|
}
|
|
25
30
|
/** Who uses the sandbox in {@link SandboxService.withSandbox}. */
|
|
26
31
|
export interface SandboxUseContext {
|
|
@@ -78,8 +83,12 @@ export type IfRunningResult<T> = {
|
|
|
78
83
|
export declare const environmentResourceName: (environmentId: string) => string;
|
|
79
84
|
/** The environment is missing, deleted, or not in the caller's org (404; ids cannot be probed). */
|
|
80
85
|
export declare const environmentNotFound: () => ApiError;
|
|
81
|
-
/**
|
|
82
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The environment's file system is gone at the provider (409); never replaced silently. On a
|
|
88
|
+
* connected machine (`device`, details `[{ source: "device" }]`) the directory is the machine
|
|
89
|
+
* owner's: `capybara-device logout`, then `login` into the environment, recreates it (ruling R24).
|
|
90
|
+
*/
|
|
91
|
+
export declare const environmentFilesLost: (device?: boolean) => ApiError;
|
|
83
92
|
/**
|
|
84
93
|
* The row still records the instance `gone` (same handle and same `created_at`). Handles compare
|
|
85
94
|
* canonically: a jsonb round trip reorders a multi-key handle's keys (final review m9).
|
|
@@ -146,18 +155,25 @@ export declare const sandboxUnavailable: () => ApiError;
|
|
|
146
155
|
* row's `created_at` (set by the fenced write after each `create`); the handle alone is the same
|
|
147
156
|
* after a rebuild on providers whose handle is the instance name (sandbox-docker).
|
|
148
157
|
*
|
|
158
|
+
* - **Provider per environment** (environments spec §6): each call resolves the provider from
|
|
159
|
+
* the environment's `environments.provider` ({@link providerForEnvironment}): a `device`
|
|
160
|
+
* environment runs on the kernel-internal `@device` provider (never the cloud one), a cloud
|
|
161
|
+
* environment on the loaded `sandbox@2` extension. A `@device` instance is never suspended or
|
|
162
|
+
* destroyed for inactivity; a deleted `@device` environment (or one of a deleted org) is
|
|
163
|
+
* destroyed by the sweep like any other.
|
|
164
|
+
*
|
|
149
165
|
* Files kept by another provider than the current one make the environment unusable
|
|
150
166
|
* (`environment.unavailable`) until that provider is back; deleting it still works while that
|
|
151
|
-
* provider is loaded ({@link SandboxProviders.
|
|
152
|
-
* uses throw `sandbox.unavailable` (503). No in-process state:
|
|
153
|
-
* Redis.
|
|
167
|
+
* provider is loaded ({@link SandboxProviders.forName}). Without the environment's provider
|
|
168
|
+
* {@link availableFor} is false and uses throw `sandbox.unavailable` (503). No in-process state:
|
|
169
|
+
* replicas share it through PG and Redis.
|
|
154
170
|
*/
|
|
155
171
|
export declare class SandboxService {
|
|
156
172
|
private readonly db;
|
|
157
173
|
/** The record of the instance each holder last used (`capk:sandbox-seen:…`). */
|
|
158
174
|
private readonly redis;
|
|
159
175
|
private readonly leases;
|
|
160
|
-
/** The
|
|
176
|
+
/** The providers (read at every call: providers are ready after the extension load). */
|
|
161
177
|
private readonly providers;
|
|
162
178
|
private readonly config;
|
|
163
179
|
private readonly log;
|
|
@@ -166,14 +182,28 @@ export declare class SandboxService {
|
|
|
166
182
|
constructor(db: KernelDb,
|
|
167
183
|
/** The record of the instance each holder last used (`capk:sandbox-seen:…`). */
|
|
168
184
|
redis: Pick<Redis, "set">, leases: SandboxLeases,
|
|
169
|
-
/** The
|
|
170
|
-
providers: Pick<SandboxProviders, "
|
|
185
|
+
/** The providers (read at every call: providers are ready after the extension load). */
|
|
186
|
+
providers: Pick<SandboxProviders, "cloud" | "forName">, config: Pick<KernelConfigService, "get">, log: Pick<KernelLogger, "info" | "warn">,
|
|
171
187
|
/** Background tasks lost while their process may live on (killed after a resume). */
|
|
172
188
|
background: Pick<BackgroundTaskStore, "pendingKills" | "clearKill">);
|
|
173
189
|
/** Poll interval while waiting for a lease (the leases' own). */
|
|
174
190
|
private get pollMs();
|
|
175
|
-
/**
|
|
176
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Whether there is a cloud sandbox provider (a loaded `sandbox@2` extension). For gates that have
|
|
193
|
+
* no environment yet (tool listing, skill files); a call on an environment asks
|
|
194
|
+
* {@link availableFor}.
|
|
195
|
+
*/
|
|
196
|
+
get cloudAvailable(): boolean;
|
|
197
|
+
/** Whether the environment's provider is there (`@device` registered, or a cloud provider). */
|
|
198
|
+
availableFor(environmentId: string): Promise<boolean>;
|
|
199
|
+
/**
|
|
200
|
+
* The provider the environment works with, read from its row ({@link providerForEnvironment});
|
|
201
|
+
* a missing row (or one of another org, with `orgId`) reads as a cloud environment, whose use
|
|
202
|
+
* then fails as not found.
|
|
203
|
+
*/
|
|
204
|
+
private providerOf;
|
|
205
|
+
/** {@link providerOf}, or `sandbox.unavailable` (503) without it. */
|
|
206
|
+
private require;
|
|
177
207
|
/** Concurrent holders of one environment (`sandbox.maxConcurrentCommands`, read at every use). */
|
|
178
208
|
private get cap();
|
|
179
209
|
/**
|
|
@@ -233,11 +263,20 @@ export declare class SandboxService {
|
|
|
233
263
|
* - When `fn` throws `SandboxGoneError` (the instance is gone) the instance is rebuilt on the
|
|
234
264
|
* kept files (or the one another holder rebuilt is taken) and `fn` runs once more with
|
|
235
265
|
* `restarted: true`; a second `gone` is thrown. Other errors are thrown as they are.
|
|
236
|
-
* - Lost files throw `environment.files_lost` (409)
|
|
266
|
+
* - Lost files throw `environment.files_lost` (409), also when `fn` throws
|
|
267
|
+
* `FileSystemGoneError` (the files are then recorded lost, ruling R24).
|
|
237
268
|
* - When `ctx.holderConversationId` last used another instance (another holder rebuilt it
|
|
238
269
|
* meanwhile), `info.restarted` is true, so it learns its processes are gone (final review m5).
|
|
239
270
|
*/
|
|
240
271
|
withSandbox<T>(ctx: SandboxUseContext, fn: (h: SandboxHandle, p: HeldProvider, info: SandboxUseInfo, signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>;
|
|
272
|
+
/**
|
|
273
|
+
* Ruling R24: an operation on the held instance `e` found its file system gone. Records
|
|
274
|
+
* `fs_status = 'lost'` while the row still records that instance on its file system (the
|
|
275
|
+
* instance handle stays, so a reset still destroys it), moving the fence on, and returns
|
|
276
|
+
* `environment.files_lost` to throw. Uses then fail until the files are reset (a connected
|
|
277
|
+
* machine: until a new login, which resets the row). A failed write only logs.
|
|
278
|
+
*/
|
|
279
|
+
private filesGone;
|
|
241
280
|
/**
|
|
242
281
|
* Records that `holder` uses the instance of `e` (`capk:sandbox-seen:<env>:<holder>` =
|
|
243
282
|
* `<created_at>:<file system id>`, with a TTL of the inactivity destruction plus a day). When the
|
|
@@ -258,7 +297,10 @@ export declare class SandboxService {
|
|
|
258
297
|
private readRow;
|
|
259
298
|
/** The instance row belongs to another org: reported as not found, so ids cannot be probed. */
|
|
260
299
|
private foreign;
|
|
261
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* Recorded running with the current provider on files not lost (lost ones go through the
|
|
302
|
+
* exclusive path, which refuses them), and not the instance found gone.
|
|
303
|
+
*/
|
|
262
304
|
private usable;
|
|
263
305
|
/**
|
|
264
306
|
* Under a just-taken shared lease: confirms the row is still `accept`ed (else releases and
|
|
@@ -295,15 +337,19 @@ export declare class SandboxService {
|
|
|
295
337
|
* (the instance only; the file system is kept and the next use builds a new instance on it); an
|
|
296
338
|
* environment that was deleted, or whose org was deleted, loses its instance and its file system
|
|
297
339
|
* and its row is deleted (as {@link destroyEnvironment} does). Environments with any holder are
|
|
298
|
-
* skipped.
|
|
340
|
+
* skipped. Inactivity never destroys a `@device` instance; a gone `@device` environment is
|
|
341
|
+
* destroyed like any other while `@device` is registered. Returns how many it destroyed. Called
|
|
342
|
+
* hourly by the `sandbox-destroy-inactive` cron.
|
|
299
343
|
*/
|
|
300
344
|
destroyInactive(now: Date): Promise<number>;
|
|
301
345
|
/**
|
|
302
346
|
* Destroys the environment's instance, then its file system, then deletes its instance row
|
|
303
347
|
* (environment deletion, offboarding); nothing to do without a row. Waits for the exclusive lease
|
|
304
|
-
* (with intent, so running commands finish and new ones wait) up to 10 minutes. Without
|
|
305
|
-
* provider it
|
|
306
|
-
*
|
|
348
|
+
* (with intent, so running commands finish and new ones wait) up to 10 minutes. Without the
|
|
349
|
+
* environment's provider it throws and the row stays, marked `sweep_failed_at` (the job is
|
|
350
|
+
* retried, and the destruction sweep finishes a deleted environment once the provider is back).
|
|
351
|
+
* Files kept by another provider that is not loaded cannot be destroyed: it throws and the row
|
|
352
|
+
* stays.
|
|
307
353
|
*/
|
|
308
354
|
destroyEnvironment(environmentId: string): Promise<void>;
|
|
309
355
|
/**
|
|
@@ -329,7 +375,6 @@ export declare class SandboxService {
|
|
|
329
375
|
* and leaves the flags of this instance's tasks for the next resume.
|
|
330
376
|
*/
|
|
331
377
|
private killPendingTasks;
|
|
332
|
-
private require;
|
|
333
378
|
/**
|
|
334
379
|
* A fenced write: only while the lease holds and the row's generation is still the one read
|
|
335
380
|
* under it (and incremented); otherwise {@link SandboxLockLostError}.
|
|
@@ -390,6 +435,13 @@ export declare class SandboxService {
|
|
|
390
435
|
private destroyRow;
|
|
391
436
|
/** Due for suspension: made by this provider, running, and idle or running for too long. */
|
|
392
437
|
private suspendDue;
|
|
438
|
+
/** The provider of an instance row: an internal one (`@device`) by name, else the cloud one. */
|
|
439
|
+
private rowProvider;
|
|
440
|
+
/**
|
|
441
|
+
* A row the idle sweeps may touch: not on a kernel-internal provider (`@device` instances live on
|
|
442
|
+
* people's machines and are never suspended or destroyed for inactivity).
|
|
443
|
+
*/
|
|
444
|
+
private cloudRow;
|
|
393
445
|
/** The environment was deleted, or its org was: the instance and the files go. */
|
|
394
446
|
private environmentGone;
|
|
395
447
|
/** Due for instance destruction: an instance (or a create in progress) unused for too long. */
|