@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.
Files changed (134) hide show
  1. package/README.md +59 -79
  2. package/dist/browser/index.d.ts +0 -2
  3. package/dist/browser/index.js +21 -27
  4. package/dist/claude-code/cards.d.ts +53 -0
  5. package/dist/claude-code/cards.js +272 -0
  6. package/dist/claude-code/config.d.ts +73 -31
  7. package/dist/claude-code/config.js +0 -2
  8. package/dist/claude-code/credentials.d.ts +21 -9
  9. package/dist/claude-code/credentials.js +43 -9
  10. package/dist/claude-code/egress.d.ts +15 -14
  11. package/dist/claude-code/egress.js +51 -14
  12. package/dist/claude-code/events.d.ts +153 -9
  13. package/dist/claude-code/events.js +289 -58
  14. package/dist/claude-code/github.d.ts +39 -0
  15. package/dist/claude-code/github.js +82 -0
  16. package/dist/claude-code/index.d.ts +24 -80
  17. package/dist/claude-code/index.js +19 -234
  18. package/dist/claude-code/model.d.ts +152 -0
  19. package/dist/claude-code/model.js +342 -0
  20. package/dist/claude-code/run.d.ts +283 -92
  21. package/dist/claude-code/run.js +620 -114
  22. package/dist/claude-code/session.d.ts +131 -0
  23. package/dist/claude-code/session.js +285 -0
  24. package/dist/claude-code/spec.d.ts +46 -0
  25. package/dist/claude-code/spec.js +83 -0
  26. package/dist/claude-code/verified.d.ts +15 -0
  27. package/dist/claude-code/verified.js +15 -0
  28. package/dist/computer/context.d.ts +19 -0
  29. package/dist/computer/context.js +222 -0
  30. package/dist/computer/file-lock.d.ts +5 -0
  31. package/dist/computer/file-lock.js +40 -0
  32. package/dist/computer/gate.d.ts +12 -12
  33. package/dist/computer/gate.js +12 -12
  34. package/dist/computer/index.d.ts +57 -255
  35. package/dist/computer/index.js +98 -1039
  36. package/dist/computer/paths.d.ts +47 -22
  37. package/dist/computer/paths.js +64 -49
  38. package/dist/computer/proxy.d.ts +45 -0
  39. package/dist/computer/proxy.js +234 -0
  40. package/dist/computer/render.d.ts +27 -38
  41. package/dist/computer/render.js +32 -65
  42. package/dist/computer/tools-exec.d.ts +3 -0
  43. package/dist/computer/tools-exec.js +268 -0
  44. package/dist/computer/tools-file.d.ts +12 -0
  45. package/dist/computer/tools-file.js +64 -0
  46. package/dist/computer/tools-grep.d.ts +3 -0
  47. package/dist/computer/tools-grep.js +129 -0
  48. package/dist/repo/checkout.d.ts +35 -6
  49. package/dist/repo/checkout.js +64 -6
  50. package/dist/repo/context.d.ts +135 -0
  51. package/dist/repo/context.js +582 -0
  52. package/dist/repo/index.d.ts +79 -57
  53. package/dist/repo/index.js +118 -1112
  54. package/dist/repo/tools-checks.d.ts +3 -0
  55. package/dist/repo/tools-checks.js +112 -0
  56. package/dist/repo/tools-clone.d.ts +4 -0
  57. package/dist/repo/tools-clone.js +192 -0
  58. package/dist/repo/tools-pr.d.ts +14 -0
  59. package/dist/repo/tools-pr.js +196 -0
  60. package/dist/repo/tools-review.d.ts +12 -0
  61. package/dist/repo/tools-review.js +393 -0
  62. package/dist/repo/tools-worktree.d.ts +4 -0
  63. package/dist/repo/tools-worktree.js +305 -0
  64. package/dist/repo/tools-worktrees.d.ts +12 -0
  65. package/dist/repo/tools-worktrees.js +48 -0
  66. package/dist/repo/url.d.ts +2 -2
  67. package/dist/repo/url.js +7 -7
  68. package/dist/scratch/index.d.ts +4 -4
  69. package/dist/scratch/index.js +12 -11
  70. package/dist/{computer → workspace}/advisory.d.ts +33 -20
  71. package/dist/{computer → workspace}/advisory.js +32 -7
  72. package/dist/workspace/ca-trust.d.ts +95 -0
  73. package/dist/workspace/ca-trust.js +312 -0
  74. package/dist/workspace/container-deps.d.ts +54 -0
  75. package/dist/workspace/container-deps.js +224 -0
  76. package/dist/workspace/container-fault.d.ts +38 -0
  77. package/dist/workspace/container-fault.js +73 -0
  78. package/dist/workspace/exec.d.ts +94 -0
  79. package/dist/workspace/exec.js +153 -0
  80. package/dist/workspace/format.d.ts +36 -0
  81. package/dist/workspace/format.js +57 -0
  82. package/dist/workspace/git-host.d.ts +79 -0
  83. package/dist/workspace/git-host.js +226 -0
  84. package/dist/workspace/git-identity.d.ts +57 -0
  85. package/dist/workspace/git-identity.js +147 -0
  86. package/dist/workspace/index.d.ts +43 -47
  87. package/dist/workspace/index.js +52 -112
  88. package/dist/workspace/install-job.d.ts +121 -0
  89. package/dist/workspace/install-job.js +866 -0
  90. package/dist/{computer → workspace}/install.d.ts +15 -19
  91. package/dist/{computer → workspace}/install.js +8 -11
  92. package/dist/workspace/object.d.ts +502 -0
  93. package/dist/workspace/object.js +1427 -0
  94. package/dist/workspace/open.d.ts +95 -0
  95. package/dist/workspace/open.js +63 -0
  96. package/dist/workspace/read.d.ts +16 -0
  97. package/dist/workspace/read.js +22 -0
  98. package/dist/workspace/shell.d.ts +45 -0
  99. package/dist/workspace/shell.js +80 -0
  100. package/dist/workspace/sync.d.ts +111 -0
  101. package/dist/workspace/sync.js +124 -0
  102. package/dist/workspace/wake.d.ts +19 -0
  103. package/dist/workspace/wake.js +10 -0
  104. package/package.json +28 -42
  105. package/dist/arc-agi/analysis.d.ts +0 -336
  106. package/dist/arc-agi/analysis.js +0 -829
  107. package/dist/arc-agi/client.d.ts +0 -82
  108. package/dist/arc-agi/client.js +0 -113
  109. package/dist/arc-agi/game-tools.d.ts +0 -25
  110. package/dist/arc-agi/game-tools.js +0 -26
  111. package/dist/arc-agi/index.d.ts +0 -42
  112. package/dist/arc-agi/index.js +0 -175
  113. package/dist/arc-agi/main-agent.d.ts +0 -27
  114. package/dist/arc-agi/main-agent.js +0 -65
  115. package/dist/arc-agi/recipe.d.ts +0 -51
  116. package/dist/arc-agi/recipe.js +0 -100
  117. package/dist/arc-agi/scorecard.d.ts +0 -118
  118. package/dist/arc-agi/scorecard.js +0 -163
  119. package/dist/arc-agi/soul.d.ts +0 -40
  120. package/dist/arc-agi/soul.js +0 -90
  121. package/dist/arc-agi/store.d.ts +0 -154
  122. package/dist/arc-agi/store.js +0 -216
  123. package/dist/arc-agi/tools.d.ts +0 -36
  124. package/dist/arc-agi/tools.js +0 -576
  125. package/dist/arc-agi/types.d.ts +0 -212
  126. package/dist/arc-agi/types.js +0 -19
  127. package/dist/claude-code/recipe.d.ts +0 -53
  128. package/dist/claude-code/recipe.js +0 -100
  129. package/dist/computer/read.d.ts +0 -137
  130. package/dist/computer/read.js +0 -203
  131. package/dist/recall/index.d.ts +0 -99
  132. package/dist/recall/index.js +0 -171
  133. package/dist/triage/index.d.ts +0 -355
  134. package/dist/triage/index.js +0 -302
