@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
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { ExtensionPointUnavailableError, MEDIA_NAME_MAX_CHARS, ToolError, } from "@capybara-hq/plugin-sdk";
|
|
3
3
|
import { isValidArtifactName } from "../../../conversations/events.js";
|
|
4
4
|
import { ApiError } from "../../../errors.js";
|
|
5
|
-
import { checkPath, clip, display, inSandbox, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
5
|
+
import { checkPath, checkPathShape, clip, display, inSandbox, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
6
6
|
const SPEC = {
|
|
7
7
|
name: "Deliver",
|
|
8
8
|
// Display only (the tool row; not in the fingerprint, never sent to the model).
|
|
@@ -18,7 +18,7 @@ const SPEC = {
|
|
|
18
18
|
file_path: {
|
|
19
19
|
type: "string",
|
|
20
20
|
minLength: 1,
|
|
21
|
-
description:
|
|
21
|
+
description: "Absolute path of the file in the sandbox, e.g. out/report.pdf in the workspace directory.",
|
|
22
22
|
},
|
|
23
23
|
name: {
|
|
24
24
|
type: "string",
|
|
@@ -96,21 +96,23 @@ export function sandboxDeliverTool(deps) {
|
|
|
96
96
|
spec: SPEC,
|
|
97
97
|
defaultPolicy: "allow",
|
|
98
98
|
// 没有沙箱或没有对象存储都交付不了(先报沙箱的原因)
|
|
99
|
-
unavailable: (ctx) => unavailableWhenNoSandbox(deps)(ctx) ??
|
|
99
|
+
unavailable: async (ctx) => (await unavailableWhenNoSandbox(deps)(ctx)) ??
|
|
100
|
+
(deps.storage.available ? null : NO_STORAGE_REASON),
|
|
100
101
|
timeoutMs: () => 2 * sandboxBaseTimeoutMs(deps.config.get()),
|
|
101
102
|
async run(input, ctx) {
|
|
102
103
|
// 存储在调用前就核对:不为一个交付不了的文件去 stat / 读沙箱
|
|
103
104
|
if (!deps.storage.available)
|
|
104
105
|
throw new ToolError(NO_STORAGE);
|
|
105
|
-
const path = checkPath(input.file_path, "file_path");
|
|
106
106
|
const given = input.name?.trim();
|
|
107
107
|
// 与 artifact_created 的 schema 同一规则,在上传前检查(不合法的名字不会在写事件时才失败)
|
|
108
108
|
if (given !== undefined && (!isValidArtifactName(given) || /[/\\]/.test(given))) {
|
|
109
109
|
throw new ToolError(BAD_NAME);
|
|
110
110
|
}
|
|
111
|
-
const name = given ?? defaultName(path);
|
|
112
111
|
const max = deps.config.get().sandbox.deliverMaxBytes;
|
|
113
|
-
|
|
112
|
+
checkPathShape(input.file_path, "file_path");
|
|
113
|
+
let path = "";
|
|
114
|
+
const { value: data, notices } = await inSandbox(deps, ctx, async ({ h, roots, p, signal }) => {
|
|
115
|
+
path = checkPath(input.file_path, roots, "file_path");
|
|
114
116
|
const st = await p.stat(h, path, signal);
|
|
115
117
|
if (st === null)
|
|
116
118
|
throw new ToolError(notFound(path));
|
|
@@ -126,6 +128,7 @@ export function sandboxDeliverTool(deps) {
|
|
|
126
128
|
throw new ToolError(tooLarge(path, max + 1, max));
|
|
127
129
|
return got.data;
|
|
128
130
|
});
|
|
131
|
+
const name = given ?? defaultName(path);
|
|
129
132
|
// 上传在沙箱锁之外进行:不占着沙箱
|
|
130
133
|
const artifactId = randomUUID();
|
|
131
134
|
const objectKey = `artifacts/${ctx.conversationId}/${artifactId}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { lineDiff, viewPath } from "../../view.js";
|
|
3
|
-
import { checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
3
|
+
import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
4
4
|
import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
5
5
|
import { READ_MAX_BYTES } from "./read.js";
|
|
6
6
|
/** 可编辑的最大文件(字节):更大的用 Bash 处理。 */
|
|
@@ -19,7 +19,7 @@ const SPEC = {
|
|
|
19
19
|
file_path: {
|
|
20
20
|
type: "string",
|
|
21
21
|
minLength: 1,
|
|
22
|
-
description:
|
|
22
|
+
description: "Absolute path of the file, e.g. src/app.py in the workspace directory.",
|
|
23
23
|
},
|
|
24
24
|
old_string: { type: "string", minLength: 1, description: "The exact text to replace." },
|
|
25
25
|
new_string: { type: "string", description: "The replacement text." },
|
|
@@ -53,43 +53,46 @@ export function sandboxEditTool(deps) {
|
|
|
53
53
|
timeoutMs: () => sandboxBaseTimeoutMs(deps.config.get()),
|
|
54
54
|
async run(input, ctx) {
|
|
55
55
|
const { old_string, new_string, replace_all = false } = input;
|
|
56
|
-
const path = checkWritable(input.file_path);
|
|
57
56
|
if (old_string === new_string)
|
|
58
57
|
throw new ToolError(SAME_TEXT);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
58
|
+
checkPathShape(input.file_path, "file_path");
|
|
59
|
+
let path = "";
|
|
60
|
+
const { value: edit, notices } = await inSandbox(deps, ctx, ({ environmentId, h, roots, p, signal }) => {
|
|
61
|
+
path = checkWritable(input.file_path, roots);
|
|
62
|
+
// read, replace, write: one holder at a time per file
|
|
63
|
+
return withPathLease(deps, { environmentId }, path, signal, async (signal) => {
|
|
64
|
+
const st = await p.stat(h, path, signal);
|
|
65
|
+
if (st === null)
|
|
66
|
+
throw new ToolError(notFound(path));
|
|
67
|
+
if (st.isDir)
|
|
68
|
+
throw new ToolError(notText(path));
|
|
69
|
+
const got = await p.readFile(h, path, { maxBytes: EDIT_MAX_BYTES, signal });
|
|
70
|
+
if (got === null)
|
|
71
|
+
throw new ToolError(notFound(path));
|
|
72
|
+
if (got.truncated || looksBinary(got.data))
|
|
73
|
+
throw new ToolError(notText(path));
|
|
74
|
+
await assertReadAndUnchanged(deps, ctx.conversationId, path, fileStateOf(got.data, st.size));
|
|
75
|
+
const text = new TextDecoder().decode(got.data);
|
|
76
|
+
const count = occurrences(text, old_string);
|
|
77
|
+
if (count === 0)
|
|
78
|
+
throw new ToolError(noMatch(path));
|
|
79
|
+
if (count > 1 && !replace_all)
|
|
80
|
+
throw new ToolError(ambiguous(count));
|
|
81
|
+
// 用函数形式替换:new_string 里的 `$&` 等不被当成替换模式
|
|
82
|
+
const next = replace_all
|
|
83
|
+
? text.split(old_string).join(new_string)
|
|
84
|
+
: text.replace(old_string, () => new_string);
|
|
85
|
+
const nextBytes = new TextEncoder().encode(next);
|
|
86
|
+
await p.writeFile(h, path, nextBytes, signal);
|
|
87
|
+
return {
|
|
88
|
+
n: replace_all ? count : 1,
|
|
89
|
+
before: text,
|
|
90
|
+
after: next,
|
|
91
|
+
// over the bytes the next Write / Edit reads back (Read's limit), as Read records it
|
|
92
|
+
file: recordedFile(normalizeSandboxPath(path), nextBytes.subarray(0, READ_MAX_BYTES), nextBytes.byteLength),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
});
|
|
93
96
|
const { n } = edit;
|
|
94
97
|
const text = `Edited ${path}: replaced ${n} occurrence${n === 1 ? "" : "s"}.`;
|
|
95
98
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
|
-
import { checkPath, clip, display, inSandbox,
|
|
2
|
+
import { checkPath, checkPathShape, clip, display, inSandbox, sandboxBaseTimeoutMs, searchTimeoutMs, syncMarkerMatcher, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
3
3
|
import { compileGlob, GlobPatternError } from "./glob-match.js";
|
|
4
4
|
/** 最多列出的文件数。 */
|
|
5
5
|
const MAX_RESULTS = 1000;
|
|
@@ -11,7 +11,7 @@ const SPEC = {
|
|
|
11
11
|
keyArgs: ["pattern"],
|
|
12
12
|
description: "Find files by name pattern in this conversation's sandbox. Use it to list files and to " +
|
|
13
13
|
'locate files by name, e.g. pattern "**/*.py" for all Python files. The pattern is matched ' +
|
|
14
|
-
"against paths relative to path (default
|
|
14
|
+
"against paths relative to path (default: the workspace directory): * matches within one directory, ** " +
|
|
15
15
|
"matches across directories, ? matches one character, {a,b} matches either. Returns absolute " +
|
|
16
16
|
"paths sorted by name, up to 1000. To search file contents, use Grep.",
|
|
17
17
|
inputSchema: {
|
|
@@ -25,7 +25,7 @@ const SPEC = {
|
|
|
25
25
|
path: {
|
|
26
26
|
type: "string",
|
|
27
27
|
minLength: 1,
|
|
28
|
-
description: "Absolute directory to search in. Default
|
|
28
|
+
description: "Absolute directory to search in. Default: the workspace directory.",
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
31
|
required: ["pattern"],
|
|
@@ -34,7 +34,7 @@ const SPEC = {
|
|
|
34
34
|
annotations: { readOnlyHint: true },
|
|
35
35
|
};
|
|
36
36
|
const ABSOLUTE_PATTERN = 'The pattern must be relative to path; pass the directory in path and a relative pattern such as "**/*.py".';
|
|
37
|
-
const notDirectory = (path) => `${clip(path, 200)} is not a directory; pass an existing directory in path (default
|
|
37
|
+
const notDirectory = (path, workspace) => `${clip(path, 200)} is not a directory; pass an existing directory in path (default ${workspace}).`;
|
|
38
38
|
const FIND_FAILED = "Listing files did not finish in time; pass a narrower path or pattern and retry.";
|
|
39
39
|
/** 内置工具 `Glob`:按文件名模式找文件(只读)。 */
|
|
40
40
|
export function sandboxGlobTool(deps) {
|
|
@@ -48,7 +48,6 @@ export function sandboxGlobTool(deps) {
|
|
|
48
48
|
const { pattern } = input;
|
|
49
49
|
if (pattern.startsWith("/"))
|
|
50
50
|
throw new ToolError(ABSOLUTE_PATTERN);
|
|
51
|
-
const dir = checkPath(input.path ?? "/workspace");
|
|
52
51
|
let matches;
|
|
53
52
|
try {
|
|
54
53
|
matches = compileGlob(pattern);
|
|
@@ -58,10 +57,15 @@ export function sandboxGlobTool(deps) {
|
|
|
58
57
|
throw e;
|
|
59
58
|
throw new ToolError(`Invalid glob pattern: ${e.message}; pass a simpler pattern (fewer wildcards and braces) and retry.`);
|
|
60
59
|
}
|
|
61
|
-
const
|
|
60
|
+
const given = input.path;
|
|
61
|
+
if (given !== undefined)
|
|
62
|
+
checkPathShape(given);
|
|
63
|
+
let dir = "";
|
|
64
|
+
const { value: files, notices } = await inSandbox(deps, ctx, async ({ h, roots, p, signal }) => {
|
|
65
|
+
dir = checkPath(input.path ?? roots.workspace, roots);
|
|
62
66
|
const st = await p.stat(h, dir, signal);
|
|
63
67
|
if (!st?.isDir)
|
|
64
|
-
throw new ToolError(notDirectory(dir));
|
|
68
|
+
throw new ToolError(notDirectory(dir, roots.workspace));
|
|
65
69
|
const r = await p.exec(h, {
|
|
66
70
|
command: "find . -type f -print0",
|
|
67
71
|
cwd: dir,
|
|
@@ -77,12 +81,13 @@ export function sandboxGlobTool(deps) {
|
|
|
77
81
|
if (r.truncated)
|
|
78
82
|
names.pop();
|
|
79
83
|
const base = dir === "/" ? "" : dir;
|
|
84
|
+
const isMarker = syncMarkerMatcher(roots);
|
|
80
85
|
return (names
|
|
81
86
|
.filter((l) => l.startsWith("./"))
|
|
82
87
|
.map((l) => l.slice(2))
|
|
83
88
|
.filter((rel) => matches(rel))
|
|
84
89
|
.map((rel) => `${base}/${rel}`)
|
|
85
|
-
.filter((abs) => !
|
|
90
|
+
.filter((abs) => !isMarker(abs))
|
|
86
91
|
.sort()
|
|
87
92
|
// 文件名里的换行显示为 `\n`(与 Grep 一致),一行一个文件
|
|
88
93
|
.map((abs) => abs.replace(/\n/g, "\\n")));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
|
-
import { checkPath, clip, display, inSandbox,
|
|
2
|
+
import { checkPath, checkPathShape, clip, display, inSandbox, sandboxBaseTimeoutMs, searchTimeoutMs, shellQuote, syncMarkerMatcher, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
3
3
|
/** The most output entries (lines, files or counts) returned in one call, whatever `head_limit` says. */
|
|
4
4
|
const MAX_MATCHES = 500;
|
|
5
5
|
/** `head_limit` when the model gives none. */
|
|
@@ -15,7 +15,7 @@ const SPEC = {
|
|
|
15
15
|
keyArgs: ["pattern", "path"],
|
|
16
16
|
description: "Search file contents in this conversation's sandbox with a regular expression (POSIX " +
|
|
17
17
|
"extended, as grep -E). Use it to find where something is defined or mentioned. Searches path " +
|
|
18
|
-
"(a file, or a directory recursively; default
|
|
18
|
+
"(a file, or a directory recursively; default: the workspace directory), skipping binary files; glob limits " +
|
|
19
19
|
'the files searched by name, e.g. "*.py". output_mode picks what comes back: ' +
|
|
20
20
|
'"files_with_matches" (default) lists the paths of files that match, "content" returns the ' +
|
|
21
21
|
"matching lines as <path>:<line>:<text> (with -A, -B or -C lines of context around them), " +
|
|
@@ -34,7 +34,7 @@ const SPEC = {
|
|
|
34
34
|
path: {
|
|
35
35
|
type: "string",
|
|
36
36
|
minLength: 1,
|
|
37
|
-
description: "Absolute file or directory to search. Default
|
|
37
|
+
description: "Absolute file or directory to search. Default: the workspace directory.",
|
|
38
38
|
},
|
|
39
39
|
glob: {
|
|
40
40
|
type: "string",
|
|
@@ -160,8 +160,11 @@ export function sandboxGrepTool(deps) {
|
|
|
160
160
|
const { pattern, glob } = g;
|
|
161
161
|
const mode = g.output_mode ?? "files_with_matches";
|
|
162
162
|
const numbered = g["-n"] ?? true;
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
if (g.path !== undefined)
|
|
164
|
+
checkPathShape(g.path);
|
|
165
|
+
let path = "";
|
|
166
|
+
const { value, notices } = await inSandbox(deps, ctx, async ({ h, roots, p, signal }) => {
|
|
167
|
+
path = checkPath(g.path ?? roots.workspace, roots);
|
|
165
168
|
const st = await p.stat(h, path, signal);
|
|
166
169
|
if (st === null)
|
|
167
170
|
throw new ToolError(notFound(path));
|
|
@@ -207,7 +210,8 @@ export function sandboxGrepTool(deps) {
|
|
|
207
210
|
: mode === "count"
|
|
208
211
|
? parseCounts(r.stdout, base)
|
|
209
212
|
: parseGrepNull(r.stdout, base, numbered);
|
|
210
|
-
const
|
|
213
|
+
const isMarker = syncMarkerMatcher(roots);
|
|
214
|
+
const entries = all.filter((l) => !isMarker(l));
|
|
211
215
|
// 退出码 2 = 有错误(如个别文件不可读);有输出时照常返回(可能为空:只匹配到隐藏的标记文件)并附上说明
|
|
212
216
|
if (r.exitCode !== 0 && all.length === 0)
|
|
213
217
|
throw new ToolError(failed(r.stderr));
|
|
@@ -2,12 +2,14 @@ import type { BuiltinTool } from "../../registry.js";
|
|
|
2
2
|
import type { SandboxToolDeps } from "./common.js";
|
|
3
3
|
export { SANDBOX_ASK_TOOL_NAMES, SANDBOX_TOOL_NAMES } from "../../builtin-names.js";
|
|
4
4
|
export { BASH_SPILL_CHARS, formatExec } from "./bash.js";
|
|
5
|
-
export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_RESTARTED_NOTICE, SANDBOX_SETUP_MARGIN_MS, SANDBOX_STARTED_NOTICE, SANDBOX_UNAVAILABLE_REASON, type SandboxToolDeps, sandboxBaseTimeoutMs, searchTimeoutMs, shellQuote, } from "./common.js";
|
|
5
|
+
export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_RESTARTED_NOTICE, SANDBOX_SETUP_MARGIN_MS, SANDBOX_STARTED_NOTICE, SANDBOX_UNAVAILABLE_REASON, type SandboxToolDeps, sandboxBaseTimeoutMs, sandboxStartedNotice, sandboxUsable, searchTimeoutMs, shellQuote, } from "./common.js";
|
|
6
6
|
export { compileGlob, GlobPatternError, MAX_GLOB_STARS } from "./glob-match.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The sandbox builtins (M5 T8): Bash / Read / Write / Edit / Glob / Grep / Deliver, all run in the
|
|
9
|
+
* conversation's environment (`SandboxService.withSandbox`) after syncing attachments to
|
|
10
|
+
* `<workspace>/attachments` (the instance's roots, environments spec §13.1); unavailable without
|
|
11
|
+
* a provider for it. Allowed by default; with the org setting `sandboxToolPolicy` set to ask,
|
|
12
|
+
* every one but Read / Glob / Grep needs approval (see policy.ts).
|
|
11
13
|
* `TaskOutput` and `TaskStop` read and stop background `Bash` tasks through
|
|
12
14
|
* `SandboxService.ifRunning` (never creating or resuming a sandbox); always allowed.
|
|
13
15
|
*/
|
|
@@ -9,12 +9,14 @@ import { taskStopTool } from "./task-stop.js";
|
|
|
9
9
|
import { sandboxWriteTool } from "./write.js";
|
|
10
10
|
export { SANDBOX_ASK_TOOL_NAMES, SANDBOX_TOOL_NAMES } from "../../builtin-names.js";
|
|
11
11
|
export { BASH_SPILL_CHARS, formatExec } from "./bash.js";
|
|
12
|
-
export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_RESTARTED_NOTICE, SANDBOX_SETUP_MARGIN_MS, SANDBOX_STARTED_NOTICE, SANDBOX_UNAVAILABLE_REASON, sandboxBaseTimeoutMs, searchTimeoutMs, shellQuote, } from "./common.js";
|
|
12
|
+
export { assignAttachmentNames, RUN_DEADLINE_MARGIN_MS, runTimeLeftMs, SANDBOX_RESTARTED_NOTICE, SANDBOX_SETUP_MARGIN_MS, SANDBOX_STARTED_NOTICE, SANDBOX_UNAVAILABLE_REASON, sandboxBaseTimeoutMs, sandboxStartedNotice, sandboxUsable, searchTimeoutMs, shellQuote, } from "./common.js";
|
|
13
13
|
export { compileGlob, GlobPatternError, MAX_GLOB_STARS } from "./glob-match.js";
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* The sandbox builtins (M5 T8): Bash / Read / Write / Edit / Glob / Grep / Deliver, all run in the
|
|
16
|
+
* conversation's environment (`SandboxService.withSandbox`) after syncing attachments to
|
|
17
|
+
* `<workspace>/attachments` (the instance's roots, environments spec §13.1); unavailable without
|
|
18
|
+
* a provider for it. Allowed by default; with the org setting `sandboxToolPolicy` set to ask,
|
|
19
|
+
* every one but Read / Glob / Grep needs approval (see policy.ts).
|
|
18
20
|
* `TaskOutput` and `TaskStop` read and stop background `Bash` tasks through
|
|
19
21
|
* `SandboxService.ifRunning` (never creating or resuming a sandbox); always allowed.
|
|
20
22
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { viewPath } from "../../view.js";
|
|
3
|
-
import { checkPath, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
3
|
+
import { checkPath, checkPathShape, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
|
|
4
4
|
import { normalizeSandboxPath, recordedFile } from "./file-state.js";
|
|
5
5
|
/** 不给 `limit` 时一次读的行数。 */
|
|
6
6
|
const DEFAULT_LIMIT = 2000;
|
|
@@ -13,8 +13,9 @@ const SPEC = {
|
|
|
13
13
|
// Display only (the tool row; not in the fingerprint, never sent to the model).
|
|
14
14
|
keyArgs: ["file_path"],
|
|
15
15
|
description: "Read a text file from this conversation's sandbox, with line numbers. Use it to look at " +
|
|
16
|
-
"source code, data files, logs and files the user attached (under
|
|
17
|
-
"Paths are absolute, under
|
|
16
|
+
"source code, data files, logs and files the user attached (under attachments/<conversation>/ in " +
|
|
17
|
+
"the workspace directory). Paths are absolute, under the workspace directory, the skills " +
|
|
18
|
+
"directory or the temporary files directory. Lines are numbered from 1; pass " +
|
|
18
19
|
"offset and limit to read part of a long file. Binary files are not shown; use Bash " +
|
|
19
20
|
"to inspect them. Do not use it to list directories; use Glob instead. Read a file before you " +
|
|
20
21
|
"write it: Write and Edit fail on a file you have not read, or one changed since you read it.",
|
|
@@ -24,7 +25,7 @@ const SPEC = {
|
|
|
24
25
|
file_path: {
|
|
25
26
|
type: "string",
|
|
26
27
|
minLength: 1,
|
|
27
|
-
description:
|
|
28
|
+
description: "Absolute path of the file, e.g. src/main.py in the workspace directory.",
|
|
28
29
|
},
|
|
29
30
|
offset: {
|
|
30
31
|
type: "integer",
|
|
@@ -81,8 +82,10 @@ export function sandboxReadTool(deps) {
|
|
|
81
82
|
timeoutMs: () => sandboxBaseTimeoutMs(deps.config.get()),
|
|
82
83
|
async run(input, ctx) {
|
|
83
84
|
const { offset = 1, limit = DEFAULT_LIMIT } = input;
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
checkPathShape(input.file_path, "file_path");
|
|
86
|
+
let path = "";
|
|
87
|
+
const { value, notices } = await inSandbox(deps, ctx, async ({ h, roots, p, signal }) => {
|
|
88
|
+
path = checkPath(input.file_path, roots, "file_path");
|
|
86
89
|
const st = await p.stat(h, path, signal);
|
|
87
90
|
if (st === null)
|
|
88
91
|
throw new ToolError(notFound(path));
|
|
@@ -16,8 +16,12 @@ export interface TaskEnv {
|
|
|
16
16
|
ifRunning: SandboxService["ifRunning"];
|
|
17
17
|
environments: SandboxToolDeps["environments"];
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The task tools' dependencies; throws the tool's error when they are missing, or when the
|
|
21
|
+
* conversation's environment cannot run (no cloud provider, and not a registered `@device` one:
|
|
22
|
+
* ruling R8).
|
|
23
|
+
*/
|
|
24
|
+
export declare function taskEnv(deps: SandboxToolDeps, ctx: BuiltinCallContext): Promise<TaskEnv>;
|
|
21
25
|
/**
|
|
22
26
|
* The task `id` when it belongs to the calling conversation; a task of another conversation (a
|
|
23
27
|
* sub-agent's, when called from its parent) is not found, the same as an unknown id.
|
|
@@ -45,7 +49,7 @@ export declare const outputFile: (task: BackgroundTaskRow) => string;
|
|
|
45
49
|
/** Why the output cannot be read: the task's sandbox instance is not running. */
|
|
46
50
|
export declare function unavailableText(task: BackgroundTaskRow, r: IfRunningResult<unknown>): string;
|
|
47
51
|
/**
|
|
48
|
-
* The exec that checks and reads task `t` (relative to
|
|
52
|
+
* The exec that checks and reads task `t` (relative to the workspace root): for a running task first
|
|
49
53
|
* its status line (the poller's, {@link pollScript}: liveness, then the exit file), then
|
|
50
54
|
* `size <bytes>` and `out <base64>`, at most `cap` bytes of the output from byte `offset` on.
|
|
51
55
|
* The file is never read whole: `tail -c +<offset+1> | head -c <cap>`. Without `read` only the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
-
import { isSandboxGoneError, ToolError, } from "@capybara-hq/plugin-sdk";
|
|
3
|
-
import { TASK_ID_RE, TASKS_DIR } from "../../../background/launch.js";
|
|
2
|
+
import { isSandboxGoneError, rootsOf, ToolError, } from "@capybara-hq/plugin-sdk";
|
|
3
|
+
import { TASK_ID_RE, TASKS_DIR, taskOutputFile } from "../../../background/launch.js";
|
|
4
4
|
import { formatElapsed, lostReasonText } from "../../../background/render.js";
|
|
5
5
|
import { actingAs, SID_RE, taskActor } from "../../../background/session.js";
|
|
6
6
|
import { parsePollOutput, polledAs, pollScript, } from "../../../background/status.js";
|
|
7
7
|
import { notRunningAs, RECREATED } from "../../../background/stop.js";
|
|
8
|
-
import { clip, display, mapSandboxError, noSandboxError, runTimeLeftMs, sandboxBaseTimeoutMs, sandboxConversationOf, unavailableWhenNoSandbox, } from "./common.js";
|
|
8
|
+
import { clip, display, mapSandboxError, noSandboxError, runTimeLeftMs, sandboxBaseTimeoutMs, sandboxConversationOf, sandboxUsable, unavailableWhenNoSandbox, } from "./common.js";
|
|
9
9
|
/** How long `TaskOutput` waits with `block` when `timeout` is not given (ms). */
|
|
10
10
|
export const TASK_OUTPUT_DEFAULT_TIMEOUT_MS = 30_000;
|
|
11
11
|
/** The longest `TaskOutput` wait (ms). */
|
|
@@ -49,9 +49,13 @@ const SPEC = {
|
|
|
49
49
|
additionalProperties: false,
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The task tools' dependencies; throws the tool's error when they are missing, or when the
|
|
54
|
+
* conversation's environment cannot run (no cloud provider, and not a registered `@device` one:
|
|
55
|
+
* ruling R8).
|
|
56
|
+
*/
|
|
57
|
+
export async function taskEnv(deps, ctx) {
|
|
58
|
+
if (!(await sandboxUsable(deps, ctx.orgId, sandboxConversationOf(ctx))))
|
|
55
59
|
throw noSandboxError();
|
|
56
60
|
const { background, tasks } = deps;
|
|
57
61
|
if (!background || !tasks || !deps.sandbox.ifRunning) {
|
|
@@ -122,7 +126,7 @@ export function statusLine(task, now = new Date()) {
|
|
|
122
126
|
return `${s} after ${elapsed}`;
|
|
123
127
|
}
|
|
124
128
|
/** The absolute path of the task's output file, as the model sees it. */
|
|
125
|
-
export const outputFile = (task) =>
|
|
129
|
+
export const outputFile = (task) => taskOutputFile(task.outputPath);
|
|
126
130
|
/** Why the output cannot be read: the task's sandbox instance is not running. */
|
|
127
131
|
export function unavailableText(task, r) {
|
|
128
132
|
if (r.kind === "not_running" && r.status === "suspended") {
|
|
@@ -132,7 +136,7 @@ export function unavailableText(task, r) {
|
|
|
132
136
|
}
|
|
133
137
|
// ---- reading a task ----
|
|
134
138
|
/**
|
|
135
|
-
* The exec that checks and reads task `t` (relative to
|
|
139
|
+
* The exec that checks and reads task `t` (relative to the workspace root): for a running task first
|
|
136
140
|
* its status line (the poller's, {@link pollScript}: liveness, then the exit file), then
|
|
137
141
|
* `size <bytes>` and `out <base64>`, at most `cap` bytes of the output from byte `offset` on.
|
|
138
142
|
* The file is never read whole: `tail -c +<offset+1> | head -c <cap>`. Without `read` only the
|
|
@@ -189,7 +193,7 @@ async function look(env, task, read, signal) {
|
|
|
189
193
|
return onTaskInstance(env, task, async (p, h, sig) => {
|
|
190
194
|
const r = await p.exec(h, {
|
|
191
195
|
command: script,
|
|
192
|
-
cwd:
|
|
196
|
+
cwd: rootsOf(h).workspace,
|
|
193
197
|
timeoutMs: READ_EXEC_TIMEOUT_MS,
|
|
194
198
|
maxOutputBytes: maxBytes,
|
|
195
199
|
signal: sig,
|
|
@@ -262,7 +266,7 @@ export function taskOutputTool(deps) {
|
|
|
262
266
|
},
|
|
263
267
|
async run(input, ctx) {
|
|
264
268
|
const { task_id, block = true, timeout } = input;
|
|
265
|
-
const env = taskEnv(deps);
|
|
269
|
+
const env = await taskEnv(deps, ctx);
|
|
266
270
|
let task = await ownedTask(env, ctx, task_id);
|
|
267
271
|
await recheckEnvironment(env, ctx);
|
|
268
272
|
if (block && task.status === "running") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
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
4
|
import { onTaskInstance, ownedTask, recheckEnvironment, statusLine, taskEnv, } from "./task-output.js";
|
|
@@ -33,7 +33,7 @@ async function lastLinesOf(env, task, signal) {
|
|
|
33
33
|
const r = await onTaskInstance(env, task, async (p, h, sig) => {
|
|
34
34
|
const res = await p.exec(h, {
|
|
35
35
|
command: tailCommand(task.id),
|
|
36
|
-
cwd:
|
|
36
|
+
cwd: rootsOf(h).workspace,
|
|
37
37
|
timeoutMs: TAIL_EXEC_TIMEOUT_MS,
|
|
38
38
|
maxOutputBytes: 64 * 1024,
|
|
39
39
|
signal: sig,
|
|
@@ -82,7 +82,7 @@ export function taskStopTool(deps) {
|
|
|
82
82
|
timeoutMs: () => sandboxBaseTimeoutMs(deps.config.get()),
|
|
83
83
|
async run(input, ctx) {
|
|
84
84
|
const { task_id } = input;
|
|
85
|
-
const env = taskEnv(deps);
|
|
85
|
+
const env = await taskEnv(deps, ctx);
|
|
86
86
|
const task = await ownedTask(env, ctx, task_id);
|
|
87
87
|
await recheckEnvironment(env, ctx);
|
|
88
88
|
if (task.status !== "running") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { lineDiff, TOOL_VIEW_DIFF_MAX_BYTES, viewPath } from "../../view.js";
|
|
3
|
-
import { checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
3
|
+
import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
4
4
|
import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
5
5
|
import { READ_MAX_BYTES } from "./read.js";
|
|
6
6
|
const SPEC = {
|
|
@@ -9,8 +9,8 @@ const SPEC = {
|
|
|
9
9
|
keyArgs: ["file_path"],
|
|
10
10
|
description: "Write a text file in this conversation's sandbox, creating parent directories as needed. " +
|
|
11
11
|
"Each call replaces the whole file (it is created if missing). Use it to create scripts, " +
|
|
12
|
-
"source files and data files. The path is absolute, under
|
|
13
|
-
"read-only). Read an existing file first; overwriting a file you have not read, or one changed " +
|
|
12
|
+
"source files and data files. The path is absolute, under the workspace directory or the " +
|
|
13
|
+
"temporary files directory (the skills directory is read-only). Read an existing file first; overwriting a file you have not read, or one changed " +
|
|
14
14
|
"since you read it, fails. To change part of an existing file, use Edit instead; to give a file to " +
|
|
15
15
|
"the user, use Deliver.",
|
|
16
16
|
inputSchema: {
|
|
@@ -19,7 +19,7 @@ const SPEC = {
|
|
|
19
19
|
file_path: {
|
|
20
20
|
type: "string",
|
|
21
21
|
minLength: 1,
|
|
22
|
-
description:
|
|
22
|
+
description: "Absolute path of the file, e.g. report.md in the workspace directory.",
|
|
23
23
|
},
|
|
24
24
|
content: { type: "string", description: "The complete new content of the file." },
|
|
25
25
|
},
|
|
@@ -43,33 +43,36 @@ export function sandboxWriteTool(deps) {
|
|
|
43
43
|
unavailable: unavailableWhenNoSandbox(deps),
|
|
44
44
|
timeoutMs: () => sandboxBaseTimeoutMs(deps.config.get()),
|
|
45
45
|
async run(input, ctx) {
|
|
46
|
-
const path = checkWritable(input.file_path);
|
|
47
46
|
const data = new TextEncoder().encode(input.content);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
47
|
+
checkPathShape(input.file_path, "file_path");
|
|
48
|
+
let path = "";
|
|
49
|
+
const { value, notices } = await inSandbox(deps, ctx, ({ environmentId, h, roots, p, signal }) => {
|
|
50
|
+
path = checkWritable(input.file_path, roots);
|
|
51
|
+
// stat, read the previous content, write: one holder at a time per file
|
|
52
|
+
return withPathLease(deps, { environmentId }, path, signal, async (signal) => {
|
|
53
|
+
const st = await p.stat(h, path, signal);
|
|
54
|
+
if (st?.isDir)
|
|
55
|
+
throw new ToolError(isDirectory(path));
|
|
56
|
+
// the previous content: for the read-before-write check, and the diff on the row (display
|
|
57
|
+
// only: a file too large or binary to diff just leaves the view without hunks)
|
|
58
|
+
let previous = "";
|
|
59
|
+
if (st !== null) {
|
|
60
|
+
const got = await p.readFile(h, path, { maxBytes: READ_MAX_BYTES, signal });
|
|
61
|
+
if (got === null)
|
|
62
|
+
throw new ToolError(vanished(path));
|
|
63
|
+
await assertReadAndUnchanged(deps, ctx.conversationId, path, fileStateOf(got.data, st.size));
|
|
64
|
+
previous =
|
|
65
|
+
st.size > PREVIOUS_MAX_BYTES ||
|
|
66
|
+
got.truncated ||
|
|
67
|
+
looksBinary(got.data) ||
|
|
68
|
+
got.data.includes(0)
|
|
69
|
+
? null
|
|
70
|
+
: new TextDecoder().decode(got.data);
|
|
71
|
+
}
|
|
72
|
+
await p.writeFile(h, path, data, signal);
|
|
73
|
+
return { existed: st !== null, previous };
|
|
74
|
+
});
|
|
75
|
+
});
|
|
73
76
|
const { existed, previous } = value;
|
|
74
77
|
const text = `${existed ? "Overwrote" : "Created"} ${path} (${data.byteLength} bytes).`;
|
|
75
78
|
return {
|
|
@@ -8,14 +8,14 @@ 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
|
-
* environment at
|
|
11
|
+
* environment 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.
|
|
15
15
|
*
|
|
16
16
|
* - Every file read from storage is checked against the manifest's sha256; a mismatch is refused.
|
|
17
17
|
* - The copy is recorded in a kernel-controlled marker, one per skill version
|
|
18
|
-
* ({@link skillMarkerPath}:
|
|
18
|
+
* ({@link skillMarkerPath}: `<skills root>/.capybara/<name>@<digest>.json`, commands cannot change it):
|
|
19
19
|
* `{ digest, files }`. A present marker with the same digest skips the copy.
|
|
20
20
|
* - An agent without any sandbox tool creates no sandbox and copies nothing (the model could not
|
|
21
21
|
* run the scripts), and is told so.
|
|
@@ -23,11 +23,14 @@ import { type SandboxToolDeps } from "./sandbox/common.js";
|
|
|
23
23
|
* to run scripts with an interpreter (`sh` / `python3` / `node`).
|
|
24
24
|
*/
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* 已复制进沙箱的技能的标记目录(内核控制:技能根目录只有提供方以 root 能写;技能名不以 `.` 开头,不会撞名)。
|
|
27
|
+
* `skillsRoot`: the instance's skills root.
|
|
27
28
|
*/
|
|
28
|
-
export declare const
|
|
29
|
+
export declare const skillsMarkerDir: (skillsRoot?: string) => string;
|
|
30
|
+
/** {@link skillsMarkerDir} of an instance with the default roots. */
|
|
31
|
+
export declare const SKILLS_MARKER_DIR: string;
|
|
29
32
|
/** The marker file of one skill version. */
|
|
30
|
-
export declare const skillMarkerPath: (name: string, digest: string) => string;
|
|
33
|
+
export declare const skillMarkerPath: (name: string, digest: string, skillsRoot?: string) => string;
|
|
31
34
|
/** 依赖:沙箱工具的依赖,加上读技能行用的库与读快照用的日志。 */
|
|
32
35
|
export interface SkillLoadDeps extends SandboxToolDeps {
|
|
33
36
|
db: KernelSchemaDb;
|