@usine/executor 0.2.1 → 0.4.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 (250) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +212 -37
  3. package/dist/child-command.d.ts +5 -8
  4. package/dist/child-command.d.ts.map +1 -1
  5. package/dist/child-command.js +10 -14
  6. package/dist/child-command.js.map +1 -1
  7. package/dist/child-environment.d.ts +19 -0
  8. package/dist/child-environment.d.ts.map +1 -0
  9. package/dist/child-environment.js +18 -0
  10. package/dist/child-environment.js.map +1 -0
  11. package/dist/claude-login-lock.d.ts +9 -0
  12. package/dist/claude-login-lock.d.ts.map +1 -0
  13. package/dist/claude-login-lock.js +15 -0
  14. package/dist/claude-login-lock.js.map +1 -0
  15. package/dist/cloudflare/client.d.ts +50 -0
  16. package/dist/cloudflare/client.d.ts.map +1 -0
  17. package/dist/cloudflare/client.js +108 -0
  18. package/dist/cloudflare/client.js.map +1 -0
  19. package/dist/cloudflare/cloudflare.d.ts +17 -0
  20. package/dist/cloudflare/cloudflare.d.ts.map +1 -0
  21. package/dist/cloudflare/cloudflare.js +704 -0
  22. package/dist/cloudflare/cloudflare.js.map +1 -0
  23. package/dist/cloudflare/model.d.ts +42 -0
  24. package/dist/cloudflare/model.d.ts.map +1 -0
  25. package/dist/cloudflare/model.js +2 -0
  26. package/dist/cloudflare/model.js.map +1 -0
  27. package/dist/cloudflare/shell/environment.d.ts +12 -0
  28. package/dist/cloudflare/shell/environment.d.ts.map +1 -0
  29. package/dist/cloudflare/shell/environment.js +12 -0
  30. package/dist/cloudflare/shell/environment.js.map +1 -0
  31. package/dist/cloudflare/shell/shell.d.ts +2 -0
  32. package/dist/cloudflare/shell/shell.d.ts.map +1 -0
  33. package/dist/cloudflare/shell/shell.js +155 -0
  34. package/dist/cloudflare/shell/shell.js.map +1 -0
  35. package/dist/credential-environment.d.ts +20 -0
  36. package/dist/credential-environment.d.ts.map +1 -0
  37. package/dist/credential-environment.js +20 -0
  38. package/dist/credential-environment.js.map +1 -0
  39. package/dist/durable-session.d.ts +81 -0
  40. package/dist/durable-session.d.ts.map +1 -0
  41. package/dist/durable-session.js +550 -0
  42. package/dist/durable-session.js.map +1 -0
  43. package/dist/exe-dev/commands.d.ts +23 -13
  44. package/dist/exe-dev/commands.d.ts.map +1 -1
  45. package/dist/exe-dev/commands.js +11 -57
  46. package/dist/exe-dev/commands.js.map +1 -1
  47. package/dist/exe-dev/exe-dev.d.ts +9 -3
  48. package/dist/exe-dev/exe-dev.d.ts.map +1 -1
  49. package/dist/exe-dev/exe-dev.js +61 -128
  50. package/dist/exe-dev/exe-dev.js.map +1 -1
  51. package/dist/exe-dev/grants.d.ts.map +1 -1
  52. package/dist/exe-dev/grants.js +35 -15
  53. package/dist/exe-dev/grants.js.map +1 -1
  54. package/dist/exe-dev/model.d.ts +15 -19
  55. package/dist/exe-dev/model.d.ts.map +1 -1
  56. package/dist/exe-dev/model.js +9 -12
  57. package/dist/exe-dev/model.js.map +1 -1
  58. package/dist/exe-dev/preflight.d.ts +8 -4
  59. package/dist/exe-dev/preflight.d.ts.map +1 -1
  60. package/dist/exe-dev/preflight.js +26 -35
  61. package/dist/exe-dev/preflight.js.map +1 -1
  62. package/dist/executor.d.ts +292 -44
  63. package/dist/executor.d.ts.map +1 -1
  64. package/dist/executor.js +187 -19
  65. package/dist/executor.js.map +1 -1
  66. package/dist/guard.d.ts +57 -7
  67. package/dist/guard.d.ts.map +1 -1
  68. package/dist/guard.js +274 -59
  69. package/dist/guard.js.map +1 -1
  70. package/dist/harness-preflight.d.ts +27 -5
  71. package/dist/harness-preflight.d.ts.map +1 -1
  72. package/dist/harness-preflight.js +32 -31
  73. package/dist/harness-preflight.js.map +1 -1
  74. package/dist/keyed-lock.d.ts +2 -3
  75. package/dist/keyed-lock.d.ts.map +1 -1
  76. package/dist/keyed-lock.js +2 -3
  77. package/dist/keyed-lock.js.map +1 -1
  78. package/dist/leftovers.d.ts +9 -0
  79. package/dist/leftovers.d.ts.map +1 -0
  80. package/dist/leftovers.js +14 -0
  81. package/dist/leftovers.js.map +1 -0
  82. package/dist/local/credentials.d.ts +21 -0
  83. package/dist/local/credentials.d.ts.map +1 -0
  84. package/dist/local/credentials.js +39 -0
  85. package/dist/local/credentials.js.map +1 -0
  86. package/dist/local/leftovers.d.ts +8 -0
  87. package/dist/local/leftovers.d.ts.map +1 -0
  88. package/dist/local/leftovers.js +23 -0
  89. package/dist/local/leftovers.js.map +1 -0
  90. package/dist/local/local.d.ts +17 -11
  91. package/dist/local/local.d.ts.map +1 -1
  92. package/dist/local/local.js +205 -132
  93. package/dist/local/local.js.map +1 -1
  94. package/dist/memory-preflight.d.ts +27 -0
  95. package/dist/memory-preflight.d.ts.map +1 -0
  96. package/dist/memory-preflight.js +83 -0
  97. package/dist/memory-preflight.js.map +1 -0
  98. package/dist/pre-push-hook.d.ts +20 -0
  99. package/dist/pre-push-hook.d.ts.map +1 -0
  100. package/dist/pre-push-hook.js +33 -0
  101. package/dist/pre-push-hook.js.map +1 -0
  102. package/dist/prepared-attempt.d.ts +41 -3
  103. package/dist/prepared-attempt.d.ts.map +1 -1
  104. package/dist/prepared-attempt.js +109 -31
  105. package/dist/prepared-attempt.js.map +1 -1
  106. package/dist/provision.d.ts +129 -15
  107. package/dist/provision.d.ts.map +1 -1
  108. package/dist/provision.js +428 -69
  109. package/dist/provision.js.map +1 -1
  110. package/dist/remote/commands.d.ts +51 -0
  111. package/dist/remote/commands.d.ts.map +1 -0
  112. package/dist/remote/commands.js +94 -0
  113. package/dist/remote/commands.js.map +1 -0
  114. package/dist/remote/container.d.ts +90 -0
  115. package/dist/remote/container.d.ts.map +1 -0
  116. package/dist/remote/container.js +480 -0
  117. package/dist/remote/container.js.map +1 -0
  118. package/dist/remote/leftovers.d.ts +14 -0
  119. package/dist/remote/leftovers.d.ts.map +1 -0
  120. package/dist/remote/leftovers.js +22 -0
  121. package/dist/remote/leftovers.js.map +1 -0
  122. package/dist/remote/liveness.d.ts +33 -0
  123. package/dist/remote/liveness.d.ts.map +1 -0
  124. package/dist/remote/liveness.js +196 -0
  125. package/dist/remote/liveness.js.map +1 -0
  126. package/dist/remote/machine.d.ts +110 -0
  127. package/dist/remote/machine.d.ts.map +1 -0
  128. package/dist/remote/machine.js +2 -0
  129. package/dist/remote/machine.js.map +1 -0
  130. package/dist/remote/remote.d.ts +28 -0
  131. package/dist/remote/remote.d.ts.map +1 -0
  132. package/dist/remote/remote.js +556 -0
  133. package/dist/remote/remote.js.map +1 -0
  134. package/dist/remote/sweep.d.ts +17 -0
  135. package/dist/remote/sweep.d.ts.map +1 -0
  136. package/dist/remote/sweep.js +117 -0
  137. package/dist/remote/sweep.js.map +1 -0
  138. package/dist/renewable-credential.d.ts +17 -0
  139. package/dist/renewable-credential.d.ts.map +1 -0
  140. package/dist/renewable-credential.js +58 -0
  141. package/dist/renewable-credential.js.map +1 -0
  142. package/dist/salvage.d.ts +38 -0
  143. package/dist/salvage.d.ts.map +1 -0
  144. package/dist/salvage.js +198 -0
  145. package/dist/salvage.js.map +1 -0
  146. package/dist/ssh/api-keys.d.ts +36 -0
  147. package/dist/ssh/api-keys.d.ts.map +1 -0
  148. package/dist/ssh/api-keys.js +72 -0
  149. package/dist/ssh/api-keys.js.map +1 -0
  150. package/dist/ssh/codex-homes.d.ts +21 -0
  151. package/dist/ssh/codex-homes.d.ts.map +1 -0
  152. package/dist/ssh/codex-homes.js +78 -0
  153. package/dist/ssh/codex-homes.js.map +1 -0
  154. package/dist/ssh/container.d.ts +32 -0
  155. package/dist/ssh/container.d.ts.map +1 -0
  156. package/dist/ssh/container.js +282 -0
  157. package/dist/ssh/container.js.map +1 -0
  158. package/dist/ssh/credentials.d.ts +43 -0
  159. package/dist/ssh/credentials.d.ts.map +1 -0
  160. package/dist/ssh/credentials.js +86 -0
  161. package/dist/ssh/credentials.js.map +1 -0
  162. package/dist/ssh/gateway/admin.d.ts +45 -0
  163. package/dist/ssh/gateway/admin.d.ts.map +1 -0
  164. package/dist/ssh/gateway/admin.js +129 -0
  165. package/dist/ssh/gateway/admin.js.map +1 -0
  166. package/dist/ssh/gateway/attachments.d.ts +31 -0
  167. package/dist/ssh/gateway/attachments.d.ts.map +1 -0
  168. package/dist/ssh/gateway/attachments.js +168 -0
  169. package/dist/ssh/gateway/attachments.js.map +1 -0
  170. package/dist/ssh/gateway/environment.d.ts +71 -0
  171. package/dist/ssh/gateway/environment.d.ts.map +1 -0
  172. package/dist/ssh/gateway/environment.js +148 -0
  173. package/dist/ssh/gateway/environment.js.map +1 -0
  174. package/dist/ssh/gateway/forward.d.ts +74 -0
  175. package/dist/ssh/gateway/forward.d.ts.map +1 -0
  176. package/dist/ssh/gateway/forward.js +247 -0
  177. package/dist/ssh/gateway/forward.js.map +1 -0
  178. package/dist/ssh/gateway/preflight.d.ts +67 -0
  179. package/dist/ssh/gateway/preflight.d.ts.map +1 -0
  180. package/dist/ssh/gateway/preflight.js +320 -0
  181. package/dist/ssh/gateway/preflight.js.map +1 -0
  182. package/dist/ssh/limits.d.ts +60 -0
  183. package/dist/ssh/limits.d.ts.map +1 -0
  184. package/dist/ssh/limits.js +84 -0
  185. package/dist/ssh/limits.js.map +1 -0
  186. package/dist/ssh/model.d.ts +153 -0
  187. package/dist/ssh/model.d.ts.map +1 -0
  188. package/dist/ssh/model.js +2 -0
  189. package/dist/ssh/model.js.map +1 -0
  190. package/dist/ssh/preflight.d.ts +35 -0
  191. package/dist/ssh/preflight.d.ts.map +1 -0
  192. package/dist/ssh/preflight.js +87 -0
  193. package/dist/ssh/preflight.js.map +1 -0
  194. package/dist/ssh/provisioner/boat.d.ts +33 -0
  195. package/dist/ssh/provisioner/boat.d.ts.map +1 -0
  196. package/dist/ssh/provisioner/boat.js +247 -0
  197. package/dist/ssh/provisioner/boat.js.map +1 -0
  198. package/dist/ssh/provisioner/hetzner.d.ts +32 -0
  199. package/dist/ssh/provisioner/hetzner.d.ts.map +1 -0
  200. package/dist/ssh/provisioner/hetzner.js +337 -0
  201. package/dist/ssh/provisioner/hetzner.js.map +1 -0
  202. package/dist/ssh/provisioner/host-key.d.ts +11 -0
  203. package/dist/ssh/provisioner/host-key.d.ts.map +1 -0
  204. package/dist/ssh/provisioner/host-key.js +57 -0
  205. package/dist/ssh/provisioner/host-key.js.map +1 -0
  206. package/dist/ssh/provisioner/http.d.ts +45 -0
  207. package/dist/ssh/provisioner/http.d.ts.map +1 -0
  208. package/dist/ssh/provisioner/http.js +72 -0
  209. package/dist/ssh/provisioner/http.js.map +1 -0
  210. package/dist/ssh/provisioner/machines.d.ts +48 -0
  211. package/dist/ssh/provisioner/machines.d.ts.map +1 -0
  212. package/dist/ssh/provisioner/machines.js +686 -0
  213. package/dist/ssh/provisioner/machines.js.map +1 -0
  214. package/dist/ssh/provisioner/model.d.ts +92 -0
  215. package/dist/ssh/provisioner/model.d.ts.map +1 -0
  216. package/dist/ssh/provisioner/model.js +37 -0
  217. package/dist/ssh/provisioner/model.js.map +1 -0
  218. package/dist/ssh/ssh.d.ts +23 -0
  219. package/dist/ssh/ssh.d.ts.map +1 -0
  220. package/dist/ssh/ssh.js +145 -0
  221. package/dist/ssh/ssh.js.map +1 -0
  222. package/dist/ssh/worker-records.d.ts +18 -0
  223. package/dist/ssh/worker-records.d.ts.map +1 -0
  224. package/dist/ssh/worker-records.js +100 -0
  225. package/dist/ssh/worker-records.js.map +1 -0
  226. package/dist/ssh/workers.d.ts +80 -0
  227. package/dist/ssh/workers.d.ts.map +1 -0
  228. package/dist/ssh/workers.js +247 -0
  229. package/dist/ssh/workers.js.map +1 -0
  230. package/package.json +25 -9
  231. package/dist/exe-dev/channel.d.ts +0 -12
  232. package/dist/exe-dev/channel.d.ts.map +0 -1
  233. package/dist/exe-dev/channel.js +0 -42
  234. package/dist/exe-dev/channel.js.map +0 -1
  235. package/dist/exe-dev/shell.d.ts +0 -12
  236. package/dist/exe-dev/shell.d.ts.map +0 -1
  237. package/dist/exe-dev/shell.js +0 -31
  238. package/dist/exe-dev/shell.js.map +0 -1
  239. package/dist/exe-dev/sweep.d.ts +0 -10
  240. package/dist/exe-dev/sweep.d.ts.map +0 -1
  241. package/dist/exe-dev/sweep.js +0 -117
  242. package/dist/exe-dev/sweep.js.map +0 -1
  243. package/dist/invalid-frame.d.ts +0 -17
  244. package/dist/invalid-frame.d.ts.map +0 -1
  245. package/dist/invalid-frame.js +0 -111
  246. package/dist/invalid-frame.js.map +0 -1
  247. package/dist/run-host-session.d.ts +0 -27
  248. package/dist/run-host-session.d.ts.map +0 -1
  249. package/dist/run-host-session.js +0 -303
  250. package/dist/run-host-session.js.map +0 -1
