@usine/executor 0.2.0 → 0.3.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 (177) hide show
  1. package/README.md +115 -37
  2. package/dist/child-command.d.ts +4 -7
  3. package/dist/child-command.d.ts.map +1 -1
  4. package/dist/child-command.js +6 -12
  5. package/dist/child-command.js.map +1 -1
  6. package/dist/claude-login-lock.d.ts +8 -0
  7. package/dist/claude-login-lock.d.ts.map +1 -0
  8. package/dist/claude-login-lock.js +9 -0
  9. package/dist/claude-login-lock.js.map +1 -0
  10. package/dist/credential-environment.d.ts +20 -0
  11. package/dist/credential-environment.d.ts.map +1 -0
  12. package/dist/credential-environment.js +20 -0
  13. package/dist/credential-environment.js.map +1 -0
  14. package/dist/durable-session.d.ts +63 -0
  15. package/dist/durable-session.d.ts.map +1 -0
  16. package/dist/durable-session.js +491 -0
  17. package/dist/durable-session.js.map +1 -0
  18. package/dist/exe-dev/commands.d.ts +23 -13
  19. package/dist/exe-dev/commands.d.ts.map +1 -1
  20. package/dist/exe-dev/commands.js +11 -57
  21. package/dist/exe-dev/commands.js.map +1 -1
  22. package/dist/exe-dev/exe-dev.d.ts +9 -3
  23. package/dist/exe-dev/exe-dev.d.ts.map +1 -1
  24. package/dist/exe-dev/exe-dev.js +53 -127
  25. package/dist/exe-dev/exe-dev.js.map +1 -1
  26. package/dist/exe-dev/grants.d.ts.map +1 -1
  27. package/dist/exe-dev/grants.js +35 -15
  28. package/dist/exe-dev/grants.js.map +1 -1
  29. package/dist/exe-dev/model.d.ts +15 -19
  30. package/dist/exe-dev/model.d.ts.map +1 -1
  31. package/dist/exe-dev/model.js +9 -12
  32. package/dist/exe-dev/model.js.map +1 -1
  33. package/dist/exe-dev/preflight.d.ts +8 -4
  34. package/dist/exe-dev/preflight.d.ts.map +1 -1
  35. package/dist/exe-dev/preflight.js +23 -33
  36. package/dist/exe-dev/preflight.js.map +1 -1
  37. package/dist/executor.d.ts +141 -38
  38. package/dist/executor.d.ts.map +1 -1
  39. package/dist/executor.js +90 -17
  40. package/dist/executor.js.map +1 -1
  41. package/dist/guard.d.ts +61 -0
  42. package/dist/guard.d.ts.map +1 -0
  43. package/dist/guard.js +288 -0
  44. package/dist/guard.js.map +1 -0
  45. package/dist/harness-preflight.d.ts +25 -4
  46. package/dist/harness-preflight.d.ts.map +1 -1
  47. package/dist/harness-preflight.js +25 -29
  48. package/dist/harness-preflight.js.map +1 -1
  49. package/dist/keyed-lock.d.ts +2 -3
  50. package/dist/keyed-lock.d.ts.map +1 -1
  51. package/dist/keyed-lock.js +2 -3
  52. package/dist/keyed-lock.js.map +1 -1
  53. package/dist/leftovers.d.ts +9 -0
  54. package/dist/leftovers.d.ts.map +1 -0
  55. package/dist/leftovers.js +14 -0
  56. package/dist/leftovers.js.map +1 -0
  57. package/dist/local/credentials.d.ts +21 -0
  58. package/dist/local/credentials.d.ts.map +1 -0
  59. package/dist/local/credentials.js +39 -0
  60. package/dist/local/credentials.js.map +1 -0
  61. package/dist/local/leftovers.d.ts +8 -0
  62. package/dist/local/leftovers.d.ts.map +1 -0
  63. package/dist/local/leftovers.js +23 -0
  64. package/dist/local/leftovers.js.map +1 -0
  65. package/dist/local/local.d.ts +16 -10
  66. package/dist/local/local.d.ts.map +1 -1
  67. package/dist/local/local.js +164 -129
  68. package/dist/local/local.js.map +1 -1
  69. package/dist/memory-preflight.d.ts +25 -0
  70. package/dist/memory-preflight.d.ts.map +1 -0
  71. package/dist/memory-preflight.js +83 -0
  72. package/dist/memory-preflight.js.map +1 -0
  73. package/dist/pre-push-hook.d.ts +20 -0
  74. package/dist/pre-push-hook.d.ts.map +1 -0
  75. package/dist/pre-push-hook.js +33 -0
  76. package/dist/pre-push-hook.js.map +1 -0
  77. package/dist/prepared-attempt.d.ts +33 -3
  78. package/dist/prepared-attempt.d.ts.map +1 -1
  79. package/dist/prepared-attempt.js +109 -39
  80. package/dist/prepared-attempt.js.map +1 -1
  81. package/dist/provision.d.ts +77 -14
  82. package/dist/provision.d.ts.map +1 -1
  83. package/dist/provision.js +357 -55
  84. package/dist/provision.js.map +1 -1
  85. package/dist/remote/commands.d.ts +51 -0
  86. package/dist/remote/commands.d.ts.map +1 -0
  87. package/dist/remote/commands.js +94 -0
  88. package/dist/remote/commands.js.map +1 -0
  89. package/dist/remote/leftovers.d.ts +14 -0
  90. package/dist/remote/leftovers.d.ts.map +1 -0
  91. package/dist/remote/leftovers.js +22 -0
  92. package/dist/remote/leftovers.js.map +1 -0
  93. package/dist/remote/liveness.d.ts +33 -0
  94. package/dist/remote/liveness.d.ts.map +1 -0
  95. package/dist/remote/liveness.js +194 -0
  96. package/dist/remote/liveness.js.map +1 -0
  97. package/dist/remote/machine.d.ts +64 -0
  98. package/dist/remote/machine.d.ts.map +1 -0
  99. package/dist/remote/machine.js +2 -0
  100. package/dist/remote/machine.js.map +1 -0
  101. package/dist/remote/remote.d.ts +24 -0
  102. package/dist/remote/remote.d.ts.map +1 -0
  103. package/dist/remote/remote.js +308 -0
  104. package/dist/remote/remote.js.map +1 -0
  105. package/dist/remote/sweep.d.ts +15 -0
  106. package/dist/remote/sweep.d.ts.map +1 -0
  107. package/dist/remote/sweep.js +116 -0
  108. package/dist/remote/sweep.js.map +1 -0
  109. package/dist/renewable-credential.d.ts +17 -0
  110. package/dist/renewable-credential.d.ts.map +1 -0
  111. package/dist/renewable-credential.js +58 -0
  112. package/dist/renewable-credential.js.map +1 -0
  113. package/dist/salvage.d.ts +30 -0
  114. package/dist/salvage.d.ts.map +1 -0
  115. package/dist/salvage.js +104 -0
  116. package/dist/salvage.js.map +1 -0
  117. package/dist/ssh/credentials.d.ts +40 -0
  118. package/dist/ssh/credentials.d.ts.map +1 -0
  119. package/dist/ssh/credentials.js +83 -0
  120. package/dist/ssh/credentials.js.map +1 -0
  121. package/dist/ssh/gateway/admin.d.ts +45 -0
  122. package/dist/ssh/gateway/admin.d.ts.map +1 -0
  123. package/dist/ssh/gateway/admin.js +129 -0
  124. package/dist/ssh/gateway/admin.js.map +1 -0
  125. package/dist/ssh/gateway/attachments.d.ts +22 -0
  126. package/dist/ssh/gateway/attachments.d.ts.map +1 -0
  127. package/dist/ssh/gateway/attachments.js +154 -0
  128. package/dist/ssh/gateway/attachments.js.map +1 -0
  129. package/dist/ssh/gateway/environment.d.ts +52 -0
  130. package/dist/ssh/gateway/environment.d.ts.map +1 -0
  131. package/dist/ssh/gateway/environment.js +78 -0
  132. package/dist/ssh/gateway/environment.js.map +1 -0
  133. package/dist/ssh/gateway/forward.d.ts +74 -0
  134. package/dist/ssh/gateway/forward.d.ts.map +1 -0
  135. package/dist/ssh/gateway/forward.js +247 -0
  136. package/dist/ssh/gateway/forward.js.map +1 -0
  137. package/dist/ssh/gateway/preflight.d.ts +28 -0
  138. package/dist/ssh/gateway/preflight.d.ts.map +1 -0
  139. package/dist/ssh/gateway/preflight.js +234 -0
  140. package/dist/ssh/gateway/preflight.js.map +1 -0
  141. package/dist/ssh/model.d.ts +65 -0
  142. package/dist/ssh/model.d.ts.map +1 -0
  143. package/dist/ssh/model.js +2 -0
  144. package/dist/ssh/model.js.map +1 -0
  145. package/dist/ssh/preflight.d.ts +16 -0
  146. package/dist/ssh/preflight.d.ts.map +1 -0
  147. package/dist/ssh/preflight.js +47 -0
  148. package/dist/ssh/preflight.js.map +1 -0
  149. package/dist/ssh/ssh.d.ts +20 -0
  150. package/dist/ssh/ssh.d.ts.map +1 -0
  151. package/dist/ssh/ssh.js +78 -0
  152. package/dist/ssh/ssh.js.map +1 -0
  153. package/dist/ssh/workers.d.ts +53 -0
  154. package/dist/ssh/workers.d.ts.map +1 -0
  155. package/dist/ssh/workers.js +173 -0
  156. package/dist/ssh/workers.js.map +1 -0
  157. package/package.json +12 -4
  158. package/dist/exe-dev/channel.d.ts +0 -11
  159. package/dist/exe-dev/channel.d.ts.map +0 -1
  160. package/dist/exe-dev/channel.js +0 -41
  161. package/dist/exe-dev/channel.js.map +0 -1
  162. package/dist/exe-dev/shell.d.ts +0 -12
  163. package/dist/exe-dev/shell.d.ts.map +0 -1
  164. package/dist/exe-dev/shell.js +0 -31
  165. package/dist/exe-dev/shell.js.map +0 -1
  166. package/dist/exe-dev/sweep.d.ts +0 -10
  167. package/dist/exe-dev/sweep.d.ts.map +0 -1
  168. package/dist/exe-dev/sweep.js +0 -117
  169. package/dist/exe-dev/sweep.js.map +0 -1
  170. package/dist/invalid-frame.d.ts +0 -17
  171. package/dist/invalid-frame.d.ts.map +0 -1
  172. package/dist/invalid-frame.js +0 -111
  173. package/dist/invalid-frame.js.map +0 -1
  174. package/dist/run-host-session.d.ts +0 -26
  175. package/dist/run-host-session.d.ts.map +0 -1
  176. package/dist/run-host-session.js +0 -241
  177. package/dist/run-host-session.js.map +0 -1
