@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
|
@@ -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;
|
|
@@ -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;
|