@dynamicagents/plugins 0.12.0 → 0.14.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 (132) 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 +55 -0
  5. package/dist/claude-code/cards.js +283 -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 +366 -0
  18. package/dist/claude-code/run.d.ts +230 -116
  19. package/dist/claude-code/run.js +380 -167
  20. package/dist/claude-code/session.d.ts +131 -0
  21. package/dist/claude-code/session.js +288 -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-deps.d.ts +4 -0
  69. package/dist/{computer/host → workspace}/container-deps.js +26 -1
  70. package/dist/{computer/host → workspace}/container-fault.d.ts +3 -3
  71. package/dist/{computer/host → workspace}/container-fault.js +23 -8
  72. package/dist/workspace/deps-snapshot.d.ts +110 -0
  73. package/dist/workspace/deps-snapshot.js +424 -0
  74. package/dist/workspace/exec.d.ts +94 -0
  75. package/dist/workspace/exec.js +153 -0
  76. package/dist/workspace/format.d.ts +36 -0
  77. package/dist/workspace/format.js +57 -0
  78. package/dist/{computer/host → workspace}/git-host.d.ts +21 -4
  79. package/dist/{computer/host → workspace}/git-host.js +72 -2
  80. package/dist/{computer/host → workspace}/git-identity.d.ts +1 -1
  81. package/dist/{computer/host → workspace}/git-identity.js +1 -1
  82. package/dist/workspace/index.d.ts +44 -47
  83. package/dist/workspace/index.js +52 -112
  84. package/dist/{computer/host → workspace}/install-job.d.ts +53 -4
  85. package/dist/{computer/host → workspace}/install-job.js +346 -178
  86. package/dist/{computer → workspace}/install.d.ts +17 -8
  87. package/dist/{computer → workspace}/install.js +24 -6
  88. package/dist/{computer/host/workspace.d.ts → workspace/object.d.ts} +125 -27
  89. package/dist/{computer/host/workspace.js → workspace/object.js} +341 -58
  90. package/dist/workspace/open.d.ts +95 -0
  91. package/dist/workspace/open.js +63 -0
  92. package/dist/workspace/read.d.ts +16 -0
  93. package/dist/workspace/read.js +22 -0
  94. package/dist/{computer → workspace}/shell.d.ts +5 -5
  95. package/dist/{computer → workspace}/shell.js +6 -6
  96. package/dist/{computer/host → workspace}/wake.d.ts +3 -1
  97. package/package.json +17 -37
  98. package/dist/arc-agi/analysis.d.ts +0 -322
  99. package/dist/arc-agi/analysis.js +0 -802
  100. package/dist/arc-agi/client.d.ts +0 -82
  101. package/dist/arc-agi/client.js +0 -113
  102. package/dist/arc-agi/game-tools.d.ts +0 -24
  103. package/dist/arc-agi/game-tools.js +0 -26
  104. package/dist/arc-agi/index.d.ts +0 -40
  105. package/dist/arc-agi/index.js +0 -175
  106. package/dist/arc-agi/main-agent.d.ts +0 -24
  107. package/dist/arc-agi/main-agent.js +0 -62
  108. package/dist/arc-agi/recipe.d.ts +0 -48
  109. package/dist/arc-agi/recipe.js +0 -97
  110. package/dist/arc-agi/scorecard.d.ts +0 -118
  111. package/dist/arc-agi/scorecard.js +0 -163
  112. package/dist/arc-agi/soul.d.ts +0 -29
  113. package/dist/arc-agi/soul.js +0 -79
  114. package/dist/arc-agi/store.d.ts +0 -153
  115. package/dist/arc-agi/store.js +0 -215
  116. package/dist/arc-agi/tools.d.ts +0 -36
  117. package/dist/arc-agi/tools.js +0 -574
  118. package/dist/arc-agi/types.d.ts +0 -211
  119. package/dist/arc-agi/types.js +0 -19
  120. package/dist/claude-code/recipe.d.ts +0 -53
  121. package/dist/claude-code/recipe.js +0 -100
  122. package/dist/computer/read.d.ts +0 -133
  123. package/dist/computer/read.js +0 -200
  124. package/dist/computer/tools-find.js +0 -216
  125. package/dist/recall/index.d.ts +0 -105
  126. package/dist/recall/index.js +0 -181
  127. package/dist/repo/tools-forge.d.ts +0 -4
  128. package/dist/triage/index.d.ts +0 -360
  129. package/dist/triage/index.js +0 -311
  130. /package/dist/{computer/host → workspace}/sync.d.ts +0 -0
  131. /package/dist/{computer/host → workspace}/sync.js +0 -0
  132. /package/dist/{computer/host → workspace}/wake.js +0 -0