@@ -0,0 +1,129 @@
1
+ import { WorkerName } from "@usine/core/worker";
2
+ import { GatewayAdminApi, } from "@usine/gateway/admin";
3
+ import { Data, DateTime, Effect, Option, Result, Schema } from "effect";
4
+ import { HttpClient, HttpClientError, HttpClientRequest } from "effect/http";
5
+ import { HttpApiClient } from "effect/http-api";
6
+ import { GatewayForwardClosed } from "./forward.js";
7
+ const GatewayAdminOperation = Schema.Literals(["attach", "renew", "detach", "list", "providers"]);
8
+ const ACTIONS = {
9
+ attach: "attach a token",
10
+ renew: "renew an attachment",
11
+ detach: "detach an attachment",
12
+ list: "list the attachments",
13
+ providers: "list the LLM providers",
14
+ };
15
+ /**
16
+ * The error for a failed Gateway admin API request. `message` contains no token; the request
17
+ * and raw failure are discarded because an attach request contains the installation token.
18
+ */
19
+ export class GatewayAdminError extends Schema.TaggedError("@usine/executor/GatewayAdminError")("GatewayAdminError", {
20
+ worker: WorkerName,
21
+ operation: GatewayAdminOperation,
22
+ message: Schema.NonEmptyString,
23
+ }) {
24
+ }
25
+ /** The timeout for one admin request. A publish keeps the Attempt's scope open while it runs. */
26
+ const ADMIN_REQUEST_TIMEOUT = "30 seconds";
27
+ /**
28
+ * The error when an admin request through the open forward gets no response. `since` is when
29
+ * the first unanswered request after the last response was sent.
30
+ */
31
+ class GatewayAdminUnanswered extends Data.TaggedError("GatewayAdminUnanswered") {
32
+ }
33
+ /** Returns the failure reason, built only from the forward's state or the failure's tag and status. */
34
+ const reason = (failure) => {
35
+ if (failure instanceof GatewayForwardClosed)
36
+ return `the forward through ${failure.gateway} has been closed since ${DateTime.formatIso(failure.since)}: ${failure.reason}`;
37
+ if (failure instanceof GatewayAdminUnanswered)
38
+ return `the admin API through ${failure.gateway} hasn't responded since ${DateTime.formatIso(failure.since)}: ${failure.detail}`;
39
+ if (HttpClientError.isHttpClientError(failure))
40
+ return failure.response === undefined
41
+ ? `no response (${failure.reason._tag})`
42
+ : `the admin API returned status ${String(failure.response.status)}`;
43
+ switch (failure._tag) {
44
+ case "TimeoutError":
45
+ return `no response within ${ADMIN_REQUEST_TIMEOUT}`;
46
+ case "Unauthorized":
47
+ return "the gateway rejected the admin token";
48
+ case "AttachmentAlreadyExpired":
49
+ return "the token had already expired";
50
+ case "AttachmentConflict":
51
+ return "the attachment ID belongs to another worker or repository";
52
+ case "SchemaError":
53
+ return "the admin API response doesn't match its schema";
54
+ default:
55
+ return `the request failed with ${failure._tag}`;
56
+ }
57
+ };
58
+ /** Returns whether the admin API returned 404 because the Gateway predates the endpoint. */
59
+ const unknownRoute = (failure) => HttpClientError.isHttpClientError(failure) && failure.response?.status === 404;
60
+ /** Returns whether the request got no response, possibly because the forward's SSH connection died unnoticed. */
61
+ const unanswered = (failure) => failure._tag === "TimeoutError" ||
62
+ (HttpClientError.isHttpClientError(failure) && failure.response === undefined);
63
+ /** Returns the log field with the time the admin API became unreachable, if the failure has it. */
64
+ const sinceFields = (failure) => {
65
+ if (failure instanceof GatewayForwardClosed)
66
+ return { closedSince: DateTime.formatIso(failure.since) };
67
+ if (failure instanceof GatewayAdminUnanswered)
68
+ return { unansweredSince: DateTime.formatIso(failure.since) };
69
+ return {};
70
+ };
71
+ const adminClientAt = (url, adminToken) => HttpApiClient.make(GatewayAdminApi, {
72
+ baseUrl: url,
73
+ transformClient: HttpClient.mapRequest(HttpClientRequest.bearerToken(adminToken)),
74
+ });
75
+ /**
76
+ * Returns the admin API client for the Gateway in front of `worker`, connected through
77
+ * `forward` with the admin token as a bearer token. If the forward is closed or a request gets
78
+ * no response, reopens the forward and retries once.
79
+ */
80
+ export const gatewayAdminFor = Effect.fn("SshExecutor.gatewayAdminFor")(function* (worker, gateway, forward) {
81
+ const http = yield* HttpClient.HttpClient;
82
+ const exchange = Effect.fn("SshExecutor.gatewayAdminRequest")(function* (operation, call) {
83
+ yield* Effect.annotateCurrentSpan({ worker, operation, gateway: forward.gateway });
84
+ const through = Effect.fnUntraced(function* (open) {
85
+ const sentAt = yield* DateTime.now;
86
+ const sent = yield* Effect.result(adminClientAt(open.url, gateway.adminToken).pipe(Effect.provideService(HttpClient.HttpClient, http), Effect.flatMap(call), Effect.timeout(ADMIN_REQUEST_TIMEOUT)));
87
+ if (Result.isFailure(sent) && unanswered(sent.failure))
88
+ return yield* new GatewayAdminUnanswered({
89
+ gateway: forward.gateway,
90
+ since: yield* forward.unanswered(sentAt),
91
+ detail: reason(sent.failure),
92
+ });
93
+ yield* forward.answered;
94
+ if (Result.isFailure(sent))
95
+ return yield* Effect.fail(sent.failure);
96
+ return sent.success;
97
+ });
98
+ const retried = (generation, failure) => Effect.logWarning("A Gateway admin request did not get through; reopening the forward and sending it once more", {
99
+ worker,
100
+ operation,
101
+ gateway: forward.gateway,
102
+ generation,
103
+ reason: reason(failure),
104
+ ...sinceFields(failure),
105
+ }).pipe(Effect.andThen(forward.reopen(generation)), Effect.flatMap(through));
106
+ const opened = yield* Effect.result(forward.current);
107
+ if (Result.isFailure(opened))
108
+ return yield* retried(opened.failure.generation, opened.failure);
109
+ const answered = yield* Effect.result(through(opened.success));
110
+ if (Result.isSuccess(answered))
111
+ return answered.success;
112
+ if (!(answered.failure instanceof GatewayAdminUnanswered))
113
+ return yield* Effect.fail(answered.failure);
114
+ return yield* retried(opened.success.generation, answered.failure);
115
+ });
116
+ const bounded = (operation) => (call) => exchange(operation, call).pipe(Effect.mapError((failure) => new GatewayAdminError({
117
+ worker,
118
+ operation,
119
+ message: `Can't ${ACTIONS[operation]} on the admin API of gateway ${forward.gateway} for worker "${worker}": ${reason(failure)}`,
120
+ })));
121
+ return {
122
+ attach: (request) => bounded("attach")((client) => client.attachments.attach({ payload: request })),
123
+ renew: (id, request) => bounded("renew")((client) => client.attachments.renew({ params: { id }, payload: request })),
124
+ detach: (id) => bounded("detach")((client) => client.attachments.detach({ params: { id } })),
125
+ list: bounded("list")((client) => client.attachments.list()),
126
+ providers: bounded("providers")((client) => client.llm.providers().pipe(Effect.map(Option.some), Effect.catchIf(unknownRoute, () => Effect.succeedNone))),
127
+ };
128
+ });
129
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../../src/ssh/gateway/admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { RemoteShell } from "@usine/agents/child-host";
2
+ import type { WorkerName } from "@usine/core/worker";
3
+ import { Effect, Option, Redacted } from "effect";
4
+ import { CredentialProvider, CredentialUnavailableError, type ExecutorError, type ExecutorOperation } from "../../executor.ts";
5
+ import { type RemoteCommands } from "../../remote/commands.ts";
6
+ import type { MachineGateway, SshMachine } from "../model.ts";
7
+ /** Writes each file of `files` on the machine through `shell`, creating its directory. */
8
+ export declare const writeMachineFiles: (commands: RemoteCommands, operation: ExecutorOperation, shell: RemoteShell, files: ReadonlyArray<readonly [path: string, contents: string]>) => Effect.Effect<string, import("../../executor.ts").ExecutorOperationError, never>;
9
+ /**
10
+ * Grants each Attempt repository access through the Gateway without a token on the machine
11
+ * (ADR 0027). Attaches the token, renews it before expiry, re-stores it every two minutes, and
12
+ * detaches it when the scope closes. If the Gateway holds the OpenAI credential, gives the
13
+ * Attempt its Codex home through `holdCodexHome`. On first use of a Worker, detaches attachments
14
+ * left by an earlier process.
15
+ */
16
+ export declare const gatewayAttachmentsFor: (commands: RemoteCommands, workerFor: (worker: WorkerName) => Effect.Effect<{
17
+ readonly gateway: Option.Option<MachineGateway>;
18
+ }, ExecutorError>, holdCodexHome: (machine: SshMachine, attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<string, never, import("effect/Scope").Scope>) => Effect.Effect<{
19
+ acquire: (attemptId: string & import("effect/Brand").Brand<"AttemptId">, worker: string & import("effect/Brand").Brand<"WorkerName">, repository: string & import("effect/Brand").Brand<"RepositoryName">, permissions: "read" | "write", machine: SshMachine, gateway: MachineGateway, operation: "cancel" | "cleanup" | "launch" | "preflight" | "prepare") => Effect.Effect<{
20
+ shell: RemoteShell;
21
+ secrets: Readonly<Record<string, Redacted.Redacted<string>>>;
22
+ readOnlyPaths: string[];
23
+ writablePaths: string[];
24
+ environment: Readonly<Record<string, string>>;
25
+ gitEnvironment: Readonly<Record<string, string>>;
26
+ refresh: Effect.Effect<void, CredentialUnavailableError, never>;
27
+ scrub: (text: string) => string;
28
+ }, CredentialUnavailableError | import("../../executor.ts").ExecutorOperationError, import("effect/Scope").Scope>;
29
+ reclaim: (worker: WorkerName) => Effect.Effect<void, never, never>;
30
+ }, never, CredentialProvider>;
31
+ //# sourceMappingURL=attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../../src/ssh/gateway/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAI5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAmB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,QAAQ,CAAC;AACnF,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,0BAA0B,CAAC;AAI7E,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA6B9D,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,aAClB,cAAc,aACb,iBAAiB,SACrB,WAAW,SACX,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,qFAC2B,CAAC;AAW7F;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,iDAGtB,UAAU,KACf,MAAM,CAAC,MAAM,CAAC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;CAAE,EAAE,aAAa,CAAC;;;;;;;;;;;sBA6D7D,UAAU;6BAsKnC,CAAC"}
@@ -0,0 +1,168 @@
1
+ import { AttachRequest, InstallationCredential } from "@usine/gateway/admin";
2
+ import { Cache, DateTime, Effect, Option, Redacted, Ref, Semaphore } from "effect";
3
+ import { CredentialProvider, CredentialUnavailableError, } from "../../executor.js";
4
+ import { sendingShell } from "../../remote/commands.js";
5
+ import { renewableCredential } from "../../renewable-credential.js";
6
+ import { attemptCodexHome } from "../codex-homes.js";
7
+ import { GH_GATEWAY_SOCKET_DIRECTORY, gatewayConfigFiles, gatewayEnvironment, } from "./environment.js";
8
+ /**
9
+ * A script that takes path and contents pairs and writes each file, creating its directory.
10
+ * Each file is written to a temporary name and renamed.
11
+ */
12
+ const WRITE_FILES_SCRIPT = String.raw `
13
+ set -e
14
+ while [ "$#" -gt 1 ]; do
15
+ mkdir -p "$(dirname "$1")"
16
+ printf '%s' "$2" > "$1.$$"
17
+ mv "$1.$$" "$1"
18
+ shift 2
19
+ done
20
+ `.trim();
21
+ /** The interval for re-storing a live Attempt's attachment, which bounds the outage after a Gateway restart. */
22
+ const RESTORE_INTERVAL = "2 minutes";
23
+ /** Writes each file of `files` on the machine through `shell`, creating its directory. */
24
+ export const writeMachineFiles = (commands, operation, shell, files) => commands.run(operation, shell, "sh", ["-c", WRITE_FILES_SCRIPT, "sh", ...files.flat()]);
25
+ /**
26
+ * Returns the directory of gh's configuration on a Worker behind a Gateway, and the Attempt's
27
+ * Codex home. Neither contains a token.
28
+ */
29
+ const configDirectories = (machine, attemptId) => ({
30
+ ghConfigDirectory: `${machine.workerDirectory}/gh`,
31
+ codexHome: attemptCodexHome(machine, attemptId),
32
+ });
33
+ /**
34
+ * Grants each Attempt repository access through the Gateway without a token on the machine
35
+ * (ADR 0027). Attaches the token, renews it before expiry, re-stores it every two minutes, and
36
+ * detaches it when the scope closes. If the Gateway holds the OpenAI credential, gives the
37
+ * Attempt its Codex home through `holdCodexHome`. On first use of a Worker, detaches attachments
38
+ * left by an earlier process.
39
+ */
40
+ export const gatewayAttachmentsFor = Effect.fn("SshExecutor.gatewayAttachmentsFor")(function* (commands, workerFor, holdCodexHome) {
41
+ const provider = yield* CredentialProvider;
42
+ const detachLeftovers = Effect.fn("SshExecutor.detachLeftoverAttachments")(function* (worker) {
43
+ const machine = yield* workerFor(worker);
44
+ yield* Effect.annotateCurrentSpan({ worker });
45
+ if (Option.isNone(machine.gateway))
46
+ return yield* Effect.logDebug("The Worker has no Gateway to detach leftovers from", {
47
+ worker,
48
+ });
49
+ const { admin, settings } = machine.gateway.value;
50
+ yield* Effect.annotateCurrentSpan({ domain: settings.domain });
51
+ const { attachments } = yield* admin.list;
52
+ const leftovers = attachments.filter((attachment) => attachment.worker === worker);
53
+ if (leftovers.length === 0)
54
+ return yield* Effect.logDebug("No earlier process left a Gateway attachment for the Worker", {
55
+ worker,
56
+ domain: settings.domain,
57
+ });
58
+ // Detaches each leftover separately so one failure doesn't stop the others.
59
+ yield* Effect.forEach(leftovers, ({ id, repository }) => admin.detach(id).pipe(Effect.tap(({ detached }) => Effect.logInfo("Detached a Gateway attachment an earlier process left", {
60
+ worker,
61
+ repository,
62
+ attachment: id,
63
+ detached,
64
+ })), Effect.tapError((error) => Effect.logWarning("Could not detach a Gateway attachment an earlier process left; it expires with its token", { worker, repository, attachment: id, message: error.message })), Effect.ignore), { discard: true });
65
+ });
66
+ // Caches a failed listing; the leftover attachments expire with their tokens within an hour.
67
+ const reclaimed = yield* Cache.make({
68
+ capacity: Number.POSITIVE_INFINITY,
69
+ lookup: (worker) => detachLeftovers(worker).pipe(Effect.tapError((error) => Effect.logWarning("Could not list the Gateway attachments an earlier process left; they expire with their tokens", { worker, message: error.message })), Effect.ignore),
70
+ });
71
+ const reclaim = (worker) => Cache.get(reclaimed, worker);
72
+ const acquire = Effect.fn("SshExecutor.attachToGateway")(function* (attemptId, worker, repository, permissions, machine, gateway, operation) {
73
+ const { admin, settings } = gateway;
74
+ const fields = { attemptId, worker, repository, permissions, domain: settings.domain };
75
+ yield* Effect.annotateCurrentSpan(fields);
76
+ const directories = configDirectories(machine, attemptId);
77
+ const codex = settings.llmProviders.includes("openai");
78
+ if (codex)
79
+ yield* holdCodexHome(machine, attemptId);
80
+ yield* writeMachineFiles(commands, operation, machine.shell, gatewayConfigFiles({
81
+ domain: settings.domain,
82
+ llmProviders: settings.llmProviders,
83
+ ...directories,
84
+ }));
85
+ const attached = yield* Ref.make(Option.none());
86
+ // Serializes publish and restore so the Gateway never gets an older token.
87
+ const turn = yield* Semaphore.make(1);
88
+ yield* Effect.addFinalizer(() => Ref.get(attached).pipe(Effect.flatMap(Option.match({
89
+ onNone: () => Effect.logDebug("The Attempt has no Gateway attachment to detach", fields),
90
+ onSome: ({ id }) => admin.detach(id).pipe(Effect.tap(({ detached }) => Effect.logInfo("Detached the Attempt's token from the Gateway", {
91
+ ...fields,
92
+ attachment: id,
93
+ detached,
94
+ })), Effect.tapError((error) => Effect.logWarning("Could not detach the Attempt's token from the Gateway; the attachment expires with the token", { ...fields, attachment: id, message: error.message })), Effect.ignore),
95
+ }))));
96
+ const requestFor = (credential) => new AttachRequest({
97
+ worker,
98
+ repository,
99
+ credential: new InstallationCredential({ token: credential.value }),
100
+ expiresAt: credential.expiresAt,
101
+ });
102
+ const publish = Effect.fn("SshExecutor.publishAttachment")(function* (credential) {
103
+ yield* Effect.annotateCurrentSpan(fields);
104
+ const current = yield* Ref.get(attached);
105
+ const answer = yield* Option.match(current, {
106
+ onNone: () => admin.attach(requestFor(credential)),
107
+ onSome: ({ id }) => admin.renew(id, requestFor(credential)),
108
+ }).pipe(Effect.mapError((cause) => new CredentialUnavailableError({
109
+ repository,
110
+ reason: "unreachable",
111
+ message: cause.message,
112
+ cause,
113
+ })));
114
+ yield* Ref.set(attached, Option.some({ id: answer.attachment.id, credential }));
115
+ yield* Effect.annotateCurrentSpan({ attachment: answer.attachment.id });
116
+ yield* Effect.logInfo(Option.isNone(current)
117
+ ? "Attached the Attempt's token to the Gateway"
118
+ : "Renewed the Attempt's attachment on the Gateway", {
119
+ ...fields,
120
+ attachment: answer.attachment.id,
121
+ expiresAt: DateTime.formatIso(answer.attachment.expiresAt),
122
+ });
123
+ return answer.callerToken;
124
+ },
125
+ // Scope closure waits for an in-flight attach, so the detach has the attachment ID.
126
+ turn.withPermits(1), Effect.uninterruptible);
127
+ // The Gateway stores attachments in memory. After a restart, re-creates the attachment with
128
+ // the same ID and the current token.
129
+ const restore = Effect.fn("SshExecutor.restoreAttachment")(function* () {
130
+ yield* Effect.annotateCurrentSpan(fields);
131
+ const current = yield* Ref.get(attached);
132
+ if (Option.isNone(current))
133
+ return;
134
+ const { id, credential } = current.value;
135
+ yield* Effect.annotateCurrentSpan({ attachment: id });
136
+ yield* admin.renew(id, requestFor(credential));
137
+ yield* Effect.logDebug("Confirmed the Attempt's attachment on the Gateway", {
138
+ ...fields,
139
+ attachment: id,
140
+ });
141
+ }, turn.withPermits(1), Effect.uninterruptible);
142
+ const credential = yield* renewableCredential(provider.issue, repository, attemptId, permissions, publish);
143
+ const callerToken = credential.published;
144
+ // Forked after renewal so scope closure stops it before the token is wiped and detached.
145
+ yield* Effect.forever(Effect.sleep(RESTORE_INTERVAL).pipe(Effect.andThen(restore()), Effect.tapError((error) => Effect.logWarning("Could not confirm the Attempt's attachment on the Gateway; will retry", {
146
+ ...fields,
147
+ message: error.message,
148
+ })), Effect.ignore)).pipe(Effect.forkScoped);
149
+ const { exported, sent } = gatewayEnvironment({
150
+ domain: settings.domain,
151
+ llmProviders: settings.llmProviders,
152
+ ...directories,
153
+ callerToken,
154
+ });
155
+ return {
156
+ shell: sendingShell(machine.shell, sent),
157
+ secrets: sent,
158
+ readOnlyPaths: [directories.ghConfigDirectory, GH_GATEWAY_SOCKET_DIRECTORY],
159
+ writablePaths: codex ? [directories.codexHome] : [],
160
+ environment: exported,
161
+ gitEnvironment: exported,
162
+ refresh: credential.refresh(),
163
+ scrub: (text) => credential.scrub(text).replaceAll(Redacted.value(callerToken), "[REDACTED]"),
164
+ };
165
+ });
166
+ return { acquire, reclaim };
167
+ });
168
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../../src/ssh/gateway/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { RepositoryName } from "@usine/core/repository";
2
+ import type { GatewayDomain, GatewayLlmProvider } from "@usine/core/worker";
3
+ import { type CallerToken } from "@usine/gateway/admin";
4
+ import { Redacted } from "effect";
5
+ /** The directory of the Unix socket that gh reaches the Gateway through. */
6
+ export declare const GH_GATEWAY_SOCKET_DIRECTORY = "/run/usine-gateway";
7
+ /** The variables sent through the SSH connection because they contain the caller token. The Worker's sshd must accept each in `AcceptEnv`. */
8
+ export declare const GATEWAY_SENT_VARIABLES: readonly ["GIT_CONFIG_VALUE_0", "GH_ENTERPRISE_TOKEN", "GH_TOKEN", "ANTHROPIC_AUTH_TOKEN", "USINE_GATEWAY_TOKEN"];
9
+ /** Returns the Gateway's forward proxy URL from a Worker; children receive it as `HTTPS_PROXY`. */
10
+ export declare const forwardProxyUrl: (domain: GatewayDomain) => string;
11
+ /** Returns the URL a Worker behind the Gateway clones and fetches `repository` from. */
12
+ export declare const gatewayRepositoryUrl: (domain: GatewayDomain, repository: RepositoryName) => string;
13
+ /**
14
+ * Returns gh's configuration files for the Gateway at `domain`. Neither file contains a token;
15
+ * gh reads the caller token from `GH_ENTERPRISE_TOKEN` and `GH_TOKEN`.
16
+ */
17
+ export declare const ghGatewayConfig: (domain: GatewayDomain) => {
18
+ readonly "config.yml": "version: \"1\"\nhttp_unix_socket: /run/usine-gateway/http.sock\n";
19
+ readonly "hosts.yml": `git.${string & import("effect/Brand").Brand<"GatewayDomain">}:
20
+ git_protocol: https
21
+ `;
22
+ };
23
+ export interface GatewayEnvironmentOptions {
24
+ readonly domain: GatewayDomain;
25
+ readonly llmProviders: ReadonlyArray<GatewayLlmProvider>;
26
+ /** The machine directory that contains the `ghGatewayConfig` files. */
27
+ readonly ghConfigDirectory: string;
28
+ /** The Attempt's machine directory that Codex uses as `CODEX_HOME` if the Gateway holds the OpenAI credential. */
29
+ readonly codexHome: string;
30
+ readonly callerToken: CallerToken;
31
+ }
32
+ /**
33
+ * Returns the configuration files that the executor writes on the machine before an Attempt,
34
+ * keyed by path: gh's files, and Codex's `config.toml` if the Gateway holds the OpenAI
35
+ * credential. None contains a token.
36
+ */
37
+ export declare const gatewayConfigFiles: (options: Omit<GatewayEnvironmentOptions, "callerToken">) => ReadonlyArray<readonly [path: string, contents: string]>;
38
+ /**
39
+ * An Attempt's environment behind a Gateway. `exported` has no secrets and goes in each command
40
+ * line; `sent` contains the caller token and goes only through the SSH connection.
41
+ */
42
+ export interface GatewayEnvironment {
43
+ readonly exported: Readonly<Record<string, string>>;
44
+ readonly sent: Readonly<Record<string, Redacted.Redacted<string>>>;
45
+ }
46
+ /**
47
+ * Returns the variables of `gatewayEnvironment` without a caller token or Codex's home, which
48
+ * only an Attempt has, for commands that reach the Gateway's forward proxy but no attachment,
49
+ * such as a preflight's harness probes.
50
+ */
51
+ export declare const gatewayExportedEnvironment: (settings: Pick<GatewayEnvironmentOptions, "domain" | "llmProviders">, ghConfigDirectory: string) => Readonly<Record<string, string>>;
52
+ /**
53
+ * Returns the environment that points git, gh, package managers, and Claude Code at the
54
+ * Gateway at `domain`.
55
+ *
56
+ * - git sends the caller token in `x-usine-gateway-token` to `git.<domain>` only, and rewrites
57
+ * `https://github.com/` and `https://git.<domain>/` to the Gateway. The header value is
58
+ * config entry 0 (`GIT_CONFIG_VALUE_0`).
59
+ * - gh uses `git.<domain>` through the Gateway socket, with the caller token as both its
60
+ * enterprise and GitHub token, so absolute pagination links also authenticate.
61
+ * - Package managers use the forward proxy at `<domain>:3128`; local addresses and the
62
+ * Gateway's own names bypass it.
63
+ * - Claude Code calls `llm.<domain>/anthropic` with the caller token if the Gateway has the
64
+ * Anthropic credential.
65
+ * - Codex reads `codexGatewayConfig` from `CODEX_HOME` and calls `llm.<domain>/openai/v1` with
66
+ * the caller token if the Gateway has the OpenAI credential.
67
+ * - A harness whose provider the Gateway has no credential for uses its own login through the
68
+ * forward proxy, whose allowlist must include the login's hosts.
69
+ */
70
+ export declare const gatewayEnvironment: (options: GatewayEnvironmentOptions) => GatewayEnvironment;
71
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../../src/ssh/gateway/environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,KAAK,WAAW,EAAwB,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,uBAAuB,CAAC;AAchE,8IAA8I;AAC9I,eAAO,MAAM,sBAAsB,YACjC,oBAAoB,EACpB,qBAAqB,EACrB,UAAU,EACV,sBAAsB,wBAEd,CAAC;AAEX,mGAAmG;AACnG,eAAO,MAAM,eAAe,WAAY,aAAa,KAAG,MACd,CAAC;AAE3C,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,WAAY,aAAa,cAAc,cAAc,KAAG,MAC/C,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,eAAe,WAAY,aAAa;aAEjD,YAAY;aACZ,WAAW;;;CACF,CAAC;AAoBd,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACzD,uEAAuE;IACvE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,kHAAkH;IAClH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,YACpB,IAAI,CAAC,yBAAyB,EAAE,aAAa,CAAC,KACtD,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CASzD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CACpE;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,aAC3B,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,cAAc,CAAC,qBACjD,MAAM,KACxB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQjC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,YAAa,yBAAyB,KAAG,kBAsCvE,CAAC"}
@@ -0,0 +1,148 @@
1
+ import { GATEWAY_TOKEN_HEADER } from "@usine/gateway/admin";
2
+ import { Redacted } from "effect";
3
+ /** The directory of the Unix socket that gh reaches the Gateway through. */
4
+ export const GH_GATEWAY_SOCKET_DIRECTORY = "/run/usine-gateway";
5
+ /**
6
+ * The Unix socket that systemd forwards to the Gateway's port 80
7
+ * (`infra/hetzner/gateway/worker-cloud-init.yaml`). gh sends plain HTTP over it, and only HTTPS
8
+ * to `GH_HOST` directly.
9
+ */
10
+ const GH_GATEWAY_SOCKET = `${GH_GATEWAY_SOCKET_DIRECTORY}/http.sock`;
11
+ const FORWARD_PROXY_PORT = 3128;
12
+ /** The variable that Codex's `model_provider` reads the caller token from (`env_key`). */
13
+ const CODEX_TOKEN_VARIABLE = "USINE_GATEWAY_TOKEN";
14
+ /** The variables sent through the SSH connection because they contain the caller token. The Worker's sshd must accept each in `AcceptEnv`. */
15
+ export const GATEWAY_SENT_VARIABLES = [
16
+ "GIT_CONFIG_VALUE_0",
17
+ "GH_ENTERPRISE_TOKEN",
18
+ "GH_TOKEN",
19
+ "ANTHROPIC_AUTH_TOKEN",
20
+ CODEX_TOKEN_VARIABLE,
21
+ ];
22
+ /** Returns the Gateway's forward proxy URL from a Worker; children receive it as `HTTPS_PROXY`. */
23
+ export const forwardProxyUrl = (domain) => `http://${domain}:${FORWARD_PROXY_PORT}`;
24
+ /** Returns the URL a Worker behind the Gateway clones and fetches `repository` from. */
25
+ export const gatewayRepositoryUrl = (domain, repository) => `http://git.${domain}/${repository}.git`;
26
+ /**
27
+ * Returns gh's configuration files for the Gateway at `domain`. Neither file contains a token;
28
+ * gh reads the caller token from `GH_ENTERPRISE_TOKEN` and `GH_TOKEN`.
29
+ */
30
+ export const ghGatewayConfig = (domain) => ({
31
+ "config.yml": `version: "1"\nhttp_unix_socket: ${GH_GATEWAY_SOCKET}\n`,
32
+ "hosts.yml": `git.${domain}:\n git_protocol: https\n`,
33
+ });
34
+ /**
35
+ * Returns Codex's `config.toml` for the Gateway at `domain`: a `model_provider` that sends the
36
+ * Responses API to `llm.<domain>/openai/v1` with the caller token from `USINE_GATEWAY_TOKEN` as
37
+ * the bearer token, and needs no login. The file contains no token.
38
+ */
39
+ const codexGatewayConfig = (domain) => [
40
+ 'model_provider = "usine-gateway"',
41
+ "",
42
+ "[model_providers.usine-gateway]",
43
+ 'name = "usine gateway"',
44
+ `base_url = "http://llm.${domain}/openai/v1"`,
45
+ `env_key = "${CODEX_TOKEN_VARIABLE}"`,
46
+ 'wire_api = "responses"',
47
+ "requires_openai_auth = false",
48
+ "",
49
+ ].join("\n");
50
+ /**
51
+ * Returns the configuration files that the executor writes on the machine before an Attempt,
52
+ * keyed by path: gh's files, and Codex's `config.toml` if the Gateway holds the OpenAI
53
+ * credential. None contains a token.
54
+ */
55
+ export const gatewayConfigFiles = (options) => {
56
+ const gh = ghGatewayConfig(options.domain);
57
+ return [
58
+ [`${options.ghConfigDirectory}/config.yml`, gh["config.yml"]],
59
+ [`${options.ghConfigDirectory}/hosts.yml`, gh["hosts.yml"]],
60
+ ...(options.llmProviders.includes("openai")
61
+ ? [[`${options.codexHome}/config.toml`, codexGatewayConfig(options.domain)]]
62
+ : []),
63
+ ];
64
+ };
65
+ /**
66
+ * Returns the variables of `gatewayEnvironment` without a caller token or Codex's home, which
67
+ * only an Attempt has, for commands that reach the Gateway's forward proxy but no attachment,
68
+ * such as a preflight's harness probes.
69
+ */
70
+ export const gatewayExportedEnvironment = (settings, ghConfigDirectory) => {
71
+ const { CODEX_HOME: _, ...exported } = gatewayEnvironment({
72
+ ...settings,
73
+ ghConfigDirectory,
74
+ codexHome: "",
75
+ callerToken: Redacted.make(""),
76
+ }).exported;
77
+ return exported;
78
+ };
79
+ /**
80
+ * Returns the environment that points git, gh, package managers, and Claude Code at the
81
+ * Gateway at `domain`.
82
+ *
83
+ * - git sends the caller token in `x-usine-gateway-token` to `git.<domain>` only, and rewrites
84
+ * `https://github.com/` and `https://git.<domain>/` to the Gateway. The header value is
85
+ * config entry 0 (`GIT_CONFIG_VALUE_0`).
86
+ * - gh uses `git.<domain>` through the Gateway socket, with the caller token as both its
87
+ * enterprise and GitHub token, so absolute pagination links also authenticate.
88
+ * - Package managers use the forward proxy at `<domain>:3128`; local addresses and the
89
+ * Gateway's own names bypass it.
90
+ * - Claude Code calls `llm.<domain>/anthropic` with the caller token if the Gateway has the
91
+ * Anthropic credential.
92
+ * - Codex reads `codexGatewayConfig` from `CODEX_HOME` and calls `llm.<domain>/openai/v1` with
93
+ * the caller token if the Gateway has the OpenAI credential.
94
+ * - A harness whose provider the Gateway has no credential for uses its own login through the
95
+ * forward proxy, whose allowlist must include the login's hosts.
96
+ */
97
+ export const gatewayEnvironment = (options) => {
98
+ const { domain, callerToken } = options;
99
+ const gitBase = `http://git.${domain}/`;
100
+ const proxy = forwardProxyUrl(domain);
101
+ const direct = `localhost,127.0.0.1,::1,${domain}`;
102
+ const exported = {
103
+ GIT_CONFIG_COUNT: "3",
104
+ GIT_CONFIG_KEY_0: `http.${gitBase}.extraHeader`,
105
+ GIT_CONFIG_KEY_1: `url.${gitBase}.insteadOf`,
106
+ GIT_CONFIG_VALUE_1: "https://github.com/",
107
+ GIT_CONFIG_KEY_2: `url.${gitBase}.insteadOf`,
108
+ GIT_CONFIG_VALUE_2: `https://git.${domain}/`,
109
+ GH_HOST: `git.${domain}`,
110
+ GH_CONFIG_DIR: options.ghConfigDirectory,
111
+ GH_PROMPT_DISABLED: "1",
112
+ HTTPS_PROXY: proxy,
113
+ HTTP_PROXY: proxy,
114
+ https_proxy: proxy,
115
+ http_proxy: proxy,
116
+ NO_PROXY: direct,
117
+ no_proxy: direct,
118
+ };
119
+ const sent = {
120
+ GIT_CONFIG_VALUE_0: Redacted.make(`${GATEWAY_TOKEN_HEADER}: ${Redacted.value(callerToken)}`),
121
+ GH_ENTERPRISE_TOKEN: callerToken,
122
+ GH_TOKEN: callerToken,
123
+ };
124
+ const harnesses = options.llmProviders.map((provider) => harnessVariables(provider, options));
125
+ return {
126
+ exported: {
127
+ ...exported,
128
+ ...Object.fromEntries(harnesses.flatMap(({ exported }) => Object.entries(exported))),
129
+ },
130
+ sent: {
131
+ ...sent,
132
+ ...Object.fromEntries(harnesses.map(({ token }) => [token, callerToken])),
133
+ },
134
+ };
135
+ };
136
+ /** Returns the variables that point `provider`'s harness at the Gateway. */
137
+ const harnessVariables = (provider, options) => {
138
+ switch (provider) {
139
+ case "anthropic":
140
+ return {
141
+ exported: { ANTHROPIC_BASE_URL: `http://llm.${options.domain}/anthropic` },
142
+ token: "ANTHROPIC_AUTH_TOKEN",
143
+ };
144
+ case "openai":
145
+ return { exported: { CODEX_HOME: options.codexHome }, token: CODEX_TOKEN_VARIABLE };
146
+ }
147
+ };
148
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../../../src/ssh/gateway/environment.ts"],"names":[],"mappings":"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"}