@dynamicagents/plugins 0.14.0 → 0.16.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.
Files changed (141) hide show
  1. package/README.md +14 -14
  2. package/dist/claude-code/cards.d.ts +2 -0
  3. package/dist/claude-code/cards.js +11 -0
  4. package/dist/claude-code/config.d.ts +10 -83
  5. package/dist/claude-code/config.js +0 -2
  6. package/dist/claude-code/credentials.d.ts +32 -50
  7. package/dist/claude-code/credentials.js +115 -74
  8. package/dist/claude-code/daemon/index.d.ts +24 -0
  9. package/dist/claude-code/daemon/index.js +203 -0
  10. package/dist/claude-code/egress.d.ts +35 -117
  11. package/dist/claude-code/egress.js +80 -325
  12. package/dist/claude-code/events.d.ts +14 -17
  13. package/dist/claude-code/events.js +19 -15
  14. package/dist/claude-code/harness-wire.d.ts +45 -0
  15. package/dist/claude-code/harness-wire.js +47 -0
  16. package/dist/claude-code/index.d.ts +19 -29
  17. package/dist/claude-code/index.js +17 -25
  18. package/dist/claude-code/language-model.d.ts +19 -0
  19. package/dist/claude-code/language-model.js +71 -0
  20. package/dist/claude-code/model.d.ts +77 -70
  21. package/dist/claude-code/model.js +327 -327
  22. package/dist/claude-code/placeholder.d.ts +12 -0
  23. package/dist/claude-code/placeholder.js +12 -0
  24. package/dist/claude-code/spec.d.ts +4 -4
  25. package/dist/claude-code/spec.js +4 -4
  26. package/dist/computer/client.d.ts +88 -0
  27. package/dist/computer/client.js +6 -0
  28. package/dist/computer/context.d.ts +7 -7
  29. package/dist/computer/context.js +14 -18
  30. package/dist/computer/gate.d.ts +4 -23
  31. package/dist/computer/gate.js +1 -37
  32. package/dist/computer/index.d.ts +44 -43
  33. package/dist/computer/index.js +29 -50
  34. package/dist/computer/paths.d.ts +5 -27
  35. package/dist/computer/paths.js +7 -30
  36. package/dist/computer/proxy.d.ts +17 -31
  37. package/dist/computer/proxy.js +9 -44
  38. package/dist/computer/render.d.ts +3 -40
  39. package/dist/computer/render.js +3 -36
  40. package/dist/computer/sandbox.d.ts +55 -0
  41. package/dist/computer/sandbox.js +236 -0
  42. package/dist/computer/shell.d.ts +36 -0
  43. package/dist/computer/shell.js +41 -0
  44. package/dist/computer/tools-exec.js +3 -9
  45. package/dist/computer/tools-file.d.ts +1 -1
  46. package/dist/computer/tools-file.js +1 -1
  47. package/dist/{repo → repo-github}/checkout.d.ts +3 -4
  48. package/dist/{repo → repo-github}/checkout.js +3 -4
  49. package/dist/{repo → repo-github}/context.d.ts +14 -32
  50. package/dist/{repo → repo-github}/context.js +46 -81
  51. package/dist/{repo → repo-github}/index.d.ts +30 -13
  52. package/dist/{repo → repo-github}/index.js +25 -6
  53. package/dist/repo-github/mcp.d.ts +95 -0
  54. package/dist/repo-github/mcp.js +107 -0
  55. package/dist/repo-github/sandbox-egress.d.ts +68 -0
  56. package/dist/repo-github/sandbox-egress.js +272 -0
  57. package/dist/repo-github/sandbox-git.d.ts +30 -0
  58. package/dist/repo-github/sandbox-git.js +113 -0
  59. package/dist/{repo → repo-github}/tools-checks.js +43 -4
  60. package/dist/{repo → repo-github}/tools-clone.js +2 -21
  61. package/dist/{repo → repo-github}/tools-pr.d.ts +0 -1
  62. package/dist/{repo → repo-github}/tools-pr.js +85 -20
  63. package/dist/{repo → repo-github}/tools-review.d.ts +6 -0
  64. package/dist/{repo → repo-github}/tools-review.js +62 -176
  65. package/dist/{repo → repo-github}/url.d.ts +11 -0
  66. package/dist/{repo → repo-github}/url.js +12 -1
  67. package/dist/{workspace → sandbox}/advisory.d.ts +16 -35
  68. package/dist/{workspace → sandbox}/advisory.js +2 -29
  69. package/dist/sandbox/agent-exec.d.ts +31 -0
  70. package/dist/sandbox/agent-exec.js +62 -0
  71. package/dist/sandbox/ca-trust.d.ts +9 -0
  72. package/dist/sandbox/ca-trust.js +21 -0
  73. package/dist/sandbox/constants.d.ts +22 -0
  74. package/dist/sandbox/constants.js +22 -0
  75. package/dist/sandbox/daemon/composite.d.ts +9 -0
  76. package/dist/sandbox/daemon/composite.js +35 -0
  77. package/dist/sandbox/daemon/index.d.ts +47 -0
  78. package/dist/sandbox/daemon/index.js +116 -0
  79. package/dist/sandbox/egress.d.ts +81 -0
  80. package/dist/sandbox/egress.js +109 -0
  81. package/dist/sandbox/exec.d.ts +57 -0
  82. package/dist/sandbox/exec.js +52 -0
  83. package/dist/sandbox/format.d.ts +2 -0
  84. package/dist/sandbox/format.js +7 -0
  85. package/dist/sandbox/header.d.ts +50 -0
  86. package/dist/sandbox/header.js +94 -0
  87. package/dist/sandbox/index.d.ts +17 -0
  88. package/dist/sandbox/index.js +17 -0
  89. package/dist/{workspace/install.d.ts → sandbox/install-plan.d.ts} +2 -2
  90. package/dist/{workspace/install.js → sandbox/install-plan.js} +2 -2
  91. package/dist/sandbox/name.d.ts +27 -0
  92. package/dist/sandbox/name.js +32 -0
  93. package/dist/sandbox/object.d.ts +244 -0
  94. package/dist/sandbox/object.js +1139 -0
  95. package/dist/scratch/index.d.ts +1 -9
  96. package/dist/scratch/index.js +2 -18
  97. package/package.json +24 -20
  98. package/dist/claude-code/github.d.ts +0 -39
  99. package/dist/claude-code/github.js +0 -82
  100. package/dist/claude-code/run.d.ts +0 -491
  101. package/dist/claude-code/run.js +0 -913
  102. package/dist/claude-code/session.d.ts +0 -131
  103. package/dist/claude-code/session.js +0 -288
  104. package/dist/workspace/ca-trust.d.ts +0 -95
  105. package/dist/workspace/ca-trust.js +0 -312
  106. package/dist/workspace/container-deps.d.ts +0 -58
  107. package/dist/workspace/container-deps.js +0 -249
  108. package/dist/workspace/container-fault.d.ts +0 -38
  109. package/dist/workspace/container-fault.js +0 -73
  110. package/dist/workspace/deps-snapshot.d.ts +0 -110
  111. package/dist/workspace/deps-snapshot.js +0 -424
  112. package/dist/workspace/exec.d.ts +0 -94
  113. package/dist/workspace/exec.js +0 -153
  114. package/dist/workspace/format.d.ts +0 -36
  115. package/dist/workspace/format.js +0 -57
  116. package/dist/workspace/git-host.d.ts +0 -79
  117. package/dist/workspace/git-host.js +0 -226
  118. package/dist/workspace/git-identity.d.ts +0 -57
  119. package/dist/workspace/git-identity.js +0 -147
  120. package/dist/workspace/index.d.ts +0 -50
  121. package/dist/workspace/index.js +0 -58
  122. package/dist/workspace/install-job.d.ts +0 -170
  123. package/dist/workspace/install-job.js +0 -934
  124. package/dist/workspace/object.d.ts +0 -522
  125. package/dist/workspace/object.js +0 -1541
  126. package/dist/workspace/open.d.ts +0 -95
  127. package/dist/workspace/open.js +0 -63
  128. package/dist/workspace/read.d.ts +0 -16
  129. package/dist/workspace/read.js +0 -22
  130. package/dist/workspace/shell.d.ts +0 -45
  131. package/dist/workspace/shell.js +0 -80
  132. package/dist/workspace/sync.d.ts +0 -111
  133. package/dist/workspace/sync.js +0 -124
  134. package/dist/workspace/wake.d.ts +0 -21
  135. package/dist/workspace/wake.js +0 -10
  136. /package/dist/{repo → repo-github}/tools-checks.d.ts +0 -0
  137. /package/dist/{repo → repo-github}/tools-clone.d.ts +0 -0
  138. /package/dist/{repo → repo-github}/tools-worktree.d.ts +0 -0
  139. /package/dist/{repo → repo-github}/tools-worktree.js +0 -0
  140. /package/dist/{repo → repo-github}/tools-worktrees.d.ts +0 -0
  141. /package/dist/{repo → repo-github}/tools-worktrees.js +0 -0
