@dynamicagents/plugins 0.12.0 → 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.
Files changed (130) hide show
  1. package/README.md +58 -77
  2. package/dist/browser/index.d.ts +0 -2
  3. package/dist/browser/index.js +21 -27
  4. package/dist/claude-code/cards.d.ts +53 -0
  5. package/dist/claude-code/cards.js +272 -0
  6. package/dist/claude-code/config.d.ts +28 -63
  7. package/dist/claude-code/config.js +0 -11
  8. package/dist/claude-code/egress.d.ts +8 -1
  9. package/dist/claude-code/egress.js +42 -4
  10. package/dist/claude-code/events.d.ts +106 -9
  11. package/dist/claude-code/events.js +201 -56
  12. package/dist/claude-code/github.d.ts +39 -0
  13. package/dist/claude-code/github.js +82 -0
  14. package/dist/claude-code/index.d.ts +16 -90
  15. package/dist/claude-code/index.js +14 -276
  16. package/dist/claude-code/model.d.ts +152 -0
  17. package/dist/claude-code/model.js +342 -0
  18. package/dist/claude-code/run.d.ts +209 -112
  19. package/dist/claude-code/run.js +339 -138
  20. package/dist/claude-code/session.d.ts +131 -0
  21. package/dist/claude-code/session.js +285 -0
  22. package/dist/claude-code/spec.d.ts +46 -0
  23. package/dist/claude-code/spec.js +83 -0
  24. package/dist/claude-code/verified.d.ts +15 -0
  25. package/dist/claude-code/verified.js +15 -0
  26. package/dist/computer/context.d.ts +1 -5
  27. package/dist/computer/context.js +7 -10
  28. package/dist/computer/file-lock.js +5 -4
  29. package/dist/computer/gate.d.ts +4 -4
  30. package/dist/computer/gate.js +5 -5
  31. package/dist/computer/index.d.ts +40 -258
  32. package/dist/computer/index.js +71 -324
  33. package/dist/computer/paths.d.ts +36 -16
  34. package/dist/computer/paths.js +42 -29
  35. package/dist/computer/proxy.d.ts +45 -0
  36. package/dist/computer/proxy.js +234 -0
  37. package/dist/computer/render.d.ts +1 -40
  38. package/dist/computer/render.js +2 -68
  39. package/dist/computer/tools-exec.js +12 -13
  40. package/dist/computer/tools-file.d.ts +9 -1
  41. package/dist/computer/tools-file.js +22 -82
  42. package/dist/computer/{tools-find.d.ts → tools-grep.d.ts} +1 -1
  43. package/dist/computer/tools-grep.js +129 -0
  44. package/dist/repo/checkout.d.ts +4 -5
  45. package/dist/repo/checkout.js +31 -8
  46. package/dist/repo/context.d.ts +35 -9
  47. package/dist/repo/context.js +101 -49
  48. package/dist/repo/index.d.ts +73 -28
  49. package/dist/repo/index.js +112 -96
  50. package/dist/repo/tools-checks.d.ts +3 -0
  51. package/dist/repo/tools-checks.js +112 -0
  52. package/dist/repo/tools-clone.js +1 -1
  53. package/dist/repo/tools-pr.d.ts +14 -0
  54. package/dist/repo/{tools-forge.js → tools-pr.js} +53 -36
  55. package/dist/repo/tools-review.d.ts +9 -0
  56. package/dist/repo/tools-review.js +67 -47
  57. package/dist/repo/tools-worktree.js +144 -60
  58. package/dist/repo/tools-worktrees.d.ts +12 -0
  59. package/dist/repo/tools-worktrees.js +48 -0
  60. package/dist/repo/url.d.ts +2 -2
  61. package/dist/repo/url.js +7 -7
  62. package/dist/scratch/index.d.ts +4 -4
  63. package/dist/scratch/index.js +12 -11
  64. package/dist/{computer → workspace}/advisory.d.ts +4 -4
  65. package/dist/{computer → workspace}/advisory.js +2 -2
  66. package/dist/{computer/host → workspace}/ca-trust.d.ts +8 -1
  67. package/dist/{computer/host → workspace}/ca-trust.js +12 -3
  68. package/dist/{computer/host → workspace}/container-fault.d.ts +3 -3
  69. package/dist/{computer/host → workspace}/container-fault.js +23 -8
  70. package/dist/workspace/exec.d.ts +94 -0
  71. package/dist/workspace/exec.js +153 -0
  72. package/dist/workspace/format.d.ts +36 -0
  73. package/dist/workspace/format.js +57 -0
  74. package/dist/{computer/host → workspace}/git-host.d.ts +21 -4
  75. package/dist/{computer/host → workspace}/git-host.js +72 -2
  76. package/dist/{computer/host → workspace}/git-identity.d.ts +1 -1
  77. package/dist/{computer/host → workspace}/git-identity.js +1 -1
  78. package/dist/workspace/index.d.ts +43 -47
  79. package/dist/workspace/index.js +52 -112
  80. package/dist/{computer/host → workspace}/install-job.d.ts +4 -4
  81. package/dist/{computer/host → workspace}/install-job.js +276 -176
  82. package/dist/{computer → workspace}/install.d.ts +8 -8
  83. package/dist/{computer → workspace}/install.js +3 -3
  84. package/dist/{computer/host/workspace.d.ts → workspace/object.d.ts} +105 -27
  85. package/dist/{computer/host/workspace.js → workspace/object.js} +217 -48
  86. package/dist/workspace/open.d.ts +95 -0
  87. package/dist/workspace/open.js +63 -0
  88. package/dist/workspace/read.d.ts +16 -0
  89. package/dist/workspace/read.js +22 -0
  90. package/dist/{computer → workspace}/shell.d.ts +5 -5
  91. package/dist/{computer → workspace}/shell.js +6 -6
  92. package/dist/{computer/host → workspace}/wake.d.ts +1 -1
  93. package/package.json +16 -36
  94. package/dist/arc-agi/analysis.d.ts +0 -322
  95. package/dist/arc-agi/analysis.js +0 -802
  96. package/dist/arc-agi/client.d.ts +0 -82
  97. package/dist/arc-agi/client.js +0 -113
  98. package/dist/arc-agi/game-tools.d.ts +0 -24
  99. package/dist/arc-agi/game-tools.js +0 -26
  100. package/dist/arc-agi/index.d.ts +0 -40
  101. package/dist/arc-agi/index.js +0 -175
  102. package/dist/arc-agi/main-agent.d.ts +0 -24
  103. package/dist/arc-agi/main-agent.js +0 -62
  104. package/dist/arc-agi/recipe.d.ts +0 -48
  105. package/dist/arc-agi/recipe.js +0 -97
  106. package/dist/arc-agi/scorecard.d.ts +0 -118
  107. package/dist/arc-agi/scorecard.js +0 -163
  108. package/dist/arc-agi/soul.d.ts +0 -29
  109. package/dist/arc-agi/soul.js +0 -79
  110. package/dist/arc-agi/store.d.ts +0 -153
  111. package/dist/arc-agi/store.js +0 -215
  112. package/dist/arc-agi/tools.d.ts +0 -36
  113. package/dist/arc-agi/tools.js +0 -574
  114. package/dist/arc-agi/types.d.ts +0 -211
  115. package/dist/arc-agi/types.js +0 -19
  116. package/dist/claude-code/recipe.d.ts +0 -53
  117. package/dist/claude-code/recipe.js +0 -100
  118. package/dist/computer/read.d.ts +0 -133
  119. package/dist/computer/read.js +0 -200
  120. package/dist/computer/tools-find.js +0 -216
  121. package/dist/recall/index.d.ts +0 -105
  122. package/dist/recall/index.js +0 -181
  123. package/dist/repo/tools-forge.d.ts +0 -4
  124. package/dist/triage/index.d.ts +0 -360
  125. package/dist/triage/index.js +0 -311
  126. /package/dist/{computer/host → workspace}/container-deps.d.ts +0 -0
  127. /package/dist/{computer/host → workspace}/container-deps.js +0 -0
  128. /package/dist/{computer/host → workspace}/sync.d.ts +0 -0
  129. /package/dist/{computer/host → workspace}/sync.js +0 -0
  130. /package/dist/{computer/host → workspace}/wake.js +0 -0
