@dynamicagents/plugins 0.13.0 → 0.15.0
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/README.md +14 -14
- package/dist/claude-code/cards.d.ts +4 -0
- package/dist/claude-code/cards.js +22 -0
- package/dist/claude-code/config.d.ts +10 -83
- package/dist/claude-code/config.js +0 -2
- package/dist/claude-code/credentials.d.ts +32 -50
- package/dist/claude-code/credentials.js +115 -74
- package/dist/claude-code/daemon/index.d.ts +24 -0
- package/dist/claude-code/daemon/index.js +203 -0
- package/dist/claude-code/egress.d.ts +35 -117
- package/dist/claude-code/egress.js +80 -325
- package/dist/claude-code/events.d.ts +14 -17
- package/dist/claude-code/events.js +19 -15
- package/dist/claude-code/harness-wire.d.ts +45 -0
- package/dist/claude-code/harness-wire.js +47 -0
- package/dist/claude-code/index.d.ts +19 -29
- package/dist/claude-code/index.js +17 -25
- package/dist/claude-code/language-model.d.ts +19 -0
- package/dist/claude-code/language-model.js +71 -0
- package/dist/claude-code/model.d.ts +77 -70
- package/dist/claude-code/model.js +328 -304
- package/dist/claude-code/placeholder.d.ts +12 -0
- package/dist/claude-code/placeholder.js +12 -0
- package/dist/claude-code/spec.d.ts +4 -4
- package/dist/claude-code/spec.js +4 -4
- package/dist/claude-code/verified.d.ts +1 -1
- package/dist/claude-code/verified.js +1 -1
- package/dist/computer/client.d.ts +88 -0
- package/dist/computer/client.js +6 -0
- package/dist/computer/context.d.ts +7 -7
- package/dist/computer/context.js +14 -18
- package/dist/computer/gate.d.ts +4 -23
- package/dist/computer/gate.js +1 -37
- package/dist/computer/index.d.ts +44 -43
- package/dist/computer/index.js +29 -50
- package/dist/computer/paths.d.ts +5 -27
- package/dist/computer/paths.js +7 -30
- package/dist/computer/proxy.d.ts +17 -31
- package/dist/computer/proxy.js +9 -44
- package/dist/computer/render.d.ts +3 -40
- package/dist/computer/render.js +3 -36
- package/dist/computer/sandbox.d.ts +55 -0
- package/dist/computer/sandbox.js +236 -0
- package/dist/computer/shell.d.ts +36 -0
- package/dist/computer/shell.js +41 -0
- package/dist/computer/tools-exec.js +3 -9
- package/dist/computer/tools-file.d.ts +1 -1
- package/dist/computer/tools-file.js +1 -1
- package/dist/{repo → repo-github}/checkout.d.ts +3 -4
- package/dist/{repo → repo-github}/checkout.js +3 -4
- package/dist/{repo → repo-github}/context.d.ts +14 -32
- package/dist/{repo → repo-github}/context.js +46 -81
- package/dist/{repo → repo-github}/index.d.ts +30 -13
- package/dist/{repo → repo-github}/index.js +25 -6
- package/dist/repo-github/mcp.d.ts +95 -0
- package/dist/repo-github/mcp.js +107 -0
- package/dist/repo-github/sandbox-egress.d.ts +68 -0
- package/dist/repo-github/sandbox-egress.js +272 -0
- package/dist/repo-github/sandbox-git.d.ts +30 -0
- package/dist/repo-github/sandbox-git.js +113 -0
- package/dist/{repo → repo-github}/tools-checks.js +43 -4
- package/dist/{repo → repo-github}/tools-clone.js +2 -21
- package/dist/{repo → repo-github}/tools-pr.d.ts +0 -1
- package/dist/{repo → repo-github}/tools-pr.js +85 -20
- package/dist/{repo → repo-github}/tools-review.d.ts +6 -0
- package/dist/{repo → repo-github}/tools-review.js +62 -176
- package/dist/{repo → repo-github}/url.d.ts +11 -0
- package/dist/{repo → repo-github}/url.js +12 -1
- package/dist/{workspace → sandbox}/advisory.d.ts +16 -35
- package/dist/{workspace → sandbox}/advisory.js +2 -29
- package/dist/sandbox/agent-exec.d.ts +31 -0
- package/dist/sandbox/agent-exec.js +62 -0
- package/dist/sandbox/ca-trust.d.ts +9 -0
- package/dist/sandbox/ca-trust.js +21 -0
- package/dist/sandbox/constants.d.ts +22 -0
- package/dist/sandbox/constants.js +22 -0
- package/dist/sandbox/daemon/composite.d.ts +9 -0
- package/dist/sandbox/daemon/composite.js +35 -0
- package/dist/sandbox/daemon/index.d.ts +47 -0
- package/dist/sandbox/daemon/index.js +116 -0
- package/dist/sandbox/egress.d.ts +81 -0
- package/dist/sandbox/egress.js +109 -0
- package/dist/sandbox/exec.d.ts +57 -0
- package/dist/sandbox/exec.js +52 -0
- package/dist/sandbox/format.d.ts +2 -0
- package/dist/sandbox/format.js +7 -0
- package/dist/sandbox/header.d.ts +50 -0
- package/dist/sandbox/header.js +94 -0
- package/dist/sandbox/index.d.ts +17 -0
- package/dist/sandbox/index.js +17 -0
- package/dist/{workspace/install.d.ts → sandbox/install-plan.d.ts} +10 -1
- package/dist/{workspace/install.js → sandbox/install-plan.js} +22 -4
- package/dist/sandbox/name.d.ts +27 -0
- package/dist/sandbox/name.js +32 -0
- package/dist/sandbox/object.d.ts +244 -0
- package/dist/sandbox/object.js +1139 -0
- package/dist/scratch/index.d.ts +1 -9
- package/dist/scratch/index.js +2 -18
- package/package.json +26 -22
- package/dist/claude-code/github.d.ts +0 -39
- package/dist/claude-code/github.js +0 -82
- package/dist/claude-code/run.d.ts +0 -474
- package/dist/claude-code/run.js +0 -901
- package/dist/claude-code/session.d.ts +0 -131
- package/dist/claude-code/session.js +0 -285
- package/dist/workspace/ca-trust.d.ts +0 -95
- package/dist/workspace/ca-trust.js +0 -312
- package/dist/workspace/container-deps.d.ts +0 -54
- package/dist/workspace/container-deps.js +0 -224
- package/dist/workspace/container-fault.d.ts +0 -38
- package/dist/workspace/container-fault.js +0 -73
- package/dist/workspace/exec.d.ts +0 -94
- package/dist/workspace/exec.js +0 -153
- package/dist/workspace/format.d.ts +0 -36
- package/dist/workspace/format.js +0 -57
- package/dist/workspace/git-host.d.ts +0 -79
- package/dist/workspace/git-host.js +0 -226
- package/dist/workspace/git-identity.d.ts +0 -57
- package/dist/workspace/git-identity.js +0 -147
- package/dist/workspace/index.d.ts +0 -49
- package/dist/workspace/index.js +0 -58
- package/dist/workspace/install-job.d.ts +0 -121
- package/dist/workspace/install-job.js +0 -866
- package/dist/workspace/object.d.ts +0 -502
- package/dist/workspace/object.js +0 -1427
- package/dist/workspace/open.d.ts +0 -95
- package/dist/workspace/open.js +0 -63
- package/dist/workspace/read.d.ts +0 -16
- package/dist/workspace/read.js +0 -22
- package/dist/workspace/shell.d.ts +0 -45
- package/dist/workspace/shell.js +0 -80
- package/dist/workspace/sync.d.ts +0 -111
- package/dist/workspace/sync.js +0 -124
- package/dist/workspace/wake.d.ts +0 -19
- package/dist/workspace/wake.js +0 -10
- /package/dist/{repo → repo-github}/tools-checks.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-clone.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.js +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.js +0 -0
package/dist/computer/paths.d.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Each refused directory is also skipped by every walk, though the walk steps
|
|
5
5
|
* over more than it refuses — see {@link WALK_SKIPS}. `.git` is refused as
|
|
6
6
|
* policy: a model that edits `.git/HEAD` corrupts a checkout in a way that
|
|
7
|
-
* surfaces much later. `node_modules` is refused
|
|
8
|
-
*
|
|
9
|
-
* the workspace, would find it empty.
|
|
7
|
+
* surfaces much later. `node_modules` is refused because it is installed
|
|
8
|
+
* output, not source: one walk into it is most of a checkout's files.
|
|
10
9
|
*
|
|
11
10
|
* Everything here is a string comparison: no filesystem, no `await`, which is
|
|
12
11
|
* what lets {@link guardPath} run before a tool opens the workspace at all.
|
|
@@ -21,33 +20,12 @@
|
|
|
21
20
|
* `.github/` are untouched.
|
|
22
21
|
*/
|
|
23
22
|
export declare function isGitInternal(path: string): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Where a session's own state lives inside a workspace, as a directory name.
|
|
26
|
-
*
|
|
27
|
-
* Every Claude Code session keeps its `CLAUDE_CONFIG_DIR` in a directory of
|
|
28
|
-
* this name at the workspace root — the Durable Object's storage, so a
|
|
29
|
-
* conversation outlives its container and a later session can continue it. See
|
|
30
|
-
* {@link file://../claude-code/run.ts SESSION_CONFIG_DIR}.
|
|
31
|
-
*
|
|
32
|
-
* **Spelled here as well as in the plugin that sets the variable**, because the
|
|
33
|
-
* module that has to step over it is the walk below, and neither may import the
|
|
34
|
-
* other: `/computer` reaching into `/claude-code` would pull a whole plugin into
|
|
35
|
-
* every agent that installs the file tools, and `/claude-code` reaches no other
|
|
36
|
-
* subpath — `verify:exports` refuses both. A spec in `/claude-code` holds the
|
|
37
|
-
* two spellings to one.
|
|
38
|
-
*/
|
|
39
|
-
export declare const SESSION_STATE_DIR = ".claude-sessions";
|
|
40
23
|
/**
|
|
41
24
|
* What every walk steps over, as directory names. Both the workspace's `glob`
|
|
42
|
-
* and the `grep` tool hand these
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* {@link SESSION_STATE_DIR} is here for a reason the refused directories do not
|
|
46
|
-
* share: a transcript is a verbatim record of everything a session read, so a
|
|
47
|
-
* walk that descended into it would answer a `grep` for a line of source with
|
|
48
|
-
* the session that quoted it.
|
|
25
|
+
* and the `grep` tool hand these on as `**\/<name>` exclusions, which prunes
|
|
26
|
+
* them: no directory named here is descended into, so none costs a read.
|
|
49
27
|
*/
|
|
50
|
-
export declare const WALK_SKIPS: readonly [".git", "node_modules"
|
|
28
|
+
export declare const WALK_SKIPS: readonly [".git", "node_modules"];
|
|
51
29
|
/**
|
|
52
30
|
* The path check every file tool and every workspace call makes, before it
|
|
53
31
|
* opens anything.
|
package/dist/computer/paths.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Each refused directory is also skipped by every walk, though the walk steps
|
|
5
5
|
* over more than it refuses — see {@link WALK_SKIPS}. `.git` is refused as
|
|
6
6
|
* policy: a model that edits `.git/HEAD` corrupts a checkout in a way that
|
|
7
|
-
* surfaces much later. `node_modules` is refused
|
|
8
|
-
*
|
|
9
|
-
* the workspace, would find it empty.
|
|
7
|
+
* surfaces much later. `node_modules` is refused because it is installed
|
|
8
|
+
* output, not source: one walk into it is most of a checkout's files.
|
|
10
9
|
*
|
|
11
10
|
* Everything here is a string comparison: no filesystem, no `await`, which is
|
|
12
11
|
* what lets {@link guardPath} run before a tool opens the workspace at all.
|
|
@@ -27,38 +26,16 @@ function hasSegment(path, segment) {
|
|
|
27
26
|
export function isGitInternal(path) {
|
|
28
27
|
return hasSegment(path, ".git");
|
|
29
28
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Where a session's own state lives inside a workspace, as a directory name.
|
|
32
|
-
*
|
|
33
|
-
* Every Claude Code session keeps its `CLAUDE_CONFIG_DIR` in a directory of
|
|
34
|
-
* this name at the workspace root — the Durable Object's storage, so a
|
|
35
|
-
* conversation outlives its container and a later session can continue it. See
|
|
36
|
-
* {@link file://../claude-code/run.ts SESSION_CONFIG_DIR}.
|
|
37
|
-
*
|
|
38
|
-
* **Spelled here as well as in the plugin that sets the variable**, because the
|
|
39
|
-
* module that has to step over it is the walk below, and neither may import the
|
|
40
|
-
* other: `/computer` reaching into `/claude-code` would pull a whole plugin into
|
|
41
|
-
* every agent that installs the file tools, and `/claude-code` reaches no other
|
|
42
|
-
* subpath — `verify:exports` refuses both. A spec in `/claude-code` holds the
|
|
43
|
-
* two spellings to one.
|
|
44
|
-
*/
|
|
45
|
-
export const SESSION_STATE_DIR = ".claude-sessions";
|
|
46
29
|
/**
|
|
47
30
|
* What every walk steps over, as directory names. Both the workspace's `glob`
|
|
48
|
-
* and the `grep` tool hand these
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* {@link SESSION_STATE_DIR} is here for a reason the refused directories do not
|
|
52
|
-
* share: a transcript is a verbatim record of everything a session read, so a
|
|
53
|
-
* walk that descended into it would answer a `grep` for a line of source with
|
|
54
|
-
* the session that quoted it.
|
|
31
|
+
* and the `grep` tool hand these on as `**\/<name>` exclusions, which prunes
|
|
32
|
+
* them: no directory named here is descended into, so none costs a read.
|
|
55
33
|
*/
|
|
56
|
-
export const WALK_SKIPS = [".git", "node_modules"
|
|
34
|
+
export const WALK_SKIPS = [".git", "node_modules"];
|
|
57
35
|
/** The sentence a `node_modules` path gets. */
|
|
58
36
|
function dependencyTreeNote(path, verb) {
|
|
59
|
-
return (`${path} is inside node_modules, which
|
|
60
|
-
`
|
|
61
|
-
`\`cat\`, \`ls\`, \`rg\`.`);
|
|
37
|
+
return (`${path} is inside node_modules, which ${verb} leaves out: it is installed ` +
|
|
38
|
+
`output, not source. Use bash there — \`cat\`, \`ls\`, \`rg\`.`);
|
|
62
39
|
}
|
|
63
40
|
/**
|
|
64
41
|
* The sentence a `.git` path gets.
|
package/dist/computer/proxy.d.ts
CHANGED
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
import type { WorkspaceLike } from "@cloudflare/think";
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { SandboxAdvisory } from "../sandbox/advisory.js";
|
|
3
|
+
import type { ComputerClient } from "./client.js";
|
|
4
4
|
/**
|
|
5
|
-
* The container's
|
|
5
|
+
* The container's checkout, as the agent's own `this.workspace`.
|
|
6
6
|
*
|
|
7
7
|
* Think's `read`, `write`, `edit` and `delete` run against whatever an agent's
|
|
8
|
-
* workspace is, and
|
|
8
|
+
* workspace is, and the computer tools replace only the ones they name. So an
|
|
9
9
|
* agent with a container and Think's default workspace would run `bash` in the
|
|
10
10
|
* container and `write` in its own SQLite, and nothing would say so. An agent
|
|
11
|
-
* that installs
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* override workspace = computerWorkspace(config, () => this.pluginContext().runtime());
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* and `computer(config).tools(ctx)` refuses to start without it — and reaches
|
|
18
|
-
* the workspace through it, so the tools and Think's file tools cannot resolve
|
|
11
|
+
* that installs the tools sets its workspace to `computerWorkspace`
|
|
12
|
+
* (`./sandbox.ts`), and the plugin refuses to start without it — and reaches
|
|
13
|
+
* the container through it, so the tools and Think's file tools cannot resolve
|
|
19
14
|
* two different ones.
|
|
20
|
-
*
|
|
21
|
-
* Built on the workspace's own filesystem, not on `@cloudflare/computer`'s
|
|
22
|
-
* Think adapter: that one's `glob` and `readDir` read every entry, and its
|
|
23
|
-
* `writeFile` knows nothing of the paths and the states this plugin refuses.
|
|
24
15
|
*/
|
|
25
16
|
/**
|
|
26
|
-
* Where a
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
17
|
+
* Where a workspace sends each call, carried on it under {@link ROUTE}. The
|
|
18
|
+
* tools route through this rather than through their own config, which is
|
|
19
|
+
* what makes a workspace built from another config, or without its runtime, a
|
|
20
|
+
* wrong workspace for both rather than a split.
|
|
30
21
|
*/
|
|
31
22
|
export interface WorkspaceRoute {
|
|
32
|
-
/** The
|
|
23
|
+
/** The sandbox the running turn names. Read per call. */
|
|
33
24
|
name(): string;
|
|
34
|
-
|
|
25
|
+
open(name: string): Promise<ComputerClient>;
|
|
26
|
+
advisories(name: string): Promise<readonly SandboxAdvisory[]>;
|
|
35
27
|
}
|
|
36
|
-
/** The route of a workspace {@link
|
|
28
|
+
/** The route of a workspace {@link routedWorkspace} built, or `undefined`. */
|
|
37
29
|
export declare function workspaceRoute(workspace: unknown): WorkspaceRoute | undefined;
|
|
38
|
-
/**
|
|
39
|
-
export declare function
|
|
40
|
-
/**
|
|
41
|
-
* @param runtime The running sub-agent's `runtime()`, read per call — how a
|
|
42
|
-
* sub-agent's file tools reach the workspace its parent prepared. A parent's
|
|
43
|
-
* is `undefined`, and {@link ComputerConfig.workspaceName} names it.
|
|
44
|
-
*/
|
|
45
|
-
export declare function computerWorkspace(config: ComputerConfig, runtime?: () => unknown): WorkspaceLike;
|
|
30
|
+
/** Think's file tools over `route`, carrying it for the computer tools to find. */
|
|
31
|
+
export declare function routedWorkspace(route: WorkspaceRoute, cwd: string): WorkspaceLike;
|
package/dist/computer/proxy.js
CHANGED
|
@@ -52,7 +52,6 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
52
52
|
});
|
|
53
53
|
import { withFileLock } from "./file-lock.js";
|
|
54
54
|
import { writeGate } from "./gate.js";
|
|
55
|
-
import { openWorkspaceFs, workspaceNameFromRuntime } from "../workspace/open.js";
|
|
56
55
|
import { guardPath, WALK_SKIPS } from "./paths.js";
|
|
57
56
|
/**
|
|
58
57
|
* The key the route is carried under. `Symbol.for`, so a second copy of this
|
|
@@ -65,16 +64,12 @@ const ROUTE = Symbol.for("@dynamicagents/plugins/computer:workspace");
|
|
|
65
64
|
* reading the rest of a large checkout into the object would buy nothing.
|
|
66
65
|
*/
|
|
67
66
|
const GLOB_LIMIT = 201;
|
|
68
|
-
/** The route of a workspace {@link
|
|
67
|
+
/** The route of a workspace {@link routedWorkspace} built, or `undefined`. */
|
|
69
68
|
export function workspaceRoute(workspace) {
|
|
70
69
|
if (typeof workspace !== "object" || workspace === null)
|
|
71
70
|
return undefined;
|
|
72
71
|
return workspace[ROUTE];
|
|
73
72
|
}
|
|
74
|
-
/** Whether `workspace` is one {@link computerWorkspace} built. */
|
|
75
|
-
export function isComputerWorkspace(workspace) {
|
|
76
|
-
return workspaceRoute(workspace) !== undefined;
|
|
77
|
-
}
|
|
78
73
|
/**
|
|
79
74
|
* Missing, as the filesystem says it across the boundary. The code does not
|
|
80
75
|
* always survive RPC, so the message is read too.
|
|
@@ -98,23 +93,6 @@ async function orNull(read) {
|
|
|
98
93
|
throw err;
|
|
99
94
|
}
|
|
100
95
|
}
|
|
101
|
-
/**
|
|
102
|
-
* `stat`, with a missing path answered as `null`.
|
|
103
|
-
*
|
|
104
|
-
* Asked of the stub's own `statOrNull` where it has one, the way
|
|
105
|
-
* {@link file://../workspace/read.ts pathExists} asks for `exists`: Think's
|
|
106
|
-
* `read` stats before it reads, so a missing file is an ordinary answer here,
|
|
107
|
-
* and a `stat` that throws across the boundary leaves the object an unhandled
|
|
108
|
-
* rejection to log for it.
|
|
109
|
-
*/
|
|
110
|
-
function statOrNull(fs, path) {
|
|
111
|
-
// Called as a method, never through `.call`: on an RPC stub `.call` is read
|
|
112
|
-
// as a remote method.
|
|
113
|
-
const remote = fs;
|
|
114
|
-
if (typeof remote.statOrNull === "function")
|
|
115
|
-
return remote.statOrNull(path);
|
|
116
|
-
return orNull(fs.stat(path));
|
|
117
|
-
}
|
|
118
96
|
async function drain(stream) {
|
|
119
97
|
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
120
98
|
}
|
|
@@ -144,29 +122,17 @@ function splitGlob(pattern, cwd) {
|
|
|
144
122
|
rest: absolute.slice(cut + 1)
|
|
145
123
|
};
|
|
146
124
|
}
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* is `undefined`, and {@link ComputerConfig.workspaceName} names it.
|
|
151
|
-
*/
|
|
152
|
-
export function computerWorkspace(config, runtime) {
|
|
153
|
-
const cwd = config.cwd ?? "/workspace";
|
|
154
|
-
// Resolved per call, never memoized: the name belongs to the turn, and a
|
|
155
|
-
// stale one would route this caller's writes into another's files.
|
|
156
|
-
const named = () => workspaceNameFromRuntime(runtime?.()) ?? config.workspaceName();
|
|
157
|
-
const host = (name) => config.binding.get(config.binding.idFromName(name));
|
|
158
|
-
/**
|
|
159
|
-
* One call, against the workspace this turn names. Opened without starting a
|
|
160
|
-
* container — see `WorkspaceHost.__getWorkspaceFsStub`.
|
|
161
|
-
*/
|
|
125
|
+
/** Think's file tools over `route`, carrying it for the computer tools to find. */
|
|
126
|
+
export function routedWorkspace(route, cwd) {
|
|
127
|
+
/** One call, against the workspace this turn names. */
|
|
162
128
|
const withFs = async (path, verb, body) => {
|
|
163
129
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
164
130
|
try {
|
|
165
131
|
const refusal = guardPath(path, verb);
|
|
166
132
|
if (refusal)
|
|
167
133
|
throw new Error(refusal);
|
|
168
|
-
const name =
|
|
169
|
-
const ws = __addDisposableResource(env_1, await
|
|
134
|
+
const name = route.name();
|
|
135
|
+
const ws = __addDisposableResource(env_1, await route.open(name), false);
|
|
170
136
|
return await body(ws.fs, name);
|
|
171
137
|
}
|
|
172
138
|
catch (e_1) {
|
|
@@ -185,8 +151,8 @@ export function computerWorkspace(config, runtime) {
|
|
|
185
151
|
const withWritableFs = (path, verb, body) => withFs(path, verb, async (fs, name) => {
|
|
186
152
|
// Fails open, for the reason the tools' own write gate does: a read of
|
|
187
153
|
// another Durable Object's state must not take out a working tool.
|
|
188
|
-
const lost = await
|
|
189
|
-
.advisories()
|
|
154
|
+
const lost = await route
|
|
155
|
+
.advisories(name)
|
|
190
156
|
.then(writeGate, () => undefined);
|
|
191
157
|
if (lost)
|
|
192
158
|
throw new Error(lost);
|
|
@@ -201,7 +167,7 @@ export function computerWorkspace(config, runtime) {
|
|
|
201
167
|
return stream === null ? null : drain(stream);
|
|
202
168
|
}),
|
|
203
169
|
stat: (path) => withFs(path, "read", async (fs) => {
|
|
204
|
-
const stat = await
|
|
170
|
+
const stat = await orNull(fs.stat(path));
|
|
205
171
|
return stat === null ? null : fileInfo({ ...stat, path });
|
|
206
172
|
}),
|
|
207
173
|
readDir: (dir = cwd, opts) => withFs(dir, "list", async (fs) => (await fs.readdir(dir, opts)).map((entry) => fileInfo({
|
|
@@ -228,7 +194,6 @@ export function computerWorkspace(config, runtime) {
|
|
|
228
194
|
// evicted media, projected skills — and none of that belongs in a
|
|
229
195
|
// repository's checkout. Without it Think skips both, with a warning.
|
|
230
196
|
};
|
|
231
|
-
const route = { name: named, host };
|
|
232
197
|
Object.defineProperty(workspace, ROUTE, { value: route });
|
|
233
198
|
return workspace;
|
|
234
199
|
}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
* One `fs.grep` hit, derived from the client rather than imported.
|
|
4
|
-
*
|
|
5
|
-
* `@cloudflare/computer` declares `WorkspaceGrepMatch` but does not re-export it,
|
|
6
|
-
* so there is no name to import. Reading it back off the method keeps this correct
|
|
7
|
-
* by construction.
|
|
8
|
-
*/
|
|
9
|
-
type GrepMatch = Awaited<ReturnType<WorkspaceClient["fs"]["grep"]>>[number];
|
|
1
|
+
import type { CommandResult, GrepMatch } from "./client.js";
|
|
10
2
|
/**
|
|
11
3
|
* Emit whole items until the byte budget is spent, and report exactly how many.
|
|
12
4
|
*
|
|
@@ -64,7 +56,7 @@ export declare function renderGrepMatches(matches: GrepMatch[], maxChars: number
|
|
|
64
56
|
*
|
|
65
57
|
* ## `status` is not the exit code
|
|
66
58
|
*
|
|
67
|
-
* `
|
|
59
|
+
* A `status` is `completed | failed | cancelled`. Dropping it
|
|
68
60
|
* renders a command killed at the `timeoutMs` ceiling exactly like one that ran
|
|
69
61
|
* to completion and failed — "your test suite was killed at ten minutes" and
|
|
70
62
|
* "your test suite has a failing test", which want opposite responses. Anything
|
|
@@ -79,34 +71,5 @@ export declare function renderResult(result: {
|
|
|
79
71
|
exitCode: number;
|
|
80
72
|
stdout: string;
|
|
81
73
|
stderr: string;
|
|
82
|
-
status?:
|
|
83
|
-
sync?: {
|
|
84
|
-
status: "complete" | "pending";
|
|
85
|
-
};
|
|
74
|
+
status?: CommandResult["status"];
|
|
86
75
|
}, maxChars: number): string;
|
|
87
|
-
/**
|
|
88
|
-
* The command ran; its changes have not landed in the workspace yet.
|
|
89
|
-
*
|
|
90
|
-
* Every command is bracketed by a sync, and the pull afterwards is what moves
|
|
91
|
-
* what it wrote from the container into the Durable Object. That pull can fail
|
|
92
|
-
* while the command itself succeeds — a transport that dropped, a container
|
|
93
|
-
* replaced underneath — and the runtime reports it in `sync` rather than in the
|
|
94
|
-
* exit code, because the command genuinely did run.
|
|
95
|
-
*
|
|
96
|
-
* Worth a sentence to the model because the *next* thing it does is usually read
|
|
97
|
-
* what it just wrote, and the file tools read the workspace rather than the
|
|
98
|
-
* container. Without this the file looks unchanged and the obvious conclusion is
|
|
99
|
-
* that the command did not work, which is the one conclusion that is wrong.
|
|
100
|
-
*
|
|
101
|
-
* Recovery is deliberately not the model's, and is not automatic either: the
|
|
102
|
-
* runtime schedules no retry of its own, so an outstanding pull lands when the
|
|
103
|
-
* host drives one or when a later command's own bracket carries it. What the
|
|
104
|
-
* model is told therefore stops short of "it will be there in a moment" — it is
|
|
105
|
-
* told the read may be stale, that re-running the command is the wrong fix
|
|
106
|
-
* because it would repeat the side effects, and to say so in its result if the
|
|
107
|
-
* workspace still disagrees with what it wrote.
|
|
108
|
-
*/
|
|
109
|
-
export declare function syncPendingNote(sync: {
|
|
110
|
-
status: "complete" | "pending";
|
|
111
|
-
} | undefined): string | undefined;
|
|
112
|
-
export {};
|
package/dist/computer/render.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { truncateOutput } from "
|
|
1
|
+
import { truncateOutput } from "@dynamicagents/core";
|
|
2
2
|
/**
|
|
3
3
|
* Turning a result into the text a model reads.
|
|
4
4
|
*
|
|
@@ -114,7 +114,7 @@ export function renderGrepMatches(matches, maxChars) {
|
|
|
114
114
|
*
|
|
115
115
|
* ## `status` is not the exit code
|
|
116
116
|
*
|
|
117
|
-
* `
|
|
117
|
+
* A `status` is `completed | failed | cancelled`. Dropping it
|
|
118
118
|
* renders a command killed at the `timeoutMs` ceiling exactly like one that ran
|
|
119
119
|
* to completion and failed — "your test suite was killed at ten minutes" and
|
|
120
120
|
* "your test suite has a failing test", which want opposite responses. Anything
|
|
@@ -137,38 +137,5 @@ export function renderResult(result, maxChars) {
|
|
|
137
137
|
const body = truncateOutput(parts.join("\n"), maxChars);
|
|
138
138
|
const state = result.status && result.status !== "completed" ? ` (${result.status})` : "";
|
|
139
139
|
const verdict = `--- exit ${result.exitCode}${state} ---`;
|
|
140
|
-
|
|
141
|
-
const transcript = body ? `${body}\n${verdict}` : `(no output)\n${verdict}`;
|
|
142
|
-
return pending ? `${transcript}\n${pending}` : transcript;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* The command ran; its changes have not landed in the workspace yet.
|
|
146
|
-
*
|
|
147
|
-
* Every command is bracketed by a sync, and the pull afterwards is what moves
|
|
148
|
-
* what it wrote from the container into the Durable Object. That pull can fail
|
|
149
|
-
* while the command itself succeeds — a transport that dropped, a container
|
|
150
|
-
* replaced underneath — and the runtime reports it in `sync` rather than in the
|
|
151
|
-
* exit code, because the command genuinely did run.
|
|
152
|
-
*
|
|
153
|
-
* Worth a sentence to the model because the *next* thing it does is usually read
|
|
154
|
-
* what it just wrote, and the file tools read the workspace rather than the
|
|
155
|
-
* container. Without this the file looks unchanged and the obvious conclusion is
|
|
156
|
-
* that the command did not work, which is the one conclusion that is wrong.
|
|
157
|
-
*
|
|
158
|
-
* Recovery is deliberately not the model's, and is not automatic either: the
|
|
159
|
-
* runtime schedules no retry of its own, so an outstanding pull lands when the
|
|
160
|
-
* host drives one or when a later command's own bracket carries it. What the
|
|
161
|
-
* model is told therefore stops short of "it will be there in a moment" — it is
|
|
162
|
-
* told the read may be stale, that re-running the command is the wrong fix
|
|
163
|
-
* because it would repeat the side effects, and to say so in its result if the
|
|
164
|
-
* workspace still disagrees with what it wrote.
|
|
165
|
-
*/
|
|
166
|
-
export function syncPendingNote(sync) {
|
|
167
|
-
if (sync?.status !== "pending")
|
|
168
|
-
return undefined;
|
|
169
|
-
return ("(The command finished, but what it wrote has not reached the workspace " +
|
|
170
|
-
"yet, so the file tools may still show the previous contents. Do not re-run " +
|
|
171
|
-
"the command to force it — that repeats whatever it already did. Read again " +
|
|
172
|
-
"in a moment, and if the workspace still disagrees with what the command " +
|
|
173
|
-
"wrote, say so in your result rather than working around it.)");
|
|
140
|
+
return body ? `${body}\n${verdict}` : `(no output)\n${verdict}`;
|
|
174
141
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { WorkspaceLike } from "@cloudflare/think";
|
|
2
|
+
import type { SandboxAdvisory } from "../sandbox/advisory.js";
|
|
3
|
+
import type { ComputerClient } from "./client.js";
|
|
4
|
+
/**
|
|
5
|
+
* The computer tools over a `/sandbox` object.
|
|
6
|
+
*
|
|
7
|
+
* The checkout is the container's own disk, so every call reaches the
|
|
8
|
+
* container: a file read wakes a stopped one (measured at about two seconds,
|
|
9
|
+
* once), and `grep` and `find` are `ripgrep` there. The sandbox's RPCs run as
|
|
10
|
+
* the agent and refuse anything outside `/workspace`.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* override workspace = computerWorkspace(config, () => this.pluginContext().runtime());
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `computer(config)`'s tools then find the sandbox through it.
|
|
17
|
+
*/
|
|
18
|
+
/** The sandbox RPCs this uses. A `/sandbox` object answers all of them. */
|
|
19
|
+
export interface SandboxComputerHost extends Rpc.DurableObjectBranded {
|
|
20
|
+
exec(argv: string[], options?: {
|
|
21
|
+
cwd?: string;
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
exitCode: number;
|
|
26
|
+
stdout: string;
|
|
27
|
+
stderr: string;
|
|
28
|
+
}>;
|
|
29
|
+
readFile(path: string): Promise<Uint8Array | null>;
|
|
30
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
31
|
+
stat(path: string): Promise<{
|
|
32
|
+
type: string;
|
|
33
|
+
size: number;
|
|
34
|
+
modifiedAt: number;
|
|
35
|
+
} | null>;
|
|
36
|
+
readDir(path: string): Promise<{
|
|
37
|
+
name: string;
|
|
38
|
+
type: string;
|
|
39
|
+
}[]>;
|
|
40
|
+
mkdir(path: string): Promise<void>;
|
|
41
|
+
rm(path: string, options?: {
|
|
42
|
+
recursive?: boolean;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
advisories(): Promise<readonly SandboxAdvisory[]>;
|
|
45
|
+
}
|
|
46
|
+
export interface SandboxComputerConfig {
|
|
47
|
+
/** Addressed by name only: the sandbox's lifecycle needs it. */
|
|
48
|
+
binding: DurableObjectNamespace<SandboxComputerHost>;
|
|
49
|
+
/** The sandbox a parent's own turn works in. A sub-agent's comes from its `runtime()`. */
|
|
50
|
+
sandboxName: () => string;
|
|
51
|
+
cwd?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function computerWorkspace(config: SandboxComputerConfig, runtime?: () => unknown): WorkspaceLike;
|
|
54
|
+
/** The computer tools' client over one sandbox. */
|
|
55
|
+
export declare function sandboxClient(host: SandboxComputerHost): ComputerClient;
|