@@ -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 as a fact: it lives on the
8
- * container's disk (see `../workspace/container-deps.ts`), so these tools, which read
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 to the store as `**\/<name>` exclusions, which
43
- * prunes them: no directory named here is descended into, so none costs a read.
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", ".claude-sessions"];
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.
@@ -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 as a fact: it lives on the
8
- * container's disk (see `../workspace/container-deps.ts`), so these tools, which read
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 to the store as `**\/<name>` exclusions, which
49
- * prunes them: no directory named here is descended into, so none costs a read.
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", SESSION_STATE_DIR];
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 lives on the container's disk rather ` +
60
- `than in the workspace ${verb} works on. Use bash there — ` +
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.
@@ -1,45 +1,31 @@
1
1
  import type { WorkspaceLike } from "@cloudflare/think";
2
- import type { WorkspaceHost } from "../workspace/open.js";
3
- import type { ComputerConfig } from "./index.js";
2
+ import type { SandboxAdvisory } from "../sandbox/advisory.js";
3
+ import type { ComputerClient } from "./client.js";
4
4
  /**
5
- * The container's workspace, as the agent's own `this.workspace`.
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 `computer()`'s tools replace only the ones it names. So an
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 `computer` sets this instead:
12
- *
13
- * ```ts
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 {@link computerWorkspace} sends each call, carried on it under
27
- * {@link ROUTE}. `computer()`'s tools route through this rather than through
28
- * their own config, which is what makes a workspace built from another config,
29
- * or without its runtime, a wrong workspace for both rather than a split.
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 workspace the running turn names. Read per call. */
23
+ /** The sandbox the running turn names. Read per call. */
33
24
  name(): string;
