@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
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  # @usine/executor
2
2
 
3
- Worker execution for [usine](https://github.com/basaltbytes/usine). The platform-free package root defines Worker preflight, scoped Attempt preparation, and cancellation. Each prepared Attempt launches its own run host and releases its resources at completion or when its scope closes. It also owns the credential provider, Worker capability errors, and adapter-neutral request types.
3
+ Worker execution for [usine](https://github.com/basaltbytes/usine). The platform-free root defines worker preflight, scoped attempt preparation, and cancellation, plus the credential provider, worker capability errors, and adapter-neutral request types. Each prepared attempt runs its Pi parent in this process as a durable task, tells it where the worker runs children (local processes, or SSH on an exe.dev VM or any Linux machine), and releases its resources at completion or when its scope closes. [Attempt execution](https://docs.usine.dev/architecture/execution/) and [Workers and the gateway](https://docs.usine.dev/architecture/workers-and-gateway/) cover the executors.
4
+
5
+ 0.x APIs can change. Supply `effect` at the workspace's pinned version.
4
6
 
5
7
  ## Attempt lifetime
6
8
 
7
- Call `Executor.prepare(request)` inside `Effect.scoped`, then call `prepared.launch(io)`
8
- once. The returned `PreparedAttempt` owns the checkout and repository access. Its
9
- `workspace` is an `AttemptWorkspace` descriptor containing the Worker, RunSpec, and
10
- worktree path; it can be serialized without exposing credentials or live handles.
9
+ Call `Executor.prepare(request)` inside `Effect.scoped`, then `prepared.launch(io)` once. The `PreparedAttempt` owns the checkout and repo access. Its `workspace`, an `AttemptWorkspace` with the worker, run spec, and worktree path, serializes without credentials or live handles.
11
10
 
12
11
  ```ts
13
12
  const result = yield* Effect.scoped(
@@ -18,56 +17,86 @@ const result = yield* Effect.scoped(
18
17
  )
19
18
  ```
20
19
 
21
- Preparation remains interruptible during clone, fetch, credential requests, and lock
22
- waits. A failed prepare releases partial acquisitions immediately. An unlaunched
23
- Attempt releases its resources when the surrounding scope closes. Launch finishes
24
- cleanup before returning on success, failure, or interruption; closing the owner
25
- scope also stops a launch running in another fiber. Each Attempt launches at most
26
- once. Cleanup failures are logged without replacing a successful agent result with
27
- a retryable failure. Set `CheckoutRequest.maxAttemptMinutes` to bound live run-host
28
- execution; preparation and resource cleanup are outside that time budget.
20
+ Preparation stays interruptible during clone, fetch, credential requests, and lock waits. A failed prepare releases partial acquisitions at once, and an unlaunched attempt when the scope closes. Launch finishes cleanup before it returns on success, failure, or interruption, and closing the owner scope stops a launch in another fiber. Each attempt launches at most once. Cleanup failures are logged and never make a successful result retryable.
21
+
22
+ `CheckoutRequest.maxAttemptMinutes` bounds live execution, excluding preparation and cleanup. Both runaway ceilings are optional: an absent one leaves that half of the guard off, and an armed one steers the parent once at 80%. The clock starts once launch registers the session. The guard delivers its warning apart from its checks, once an `execute` relays the parent's channel (`AttemptRuntime.attached`): no trip waits for delivery, and the append that reaches the token warning returns before the steer is delivered, since the durable runtime records the steer in order with its envelopes.
23
+
24
+ `CheckoutRequest.permissions` is the loop's permission level. A `read` attempt's credential has `contents: read`, so it can't push. An exe.dev worker can't grant that, and refuses the attempt with a `credentials` `WorkerCapabilityError` before attaching the integration. A `read` checkout also gets a pre-push hook that stops `git push` with a message naming the loop, instead of GitHub's 403; `git push --no-verify` skips it, and the token still refuses. A `read` attempt is never salvaged.
25
+
26
+ ### Salvage
27
+
28
+ An attempt that ends without success is salvaged before its worktree is removed: uncommitted changes are committed as `wip(usine): salvage attempt <id>`, and HEAD is pushed to `CheckoutRequest.salvageRef` with the worker's credentials. The result reaches the engine through `Sink.salvage`. A failed push keeps the worktree and reports its path and the failure: git's exit code and stderr, so a deleted remote reads differently from a revoked app, or a push stalled past two minutes. Local and SSH adapters scrub the token from that stderr; an exe.dev VM never has one. The startup sweep leaves such a worktree alone. Each later startup retries the push with fresh credentials, for open and closed runs, and rewrites the record from `Retained` to `Pushed` on success; until then the record shows the latest failure in the run drawer.
29
+
30
+ A request whose `resume` names a pushed salvage fetches the revision and applies the salvage on top. A salvage forked from the fetched commit is checked out as is; one from an older base is rebased onto it. A salvage with no changes beyond the revision is dropped. One that doesn't rebase cleanly is left out: the worktree starts from the revision, `RunSpec.conflictingSalvage` names it, and its branch keeps the work. A missing ref falls back to the revision.
29
31
 
30
- Recognizable Effect log lines on run-host stdout are dropped with a warning that records safe shape metadata. Invalid JSON, schema-invalid frames, and protocol sequence violations still fail the Attempt.
32
+ Startup reconciliation lists each worker's `attempts/` once per executor instance, and adapters skip historical attempts missing from the listing without checking paths. A failed listing stays cached for that startup and is retried after a restart. Existing checkouts keep the record-before-remove salvage order; succeeded attempts only need removal.
31
33
 
32
- This replaces the 0.x `Executor.launch(prepared, io)` and `Executor.cleanup(prepared)`
33
- methods. Consumers supply a scope instead of coordinating cleanup. The engine's
34
- `StepExecutor` reservation, execution, and interruption contract remains unchanged.
34
+ ### Cancel
35
+
36
+ `cancel(attemptId)` applies from the start of preparation: harness probes, credential acquisition, repo lock waits, and checkout creation. It stops preparation before releasing acquired resources and blocks a later launch. `cancel` and the canceled `prepare` or `launch` return only after release. For a live attempt, it cancels the parent task and waits the configured grace before forcing termination. Concurrent and repeated cancels wait for cleanup and keep the launch's typed failure; after release, cancel does nothing.
37
+
38
+ This replaces the 0.x `Executor.launch(prepared, io)` and `Executor.cleanup(prepared)`; the engine's `StepExecutor` contract is unchanged.
35
39
 
36
40
  ## Worker preflight
37
41
 
38
- Call `executor.preflight(worker, brains)` with the instance roster’s `BrainSpec` values before scheduling Attempts. Both adapters invoke `usine-run-host --preflight '<brain-json>'` on the Worker. The run host checks each selected harness’s executable and authentication; Codex requires supported CLI 0.144.0 or 0.153.4 and a ChatGPT login, Claude requires its CLI and subscription login, and Pi children require API keys for their selected models.
42
+ Call `executor.preflight(worker, brains, maxConcurrentAttempts)` with the roster's `BrainSpec` values and the worker's `max_concurrent_attempts` before scheduling.
43
+
44
+ Every adapter compares the machine's memory with `maxConcurrentAttempts`. The local adapter reads `/proc/meminfo` on Linux and physical memory on macOS, where swap grows on demand; exe.dev and SSH read `/proc/meminfo` over SSH. Under 1.5 GiB per concurrent attempt, or no swap with more than one, is a `memory` `WorkerCapabilityError`; under 3 GiB logs a warning ([#270](https://github.com/basaltbytes/usine/issues/270)).
45
+
46
+ Every adapter runs the `@usine/agents/preflight` checks where children run: local on this machine (override with `LocalExecutorOptions.harnessReadiness`), exe.dev and SSH over SSH (`SshExecutorOptions.harnessReadiness`). Codex needs CLI 0.144.0 or 0.153.4 and a ChatGPT login, Claude its CLI and subscription login, and Pi children API keys for their models. Pi children run tools in the orchestrator, so exe.dev and SSH workers report Pi brains unavailable.
47
+
48
+ `prepare` repeats the assigned brain's probe before acquiring credentials or a checkout, to catch roster changes and logouts. A capability gap returns `WorkerCapabilityError`, which needs the operator. Transport failures, such as an SSH client that can't reach the VM, and probe timeouts are retryable infrastructure failures.
39
49
 
40
- `prepare` repeats the assigned brain’s probe before acquiring credentials or creating a checkout. This checks roster changes and logouts after startup. A reported capability gap returns `WorkerCapabilityError` and requires operator action. Transport failures and probe timeouts remain infrastructure failures eligible for retry. Custom run hosts must implement the credential-free `HarnessReadiness` response from `@usine/core/agent/capability`.
50
+ A Claude check holds the worker's `ClaudeLoginLock` (`@usine/agents/runtime`) until it returns, so two preparing attempts, or a preparing attempt and a starting Claude child, never refresh the login at once. The local, exe.dev, and SSH layers need that service: build `claudeLoginLockLayer` from the root once and give it to the executor and the attempt runtime.
41
51
 
42
- This replaces exe.dev’s `HarnessLoginCheck` and `harnessLoginChecks` options. Pass the roster to `preflight` so readiness follows the agents the instance actually uses.
52
+ It replaces exe.dev's `HarnessLoginCheck` and `harnessLoginChecks` options.
43
53
 
44
- ## Local Worker
54
+ ## Local worker
45
55
 
46
- The Node adapter at `@usine/executor/local` does the following for each Attempt:
56
+ For each attempt, `@usine/executor/local`:
47
57
 
48
- - checks Git, the installed run-host protocol, and the assigned harness before accepting work;
49
- - updates a per-repository bare cache, then creates a detached worktree at the resolved revision;
50
- - mints one repository credential and passes it only through the child process environment;
51
- - exchanges protocol-v4 envelopes with `@usine/run-host` over newline-delimited standard input and output, including immutable payload, agent context, child-session budget, and normalized session events;
52
- - interrupts cooperatively before a hard kill, then removes the worktree and wipes the credential.
58
+ - checks Git and the assigned harness before accepting work;
59
+ - updates a per-repo bare cache, then creates a detached worktree at the resolved revision;
60
+ - renews repo credentials before expiry. Git reads an attempt token file and `gh` its private `GH_CONFIG_DIR` on each call, so a salvage push after hours uses the latest credential; startup reconciliation issues a fresh one;
61
+ - runs the Pi parent here through `AttemptRuntime` (`@usine/durable`) with the attempt's environment: allowlisted variables, credential settings, and `TURBO_CACHE_DIR`, never the orchestrator's environment;
62
+ - harvests commits and pull requests from the worktree, and the attempt's comments and reviews on the item, with `@usine/core/step/harvest`, running `git` and `gh` in that environment. A failed or canceled attempt (unless this process stopped) gets only its comments harvested, after its parent task stops, through `Sink.comments`. `Sink.windows` reads the attempt windows after GitHub lists the comments, and the harvest keeps comments by the window rule of `@usine/core` (#429). If the windows can't be read, a finished attempt's harvest fails and a failed attempt records no comment;
63
+ - cancels the parent task before forced interruption, then removes the worktree and wipes the credential.
53
64
 
54
- `LocalExecutorOptions.runHost` can override the child command for another compatible host. The adapter otherwise launches the executable shipped by `@usine/run-host` and rejects a protocol-version mismatch during preflight.
65
+ `TURBO_CACHE_DIR` is `.usine/workers/<worker>/turbo`. Startup cache sweeps prune only the `*.git` directories the adapter creates under `repos/`, skip and log anything else at debug, and end with an info log of pruned, skipped, and failed counts.
55
66
 
56
- The adapter sets `TURBO_CACHE_DIR` to `.usine/workers/<worker>/turbo` for the run host. Startup cache sweeps prune only the `*.git` cache directories the adapter itself creates under `repos/`; every other entry is skipped and logged at debug level, and each sweep ends with an info log of pruned, skipped, and failed counts.
67
+ The local layer needs Effect `FileSystem`, `Path`, and `ChildProcessSpawner`, a `CredentialProvider`, and an `AttemptRuntime`.
57
68
 
58
- The local layer requires Effect `FileSystem`, `Path`, and `ChildProcessSpawner` services plus a `CredentialProvider`. Everything is 0.x, so APIs may change. Supply `effect` at the version pinned by the usine workspace.
69
+ ### Restarts
59
70
 
60
- ## exe.dev Worker
71
+ If the orchestrator stops during an attempt, the parent task stays live in durable storage and the worktree on disk. After a restart, `isLive` lets the engine reattach, and `prepare` with `reattach` takes over the worktree with fresh credentials, reading the run spec and base commit from `AttemptRuntime.request`. A parent that reported success before its harvest also counts as live, so the reattachment harvests it.
61
72
 
62
- `@usine/executor/exe-dev` runs the same contract on a persistent exe.dev VM. The configured Worker name must match the VM name. The VM image must already contain Git, Node.js 22 or later, the compatible `usine-run-host` executable, and every coding-agent login used by the instance.
73
+ The reattached launch resumes its runaway guard (#178) from `Sink.transcript`: each session's highest token report, finished children included; the parent's hold, if still placed; and the warnings already sent, which aren't resent. The guard checks the restored total at start, so a total past a lowered ceiling trips at once. A warning that the restored totals owe waits until `execute` relays the parent's channel.
63
74
 
64
- Preflight creates no checkouts or credential grants. It reads VM metadata through `ssh exe.dev ls --json`, checks Git and Node.js over SSH, compares the run-host protocol version, probes the supplied roster through the remote run host, and asks exe.dev to test each native GitHub integration.
75
+ Unsupervised time comes from `AttemptRuntime.unsupervised`. A wall-clock guard records it every 30 seconds while it grows and once when it stops, within a five-second grace. Only a running guard counts: quiet time before a restart counts, downtime doesn't. Each unclean stop loses at most the last 30 seconds, so a crash lets an attempt run that much past its ceiling.
65
76
 
66
- Prepare attaches the repository's integration to the VM with a bounded `--for` grant. It then fetches a bare cached clone from `github.int.exe.xyz` and creates a detached worktree for the Attempt. Launch keeps one SSH process open and exchanges protocol-v4 frames on that process's standard input and output. Cleanup removes the worktree, prunes its cache registration, and detaches the integration after the last concurrent Attempt using it finishes.
77
+ A reattachment reads only what its armed ceilings use: the transcript for either, the unsupervised time for the clock. If one can't be read after the store's retries, the guard doesn't start over with a new allowance. The durable session cancels the parent task, the launch salvages the worktree through `Sink.salvage`, and the attempt fails with an `InfraFailure` naming what couldn't be read, so the next attempt starts from the salvage.
67
78
 
68
- The first prepare on each VM after orchestrator startup sweeps abandoned Attempt worktrees and prunes cached worktree registrations left by a crash. Each sweep removal is isolated and logged: an immovable leftover never blocks its siblings or later prepares, and the sweep runs once per process regardless of partial failures. One-shot lobby and SSH commands carry a ten-minute timeout, and the SSH client sends keepalives so a dead link fails the operation instead of parking it. Cancellation also bounds interrupt persistence and delivery before escalating to a hard kill, so a parked store or remote process cannot stall shutdown; before removing a workspace, cleanup reads the run host's recorded pid on the VM and confirms the process is dead, because killing the local SSH client alone says nothing about the remote side.
79
+ After the harvest, the result is recorded in durable storage before the worktree is released, and `outcome` returns it, or the parent's failure, to an engine that stopped before recording the attempt. If storage has the parent's failure, `recover` first records the open attempt's comments, harvested in its worktree with a fresh credential, through the `RecoverySink`'s `comments`, filtered by the windows that its `windows` reads after GitHub lists them. A missing worktree or credential, or unreadable windows, is logged and records none.
69
80
 
70
- Concurrent Attempts against different repositories leave their integrations attached to the VM simultaneously; each grant stays time-boxed, and the refcounted release detaches an integration only after its last Attempt finishes. A failed detach is logged and left to lapse with its time-box. If remote termination cannot be confirmed, cleanup retains the checkout and its Git registration for recovery, logs its location, and still releases the credential grant. Partial preparation rolls back without requiring remote-process confirmation because no run host has launched.
81
+ Every other end, whether failure, cancel, or tripped ceiling, cancels the parent task. The engine calls `recover` only for attempts that no run reattaches, so `recover` cancels a live parent task first and reconciles the worktree even if `isLive` still reports the attempt, as for an unharvested report. If the stop can't be confirmed (liveness unreadable, or the cancel fails or outlasts the interrupt grace), `recover` and `cancel` fail with a `cancel` `ExecutorOperationError`, and `recover` leaves the worktree, which the task might still write.
82
+
83
+ ## exe.dev worker
84
+
85
+ `@usine/executor/exe-dev` runs the same contract on a persistent exe.dev VM whose name is the worker name. Its image must already have Git, the roster's `claude` and `codex` CLIs, and every coding-agent login the instance uses.
86
+
87
+ Preflight creates no checkouts or grants. It reads VM metadata with `ssh exe.dev ls --json`, checks Git and memory over SSH, probes the roster on the VM, and asks exe.dev to test each GitHub integration.
88
+
89
+ Prepare attaches the repo's integration to the VM with a bounded `--for` grant, fetches a bare cached clone from `github.int.exe.xyz`, and creates a detached worktree. Launch runs the Pi parent here through `AttemptRuntime` with a `RemoteShell` host: each Claude Code or Codex child is one SSH command that enters the worktree, exports `GH_HOST=github.int.exe.xyz` over the VM's environment, and runs the harness. Harvesting runs `git` and `gh` the same way. Cleanup removes the worktree, prunes its cache registration, and detaches the integration after the last concurrent attempt that uses it.
90
+
91
+ An exited SSH client doesn't prove its remote command stopped, so each remote child writes its pid to `<worktree>.child-<n>.pid`. Before a workspace is removed or kept, cleanup stops every recorded child on the VM and confirms it's dead; cancel, recovery, and reattach do the same.
92
+
93
+ After a restart, `prepare` with `reattach` checks that the stored request names this worktree and that it still exists on the VM, stops leftover children, and takes it over with a fresh grant. If the VM or worktree is gone, the reattachment fails and cancels the parent task, and the engine retries with a new attempt.
94
+
95
+ Startup reconciles unsuccessful attempts before the engine recovers their runs: each checkout is snapshotted and pushed, and its salvage recorded, before removal. Failed pushes and unknown crash leftovers stay on disk. Cache sweeps prune registrations of missing directories and never delete checkouts. One-shot lobby and SSH commands time out after ten minutes, and the SSH client sends keepalives, so a dead link fails the operation.
96
+
97
+ Concurrent attempts on different repos keep their integrations attached together. Each grant renews at half its lifetime while its attempt runs, a failed renewal retries every minute, and the refcounted release detaches an integration after its last attempt. The grant length, `credentialGrantMinutes` (65 by default), is a renewal window, unrelated to `max_attempt_minutes`; an attempt without a ceiling keeps its grant for its whole life. A failed detach is logged and lapses with its time-box. If stopping the remote children can't be confirmed, cleanup keeps the checkout and its Git registration, logs the location, and still releases the grant. Partial preparation rolls back without stopping children, since none started.
98
+
99
+ The exe.dev layer needs a `ChildProcessSpawner` and an `AttemptRuntime`.
71
100
 
72
101
  ```ts
73
102
  import { RepositoryName } from "@usine/core/repository"
@@ -85,4 +114,53 @@ const executorLayer = layer({
85
114
  })
86
115
  ```
87
116
 
88
- The native integration holds the GitHub credential at exe.dev's network edge. No GitHub token enters the RunSpec, the Attempt channel, the local SSH environment, or the VM. Git pushes from this Worker use `exe-dev-github-integration[bot]`; agent-authored PRs and comments should carry the Agent signature used by the run.
117
+ The integration keeps the GitHub credential at exe.dev's network edge: no GitHub token enters the run spec, the durable request, the local SSH environment, or the VM. Pushes from this worker use `exe-dev-github-integration[bot]`; agent-authored PRs and comments should carry the run's agent signature.
118
+
119
+ Salvage inspection, staging, or snapshot failures keep the checkout, uncommitted files included. Restart recovery uses fresh credentials and records the recovered ref before releasing the worktree.
120
+
121
+ ## SSH worker
122
+
123
+ `@usine/executor/ssh` runs the same contract on any Linux machine that the orchestrator reaches over SSH ([ADR 0026](../../docs/adr/0026-eu-ssh-worker.md)); `infra/hetzner` creates one on Hetzner Cloud. Each worker has an SSH destination, an optional identity file and port, and a root under which it keeps `workers/<name>/repos`, `attempts`, and `credentials`. The machine needs Git, gh, the roster's `claude` and `codex` CLIs, and their logins for the SSH user.
124
+
125
+ Preflight changes nothing. It checks the SSH connection, a writable root, Git, memory for `maxConcurrentAttempts`, and the roster's harnesses where children run.
126
+
127
+ Prepare mints the attempt's single-repo token through the `CredentialProvider`, writes it with a gh `hosts.yml` to `<root>/workers/<name>/credentials/<attempt>`, and fetches the bare cached clone from `https://github.com/<repository>.git` with git's credential helper reading that file. The token goes over the writing command's stdin, so it's in no command line, process environment, or log, and git and gh output is scrubbed of it. As on the local worker, git and harvest commands renew a token that expires within five minutes; children, harvesting, and the salvage push export the same file paths, so they read the latest token. Cleanup stops the children, removes the checkout, then the credential directory. A process's first use of a worker, for an attempt or startup reconciliation, removes credentials an earlier process left; a reattached attempt gets a fresh one.
128
+
129
+ Children, pid files, the kill script, sweeps, salvage, recovery, and reattachment share the exe.dev worker's code.
130
+
131
+ ### Behind a gateway
132
+
133
+ A worker whose address has a `gateway` is on a private network behind a gateway ([ADR 0027](../../docs/adr/0027-gateway-credential-proxy.md)), with no GitHub token on its machine. Prepare mints the same token, attaches it through the gateway's admin API for the worker and repo (`POST /v1/attachments` with the expiry), and keeps the attachment id and returned caller token. Renewal replaces the token on that attachment (`PUT /v1/attachments/<id>`) before expiry, and every two minutes the same request stores the attachment again, so a restarted gateway gets it back. Cleanup detaches after the checkout is cleaned up; a failed detach logs a warning, since the attachment expires with its token. Admin requests time out after 30 seconds. A process's first use of a worker detaches each attachment the gateway has for it, one by one, so a reattached attempt attaches again with a new caller token.
134
+
135
+ The cached clone fetches `http://git.<domain>/<repository>.git`. Children, harvesting, and the executor's git commands export settings that point git, gh, and package managers at the gateway: git sends the caller token only to `git.<domain>` and rewrites `https://github.com/` there, gh uses `git.<domain>` through the worker's socket with its config in `<root>/workers/<name>/gh`, and `HTTPS_PROXY` is the forward proxy.
136
+
137
+ If the gateway has the Anthropic credential, Claude Code calls `llm.<domain>/anthropic`, and preflight checks only its binary. Otherwise Claude Code, and Codex always, use the operator's login on the machine through the forward proxy: preflight checks the login as without a gateway, then that each of the login's hosts answers HTTP through the proxy (`api.anthropic.com` and `platform.claude.com`, or `chatgpt.com` and `auth.openai.com`). A refused host is a `gateway` capability error naming the host to add to the egress allowlist. The caller token is in no command line: each SSH command sends `GIT_CONFIG_VALUE_0`, `GH_ENTERPRISE_TOKEN`, `GH_TOKEN`, and `ANTHROPIC_AUTH_TOKEN` from the client's environment (`SendEnv`), and the machine's sshd must accept them (`AcceptEnv`).
138
+
139
+ The admin API listens on the gateway's loopback. With the first worker behind a gateway, the executor opens its own forward, `ssh -N -L 127.0.0.1:<port>:127.0.0.1:<adminPort> <destination>`, with the same SSH command and environment as its other clients, on a local port the kernel picks; workers behind that gateway share it. When the client exits, the executor starts another after a pause of one second, doubling up to one minute, and logs each open, exit, and reopen with the gateway's destination. An admin request that finds the forward closed, or gets no answer, reopens it at once and is sent once more. A second failure fails the request, with a message giving since when the forward has been closed, or, if it opens but the admin API doesn't answer, since when the API last answered through it. The forward closes when the last worker behind the gateway is removed, or with the layer.
140
+
141
+ Preflight also checks, without attaching, that the forward opens (giving the `ssh` command to run if it doesn't), that the admin API accepts the admin token, that sshd accepts the caller token variables, and that the machine reaches `git.<domain>`, which refuses requests without a caller token. A gap is a `gateway` `WorkerCapabilityError`.
142
+
143
+ If the worker's `llmProviders` lists `anthropic`, preflight also reads the gateway's Anthropic credential kind (`GET /v1/providers`) and records it with the worker. A gateway with none is a `gateway` capability error. For a Claude Code brain, each child reads the recorded kind as `AttemptRequest.gatewayCredential` when it starts (none for Codex and Pi), so its session records the credential, and usage counts a Claude Code child on the gateway's API key in the API keys pool ([ADR 0013](../../docs/adr/0013-cost-accounting-native-meters-guardrails.md)).
144
+
145
+ A gateway released before that endpoint answers 404: the worker's first such preflight logs `SSH Worker's Gateway does not report the kind of its Anthropic credential` at warning level, later ones at debug, and its Claude Code children count in the Claude plan pool. Each preflight asks again, so the first one after an upgrade records the kind. A gateway reload that changes the credential reaches the record at the worker's next preflight: at orchestrator start, or when a configuration reload adds the worker, gives it more slots, or adds a brain. A running attempt's next Claude Code child records the new kind; earlier children keep theirs. Local and exe.dev workers have no gateway, so their children record none.
146
+
147
+ The SSH layer needs a `ChildProcessSpawner`, a `CredentialProvider`, an `AttemptRuntime`, and an `HttpClient` for admin APIs. It provides `SshWorkers` beside the `Executor`, starting with the workers of `workers`. `addWorker(address)` adds a worker at runtime; behind a gateway, the worker shares that gateway's forward, or opens one if it's the first. `removeWorker(name)` removes a worker once no attempt runs on it, and closes its gateway's forward if no other worker uses it. Adding a worker the executor has with the same address changes nothing; with another address, it fails with a `machine` capability error until the worker is removed.
148
+
149
+ ```ts
150
+ import { SshDestination, SshIdentityFile, WorkerName, WorkerRoot } from "@usine/core/worker"
151
+ import { layer } from "@usine/executor/ssh"
152
+ import { Option } from "effect"
153
+
154
+ const executorLayer = layer({
155
+ workers: [
156
+ {
157
+ name: WorkerName.make("eu-one"),
158
+ destination: SshDestination.make("usine@203.0.113.7"),
159
+ identityFile: Option.some(SshIdentityFile.make("~/.ssh/usine_worker")),
160
+ port: Option.none(),
161
+ root: WorkerRoot.make("/home/usine/.usine"),
162
+ gateway: Option.none(),
163
+ },
164
+ ],
165
+ })
166
+ ```
@@ -6,16 +6,13 @@ export interface CommandResult {
6
6
  readonly stderr: string;
7
7
  readonly stdout: string;
8
8
  }
9
- /**
10
- * Returns only the named process variables, so the App private key and every
11
- * other orchestrator secret stay out of the child.
12
- */
9
+ /** Returns the variables in `names` that are set in the process environment. */
13
10
  export declare const allowlistedEnvironment: (names: ReadonlyArray<string>) => Effect.Effect<Record<string, string>>;
14
11
  /**
15
- * Runs one child process under the timeout and returns its whole output; the
16
- * caller decides what the exit code means.
12
+ * Runs `command` with a 10-minute timeout and returns its exit code and output without checking
13
+ * the exit code. Fails with `ExecutorOperationError` if the command can't run or times out.
17
14
  */
18
15
  export declare const collectCommand: (spawner: ChildProcessSpawner.ChildProcessSpawner["Service"], operation: ExecutorOperation, command: string, args: ReadonlyArray<string>, options: ChildProcess.CommandOptions) => Effect.Effect<CommandResult, ExecutorOperationError>;
19
- /** Returns the collected stdout, or logs and fails when the exit code is non-zero. */
16
+ /** Returns the collected stdout. If the exit code is non-zero, logs a warning and fails with `ExecutorOperationError`. */
20
17
  export declare const requireSuccess: (operation: "cancel" | "cleanup" | "launch" | "preflight" | "prepare", host: string, command: string, args: readonly string[], result: CommandResult) => Effect.Effect<string, ExecutorOperationError, never>;
21
18
  //# sourceMappingURL=child-command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"child-command.d.ts","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAyB,MAAM,QAAQ,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,UAC1B,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASpC,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,cAAc,YAChB,mBAAmB,CAAC,mBAAmB,CAAC,SAAS,CAAC,aAChD,iBAAiB,WACnB,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,WAClB,YAAY,CAAC,cAAc,KACnC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,sBAAsB,CA+BnD,CAAC;AAEJ,sFAAsF;AACtF,eAAO,MAAM,cAAc,+MAsBzB,CAAC"}
1
+ {"version":3,"file":"child-command.d.ts","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAyB,MAAM,QAAQ,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAID,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,UAC1B,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpC,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,cAAc,YAChB,mBAAmB,CAAC,mBAAmB,CAAC,SAAS,CAAC,aAChD,iBAAiB,WACnB,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,WAClB,YAAY,CAAC,cAAc,KACnC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,sBAAsB,CA+BnD,CAAC;AAEJ,0HAA0H;AAC1H,eAAO,MAAM,cAAc,+MAsBzB,CAAC"}
@@ -1,18 +1,12 @@
1
1
  import { Array as Arr, Config, Effect, Fiber, Option, Stream } from "effect";
2
2
  import { ChildProcess } from "effect/unstable/process";
3
3
  import { ExecutorOperationError } from "./executor.js";
4
- /** Bounds every adapter command so a hung clone or SSH hop stays cancellable. */
5
4
  const COMMAND_TIMEOUT = "10 minutes";
5
+ /** Returns the variables in `names` that are set in the process environment. */
6
+ export const allowlistedEnvironment = (names) => Effect.forEach(names, (name) => Effect.map(Config.option(Config.string(name)), (value) => Option.map(value, (found) => [name, found]))).pipe(Effect.map((entries) => Object.fromEntries(Arr.getSomes(entries))), Effect.orDie);
6
7
  /**
7
- * Returns only the named process variables, so the App private key and every
8
- * other orchestrator secret stay out of the child.
9
- */
10
- export const allowlistedEnvironment = (names) => Effect.forEach(names, (name) => Effect.map(Config.option(Config.string(name)), (value) => Option.map(value, (found) => [name, found]))).pipe(Effect.map((entries) => Object.fromEntries(Arr.getSomes(entries))),
11
- // An unreadable process environment at layer build is a defect, not a failure to handle.
12
- Effect.orDie);
13
- /**
14
- * Runs one child process under the timeout and returns its whole output; the
15
- * caller decides what the exit code means.
8
+ * Runs `command` with a 10-minute timeout and returns its exit code and output without checking
9
+ * the exit code. Fails with `ExecutorOperationError` if the command can't run or times out.
16
10
  */
17
11
  export const collectCommand = (spawner, operation, command, args, options) => Effect.scoped(Effect.gen(function* () {
18
12
  const handle = yield* spawner.spawn(ChildProcess.make(command, args, options));
@@ -26,10 +20,10 @@ export const collectCommand = (spawner, operation, command, args, options) => Ef
26
20
  return { exitCode: Number(exitCode), stderr, stdout };
27
21
  })).pipe(Effect.timeout(COMMAND_TIMEOUT), Effect.mapError((cause) => new ExecutorOperationError({
28
22
  operation,
29
- message: `${command} could not be executed`,
23
+ message: `Can't run ${command}`,
30
24
  cause,
31
25
  })));