@@ -0,0 +1,424 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { DEPS_ROOTS_COMMAND, parseDepsRoots } from "./container-deps.js";
54
+ /**
55
+ * A snapshot of a container that has just installed, so the next container
56
+ * starts from it instead of installing again.
57
+ *
58
+ * Dependency trees live on the container's disk (see `./container-deps.ts`), so
59
+ * a new container reinstalls before its first command can use them — what that
60
+ * costs is in `./install-job.ts`. A Containers snapshot is that disk: restored,
61
+ * the trees are back. It holds nothing else worth having — the checkout is this
62
+ * object's storage, mounted over FUSE, and a mount is not in a snapshot.
63
+ *
64
+ * ## When a snapshot is restored
65
+ *
66
+ * Only into a start that would otherwise install the same thing, which takes
67
+ * all of:
68
+ *
69
+ * - **The image it was taken on.** A snapshot replaces the image rather than
70
+ * layering on it, so one from before a deploy boots the old image's
71
+ * `computerd`, which nothing downstream catches.
72
+ * - **The same checkout and install fingerprint**, so the marker it carries is
73
+ * the one an install would write now.
74
+ * - **Every package root's inputs unchanged.** A bootstrap installs roots the
75
+ * fingerprint does not cover — a submodule's lockfile — and a restore brings
76
+ * all of them back at once.
77
+ * - **The same set of package roots.** A root added since — a new submodule —
78
+ * has no tree in the snapshot, and restoring it would skip the install that
79
+ * gives it one: see {@link packageRootSet}.
80
+ *
81
+ * What came up is then checked, not assumed: the setup mounts the restored trees
82
+ * and reads the marker, and only a match is adopted as installed. Anything else —
83
+ * an expired snapshot, which the platform reports as a failed start, after which
84
+ * the backend's restart takes the image — drops the record, and the install runs
85
+ * and snapshots again.
86
+ *
87
+ * ## Taken of the tree it was asked for, and nothing since
88
+ *
89
+ * An install asks for its snapshot in the same write as its verdict, so a stop
90
+ * that comes before the wake still sees the request. Snapshots run one at a
91
+ * time, and one is recorded only if the install record still names the tree it
92
+ * was taken of: a reinstall empties the tree before it rebuilds it, and a
93
+ * snapshot taken across that is of neither.
94
+ *
95
+ * ## The record outlives the container, and the snapshot outlives the record
96
+ *
97
+ * Unlike the tree record, which goes with its container, this is what a later
98
+ * container starts from. The platform keeps a snapshot for thirty days from its
99
+ * last restore and offers no way to delete one, so a dropped record leaves its
100
+ * snapshot to expire.
101
+ */
102
+ /** Where the record lives. */
103
+ const RECORD_KEY = "deps:snapshot";
104
+ /** The tree a finished install asked to have snapshotted, until it is. */
105
+ const DUE_KEY = "deps:snapshot-due";
106
+ /**
107
+ * Which package roots a checkout has, as one digest: every directory under
108
+ * `dir` with a `package.json`, and the submodules `.gitmodules` declares — one a
109
+ * bootstrap clones in the container has no `package.json` here until then.
110
+ */
111
+ export async function packageRootSet(fs, dir) {
112
+ const found = await fs
113
+ .find(dir, "**/package.json", { exclude: ["**/.git", "**/node_modules"] })
114
+ .catch((err) => {
115
+ if (err?.code === "ENOENT")
116
+ return [];
117
+ throw err;
118
+ });
119
+ const gitmodules = await fs
120
+ .readFile(`${dir}/.gitmodules`, "utf8")
121
+ .catch(() => "");
122
+ const input = [
123
+ ...found.map((entry) => entry.path).sort(),
124
+ "\0.gitmodules",
125
+ gitmodules
126
+ ].join("\n");
127
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
128
+ return [...new Uint8Array(digest)]
129
+ .map((b) => b.toString(16).padStart(2, "0"))
130
+ .join("");
131
+ }
132
+ /**
133
+ * The launch options `ContainerBackend` spreads into every start, whose
134
+ * `containerSnapshot` is read at that moment.
135
+ *
136
+ * A getter, because the backend keeps this object for its lifetime and builds
137
+ * each start from it — the initial one and every restart after a failed health
138
+ * check — and only the first of those may restore: a restart is the backend
139
+ * recovering from a start that did not come up, which an expired snapshot is.
140
+ */
141
+ function launchOptions(take) {
142
+ return {
143
+ get containerSnapshot() {
144
+ return take();
145
+ }
146
+ };
147
+ }
148
+ export class DepsSnapshot {
149
+ deps;
150
+ /** Restore with the next start, once. */
151
+ #pending;
152
+ /** Whether a start has taken {@link #pending} since it was armed. */
153
+ #launched = false;
154
+ #taking;
155
+ launch = launchOptions(() => {
156
+ const pending = this.#pending;
157
+ if (!pending)
158
+ return undefined;
159
+ this.#pending = undefined;
160
+ this.#launched = true;
161
+ return pending;
162
+ });
163
+ constructor(deps) {
164
+ this.deps = deps;
165
+ }
166
+ /**
167
+ * Restore with the next start if the record fits it, and say which record.
168
+ *
169
+ * `expected` is a thunk because it reads the checkout, and a workspace with no
170
+ * snapshot or one from another image should not pay for that.
171
+ */
172
+ async arm(expected) {
173
+ this.disarm();
174
+ const record = await this.get();
175
+ if (!record)
176
+ return undefined;
177
+ const refused = await this.#refusal(record, expected);
178
+ if (refused) {
179
+ console.info(`[${this.deps.tag()}] not restoring the dependency snapshot`, {
180
+ id: this.deps.id(),
181
+ snapshot: record.id,
182
+ reason: refused
183
+ });
184
+ return undefined;
185
+ }
186
+ this.#pending = { id: record.id };
187
+ console.info(`[${this.deps.tag()}] restoring the dependency snapshot`, {
188
+ id: this.deps.id(),
189
+ snapshot: record.id,
190
+ bytes: record.size,
191
+ dir: record.dir
192
+ });
193
+ return record;
194
+ }
195
+ /** Forget an armed restore, taken or not. */
196
+ disarm() {
197
+ this.#pending = undefined;
198
+ this.#launched = false;
199
+ }
200
+ /**
201
+ * Whether the container that came up holds `record`'s tree, judged by the
202
+ * marker the setup read in it.
203
+ *
204
+ * Drops the record when a start took it and the tree is not there; leaves it
205
+ * when no start did, since nothing was learned about it.
206
+ */
207
+ async restored(record, marker) {
208
+ if (!this.#launched)
209
+ return false;
210
+ if (marker === record.fingerprint)
211
+ return true;
212
+ await this.#drop(record, marker === undefined
213
+ ? "the restored container could not be read"
214
+ : "the restored container does not hold the tree");
215
+ return false;
216
+ }
217
+ /**
218
+ * Snapshot the running container, which has just installed `tree`, after any
219
+ * snapshot already in flight — a later install's request is never dropped for
220
+ * an earlier one's.
221
+ *
222
+ * Never throws: a container that cannot be snapshotted installs again next
223
+ * time, which is today's cost and no worse.
224
+ */
225
+ async take(tree) {
226
+ const turn = (this.#taking ?? Promise.resolve()).then(() => this.#take(tree));
227
+ this.#taking = turn;
228
+ try {
229
+ await turn;
230
+ }
231
+ finally {
232
+ if (this.#taking === turn)
233
+ this.#taking = undefined;
234
+ }
235
+ }
236
+ /** Snapshots in flight, finished — for whatever is about to stop the container. */
237
+ async settled() {
238
+ await this.#taking;
239
+ }
240
+ /**
241
+ * The entries that ask for `tree`'s snapshot, for the install to write with
242
+ * its verdict — see the file comment, and {@link takeDue}.
243
+ */
244
+ dueEntries(tree) {
245
+ return { [DUE_KEY]: tree };
246
+ }
247
+ /** Take the snapshot an install asked for, if one is waiting. */
248
+ async takeDue() {
249
+ const due = await this.deps.storage.get(DUE_KEY);
250
+ if (!due)
251
+ return;
252
+ await this.deps.storage.delete(DUE_KEY);
253
+ await this.take(due);
254
+ }
255
+ /** The container went, and the disk an asked-for snapshot was of with it. */
256
+ async forgetDue() {
257
+ await this.deps.storage.delete(DUE_KEY);
258
+ }
259
+ async get() {
260
+ return await this.deps.storage.get(RECORD_KEY);
261
+ }
262
+ /**
263
+ * Start from another workspace's snapshot — one taken for the same checkout
264
+ * path on the same image, as a worktree shares with the checkout it was cut
265
+ * from.
266
+ *
267
+ * Kept out when this workspace's own snapshot already fits, and refused when
268
+ * the offered one does not.
269
+ */
270
+ async seed(record, expected) {
271
+ if (!isRecord(record))
272
+ return { seeded: false, reason: "not a snapshot" };
273
+ const now = await expected();
274
+ const fits = async () => now;
275
+ const own = await this.get();
276
+ if (own && !(await this.#refusal(own, fits)))
277
+ return { seeded: false, reason: "this workspace's own snapshot fits" };
278
+ const refused = await this.#refusal(record, fits);
279
+ if (refused)
280
+ return { seeded: false, reason: refused };
281
+ await this.deps.storage.put(RECORD_KEY, record);
282
+ console.info(`[${this.deps.tag()}] seeded a dependency snapshot`, {
283
+ id: this.deps.id(),
284
+ snapshot: record.id,
285
+ dir: record.dir
286
+ });
287
+ return { seeded: true };
288
+ }
289
+ /**
290
+ * Why `record` must not be restored now, or `undefined` if it may. A check
291
+ * that throws refuses: a start must not fail for a snapshot it can go without.
292
+ */
293
+ async #refusal(record, expected) {
294
+ if (record.image !== this.deps.image())
295
+ return "taken on another image";
296
+ try {
297
+ const tree = await expected();
298
+ if (!tree)
299
+ return "there is nothing to install here";
300
+ if (record.dir !== tree.dir)
301
+ return "taken for another checkout";
302
+ if (record.fingerprint !== tree.fingerprint)
303
+ return "the install's inputs have changed";
304
+ for (const root of record.roots) {
305
+ if ((await this.deps.rootFingerprint(root.dir)) !== root.fingerprint)
306
+ return `${root.dir} has changed`;
307
+ }
308
+ if ((await packageRootSet(this.deps.workspace().fs, tree.dir)) !==
309
+ record.rootSet)
310
+ return "the checkout's package roots have changed";
311
+ return undefined;
312
+ }
313
+ catch (err) {
314
+ return `it could not be checked: ${String(err)}`;
315
+ }
316
+ }
317
+ /** Whether the install record still names `tree`. */
318
+ async #holds(tree) {
319
+ const now = await this.deps.tree();
320
+ return (now?.dir === tree.dir &&
321
+ now.fingerprint === tree.fingerprint &&
322
+ now.at === tree.at);
323
+ }
324
+ async #drop(record, reason) {
325
+ // Only this record: a later install may have put a newer one there.
326
+ if ((await this.get())?.id === record.id)
327
+ await this.deps.storage.delete(RECORD_KEY);
328
+ console.warn(`[${this.deps.tag()}] dropped the dependency snapshot`, {
329
+ id: this.deps.id(),
330
+ snapshot: record.id,
331
+ reason
332
+ });
333
+ }
334
+ async #take(tree) {
335
+ const container = this.deps.container();
336
+ const image = this.deps.image();
337
+ if (!container?.running || image === undefined)
338
+ return;
339
+ // Replaced, or reinstalling, since the install asked.
340
+ if (!(await this.#holds(tree)))
341
+ return;
342
+ const startedAt = Date.now();
343
+ try {
344
+ const roots = await this.#roots();
345
+ // Without a root to mount, a restore would read no marker and be dropped.
346
+ if (roots.length === 0) {
347
+ console.warn(`[${this.deps.tag()}] no dependency tree to snapshot on this disk`, { id: this.deps.id(), dir: tree.dir });
348
+ return;
349
+ }
350
+ const fingerprinted = [];
351
+ for (const dir of roots)
352
+ fingerprinted.push({
353
+ dir,
354
+ fingerprint: await this.deps.rootFingerprint(dir)
355
+ });
356
+ const rootSet = await packageRootSet(this.deps.workspace().fs, tree.dir);
357
+ const snapshot = await container.snapshotContainer({
358
+ name: `deps-${tree.fingerprint.slice(0, 16)}`
359
+ });
360
+ if (!(await this.#holds(tree))) {
361
+ console.warn(`[${this.deps.tag()}] discarded a dependency snapshot taken while the tree changed`, { id: this.deps.id(), snapshot: snapshot.id, dir: tree.dir });
362
+ return;
363
+ }
364
+ await this.deps.storage.put(RECORD_KEY, {
365
+ id: snapshot.id,
366
+ size: snapshot.size,
367
+ image,
368
+ dir: tree.dir,
369
+ fingerprint: tree.fingerprint,
370
+ roots: fingerprinted,
371
+ rootSet,
372
+ at: Date.now()
373
+ });
374
+ console.info(`[${this.deps.tag()}] took a dependency snapshot`, {
375
+ id: this.deps.id(),
376
+ snapshot: snapshot.id,
377
+ bytes: snapshot.size,
378
+ roots,
379
+ ms: Date.now() - startedAt
380
+ });
381
+ }
382
+ catch (err) {
383
+ console.warn(`[${this.deps.tag()}] could not take a dependency snapshot`, {
384
+ id: this.deps.id(),
385
+ err: String(err)
386
+ });
387
+ }
388
+ }
389
+ /** The package roots whose trees are on this container's disk. */
390
+ async #roots() {
391
+ const env_1 = { stack: [], error: void 0, hasError: false };
392
+ try {
393
+ const handle = __addDisposableResource(env_1, await this.deps
394
+ .workspace()
395
+ .runtime.exec(DEPS_ROOTS_COMMAND, {
396
+ cwd: "/",
397
+ encoding: "utf8",
398
+ timeoutMs: 30_000
399
+ }), false);
400
+ const result = await handle.result();
401
+ if (result.exitCode !== 0)
402
+ throw new Error(`could not list the dependency trees: ${result.stderr || `exit ${result.exitCode}`}`);
403
+ return parseDepsRoots(result.stdout ?? "");
404
+ }
405
+ catch (e_1) {
406
+ env_1.error = e_1;
407
+ env_1.hasError = true;
408
+ }
409
+ finally {
410
+ __disposeResources(env_1);
411
+ }
412
+ }
413
+ }
414
+ /** Enough of a record's shape to restore from — it arrives over RPC. */
415
+ function isRecord(value) {
416
+ const r = value;
417
+ return (typeof r?.id === "string" &&
418
+ typeof r.image === "string" &&
419
+ typeof r.dir === "string" &&
420
+ typeof r.fingerprint === "string" &&
421
+ typeof r.rootSet === "string" &&
422
+ Array.isArray(r.roots) &&
423
+ r.roots.every((root) => typeof root?.dir === "string"));
424
+ }
@@ -0,0 +1,94 @@
1
+ import { type WorkspaceHost } from "./open.js";
2
+ /**
3
+ * Running a command in a workspace's container, from outside the workspace
4
+ * object: what `/computer`'s tools and `/repo`'s git share, and how a host
5
+ * reaches its own container.
6
+ */
7
+ /** A command that has not finished in this long is a hung command. */
8
+ export declare const DEFAULT_TIMEOUT_MS: number;
9
+ /** Where checkouts live, in the container and in the workspace alike. */
10
+ export declare const DEFAULT_CWD = "/workspace";
11
+ /** Which workspace a caller reaches, and how its commands run there. */
12
+ export interface WorkspaceClientConfig {
13
+ /**
14
+ * The Durable Object namespace holding workspaces, closed over at
15
+ * instantiation so it is never model input.
16
+ */
17
+ binding: DurableObjectNamespace<WorkspaceHost>;
18
+ /**
19
+ * Which workspace this agent gets, by name. A thunk because the useful name —
20
+ * caller plus repository — does not exist when the plugin list is built.
21
+ *
22
+ * One workspace is one container is one repository: `@cloudflare/computer`
23
+ * pairs a Durable Object with exactly one container, so "never mix two
24
+ * repositories" is structural here rather than a convention to maintain.
25
+ */
26
+ workspaceName: () => string;
27
+ /** Working directory for every command. Defaults to `/workspace`. */
28
+ cwd?: string;
29
+ /**
30
+ * Run every command through this shell, e.g. `"bash"`. Unset, the command
31
+ * string goes to the runtime as-is and lands on whatever `/bin/sh` is.
32
+ *
33
+ * Worth setting, because "whatever `/bin/sh` is" is **dash** on Debian and
34
+ * Ubuntu, and a model writing shell writes *bash*. `${PIPESTATUS[0]}` is the
35
+ * one that costs most — a bash builtin a model reaches for to read a piped exit
36
+ * code, which dash fails with exit 2, after the command it wrapped has already
37
+ * run. It is one member of a family (`[[ ]]`, arrays, `set -o pipefail`,
38
+ * process substitution), so the fix is the shell rather than a note in a prompt
39
+ * telling the model to write POSIX.
40
+ *
41
+ * Set it only if the image actually has that shell: a missing one fails *every*
42
+ * command rather than the bash-flavoured ones.
43
+ */
44
+ shell?: string;
45
+ /** Per-command timeout. Defaults to ten minutes. */
46
+ timeoutMs?: number;
47
+ }
48
+ /**
49
+ * An `exec` bound to this workspace, for plugins that need a shell but should
50
+ * not own a container.
51
+ *
52
+ * `@dynamicagents/plugins/repo` is the reason this exists: it needs `git` on a real
53
+ * shell, but depending on this module would weld the two together and stop a
54
+ * host from pointing it at its own container. Structurally typed for the same
55
+ * reason — the two plugins compose without either importing the other.
56
+ *
57
+ * ## The agent tools' `env` is deliberately **not** merged here
58
+ *
59
+ * `/computer`'s `ComputerConfig.env` is merged into every command its tools
60
+ * run; this takes no environment of its own.
61
+ *
62
+ * What this hands out is a raw shell to *another plugin*, whose commands this
63
+ * module neither writes nor sees. `/repo` runs its unauthenticated git through
64
+ * it and pins the environment those need — `GIT_TERMINAL_PROMPT=0` plus the
65
+ * model-authored values it passes as variables. A host environment merged
66
+ * underneath lets through every key it does *not* pin: `http_proxy`,
67
+ * `GIT_PROXY_COMMAND`, `GIT_SSL_CAINFO`, `GIT_CONFIG_GLOBAL`, `GIT_EXEC_PATH` —
68
+ * the last two redirect the config git reads and relocate its helper binaries.
69
+ *
70
+ * No token is at stake, since the credentialed operations do not run here at
71
+ * all. The point is that changing what git does inside another plugin's commands
72
+ * should not be something a tools plugin's config field does by accident,
73
+ * from a host that only meant to set a registry. A host that wants it composes
74
+ * it where the merge is visible:
75
+ *
76
+ * ```ts
77
+ * exec: (command, options) =>
78
+ * workspaceExec(config)(command, {
79
+ * ...options,
80
+ * env: { ...mine, ...options?.env }
81
+ * });
82
+ * ```
83
+ */
84
+ export declare function workspaceExec(config: WorkspaceClientConfig): (command: string, options?: {
85
+ cwd?: string;
86
+ env?: Record<string, string | undefined>;
87
+ timeout?: number;
88
+ runtime?: unknown;
89
+ }) => Promise<{
90
+ success: boolean;
91
+ stdout: string;
92
+ stderr: string;
93
+ exitCode: number;
94
+ }>;
@@ -0,0 +1,153 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { cancelledNote } from "./format.js";
54
+ import { openWorkspace, workspaceNameFromRuntime } from "./open.js";
55
+ import { withShell } from "./shell.js";
56
+ /**
57
+ * Running a command in a workspace's container, from outside the workspace
58
+ * object: what `/computer`'s tools and `/repo`'s git share, and how a host
59
+ * reaches its own container.
60
+ */
61
+ /** A command that has not finished in this long is a hung command. */
62
+ export const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
63
+ /** Where checkouts live, in the container and in the workspace alike. */
64
+ export const DEFAULT_CWD = "/workspace";
65
+ /**
66
+ * An `exec` bound to this workspace, for plugins that need a shell but should
67
+ * not own a container.
68
+ *
69
+ * `@dynamicagents/plugins/repo` is the reason this exists: it needs `git` on a real
70
+ * shell, but depending on this module would weld the two together and stop a
71
+ * host from pointing it at its own container. Structurally typed for the same
72
+ * reason — the two plugins compose without either importing the other.
73
+ *
74
+ * ## The agent tools' `env` is deliberately **not** merged here
75
+ *
76
+ * `/computer`'s `ComputerConfig.env` is merged into every command its tools
77
+ * run; this takes no environment of its own.
78
+ *
79
+ * What this hands out is a raw shell to *another plugin*, whose commands this
80
+ * module neither writes nor sees. `/repo` runs its unauthenticated git through
81
+ * it and pins the environment those need — `GIT_TERMINAL_PROMPT=0` plus the
82
+ * model-authored values it passes as variables. A host environment merged
83
+ * underneath lets through every key it does *not* pin: `http_proxy`,
84
+ * `GIT_PROXY_COMMAND`, `GIT_SSL_CAINFO`, `GIT_CONFIG_GLOBAL`, `GIT_EXEC_PATH` —
85
+ * the last two redirect the config git reads and relocate its helper binaries.
86
+ *
87
+ * No token is at stake, since the credentialed operations do not run here at
88
+ * all. The point is that changing what git does inside another plugin's commands
89
+ * should not be something a tools plugin's config field does by accident,
90
+ * from a host that only meant to set a registry. A host that wants it composes
91
+ * it where the merge is visible:
92
+ *
93
+ * ```ts
94
+ * exec: (command, options) =>
95
+ * workspaceExec(config)(command, {
96
+ * ...options,
97
+ * env: { ...mine, ...options?.env }
98
+ * });
99
+ * ```
100
+ */
101
+ export function workspaceExec(config) {
102
+ return async (command, options) => {
103
+ const env_1 = { stack: [], error: void 0, hasError: false };
104
+ try {
105
+ // The caller forwards its sub-agent's runtime opaquely; only this side
106
+ // knows the key it might carry. That is what lets `/repo` reach a sub-agent's
107
+ // shared workspace without importing anything from here.
108
+ const name = workspaceNameFromRuntime(options?.runtime) ?? config.workspaceName();
109
+ const ws = __addDisposableResource(env_1, await openWorkspace(config.binding.get(config.binding.idFromName(name))), false);
110
+ const env = options?.env
111
+ ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== undefined))
112
+ : undefined;
113
+ // Named rather than inlined, because the note below has to state it: a
114
+ // ceiling the model cannot see is one it cannot work within.
115
+ const timeoutMs = options?.timeout ?? config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
116
+ // `withShell`, never `withShellTranscript`: the caller reads this result in
117
+ // code. `/repo` compares `stdout` against a URL, tests it for emptiness to
118
+ // decide a tree is clean, and reads a sha out of it to push — all of which
119
+ // need `stdout` to carry the answer alone, with git's diagnostics on the
120
+ // channel git put them on.
121
+ const handle = __addDisposableResource(env_1, await ws.runtime.exec(withShell(command, config.shell), {
122
+ cwd: options?.cwd ?? config.cwd ?? DEFAULT_CWD,
123
+ encoding: "utf8",
124
+ timeoutMs,
125
+ ...(env ? { env } : {})
126
+ }), false);
127
+ const result = await handle.result();
128
+ const killed = cancelledNote(result.status, result.exitCode, timeoutMs);
129
+ return {
130
+ // `/repo` branches on `success`, which this runtime does not report — it
131
+ // has `status` and `exitCode`. Derived from the exit code rather than the
132
+ // status, because a command that runs and fails is `completed` here.
133
+ success: result.exitCode === 0,
134
+ stdout: result.stdout,
135
+ // Appended rather than substituted: a command killed at the ceiling may
136
+ // well have written plenty first, and that output is the more useful half.
137
+ // This is the only field the note can travel on — `stdout` is a data
138
+ // channel the caller parses, and the four fields are the whole contract.
139
+ stderr: killed
140
+ ? [result.stderr, killed].filter(Boolean).join("\n")
141
+ : result.stderr,
142
+ exitCode: result.exitCode
143
+ };
144
+ }
145
+ catch (e_1) {
146
+ env_1.error = e_1;
147
+ env_1.hasError = true;
148
+ }
149
+ finally {
150
+ __disposeResources(env_1);
151
+ }
152
+ };
153
+ }