@@ -1,7 +1,7 @@
1
- import type { RepoGitResult } from "../../repo/index.js";
1
+ import type { RepoGitResult } from "../repo/index.js";
2
2
  import type { GitClient } from "@cloudflare/computer/git";
3
3
  /**
4
- * The git operations that need the forge token, run on this side.
4
+ * The git operations that need the GitHub token, run on this side.
5
5
  *
6
6
  * They are here rather than on the `WorkspaceStub` the plugin already holds
7
7
  * because `WorkspaceGitStub` exposes only `cli(argv)` across a Durable Object
@@ -22,12 +22,12 @@ import type { GitClient } from "@cloudflare/computer/git";
22
22
  * the workspace object's job and it must happen first: git here reads that
23
23
  * object's SQLite, so a commit made in the container that has not been pulled
24
24
  * yet is a commit this code cannot see. The workspace object's `gitClone`,
25
- * `gitFetch` and `gitPush` in `./workspace.ts` are where that is settled.
25
+ * `gitFetch` and `gitPush` in `./object.ts` are where that is settled.
26
26
  */
27
27
  export interface GitHostDeps {
28
28
  /** The workspace's own client — isomorphic-git over the local store. */
29
29
  git: () => GitClient;
30
- /** The forge credential, read at the moment it would be handed over. */
30
+ /** The GitHub credential, read at the moment it would be handed over. */
31
31
  token: () => string | undefined;
32
32
  tag: () => string;
33
33
  }
