@dynamicagents/plugins 0.14.0 → 0.15.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 +14 -14
- package/dist/claude-code/cards.d.ts +2 -0
- package/dist/claude-code/cards.js +11 -0
- package/dist/claude-code/config.d.ts +10 -83
- package/dist/claude-code/config.js +0 -2
- package/dist/claude-code/credentials.d.ts +32 -50
- package/dist/claude-code/credentials.js +115 -74
- package/dist/claude-code/daemon/index.d.ts +24 -0
- package/dist/claude-code/daemon/index.js +203 -0
- package/dist/claude-code/egress.d.ts +35 -117
- package/dist/claude-code/egress.js +80 -325
- package/dist/claude-code/events.d.ts +14 -17
- package/dist/claude-code/events.js +19 -15
- package/dist/claude-code/harness-wire.d.ts +45 -0
- package/dist/claude-code/harness-wire.js +47 -0
- package/dist/claude-code/index.d.ts +19 -29
- package/dist/claude-code/index.js +17 -25
- package/dist/claude-code/language-model.d.ts +19 -0
- package/dist/claude-code/language-model.js +71 -0
- package/dist/claude-code/model.d.ts +77 -70
- package/dist/claude-code/model.js +327 -327
- package/dist/claude-code/placeholder.d.ts +12 -0
- package/dist/claude-code/placeholder.js +12 -0
- package/dist/claude-code/spec.d.ts +4 -4
- package/dist/claude-code/spec.js +4 -4
- package/dist/computer/client.d.ts +88 -0
- package/dist/computer/client.js +6 -0
- package/dist/computer/context.d.ts +7 -7
- package/dist/computer/context.js +14 -18
- package/dist/computer/gate.d.ts +4 -23
- package/dist/computer/gate.js +1 -37
- package/dist/computer/index.d.ts +44 -43
- package/dist/computer/index.js +29 -50
- package/dist/computer/paths.d.ts +5 -27
- package/dist/computer/paths.js +7 -30
- package/dist/computer/proxy.d.ts +17 -31
- package/dist/computer/proxy.js +9 -44
- package/dist/computer/render.d.ts +3 -40
- package/dist/computer/render.js +3 -36
- package/dist/computer/sandbox.d.ts +55 -0
- package/dist/computer/sandbox.js +236 -0
- package/dist/computer/shell.d.ts +36 -0
- package/dist/computer/shell.js +41 -0
- package/dist/computer/tools-exec.js +3 -9
- package/dist/computer/tools-file.d.ts +1 -1
- package/dist/computer/tools-file.js +1 -1
- package/dist/{repo → repo-github}/checkout.d.ts +3 -4
- package/dist/{repo → repo-github}/checkout.js +3 -4
- package/dist/{repo → repo-github}/context.d.ts +14 -32
- package/dist/{repo → repo-github}/context.js +46 -81
- package/dist/{repo → repo-github}/index.d.ts +30 -13
- package/dist/{repo → repo-github}/index.js +25 -6
- package/dist/repo-github/mcp.d.ts +95 -0
- package/dist/repo-github/mcp.js +107 -0
- package/dist/repo-github/sandbox-egress.d.ts +68 -0
- package/dist/repo-github/sandbox-egress.js +272 -0
- package/dist/repo-github/sandbox-git.d.ts +30 -0
- package/dist/repo-github/sandbox-git.js +113 -0
- package/dist/{repo → repo-github}/tools-checks.js +43 -4
- package/dist/{repo → repo-github}/tools-clone.js +2 -21
- package/dist/{repo → repo-github}/tools-pr.d.ts +0 -1
- package/dist/{repo → repo-github}/tools-pr.js +85 -20
- package/dist/{repo → repo-github}/tools-review.d.ts +6 -0
- package/dist/{repo → repo-github}/tools-review.js +62 -176
- package/dist/{repo → repo-github}/url.d.ts +11 -0
- package/dist/{repo → repo-github}/url.js +12 -1
- package/dist/{workspace → sandbox}/advisory.d.ts +16 -35
- package/dist/{workspace → sandbox}/advisory.js +2 -29
- package/dist/sandbox/agent-exec.d.ts +31 -0
- package/dist/sandbox/agent-exec.js +62 -0
- package/dist/sandbox/ca-trust.d.ts +9 -0
- package/dist/sandbox/ca-trust.js +21 -0
- package/dist/sandbox/constants.d.ts +22 -0
- package/dist/sandbox/constants.js +22 -0
- package/dist/sandbox/daemon/composite.d.ts +9 -0
- package/dist/sandbox/daemon/composite.js +35 -0
- package/dist/sandbox/daemon/index.d.ts +47 -0
- package/dist/sandbox/daemon/index.js +116 -0
- package/dist/sandbox/egress.d.ts +81 -0
- package/dist/sandbox/egress.js +109 -0
- package/dist/sandbox/exec.d.ts +57 -0
- package/dist/sandbox/exec.js +52 -0
- package/dist/sandbox/format.d.ts +2 -0
- package/dist/sandbox/format.js +7 -0
- package/dist/sandbox/header.d.ts +50 -0
- package/dist/sandbox/header.js +94 -0
- package/dist/sandbox/index.d.ts +17 -0
- package/dist/sandbox/index.js +17 -0
- package/dist/{workspace/install.d.ts → sandbox/install-plan.d.ts} +2 -2
- package/dist/{workspace/install.js → sandbox/install-plan.js} +2 -2
- package/dist/sandbox/name.d.ts +27 -0
- package/dist/sandbox/name.js +32 -0
- package/dist/sandbox/object.d.ts +244 -0
- package/dist/sandbox/object.js +1139 -0
- package/dist/scratch/index.d.ts +1 -9
- package/dist/scratch/index.js +2 -18
- package/package.json +22 -18
- package/dist/claude-code/github.d.ts +0 -39
- package/dist/claude-code/github.js +0 -82
- package/dist/claude-code/run.d.ts +0 -491
- package/dist/claude-code/run.js +0 -913
- package/dist/claude-code/session.d.ts +0 -131
- package/dist/claude-code/session.js +0 -288
- package/dist/workspace/ca-trust.d.ts +0 -95
- package/dist/workspace/ca-trust.js +0 -312
- package/dist/workspace/container-deps.d.ts +0 -58
- package/dist/workspace/container-deps.js +0 -249
- package/dist/workspace/container-fault.d.ts +0 -38
- package/dist/workspace/container-fault.js +0 -73
- package/dist/workspace/deps-snapshot.d.ts +0 -110
- package/dist/workspace/deps-snapshot.js +0 -424
- package/dist/workspace/exec.d.ts +0 -94
- package/dist/workspace/exec.js +0 -153
- package/dist/workspace/format.d.ts +0 -36
- package/dist/workspace/format.js +0 -57
- package/dist/workspace/git-host.d.ts +0 -79
- package/dist/workspace/git-host.js +0 -226
- package/dist/workspace/git-identity.d.ts +0 -57
- package/dist/workspace/git-identity.js +0 -147
- package/dist/workspace/index.d.ts +0 -50
- package/dist/workspace/index.js +0 -58
- package/dist/workspace/install-job.d.ts +0 -170
- package/dist/workspace/install-job.js +0 -934
- package/dist/workspace/object.d.ts +0 -522
- package/dist/workspace/object.js +0 -1541
- package/dist/workspace/open.d.ts +0 -95
- package/dist/workspace/open.js +0 -63
- package/dist/workspace/read.d.ts +0 -16
- package/dist/workspace/read.js +0 -22
- package/dist/workspace/shell.d.ts +0 -45
- package/dist/workspace/shell.js +0 -80
- package/dist/workspace/sync.d.ts +0 -111
- package/dist/workspace/sync.js +0 -124
- package/dist/workspace/wake.d.ts +0 -21
- package/dist/workspace/wake.js +0 -10
- /package/dist/{repo → repo-github}/tools-checks.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-clone.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktree.js +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.d.ts +0 -0
- /package/dist/{repo → repo-github}/tools-worktrees.js +0 -0
|
@@ -1,312 +0,0 @@
|
|
|
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 { deploymentFault } from "./container-fault.js";
|
|
54
|
-
/**
|
|
55
|
-
* Making a container able to speak TLS, once there is a CA to trust.
|
|
56
|
-
*
|
|
57
|
-
* Only `egress: { mode: "http-gateway" }` needs this, but it lives on every
|
|
58
|
-
* workspace because the cost of getting it wrong is asymmetric: the trust
|
|
59
|
-
* install against a container with no CA prints one line and exits 0, while a
|
|
60
|
-
* container that missed it fails every later HTTPS connection with an error
|
|
61
|
-
* that names no cause.
|
|
62
|
-
*/
|
|
63
|
-
/**
|
|
64
|
-
* Install the egress interception CA, and say what was there to install.
|
|
65
|
-
*
|
|
66
|
-
* One shell string rather than a script in the image, because **the image cannot
|
|
67
|
-
* run this at all**, and the reason is ordering rather than preference.
|
|
68
|
-
* Cloudflare's own recipe assumes interception is declared as container
|
|
69
|
-
* configuration, so the runtime mounts the CA before the container starts.
|
|
70
|
-
* `@cloudflare/computer` uses the raw container API, where the only interception
|
|
71
|
-
* hooks are methods called *after* `start()` — so an entrypoint runs strictly
|
|
72
|
-
* before the CA can exist, finds nothing, and reports the container
|
|
73
|
-
* un-intercepted while every later TLS connection fails.
|
|
74
|
-
*
|
|
75
|
-
* The listing comes first and runs unconditionally: when the CA is missing,
|
|
76
|
-
* *what else is in that directory* is the only evidence distinguishing "mounted
|
|
77
|
-
* somewhere else" from "never provisioned", and it costs one line.
|
|
78
|
-
*/
|
|
79
|
-
export const TRUST_CA_COMMAND = [
|
|
80
|
-
'ls -A /etc/cloudflare/certs 2>&1 || echo "(no /etc/cloudflare/certs)"',
|
|
81
|
-
"CA=/etc/cloudflare/certs/cloudflare-containers-ca.crt",
|
|
82
|
-
'if [ -r "$CA" ]; then',
|
|
83
|
-
// Trailing operators, not leading ones: a newline ends a command in sh, so an
|
|
84
|
-
// `&&` opening the next line is a syntax error rather than a continuation.
|
|
85
|
-
' install -m 644 "$CA" /usr/local/share/ca-certificates/cloudflare-containers-ca.crt &&',
|
|
86
|
-
" update-ca-certificates > /dev/null 2>&1 &&",
|
|
87
|
-
' echo TRUSTED || echo "TRUST FAILED"',
|
|
88
|
-
"else",
|
|
89
|
-
' echo "NO CA AT $CA"',
|
|
90
|
-
"fi"
|
|
91
|
-
].join("\n");
|
|
92
|
-
/** How long the trust install may take before it is not going to happen. */
|
|
93
|
-
const TRUST_TIMEOUT_MS = 30_000;
|
|
94
|
-
/**
|
|
95
|
-
* Whether the container this object is talking to has had the CA installed.
|
|
96
|
-
*
|
|
97
|
-
* ## Why the answer is in memory
|
|
98
|
-
*
|
|
99
|
-
* What it describes is a *container*, which does not outlive the isolate in any
|
|
100
|
-
* way worth persisting. A stored `true` would be the exact bug this exists to
|
|
101
|
-
* prevent — a flag surviving the container it describes, and vouching for its
|
|
102
|
-
* replacement.
|
|
103
|
-
*
|
|
104
|
-
* It is wrong only in the safe direction. An isolate that lost it re-runs an
|
|
105
|
-
* idempotent `install -m 644`.
|
|
106
|
-
*
|
|
107
|
-
* ## What clears it
|
|
108
|
-
*
|
|
109
|
-
* **The container's own exit**, watched through `monitor()`. This is the signal
|
|
110
|
-
* that does not depend on anyone noticing: a container replaced underneath a
|
|
111
|
-
* running command — by a reconnect, a relaunch, a crash — exits, and the watch
|
|
112
|
-
* fires whoever caused it. Nothing else here is in a position to know, because
|
|
113
|
-
* most commands run through the stub this object hands out and report their
|
|
114
|
-
* failures to their own caller, not to it.
|
|
115
|
-
*
|
|
116
|
-
* The watch lives in memory beside the flag it clears, which is the right
|
|
117
|
-
* lifetime: an isolate that loses one loses the other, and the replacement
|
|
118
|
-
* starts out untrusted. It is deliberately not held open with `waitUntil` —
|
|
119
|
-
* that would keep an invocation alive for the life of the container.
|
|
120
|
-
*
|
|
121
|
-
* A container seen not running, and a command reporting `EEXEC_LOST`, both call
|
|
122
|
-
* {@link forget} as well. They are cheap, they are strictly earlier in some
|
|
123
|
-
* orderings, and neither is load-bearing on its own.
|
|
124
|
-
*/
|
|
125
|
-
export class ContainerTrust {
|
|
126
|
-
deps;
|
|
127
|
-
#trusted = false;
|
|
128
|
-
/** Whether an exit watch is standing for the container now running. */
|
|
129
|
-
#watching = false;
|
|
130
|
-
/** Bumped by {@link forget}, so a watch armed before it is ignored. */
|
|
131
|
-
#generation = 0;
|
|
132
|
-
/**
|
|
133
|
-
* Whether this container's deployment fault has already been reported.
|
|
134
|
-
*
|
|
135
|
-
* Same lifetime as {@link ContainerTrust.forget}'s other flag, and for the
|
|
136
|
-
* same reason: what it describes is one container. A fault that outlived it
|
|
137
|
-
* would silence the report for the replacement, which is the container an
|
|
138
|
-
* operator is waiting to hear about.
|
|
139
|
-
*/
|
|
140
|
-
#faultReported = false;
|
|
141
|
-
constructor(deps) {
|
|
142
|
-
this.deps = deps;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Forget that any container was trusted.
|
|
146
|
-
*
|
|
147
|
-
* Drops the standing watch too, because this is also reached for a container
|
|
148
|
-
* that keeps running — a `running` read, a lost exec. The watch it leaves
|
|
149
|
-
* behind ignores that container's real exit, so without a fresh one nothing
|
|
150
|
-
* would clear the flag again and the replacement would be vouched for.
|
|
151
|
-
*/
|
|
152
|
-
forget() {
|
|
153
|
-
this.#generation += 1;
|
|
154
|
-
this.#trusted = false;
|
|
155
|
-
this.#watching = false;
|
|
156
|
-
this.#faultReported = false;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Watch this container's exit, so its replacement is not trusted on its
|
|
160
|
-
* behalf.
|
|
161
|
-
*
|
|
162
|
-
* Re-armed rather than kept: the promise settles once, for the container it
|
|
163
|
-
* was taken from, so the next {@link ensure} takes a fresh one for whatever is
|
|
164
|
-
* running then.
|
|
165
|
-
*
|
|
166
|
-
* A rejection is an exit too — the runtime reporting the container went away
|
|
167
|
-
* badly — so both settlements clear the flag, and neither is allowed to
|
|
168
|
-
* surface as an unhandled rejection.
|
|
169
|
-
*
|
|
170
|
-
* An exit already accounted for — the container was forgotten since — is not
|
|
171
|
-
* reported: a replacement may be running by then, and it is not the one that
|
|
172
|
-
* exited.
|
|
173
|
-
*/
|
|
174
|
-
#watch() {
|
|
175
|
-
if (this.#watching)
|
|
176
|
-
return;
|
|
177
|
-
const container = this.deps.container();
|
|
178
|
-
if (!container)
|
|
179
|
-
return;
|
|
180
|
-
const generation = this.#generation;
|
|
181
|
-
try {
|
|
182
|
-
// A stale watch returns before touching anything: `#watching` belongs to
|
|
183
|
-
// whichever watch was armed since.
|
|
184
|
-
const done = () => {
|
|
185
|
-
if (generation !== this.#generation)
|
|
186
|
-
return;
|
|
187
|
-
this.forget();
|
|
188
|
-
this.deps.onExit?.();
|
|
189
|
-
};
|
|
190
|
-
container.monitor().then(done, done);
|
|
191
|
-
this.#watching = true;
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
// No watch, so the flag keeps its other two ways of being cleared. Worth a
|
|
195
|
-
// line because it silently widens the window this class exists to close.
|
|
196
|
-
console.warn(`[${this.deps.tag()}] could not watch the container`, {
|
|
197
|
-
id: this.deps.id(),
|
|
198
|
-
err: String(err)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Trust the CA if this container has not been trusted yet.
|
|
204
|
-
*
|
|
205
|
-
* Called from the workspace's `ready()` path, which is the earliest point that
|
|
206
|
-
* is not too early: the backend's `connect()` has completed, so start and
|
|
207
|
-
* interception have both happened.
|
|
208
|
-
*
|
|
209
|
-
* **Never throws.** A workspace that cannot trust the CA still has a checkout,
|
|
210
|
-
* a shell and a git history, and whatever runs next will say plainly what went
|
|
211
|
-
* wrong — refusing to proceed here would replace a legible TLS error with an
|
|
212
|
-
* opaque one.
|
|
213
|
-
*/
|
|
214
|
-
async ensure() {
|
|
215
|
-
this.#watch();
|
|
216
|
-
if (this.#trusted)
|
|
217
|
-
return;
|
|
218
|
-
// An exec pushes the workspace before it spawns, and a new container is
|
|
219
|
-
// empty, so this one — the first in every container — carries the whole
|
|
220
|
-
// tree. {@link TRUST_TIMEOUT_MS} bounds the process, not any of that.
|
|
221
|
-
const startedAt = Date.now();
|
|
222
|
-
try {
|
|
223
|
-
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
224
|
-
try {
|
|
225
|
-
const handle = __addDisposableResource(env_1, await this.deps
|
|
226
|
-
.workspace()
|
|
227
|
-
.runtime.exec(TRUST_CA_COMMAND, {
|
|
228
|
-
cwd: "/",
|
|
229
|
-
encoding: "utf8",
|
|
230
|
-
timeoutMs: TRUST_TIMEOUT_MS
|
|
231
|
-
}), false);
|
|
232
|
-
const result = await handle.result();
|
|
233
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
234
|
-
// Marked only when the command reported an outcome that will not change
|
|
235
|
-
// inside this container. `TRUSTED` is the success; `NO CA AT …` is the
|
|
236
|
-
// intentional terminal case, since interception is configured by the
|
|
237
|
-
// `connect()` already awaited and a CA absent now stays absent until the
|
|
238
|
-
// container is replaced — retrying that on the object's hottest path would
|
|
239
|
-
// buy nothing.
|
|
240
|
-
//
|
|
241
|
-
// `TRUST FAILED` is neither. The command exits 0 whatever happens, so
|
|
242
|
-
// treating "it ran" as "it worked" would mark a container trusted whose
|
|
243
|
-
// `install` or `update-ca-certificates` failed — and every later HTTPS
|
|
244
|
-
// connection in it fails with nothing naming the cause, permanently,
|
|
245
|
-
// because nothing tries again.
|
|
246
|
-
//
|
|
247
|
-
// A throw leaves this false too, so an unreachable container is retried.
|
|
248
|
-
this.#trusted = !output.includes("TRUST FAILED");
|
|
249
|
-
// Logged at info once per container, deliberately. The container's own
|
|
250
|
-
// stdout does not reach Workers Observability, so this line is the only
|
|
251
|
-
// place an operator can see whether the container can speak TLS at all —
|
|
252
|
-
// and its absence is itself the answer when a workspace never got this far.
|
|
253
|
-
//
|
|
254
|
-
// Once per container rather than per call is also what makes it readable:
|
|
255
|
-
// on the busiest entry point in the object, a line per call would bury the
|
|
256
|
-
// one that matters under thousands that say the same thing.
|
|
257
|
-
console.info(`[${this.deps.tag()}] container TLS trust`, {
|
|
258
|
-
id: this.deps.id(),
|
|
259
|
-
exitCode: result.exitCode,
|
|
260
|
-
trusted: this.#trusted,
|
|
261
|
-
// Where a cold workspace's time went, on the line already logged once
|
|
262
|
-
// per container.
|
|
263
|
-
pushed: result.pushed,
|
|
264
|
-
ms: Date.now() - startedAt,
|
|
265
|
-
output
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
catch (e_1) {
|
|
269
|
-
env_1.error = e_1;
|
|
270
|
-
env_1.hasError = true;
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
__disposeResources(env_1);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
catch (err) {
|
|
277
|
-
// A deployment fault is reported as itself, at error, once per container.
|
|
278
|
-
// The generic line below is the right one for a container that is merely
|
|
279
|
-
// unreachable — a retry is what clears that. It is the wrong one for a
|
|
280
|
-
// Worker whose image or container application cannot work at all:
|
|
281
|
-
// retrying logs the same symptom until something else gives up, and the
|
|
282
|
-
// sentence naming the cause is the one an operator needs.
|
|
283
|
-
//
|
|
284
|
-
// Only the *log* stops. The trust command is still sent on the next call,
|
|
285
|
-
// because the classification is a string match and this is the direction
|
|
286
|
-
// that is safe to be wrong in: a misread costs a line nobody needed, where
|
|
287
|
-
// refusing to retry would leave a container permanently untrusted on the
|
|
288
|
-
// strength of a message the library is free to reword. What actually ends
|
|
289
|
-
// the reporting is the container going away, which clears the flag through
|
|
290
|
-
// the same watch that clears the trust.
|
|
291
|
-
const fault = deploymentFault(err);
|
|
292
|
-
if (fault) {
|
|
293
|
-
if (!this.#faultReported) {
|
|
294
|
-
this.#faultReported = true;
|
|
295
|
-
console.error(`[${this.deps.tag()}] ${fault.summary}`, {
|
|
296
|
-
id: this.deps.id(),
|
|
297
|
-
remedy: fault.remedy,
|
|
298
|
-
err: String(err)
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
console.warn(`[${this.deps.tag()}] could not trust the interception CA`, {
|
|
304
|
-
id: this.deps.id(),
|
|
305
|
-
// Separates a container that refused at once from one that was pushed
|
|
306
|
-
// to and then dropped.
|
|
307
|
-
ms: Date.now() - startedAt,
|
|
308
|
-
err: String(err)
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type Workspace } from "@cloudflare/computer";
|
|
2
|
-
/** Written into the tree by a successful host install; see `./install-job.ts`. */
|
|
3
|
-
export declare const INSTALLED_MARKER = ".da-installed";
|
|
4
|
-
/** Every package root with a tree on this container's disk, one per line. */
|
|
5
|
-
export declare const DEPS_ROOTS_COMMAND = "for f in /var/lib/workspace-deps/*.root; do [ -f \"$f\" ] && { cat \"$f\"; echo; }; done; true";
|
|
6
|
-
/** {@link DEPS_ROOTS_COMMAND}'s output as paths, each once. */
|
|
7
|
-
export declare function parseDepsRoots(stdout: string): string[];
|
|
8
|
-
/**
|
|
9
|
-
* The per-container setup, and a read of the marker under `dir`.
|
|
10
|
-
*
|
|
11
|
-
* The helper mounts the package root the command targets — its directory, or
|
|
12
|
-
* `--prefix`, `--cwd`, `--dir`, `-C` — only under the workspace, never inside
|
|
13
|
-
* another tree. Workspace members are not mounted: a mount point cannot be
|
|
14
|
-
* removed, so one guessed into `dist/` breaks `rm -rf dist`, and an install at
|
|
15
|
-
* the root puts the bulk of the tree there.
|
|
16
|
-
*
|
|
17
|
-
* Wrappers are written with `install`, never through the path: `npm` is
|
|
18
|
-
* commonly a symlink to `npm-cli.js`, and writing through it replaces npm with a
|
|
19
|
-
* wrapper that execs itself. For the same reason a wrapper finds its tool on
|
|
20
|
-
* `PATH` with `WRAPPER_DIR` skipped, when it runs. `corepack enable` puts its
|
|
21
|
-
* shims beside the `corepack` on `PATH` — the wrapper — so it is pointed at the
|
|
22
|
-
* real one's directory instead.
|
|
23
|
-
*/
|
|
24
|
-
export declare function depsSetupCommand(workspaceDir: string, dir?: string): string;
|
|
25
|
-
/** What a setup run found in the container. */
|
|
26
|
-
export interface DepsFound {
|
|
27
|
-
/** The marker's contents, or `null` when no host install completed here. */
|
|
28
|
-
marker: string | null;
|
|
29
|
-
}
|
|
30
|
-
export interface ContainerDepsDeps {
|
|
31
|
-
workspace: () => Workspace;
|
|
32
|
-
workspaceDir: string;
|
|
33
|
-
tag: () => string;
|
|
34
|
-
id: () => string;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* The setup above, once per container.
|
|
38
|
-
*
|
|
39
|
-
* In memory, with {@link file://./ca-trust.ts}'s lifetime and for its reason: it
|
|
40
|
-
* describes one container, and an isolate that lost it re-runs an idempotent
|
|
41
|
-
* command.
|
|
42
|
-
*/
|
|
43
|
-
export declare class ContainerDeps {
|
|
44
|
-
#private;
|
|
45
|
-
private readonly deps;
|
|
46
|
-
constructor(deps: ContainerDepsDeps);
|
|
47
|
-
forget(): void;
|
|
48
|
-
/**
|
|
49
|
-
* Run the setup if this container has not had it, and report the marker under
|
|
50
|
-
* `dir`. `undefined` when it had, or with no `dir`.
|
|
51
|
-
*
|
|
52
|
-
* Throws when the container answered and the setup did not finish: nothing
|
|
53
|
-
* would say so later, since an unwrapped install succeeds, into the workspace.
|
|
54
|
-
* An unreachable container is only logged, like the CA install: the command
|
|
55
|
-
* after it fails the same way.
|
|
56
|
-
*/
|
|
57
|
-
ensure(dir?: string): Promise<DepsFound | undefined>;
|
|
58
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
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
|
-
* A container restored from a snapshot (see `./deps-snapshot.ts`) has the trees
|
|
70
|
-
* on its disk and none of the mounts, which are kernel state. So each mount
|
|
71
|
-
* records its package root beside its tree, and the setup mounts every recorded
|
|
72
|
-
* root again before it reads the marker through one.
|
|
73
|
-
*/
|
|
74
|
-
/** Where the trees live on the container's disk. */
|
|
75
|
-
const DEPS_DISK = "/var/lib/workspace-deps";
|
|
76
|
-
/** First on Debian's default `PATH`, so a wrapper here shadows the real tool. */
|
|
77
|
-
const WRAPPER_DIR = "/usr/local/sbin";
|
|
78
|
-
const MOUNT_HELPER = `${WRAPPER_DIR}/workspace-deps-mount`;
|
|
79
|
-
/**
|
|
80
|
-
* Every entry point that can write a `node_modules`, wrapped whether or not it
|
|
81
|
-
* is installed yet: `corepack enable` or `npm i -g pnpm` adds one later.
|
|
82
|
-
*/
|
|
83
|
-
const WRAPPED = [
|
|
84
|
-
"npm",
|
|
85
|
-
"npx",
|
|
86
|
-
"pnpm",
|
|
87
|
-
"yarn",
|
|
88
|
-
"corepack",
|
|
89
|
-
"bun",
|
|
90
|
-
"bunx"
|
|
91
|
-
];
|
|
92
|
-
/** Written into the tree by a successful host install; see `./install-job.ts`. */
|
|
93
|
-
export const INSTALLED_MARKER = ".da-installed";
|
|
94
|
-
/**
|
|
95
|
-
* The package root a tree belongs to, written beside it as `<tree>.root`.
|
|
96
|
-
*
|
|
97
|
-
* Beside rather than inside: `npm ci` empties the tree it installs into, and the
|
|
98
|
-
* hash a tree is named by cannot be turned back into the path.
|
|
99
|
-
*/
|
|
100
|
-
const ROOT_SUFFIX = ".root";
|
|
101
|
-
/** Every package root with a tree on this container's disk, one per line. */
|
|
102
|
-
export const DEPS_ROOTS_COMMAND = `for f in ${DEPS_DISK}/*${ROOT_SUFFIX}; do [ -f "$f" ] && { cat "$f"; echo; }; done; true`;
|
|
103
|
-
/** {@link DEPS_ROOTS_COMMAND}'s output as paths, each once. */
|
|
104
|
-
export function parseDepsRoots(stdout) {
|
|
105
|
-
const roots = stdout
|
|
106
|
-
.split("\n")
|
|
107
|
-
.map((line) => line.trim())
|
|
108
|
-
.filter((line) => line.startsWith("/"));
|
|
109
|
-
return [...new Set(roots)];
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* The per-container setup, and a read of the marker under `dir`.
|
|
113
|
-
*
|
|
114
|
-
* The helper mounts the package root the command targets — its directory, or
|
|
115
|
-
* `--prefix`, `--cwd`, `--dir`, `-C` — only under the workspace, never inside
|
|
116
|
-
* another tree. Workspace members are not mounted: a mount point cannot be
|
|
117
|
-
* removed, so one guessed into `dist/` breaks `rm -rf dist`, and an install at
|
|
118
|
-
* the root puts the bulk of the tree there.
|
|
119
|
-
*
|
|
120
|
-
* Wrappers are written with `install`, never through the path: `npm` is
|
|
121
|
-
* commonly a symlink to `npm-cli.js`, and writing through it replaces npm with a
|
|
122
|
-
* wrapper that execs itself. For the same reason a wrapper finds its tool on
|
|
123
|
-
* `PATH` with `WRAPPER_DIR` skipped, when it runs. `corepack enable` puts its
|
|
124
|
-
* shims beside the `corepack` on `PATH` — the wrapper — so it is pointed at the
|
|
125
|
-
* real one's directory instead.
|
|
126
|
-
*/
|
|
127
|
-
export function depsSetupCommand(workspaceDir, dir) {
|
|
128
|
-
const helper = [
|
|
129
|
-
"#!/bin/sh",
|
|
130
|
-
'cwd="$1"; dir="$1"; shift',
|
|
131
|
-
'while [ $# -gt 0 ]; do case "$1" in',
|
|
132
|
-
" --) break;;",
|
|
133
|
-
' --prefix=*|--cwd=*|--dir=*) dir="${1#*=}";;',
|
|
134
|
-
' --prefix|--cwd|--dir|-C) [ $# -gt 1 ] && { dir="$2"; shift; };;',
|
|
135
|
-
"esac; shift; done",
|
|
136
|
-
'case "$dir" in /*) ;; *) dir="$cwd/$dir";; esac',
|
|
137
|
-
'dir=$(cd "$dir" 2>/dev/null && pwd -P) || exit 0',
|
|
138
|
-
'while [ "$dir" != / ] && [ ! -f "$dir/package.json" ]; do dir=$(dirname "$dir"); done',
|
|
139
|
-
`case "$dir" in */node_modules/*) exit 0;; ${workspaceDir}|${workspaceDir}/*) ;; *) exit 0;; esac`,
|
|
140
|
-
'nm="$dir/node_modules"',
|
|
141
|
-
'mountpoint -q "$nm" && exit 0',
|
|
142
|
-
`disk="${DEPS_DISK}/$(printf %s "$dir" | sha256sum | cut -c1-16)"`,
|
|
143
|
-
`mkdir -p "$disk" "$nm" && mount --bind "$disk" "$nm" && { printf %s "$dir" > "$disk${ROOT_SUFFIX}"; exit 0; }`,
|
|
144
|
-
`echo "workspace-deps: could not put $nm on the container's disk" >&2`,
|
|
145
|
-
"exit 1"
|
|
146
|
-
].join("\n");
|
|
147
|
-
const wrapper = [
|
|
148
|
-
"#!/bin/sh",
|
|
149
|
-
`${MOUNT_HELPER} "$PWD" "$@" || exit 1`,
|
|
150
|
-
'tool=$(basename "$0"); real=',
|
|
151
|
-
`IFS=:; for d in $PATH; do [ "$d" = ${WRAPPER_DIR} ] && continue; [ -x "$d/$tool" ] && { real="$d"; break; }; done; unset IFS`,
|
|
152
|
-
'[ -n "$real" ] || { echo "$tool: not found" >&2; exit 127; }',
|
|
153
|
-
'case "$tool:${1:-}:$*" in',
|
|
154
|
-
" *--install-directory*) ;;",
|
|
155
|
-
' corepack:enable:*|corepack:disable:*) cmd="$1"; shift; set -- "$cmd" --install-directory "$real" "$@";;',
|
|
156
|
-
"esac",
|
|
157
|
-
'exec "$real/$tool" "$@"'
|
|
158
|
-
].join("\n");
|
|
159
|
-
return [
|
|
160
|
-
`printf '%s\\n' ${shellQuote(helper)} > /tmp/workspace-deps-mount`,
|
|
161
|
-
`install -m 755 /tmp/workspace-deps-mount ${MOUNT_HELPER} || exit 1`,
|
|
162
|
-
`printf '%s\\n' ${shellQuote(wrapper)} > /tmp/workspace-deps-wrap`,
|
|
163
|
-
`for tool in ${WRAPPED.join(" ")}; do install -m 755 /tmp/workspace-deps-wrap "${WRAPPER_DIR}/$tool" || exit 1; done`,
|
|
164
|
-
// A root that is not in this checkout is left alone by the helper, and one
|
|
165
|
-
// that cannot be mounted reads as no marker below — an install, not a fault.
|
|
166
|
-
`for f in ${DEPS_DISK}/*${ROOT_SUFFIX}; do [ -f "$f" ] && ${MOUNT_HELPER} "$(cat "$f")"; done`,
|
|
167
|
-
...(dir
|
|
168
|
-
? [
|
|
169
|
-
`echo "MARKER $(cat ${shellQuote(`${dir}/node_modules/${INSTALLED_MARKER}`)} 2>/dev/null || echo -)"`
|
|
170
|
-
]
|
|
171
|
-
: []),
|
|
172
|
-
"echo SETUP OK"
|
|
173
|
-
].join("\n");
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* The setup above, once per container.
|
|
177
|
-
*
|
|
178
|
-
* In memory, with {@link file://./ca-trust.ts}'s lifetime and for its reason: it
|
|
179
|
-
* describes one container, and an isolate that lost it re-runs an idempotent
|
|
180
|
-
* command.
|
|
181
|
-
*/
|
|
182
|
-
export class ContainerDeps {
|
|
183
|
-
deps;
|
|
184
|
-
#done = false;
|
|
185
|
-
constructor(deps) {
|
|
186
|
-
this.deps = deps;
|
|
187
|
-
}
|
|
188
|
-
forget() {
|
|
189
|
-
this.#done = false;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Run the setup if this container has not had it, and report the marker under
|
|
193
|
-
* `dir`. `undefined` when it had, or with no `dir`.
|
|
194
|
-
*
|
|
195
|
-
* Throws when the container answered and the setup did not finish: nothing
|
|
196
|
-
* would say so later, since an unwrapped install succeeds, into the workspace.
|
|
197
|
-
* An unreachable container is only logged, like the CA install: the command
|
|
198
|
-
* after it fails the same way.
|
|
199
|
-
*/
|
|
200
|
-
async ensure(dir) {
|
|
201
|
-
if (this.#done)
|
|
202
|
-
return undefined;
|
|
203
|
-
const startedAt = Date.now();
|
|
204
|
-
let output;
|
|
205
|
-
try {
|
|
206
|
-
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
207
|
-
try {
|
|
208
|
-
const handle = __addDisposableResource(env_1, await this.deps
|
|
209
|
-
.workspace()
|
|
210
|
-
.runtime.exec(depsSetupCommand(this.deps.workspaceDir, dir), {
|
|
211
|
-
cwd: "/",
|
|
212
|
-
encoding: "utf8",
|
|
213
|
-
timeoutMs: 30_000
|
|
214
|
-
}), false);
|
|
215
|
-
const result = await handle.result();
|
|
216
|
-
output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
217
|
-
this.#done = result.exitCode === 0 && output.includes("SETUP OK");
|
|
218
|
-
}
|
|
219
|
-
catch (e_1) {
|
|
220
|
-
env_1.error = e_1;
|
|
221
|
-
env_1.hasError = true;
|
|
222
|
-
}
|
|
223
|
-
finally {
|
|
224
|
-
__disposeResources(env_1);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
const fault = deploymentFault(err);
|
|
229
|
-
console.warn(`[${this.deps.tag()}] ${fault?.summary ?? "could not set up container dependencies"}`, {
|
|
230
|
-
id: this.deps.id(),
|
|
231
|
-
...(fault ? { remedy: fault.remedy } : {}),
|
|
232
|
-
err: String(err)
|
|
233
|
-
});
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
console.info(`[${this.deps.tag()}] container dependency setup`, {
|
|
237
|
-
id: this.deps.id(),
|
|
238
|
-
ok: this.#done,
|
|
239
|
-
ms: Date.now() - startedAt,
|
|
240
|
-
...(this.#done ? {} : { output })
|
|
241
|
-
});
|
|
242
|
-
if (!this.#done)
|
|
243
|
-
throw new Error(`could not put node_modules on the container's disk: ${output.trim() || "the setup exited without output"}`);
|
|
244
|
-
const marker = /^MARKER (.*)$/m.exec(output)?.[1]?.trim();
|
|
245
|
-
if (marker === undefined)
|
|
246
|
-
return undefined;
|
|
247
|
-
return { marker: marker === "-" ? null : marker };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
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;
|