@capybara-hq/kernel 0.1.0-rc.7 → 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/identity/me.controller.d.ts +1 -3
- package/dist/identity/me.controller.js +1 -9
- package/dist/identity/me.schemas.d.ts +14 -0
- package/dist/identity/me.schemas.js +17 -0
- 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 -10
- package/dist/platform/operation.js +1 -1
- package/dist/platform/ops/environments.ops.js +21 -3
- package/dist/platform/ops/platform.ops.d.ts +7 -2
- package/dist/platform/ops/platform.ops.js +50 -3
- package/dist/platform/route-schemas.js +3 -0
- 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 +6 -3
- package/src/migrations/0093_connected_environments.sql +37 -0
- package/src/migrations/0094_environment_paths_idx.sql +7 -0
|
@@ -9,19 +9,19 @@ import { resolveSkillForkType } from "../../runtime/subagents/roster.js";
|
|
|
9
9
|
import { invocationOf, resolveSkills } from "../../skills/catalog.js";
|
|
10
10
|
import { PLATFORM_SOURCE, skillFileStore } from "../../skills/usable.js";
|
|
11
11
|
import { AGENT_TOOL, SANDBOX_TOOL_NAMES } from "../builtin-names.js";
|
|
12
|
-
import { clip, display, inSandbox, sandboxBaseTimeoutMs, withNotices, withPathLease, } from "./sandbox/common.js";
|
|
12
|
+
import { clip, display, inSandbox, sandboxBaseTimeoutMs, sandboxConversationOf, sandboxUsable, withNotices, withPathLease, } from "./sandbox/common.js";
|
|
13
13
|
/**
|
|
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
|
-
* environment at
|
|
17
|
+
* environment 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.
|
|
21
21
|
*
|
|
22
22
|
* - Every file read from storage is checked against the manifest's sha256; a mismatch is refused.
|
|
23
23
|
* - The copy is recorded in a kernel-controlled marker, one per skill version
|
|
24
|
-
* ({@link skillMarkerPath}:
|
|
24
|
+
* ({@link skillMarkerPath}: `<skills root>/.capybara/<name>@<digest>.json`, commands cannot change it):
|
|
25
25
|
* `{ digest, files }`. A present marker with the same digest skips the copy.
|
|
26
26
|
* - An agent without any sandbox tool creates no sandbox and copies nothing (the model could not
|
|
27
27
|
* run the scripts), and is told so.
|
|
@@ -29,11 +29,14 @@ import { clip, display, inSandbox, sandboxBaseTimeoutMs, withNotices, withPathLe
|
|
|
29
29
|
* to run scripts with an interpreter (`sh` / `python3` / `node`).
|
|
30
30
|
*/
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* 已复制进沙箱的技能的标记目录(内核控制:技能根目录只有提供方以 root 能写;技能名不以 `.` 开头,不会撞名)。
|
|
33
|
+
* `skillsRoot`: the instance's skills root.
|
|
33
34
|
*/
|
|
34
|
-
export const
|
|
35
|
+
export const skillsMarkerDir = (skillsRoot = SKILLS_SANDBOX_ROOT) => `${skillsRoot}/.capybara`;
|
|
36
|
+
/** {@link skillsMarkerDir} of an instance with the default roots. */
|
|
37
|
+
export const SKILLS_MARKER_DIR = skillsMarkerDir();
|
|
35
38
|
/** The marker file of one skill version. */
|
|
36
|
-
export const skillMarkerPath = (name, digest) => `${
|
|
39
|
+
export const skillMarkerPath = (name, digest, skillsRoot = SKILLS_SANDBOX_ROOT) => `${skillsMarkerDir(skillsRoot)}/${name}@${digest}.json`;
|
|
37
40
|
const MARKER_MAX_BYTES = 256 * 1024;
|
|
38
41
|
/** 结果里最多列出的文件数(其余只给数量)。 */
|
|
39
42
|
const MAX_LISTED_FILES = 200;
|
|
@@ -46,7 +49,7 @@ const SPEC = {
|
|
|
46
49
|
description: "Load a skill from the skills list: returns the skill's instructions " +
|
|
47
50
|
"(its SKILL.md) and the list of its files. Call it before starting a task that matches a " +
|
|
48
51
|
"skill's description, then follow the returned instructions. If the skill has scripts or other " +
|
|
49
|
-
"files, they are copied read-only to
|
|
52
|
+
"files, they are copied read-only to <name>@<version-id>/ in the skills directory of this environment; run scripts " +
|
|
50
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 " +
|
|
51
54
|
"$ARGUMENTS and $1, $2… in the instructions). Do not use it for skills that are not in the list.",
|
|
52
55
|
inputSchema: {
|
|
@@ -157,13 +160,13 @@ async function readVerified(store, key, file, skill, signal) {
|
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
/**
|
|
160
|
-
* Copies the skill version whole to
|
|
163
|
+
* Copies the skill version whole to `<skills root>/<name>@<digest>/` (skipped when its marker matches).
|
|
161
164
|
* Every file is checked against the manifest before it is written; the marker is written last, so
|
|
162
165
|
* a failure midway means the next call starts over.
|
|
163
166
|
*/
|
|
164
167
|
async function copySkill(s, v) {
|
|
165
168
|
const { name } = v.entry;
|
|
166
|
-
const markerPath = skillMarkerPath(name, v.digest);
|
|
169
|
+
const markerPath = skillMarkerPath(name, v.digest, s.roots.skills);
|
|
167
170
|
const m = await s.p.readFile(s.h, markerPath, { maxBytes: MARKER_MAX_BYTES, signal: s.signal });
|
|
168
171
|
const cur = m && !m.truncated ? parseMarker(m.data) : undefined;
|
|
169
172
|
if (cur?.digest === v.digest)
|
|
@@ -172,7 +175,7 @@ async function copySkill(s, v) {
|
|
|
172
175
|
const data = f.path === SKILL_ENTRY_FILE
|
|
173
176
|
? v.entryBytes
|
|
174
177
|
: await readVerified(v.store, `${v.keyPrefix}${f.path}`, f, name, s.signal);
|
|
175
|
-
await s.p.writeFile(s.h, skillSandboxPath(name, v.digest, f.path), data, s.signal);
|
|
178
|
+
await s.p.writeFile(s.h, skillSandboxPath(name, v.digest, f.path, s.roots.skills), data, s.signal);
|
|
176
179
|
}
|
|
177
180
|
const entry = { digest: v.digest, files: v.manifest.files.map((f) => f.path) };
|
|
178
181
|
await s.p.writeFile(s.h, markerPath, new TextEncoder().encode(JSON.stringify(entry)), s.signal);
|
|
@@ -435,14 +438,15 @@ export function skillLoadTool(deps) {
|
|
|
435
438
|
}
|
|
436
439
|
if ("errors" in parsed)
|
|
437
440
|
throw new ToolError(integrityText(name));
|
|
438
|
-
|
|
441
|
+
// the skills root of the instance the files were copied to (the default without a copy)
|
|
442
|
+
let skillsRoot = SKILLS_SANDBOX_ROOT;
|
|
439
443
|
const others = manifest.files.filter((f) => f.path !== SKILL_ENTRY_FILE);
|
|
440
444
|
let notices = [];
|
|
441
445
|
let filesText;
|
|
442
446
|
if (others.length === 0) {
|
|
443
447
|
filesText = "This skill has no files besides its instructions.";
|
|
444
448
|
}
|
|
445
|
-
else if (!deps.
|
|
449
|
+
else if (!(await sandboxUsable(deps, ctx.orgId, sandboxConversationOf(ctx)))) {
|
|
446
450
|
filesText =
|
|
447
451
|
`Skill files (for reference):\n${fileList(manifest.files)}\n` +
|
|
448
452
|
"This deployment has no sandbox, so the skill's scripts and other files are not available; " +
|
|
@@ -457,19 +461,21 @@ export function skillLoadTool(deps) {
|
|
|
457
461
|
}
|
|
458
462
|
else {
|
|
459
463
|
// the skill's marker is read, compared and written: one holder at a time per skill
|
|
460
|
-
const r = await inSandbox(deps, ctx, (s) => withPathLease(deps, s, skillMarkerPath(name, digest), s.signal, (signal) => copySkill({ ...s, signal }, v)));
|
|
464
|
+
const r = await inSandbox(deps, ctx, (s) => withPathLease(deps, s, skillMarkerPath(name, digest, s.roots.skills), s.signal, (signal) => copySkill({ ...s, signal }, v).then(() => s.roots)));
|
|
461
465
|
notices = r.notices;
|
|
466
|
+
skillsRoot = r.value.skills;
|
|
467
|
+
const dir = `${skillSandboxDir(name, digest, skillsRoot)}/`;
|
|
462
468
|
filesText =
|
|
463
469
|
`Skill files (copied to ${dir} in the sandbox, read-only):\n${fileList(manifest.files)}\n` +
|
|
464
470
|
`Run scripts with an interpreter, e.g. \`sh ${dir}<script>.sh\`, \`python3 ${dir}<script>.py\` ` +
|
|
465
471
|
"or `node`; the files are not executable, so `./script` does not work. Write outputs under " +
|
|
466
|
-
|
|
472
|
+
`${r.value.workspace}, not ${skillsRoot}.`;
|
|
467
473
|
}
|
|
468
474
|
const invocation = invocationOf(manifest);
|
|
469
475
|
const fork = invocation.fork;
|
|
470
476
|
let instructions;
|
|
471
477
|
try {
|
|
472
|
-
instructions = renderSkillBody(parsed.body.trim(), invocation, args ?? "", name, digest);
|
|
478
|
+
instructions = renderSkillBody(parsed.body.trim(), invocation, args ?? "", name, digest, skillsRoot);
|
|
473
479
|
}
|
|
474
480
|
catch (e) {
|
|
475
481
|
if (!(e instanceof RangeError))
|
|
@@ -29,7 +29,7 @@ const SPEC = {
|
|
|
29
29
|
"user-invocable, context, agent, argument-hint and arguments, which are kept; with " +
|
|
30
30
|
"disable-model-invocation: true only a person can invoke the skill and you cannot load it). " +
|
|
31
31
|
"Every other entry in `files` is stored at " +
|
|
32
|
-
"
|
|
32
|
+
"<name>@<version-id>/<path> in the skills directory once loaded. Files of the previous version that you leave out are not part of the new " +
|
|
33
33
|
"version. Text files only, at most 1 MB per skill in total. Use an existing skill's name to update " +
|
|
34
34
|
"it; an organization skill with the same name is shadowed by this agent's own skill. On a private " +
|
|
35
35
|
"agent the new version takes effect immediately. On a shared agent the change is sent to the " +
|
|
@@ -41,7 +41,7 @@ const SPEC = {
|
|
|
41
41
|
type: "string",
|
|
42
42
|
minLength: 1,
|
|
43
43
|
maxLength: SKILL_LIMITS.nameMaxChars,
|
|
44
|
-
description: 'Skill name: lowercase letters, digits and single hyphens, e.g. "weekly-report". Also the folder name
|
|
44
|
+
description: 'Skill name: lowercase letters, digits and single hyphens, e.g. "weekly-report". Also the folder name in the skills directory.',
|
|
45
45
|
},
|
|
46
46
|
description: {
|
|
47
47
|
type: "string",
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -249,6 +249,8 @@ let ToolPipeline = class ToolPipeline {
|
|
|
249
249
|
subagents: ctx.agent.subagents,
|
|
250
250
|
child: ctx.parent !== undefined,
|
|
251
251
|
forkChild: ctx.parent?.forSkill === true,
|
|
252
|
+
sandboxConversationId: ctx.parent?.rootConversationId ?? ctx.conversationId,
|
|
253
|
+
replay: ctx.conversationKind === "eval",
|
|
252
254
|
});
|
|
253
255
|
}
|
|
254
256
|
return resolved;
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -12,6 +12,18 @@ import { BuiltinTools } from "./builtins/builtin-tools.js";
|
|
|
12
12
|
import { OrgToolSwitches } from "./org-tool-switches.js";
|
|
13
13
|
export { BUILTIN_TOOL_NAMES } from "./builtin-names.js";
|
|
14
14
|
export { canonicalJson } from "./canonical-json.js";
|
|
15
|
+
/**
|
|
16
|
+
* What a tool's `unavailable` is asked with: the org, and for a run's listing the root
|
|
17
|
+
* conversation (`conversationId`; the sandbox tools follow its environment, ruling R8), whether
|
|
18
|
+
* the run is an eval replay (`replay`: its tool calls are never executed), and a `memo` shared by
|
|
19
|
+
* the tools of one listing (one answer per question per listing).
|
|
20
|
+
*/
|
|
21
|
+
export interface ToolListingScope {
|
|
22
|
+
orgId: string;
|
|
23
|
+
conversationId?: string;
|
|
24
|
+
replay?: boolean;
|
|
25
|
+
memo?: Map<string, unknown>;
|
|
26
|
+
}
|
|
15
27
|
/** A problem of a saved definition's `disabledTools` (`path` is `disabledTools.<i>`). */
|
|
16
28
|
export interface ToolRefIssue {
|
|
17
29
|
path: string;
|
|
@@ -82,11 +94,9 @@ export interface BuiltinTool {
|
|
|
82
94
|
/**
|
|
83
95
|
* The factual reason this tool is unavailable for the org right now (English, actionable; it goes
|
|
84
96
|
* into the "tool unavailable" reminder), or null when it is available. May be async (e.g. a
|
|
85
|
-
* per-org extension-point default).
|
|
97
|
+
* per-org extension-point default). See {@link ToolListingScope} for what a listing passes.
|
|
86
98
|
*/
|
|
87
|
-
unavailable?: (ctx:
|
|
88
|
-
orgId: string;
|
|
89
|
-
}) => Promise<string | null> | string | null;
|
|
99
|
+
unavailable?: (ctx: ToolListingScope) => Promise<string | null> | string | null;
|
|
90
100
|
/**
|
|
91
101
|
* A policy key derived from the validated input (Platform tool spec §6.2), or null to use the
|
|
92
102
|
* tool key. With a key, agent and parent policies, grants and approvals are keyed by it (an
|
|
@@ -334,6 +344,13 @@ export declare class ToolRegistry {
|
|
|
334
344
|
* it reaches its parent with neither tool (`SendMessage` only to its own sub-agents).
|
|
335
345
|
*/
|
|
336
346
|
forkChild?: boolean;
|
|
347
|
+
/**
|
|
348
|
+
* The root conversation of the run (a sub-agent's tree root): tools whose availability
|
|
349
|
+
* depends on the conversation's environment (the sandbox tools) are listed by it.
|
|
350
|
+
*/
|
|
351
|
+
sandboxConversationId?: string;
|
|
352
|
+
/** An eval replay's listing: its calls are never executed (the sandbox tools are listed). */
|
|
353
|
+
replay?: boolean;
|
|
337
354
|
}): Promise<AgentTools>;
|
|
338
355
|
/** 是否启用 `ToolSearch`(见 {@link forAgent});先比阈值,超过才读组织设置。 */
|
|
339
356
|
private searchActive;
|
package/dist/tools/registry.js
CHANGED
|
@@ -348,6 +348,14 @@ let ToolRegistry = class ToolRegistry {
|
|
|
348
348
|
}
|
|
349
349
|
async resolve(orgId, def) {
|
|
350
350
|
const disabled = def.disabledTools ?? [];
|
|
351
|
+
const scope = {
|
|
352
|
+
orgId,
|
|
353
|
+
...(def.sandboxConversationId !== undefined && {
|
|
354
|
+
conversationId: def.sandboxConversationId,
|
|
355
|
+
}),
|
|
356
|
+
...(def.replay === true && { replay: true }),
|
|
357
|
+
memo: new Map(),
|
|
358
|
+
};
|
|
351
359
|
const tools = [];
|
|
352
360
|
const unavailable = [];
|
|
353
361
|
const hidden = [];
|
|
@@ -373,7 +381,7 @@ let ToolRegistry = class ToolRegistry {
|
|
|
373
381
|
const b = this.builtins.find((x) => x.spec.name === name);
|
|
374
382
|
if (!b)
|
|
375
383
|
continue;
|
|
376
|
-
const why = (await b.unavailable?.(
|
|
384
|
+
const why = (await b.unavailable?.(scope)) ?? null;
|
|
377
385
|
if (why !== null) {
|
|
378
386
|
hidden.push({ ref: name, reason: why });
|
|
379
387
|
continue;
|
|
@@ -392,7 +400,7 @@ let ToolRegistry = class ToolRegistry {
|
|
|
392
400
|
if (name === BUILTIN.Bash) {
|
|
393
401
|
for (const companion of BASH_COMPANION_TOOLS) {
|
|
394
402
|
const c = this.builtins.find((x) => x.spec.name === companion);
|
|
395
|
-
if (c && ((await c.unavailable?.(
|
|
403
|
+
if (c && ((await c.unavailable?.(scope)) ?? null) === null) {
|
|
396
404
|
add(builtinTool(c, c.spec));
|
|
397
405
|
}
|
|
398
406
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.9",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@fastify/cookie": "^11.1.3",
|
|
34
34
|
"@fastify/static": "^9.3.0",
|
|
35
|
+
"@fastify/websocket": "^11.3.7",
|
|
35
36
|
"@langchain/core": "^1.2.18",
|
|
36
37
|
"@langchain/langgraph": "^1.5.1",
|
|
37
38
|
"@langchain/langgraph-checkpoint": "^1.2.0",
|
|
@@ -64,14 +65,16 @@
|
|
|
64
65
|
"yaml": "^2.9.1",
|
|
65
66
|
"yauzl": "3.4.0",
|
|
66
67
|
"zod": "^4.6.7",
|
|
67
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.6"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
71
72
|
"@types/pg": "^8.23.1",
|
|
72
73
|
"@types/semver": "^7.8.0",
|
|
74
|
+
"@types/ws": "^8.18.2",
|
|
73
75
|
"@types/yauzl": "3.4.0",
|
|
74
|
-
"
|
|
76
|
+
"ws": "^8.22.0",
|
|
77
|
+
"@capybara-hq/testing": "0.1.0-rc.9"
|
|
75
78
|
},
|
|
76
79
|
"scripts": {
|
|
77
80
|
"build": "tsc -b"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- Environments phase 2 (spec 2026-11-07-environments-design §6, §13): a person's own machine,
|
|
2
|
+
-- connected through the device program, becomes an environment with source 'device'. Expand-only:
|
|
3
|
+
-- the source CHECK list grows and a new table holds the connected machine.
|
|
4
|
+
|
|
5
|
+
-- 0088 left the CHECK unnamed, so Postgres named it environments_source_check; re-add it under
|
|
6
|
+
-- the same name with the longer list.
|
|
7
|
+
ALTER TABLE environments DROP CONSTRAINT IF EXISTS environments_source_check;
|
|
8
|
+
ALTER TABLE environments
|
|
9
|
+
ADD CONSTRAINT environments_source_check CHECK (source IN ('cloud', 'device'));
|
|
10
|
+
|
|
11
|
+
-- The machine connected to a device environment. credential_hash is a hash of the machine's
|
|
12
|
+
-- long-lived credential (cap_dev_...), never the credential itself; revoking a machine sets
|
|
13
|
+
-- revoked_at and keeps the row, so only one unrevoked machine exists per environment.
|
|
14
|
+
-- program_version, mode, roots and session_isolation are what the program reported on its last
|
|
15
|
+
-- connection; last_seen_at is the last time it connected or pinged.
|
|
16
|
+
CREATE TABLE IF NOT EXISTS environment_machines (
|
|
17
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
18
|
+
org_id text NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
|
|
19
|
+
environment_id text NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
|
|
20
|
+
account_id text NOT NULL,
|
|
21
|
+
credential_hash text NOT NULL UNIQUE,
|
|
22
|
+
name text NOT NULL,
|
|
23
|
+
os text NOT NULL,
|
|
24
|
+
arch text NOT NULL,
|
|
25
|
+
program_version text,
|
|
26
|
+
mode text CHECK (mode IN ('os', 'docker')),
|
|
27
|
+
roots jsonb,
|
|
28
|
+
session_isolation boolean NOT NULL DEFAULT false,
|
|
29
|
+
last_seen_at timestamptz,
|
|
30
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
31
|
+
revoked_at timestamptz
|
|
32
|
+
);
|
|
33
|
+
CREATE UNIQUE INDEX IF NOT EXISTS environment_machines_active_uq
|
|
34
|
+
ON environment_machines (environment_id)
|
|
35
|
+
WHERE revoked_at IS NULL;
|
|
36
|
+
-- The org cascade delete looks rows up by org (as environment_instances_org_idx in 0088).
|
|
37
|
+
CREATE INDEX IF NOT EXISTS environment_machines_org_idx ON environment_machines (org_id);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Environments phase 2 (spec 2026-11-07-environments-design §13.1, ruling R22): every sandbox tool
|
|
2
|
+
-- call reads the conversation's latest environment_paths event; a partial index answers it
|
|
3
|
+
-- without scanning the conversation's events backwards. Not CONCURRENTLY, like the other index
|
|
4
|
+
-- migrations (0025).
|
|
5
|
+
CREATE INDEX IF NOT EXISTS conversation_events_environment_paths_idx
|
|
6
|
+
ON conversation_events (conversation_id, seq)
|
|
7
|
+
WHERE type = 'environment_paths';
|