32
- /** Returns the collected stdout, or logs and fails when the exit code is non-zero. */
26
+ /** Returns the collected stdout. If the exit code is non-zero, logs a warning and fails with `ExecutorOperationError`. */
33
27
  export const requireSuccess = Effect.fn("Executor.requireSuccess")(function* (operation, host, command, args, result) {
34
28
  if (result.exitCode !== 0) {
35
29
  yield* Effect.logWarning("Executor command exited non-zero", {
@@ -1 +1 @@
1
- {"version":3,"file":"child-command.js","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"child-command.js","sourceRoot":"","sources":["../src/child-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { ClaudeLoginLock } from "@usine/agents/runtime";
2
+ import { Layer } from "effect";
3
+ /**
4
+ * A `ClaudeLoginLock` with one mutex per Worker name. Each build creates new locks, so build it
5
+ * once per process and provide it to both the executor and the Attempt runtime.
6
+ */
7
+ export declare const claudeLoginLockLayer: Layer.Layer<ClaudeLoginLock>;
8
+ //# sourceMappingURL=claude-login-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-login-lock.d.ts","sourceRoot":"","sources":["../src/claude-login-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAG/B;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,KAAK,CAAC,eAAe,CAG7D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { ClaudeLoginLock } from "@usine/agents/runtime";
2
+ import { Layer } from "effect";
3
+ import { keyedLockFor } from "./keyed-lock.js";
4
+ /**
5
+ * A `ClaudeLoginLock` with one mutex per Worker name. Each build creates new locks, so build it
6
+ * once per process and provide it to both the executor and the Attempt runtime.
7
+ */
8
+ export const claudeLoginLockLayer = Layer.effect(ClaudeLoginLock, keyedLockFor());
9
+ //# sourceMappingURL=claude-login-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-login-lock.js","sourceRoot":"","sources":["../src/claude-login-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAiC,KAAK,CAAC,MAAM,CAC5E,eAAe,EACf,YAAY,EAAc,CAC3B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /** Returns the gh `hosts.yml` content that authenticates github.com with `token`. */
2
+ export declare const ghHostsConfig: (token: string) => string;
3
+ /** The gh `config.yml` content, required next to `hosts.yml`. */
4
+ export declare const GH_CONFIG = "version: \"1\"\n";
5
+ /**
6
+ * Returns the environment variables that point git and gh at the credential files in
7
+ * `directory`. git reads `directory/token` and gh reads `directory/hosts.yml` on each use, so
8
+ * renewing the token only requires rewriting those files.
9
+ */
10
+ export declare const credentialEnvironment: (directory: string) => {
11
+ GH_CONFIG_DIR: string;
12
+ GH_PROMPT_DISABLED: string;
13
+ USINE_GITHUB_TOKEN_FILE: string;
14
+ GIT_CONFIG_COUNT: string;
15
+ GIT_CONFIG_KEY_0: string;
16
+ GIT_CONFIG_VALUE_0: string;
17
+ GIT_CONFIG_KEY_1: string;
18
+ GIT_CONFIG_VALUE_1: string;
19
+ };
20
+ //# sourceMappingURL=credential-environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-environment.d.ts","sourceRoot":"","sources":["../src/credential-environment.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,eAAO,MAAM,aAAa,UAAW,MAAM,KAAG,MACyJ,CAAC;AAExM,iEAAiE;AACjE,eAAO,MAAM,SAAS,qBAAmB,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,cAAe,MAAM;;;;;;;;;CAWlB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /** Returns the gh `hosts.yml` content that authenticates github.com with `token`. */
2
+ export const ghHostsConfig = (token) => `github.com:\n user: x-access-token\n oauth_token: ${JSON.stringify(token)}\n git_protocol: https\n users:\n x-access-token:\n oauth_token: ${JSON.stringify(token)}\n`;
3
+ /** The gh `config.yml` content, required next to `hosts.yml`. */
4
+ export const GH_CONFIG = 'version: "1"\n';
5
+ /**
6
+ * Returns the environment variables that point git and gh at the credential files in
7
+ * `directory`. git reads `directory/token` and gh reads `directory/hosts.yml` on each use, so
8
+ * renewing the token only requires rewriting those files.
9
+ */
10
+ export const credentialEnvironment = (directory) => ({
11
+ GH_CONFIG_DIR: directory,
12
+ GH_PROMPT_DISABLED: "1",
13
+ USINE_GITHUB_TOKEN_FILE: `${directory}/token`,
14
+ GIT_CONFIG_COUNT: "2",
15
+ GIT_CONFIG_KEY_0: "credential.helper",
16
+ GIT_CONFIG_VALUE_0: "",
17
+ GIT_CONFIG_KEY_1: "credential.helper",
18
+ GIT_CONFIG_VALUE_1: '!f() { if [ "$1" = get ]; then echo username=x-access-token; printf "password="; cat "$USINE_GITHUB_TOKEN_FILE"; echo; fi; }; f',
19
+ });
20
+ //# sourceMappingURL=credential-environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-environment.js","sourceRoot":"","sources":["../src/credential-environment.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAa,EAAU,EAAE,CACrD,2DAA2D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,4FAA4F,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;AAExM,iEAAiE;AACjE,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,SAAiB,EAAE,EAAE,CACzD,CAAC;IACC,aAAa,EAAE,SAAS;IACxB,kBAAkB,EAAE,GAAG;IACvB,uBAAuB,EAAE,GAAG,SAAS,QAAQ;IAC7C,gBAAgB,EAAE,GAAG;IACrB,gBAAgB,EAAE,mBAAmB;IACrC,kBAAkB,EAAE,EAAE;IACtB,gBAAgB,EAAE,mBAAmB;IACrC,kBAAkB,EAChB,iIAAiI;CACpI,CAAkC,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { ChildHost } from "@usine/agents/child-host";
2
+ import * as AttemptChannel from "@usine/core/attempt/channel";
3
+ import { AgentFailure, type AttemptFailure, InfraFailure, RunawayFailure } from "@usine/core/attempt/failure";
4
+ import type { AttemptRecovery, RecoverySink } from "@usine/core/attempt/salvage";
5
+ import type { GatewayCredentialKind } from "@usine/core/worker";
6
+ import type { AttemptWindowsRead, HarvestSpec } from "@usine/core/step/harvest";
7
+ import type { Comment, StepResult, StepResultFact } from "@usine/core/step/result";
8
+ import { AttemptRuntime } from "@usine/durable/attempt-runtime";
9
+ import { Effect, Option, Result, type Scope } from "effect";
10
+ import { type AttemptWorkspace, type CheckoutRequest, type ExecutorError, ExecutorOperationError } from "./executor.ts";
11
+ import { type AcquiredAttempt } from "./prepared-attempt.ts";
12
+ export interface DurableCheckout {
13
+ /** The host with the checkout, where the children run. */
14
+ readonly host: ChildHost;
15
+ /** The complete environment for the children and harvesting commands (ADR 0011). */
16
+ readonly environment: Record<string, string>;
17
+ /**
18
+ * Returns the credential kind the Gateway injects for the children, from the Worker's latest
19
+ * preflight. Returns `None` if the children use their harness's own login.
20
+ */
21
+ readonly gatewayCredential: Effect.Effect<Option.Option<GatewayCredentialKind>>;
22
+ /**
23
+ * Returns the checkout's commits and pull requests, and the comments the Attempt posted on its
24
+ * Item within its own window and outside the windows of the Agent's other Attempts. Reads
25
+ * `windows` after GitHub lists the comments, and fails if they can't be read.
26
+ */
27
+ readonly harvest: (windows: AttemptWindowsRead<AttemptFailure>) => Effect.Effect<ReadonlyArray<StepResultFact>, AttemptFailure>;
28
+ /**
29
+ * Returns only the comments, filtered as in `harvest`, for an unsuccessful Attempt. Returns none
30
+ * if GitHub fails or the windows can't be read.
31
+ */
32
+ readonly harvestComments: (windows: AttemptWindowsRead<AttemptFailure>) => Effect.Effect<ReadonlyArray<Comment>>;
33
+ /** Keeps the checkout on disk so a restart can reattach the parent or repeat an unrecorded harvest. */
34
+ readonly keepForReattach: Effect.Effect<void>;
35
+ }
36
+ /**
37
+ * A function that returns the comments the Attempt described by `spec` posted on its Item, read
38
+ * from its checkout with a new credential and filtered by `windows`. Fails only if the
39
+ * credential can't be acquired.
40
+ */
41
+ export type PostedCommentsHarvest = <W>(spec: HarvestSpec, windows: AttemptWindowsRead<W>) => Effect.Effect<ReadonlyArray<Comment>, ExecutorError>;
42
+ export interface DurableSessionOptions {
43
+ /** The timeout for each cancellation stage: the parent task's abort, then the Attempt's end. */
44
+ readonly interruptGrace: Parameters<typeof Effect.timeoutOption>[1];
45
+ }
46
+ /**
47
+ * Returns the session that runs Attempts whose Pi parent is a durable task in this process
48
+ * (`AttemptRuntime`), for every Worker kind. If a launch is interrupted without a stop, the
49
+ * parent task stays live and the checkout stays on disk for reattachment after restart; every
50
+ * other ending stops the parent task.
51
+ */
52
+ export declare const durableSessionFor: (options: DurableSessionOptions) => Effect.Effect<{
53
+ readonly launch: (prepared: AttemptWorkspace, io: AttemptChannel.Sink, checkout: DurableCheckout) => Effect.Effect<StepResult, AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | RunawayFailure, never>;
54
+ readonly prepare: (acquire: Effect.Effect<AcquiredAttempt, ExecutorError, Scope.Scope>, request: CheckoutRequest) => Effect.Effect<import("./executor.ts").PreparedAttempt, import("./executor.ts").CredentialUnavailableError | ExecutorOperationError | import("./executor.ts").WorkerCapabilityError, Scope.Scope>;
55
+ readonly cancel: (attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<void, import("./executor.ts").CredentialUnavailableError | ExecutorOperationError | import("./executor.ts").WorkerCapabilityError, never>;
56
+ readonly intervene: (attemptId: string & import("effect/Brand").Brand<"AttemptId">, intervention: AttemptChannel.Intervention) => Effect.Effect<undefined, AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | RunawayFailure, never>;
57
+ readonly isLive: (attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<boolean, AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | RunawayFailure, never>;
58
+ readonly outcome: (attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<Option.Option<Result.Result<StepResult, AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | RunawayFailure>>, AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | RunawayFailure, never>;
59
+ readonly stopForRecovery: (attempt: AttemptRecovery) => Effect.Effect<void, ExecutorOperationError, never>;
60
+ readonly recoverPostedComments: <E>(attempt: AttemptRecovery, harvest: Option.Option<PostedCommentsHarvest>, record: Pick<RecoverySink<E>, "comments" | "windows">) => Effect.Effect<void, never, never>;
61
+ readonly storedRequest: (attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<import("@usine/durable/attempt-runtime").StoredRequest, ExecutorOperationError, never>;
62
+ }, never, AttemptRuntime>;
63
+ //# sourceMappingURL=durable-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-session.d.ts","sourceRoot":"","sources":["../src/durable-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EACL,YAAY,EACZ,KAAK,cAAc,EACnB,YAAY,EACZ,cAAc,EACf,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAGL,cAAc,EAEf,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAIL,MAAM,EAIN,MAAM,EAEN,MAAM,EACN,KAAK,KAAK,EACX,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,eAAe,EAAyC,MAAM,uBAAuB,CAAC;AAcpG,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAChF;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,kBAAkB,CAAC,cAAc,CAAC,KACxC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC,CAAC;IAClE;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,kBAAkB,CAAC,cAAc,CAAC,KACxC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,uGAAuG;IACvG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EACpC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;AAE1D,MAAM,WAAW,qBAAqB;IACpC,gGAAgG;IAChG,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CACrE;AASD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB;;gCA8bjB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,WAC1D,eAAe;;;;;;qCAmGiE,CAAC;;yBAmG5F,CAAC"}