@@ -1,4 +1,4 @@
1
- import { humanMs } from "./render.js";
1
+ import { humanMs } from "./format.js";
2
2
  /** What each advisory *means*. The one exhaustive home for policy. */
3
3
  export function shapeOf(advisory) {
4
4
  switch (advisory.kind) {
@@ -129,9 +129,34 @@ export function deriveAdvisories(input) {
129
129
  });
130
130
  break;
131
131
  case "failed":
132
- // Always reported. The record is durable evidence that an install failed,
133
- // and nothing available here is evidence that a later one succeeded — so
134
- // the presence of a tree qualifies the advisory instead of deleting it.
132
+ /**
133
+ * A queued reinstall changes the *kind*, not the severity of the facts.
134
+ *
135
+ * `deps-broken` is permanent by definition — nothing clears that record
136
+ * except another checkout — and that permanence is what the reader acts
137
+ * on: a session told this installs for itself. When a reinstall is
138
+ * already armed that is the wrong instruction and the wrong shape, because
139
+ * the condition does clear with nobody acting. `deps-building` is the one
140
+ * that says so, and its wording for a session already asks the reader to
141
+ * wait rather than start a second install on top of the first.
142
+ *
143
+ * Counted from when the reinstall was armed rather than when it starts:
144
+ * arming is the moment the repair became certain, and the alarm that runs
145
+ * it owns no clock this can read.
146
+ */
147
+ if (input.reinstallArmedAt !== undefined) {
148
+ advisories.push({
149
+ kind: "deps-building",
150
+ command: install.command,
151
+ startedAt: input.reinstallArmedAt,
152
+ ...(install.tail ? { tail: install.tail } : {})
153
+ });
154
+ break;
155
+ }
156
+ // Otherwise always reported. The record is durable evidence that an
157
+ // install failed, and nothing available here is evidence that a later one
158
+ // succeeded — so the presence of a tree qualifies the advisory instead of
159
+ // deleting it.
135
160
  advisories.push({
136
161
  kind: "deps-broken",
137
162
  command: install.command,
@@ -147,8 +172,8 @@ export function deriveAdvisories(input) {
147
172
  advisories.push({ kind: "deps-absent", reason: install.reason });
148
173
  break;
149
174
  // Nothing to say. `idle` means no install has been armed yet, which the
150
- // host's cold-container check resolves on its own, and `done` is the
151
- // ordinary case — a caller told its dependencies are fine has been told
175
+ // host resolves on its own when it finds no tree in the workspace, and
176
+ // `done` is the ordinary case — a caller told its dependencies are fine has been told
152
177
  // nothing, at the cost of prefix tokens on every run.
153
178
  case "idle":
154
179
  case "done":
@@ -163,7 +188,7 @@ export function deriveAdvisories(input) {
163
188
  /**
164
189
  * Everything a session should know before it starts, or `undefined` for silence.
165
190
  *
166
- * The counterpart to {@link file://./gate.ts execGate}, for the reader that is a
191
+ * The counterpart to {@link file://../computer/gate.ts execGate}, for the reader that is a
167
192
  * whole run rather than one command. It cannot be blocked and does not need to
168
193
  * be: it can install dependencies itself, and the only thing it must not do is
169
194
  * work silently against a workspace that is not what it assumes.
@@ -0,0 +1,95 @@
1
+ import type { Workspace } from "@cloudflare/computer";
2
+ /**
3
+ * Making a container able to speak TLS, once there is a CA to trust.
4
+ *
5
+ * Only `egress: { mode: "http-gateway" }` needs this, but it lives on every
6
+ * workspace because the cost of getting it wrong is asymmetric: the trust
7
+ * install against a container with no CA prints one line and exits 0, while a
8
+ * container that missed it fails every later HTTPS connection with an error
9
+ * that names no cause.
10
+ */
11
+ /**
12
+ * Install the egress interception CA, and say what was there to install.
13
+ *
14
+ * One shell string rather than a script in the image, because **the image cannot
15
+ * run this at all**, and the reason is ordering rather than preference.
16
+ * Cloudflare's own recipe assumes interception is declared as container
17
+ * configuration, so the runtime mounts the CA before the container starts.
18
+ * `@cloudflare/computer` uses the raw container API, where the only interception
19
+ * hooks are methods called *after* `start()` — so an entrypoint runs strictly
20
+ * before the CA can exist, finds nothing, and reports the container
21
+ * un-intercepted while every later TLS connection fails.
22
+ *
23
+ * The listing comes first and runs unconditionally: when the CA is missing,
24
+ * *what else is in that directory* is the only evidence distinguishing "mounted
25
+ * somewhere else" from "never provisioned", and it costs one line.
26
+ */
27
+ export declare const TRUST_CA_COMMAND: string;
28
+ export interface ContainerTrustDeps {
29
+ workspace: () => Workspace;
30
+ /** The runtime's own container handle, for the exit this has to hear about. */
31
+ container: () => Container | undefined;
32
+ /** Log prefix and object id, for the one line an operator can see. */
33
+ tag: () => string;
34
+ id: () => string;
35
+ /** Told when the watched container exits, for what else it described. */
36
+ onExit?: () => void;
37
+ }
38
+ /**
39
+ * Whether the container this object is talking to has had the CA installed.
40
+ *
41
+ * ## Why the answer is in memory
42
+ *
43
+ * What it describes is a *container*, which does not outlive the isolate in any
44
+ * way worth persisting. A stored `true` would be the exact bug this exists to
45
+ * prevent — a flag surviving the container it describes, and vouching for its
46
+ * replacement.
47
+ *
48
+ * It is wrong only in the safe direction. An isolate that lost it re-runs an
49
+ * idempotent `install -m 644`.
50
+ *
51
+ * ## What clears it
52
+ *
53
+ * **The container's own exit**, watched through `monitor()`. This is the signal
54
+ * that does not depend on anyone noticing: a container replaced underneath a
55
+ * running command — by a reconnect, a relaunch, a crash — exits, and the watch
56
+ * fires whoever caused it. Nothing else here is in a position to know, because
57
+ * most commands run through the stub this object hands out and report their
58
+ * failures to their own caller, not to it.
59
+ *
60
+ * The watch lives in memory beside the flag it clears, which is the right
61
+ * lifetime: an isolate that loses one loses the other, and the replacement
62
+ * starts out untrusted. It is deliberately not held open with `waitUntil` —
63
+ * that would keep an invocation alive for the life of the container.
64
+ *
65
+ * A container seen not running, and a command reporting `EEXEC_LOST`, both call
66
+ * {@link forget} as well. They are cheap, they are strictly earlier in some
67
+ * orderings, and neither is load-bearing on its own.
68
+ */
69
+ export declare class ContainerTrust {
70
+ #private;
71
+ private readonly deps;
72
+ constructor(deps: ContainerTrustDeps);
73
+ /**
74
+ * Forget that any container was trusted.
75
+ *
76
+ * Drops the standing watch too, because this is also reached for a container
77
+ * that keeps running — a `running` read, a lost exec. The watch it leaves
78
+ * behind ignores that container's real exit, so without a fresh one nothing
79
+ * would clear the flag again and the replacement would be vouched for.
80
+ */
81
+ forget(): void;
82
+ /**
83
+ * Trust the CA if this container has not been trusted yet.
84
+ *
85
+ * Called from the workspace's `ready()` path, which is the earliest point that
86
+ * is not too early: the backend's `connect()` has completed, so start and
87
+ * interception have both happened.
88
+ *
89
+ * **Never throws.** A workspace that cannot trust the CA still has a checkout,
90
+ * a shell and a git history, and whatever runs next will say plainly what went
91
+ * wrong — refusing to proceed here would replace a legible TLS error with an
92
+ * opaque one.
93
+ */
94
+ ensure(): Promise<void>;
95
+ }
@@ -0,0 +1,312 @@
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
+ }
@@ -0,0 +1,54 @@
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
+ /**
5
+ * The per-container setup, and a read of the marker under `dir`.
6
+ *
7
+ * The helper mounts the package root the command targets — its directory, or
8
+ * `--prefix`, `--cwd`, `--dir`, `-C` — only under the workspace, never inside
9
+ * another tree. Workspace members are not mounted: a mount point cannot be
10
+ * removed, so one guessed into `dist/` breaks `rm -rf dist`, and an install at
11
+ * the root puts the bulk of the tree there.
12
+ *
13
+ * Wrappers are written with `install`, never through the path: `npm` is
14
+ * commonly a symlink to `npm-cli.js`, and writing through it replaces npm with a
15
+ * wrapper that execs itself. For the same reason a wrapper finds its tool on
16
+ * `PATH` with `WRAPPER_DIR` skipped, when it runs. `corepack enable` puts its
17
+ * shims beside the `corepack` on `PATH` — the wrapper — so it is pointed at the
18
+ * real one's directory instead.
19
+ */
20
+ export declare function depsSetupCommand(workspaceDir: string, dir?: string): string;
21
+ /** What a setup run found in the container. */
22
+ export interface DepsFound {
23
+ /** The marker's contents, or `null` when no host install completed here. */
24
+ marker: string | null;
25
+ }
26
+ export interface ContainerDepsDeps {
27
+ workspace: () => Workspace;
28
+ workspaceDir: string;
29
+ tag: () => string;
30
+ id: () => string;
31
+ }
32
+ /**
33
+ * The setup above, once per container.
34
+ *
35
+ * In memory, with {@link file://./ca-trust.ts}'s lifetime and for its reason: it
36
+ * describes one container, and an isolate that lost it re-runs an idempotent
37
+ * command.
38
+ */
39
+ export declare class ContainerDeps {
40
+ #private;
41
+ private readonly deps;
42
+ constructor(deps: ContainerDepsDeps);
43
+ forget(): void;
44
+ /**
45
+ * Run the setup if this container has not had it, and report the marker under
46
+ * `dir`. `undefined` when it had, or with no `dir`.
47
+ *
48
+ * Throws when the container answered and the setup did not finish: nothing
49
+ * would say so later, since an unwrapped install succeeds, into the workspace.
50
+ * An unreachable container is only logged, like the CA install: the command
51
+ * after it fails the same way.
52
+ */
53
+ ensure(dir?: string): Promise<DepsFound | undefined>;
54
+ }