@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
|
@@ -0,0 +1,224 @@
|
|
|
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 { shellQuote } from "@cloudflare/computer";
|
|
54
|
+
import { deploymentFault } from "./container-fault.js";
|
|
55
|
+
/**
|
|
56
|
+
* Dependency trees live on the container's disk, never in the workspace.
|
|
57
|
+
*
|
|
58
|
+
* A tree in the workspace is written through FUSE into computerd's in-memory
|
|
59
|
+
* store, pulled into this object's SQLite, and pushed back into every fresh
|
|
60
|
+
* container before its first command — measured at 76,740 entries and nine
|
|
61
|
+
* minutes for one cold container. So a package root's `node_modules` is a bind
|
|
62
|
+
* mount of container disk, and a new container reinstalls.
|
|
63
|
+
*
|
|
64
|
+
* The mount is made by the package manager's own invocation, through wrappers
|
|
65
|
+
* this installs ahead of the real binaries on `PATH`. Mounting earlier cannot
|
|
66
|
+
* work: a bootstrap that initialises submodules and installs each of them in one
|
|
67
|
+
* command creates the directories the mounts belong in.
|
|
68
|
+
*/
|
|
69
|
+
/** Where the trees live on the container's disk. */
|
|
70
|
+
const DEPS_DISK = "/var/lib/workspace-deps";
|
|
71
|
+
/** First on Debian's default `PATH`, so a wrapper here shadows the real tool. */
|
|
72
|
+
const WRAPPER_DIR = "/usr/local/sbin";
|
|
73
|
+
const MOUNT_HELPER = `${WRAPPER_DIR}/workspace-deps-mount`;
|
|
74
|
+
/**
|
|
75
|
+
* Every entry point that can write a `node_modules`, wrapped whether or not it
|
|
76
|
+
* is installed yet: `corepack enable` or `npm i -g pnpm` adds one later.
|
|
77
|
+
*/
|
|
78
|
+
const WRAPPED = [
|
|
79
|
+
"npm",
|
|
80
|
+
"npx",
|
|
81
|
+
"pnpm",
|
|
82
|
+
"yarn",
|
|
83
|
+
"corepack",
|
|
84
|
+
"bun",
|
|
85
|
+
"bunx"
|
|
86
|
+
];
|
|
87
|
+
/** Written into the tree by a successful host install; see `./install-job.ts`. */
|
|
88
|
+
export const INSTALLED_MARKER = ".da-installed";
|
|
89
|
+
/**
|
|
90
|
+
* The per-container setup, and a read of the marker under `dir`.
|
|
91
|
+
*
|
|
92
|
+
* The helper mounts the package root the command targets — its directory, or
|
|
93
|
+
* `--prefix`, `--cwd`, `--dir`, `-C` — only under the workspace, never inside
|
|
94
|
+
* another tree. Workspace members are not mounted: a mount point cannot be
|
|
95
|
+
* removed, so one guessed into `dist/` breaks `rm -rf dist`, and an install at
|
|
96
|
+
* the root puts the bulk of the tree there.
|
|
97
|
+
*
|
|
98
|
+
* Wrappers are written with `install`, never through the path: `npm` is
|
|
99
|
+
* commonly a symlink to `npm-cli.js`, and writing through it replaces npm with a
|
|
100
|
+
* wrapper that execs itself. For the same reason a wrapper finds its tool on
|
|
101
|
+
* `PATH` with `WRAPPER_DIR` skipped, when it runs. `corepack enable` puts its
|
|
102
|
+
* shims beside the `corepack` on `PATH` — the wrapper — so it is pointed at the
|
|
103
|
+
* real one's directory instead.
|
|
104
|
+
*/
|
|
105
|
+
export function depsSetupCommand(workspaceDir, dir) {
|
|
106
|
+
const helper = [
|
|
107
|
+
"#!/bin/sh",
|
|
108
|
+
'cwd="$1"; dir="$1"; shift',
|
|
109
|
+
'while [ $# -gt 0 ]; do case "$1" in',
|
|
110
|
+
" --) break;;",
|
|
111
|
+
' --prefix=*|--cwd=*|--dir=*) dir="${1#*=}";;',
|
|
112
|
+
' --prefix|--cwd|--dir|-C) [ $# -gt 1 ] && { dir="$2"; shift; };;',
|
|
113
|
+
"esac; shift; done",
|
|
114
|
+
'case "$dir" in /*) ;; *) dir="$cwd/$dir";; esac',
|
|
115
|
+
'dir=$(cd "$dir" 2>/dev/null && pwd -P) || exit 0',
|
|
116
|
+
'while [ "$dir" != / ] && [ ! -f "$dir/package.json" ]; do dir=$(dirname "$dir"); done',
|
|
117
|
+
`case "$dir" in */node_modules/*) exit 0;; ${workspaceDir}|${workspaceDir}/*) ;; *) exit 0;; esac`,
|
|
118
|
+
'nm="$dir/node_modules"',
|
|
119
|
+
'mountpoint -q "$nm" && exit 0',
|
|
120
|
+
`disk="${DEPS_DISK}/$(printf %s "$dir" | sha256sum | cut -c1-16)"`,
|
|
121
|
+
'mkdir -p "$disk" "$nm" && mount --bind "$disk" "$nm" && exit 0',
|
|
122
|
+
`echo "workspace-deps: could not put $nm on the container's disk" >&2`,
|
|
123
|
+
"exit 1"
|
|
124
|
+
].join("\n");
|
|
125
|
+
const wrapper = [
|
|
126
|
+
"#!/bin/sh",
|
|
127
|
+
`${MOUNT_HELPER} "$PWD" "$@" || exit 1`,
|
|
128
|
+
'tool=$(basename "$0"); real=',
|
|
129
|
+
`IFS=:; for d in $PATH; do [ "$d" = ${WRAPPER_DIR} ] && continue; [ -x "$d/$tool" ] && { real="$d"; break; }; done; unset IFS`,
|
|
130
|
+
'[ -n "$real" ] || { echo "$tool: not found" >&2; exit 127; }',
|
|
131
|
+
'case "$tool:${1:-}:$*" in',
|
|
132
|
+
" *--install-directory*) ;;",
|
|
133
|
+
' corepack:enable:*|corepack:disable:*) cmd="$1"; shift; set -- "$cmd" --install-directory "$real" "$@";;',
|
|
134
|
+
"esac",
|
|
135
|
+
'exec "$real/$tool" "$@"'
|
|
136
|
+
].join("\n");
|
|
137
|
+
return [
|
|
138
|
+
`printf '%s\\n' ${shellQuote(helper)} > /tmp/workspace-deps-mount`,
|
|
139
|
+
`install -m 755 /tmp/workspace-deps-mount ${MOUNT_HELPER} || exit 1`,
|
|
140
|
+
`printf '%s\\n' ${shellQuote(wrapper)} > /tmp/workspace-deps-wrap`,
|
|
141
|
+
`for tool in ${WRAPPED.join(" ")}; do install -m 755 /tmp/workspace-deps-wrap "${WRAPPER_DIR}/$tool" || exit 1; done`,
|
|
142
|
+
...(dir
|
|
143
|
+
? [
|
|
144
|
+
`echo "MARKER $(cat ${shellQuote(`${dir}/node_modules/${INSTALLED_MARKER}`)} 2>/dev/null || echo -)"`
|
|
145
|
+
]
|
|
146
|
+
: []),
|
|
147
|
+
"echo SETUP OK"
|
|
148
|
+
].join("\n");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The setup above, once per container.
|
|
152
|
+
*
|
|
153
|
+
* In memory, with {@link file://./ca-trust.ts}'s lifetime and for its reason: it
|
|
154
|
+
* describes one container, and an isolate that lost it re-runs an idempotent
|
|
155
|
+
* command.
|
|
156
|
+
*/
|
|
157
|
+
export class ContainerDeps {
|
|
158
|
+
deps;
|
|
159
|
+
#done = false;
|
|
160
|
+
constructor(deps) {
|
|
161
|
+
this.deps = deps;
|
|
162
|
+
}
|
|
163
|
+
forget() {
|
|
164
|
+
this.#done = false;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Run the setup if this container has not had it, and report the marker under
|
|
168
|
+
* `dir`. `undefined` when it had, or with no `dir`.
|
|
169
|
+
*
|
|
170
|
+
* Throws when the container answered and the setup did not finish: nothing
|
|
171
|
+
* would say so later, since an unwrapped install succeeds, into the workspace.
|
|
172
|
+
* An unreachable container is only logged, like the CA install: the command
|
|
173
|
+
* after it fails the same way.
|
|
174
|
+
*/
|
|
175
|
+
async ensure(dir) {
|
|
176
|
+
if (this.#done)
|
|
177
|
+
return undefined;
|
|
178
|
+
const startedAt = Date.now();
|
|
179
|
+
let output;
|
|
180
|
+
try {
|
|
181
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
182
|
+
try {
|
|
183
|
+
const handle = __addDisposableResource(env_1, await this.deps
|
|
184
|
+
.workspace()
|
|
185
|
+
.runtime.exec(depsSetupCommand(this.deps.workspaceDir, dir), {
|
|
186
|
+
cwd: "/",
|
|
187
|
+
encoding: "utf8",
|
|
188
|
+
timeoutMs: 30_000
|
|
189
|
+
}), false);
|
|
190
|
+
const result = await handle.result();
|
|
191
|
+
output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
192
|
+
this.#done = result.exitCode === 0 && output.includes("SETUP OK");
|
|
193
|
+
}
|
|
194
|
+
catch (e_1) {
|
|
195
|
+
env_1.error = e_1;
|
|
196
|
+
env_1.hasError = true;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
__disposeResources(env_1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const fault = deploymentFault(err);
|
|
204
|
+
console.warn(`[${this.deps.tag()}] ${fault?.summary ?? "could not set up container dependencies"}`, {
|
|
205
|
+
id: this.deps.id(),
|
|
206
|
+
...(fault ? { remedy: fault.remedy } : {}),
|
|
207
|
+
err: String(err)
|
|
208
|
+
});
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
console.info(`[${this.deps.tag()}] container dependency setup`, {
|
|
212
|
+
id: this.deps.id(),
|
|
213
|
+
ok: this.#done,
|
|
214
|
+
ms: Date.now() - startedAt,
|
|
215
|
+
...(this.#done ? {} : { output })
|
|
216
|
+
});
|
|
217
|
+
if (!this.#done)
|
|
218
|
+
throw new Error(`could not put node_modules on the container's disk: ${output.trim() || "the setup exited without output"}`);
|
|
219
|
+
const marker = /^MARKER (.*)$/m.exec(output)?.[1]?.trim();
|
|
220
|
+
if (marker === undefined)
|
|
221
|
+
return undefined;
|
|
222
|
+
return { marker: marker === "-" ? null : marker };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The container failures that are the deployment's fault, not the workspace's.
|
|
3
|
+
*
|
|
4
|
+
* Everything else this host catches is the kind of thing a retry clears: a
|
|
5
|
+
* container that is starting, a connection that dropped, a command that died
|
|
6
|
+
* with it. These are not. They describe a Worker and a container application
|
|
7
|
+
* that cannot work together no matter how many times they are asked, and they
|
|
8
|
+
* end only when an operator deploys.
|
|
9
|
+
*
|
|
10
|
+
* **This changes what is said, not what is done.** No caller branches its
|
|
11
|
+
* control flow on the answer, and none should start: the match is on a string
|
|
12
|
+
* the library is free to reword, so a misread has to cost a misleading sentence
|
|
13
|
+
* rather than a workspace that stopped trying. What it earns is the log line.
|
|
14
|
+
* The cause is already in the thrown message, but a caller logs its own
|
|
15
|
+
* sentence and the cause travels in a detail field a log digest truncates — so
|
|
16
|
+
* an operator scanning for what broke reads the symptom ("could not trust the
|
|
17
|
+
* interception CA") and not the reason. {@link deploymentFault} is how a caller
|
|
18
|
+
* puts the reason in the message instead, and how the install record stops
|
|
19
|
+
* offering advice that cannot work.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* A failure no retry can clear, in the two registers a caller needs.
|
|
23
|
+
*
|
|
24
|
+
* `summary` is the log message: what is wrong, short enough to scan. `remedy`
|
|
25
|
+
* goes to whoever is blocked by it — it reaches a model through the install
|
|
26
|
+
* record, so it says what a person has to do rather than what a tool should try.
|
|
27
|
+
*/
|
|
28
|
+
export interface DeploymentFault {
|
|
29
|
+
readonly summary: string;
|
|
30
|
+
readonly remedy: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether this error says the deployment is wrong, and what to say about it.
|
|
34
|
+
*
|
|
35
|
+
* `undefined` for everything else, which is the answer that keeps the existing
|
|
36
|
+
* behaviour: a caller that gets it logs and retries exactly as before.
|
|
37
|
+
*/
|
|
38
|
+
export declare function deploymentFault(err: unknown): DeploymentFault | undefined;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The container failures that are the deployment's fault, not the workspace's.
|
|
3
|
+
*
|
|
4
|
+
* Everything else this host catches is the kind of thing a retry clears: a
|
|
5
|
+
* container that is starting, a connection that dropped, a command that died
|
|
6
|
+
* with it. These are not. They describe a Worker and a container application
|
|
7
|
+
* that cannot work together no matter how many times they are asked, and they
|
|
8
|
+
* end only when an operator deploys.
|
|
9
|
+
*
|
|
10
|
+
* **This changes what is said, not what is done.** No caller branches its
|
|
11
|
+
* control flow on the answer, and none should start: the match is on a string
|
|
12
|
+
* the library is free to reword, so a misread has to cost a misleading sentence
|
|
13
|
+
* rather than a workspace that stopped trying. What it earns is the log line.
|
|
14
|
+
* The cause is already in the thrown message, but a caller logs its own
|
|
15
|
+
* sentence and the cause travels in a detail field a log digest truncates — so
|
|
16
|
+
* an operator scanning for what broke reads the symptom ("could not trust the
|
|
17
|
+
* interception CA") and not the reason. {@link deploymentFault} is how a caller
|
|
18
|
+
* puts the reason in the message instead, and how the install record stops
|
|
19
|
+
* offering advice that cannot work.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Match on the shape the backend formats, not on the prose after it.
|
|
23
|
+
*
|
|
24
|
+
* `[stage=auth]` is the stage label `ContainerBackend` writes into every
|
|
25
|
+
* message it throws from that stage, and the container-application sentence is
|
|
26
|
+
* the runtime's own, surfaced verbatim through the backend's `priorExit=` field.
|
|
27
|
+
* `prepared images` is in both of the backend's refusals to start without an
|
|
28
|
+
* image — none prepared at all, and none under the key it was asked for — and
|
|
29
|
+
* survives the reconnect wrapper's truncation of either. All are stabler than
|
|
30
|
+
* the explanations they introduce, and none has a `code` to key on the way a
|
|
31
|
+
* lost exec does.
|
|
32
|
+
*/
|
|
33
|
+
const AUTH_STAGE = "[stage=auth]";
|
|
34
|
+
const NO_APPLICATION = "no container application";
|
|
35
|
+
const NO_IMAGE = "prepared images";
|
|
36
|
+
/**
|
|
37
|
+
* Whether this error says the deployment is wrong, and what to say about it.
|
|
38
|
+
*
|
|
39
|
+
* `undefined` for everything else, which is the answer that keeps the existing
|
|
40
|
+
* behaviour: a caller that gets it logs and retries exactly as before.
|
|
41
|
+
*/
|
|
42
|
+
export function deploymentFault(err) {
|
|
43
|
+
const text = String(err);
|
|
44
|
+
if (text.includes(AUTH_STAGE))
|
|
45
|
+
return {
|
|
46
|
+
summary: "the container image is older than this Worker",
|
|
47
|
+
remedy: "the container is running an image that predates this Worker, so the " +
|
|
48
|
+
"two cannot authenticate and nothing can reach it. The image and the " +
|
|
49
|
+
"Worker are released as a pair and have to be deployed together, so " +
|
|
50
|
+
"this clears when an operator redeploys the Worker with its container " +
|
|
51
|
+
"image — no command here can recover it."
|
|
52
|
+
};
|
|
53
|
+
if (text.includes(NO_APPLICATION))
|
|
54
|
+
return {
|
|
55
|
+
summary: "this Worker has no container application",
|
|
56
|
+
remedy: "the container application this workspace starts containers from does " +
|
|
57
|
+
"not exist, so no container can start. Deploying the Worker is what " +
|
|
58
|
+
"creates it — deleting it does not cause it to be rebuilt — so this " +
|
|
59
|
+
"clears when an operator redeploys, and not before."
|
|
60
|
+
};
|
|
61
|
+
if (text.includes(NO_IMAGE))
|
|
62
|
+
return {
|
|
63
|
+
summary: "this Worker prepared no image for the workspace container",
|
|
64
|
+
remedy: "the container application has no image under the name this workspace " +
|
|
65
|
+
"starts, so no container can start. The workspace schedules its own " +
|
|
66
|
+
'containers, which takes `scheduling_policy: "durable_object"` and an ' +
|
|
67
|
+
"`images` entry for that name in the Worker's containers block, and an " +
|
|
68
|
+
"application created under another policy is deleted before that " +
|
|
69
|
+
"deploy rather than switched. This clears when an operator redeploys " +
|
|
70
|
+
"with both in place, and not before."
|
|
71
|
+
};
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WorkspaceRuntimeStatus } from "@cloudflare/computer";
|
|
2
|
+
/**
|
|
3
|
+
* Text shared by the workspace and the tools over it: the output bound, the
|
|
4
|
+
* elapsed-time phrase, and what a killed command is told.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Middle-out truncation, so both the first error and the final summary survive.
|
|
8
|
+
*
|
|
9
|
+
* The guard is not defensive padding. Without it a `max` at or below the marker's
|
|
10
|
+
* own length makes `half` zero or negative, and `slice(-0)` is `slice(0)` — the
|
|
11
|
+
* *whole* string — so the function returns more than it was given: 500 characters
|
|
12
|
+
* in, 543 out at `max: 80`. A silent inversion of the one thing it does,
|
|
13
|
+
* reachable from a public config field.
|
|
14
|
+
*/
|
|
15
|
+
export declare function truncateOutput(text: string, max: number): string;
|
|
16
|
+
/** Elapsed time in the shape a sentence wants. */
|
|
17
|
+
export declare function humanMs(ms: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* Why a command has nothing to show for itself.
|
|
20
|
+
*
|
|
21
|
+
* {@link renderResult} states a non-`completed` status on its verdict line;
|
|
22
|
+
* {@link file://./exec.ts workspaceExec} has no verdict line, only the four fields its caller
|
|
23
|
+
* branches on. A *killed* process writes nothing, so without this a `git clone`
|
|
24
|
+
* that hit the ten-minute ceiling reaches the model through
|
|
25
|
+
* `@dynamicagents/plugins/repo` as `clone failed:` with an empty reason — which reads
|
|
26
|
+
* like a bug in the plugin rather than a limit it can work within.
|
|
27
|
+
*
|
|
28
|
+
* Only `cancelled`. A `failed` status is an ordinary non-zero exit, where the
|
|
29
|
+
* command's own stderr is the better explanation and this would be noise on top
|
|
30
|
+
* of it.
|
|
31
|
+
*
|
|
32
|
+
* Both plausible causes are named because the exit code cannot separate them:
|
|
33
|
+
* 137 is SIGKILL, which is what the timeout sends *and* what the kernel sends a
|
|
34
|
+
* container that ran out of memory.
|
|
35
|
+
*/
|
|
36
|
+
export declare function cancelledNote(status: WorkspaceRuntimeStatus | undefined, exitCode: number, timeoutMs: number): string | undefined;
|