@@ -0,0 +1,234 @@
1
+ import { HarnessReady, HarnessUnavailableError } from "@usine/core/agent/capability";
2
+ import { Effect, Option, Redacted, Ref } from "effect";
3
+ import { ExecutorOperationError, WorkerCapabilityError } from "../../executor.js";
4
+ import { sendingShell } from "../../remote/commands.js";
5
+ import { forwardProxyUrl, GATEWAY_SENT_VARIABLES, gatewayEnvironment } from "./environment.js";
6
+ /** A placeholder caller token that lets preflight check the sent variables without attaching anything. */
7
+ const PROBE_TOKEN = Redacted.make("usine-preflight");
8
+ /** A script that takes name and value pairs and prints each name whose variable doesn't have that value on the machine. */
9
+ const SENT_VARIABLES_SCRIPT = `
10
+ while [ "$#" -gt 1 ]; do
11
+ test "$(printenv "$1")" = "$2" || echo "$1"
12
+ shift 2
13
+ done
14
+ `.trim();
15
+ /** Matches the Gateway's refusal header for a request without a caller token. */
16
+ const REFUSED_WITHOUT_TOKEN = /^x-usine-gateway-refusal:\s*missing-caller-token\s*$/imu;
17
+ /** The exit code OpenSSH uses when it can't connect to the machine or run the command. */
18
+ const SSH_FAILURE = 255;
19
+ const gatewayGap = (worker, message, cause) => new WorkerCapabilityError({ worker, capability: "gateway", message, cause });
20
+ /** Returns the error for an SSH transport failure, which isn't a capability gap. */
21
+ const unreachable = (host, command, result) => new ExecutorOperationError({
22
+ operation: "preflight",
23
+ message: `Can't reach ${host} to run ${command}`,
24
+ cause: new Error(result.stderr.trim() || `ssh exited with code ${SSH_FAILURE}`),
25
+ });
26
+ /**
27
+ * Logs a warning, once per Worker, that the Gateway is too old to report its Anthropic
28
+ * credential kind (#248). Later preflights log at debug level.
29
+ */
30
+ const warnOfUnreportedCredential = (worker, gateway) => Ref.getAndSet(gateway.warnedOfUnreportedCredential, true).pipe(Effect.flatMap((warned) => warned
31
+ ? Effect.logDebug("SSH Worker's Gateway still does not report the kind of its Anthropic credential; already warned for this Worker", { worker, gateway: gateway.forward.gateway })
32
+ : Effect.logWarning("SSH Worker's Gateway does not report the kind of its Anthropic credential; its Claude Code children count in the Claude plan pool until the Gateway is upgraded", { worker, gateway: gateway.forward.gateway })));
33
+ /**
34
+ * Reads the Gateway's Anthropic credential kind and records it for the Worker (ADR 0013).
35
+ * Fails if the Worker's `llmProviders` includes Anthropic and the Gateway has no Anthropic
36
+ * credential. If the Gateway is too old to report credentials, keeps the Claude plan pool and
37
+ * logs a warning.
38
+ */
39
+ const recordAnthropicCredential = Effect.fn("SshExecutor.recordAnthropicCredential")(function* (worker, gateway) {
40
+ yield* Effect.annotateCurrentSpan({ worker, gateway: gateway.forward.gateway });
41
+ const reported = yield* gateway.admin.providers.pipe(Effect.mapError((error) => gatewayGap(worker, error.message, error)));
42
+ const credential = yield* Option.match(reported, {
43
+ onNone: () => warnOfUnreportedCredential(worker, gateway).pipe(Effect.as(Option.none())),
44
+ onSome: ({ providers }) => {
45
+ const anthropic = providers.find(({ provider }) => provider === "anthropic");
46
+ return anthropic === undefined
47
+ ? Effect.fail(gatewayGap(worker, `The gateway of SSH worker "${worker}" has no Anthropic credential, but the worker's llm_providers includes anthropic. Set ANTHROPIC_API_KEY or USINE_GATEWAY_ANTHROPIC_SUBSCRIPTION_TOKEN on the gateway, or remove anthropic from llm_providers.`, new Error(`the gateway reports ${providers.map(({ provider }) => provider).join(", ") || "no provider"}`)))
48
+ : Effect.succeed(Option.some(anthropic.credential));
49
+ },
50
+ });
51
+ yield* Ref.set(gateway.anthropicCredential, credential);
52
+ return credential;
53
+ });
54
+ /**
55
+ * Returns the credential kind the Gateway injects for `harness`, as recorded by the last
56
+ * preflight. Only Claude Code uses a Gateway credential; Codex and Pi use their own logins.
57
+ */
58
+ export const gatewayCredentialFor = (gateway, harness) => {
59
+ switch (harness) {
60
+ case "claude":
61
+ return Ref.get(gateway.anthropicCredential);
62
+ case "codex":
63
+ case "pi":
64
+ return Effect.succeedNone;
65
+ }
66
+ };
67
+ /**
68
+ * Verifies the Gateway in front of an SSH Worker without attaching anything: the admin API
69
+ * forward opens and accepts the admin token, the machine's sshd accepts the caller-token
70
+ * variables, and the machine can connect to `git.<domain>`, which rejects a request without a caller
71
+ * token. If the Gateway has the Anthropic credential, records its kind for the Worker.
72
+ */
73
+ export const verifyGateway = Effect.fn("SshExecutor.verifyGateway")(function* (commands, worker, machine, gateway) {
74
+ const { admin, forward, settings } = gateway;
75
+ const gitHost = `git.${settings.domain}`;
76
+ yield* Effect.annotateCurrentSpan({ worker, domain: settings.domain, gateway: forward.gateway });
77
+ const open = yield* forward.current.pipe(Effect.catchTag("GatewayForwardClosed", (closed) => forward.reopen(closed.generation)), Effect.mapError((closed) => gatewayGap(worker, `Can't open the forward to the gateway admin API of SSH worker "${worker}" through ${closed.gateway}: ${closed.reason}. Run ${forward.command} to see the SSH error.`, closed)));
78
+ const { attachments } = yield* admin.list.pipe(Effect.mapError((error) => gatewayGap(worker, error.message, error)));
79
+ const anthropicCredential = settings.llmProviders.includes("anthropic")
80
+ ? yield* recordAnthropicCredential(worker, gateway)
81
+ : Option.none();
82
+ const { sent } = gatewayEnvironment({
83
+ domain: settings.domain,
84
+ llmProviders: settings.llmProviders,
85
+ ghConfigDirectory: machine.workerDirectory,
86
+ callerToken: PROBE_TOKEN,
87
+ });
88
+ const received = yield* commands.run("preflight", sendingShell(machine.shell, sent), "sh", [
89
+ "-c",
90
+ SENT_VARIABLES_SCRIPT,
91
+ "sh",
92
+ ...Object.entries(sent).flatMap(([name, value]) => [name, Redacted.value(value)]),
93
+ ]);
94
+ const dropped = received.split("\n").filter((name) => name !== "");
95
+ if (dropped.length > 0)
96
+ return yield* gatewayGap(worker, `The sshd on SSH worker "${worker}" doesn't accept ${dropped.join(", ")}. Add AcceptEnv ${GATEWAY_SENT_VARIABLES.join(" ")} to its sshd configuration.`, new Error(`variables not received: ${dropped.join(" ")}`));
97
+ const probed = yield* commands.inspect("preflight", machine.shell, "curl", [
98
+ "-sS",
99
+ "--noproxy",
100
+ "*",
101
+ "--max-time",
102
+ "15",
103
+ "-o",
104
+ "/dev/null",
105
+ "-D",
106
+ "-",
107
+ `http://${gitHost}/`,
108
+ ]);
109
+ if (probed.exitCode === SSH_FAILURE)
110
+ return yield* unreachable(machine.shell.destination, "curl", probed);
111
+ if (probed.exitCode !== 0 || !REFUSED_WITHOUT_TOKEN.test(probed.stdout))
112
+ return yield* gatewayGap(worker, `SSH worker "${worker}" can't reach its gateway at ${gitHost}`, new Error(probed.stderr.trim() ||
113
+ probed.stdout.split("\n")[0]?.trim() ||
114
+ `curl exited with code ${String(probed.exitCode)}`));
115
+ yield* Effect.logInfo("SSH Worker Gateway verified", {
116
+ worker,
117
+ domain: settings.domain,
118
+ gateway: forward.gateway,
119
+ adminPort: settings.adminPort,
120
+ forwardPort: open.url.port,
121
+ llmProviders: settings.llmProviders.join(","),
122
+ anthropicCredential: Option.getOrElse(anthropicCredential, () => "none"),
123
+ attachments: attachments.filter((attachment) => attachment.worker === worker).length,
124
+ });
125
+ });
126
+ /**
127
+ * The harnesses that can use the operator's login on the machine, with the hosts each login
128
+ * calls through the forward proxy. `infra/hetzner/gateway/egress-allowlist` lists the same
129
+ * hosts.
130
+ */
131
+ const LOGINS = {
132
+ claude: { label: "Claude Code", hosts: ["api.anthropic.com", "platform.claude.com"] },
133
+ codex: { label: "Codex", hosts: ["chatgpt.com", "auth.openai.com"] },
134
+ };
135
+ /**
136
+ * A script that takes the forward proxy URL and host names, and prints one line per host: the
137
+ * host, curl's exit code, the proxy's CONNECT status, and the host's HTTP status (`000` if the
138
+ * host didn't respond).
139
+ */
140
+ const LOGIN_HOSTS_SCRIPT = `
141
+ proxy=$1
142
+ shift
143
+ for host in "$@"; do
144
+ codes=$(curl -s -o /dev/null --max-time 10 --proxy "$proxy" -w '%{http_connect} %{http_code}' "https://$host/")
145
+ echo "$host $? $codes"
146
+ done
147
+ `.trim();
148
+ const LOGIN_HOST_ANSWER = /^(\S+) (\d+) (\d{3}) (\d{3})$/u;
149
+ /** The CONNECT status the forward proxy returns for a host that isn't in its allowlist. */
150
+ const PROXY_REFUSED = "403";
151
+ /**
152
+ * Probes a brain on a Worker behind a Gateway (ADR 0027). If the Gateway has the Anthropic
153
+ * credential, checks only the Claude Code binary. Otherwise, Claude Code and Codex use the
154
+ * operator's login (ADR 0026): `probe` checks the login, then the machine must connect to the
155
+ * login's hosts through the forward proxy. Pi goes to `probe`, which rejects it on any remote
156
+ * machine.
157
+ */
158
+ export const gatewayHarnessProbe = (commands, machine, gateway, probe) => {
159
+ const worker = machine.address.name;
160
+ const host = machine.shell.destination;
161
+ const { domain } = gateway.settings;
162
+ const proxy = forwardProxyUrl(domain);
163
+ const claudeBinary = Effect.fn("SshExecutor.probeClaudeBehindGateway")(function* () {
164
+ yield* Effect.annotateCurrentSpan({ host, domain });
165
+ const result = yield* commands.inspect("preflight", machine.shell, "claude", ["--version"]);
166
+ if (result.exitCode === SSH_FAILURE)
167
+ return yield* unreachable(host, "claude", result);
168
+ if (result.exitCode !== 0)
169
+ return new HarnessUnavailableError({
170
+ harness: "claude",
171
+ capability: "binary",
172
+ message: "claude --version failed. Check the Claude Code installation on the worker.",
173
+ });
174
+ yield* Effect.logInfo("Claude Code uses the Gateway's Anthropic credential; checked its binary only", { host, version: result.stdout.trim() });
175
+ return new HarnessReady({ harness: "claude" });
176
+ });
177
+ const loginHostsReachable = Effect.fn("SshExecutor.probeLoginHostsBehindGateway")(function* (harness) {
178
+ const { label, hosts } = LOGINS[harness];
179
+ yield* Effect.annotateCurrentSpan({ worker, host, domain, harness });
180
+ const result = yield* commands.inspect("preflight", machine.shell, "sh", [
181
+ "-c",
182
+ LOGIN_HOSTS_SCRIPT,
183
+ "sh",
184
+ proxy,
185
+ ...hosts,
186
+ ]);
187
+ if (result.exitCode === SSH_FAILURE)
188
+ return yield* unreachable(host, "curl", result);
189
+ const answers = new Map(result.stdout.split("\n").flatMap((line) => {
190
+ const match = LOGIN_HOST_ANSWER.exec(line.trim());
191
+ return match === null
192
+ ? []
193
+ : [[match[1], { exitCode: match[2], connect: match[3], status: match[4] }]];
194
+ }));
195
+ for (const loginHost of hosts) {
196
+ const answer = answers.get(loginHost);
197
+ if (answer !== undefined && answer.status !== "000")
198
+ continue;
199
+ const cause = new Error(answer === undefined
200
+ ? `no response from ${loginHost}: ${result.stderr.trim() || `sh exited with code ${String(result.exitCode)}`}`
201
+ : `curl exited with code ${answer.exitCode}; the proxy returned ${answer.connect} for CONNECT`);
202
+ return yield* gatewayGap(worker, answer?.connect === PROXY_REFUSED
203
+ ? `${label} uses its login on SSH worker "${worker}", and the gateway's forward proxy blocks ${loginHost}. Add ${loginHost} to the gateway's egress allowlist.`
204
+ : `${label} uses its login on SSH worker "${worker}", which can't reach ${loginHost} through the gateway's forward proxy at ${proxy}`, cause);
205
+ }
206
+ yield* Effect.logInfo("SSH Worker reaches its harness login's hosts through the Gateway", {
207
+ worker,
208
+ harness,
209
+ domain,
210
+ hosts: hosts.join(","),
211
+ });
212
+ });
213
+ const loginOnMachine = Effect.fn("SshExecutor.probeLoginBehindGateway")(function* (brain, harness) {
214
+ yield* Effect.annotateCurrentSpan({ worker, host, domain, harness });
215
+ const readiness = yield* probe(brain);
216
+ if (readiness._tag === "HarnessUnavailableError")
217
+ return readiness;
218
+ yield* loginHostsReachable(harness);
219
+ return readiness;
220
+ });
221
+ return (brain) => {
222
+ switch (brain.harness) {
223
+ case "claude":
224
+ return gateway.settings.llmProviders.includes("anthropic")
225
+ ? claudeBinary()
226
+ : loginOnMachine(brain, "claude");
227
+ case "codex":
228
+ return loginOnMachine(brain, "codex");
229
+ case "pi":
230
+ return probe(brain);
231
+ }
232
+ };
233
+ };
234
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/ssh/gateway/preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { SshCommand } from "@usine/agents/child-host";
2
+ import type { GatewayAdminPort, GatewayCredentialKind, GatewayDomain, GatewayLlmProvider, SshDestination, SshIdentityFile, SshPort, WorkerName, WorkerRoot } from "@usine/core/worker";
3
+ import type { Effect, Option, Redacted, Ref } from "effect";
4
+ import type { HarnessProbe } from "../harness-preflight.ts";
5
+ import type { RemoteMachine } from "../remote/machine.ts";
6
+ import type { GatewayAdmin } from "./gateway/admin.ts";
7
+ import type { GatewayForward } from "./gateway/forward.ts";
8
+ /** The Gateway in front of an SSH Worker's machine on a private network (ADR 0027). */
9
+ export interface SshWorkerGateway {
10
+ /** The Gateway's private DNS domain. GitHub is at `git.<domain>`, LLM providers at `llm.<domain>`, and the forward proxy at `<domain>:3128`. */
11
+ readonly domain: GatewayDomain;
12
+ /** The `ssh` destination for the Gateway: `user@host` or a Host alias. The admin API forward connects through it. */
13
+ readonly destination: SshDestination;
14
+ /** The admin API port on the Gateway's loopback interface. */
15
+ readonly adminPort: GatewayAdminPort;
16
+ /** The admin API bearer token. */
17
+ readonly adminToken: Redacted.Redacted<string>;
18
+ /** The LLM providers the Gateway has credentials for. A harness whose provider isn't listed uses its own login through the forward proxy. */
19
+ readonly llmProviders: ReadonlyArray<GatewayLlmProvider>;
20
+ }
21
+ export interface SshWorkerAddress {
22
+ /**
23
+ * The Worker's name in `usine.yaml`; its state is in `workers/<name>` under `root`. A request
24
+ * for a Worker without an address fails with a `machine` capability error.
25
+ */
26
+ readonly name: WorkerName;
27
+ /** The `ssh` destination: `user@host` or a Host alias. */
28
+ readonly destination: SshDestination;
29
+ /** The private key for authentication. If `None`, the SSH agent and configuration are used. */
30
+ readonly identityFile: Option.Option<SshIdentityFile>;
31
+ /** The SSH server port. If `None`, the SSH configuration's port is used. */
32
+ readonly port: Option.Option<SshPort>;
33
+ /** The machine directory that contains `workers/<name>`. */
34
+ readonly root: WorkerRoot;
35
+ /** The Gateway in front of the machine. If `None`, each Attempt's token is written on the machine (ADR 0026). */
36
+ readonly gateway: Option.Option<SshWorkerGateway>;
37
+ }
38
+ export interface SshExecutorOptions {
39
+ readonly workers: ReadonlyArray<SshWorkerAddress>;
40
+ /** The local SSH command and the options placed before each Worker's port and identity file. Defaults to `ssh` in batch mode with keepalives. */
41
+ readonly ssh?: SshCommand;
42
+ /** The timeout for the parent task's cancellation, then separately for the Attempt to finish. Defaults to 10 seconds. */
43
+ readonly interruptGrace?: Parameters<typeof Effect.timeoutOption>[1];
44
+ /** Probes a brain's binary and login on a Worker's machine. Defaults to the `@usine/agents/preflight` checks over SSH, which send no prompt. Behind a Gateway, the Gateway checks also apply. */
45
+ readonly harnessReadiness?: (machine: SshMachine) => HarnessProbe;
46
+ }
47
+ export interface MachineGateway {
48
+ readonly settings: SshWorkerGateway;
49
+ readonly forward: GatewayForward;
50
+ readonly admin: GatewayAdmin;
51
+ /**
52
+ * The Anthropic credential kind the Gateway uses for Claude Code, from the last preflight.
53
+ * `None` before the first preflight, if `llmProviders` doesn't include `anthropic`, or if the
54
+ * Gateway is too old to report it.
55
+ */
56
+ readonly anthropicCredential: Ref.Ref<Option.Option<GatewayCredentialKind>>;
57
+ /** Whether a preflight already warned that the Gateway is too old to report its Anthropic credential kind. */
58
+ readonly warnedOfUnreportedCredential: Ref.Ref<boolean>;
59
+ }
60
+ export interface SshMachine extends RemoteMachine {
61
+ /** The Worker's address from `usine.yaml`; preflight checks its root. */
62
+ readonly address: SshWorkerAddress;
63
+ readonly gateway: Option.Option<MachineGateway>;
64
+ }
65
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/ssh/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,OAAO,EACP,UAAU,EACV,UAAU,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,gJAAgJ;IAChJ,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,qHAAqH;IACrH,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/C,6IAA6I;IAC7I,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;IACrC,+FAA+F;IAC/F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,iHAAiH;IACjH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAClD,iJAAiJ;IACjJ,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,iMAAiM;IACjM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,YAAY,CAAC;CACnE;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC5E,8GAA8G;IAC9G,QAAQ,CAAC,4BAA4B,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;CACjD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/ssh/model.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import type { ClaudeLoginLock } from "@usine/agents/runtime";
2
+ import type { BrainSpec } from "@usine/core/agent/brain";
3
+ import type { WorkerName } from "@usine/core/worker";
4
+ import { Effect } from "effect";
5
+ import { type ExecutorError, WorkerCapabilityError } from "../executor.ts";
6
+ import { type HarnessProbe } from "../harness-preflight.ts";
7
+ import type { RemoteCommands } from "../remote/commands.ts";
8
+ import type { SshMachine } from "./model.ts";
9
+ /**
10
+ * Verifies an SSH Worker without changing its machine: SSH connects, the root is a writable
11
+ * directory, Git runs, memory is enough for the concurrent Attempts, the Gateway (if any)
12
+ * responds and is reachable, and each brain is ready. Claude brains are checked under the
13
+ * Worker's lock in `logins`.
14
+ */
15
+ export declare const sshPreflightFor: (commands: RemoteCommands, machineFor: (worker: WorkerName) => Effect.Effect<SshMachine, ExecutorError>, harnessProbe: (machine: SshMachine) => HarnessProbe, logins: ClaudeLoginLock["Service"]) => (worker: string & import("effect/Brand").Brand<"WorkerName">, brains: readonly BrainSpec[], maxConcurrentAttempts: number) => Effect.Effect<undefined, import("../executor.ts").CredentialUnavailableError | import("../executor.ts").ExecutorOperationError | WorkerCapabilityError, never>;
16
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EACL,KAAK,aAAa,EAElB,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAA0B,MAAM,yBAAyB,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAY7C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,aAChB,cAAc,cACZ,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,gBAC9D,CAAC,OAAO,EAAE,UAAU,KAAK,YAAY,UAC3C,eAAe,CAAC,SAAS,CAAC,iSA0EX,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { Effect, Option } from "effect";
2
+ import { logPreflightFailure, WorkerCapabilityError, } from "../executor.js";
3
+ import { verifyHarnessReadiness } from "../harness-preflight.js";
4
+ import { verifyLinuxMemory } from "../memory-preflight.js";
5
+ import { verifyGateway } from "./gateway/preflight.js";
6
+ /** A script that succeeds if `$1` is a directory the SSH user can write to. */
7
+ const WRITABLE_DIRECTORY_SCRIPT = 'test -d "$1" && test -w "$1"';
8
+ const capabilityError = (worker, capability, message, cause) => new WorkerCapabilityError({ worker, capability, message, cause });
9
+ /**
10
+ * Verifies an SSH Worker without changing its machine: SSH connects, the root is a writable
11
+ * directory, Git runs, memory is enough for the concurrent Attempts, the Gateway (if any)
12
+ * responds and is reachable, and each brain is ready. Claude brains are checked under the
13
+ * Worker's lock in `logins`.
14
+ */
15
+ export const sshPreflightFor = (commands, machineFor, harnessProbe, logins) => Effect.fn("SshExecutor.preflight")(function* (worker, brains, maxConcurrentAttempts) {
16
+ const machine = yield* machineFor(worker);
17
+ const host = machine.shell.destination;
18
+ const root = machine.address.root;
19
+ yield* Effect.annotateCurrentSpan({ worker, host, maxConcurrentAttempts });
20
+ const connected = yield* commands
21
+ .inspect("preflight", machine.shell, "true", [])
22
+ .pipe(Effect.mapError((cause) => capabilityError(worker, "machine", `Can't connect to SSH worker "${worker}" at ${host}`, cause)));
23
+ if (connected.exitCode !== 0)
24
+ return yield* capabilityError(worker, "machine", `Can't connect to SSH worker "${worker}" at ${host}`, new Error(connected.stderr.trim() || `ssh exited with code ${String(connected.exitCode)}`));
25
+ const rootChecked = yield* commands
26
+ .inspect("preflight", machine.shell, "sh", ["-c", WRITABLE_DIRECTORY_SCRIPT, "sh", root])
27
+ .pipe(Effect.mapError((cause) => capabilityError(worker, "machine", `Can't inspect ${root} on ${host}`, cause)));
28
+ if (rootChecked.exitCode !== 0)
29
+ return yield* capabilityError(worker, "machine", `The root ${root} of SSH worker "${worker}" isn't a writable directory on ${host}`, new Error(rootChecked.stderr.trim() || `exit code ${String(rootChecked.exitCode)}`));
30
+ const gitVersion = (yield* commands
31
+ .run("preflight", machine.shell, "git", ["--version"])
32
+ .pipe(Effect.mapError((cause) => capabilityError(worker, "toolchain", `Worker "${worker}" can't run Git`, cause)))).trim();
33
+ if (!gitVersion.startsWith("git version "))
34
+ return yield* capabilityError(worker, "toolchain", `Worker "${worker}" returned an unrecognized Git version`, new Error(gitVersion));
35
+ yield* verifyLinuxMemory(worker, maxConcurrentAttempts, commands.run("preflight", machine.shell, "cat", ["/proc/meminfo"]));
36
+ if (Option.isSome(machine.gateway))
37
+ yield* verifyGateway(commands, worker, machine, machine.gateway.value);
38
+ yield* verifyHarnessReadiness(worker, brains, harnessProbe(machine), logins);
39
+ yield* Effect.logInfo("SSH Worker preflight verified", {
40
+ worker,
41
+ host,
42
+ root,
43
+ gitVersion,
44
+ harnesses: [...new Set(brains.map(({ harness }) => harness))].join(","),
45
+ });
46
+ }, logPreflightFailure);
47
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/ssh/preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { ClaudeLoginLock } from "@usine/agents/runtime";
2
+ import type { AttemptRuntime } from "@usine/durable/attempt-runtime";
3
+ import { Layer } from "effect";
4
+ import type { HttpClient } from "effect/unstable/http";
5
+ import { ChildProcessSpawner } from "effect/unstable/process";
6
+ import { type CredentialProvider, Executor } from "../executor.ts";
7
+ import type { SshExecutorOptions } from "./model.ts";
8
+ import { SshWorkers } from "./workers.ts";
9
+ /**
10
+ * The executor for Linux machines accessed over SSH. Each Attempt's Pi parent is a durable task
11
+ * in this process (`AttemptRuntime`), and each child runs on the machine as one SSH command.
12
+ * Without a Gateway, the Attempt's GitHub token is written next to its checkout (ADR 0026).
13
+ * Behind a Gateway, the machine gets only a caller token for an attachment made through the
14
+ * Gateway's admin API (ADR 0027). `SshWorkers` adds and removes Workers at runtime. Requires
15
+ * Node.
16
+ */
17
+ export declare const layer: (options: SshExecutorOptions) => Layer.Layer<Executor | SshWorkers, never, AttemptRuntime | ClaudeLoginLock | CredentialProvider | ChildProcessSpawner.ChildProcessSpawner | HttpClient.HttpClient>;
18
+ export type { SshExecutorOptions, SshMachine, SshWorkerAddress, SshWorkerGateway, } from "./model.ts";
19
+ export { SshWorkers } from "./workers.ts";
20
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../src/ssh/ssh.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAmB,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,KAAK,kBAAkB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AASnE,OAAO,KAAK,EAAE,kBAAkB,EAAc,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAkB,UAAU,EAAE,MAAM,cAAc,CAAC;AAmF1D;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,YACP,kBAAkB,KAC1B,KAAK,CAAC,KAAK,CACZ,QAAQ,GAAG,UAAU,EACrB,KAAK,EACH,cAAc,GACd,eAAe,GACf,kBAAkB,GAClB,mBAAmB,CAAC,mBAAmB,GACvC,UAAU,CAAC,UAAU,CACyB,CAAC;AAEnD,YAAY,EACV,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,78 @@
1
+ import { ClaudeLoginLock } from "@usine/agents/runtime";
2
+ import { Context, Effect, Layer, Option } from "effect";
3
+ import { ChildProcessSpawner } from "effect/unstable/process";
4
+ import { Executor } from "../executor.js";
5
+ import { agentsHarnessProbe } from "../harness-preflight.js";
6
+ import { DEFAULT_SSH, remoteCommandsFor } from "../remote/commands.js";
7
+ import { remoteExecutorFor } from "../remote/remote.js";
8
+ import { sshCredentialsFor } from "./credentials.js";
9
+ import { gatewayAttachmentsFor } from "./gateway/attachments.js";
10
+ import { gatewayRepositoryUrl } from "./gateway/environment.js";
11
+ import { gatewayCredentialFor, gatewayHarnessProbe } from "./gateway/preflight.js";
12
+ import { sshPreflightFor } from "./preflight.js";
13
+ import { sshMachinesFor, SshWorkers } from "./workers.js";
14
+ const makeSshExecutor = (options) => Effect.gen(function* () {
15
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
16
+ const logins = yield* ClaudeLoginLock;
17
+ const commands = yield* remoteCommandsFor;
18
+ const base = options.ssh ?? DEFAULT_SSH;
19
+ if (new Set(options.workers.map(({ name }) => name)).size !== options.workers.length)
20
+ return yield* Effect.die(new Error("SshExecutorOptions.workers contains duplicate names"));
21
+ const { machineFor, workers } = yield* sshMachinesFor(base, commands);
22
+ // Worker names are distinct, so adding them can't fail.
23
+ yield* Effect.forEach(options.workers, workers.addWorker, { discard: true }).pipe(Effect.orDie);
24
+ // Probes with no extra environment; the harness login is on the machine or at its Gateway.
25
+ const harnessProbe = (machine) => {
26
+ const probe = options.harnessReadiness?.(machine) ?? agentsHarnessProbe(spawner, machine.shell, {});
27
+ return Option.match(machine.gateway, {
28
+ onNone: () => probe,
29
+ onSome: (gateway) => gatewayHarnessProbe(commands, machine, gateway, probe),
30
+ });
31
+ };
32
+ const credentials = yield* sshCredentialsFor(commands, machineFor);
33
+ const attachments = yield* gatewayAttachmentsFor(commands, machineFor);
34
+ // Runs for every Worker, because one that moved behind a Gateway can still have tokens from
35
+ // an earlier process.
36
+ const reclaim = (worker) => credentials.reclaim(worker).pipe(Effect.andThen(attachments.reclaim(worker)));
37
+ const grantFor = (repository) => Effect.succeed({
38
+ acquire: (attemptId, worker, permissions, machine, operation) => Effect.gen(function* () {
39
+ yield* reclaim(worker);
40
+ if (Option.isNone(machine.gateway))
41
+ return yield* credentials.acquire(attemptId, worker, repository, permissions, machine, operation);
42
+ return yield* attachments.acquire(attemptId, worker, repository, permissions, machine, machine.gateway.value, operation);
43
+ }),
44
+ });
45
+ const executor = yield* remoteExecutorFor({
46
+ kind: "ssh",
47
+ spanPrefix: "SshExecutor",
48
+ label: "SSH",
49
+ commands,
50
+ machineFor,
51
+ preflight: sshPreflightFor(commands, machineFor, harnessProbe, logins),
52
+ harnessProbe,
53
+ repositoryUrl: (machine, repository) => Option.match(machine.gateway, {
54
+ onNone: () => `https://github.com/${repository}.git`,
55
+ onSome: ({ settings }) => gatewayRepositoryUrl(settings.domain, repository),
56
+ }),
57
+ gatewayCredential: (machine, harness) => Option.match(machine.gateway, {
58
+ onNone: () => Effect.succeedNone,
59
+ onSome: (gateway) => gatewayCredentialFor(gateway, harness),
60
+ }),
61
+ grantFor,
62
+ readOnlyAccessGap: Option.none(),
63
+ reclaim,
64
+ interruptGrace: options.interruptGrace ?? "10 seconds",
65
+ });
66
+ return Context.make(Executor, executor).pipe(Context.add(SshWorkers, workers));
67
+ });
68
+ /**
69
+ * The executor for Linux machines accessed over SSH. Each Attempt's Pi parent is a durable task
70
+ * in this process (`AttemptRuntime`), and each child runs on the machine as one SSH command.
71
+ * Without a Gateway, the Attempt's GitHub token is written next to its checkout (ADR 0026).
72
+ * Behind a Gateway, the machine gets only a caller token for an attachment made through the
73
+ * Gateway's admin API (ADR 0027). `SshWorkers` adds and removes Workers at runtime. Requires
74
+ * Node.
75
+ */
76
+ export const layer = (options) => Layer.effectContext(makeSshExecutor(options));
77
+ export { SshWorkers } from "./workers.js";
78
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../src/ssh/ssh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { WorkerName } from "@usine/core/worker";
2
+ import { Context, Effect, Scope } from "effect";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import { ChildProcessSpawner } from "effect/unstable/process";
5
+ import { WorkerCapabilityError } from "../executor.ts";
6
+ import type { RemoteCommands } from "../remote/commands.ts";
7
+ import type { SshMachine, SshWorkerAddress } from "./model.ts";
8
+ declare const SshWorkers_base: Context.ServiceClass<SshWorkers, "@usine/executor/SshWorkers", {
9
+ /**
10
+ * Adds the machine at `address` for preflight and Attempts. Behind a Gateway, shares the admin
11
+ * API forward with other Workers that have the same `destination` and `adminPort`, or opens it.
12
+ * Adding a Worker again with the same address does nothing; with a different address, fails
13
+ * with a `machine` capability error.
14
+ */
15
+ readonly addWorker: (address: SshWorkerAddress) => Effect.Effect<void, WorkerCapabilityError>;
16
+ /**
17
+ * Removes `worker`'s machine, so later requests for it fail with a `machine` capability error.
18
+ * Call only when no Attempt runs on the Worker. If it was the last Worker behind its Gateway,
19
+ * closes the admin API forward. Removing an unknown Worker does nothing.
20
+ */
21
+ readonly removeWorker: (worker: WorkerName) => Effect.Effect<void>;
22
+ }>;
23
+ /** The Workers of the SSH executor, starting with `SshExecutorOptions.workers`. A reload adds and removes Workers at runtime. */
24
+ export declare class SshWorkers extends SshWorkers_base {
25
+ }
26
+ /**
27
+ * Returns the SSH Workers' machines, which `addWorker` and `removeWorker` change at runtime and
28
+ * `machineFor` reads on each call. Each admin API forward runs in its own child scope and
29
+ * closes with its last Worker or the caller's scope.
30
+ */
31
+ export declare const sshMachinesFor: (base: {
32
+ readonly command: string;
33
+ readonly args: readonly string[];
34
+ }, commands: RemoteCommands) => Effect.Effect<{
35
+ machineFor: (worker: string & import("effect/Brand").Brand<"WorkerName">) => Effect.Effect<SshMachine, WorkerCapabilityError, never>;
36
+ workers: {
37
+ /**
38
+ * Adds the machine at `address` for preflight and Attempts. Behind a Gateway, shares the admin
39
+ * API forward with other Workers that have the same `destination` and `adminPort`, or opens it.
40
+ * Adding a Worker again with the same address does nothing; with a different address, fails
41
+ * with a `machine` capability error.
42
+ */
43
+ readonly addWorker: (address: SshWorkerAddress) => Effect.Effect<void, WorkerCapabilityError>;
44
+ /**
45
+ * Removes `worker`'s machine, so later requests for it fail with a `machine` capability error.
46
+ * Call only when no Attempt runs on the Worker. If it was the last Worker behind its Gateway,
47
+ * closes the admin API forward. Removing an unknown Worker does nothing.
48
+ */
49
+ readonly removeWorker: (worker: WorkerName) => Effect.Effect<void>;
50
+ };
51
+ }, never, ChildProcessSpawner.ChildProcessSpawner | HttpClient.HttpClient | Scope.Scope>;
52
+ export {};
53
+ //# sourceMappingURL=workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../src/ssh/workers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAA4B,KAAK,EAAmB,MAAM,QAAQ,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAkB,UAAU,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;;IAM7F;;;;;OAKG;wBACiB,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAC7F;;;;OAIG;2BACoB,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAhBtE,iIAAiI;AACjI,qBAAa,UAAW,SAAQ,eAiBC;CAAG;AA8BpC;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;QAjDvB;;;;;WAKG;4BACiB,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,qBAAqB,CAAC;QAC7F;;;;WAIG;+BACoB,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;wFAoNpE,CAAC"}