@@ -53,6 +53,11 @@ export declare class WorkspaceGitHost {
53
53
  allowedHosts: string[];
54
54
  depth?: number;
55
55
  }): Promise<RepoGitResult>;
56
+ /**
57
+ * `remote.origin.url` from the local store, or `undefined` where there is no
58
+ * checkout or no origin. Reads only, and needs no credential.
59
+ */
60
+ origin(dir: string): Promise<string | undefined>;
56
61
  push(req: {
57
62
  url: string;
58
63
  dir: string;
@@ -60,3 +65,15 @@ export declare class WorkspaceGitHost {
60
65
  allowedHosts: string[];
61
66
  }): Promise<RepoGitResult>;
62
67
  }
68
+ /**
69
+ * A git error's message with the chain of causes behind it.
70
+ *
71
+ * `@cloudflare/computer/git` wraps isomorphic-git's errors, and classifies some
72
+ * by a heuristic: any failure whose message mentions `.git` beside a missing
73
+ * path or a ref it could not find is reported as "not a git repository" — a
74
+ * sentence that is wrong about a checkout that plainly is one, and names
75
+ * neither the file nor the ref. What actually failed is only on `cause`, which
76
+ * a Durable Object boundary does not carry, so it is folded into the message
77
+ * here while the error is still itself.
78
+ */
79
+ export declare function describeGitError(err: unknown): string;
@@ -1,3 +1,18 @@
1
+ /**
2
+ * What the container's own `git init` would write, over isomorphic-git's.
3
+ *
4
+ * isomorphic-git's `init` writes `core.*` for a filesystem without symlinks or
5
+ * an executable bit that ignores case, and the container's git obeys it. With
6
+ * `symlinks = false`, a command there that rewrites the tree — starter puts a
7
+ * worktree on its branch with `checkout -f` — writes each link as a file holding
8
+ * its target, and `status` calls that clean. With `filemode = false`, a script
9
+ * made executable is committed as 100644. The store models all three.
10
+ */
11
+ const CONTAINER_CORE_CONFIG = {
12
+ "core.symlinks": true,
13
+ "core.filemode": true,
14
+ "core.ignorecase": false
15
+ };
1
16
  export class WorkspaceGitHost {
2
17
  deps;
3
18
  constructor(deps) {
@@ -20,6 +35,10 @@ export class WorkspaceGitHost {
20
35
  // checked at the moment the token would be handed over. The cost is four
21
36
  // calls instead of one; `clone` is these four.
22
37
  await git.init({ dir: req.dir });
38
+ // Straight after `init`, so a clone that fails part-way leaves nothing
39
+ // behind that the container's git would misread.
40
+ for (const [path, value] of Object.entries(CONTAINER_CORE_CONFIG))
41
+ await git.configSet({ dir: req.dir, path, value });
23
42
  await git.remoteAdd({
24
43
  dir: req.dir,
25
44
  name: "origin",
@@ -46,7 +65,7 @@ export class WorkspaceGitHost {
46
65
  throw new Error(`cloned ${req.url} but the remote named no default branch to check out`);
47
66
  await git.checkout({ dir: req.dir, ref: landed });
48
67
  // What a real `git clone` writes and the container's git will look for:
49
- // without it the branch tracks nothing, and a subagent reaching for a bare
68
+ // without it the branch tracks nothing, and an agent reaching for a bare
50
69
  // `git status` in the shell sees a branch with no upstream.
51
70
  await git.configSet({
52
71
  dir: req.dir,
@@ -70,11 +89,33 @@ export class WorkspaceGitHost {
70
89
  prune: true,
71
90
  tags: false,
72
91
  singleBranch: false,
92
+ // Every branch is fetched, but isomorphic-git still resolves one ref
93
+ // against the remote's list, and left to itself it picks the current
94
+ // branch's upstream. A checkout left on a branch whose pull request
95
+ // merged — and whose remote branch went with it — then fails every
96
+ // fetch with `Could not find refs/heads/<branch>`. The remote's HEAD
97
+ // is always listed.
98
+ remoteRef: "HEAD",
73
99
  ...(req.depth ? { depth: req.depth } : {})
74
100
  });
75
101
  return `fetched ${req.url} (default branch ${result.defaultBranch ?? "unknown"})`;
76
102
  });
77
103
  }
104
+ /**
105
+ * `remote.origin.url` from the local store, or `undefined` where there is no
106
+ * checkout or no origin. Reads only, and needs no credential.
107
+ */
108
+ async origin(dir) {
109
+ try {
110
+ const url = await this.deps
111
+ .git()
112
+ .configGet({ dir, path: "remote.origin.url" });
113
+ return typeof url === "string" && url ? url : undefined;
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ }
78
119
  async push(req) {
79
120
  return this.#run(req.allowedHosts, async (git, onAuth) => {
80
121
  const result = await git.push({
@@ -146,11 +187,40 @@ export class WorkspaceGitHost {
146
187
  }
147
188
  catch (err) {
148
189
  const code = err?.code;
190
+ const message = describeGitError(err);
191
+ console.error(`[${this.deps.tag()}] git failed`, { code, message });
149
192
  return {
150
193
  ok: false,
151
194
  ...(typeof code === "string" ? { code } : {}),
152
- message: err instanceof Error ? err.message : String(err)
195
+ message
153
196
  };
154
197
  }
155
198
  }
156
199
  }
200
+ /**
201
+ * A git error's message with the chain of causes behind it.
202
+ *
203
+ * `@cloudflare/computer/git` wraps isomorphic-git's errors, and classifies some
204
+ * by a heuristic: any failure whose message mentions `.git` beside a missing
205
+ * path or a ref it could not find is reported as "not a git repository" — a
206
+ * sentence that is wrong about a checkout that plainly is one, and names
207
+ * neither the file nor the ref. What actually failed is only on `cause`, which
208
+ * a Durable Object boundary does not carry, so it is folded into the message
209
+ * here while the error is still itself.
210
+ */
211
+ export function describeGitError(err) {
212
+ const parts = [];
213
+ const seen = new Set();
214
+ let at = err;
215
+ for (let depth = 0; at !== undefined && at !== null && depth < 4; depth++) {
216
+ const e = at;
217
+ const text = typeof e.message === "string" ? e.message : String(at);
218
+ if (!seen.has(text)) {
219
+ seen.add(text);
220
+ const name = typeof e.name === "string" ? e.name : "";
221
+ parts.push(name && depth > 0 ? `${name}: ${text}` : text);
222
+ }
223
+ at = e.cause;
224
+ }
225
+ return parts.join(" ← ");
226
+ }
@@ -4,7 +4,7 @@ import type { Workspace } from "@cloudflare/computer";
4
4
  * answers for it.
5
5
  *
6
6
  * `/repo` configures the checkouts it clones and names the identity on every
7
- * commit it makes — see {@link file://../../repo/checkout.ts writeGitIdentity}.
7
+ * commit it makes — see {@link file://../repo/checkout.ts writeGitIdentity}.
8
8
  * Neither reaches a repository the container made for itself: a submodule
9
9
  * checked out by a bootstrap, a `git init`, a second clone from the shell. Those
10
10
  * have no identity at all, and git refuses to commit without one — so whatever
@@ -55,7 +55,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
55
55
  * answers for it.
56
56
  *
57
57
  * `/repo` configures the checkouts it clones and names the identity on every
58
- * commit it makes — see {@link file://../../repo/checkout.ts writeGitIdentity}.
58
+ * commit it makes — see {@link file://../repo/checkout.ts writeGitIdentity}.
59
59
  * Neither reaches a repository the container made for itself: a submodule
60
60
  * checked out by a bootstrap, a `git init`, a second clone from the shell. Those
61
61
  * have no identity at all, and git refuses to commit without one — so whatever
@@ -1,53 +1,49 @@
1
- import type { ToolSet } from "ai";
2
- import type { AgentPlugin, WorkspaceBacking, WorkspaceHandle } from "@dynamicagents/core";
3
1
  /**
4
- * `@dynamicagents/plugins/workspace` — a durable file store for long subagent runs,
5
- * and the model-facing tools over it.
6
- *
7
- * Core already owns the *interface*: `WorkspaceHandle`, `WorkspaceBacking`, the
8
- * per-file and file-count caps, and `makeWorkspaceHandle` which enforces them.
9
- * What core deliberately does not own is a **backend**, because the only good
10
- * one is `@cloudflare/shell` and shell is experimental ("expect breaking
11
- * changes"). An agent that never delegates file work should not carry that
12
- * dependency, so it ships here and is an optional peer.
13
- *
14
- * This is the only `@cloudflare/shell` import surface in the package, and stays
15
- * that way: everything else codes against core's stable wrapper rather than
16
- * shell's large, churning API.
17
- *
18
- * Two halves, and they are independent:
19
- *
20
- * - `workspaceBacking` gives every subagent execution a real file store. An
21
- * agent that installs this plugin and nothing else still benefits, because the
22
- * tool families that persist session state to the workspace now have somewhere
23
- * durable to put it.
24
- * - the `workspace` tool family puts `ws_read`/`ws_write`/`ws_list` in front of
25
- * the *model*, so it can keep notes across a context window it will outlive.
26
- *
27
- * A recipe opts into the second by naming `workspace` in its `toolFamilies`;
28
- * the first applies to every execution regardless.
2
+ * `@dynamicagents/plugins/workspace` — the Durable Object a container workspace
3
+ * lives in, and the client side every other plugin reaches it through.
4
+ *
5
+ * The filesystem **is** a Durable Object's SQLite, mounted into the container
6
+ * over FUSE by `computerd`. Commands see a normal `/workspace`; the Worker reads
7
+ * the same tree over RPC; and when the container is replaced the tree is pushed
8
+ * back into the new one — so a checkout outlives the container that held it.
9
+ *
10
+ * `WorkspaceObjectBase` is the object: container backend, egress, CA trust,
11
+ * credentialed git, the dependency install, the sync drain and idle reclaim. A
12
+ * host subclasses it and answers a short config. Everything else here is what
13
+ * reaches it from outside: `openWorkspace`, `workspaceExec`, and the advisory
14
+ * vocabulary it reports its state in.
15
+ *
16
+ * `@dynamicagents/plugins/computer` is the agent's tools over it; `/repo` and
17
+ * `/scratch` take `workspaceExec` injected.
18
+ *
19
+ * Requires the Workers **Paid** plan (containers) and a Durable Object binding
20
+ * whose class extends `WorkspaceObjectBase` — see the README for the wrangler
21
+ * block.
29
22
  */
30
- /** This plugin's tool-family name, as a recipe's `toolFamilies` lists it. */
31
- export declare const WORKSPACE_FAMILY = "workspace";
23
+ /** How a repository installs its dependencies — the mechanical half. */
24
+ export { DEFAULT_INSTALL_PLAN, installFingerprint, resolveInstallCommand, type InstallPlan, type InstallProbe, type InstallResolution, type InstallRule, type InstallState } from "./install.js";
32
25
  /**
33
- * The model-facing workspace tools: read/write/list over the execution's durable
34
- * file store.
35
- *
36
- * Long recipes keep a small rolling context window, so these are how a model
37
- * persists anything it must remember after older turns scroll out. Exported
38
- * separately from the plugin so they unit-test without a runtime, and so a host
39
- * with its own tool assembly can reuse them.
26
+ * The workspace-advisory vocabulary, which a host needs all of: the type to
27
+ * return over RPC, `deriveAdvisories` to build it from what it already knows,
28
+ * and `sessionAdvisory` for a host driving a whole session rather than one
29
+ * command. `shapeOf` and `renderAdvisory` are exported for the specs that hold
30
+ * their exhaustiveness — a host has no reason to call either, since calling them
31
+ * would mean writing policy or wording that has one home already.
40
32
  */
41
- export declare function buildWorkspaceTools(workspace: WorkspaceHandle): ToolSet;
33
+ export { deriveAdvisories, renderAdvisory, sessionAdvisory, shapeOf, type AdvisoryAudience, type AdvisoryInput, type AdvisoryShape, type WorkspaceAdvisory } from "./advisory.js";
34
+ export { pathExists } from "./read.js";
35
+ export { cancelledNote, truncateOutput } from "./format.js";
36
+ export { openWorkspace, openWorkspaceFs, workspaceNameFromRuntime, WORKSPACE_RUNTIME_KEY, type WorkspaceHost } from "./open.js";
37
+ export { withShell, withShellTranscript } from "./shell.js";
38
+ export { workspaceExec, DEFAULT_CWD, DEFAULT_TIMEOUT_MS, type WorkspaceClientConfig } from "./exec.js";
42
39
  /**
43
- * Build a shell {@link Workspace} over a Durable Object's SQLite storage.
44
- *
45
- * The executing facet passes its *own* `ctx.storage.sql`, so per-execution
46
- * isolation is free and deleting the child wipes the workspace with the rest of
47
- * its storage. `name` is lazy because a facet's name is set after construction.
48
- *
49
- * Exported because a host that assembles its own `SubagentRuntime` by hand may
50
- * want it directly; the plugin's `workspaceBacking` is this same function.
40
+ * The object itself. The modules below import the client-side leaves beside
41
+ * them, never this barrel: a cycle through a module that builds a class at
42
+ * import time is a base that evaluates `undefined`.
51
43
  */
52
- export declare function durableWorkspaceBacking(sql: SqlStorage, name: () => string | undefined): WorkspaceBacking;
53
- export declare function workspace(): AgentPlugin;
44
+ export { WorkspaceObjectBase, IDLE_RECLAIM_MS, WORKSPACE_DIR, workspaceName, type WorkspaceObjectConfig, type WorkspaceGitConfig } from "./object.js";
45
+ export { InstallJob, type InstallJobDeps } from "./install-job.js";
46
+ export { WorkspaceSync, SYNC_DRAIN_BUDGET_MS, SYNC_DRAIN_RESUME_MS, SYNC_DRAIN_MAX_BACKOFF_MS, syncRetryDelayMs, type DrainOutcome, type SyncDrainIntent, type WorkspaceSyncDeps } from "./sync.js";
47
+ export { ContainerTrust, TRUST_CA_COMMAND, type ContainerTrustDeps } from "./ca-trust.js";
48
+ export { WorkspaceGitHost, type GitHostDeps } from "./git-host.js";
49
+ export type { WorkspaceWakeHandlers } from "./wake.js";
@@ -1,118 +1,58 @@
1
- import { tool } from "ai";
2
- import { z } from "zod";
3
- import { definePlugin } from "@dynamicagents/core";
4
- import { Workspace } from "@cloudflare/shell";
5
1
  /**
6
- * `@dynamicagents/plugins/workspace` — a durable file store for long subagent runs,
7
- * and the model-facing tools over it.
8
- *
9
- * Core already owns the *interface*: `WorkspaceHandle`, `WorkspaceBacking`, the
10
- * per-file and file-count caps, and `makeWorkspaceHandle` which enforces them.
11
- * What core deliberately does not own is a **backend**, because the only good
12
- * one is `@cloudflare/shell` and shell is experimental ("expect breaking
13
- * changes"). An agent that never delegates file work should not carry that
14
- * dependency, so it ships here and is an optional peer.
15
- *
16
- * This is the only `@cloudflare/shell` import surface in the package, and stays
17
- * that way: everything else codes against core's stable wrapper rather than
18
- * shell's large, churning API.
19
- *
20
- * Two halves, and they are independent:
21
- *
22
- * - `workspaceBacking` gives every subagent execution a real file store. An
23
- * agent that installs this plugin and nothing else still benefits, because the
24
- * tool families that persist session state to the workspace now have somewhere
25
- * durable to put it.
26
- * - the `workspace` tool family puts `ws_read`/`ws_write`/`ws_list` in front of
27
- * the *model*, so it can keep notes across a context window it will outlive.
28
- *
29
- * A recipe opts into the second by naming `workspace` in its `toolFamilies`;
30
- * the first applies to every execution regardless.
2
+ * `@dynamicagents/plugins/workspace` — the Durable Object a container workspace
3
+ * lives in, and the client side every other plugin reaches it through.
4
+ *
5
+ * The filesystem **is** a Durable Object's SQLite, mounted into the container
6
+ * over FUSE by `computerd`. Commands see a normal `/workspace`; the Worker reads
7
+ * the same tree over RPC; and when the container is replaced the tree is pushed
8
+ * back into the new one — so a checkout outlives the container that held it.
9
+ *
10
+ * `WorkspaceObjectBase` is the object: container backend, egress, CA trust,
11
+ * credentialed git, the dependency install, the sync drain and idle reclaim. A
12
+ * host subclasses it and answers a short config. Everything else here is what
13
+ * reaches it from outside: `openWorkspace`, `workspaceExec`, and the advisory
14
+ * vocabulary it reports its state in.
15
+ *
16
+ * `@dynamicagents/plugins/computer` is the agent's tools over it; `/repo` and
17
+ * `/scratch` take `workspaceExec` injected.
18
+ *
19
+ * Requires the Workers **Paid** plan (containers) and a Durable Object binding
20
+ * whose class extends `WorkspaceObjectBase` — see the README for the wrangler
21
+ * block.
31
22
  */
32
- /** This plugin's tool-family name, as a recipe's `toolFamilies` lists it. */
33
- export const WORKSPACE_FAMILY = "workspace";
23
+ /** How a repository installs its dependencies — the mechanical half. */
24
+ export { DEFAULT_INSTALL_PLAN, installFingerprint, resolveInstallCommand } from "./install.js";
34
25
  /**
35
- * The model-facing workspace tools: read/write/list over the execution's durable
36
- * file store.
37
- *
38
- * Long recipes keep a small rolling context window, so these are how a model
39
- * persists anything it must remember after older turns scroll out. Exported
40
- * separately from the plugin so they unit-test without a runtime, and so a host
41
- * with its own tool assembly can reuse them.
26
+ * The workspace-advisory vocabulary, which a host needs all of: the type to
27
+ * return over RPC, `deriveAdvisories` to build it from what it already knows,
28
+ * and `sessionAdvisory` for a host driving a whole session rather than one
29
+ * command. `shapeOf` and `renderAdvisory` are exported for the specs that hold
30
+ * their exhaustiveness — a host has no reason to call either, since calling them
31
+ * would mean writing policy or wording that has one home already.
42
32
  */
43
- export function buildWorkspaceTools(workspace) {
44
- return {
45
- ws_read: tool({
46
- description: "Read a file from your workspace. Returns the file's text, or a note if it does not exist.",
47
- inputSchema: z.object({
48
- path: z.string().describe("File path, e.g. 'notes.md'")
49
- }),
50
- execute: async ({ path }) => {
51
- const content = await workspace.read(path);
52
- return content === null ? `(no file at ${path})` : content;
53
- }
54
- }),
55
- ws_write: tool({
56
- description: "Create or overwrite a workspace file. Use this to persist notes, hypotheses, and plans so you remember them after older turns scroll out of context.",
57
- inputSchema: z.object({
58
- path: z.string().describe("File path, e.g. 'notes.md'"),
59
- content: z
60
- .string()
61
- .describe("Full file content (overwrites any existing file)")
62
- }),
63
- execute: async ({ path, content }) => {
64
- // Returned rather than thrown: a write over the size cap is the model's
65
- // mistake to correct, and it can only correct what it is told. The SDK
66
- // would surface a throw as a tool error too, but this reads better and
67
- // keeps the limit itself in the message.
68
- try {
69
- await workspace.write(path, content);
70
- return `wrote ${path}`;
71
- }
72
- catch (err) {
73
- return `error writing ${path}: ${String(err)}`;
74
- }
75
- }
76
- }),
77
- ws_list: tool({
78
- description: "List the files currently in your workspace.",
79
- inputSchema: z.object({
80
- dir: z.string().optional().describe("Directory to list (default: root)")
81
- }),
82
- execute: async ({ dir }) => {
83
- const entries = await workspace.list(dir);
84
- if (entries.length === 0)
85
- return "(workspace is empty)";
86
- return entries
87
- .map((e) => `${e.path}${e.type === "directory" ? "/" : ""} (${e.size} bytes)`)
88
- .join("\n");
89
- }
90
- })
91
- };
92
- }
33
+ export { deriveAdvisories, renderAdvisory, sessionAdvisory, shapeOf } from "./advisory.js";
34
+ // A host implementing `InstallProbe` needs exactly this, and hand-rolling it
35
+ // loses the fast path: across a Durable Object boundary `fs` is a stub carrying
36
+ // `exists`, which a `stat`-only probe never asks for.
37
+ export { pathExists } from "./read.js";
38
+ export { cancelledNote, truncateOutput } from "./format.js";
39
+ export { openWorkspace, openWorkspaceFs, workspaceNameFromRuntime, WORKSPACE_RUNTIME_KEY } from "./open.js";
40
+ export { withShell, withShellTranscript } from "./shell.js";
41
+ export { workspaceExec, DEFAULT_CWD, DEFAULT_TIMEOUT_MS } from "./exec.js";
93
42
  /**
94
- * Build a shell {@link Workspace} over a Durable Object's SQLite storage.
95
- *
96
- * The executing facet passes its *own* `ctx.storage.sql`, so per-execution
97
- * isolation is free and deleting the child wipes the workspace with the rest of
98
- * its storage. `name` is lazy because a facet's name is set after construction.
99
- *
100
- * Exported because a host that assembles its own `SubagentRuntime` by hand may
101
- * want it directly; the plugin's `workspaceBacking` is this same function.
43
+ * The object itself. The modules below import the client-side leaves beside
44
+ * them, never this barrel: a cycle through a module that builds a class at
45
+ * import time is a base that evaluates `undefined`.
102
46
  */
103
- export function durableWorkspaceBacking(sql, name) {
104
- return new Workspace({ sql, name });
105
- }
106
- export function workspace() {
107
- return definePlugin({
108
- key: "workspace",
109
- workspaceBacking: durableWorkspaceBacking,
110
- toolFamilies: {
111
- [WORKSPACE_FAMILY]: (ctx) => ({
112
- tools: buildWorkspaceTools(ctx.workspace)
113
- })
114
- }
115
- // No `requires`: shell backs the workspace with the Durable Object's own
116
- // SQLite, which every DO already has. Nothing for the host to declare.
117
- });
118
- }
47
+ export { WorkspaceObjectBase, IDLE_RECLAIM_MS, WORKSPACE_DIR, workspaceName } from "./object.js";
48
+ // The dependency install, exported because a host that wants to report on one —
49
+ // or a spec that drives it — needs the type, not because anything but
50
+ // `./object.ts` constructs it.
51
+ export { InstallJob } from "./install-job.js";
52
+ // The pull the library owns no alarm for. Its constants are exported because a
53
+ // host deferring to a drain has to back off at the same rate this does.
54
+ export { WorkspaceSync, SYNC_DRAIN_BUDGET_MS, SYNC_DRAIN_RESUME_MS, SYNC_DRAIN_MAX_BACKOFF_MS, syncRetryDelayMs } from "./sync.js";
55
+ // `TRUST_CA_COMMAND` is exported so a host can assert what its image will be
56
+ // asked to run — see the file for why this cannot live in an entrypoint.
57
+ export { ContainerTrust, TRUST_CA_COMMAND } from "./ca-trust.js";
58
+ export { WorkspaceGitHost } from "./git-host.js";
@@ -1,6 +1,6 @@
1
1
  import { type Workspace } from "@cloudflare/computer";
2
2
  import type { Scheduler } from "@dynamicagents/core/alarm";
3
- import { type InstallPlan, type InstallState } from "../install.js";
3
+ import { type InstallPlan, type InstallState } from "./install.js";
4
4
  import type { WorkspaceWakeHandlers } from "./wake.js";
5
5
  export interface InstallJobDeps {
6
6
  storage: DurableObjectStorage;
@@ -71,7 +71,7 @@ export declare class InstallJob {
71
71
  *
72
72
  * Called from `repo_clone` through the repo plugin's `afterCheckout` hook, so
73
73
  * it runs inside a model turn and must not block on the install — 225 seconds
74
- * for slack-gatekeeper, against a chunk step that dies at ten minutes.
74
+ * for slack-gatekeeper, against a turn that is cut at fifteen minutes.
75
75
  *
76
76
  * That caller is a model turn, which lives long enough to hold the drain handed
77
77
  * to `ctx.waitUntil` below. **A short-lived caller cannot**, which is why an
@@ -102,7 +102,7 @@ export declare class InstallJob {
102
102
  /**
103
103
  * When a reinstall was queued, if one is waiting to run.
104
104
  *
105
- * Read by {@link file://../advisory.ts deriveAdvisories}, which is the only
105
+ * Read by {@link file://./advisory.ts deriveAdvisories}, which is the only
106
106
  * caller that needs it: a `failed` record with a repair already queued is a
107
107
  * transient condition wearing a permanent record's clothes.
108
108
  */
@@ -112,7 +112,7 @@ export declare class InstallJob {
112
112
  *
113
113
  * `clearArmed` first and unconditionally: this handler runs for minutes, and
114
114
  * the stamp left in place is what a second arming would recognise as its own.
115
- * `startInstall`'s in-flight guard would catch a double, but the cheaper
115
+ * The reservation in `#beginInstall` would catch a double, but the cheaper
116
116
  * answer is not to schedule one.
117
117
  */
118
118
  onRun(): Promise<void>;