@dynamicagents/plugins 0.11.1 → 0.13.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 +59 -79
- package/dist/browser/index.d.ts +0 -2
- package/dist/browser/index.js +21 -27
- package/dist/claude-code/cards.d.ts +53 -0
- package/dist/claude-code/cards.js +272 -0
- package/dist/claude-code/config.d.ts +73 -31
- package/dist/claude-code/config.js +0 -2
- package/dist/claude-code/credentials.d.ts +21 -9
- package/dist/claude-code/credentials.js +43 -9
- package/dist/claude-code/egress.d.ts +15 -14
- package/dist/claude-code/egress.js +51 -14
- package/dist/claude-code/events.d.ts +153 -9
- package/dist/claude-code/events.js +289 -58
- package/dist/claude-code/github.d.ts +39 -0
- package/dist/claude-code/github.js +82 -0
- package/dist/claude-code/index.d.ts +24 -80
- package/dist/claude-code/index.js +19 -234
- package/dist/claude-code/model.d.ts +152 -0
- package/dist/claude-code/model.js +342 -0
- package/dist/claude-code/run.d.ts +283 -92
- package/dist/claude-code/run.js +620 -114
- package/dist/claude-code/session.d.ts +131 -0
- package/dist/claude-code/session.js +285 -0
- package/dist/claude-code/spec.d.ts +46 -0
- package/dist/claude-code/spec.js +83 -0
- package/dist/claude-code/verified.d.ts +15 -0
- package/dist/claude-code/verified.js +15 -0
- package/dist/computer/context.d.ts +19 -0
- package/dist/computer/context.js +222 -0
- package/dist/computer/file-lock.d.ts +5 -0
- package/dist/computer/file-lock.js +40 -0
- package/dist/computer/gate.d.ts +12 -12
- package/dist/computer/gate.js +12 -12
- package/dist/computer/index.d.ts +57 -255
- package/dist/computer/index.js +98 -1039
- package/dist/computer/paths.d.ts +47 -22
- package/dist/computer/paths.js +64 -49
- package/dist/computer/proxy.d.ts +45 -0
- package/dist/computer/proxy.js +234 -0
- package/dist/computer/render.d.ts +27 -38
- package/dist/computer/render.js +32 -65
- package/dist/computer/tools-exec.d.ts +3 -0
- package/dist/computer/tools-exec.js +268 -0
- package/dist/computer/tools-file.d.ts +12 -0
- package/dist/computer/tools-file.js +64 -0
- package/dist/computer/tools-grep.d.ts +3 -0
- package/dist/computer/tools-grep.js +129 -0
- package/dist/repo/checkout.d.ts +35 -6
- package/dist/repo/checkout.js +64 -6
- package/dist/repo/context.d.ts +135 -0
- package/dist/repo/context.js +582 -0
- package/dist/repo/index.d.ts +79 -57
- package/dist/repo/index.js +118 -1112
- package/dist/repo/tools-checks.d.ts +3 -0
- package/dist/repo/tools-checks.js +112 -0
- package/dist/repo/tools-clone.d.ts +4 -0
- package/dist/repo/tools-clone.js +192 -0
- package/dist/repo/tools-pr.d.ts +14 -0
- package/dist/repo/tools-pr.js +196 -0
- package/dist/repo/tools-review.d.ts +12 -0
- package/dist/repo/tools-review.js +393 -0
- package/dist/repo/tools-worktree.d.ts +4 -0
- package/dist/repo/tools-worktree.js +305 -0
- package/dist/repo/tools-worktrees.d.ts +12 -0
- package/dist/repo/tools-worktrees.js +48 -0
- package/dist/repo/url.d.ts +2 -2
- package/dist/repo/url.js +7 -7
- package/dist/scratch/index.d.ts +4 -4
- package/dist/scratch/index.js +12 -11
- package/dist/{computer → workspace}/advisory.d.ts +33 -20
- package/dist/{computer → workspace}/advisory.js +32 -7
- package/dist/workspace/ca-trust.d.ts +95 -0
- package/dist/workspace/ca-trust.js +312 -0
- package/dist/workspace/container-deps.d.ts +54 -0
- package/dist/workspace/container-deps.js +224 -0
- package/dist/workspace/container-fault.d.ts +38 -0
- package/dist/workspace/container-fault.js +73 -0
- package/dist/workspace/exec.d.ts +94 -0
- package/dist/workspace/exec.js +153 -0
- package/dist/workspace/format.d.ts +36 -0
- package/dist/workspace/format.js +57 -0
- package/dist/workspace/git-host.d.ts +79 -0
- package/dist/workspace/git-host.js +226 -0
- package/dist/workspace/git-identity.d.ts +57 -0
- package/dist/workspace/git-identity.js +147 -0
- package/dist/workspace/index.d.ts +43 -47
- package/dist/workspace/index.js +52 -112
- package/dist/workspace/install-job.d.ts +121 -0
- package/dist/workspace/install-job.js +866 -0
- package/dist/{computer → workspace}/install.d.ts +15 -19
- package/dist/{computer → workspace}/install.js +8 -11
- package/dist/workspace/object.d.ts +502 -0
- package/dist/workspace/object.js +1427 -0
- package/dist/workspace/open.d.ts +95 -0
- package/dist/workspace/open.js +63 -0
- package/dist/workspace/read.d.ts +16 -0
- package/dist/workspace/read.js +22 -0
- package/dist/workspace/shell.d.ts +45 -0
- package/dist/workspace/shell.js +80 -0
- package/dist/workspace/sync.d.ts +111 -0
- package/dist/workspace/sync.js +124 -0
- package/dist/workspace/wake.d.ts +19 -0
- package/dist/workspace/wake.js +10 -0
- package/package.json +28 -42
- package/dist/arc-agi/analysis.d.ts +0 -336
- package/dist/arc-agi/analysis.js +0 -829
- package/dist/arc-agi/client.d.ts +0 -82
- package/dist/arc-agi/client.js +0 -113
- package/dist/arc-agi/game-tools.d.ts +0 -25
- package/dist/arc-agi/game-tools.js +0 -26
- package/dist/arc-agi/index.d.ts +0 -42
- package/dist/arc-agi/index.js +0 -175
- package/dist/arc-agi/main-agent.d.ts +0 -27
- package/dist/arc-agi/main-agent.js +0 -65
- package/dist/arc-agi/recipe.d.ts +0 -51
- package/dist/arc-agi/recipe.js +0 -100
- package/dist/arc-agi/scorecard.d.ts +0 -118
- package/dist/arc-agi/scorecard.js +0 -163
- package/dist/arc-agi/soul.d.ts +0 -40
- package/dist/arc-agi/soul.js +0 -90
- package/dist/arc-agi/store.d.ts +0 -154
- package/dist/arc-agi/store.js +0 -216
- package/dist/arc-agi/tools.d.ts +0 -36
- package/dist/arc-agi/tools.js +0 -576
- package/dist/arc-agi/types.d.ts +0 -212
- package/dist/arc-agi/types.js +0 -19
- package/dist/claude-code/recipe.d.ts +0 -53
- package/dist/claude-code/recipe.js +0 -100
- package/dist/computer/read.d.ts +0 -137
- package/dist/computer/read.js +0 -203
- package/dist/recall/index.d.ts +0 -99
- package/dist/recall/index.js +0 -171
- package/dist/triage/index.d.ts +0 -355
- package/dist/triage/index.js +0 -302
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* ## Why this exists at all
|
|
12
12
|
*
|
|
13
|
-
* The alternative is a sentence in the
|
|
13
|
+
* The alternative is a sentence in the sub-agent's soul — "install dependencies
|
|
14
14
|
* before you do anything else" — and a standard with no command attached is one a
|
|
15
15
|
* model satisfies with whichever check is cheapest: edit a README, run
|
|
16
16
|
* `prettier --check` on that one file, report the change verified, and never run
|
|
@@ -36,25 +36,24 @@ export interface InstallProbe {
|
|
|
36
36
|
* Where the install has got to, as the host reports it and the tools read it.
|
|
37
37
|
*
|
|
38
38
|
* Declared here rather than in the host because both sides depend on it and
|
|
39
|
-
* neither owns the other: the Durable Object runs the job, and `
|
|
39
|
+
* neither owns the other: the Durable Object runs the job, and `bash` refuses
|
|
40
40
|
* to run against a tree that is still being built. A shape they agreed on
|
|
41
41
|
* informally would drift, and the drift would show up as a shell command running
|
|
42
42
|
* halfway through an `npm ci`.
|
|
43
43
|
*
|
|
44
|
-
* ## Why it is
|
|
44
|
+
* ## Why it is the host's `JobState` with one field intersected in
|
|
45
45
|
*
|
|
46
|
-
* Core owns the *choreography* around a job a Durable Object drives
|
|
47
|
-
* alarm — arming, the single-flight guard, the staleness bound, the
|
|
48
|
-
*
|
|
46
|
+
* Core's `/job` owns the *choreography* around a job a Durable Object drives
|
|
47
|
+
* through its alarm — arming, the single-flight guard, the staleness bound, the
|
|
48
|
+
* generation a drain writes under. This package owns what an install
|
|
49
49
|
* specifically **is**: which package manager, which lockfile, what counts as
|
|
50
50
|
* skippable. Splitting them along that line is what lets a second job on the
|
|
51
51
|
* same object — a coding-agent run, say — reuse every rule without inheriting
|
|
52
52
|
* `npm ci`.
|
|
53
53
|
*
|
|
54
54
|
* `JobState` intersects `TExtra` into its `running`/`done`/`failed` variants
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* the top level and no call site moved.
|
|
55
|
+
* rather than nesting it under `meta`, so a reader spells `status.command` at
|
|
56
|
+
* the top level.
|
|
58
57
|
*/
|
|
59
58
|
export type InstallState = JobState<{
|
|
60
59
|
command: string;
|
|
@@ -137,8 +136,7 @@ export declare function resolveInstallCommand(fs: InstallProbe, dir: string, pla
|
|
|
137
136
|
*
|
|
138
137
|
* Content, not mtime: a `git fetch && reset --hard` onto a new commit rewrites
|
|
139
138
|
* these files whether or not their dependencies changed, and re-installing on
|
|
140
|
-
* every commit would throw away
|
|
141
|
-
* having.
|
|
139
|
+
* every commit would throw away a tree that is already correct.
|
|
142
140
|
*
|
|
143
141
|
* ## Both files, not just the lockfile
|
|
144
142
|
*
|
|
@@ -148,7 +146,7 @@ export declare function resolveInstallCommand(fs: InstallProbe, dir: string, pla
|
|
|
148
146
|
* from `pnpm@9` to `pnpm@10`, or add a dependency without regenerating the lock,
|
|
149
147
|
* and every one of those changes what an install produces while leaving the
|
|
150
148
|
* lockfile byte-identical. Hashing the lockfile alone calls that a match, skips
|
|
151
|
-
* the install, and hands the
|
|
149
|
+
* the install, and hands the agent a `node_modules` that is quietly wrong —
|
|
152
150
|
* which surfaces as a missing module in a build, three tool calls later, with
|
|
153
151
|
* nothing pointing back at the install that never ran.
|
|
154
152
|
*
|
|
@@ -171,8 +169,8 @@ export declare function resolveInstallCommand(fs: InstallProbe, dir: string, pla
|
|
|
171
169
|
* {@link INSTALL_CONFIG_FILES} is hashed too, for the same reason `package.json`
|
|
172
170
|
* is: a lockfile says which versions, and `.npmrc` says how they are laid out.
|
|
173
171
|
* Flipping pnpm's `node-linker` to `hoisted` produces a different
|
|
174
|
-
* `node_modules` from a byte-identical lockfile, and a
|
|
175
|
-
* otherwise skip the reinstall that change requires.
|
|
172
|
+
* `node_modules` from a byte-identical lockfile, and a matching fingerprint
|
|
173
|
+
* would otherwise skip the reinstall that change requires.
|
|
176
174
|
*
|
|
177
175
|
* The residual is worth stating because it cannot be closed here: an override
|
|
178
176
|
* that *builds* — `npm ci && npm run build`, the documented example — is not
|
|
@@ -184,10 +182,8 @@ export declare function resolveInstallCommand(fs: InstallProbe, dir: string, pla
|
|
|
184
182
|
* ## This is only half of the skip condition
|
|
185
183
|
*
|
|
186
184
|
* A matching fingerprint means "the same install would produce the same tree".
|
|
187
|
-
* It does **not** mean the tree is there
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* the fingerprint alone skips the install that the empty tree needs most. The
|
|
191
|
-
* caller must also confirm `node_modules` is actually present.
|
|
185
|
+
* It does **not** mean the tree is there: it is on the container's disk, and
|
|
186
|
+
* goes with the container. The caller also needs a record that this container
|
|
187
|
+
* finished it — see `./install-job.ts`.
|
|
192
188
|
*/
|
|
193
189
|
export declare function installFingerprint(fs: InstallProbe, dir: string, resolution: InstallResolution): Promise<string | null>;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* ## Why this exists at all
|
|
12
12
|
*
|
|
13
|
-
* The alternative is a sentence in the
|
|
13
|
+
* The alternative is a sentence in the sub-agent's soul — "install dependencies
|
|
14
14
|
* before you do anything else" — and a standard with no command attached is one a
|
|
15
15
|
* model satisfies with whichever check is cheapest: edit a README, run
|
|
16
16
|
* `prettier --check` on that one file, report the change verified, and never run
|
|
@@ -211,8 +211,7 @@ async function firstPresent(fs, dir, names) {
|
|
|
211
211
|
*
|
|
212
212
|
* Content, not mtime: a `git fetch && reset --hard` onto a new commit rewrites
|
|
213
213
|
* these files whether or not their dependencies changed, and re-installing on
|
|
214
|
-
* every commit would throw away
|
|
215
|
-
* having.
|
|
214
|
+
* every commit would throw away a tree that is already correct.
|
|
216
215
|
*
|
|
217
216
|
* ## Both files, not just the lockfile
|
|
218
217
|
*
|
|
@@ -222,7 +221,7 @@ async function firstPresent(fs, dir, names) {
|
|
|
222
221
|
* from `pnpm@9` to `pnpm@10`, or add a dependency without regenerating the lock,
|
|
223
222
|
* and every one of those changes what an install produces while leaving the
|
|
224
223
|
* lockfile byte-identical. Hashing the lockfile alone calls that a match, skips
|
|
225
|
-
* the install, and hands the
|
|
224
|
+
* the install, and hands the agent a `node_modules` that is quietly wrong —
|
|
226
225
|
* which surfaces as a missing module in a build, three tool calls later, with
|
|
227
226
|
* nothing pointing back at the install that never ran.
|
|
228
227
|
*
|
|
@@ -245,8 +244,8 @@ async function firstPresent(fs, dir, names) {
|
|
|
245
244
|
* {@link INSTALL_CONFIG_FILES} is hashed too, for the same reason `package.json`
|
|
246
245
|
* is: a lockfile says which versions, and `.npmrc` says how they are laid out.
|
|
247
246
|
* Flipping pnpm's `node-linker` to `hoisted` produces a different
|
|
248
|
-
* `node_modules` from a byte-identical lockfile, and a
|
|
249
|
-
* otherwise skip the reinstall that change requires.
|
|
247
|
+
* `node_modules` from a byte-identical lockfile, and a matching fingerprint
|
|
248
|
+
* would otherwise skip the reinstall that change requires.
|
|
250
249
|
*
|
|
251
250
|
* The residual is worth stating because it cannot be closed here: an override
|
|
252
251
|
* that *builds* — `npm ci && npm run build`, the documented example — is not
|
|
@@ -258,11 +257,9 @@ async function firstPresent(fs, dir, names) {
|
|
|
258
257
|
* ## This is only half of the skip condition
|
|
259
258
|
*
|
|
260
259
|
* A matching fingerprint means "the same install would produce the same tree".
|
|
261
|
-
* It does **not** mean the tree is there
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* the fingerprint alone skips the install that the empty tree needs most. The
|
|
265
|
-
* caller must also confirm `node_modules` is actually present.
|
|
260
|
+
* It does **not** mean the tree is there: it is on the container's disk, and
|
|
261
|
+
* goes with the container. The caller also needs a record that this container
|
|
262
|
+
* finished it — see `./install-job.ts`.
|
|
266
263
|
*/
|
|
267
264
|
export async function installFingerprint(fs, dir, resolution) {
|
|
268
265
|
if (resolution.kind !== "run")
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { DurableObject } from "cloudflare:workers";
|
|
2
|
+
import { type WorkspaceEgressPolicy, type WorkspaceStub } from "@cloudflare/computer";
|
|
3
|
+
import { ContainerBackend, type ContainerInstanceSize } from "@cloudflare/computer/backends/container";
|
|
4
|
+
import { type WorkspaceAdvisory } from "./advisory.js";
|
|
5
|
+
import type { InstallPlan, InstallState } from "./install.js";
|
|
6
|
+
import type { RepoGitResult } from "../repo/index.js";
|
|
7
|
+
/**
|
|
8
|
+
* A workspace: one Durable Object, one container, one repository.
|
|
9
|
+
*
|
|
10
|
+
* **Shared by every agent in a Worker that has a container.** Each subclasses
|
|
11
|
+
* {@link WorkspaceObjectBase}, supplies a {@link WorkspaceObjectConfig}, and
|
|
12
|
+
* inherits everything else. Keep that seam narrow: it is the complete answer to
|
|
13
|
+
* "what is different about this agent's container", and anything that does not
|
|
14
|
+
* fit through it becomes a second copy of this file.
|
|
15
|
+
*
|
|
16
|
+
* A directory inside the plugin rather than a subpath beside it, so that one
|
|
17
|
+
* capability stays one import path and a consumer cannot take the tools without
|
|
18
|
+
* the object they address. The bundle cost of that is nothing: `sideEffects` is
|
|
19
|
+
* false, so an agent that only calls `bash` carries no container backend and
|
|
20
|
+
* no isomorphic-git.
|
|
21
|
+
*
|
|
22
|
+
* What this file must not do is import the plugin barrel. The barrel re-exports
|
|
23
|
+
* this module, and the class below is built at import time — a cycle would
|
|
24
|
+
* evaluate its base as `undefined`. The imports above reach the leaf modules
|
|
25
|
+
* beside it instead.
|
|
26
|
+
*
|
|
27
|
+
* `@cloudflare/computer` pairs a SQLite-backed virtual filesystem in *this*
|
|
28
|
+
* object's storage with a container running `computerd`, which mounts it over
|
|
29
|
+
* FUSE at `/workspace`. Commands run against the same tree the Worker reads over
|
|
30
|
+
* RPC, and the tree outlives the container.
|
|
31
|
+
*
|
|
32
|
+
* **One repository per object**, because `@cloudflare/computer` is strictly 1 DO ↔ 1
|
|
33
|
+
* container: the id derives from caller *and* repository (see `workspaceName`),
|
|
34
|
+
* so two repositories for one caller are two objects and two containers.
|
|
35
|
+
*
|
|
36
|
+
* **It constructs `Workspace` rather than using `withWorkspace`.** The mixin
|
|
37
|
+
* stores the `Workspace` under a module-private symbol the package does not
|
|
38
|
+
* export, so a method on the object cannot reach it — and what the host is
|
|
39
|
+
* required to do needs exactly that: driving an outstanding pull (the library
|
|
40
|
+
* owns no alarm and schedules nothing of its own), and the direct `runtime`
|
|
41
|
+
* access the detached install needs. So this object owns the `Workspace` and
|
|
42
|
+
* implements the one method the mixin otherwise provides,
|
|
43
|
+
* `__getWorkspaceStub`. Callers outside see no difference.
|
|
44
|
+
*
|
|
45
|
+
* ## What is here, and what is a module next door
|
|
46
|
+
*
|
|
47
|
+
* This file is the Durable Object: the wake map, the deadlines, the RPC surface
|
|
48
|
+
* and the wiring between them. The work each of those drives has its own home,
|
|
49
|
+
* and each carries the reasoning for its own decisions:
|
|
50
|
+
*
|
|
51
|
+
* - `./install-job.ts` — when a dependency install runs, and every guard on it.
|
|
52
|
+
* - `./sync.ts` — the host's half of syncing, which nothing else will do.
|
|
53
|
+
* - `./ca-trust.ts` — making a container able to speak TLS.
|
|
54
|
+
* - `./git-identity.ts` — who a container's commits belong to.
|
|
55
|
+
* - `./git-host.ts` — the operations that hold the GitHub credential.
|
|
56
|
+
*
|
|
57
|
+
* Each takes a small explicit seam, and that is the test: a module that needed
|
|
58
|
+
* the whole object back would be a module that did not want extracting.
|
|
59
|
+
*/
|
|
60
|
+
/** Where every checkout lives, inside the container and in the VFS. */
|
|
61
|
+
export declare const WORKSPACE_DIR = "/workspace";
|
|
62
|
+
/**
|
|
63
|
+
* The Durable Object name for one caller's checkout of one repository.
|
|
64
|
+
*
|
|
65
|
+
* Exported because several places must agree on it and none can see the others:
|
|
66
|
+
* the plugin that resolves the stub, the agent that hands it down to sub-agents,
|
|
67
|
+
* and the cancellation path. A pipe rather than a slash, so the caller half
|
|
68
|
+
* cannot forge a repository boundary by containing one.
|
|
69
|
+
*
|
|
70
|
+
* `repo` is undefined only before the first `repo_clone` or `scratch_open` of a
|
|
71
|
+
* session — what a task works in is model-chosen, so there is genuinely nothing
|
|
72
|
+
* to key on until it has chosen. That window resolves to a caller-level
|
|
73
|
+
* workspace, which is never worked in: `beforeCheckout` sets the repository
|
|
74
|
+
* before any git runs, and `scratch_open` sets its sentinel before anything
|
|
75
|
+
* resolves a name.
|
|
76
|
+
*
|
|
77
|
+
* `repo` is not always a repository. A host may pass a sentinel through here as
|
|
78
|
+
* one — a scratchpad is modelled that way, which is what keys it to its own
|
|
79
|
+
* object and its own container. Anything that is not a GitHub name works, as long
|
|
80
|
+
* as no caller could clone something that collides with it.
|
|
81
|
+
*/
|
|
82
|
+
export declare function workspaceName(callerKey: string, repo?: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* How long a workspace survives without being used.
|
|
85
|
+
*
|
|
86
|
+
* A Durable Object is **never** reclaimed by the platform, and a namespace
|
|
87
|
+
* cannot be enumerated from a Worker, so nothing else is coming to clean up.
|
|
88
|
+
* Source-only workspaces are small (6.3 MB for slack-gatekeeper), which makes
|
|
89
|
+
* this hygiene rather than cost control — but unbounded hygiene is still
|
|
90
|
+
* unbounded.
|
|
91
|
+
*
|
|
92
|
+
* Exported because a host keeping work in a workspace has to say how long that
|
|
93
|
+
* work survives untouched.
|
|
94
|
+
*/
|
|
95
|
+
export declare const IDLE_RECLAIM_MS: number;
|
|
96
|
+
/**
|
|
97
|
+
* `work`, refused once `ms` pass without it settling. The work itself goes on;
|
|
98
|
+
* only the wait for it ends — see {@link READY_DEADLINE_MS}.
|
|
99
|
+
*/
|
|
100
|
+
export declare function readyWithin(work: Promise<void>, ms: number): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Named rather than the anonymous class expression an app would write here.
|
|
103
|
+
*
|
|
104
|
+
* This package emits declarations, and `DurableObject` brings `ctx` and `env` in
|
|
105
|
+
* protected — which makes the mixin's return type unnameable in a `.d.ts` and
|
|
106
|
+
* fails the build with TS4094. A named base gives the emitted type something to
|
|
107
|
+
* refer to. It costs one identifier and is invisible to a consumer.
|
|
108
|
+
*/
|
|
109
|
+
declare class WorkspaceContainerHost extends DurableObject<Cloudflare.Env> {
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* `withWorkspaceContainer` adds one method, `getWorkspaceContainer()`, over
|
|
113
|
+
* `this.ctx.container` — the runtime's own container handle, started with the
|
|
114
|
+
* image and instance size this object asks for. Stopping it is this object's
|
|
115
|
+
* job — see {@link CONTAINER_IDLE_MS}.
|
|
116
|
+
*/
|
|
117
|
+
declare const WorkspaceContainerBase: typeof WorkspaceContainerHost & (new (...args: any[]) => WorkspaceContainerHost & {
|
|
118
|
+
getWorkspaceContainer(): import("@cloudflare/computer/backends/container").WorkspaceContainerAPI;
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* The GitHub credential, and who a commit made on this side is attributed to.
|
|
122
|
+
*
|
|
123
|
+
* Config rather than an env read, because this class cannot name a consumer's
|
|
124
|
+
* ambient `Env` — it is an interface `wrangler types` generates into *their*
|
|
125
|
+
* app, and a plugin that named it would compile only against the one Worker that
|
|
126
|
+
* happened to spell its bindings that way.
|
|
127
|
+
*
|
|
128
|
+
* It is also the honest direction. These are the repo plugin's secrets, declared
|
|
129
|
+
* by that plugin's `requires.secrets`, and a workspace holds them only because
|
|
130
|
+
* git runs on this side of the container boundary — see
|
|
131
|
+
* {@link file://./git-host.ts} for why it has to.
|
|
132
|
+
*/
|
|
133
|
+
export interface WorkspaceGitConfig {
|
|
134
|
+
/**
|
|
135
|
+
* The **name** of the binding holding the GitHub credential — never the
|
|
136
|
+
* credential itself.
|
|
137
|
+
*
|
|
138
|
+
* `workspaceConfig()` is an ordinary method on the prototype, and on a Durable
|
|
139
|
+
* Object `protected` is a typechecker's opinion rather than a runtime
|
|
140
|
+
* boundary: anything holding the namespace can call it. A config carrying the
|
|
141
|
+
* token would therefore hand it to any caller that asked — exactly what
|
|
142
|
+
* {@link file://./git-host.ts} exists to prevent, since the credential is
|
|
143
|
+
* supposed to be unreachable from anywhere but the moment git authenticates.
|
|
144
|
+
*
|
|
145
|
+
* So the config names the binding and the object reads it. Naming a secret by
|
|
146
|
+
* string is what the plugin contract's `requires.secrets` already does, and it
|
|
147
|
+
* is the only form available here: this package cannot name a consumer's
|
|
148
|
+
* ambient `Env`.
|
|
149
|
+
*
|
|
150
|
+
* An unset binding reads `undefined`, which means an unauthenticated request
|
|
151
|
+
* rather than a throw — that is what lets a workspace with no token still
|
|
152
|
+
* clone a public repository.
|
|
153
|
+
*/
|
|
154
|
+
tokenBinding: string;
|
|
155
|
+
/**
|
|
156
|
+
* Who a commit is attributed to — on this side, and in the container.
|
|
157
|
+
*
|
|
158
|
+
* Two things read it: this object's git client, and `./git-identity.ts`,
|
|
159
|
+
* which writes it into the container's system config so a repository the
|
|
160
|
+
* container made for itself is attributed rather than nameless.
|
|
161
|
+
*
|
|
162
|
+
* **The repo plugin is not one of them.** It resolves `RepoConfig.author`
|
|
163
|
+
* independently, with a generic fallback of its own, and nothing in either
|
|
164
|
+
* package checks the two agree — so a commit *can* be attributed differently
|
|
165
|
+
* depending on which side made it, and the only thing preventing that is a
|
|
166
|
+
* consumer answering both from one place. A deployment's fallback for an
|
|
167
|
+
* unset binding belongs where that binding is read; this takes the resolved
|
|
168
|
+
* answer.
|
|
169
|
+
*/
|
|
170
|
+
author: {
|
|
171
|
+
name: string;
|
|
172
|
+
email: string;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* What one workspace object does not share with the next.
|
|
177
|
+
*
|
|
178
|
+
* Everything else about a workspace is identical between agents, which is why
|
|
179
|
+
* this interface is short and why it is worth having at all: a seam this narrow
|
|
180
|
+
* makes "what is different about this agent's container" a question with a
|
|
181
|
+
* complete answer in one place.
|
|
182
|
+
*/
|
|
183
|
+
export interface WorkspaceObjectConfig {
|
|
184
|
+
/**
|
|
185
|
+
* The wrangler Durable Object binding this class is bound as.
|
|
186
|
+
*
|
|
187
|
+
* Not cosmetic and not derivable: `computerd` dials **back** through it. The
|
|
188
|
+
* backend builds the container's loopback from this name plus the object id,
|
|
189
|
+
* so a wrong one produces a container that starts, mounts nothing and fails
|
|
190
|
+
* at the first command with no mention of a binding.
|
|
191
|
+
*/
|
|
192
|
+
binding: string;
|
|
193
|
+
/**
|
|
194
|
+
* Where this workspace's container may send traffic, and through what.
|
|
195
|
+
*
|
|
196
|
+
* `direct` is the plain behaviour: the container's own network position.
|
|
197
|
+
* `http-gateway` routes **everything** through a `Fetcher` this Worker
|
|
198
|
+
* supplies, which is what puts the Worker on the model path — see
|
|
199
|
+
* `@dynamicagents/plugins/claude-code`.
|
|
200
|
+
*
|
|
201
|
+
* **Required in practice, and its absence is silent.** It is a policy that
|
|
202
|
+
* defaults to `{ mode: "none" }`, and the backend derives the container's
|
|
203
|
+
* network flag from it. Omit it and the container
|
|
204
|
+
* comes up with no network at all: the workspace mounts, commands run, and
|
|
205
|
+
* the install dies on a registry it cannot reach with nothing naming egress
|
|
206
|
+
* as the cause.
|
|
207
|
+
*/
|
|
208
|
+
egress: WorkspaceEgressPolicy;
|
|
209
|
+
/** How this deployment installs dependencies for this agent's checkouts. */
|
|
210
|
+
installPlan: InstallPlan;
|
|
211
|
+
/** Log prefix — `coding-workspace`, `claude-coordinator-workspace`. */
|
|
212
|
+
label: string;
|
|
213
|
+
/**
|
|
214
|
+
* How long this agent's container stays up after the last command **started**.
|
|
215
|
+
*
|
|
216
|
+
* A per-agent value because the invariant on {@link CONTAINER_IDLE_MS} — it
|
|
217
|
+
* must exceed the longest command the shell allows — is an invariant about the
|
|
218
|
+
* *agent*, and the two differ by a factor of four. The coder's longest command
|
|
219
|
+
* is a tool call; `claude-coordinator`'s is a whole `claude -p` session that runs
|
|
220
|
+
* detached for its entire timeout.
|
|
221
|
+
*
|
|
222
|
+
* Omit it for the default. Raise it, never lower it, and raise it whenever the
|
|
223
|
+
* agent's longest command grows.
|
|
224
|
+
*/
|
|
225
|
+
containerIdleMs?: number;
|
|
226
|
+
/**
|
|
227
|
+
* The size this agent's container starts at — a tier such as `standard-2`, or
|
|
228
|
+
* `{ vcpu, memoryMib, diskMb }`.
|
|
229
|
+
*
|
|
230
|
+
* Requested on every start, and part of what a running container is matched
|
|
231
|
+
* against: change it and the next connect replaces a container started at the
|
|
232
|
+
* old size. Which size, and why there is no default: {@link file://./README.md}.
|
|
233
|
+
*/
|
|
234
|
+
instance: ContainerInstanceSize;
|
|
235
|
+
/** The GitHub credential and commit identity — see {@link WorkspaceGitConfig}. */
|
|
236
|
+
git: WorkspaceGitConfig;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* One caller's checkout of one repository, and the container that mounts it.
|
|
240
|
+
*
|
|
241
|
+
* Abstract because a Durable Object class takes no constructor arguments, so
|
|
242
|
+
* per-agent configuration cannot arrive that way. {@link workspaceConfig} is the
|
|
243
|
+
* seam, for the same reason Think's own hooks are methods rather than options.
|
|
244
|
+
*
|
|
245
|
+
* ## Why `backend` and `#workspace` are lazy
|
|
246
|
+
*
|
|
247
|
+
* **Base class fields run before subclass fields**, so as plain fields they
|
|
248
|
+
* would read `undefined` from any `workspaceConfig()` that touches a subclass
|
|
249
|
+
* field — which `ClaudeCoordinatorWorkspace`'s does, for its credential store.
|
|
250
|
+
* Memoised getters remove the hazard rather than documenting it, which is what
|
|
251
|
+
* lets a subclass implement the seam however it likes.
|
|
252
|
+
*/
|
|
253
|
+
export declare abstract class WorkspaceObjectBase<TEnv extends Cloudflare.Env = Cloudflare.Env> extends WorkspaceContainerBase {
|
|
254
|
+
#private;
|
|
255
|
+
/**
|
|
256
|
+
* This Worker's bindings, as the host's own generated `Env` spells them.
|
|
257
|
+
*
|
|
258
|
+
* Re-declared rather than inherited so a subclass reading a binding of its own
|
|
259
|
+
* — `claude-coordinator`'s reaches for its credential pool — gets its real type
|
|
260
|
+
* instead of the base's. `declare` because the field is the runtime's; this
|
|
261
|
+
* only narrows what TypeScript believes about it.
|
|
262
|
+
*/
|
|
263
|
+
readonly env: TEnv;
|
|
264
|
+
/**
|
|
265
|
+
* Everything this agent's workspace does differently. Called once, lazily.
|
|
266
|
+
*
|
|
267
|
+
* Read through {@link #cfg}, never directly: an implementation may build
|
|
268
|
+
* something real — `claude-coordinator`'s constructs its egress gateway — and this
|
|
269
|
+
* is consulted on the busiest path in the object.
|
|
270
|
+
*/
|
|
271
|
+
protected abstract workspaceConfig(): WorkspaceObjectConfig;
|
|
272
|
+
get backend(): ContainerBackend;
|
|
273
|
+
/**
|
|
274
|
+
* What `getWorkspace(stub)` calls from outside this object.
|
|
275
|
+
*
|
|
276
|
+
* The one piece of `withWorkspace` reimplemented here — see the file comment
|
|
277
|
+
* for why the mixin is not used. `ready()` first, because the stub is only
|
|
278
|
+
* meaningful once the workspace has opened its store.
|
|
279
|
+
*/
|
|
280
|
+
__getWorkspaceStub(): Promise<WorkspaceStub>;
|
|
281
|
+
/**
|
|
282
|
+
* The same workspace, for a caller that will only touch the filesystem.
|
|
283
|
+
*
|
|
284
|
+
* **The container is not started, and that is the whole difference.** The
|
|
285
|
+
* filesystem is this object's own SQLite, so a read or write of it needs no
|
|
286
|
+
* container and an absent one cannot block it. {@link __getWorkspaceStub} adds
|
|
287
|
+
* `#ready()`, and the first command in a fresh container re-pushes the whole
|
|
288
|
+
* tree before it runs, with `timeoutMs` reaching the spawned process and
|
|
289
|
+
* nothing earlier.
|
|
290
|
+
*
|
|
291
|
+
* A stub cannot enforce what the caller does with it: `runtime.exec` on this
|
|
292
|
+
* one would run against a container with no CA. The exec paths take the other
|
|
293
|
+
* method — see `openWorkspaceFs` in `./open.ts`.
|
|
294
|
+
*/
|
|
295
|
+
__getWorkspaceFsStub(): Promise<WorkspaceStub>;
|
|
296
|
+
/**
|
|
297
|
+
* Clone, fetch and push — the operations that need the GitHub token.
|
|
298
|
+
*
|
|
299
|
+
* The work is `./git-host.ts`, which owns why the credential stays on this
|
|
300
|
+
* side of the boundary. What stays here is what only this object can do: the
|
|
301
|
+
* entry-point bookkeeping, and making the workspace current before git reads
|
|
302
|
+
* it.
|
|
303
|
+
*/
|
|
304
|
+
gitClone(req: {
|
|
305
|
+
url: string;
|
|
306
|
+
dir: string;
|
|
307
|
+
allowedHosts: string[];
|
|
308
|
+
branch?: string;
|
|
309
|
+
depth?: number;
|
|
310
|
+
}): Promise<RepoGitResult>;
|
|
311
|
+
gitFetch(req: {
|
|
312
|
+
url: string;
|
|
313
|
+
dir: string;
|
|
314
|
+
allowedHosts: string[];
|
|
315
|
+
depth?: number;
|
|
316
|
+
}): Promise<RepoGitResult>;
|
|
317
|
+
gitPush(req: {
|
|
318
|
+
url: string;
|
|
319
|
+
dir: string;
|
|
320
|
+
branch: string;
|
|
321
|
+
allowedHosts: string[];
|
|
322
|
+
}): Promise<RepoGitResult>;
|
|
323
|
+
/**
|
|
324
|
+
* A checkout's `origin`, from this object's storage. Starts no container and
|
|
325
|
+
* moves no deadline, like {@link checkoutDir}: a pull request polled for its
|
|
326
|
+
* review is not the workspace in use.
|
|
327
|
+
*/
|
|
328
|
+
gitOrigin(dir: string): Promise<string | undefined>;
|
|
329
|
+
/**
|
|
330
|
+
* Start this checkout's dependency install, and return without waiting.
|
|
331
|
+
*
|
|
332
|
+
* Called from `repo_clone` through the repo plugin's `afterCheckout` hook, so
|
|
333
|
+
* it runs inside a model turn — see `./install-job.ts` for why the drain's owner
|
|
334
|
+
* is the thing that matters here.
|
|
335
|
+
*/
|
|
336
|
+
startInstall(req: {
|
|
337
|
+
dir: string;
|
|
338
|
+
repo?: string;
|
|
339
|
+
}): Promise<InstallState>;
|
|
340
|
+
/**
|
|
341
|
+
* Stop the container, and **keep everything on disk**.
|
|
342
|
+
*
|
|
343
|
+
* The counterpart to {@link reclaimIfIdle}, and the difference is the whole
|
|
344
|
+
* reason both exist. A reclaim is for a workspace nobody wants again: it empties
|
|
345
|
+
* storage, so the checkout and the dependency tree go with the container. This is
|
|
346
|
+
* for a workspace that will be worked in again — the next task on the same
|
|
347
|
+
* repository has to skip the clone and the install, which is the cost the whole
|
|
348
|
+
* design is arranged around.
|
|
349
|
+
*
|
|
350
|
+
* **Why a caller needs this at all.** The idle deadline would eventually do it,
|
|
351
|
+
* but it cannot be tuned down to meet a cost target: `containerIdleMs` must
|
|
352
|
+
* exceed the longest command the agent allows, and a container that outlives its
|
|
353
|
+
* work by that much bills for the difference. So a host that knows the work is
|
|
354
|
+
* finished says so, and the deadline goes back to being a backstop for the case
|
|
355
|
+
* where nothing got to say anything.
|
|
356
|
+
*
|
|
357
|
+
* Deliberately does **not** touch: `#touch()` would push the very deadline this
|
|
358
|
+
* is standing in for.
|
|
359
|
+
*
|
|
360
|
+
* `released: false` means the container is still up and the standing deadline
|
|
361
|
+
* still owns it — an install is running, or a pull is still moving blocks. Both
|
|
362
|
+
* resolve themselves: the drain re-arms the deadline to come back and finish,
|
|
363
|
+
* which is the same path `#onContainerIdle` takes. Under a {@link hold} the
|
|
364
|
+
* release is deferred instead, to the last `unhold`.
|
|
365
|
+
*/
|
|
366
|
+
releaseContainer(): Promise<{
|
|
367
|
+
released: boolean;
|
|
368
|
+
}>;
|
|
369
|
+
/**
|
|
370
|
+
* Keep the container up for work this object cannot see, until `untilMs` or
|
|
371
|
+
* {@link unhold}.
|
|
372
|
+
*
|
|
373
|
+
* A Claude Code session a host starts here runs detached: nothing calls in
|
|
374
|
+
* while it works, and the runtime cannot list
|
|
375
|
+
* what is running. So a host done with the workspace cannot tell that the
|
|
376
|
+
* container is not, and {@link releaseContainer} would stop the session with
|
|
377
|
+
* it. A hold is the host saying so. It defers a release; it is not use, so it
|
|
378
|
+
* does not touch, and the idle deadline already outlasts a session.
|
|
379
|
+
*
|
|
380
|
+
* `untilMs` bounds a hold whose `unhold` never comes — a run cut between its
|
|
381
|
+
* start and its end. Past it the hold is ignored.
|
|
382
|
+
*/
|
|
383
|
+
hold(key: string, untilMs: number): Promise<void>;
|
|
384
|
+
/**
|
|
385
|
+
* The work a {@link hold} stood for is over. When it was the last hold, a
|
|
386
|
+
* release asked for while it stood happens now — unless the workspace was
|
|
387
|
+
* used since, in which case the next release is asked for by whoever used it.
|
|
388
|
+
*/
|
|
389
|
+
unhold(key: string): Promise<{
|
|
390
|
+
released: boolean;
|
|
391
|
+
}>;
|
|
392
|
+
/**
|
|
393
|
+
* Throw this workspace away if nothing has touched it for `maxIdleMs`.
|
|
394
|
+
*
|
|
395
|
+
* Re-checks the clock rather than trusting the caller: the alarm may have been
|
|
396
|
+
* armed a week ago, and a use since then must win. Safe to call from anywhere
|
|
397
|
+
* for the same reason, which is what lets the agent's cron poke it as a
|
|
398
|
+
* backstop without needing to know anything.
|
|
399
|
+
*/
|
|
400
|
+
reclaimIfIdle(maxIdleMs?: number): Promise<{
|
|
401
|
+
reclaimed: boolean;
|
|
402
|
+
idleMs: number;
|
|
403
|
+
bytes: number;
|
|
404
|
+
}>;
|
|
405
|
+
/**
|
|
406
|
+
* Record what was checked out here, and say whether it is actually visible.
|
|
407
|
+
*
|
|
408
|
+
* **The single writer of {@link CHECKOUT_KEY}**, called by the two paths that
|
|
409
|
+
* know a checkout landed: the repo plugin's `afterCheckout` hook, which fires
|
|
410
|
+
* only once a tree is established, and `scratch_open`, which fires once
|
|
411
|
+
* `git init` has exited 0.
|
|
412
|
+
*
|
|
413
|
+
* **Why the checkout is recorded apart from the install**, which call sites
|
|
414
|
+
* point here for rather than restating: an install is conditional and a
|
|
415
|
+
* checkout is not. `resolveInstallCommand` skips a checkout it finds nothing
|
|
416
|
+
* to install in — any without a `package.json` — so a path written only as
|
|
417
|
+
* part of an install is missing exactly there. What that costs is not an
|
|
418
|
+
* optimisation: {@link checkoutDir} is how a delegated session is told where
|
|
419
|
+
* to work, so a repository can clone perfectly, report itself correctly
|
|
420
|
+
* through the repo tools, and never be worked in. The install keeps its own
|
|
421
|
+
* context for the mirror-image reason — what to re-run on a cold container has
|
|
422
|
+
* a different lifetime from what is on disk.
|
|
423
|
+
*
|
|
424
|
+
* Returns the probe as well as the path, so a caller learns in one round trip
|
|
425
|
+
* whether the tree is visible rather than discovering it a delegation later.
|
|
426
|
+
*/
|
|
427
|
+
noteCheckout(req: {
|
|
428
|
+
dir: string;
|
|
429
|
+
repo?: string;
|
|
430
|
+
kind: "repo" | "scratch";
|
|
431
|
+
}): Promise<{
|
|
432
|
+
dir: string;
|
|
433
|
+
present: boolean;
|
|
434
|
+
}>;
|
|
435
|
+
/**
|
|
436
|
+
* Where the work is — a directory holding a git repository, or nothing.
|
|
437
|
+
*
|
|
438
|
+
* Two callers depend on that being the meaning rather than "a path somebody
|
|
439
|
+
* wrote down once": a Claude Code session takes it as its cwd, and
|
|
440
|
+
* `discardWorkingTree` runs `git reset --hard` in it. Both want the same
|
|
441
|
+
* question answered, and neither can recover from a confident wrong answer.
|
|
442
|
+
*
|
|
443
|
+
* So it is **probed, not remembered**. A record is where to look; `.git` being
|
|
444
|
+
* there is what makes the answer true. That costs one local read of this
|
|
445
|
+
* object's own SQLite — everything under the checkout is durable, `.git`
|
|
446
|
+
* included — and it is what turns a stale record into `undefined` instead of
|
|
447
|
+
* into a session started in a directory that is no longer a checkout.
|
|
448
|
+
*
|
|
449
|
+
* The record is the only source. A workspace that has never recorded one
|
|
450
|
+
* answers `undefined` until its next checkout does, which `repo_clone` does on
|
|
451
|
+
* any tree it can fetch and reset.
|
|
452
|
+
*/
|
|
453
|
+
checkoutDir(): Promise<string | undefined>;
|
|
454
|
+
/**
|
|
455
|
+
* Everything currently true about this workspace that a caller must not assume
|
|
456
|
+
* away — the array `bash`, `edit` and the workspace's writes all consult.
|
|
457
|
+
*
|
|
458
|
+
* The policy is not here. `deriveAdvisories` decides which facts matter and
|
|
459
|
+
* how they are worded; this method gathers what only the object can see and
|
|
460
|
+
* hands it over. That split is why a host cannot get the severity of its own
|
|
461
|
+
* workspace wrong.
|
|
462
|
+
*
|
|
463
|
+
* **Nothing that starts a long job belongs on this path**, and the rule is
|
|
464
|
+
* sharper here than anywhere else in the object because every tool call reads
|
|
465
|
+
* it. `startInstall` in particular must never be reached from here: it hands
|
|
466
|
+
* its drain to `ctx.waitUntil`, whose lifetime is the invocation's, and an
|
|
467
|
+
* invocation on this path is a tool call that returns in milliseconds. The
|
|
468
|
+
* drain outlives its owner, dies mid-`npm ci` with "WritableStream RPC stub
|
|
469
|
+
* was disposed without calling close()", and leaves a half-written tree.
|
|
470
|
+
*
|
|
471
|
+
* A missing dependency tree arms an install here, which runs in the alarm,
|
|
472
|
+
* which owns no request and outlives every RPC. Here because a command reads
|
|
473
|
+
* this before its `#ready()` starts a container, so the gate holds it for the
|
|
474
|
+
* install rather than letting it outrun one.
|
|
475
|
+
*/
|
|
476
|
+
advisories(): Promise<readonly WorkspaceAdvisory[]>;
|
|
477
|
+
/**
|
|
478
|
+
* `computerd`'s outbound WebSocket upgrade, on its way back in.
|
|
479
|
+
*
|
|
480
|
+
* The container dials the loopback rather than the other way round, so this
|
|
481
|
+
* object is the server for its own container's capnweb session. Everything
|
|
482
|
+
* else on this object is RPC; this is the only HTTP it speaks.
|
|
483
|
+
*/
|
|
484
|
+
fetch(request: Request): Promise<Response>;
|
|
485
|
+
/**
|
|
486
|
+
* Every durable wake-up this object has, dispatched from the one alarm.
|
|
487
|
+
*
|
|
488
|
+
* **This must not throw.** The runtime retries a failing alarm handler a
|
|
489
|
+
* bounded number of times and then stops for good — so a throw here would
|
|
490
|
+
* eventually take every *future* wake-up down with it, permanently, and the
|
|
491
|
+
* only symptom is that nothing ever happens again.
|
|
492
|
+
*
|
|
493
|
+
* Nothing else belongs here. Each reason to wake is a registered callback the
|
|
494
|
+
* scheduler dispatches by name, so this method neither matches on keys nor
|
|
495
|
+
* catches per-callback failures — the scheduler retries a failing callback and
|
|
496
|
+
* reports one that fails for good through `onError`. Nor does it sweep for a
|
|
497
|
+
* schedule that neither rescheduled nor cleared itself: a one-shot row is
|
|
498
|
+
* dropped when it runs, so it cannot stay due forever.
|
|
499
|
+
*/
|
|
500
|
+
alarm(): Promise<void>;
|
|
501
|
+
}
|
|
502
|
+
export {};
|