34
- host(name: string): DurableObjectStub<WorkspaceHost>;
25
+ open(name: string): Promise<ComputerClient>;
26
+ advisories(name: string): Promise<readonly SandboxAdvisory[]>;
35
27
  }
36
- /** The route of a workspace {@link computerWorkspace} built, or `undefined`. */
28
+ /** The route of a workspace {@link routedWorkspace} built, or `undefined`. */
37
29
  export declare function workspaceRoute(workspace: unknown): WorkspaceRoute | undefined;
38
- /** Whether `workspace` is one {@link computerWorkspace} built. */
39
- export declare function isComputerWorkspace(workspace: unknown): boolean;
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;
@@ -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 computerWorkspace} built, or `undefined`. */
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
- * @param runtime The running sub-agent's `runtime()`, read per call — how a
149
- * sub-agent's file tools reach the workspace its parent prepared. A parent's
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 = named();
169
- const ws = __addDisposableResource(env_1, await openWorkspaceFs(host(name)), false);
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 host(name)
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 statOrNull(fs, path);
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 { WorkspaceClient, WorkspaceRuntimeStatus } from "@cloudflare/computer";
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
- * `WorkspaceRuntimeStatus` is `completed | failed | cancelled`. Dropping it
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?: WorkspaceRuntimeStatus;
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 {};
@@ -1,4 +1,4 @@
1
- import { truncateOutput } from "../workspace/format.js";
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
- * `WorkspaceRuntimeStatus` is `completed | failed | cancelled`. Dropping it
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
- const pending = syncPendingNote(result.sync);
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;