@usine/executor 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +115 -37
  2. package/dist/child-command.d.ts +4 -7
  3. package/dist/child-command.d.ts.map +1 -1
  4. package/dist/child-command.js +6 -12
  5. package/dist/child-command.js.map +1 -1
  6. package/dist/claude-login-lock.d.ts +8 -0
  7. package/dist/claude-login-lock.d.ts.map +1 -0
  8. package/dist/claude-login-lock.js +9 -0
  9. package/dist/claude-login-lock.js.map +1 -0
  10. package/dist/credential-environment.d.ts +20 -0
  11. package/dist/credential-environment.d.ts.map +1 -0
  12. package/dist/credential-environment.js +20 -0
  13. package/dist/credential-environment.js.map +1 -0
  14. package/dist/durable-session.d.ts +63 -0
  15. package/dist/durable-session.d.ts.map +1 -0
  16. package/dist/durable-session.js +491 -0
  17. package/dist/durable-session.js.map +1 -0
  18. package/dist/exe-dev/commands.d.ts +23 -13
  19. package/dist/exe-dev/commands.d.ts.map +1 -1
  20. package/dist/exe-dev/commands.js +11 -57
  21. package/dist/exe-dev/commands.js.map +1 -1
  22. package/dist/exe-dev/exe-dev.d.ts +9 -3
  23. package/dist/exe-dev/exe-dev.d.ts.map +1 -1
  24. package/dist/exe-dev/exe-dev.js +53 -127
  25. package/dist/exe-dev/exe-dev.js.map +1 -1
  26. package/dist/exe-dev/grants.d.ts.map +1 -1
  27. package/dist/exe-dev/grants.js +35 -15
  28. package/dist/exe-dev/grants.js.map +1 -1
  29. package/dist/exe-dev/model.d.ts +15 -19
  30. package/dist/exe-dev/model.d.ts.map +1 -1
  31. package/dist/exe-dev/model.js +9 -12
  32. package/dist/exe-dev/model.js.map +1 -1
  33. package/dist/exe-dev/preflight.d.ts +8 -4
  34. package/dist/exe-dev/preflight.d.ts.map +1 -1
  35. package/dist/exe-dev/preflight.js +23 -33
  36. package/dist/exe-dev/preflight.js.map +1 -1
  37. package/dist/executor.d.ts +141 -38
  38. package/dist/executor.d.ts.map +1 -1
  39. package/dist/executor.js +90 -17
  40. package/dist/executor.js.map +1 -1
  41. package/dist/guard.d.ts +61 -0
  42. package/dist/guard.d.ts.map +1 -0
  43. package/dist/guard.js +288 -0
  44. package/dist/guard.js.map +1 -0
  45. package/dist/harness-preflight.d.ts +25 -4
  46. package/dist/harness-preflight.d.ts.map +1 -1
  47. package/dist/harness-preflight.js +25 -29
  48. package/dist/harness-preflight.js.map +1 -1
  49. package/dist/keyed-lock.d.ts +2 -3
  50. package/dist/keyed-lock.d.ts.map +1 -1
  51. package/dist/keyed-lock.js +2 -3
  52. package/dist/keyed-lock.js.map +1 -1
  53. package/dist/leftovers.d.ts +9 -0
  54. package/dist/leftovers.d.ts.map +1 -0
  55. package/dist/leftovers.js +14 -0
  56. package/dist/leftovers.js.map +1 -0
  57. package/dist/local/credentials.d.ts +21 -0
  58. package/dist/local/credentials.d.ts.map +1 -0
  59. package/dist/local/credentials.js +39 -0
  60. package/dist/local/credentials.js.map +1 -0
  61. package/dist/local/leftovers.d.ts +8 -0
  62. package/dist/local/leftovers.d.ts.map +1 -0
  63. package/dist/local/leftovers.js +23 -0
  64. package/dist/local/leftovers.js.map +1 -0
  65. package/dist/local/local.d.ts +16 -10
  66. package/dist/local/local.d.ts.map +1 -1
  67. package/dist/local/local.js +164 -129
  68. package/dist/local/local.js.map +1 -1
  69. package/dist/memory-preflight.d.ts +25 -0
  70. package/dist/memory-preflight.d.ts.map +1 -0
  71. package/dist/memory-preflight.js +83 -0
  72. package/dist/memory-preflight.js.map +1 -0
  73. package/dist/pre-push-hook.d.ts +20 -0
  74. package/dist/pre-push-hook.d.ts.map +1 -0
  75. package/dist/pre-push-hook.js +33 -0
  76. package/dist/pre-push-hook.js.map +1 -0
  77. package/dist/prepared-attempt.d.ts +33 -3
  78. package/dist/prepared-attempt.d.ts.map +1 -1
  79. package/dist/prepared-attempt.js +109 -39
  80. package/dist/prepared-attempt.js.map +1 -1
  81. package/dist/provision.d.ts +77 -14
  82. package/dist/provision.d.ts.map +1 -1
  83. package/dist/provision.js +357 -55
  84. package/dist/provision.js.map +1 -1
  85. package/dist/remote/commands.d.ts +51 -0
  86. package/dist/remote/commands.d.ts.map +1 -0
  87. package/dist/remote/commands.js +94 -0
  88. package/dist/remote/commands.js.map +1 -0
  89. package/dist/remote/leftovers.d.ts +14 -0
  90. package/dist/remote/leftovers.d.ts.map +1 -0
  91. package/dist/remote/leftovers.js +22 -0
  92. package/dist/remote/leftovers.js.map +1 -0
  93. package/dist/remote/liveness.d.ts +33 -0
  94. package/dist/remote/liveness.d.ts.map +1 -0
  95. package/dist/remote/liveness.js +194 -0
  96. package/dist/remote/liveness.js.map +1 -0
  97. package/dist/remote/machine.d.ts +64 -0
  98. package/dist/remote/machine.d.ts.map +1 -0
  99. package/dist/remote/machine.js +2 -0
  100. package/dist/remote/machine.js.map +1 -0
  101. package/dist/remote/remote.d.ts +24 -0
  102. package/dist/remote/remote.d.ts.map +1 -0
  103. package/dist/remote/remote.js +308 -0
  104. package/dist/remote/remote.js.map +1 -0
  105. package/dist/remote/sweep.d.ts +15 -0
  106. package/dist/remote/sweep.d.ts.map +1 -0
  107. package/dist/remote/sweep.js +116 -0
  108. package/dist/remote/sweep.js.map +1 -0
  109. package/dist/renewable-credential.d.ts +17 -0
  110. package/dist/renewable-credential.d.ts.map +1 -0
  111. package/dist/renewable-credential.js +58 -0
  112. package/dist/renewable-credential.js.map +1 -0
  113. package/dist/salvage.d.ts +30 -0
  114. package/dist/salvage.d.ts.map +1 -0
  115. package/dist/salvage.js +104 -0
  116. package/dist/salvage.js.map +1 -0
  117. package/dist/ssh/credentials.d.ts +40 -0
  118. package/dist/ssh/credentials.d.ts.map +1 -0
  119. package/dist/ssh/credentials.js +83 -0
  120. package/dist/ssh/credentials.js.map +1 -0
  121. package/dist/ssh/gateway/admin.d.ts +45 -0
  122. package/dist/ssh/gateway/admin.d.ts.map +1 -0
  123. package/dist/ssh/gateway/admin.js +129 -0
  124. package/dist/ssh/gateway/admin.js.map +1 -0
  125. package/dist/ssh/gateway/attachments.d.ts +22 -0
  126. package/dist/ssh/gateway/attachments.d.ts.map +1 -0
  127. package/dist/ssh/gateway/attachments.js +154 -0
  128. package/dist/ssh/gateway/attachments.js.map +1 -0
  129. package/dist/ssh/gateway/environment.d.ts +52 -0
  130. package/dist/ssh/gateway/environment.d.ts.map +1 -0
  131. package/dist/ssh/gateway/environment.js +78 -0
  132. package/dist/ssh/gateway/environment.js.map +1 -0
  133. package/dist/ssh/gateway/forward.d.ts +74 -0
  134. package/dist/ssh/gateway/forward.d.ts.map +1 -0
  135. package/dist/ssh/gateway/forward.js +247 -0
  136. package/dist/ssh/gateway/forward.js.map +1 -0
  137. package/dist/ssh/gateway/preflight.d.ts +28 -0
  138. package/dist/ssh/gateway/preflight.d.ts.map +1 -0
  139. package/dist/ssh/gateway/preflight.js +234 -0
  140. package/dist/ssh/gateway/preflight.js.map +1 -0
  141. package/dist/ssh/model.d.ts +65 -0
  142. package/dist/ssh/model.d.ts.map +1 -0
  143. package/dist/ssh/model.js +2 -0
  144. package/dist/ssh/model.js.map +1 -0
  145. package/dist/ssh/preflight.d.ts +16 -0
  146. package/dist/ssh/preflight.d.ts.map +1 -0
  147. package/dist/ssh/preflight.js +47 -0
  148. package/dist/ssh/preflight.js.map +1 -0
  149. package/dist/ssh/ssh.d.ts +20 -0
  150. package/dist/ssh/ssh.d.ts.map +1 -0
  151. package/dist/ssh/ssh.js +78 -0
  152. package/dist/ssh/ssh.js.map +1 -0
  153. package/dist/ssh/workers.d.ts +53 -0
  154. package/dist/ssh/workers.d.ts.map +1 -0
  155. package/dist/ssh/workers.js +173 -0
  156. package/dist/ssh/workers.js.map +1 -0
  157. package/package.json +12 -4
  158. package/dist/exe-dev/channel.d.ts +0 -11
  159. package/dist/exe-dev/channel.d.ts.map +0 -1
  160. package/dist/exe-dev/channel.js +0 -41
  161. package/dist/exe-dev/channel.js.map +0 -1
  162. package/dist/exe-dev/shell.d.ts +0 -12
  163. package/dist/exe-dev/shell.d.ts.map +0 -1
  164. package/dist/exe-dev/shell.js +0 -31
  165. package/dist/exe-dev/shell.js.map +0 -1
  166. package/dist/exe-dev/sweep.d.ts +0 -10
  167. package/dist/exe-dev/sweep.d.ts.map +0 -1
  168. package/dist/exe-dev/sweep.js +0 -117
  169. package/dist/exe-dev/sweep.js.map +0 -1
  170. package/dist/invalid-frame.d.ts +0 -17
  171. package/dist/invalid-frame.d.ts.map +0 -1
  172. package/dist/invalid-frame.js +0 -111
  173. package/dist/invalid-frame.js.map +0 -1
  174. package/dist/run-host-session.d.ts +0 -26
  175. package/dist/run-host-session.d.ts.map +0 -1
  176. package/dist/run-host-session.js +0 -241
  177. package/dist/run-host-session.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { type ExecutorOperationError, WorkerCapabilityError } from "./executor.ts";
3
+ /** Swap that the OS allocates on demand, as on macOS. */
4
+ export declare const SwapOnDemand: Schema.TaggedStruct<"SwapOnDemand", {}>;
5
+ declare const WorkerMemory_base: Schema.Class<WorkerMemory, Schema.Struct<{
6
+ readonly totalBytes: Schema.Int;
7
+ readonly swap: Schema.Union<readonly [Schema.TaggedStruct<"NoSwap", {}>, Schema.TaggedStruct<"SwapSpace", {
8
+ readonly bytes: Schema.Int;
9
+ }>, Schema.TaggedStruct<"SwapOnDemand", {}>]>;
10
+ }>, {}>;
11
+ export declare class WorkerMemory extends WorkerMemory_base {
12
+ }
13
+ /**
14
+ * Checks that `worker`'s machine can run `maxConcurrentAttempts` concurrent Attempts. Fails
15
+ * below 1.5 GiB per Attempt or for more than one Attempt without swap; logs a warning below
16
+ * 3 GiB per Attempt.
17
+ */
18
+ export declare const verifyWorkerMemory: (worker: string & import("effect/Brand").Brand<"WorkerName">, maxConcurrentAttempts: number, memory: WorkerMemory) => Effect.Effect<undefined, WorkerCapabilityError, never>;
19
+ /**
20
+ * Runs `verifyWorkerMemory` for a Linux machine whose `/proc/meminfo` is returned by `meminfo`.
21
+ * Fails if the output lacks MemTotal or SwapTotal.
22
+ */
23
+ export declare const verifyLinuxMemory: (worker: string & import("effect/Brand").Brand<"WorkerName">, maxConcurrentAttempts: number, meminfo: Effect.Effect<string, ExecutorOperationError, never>) => Effect.Effect<undefined, WorkerCapabilityError, never>;
24
+ export {};
25
+ //# sourceMappingURL=memory-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-preflight.d.ts","sourceRoot":"","sources":["../src/memory-preflight.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,MAAM,QAAQ,CAAC;AACvD,OAAO,EAAE,KAAK,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAgBnF,yDAAyD;AACzD,eAAO,MAAM,YAAY,yCAA0C,CAAC;;;;;;;AAKpE,qBAAa,YAAa,SAAQ,iBAGhC;CAAG;AAiCL;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,8KA8C7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,uNAkB5B,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { Effect, Match, Option, Schema } from "effect";
2
+ import { WorkerCapabilityError } from "./executor.js";
3
+ const GIB = 1024 ** 3;
4
+ // Two Attempts running the usine monorepo gates on a 4 GB Worker without swap made sshd
5
+ // unresponsive (#270).
6
+ const MEMORY_PER_ATTEMPT_WARNING = 3 * GIB;
7
+ const MEMORY_PER_ATTEMPT_MINIMUM = 1.5 * GIB;
8
+ const NoSwap = Schema.TaggedStruct("NoSwap", {});
9
+ /** A fixed swap size in bytes, as reported by Linux. */
10
+ const SwapSpace = Schema.TaggedStruct("SwapSpace", {
11
+ bytes: Schema.Int.check(Schema.isGreaterThan(0)),
12
+ });
13
+ /** Swap that the OS allocates on demand, as on macOS. */
14
+ export const SwapOnDemand = Schema.TaggedStruct("SwapOnDemand", {});
15
+ const WorkerSwap = Schema.Union([NoSwap, SwapSpace, SwapOnDemand]);
16
+ export class WorkerMemory extends Schema.Class("@usine/executor/WorkerMemory")({
17
+ totalBytes: Schema.Int.check(Schema.isGreaterThan(0)),
18
+ swap: WorkerSwap,
19
+ }) {
20
+ }
21
+ const decodeWorkerMemory = Schema.decodeUnknownOption(WorkerMemory);
22
+ const kibibytes = (meminfo, pattern) => Option.map(Option.fromNullishOr(pattern.exec(meminfo)?.[1]), Number);
23
+ /** Parses `/proc/meminfo`. Returns `None` if MemTotal or SwapTotal is missing. */
24
+ const memoryFromMeminfo = (meminfo) => Option.flatMap(Option.all({
25
+ total: kibibytes(meminfo, /^MemTotal:\s+(\d+) kB$/mu),
26
+ swap: kibibytes(meminfo, /^SwapTotal:\s+(\d+) kB$/mu),
27
+ }), ({ total, swap }) => decodeWorkerMemory({
28
+ totalBytes: total * 1024,
29
+ swap: swap === 0 ? NoSwap.make({}) : SwapSpace.make({ bytes: swap * 1024 }),
30
+ }));
31
+ const gib = (bytes) => `${(bytes / GIB).toFixed(1)} GiB`;
32
+ const swapText = (swap) => Match.valueTags(swap, {
33
+ NoSwap: () => "none",
34
+ SwapSpace: ({ bytes }) => gib(bytes),
35
+ SwapOnDemand: () => "on demand",
36
+ });
37
+ const memoryError = (worker, message, cause) => new WorkerCapabilityError({ worker, capability: "memory", message, cause });
38
+ /**
39
+ * Checks that `worker`'s machine can run `maxConcurrentAttempts` concurrent Attempts. Fails
40
+ * below 1.5 GiB per Attempt or for more than one Attempt without swap; logs a warning below
41
+ * 3 GiB per Attempt.
42
+ */
43
+ export const verifyWorkerMemory = Effect.fn("Executor.verifyWorkerMemory")(function* (worker, maxConcurrentAttempts, memory) {
44
+ const perAttempt = memory.totalBytes / maxConcurrentAttempts;
45
+ const fields = {
46
+ worker,
47
+ maxConcurrentAttempts,
48
+ memory: gib(memory.totalBytes),
49
+ swap: swapText(memory.swap),
50
+ perAttempt: gib(perAttempt),
51
+ };
52
+ yield* Effect.annotateCurrentSpan(fields);
53
+ const withoutSwap = memory.swap._tag === "NoSwap";
54
+ if (perAttempt < MEMORY_PER_ATTEMPT_MINIMUM) {
55
+ const fitting = Math.floor(memory.totalBytes / MEMORY_PER_ATTEMPT_MINIMUM);
56
+ const carried = withoutSwap ? Math.min(fitting, 1) : fitting;
57
+ const advice = carried === 0
58
+ ? "Add memory to the machine."
59
+ : `Set max_concurrent_attempts to ${carried} or less, or add memory to the machine.`;
60
+ return yield* memoryError(worker, `Worker "${worker}" has ${gib(memory.totalBytes)} of memory, which is ${gib(perAttempt)} per attempt with max_concurrent_attempts set to ${maxConcurrentAttempts}. An attempt needs at least ${gib(MEMORY_PER_ATTEMPT_MINIMUM)}. ${advice}`, memory);
61
+ }
62
+ if (withoutSwap && maxConcurrentAttempts > 1)
63
+ return yield* memoryError(worker, `Worker "${worker}" has no swap, and max_concurrent_attempts is ${maxConcurrentAttempts}. Concurrent attempts without swap can make the machine thrash until SSH stops responding. Add swap, or set max_concurrent_attempts to 1.`, memory);
64
+ if (perAttempt < MEMORY_PER_ATTEMPT_WARNING)
65
+ yield* Effect.logWarning(`Worker memory is below ${gib(MEMORY_PER_ATTEMPT_WARNING)} per concurrent Attempt`, {
66
+ ...fields,
67
+ suggestedMaxConcurrentAttempts: Math.max(1, Math.floor(memory.totalBytes / MEMORY_PER_ATTEMPT_WARNING)),
68
+ });
69
+ else
70
+ yield* Effect.logInfo("Worker memory verified", fields);
71
+ });
72
+ /**
73
+ * Runs `verifyWorkerMemory` for a Linux machine whose `/proc/meminfo` is returned by `meminfo`.
74
+ * Fails if the output lacks MemTotal or SwapTotal.
75
+ */
76
+ export const verifyLinuxMemory = Effect.fn("Executor.verifyLinuxMemory")(function* (worker, maxConcurrentAttempts, meminfo) {
77
+ const output = yield* meminfo.pipe(Effect.mapError((cause) => memoryError(worker, `Can't read /proc/meminfo on worker "${worker}"`, cause)));
78
+ const memory = memoryFromMeminfo(output);
79
+ if (Option.isNone(memory))
80
+ return yield* memoryError(worker, `/proc/meminfo on worker "${worker}" has no MemTotal or SwapTotal`, new Error(output.slice(0, 200)));
81
+ yield* verifyWorkerMemory(worker, maxConcurrentAttempts, memory.value);
82
+ });
83
+ //# sourceMappingURL=memory-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-preflight.js","sourceRoot":"","sources":["../src/memory-preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { LoopName } from "@usine/core/loop";
2
+ import { Option } from "effect";
3
+ /**
4
+ * The hooks directory inside the checkout's git directory for `permissions: read` Loops.
5
+ * `git worktree remove` deletes it with the checkout.
6
+ */
7
+ export declare const PRE_PUSH_HOOKS_DIRECTORY = "usine-hooks";
8
+ /**
9
+ * Returns a pre-push hook that rejects every push with `pushRefusal`, so the Agent sees the
10
+ * reason instead of GitHub's 403. `git push --no-verify` skips it; the `contents: read` token is
11
+ * what blocks the push (ADR 0011).
12
+ */
13
+ export declare const prePushHook: (loop: LoopName) => string;
14
+ /**
15
+ * Returns `environment` with `core.hooksPath` set to `hooks`, if any, after the entries counted
16
+ * in `GIT_CONFIG_COUNT`. Environment config overrides repository config, so the hook runs even
17
+ * if the repository sets `core.hooksPath`.
18
+ */
19
+ export declare const withGitHooks: (environment: Readonly<Record<string, string>>, hooks: Option.Option<string>) => Record<string, string>;
20
+ //# sourceMappingURL=pre-push-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-push-hook.d.ts","sourceRoot":"","sources":["../src/pre-push-hook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;GAGG;AACH,eAAO,MAAM,wBAAwB,gBAAgB,CAAC;AAQtD;;;;GAIG;AACH,eAAO,MAAM,WAAW,SAAU,QAAQ,KAAG,MACgB,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,YAAY,gBACV,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,SACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAYpB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Option } from "effect";
2
+ /**
3
+ * The hooks directory inside the checkout's git directory for `permissions: read` Loops.
4
+ * `git worktree remove` deletes it with the checkout.
5
+ */
6
+ export const PRE_PUSH_HOOKS_DIRECTORY = "usine-hooks";
7
+ const quote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
8
+ /** Returns the message printed when a push from a `permissions: read` checkout is rejected. */
9
+ const pushRefusal = (loop) => `usine refused this push: the Loop ${loop} has permissions: read, so its Attempts cannot push to the repository.`;
10
+ /**
11
+ * Returns a pre-push hook that rejects every push with `pushRefusal`, so the Agent sees the
12
+ * reason instead of GitHub's 403. `git push --no-verify` skips it; the `contents: read` token is
13
+ * what blocks the push (ADR 0011).
14
+ */
15
+ export const prePushHook = (loop) => `#!/bin/sh\necho ${quote(pushRefusal(loop))} >&2\nexit 1\n`;
16
+ /**
17
+ * Returns `environment` with `core.hooksPath` set to `hooks`, if any, after the entries counted
18
+ * in `GIT_CONFIG_COUNT`. Environment config overrides repository config, so the hook runs even
19
+ * if the repository sets `core.hooksPath`.
20
+ */
21
+ export const withGitHooks = (environment, hooks) => Option.match(hooks, {
22
+ onNone: () => ({ ...environment }),
23
+ onSome: (directory) => {
24
+ const index = Number(environment.GIT_CONFIG_COUNT ?? "0");
25
+ return {
26
+ ...environment,
27
+ GIT_CONFIG_COUNT: String(index + 1),
28
+ [`GIT_CONFIG_KEY_${index}`]: "core.hooksPath",
29
+ [`GIT_CONFIG_VALUE_${index}`]: directory,
30
+ };
31
+ },
32
+ });
33
+ //# sourceMappingURL=pre-push-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-push-hook.js","sourceRoot":"","sources":["../src/pre-push-hook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAEtD,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAE/E,+FAA+F;AAC/F,MAAM,WAAW,GAAG,CAAC,IAAc,EAAU,EAAE,CAC7C,qCAAqC,IAAI,wEAAwE,CAAC;AAEpH;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAU,EAAE,CACpD,mBAAmB,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,WAA6C,EAC7C,KAA4B,EACJ,EAAE,CAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;IAClB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,WAAW,EAAE,CAAC;IAClC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,gBAAgB,IAAI,GAAG,CAAC,CAAC;QAC1D,OAAO;YACL,GAAG,WAAW;YACd,gBAAgB,EAAE,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;YACnC,CAAC,kBAAkB,KAAK,EAAE,CAAC,EAAE,gBAAgB;YAC7C,CAAC,oBAAoB,KAAK,EAAE,CAAC,EAAE,SAAS;SACzC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -1,5 +1,35 @@
1
- import { Effect, Scope } from "effect";
1
+ import type { AttemptId } from "@usine/core/attempt";
2
+ import * as AttemptChannel from "@usine/core/attempt/channel";
3
+ import { type AttemptFailure, InfraFailure, type RunawayCeiling } from "@usine/core/attempt/failure";
4
+ import { type Duration, Effect, Scope } from "effect";
2
5
  import type { CheckoutRequest, ExecutorError, PreparedAttempt } from "./executor.ts";
3
- /** Owns partial preparation and the run host in one child scope, released at the Attempt's end. */
4
- export declare const prepareAttempt: (acquire: Effect.Effect<PreparedAttempt, ExecutorError, Scope.Scope>, request: CheckoutRequest) => Effect.Effect<PreparedAttempt, ExecutorError, Scope.Scope>;
6
+ import { type AttemptGuardSeed } from "./guard.ts";
7
+ import type { ProvisionedWorktree } from "./provision.ts";
8
+ export type AttemptStop = {
9
+ readonly _tag: "Cancel";
10
+ } | {
11
+ readonly _tag: "Runaway";
12
+ readonly ceiling: RunawayCeiling;
13
+ };
14
+ /** The result of an executor's `acquire`: the prepared Attempt and its salvage handles. */
15
+ export interface AcquiredAttempt extends PreparedAttempt {
16
+ readonly salvaged: ProvisionedWorktree["salvaged"];
17
+ readonly recordSalvage: ProvisionedWorktree["recordSalvage"];
18
+ }
19
+ /**
20
+ * Returns `prepare` and `cancel` for one executor's Attempts, tracked from preparation until
21
+ * release so `cancel` works at any stage. `cancel`, a failed `prepare`, and `launch` each return
22
+ * only after the Attempt's resources are released.
23
+ *
24
+ * - `stop`: stops the live session and returns whether one existed.
25
+ * - `registered`: completes when the launch registers the live session.
26
+ * - `warnParent`: sends the guard's warning steer; can wait for the parent's channel.
27
+ * - `guardSeed`: returns the guard's initial state. If it fails, the launch records salvage,
28
+ * releases the resources, and fails.
29
+ * - `recordUnsupervised`: persists the guard's unsupervised time for a later reattachment.
30
+ */
31
+ export declare const preparedAttemptsFor: (stop: (attemptId: AttemptId, reason: AttemptStop) => Effect.Effect<boolean, ExecutorError>, warnParent: (attemptId: AttemptId, intervention: AttemptChannel.Intervention) => Effect.Effect<void, AttemptFailure>, registered: (attemptId: AttemptId) => Effect.Effect<void>, guardSeed: (request: CheckoutRequest, io: AttemptChannel.Sink) => Effect.Effect<AttemptGuardSeed, InfraFailure>, recordUnsupervised: (attemptId: AttemptId, unsupervised: Duration.Duration) => Effect.Effect<void, AttemptFailure>) => Effect.Effect<{
32
+ prepare: (acquire: Effect.Effect<AcquiredAttempt, ExecutorError, Scope.Scope>, request: CheckoutRequest) => Effect.Effect<PreparedAttempt, ExecutorError, Scope.Scope>;
33
+ cancel: (attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<void, import("./executor.ts").CredentialUnavailableError | import("./executor.ts").ExecutorOperationError | import("./executor.ts").WorkerCapabilityError, never>;
34
+ }, never, never>;
5
35
  //# sourceMappingURL=prepared-attempt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prepared-attempt.d.ts","sourceRoot":"","sources":["../src/prepared-attempt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAoB,KAAK,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErF,mGAAmG;AACnG,eAAO,MAAM,cAAc,YAChB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,WAC1D,eAAe,KACvB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,CAqDzD,CAAC"}
1
+ {"version":3,"file":"prepared-attempt.d.ts","sourceRoot":"","sources":["../src/prepared-attempt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,cAAc,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EACnB,YAAY,EACZ,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,EAAoB,KAAK,EAAE,MAAM,QAAQ,CAAC;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,CAAC;AAEnE,2FAA2F;AAC3F,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CAC9D;AASD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,SACxB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,cAC9E,CACV,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,cAAc,CAAC,YAAY,KACtC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,cAC5B,CAAC,SAAS,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAC9C,CACT,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,cAAc,CAAC,IAAI,KACpB,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,YAAY,CAAC,sBAC9B,CAClB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,QAAQ,CAAC,QAAQ,KAC5B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC;uBAwB3B,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,WAC1D,eAAe,KACvB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC;;gBAoH7D,CAAC"}
@@ -1,40 +1,110 @@
1
- import { InfraFailure } from "@usine/core/attempt/failure";
2
- import { Effect, Exit, Fiber, Ref, Scope } from "effect";
3
- /** Owns partial preparation and the run host in one child scope, released at the Attempt's end. */
4
- export const prepareAttempt = (acquire, request) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
5
- const scope = yield* Scope.fork(yield* Effect.scope);
6
- // acquireRelease masks acquisition by default in Effect rc.111. Only the
7
- // ownership transfer is masked here; clone, fetch, and lock waits must stop
8
- // promptly. Each acquiring implementation registers its own finalizers.
9
- const prepared = yield* restore(acquire).pipe(Scope.provide(scope), Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause))));
10
- const unavailable = yield* Ref.make(false);
11
- yield* Scope.addFinalizer(scope, Ref.set(unavailable, true));
12
- return {
13
- workspace: prepared.workspace,
14
- launch: Effect.fn("PreparedAttempt.launch")(function* (io) {
15
- const attemptId = prepared.workspace.spec.attemptId;
16
- yield* Effect.annotateCurrentSpan({ attemptId, worker: prepared.workspace.worker });
17
- if (yield* Ref.getAndSet(unavailable, true)) {
18
- yield* Effect.logWarning("Attempt was already launched or released", { attemptId });
19
- return yield* new InfraFailure({
20
- message: `Attempt ${attemptId} was already launched or released`,
21
- cause: new Error("prepared Attempt is no longer available"),
22
- });
23
- }
24
- // Closing the owner's scope interrupts the session before checkout and
25
- // credential finalizers. Interrupting a caller waiting on join must also
26
- // stop that scoped fiber, so timeout/cancel completes the handshake first.
27
- return yield* Effect.acquireUseRelease(Effect.forkIn(prepared.launch(io).pipe(
28
- // End the timer with the live session. Checkout/grant finalizers run
29
- // afterward and cannot turn completed agent work into a retry.
30
- Effect.timeoutOrElse({
31
- duration: `${request.maxAttemptMinutes} minutes`,
32
- orElse: () => Effect.fail(new InfraFailure({
33
- message: `Attempt exceeded the ${request.maxAttemptMinutes}-minute budget`,
34
- cause: new Error("attempt timeout"),
35
- })),
36
- })), scope), Fiber.join, Fiber.interrupt).pipe(Effect.onExit((exit) => Scope.close(scope, exit)));
37
- }),
38
- };
39
- }));
1
+ import * as AttemptChannel from "@usine/core/attempt/channel";
2
+ import { InfraFailure, } from "@usine/core/attempt/failure";
3
+ import { Cause, Deferred, Effect, Exit, Fiber, Ref, Scope } from "effect";
4
+ import { attemptGuard } from "./guard.js";
5
+ /**
6
+ * Returns `prepare` and `cancel` for one executor's Attempts, tracked from preparation until
7
+ * release so `cancel` works at any stage. `cancel`, a failed `prepare`, and `launch` each return
8
+ * only after the Attempt's resources are released.
9
+ *
10
+ * - `stop`: stops the live session and returns whether one existed.
11
+ * - `registered`: completes when the launch registers the live session.
12
+ * - `warnParent`: sends the guard's warning steer; can wait for the parent's channel.
13
+ * - `guardSeed`: returns the guard's initial state. If it fails, the launch records salvage,
14
+ * releases the resources, and fails.
15
+ * - `recordUnsupervised`: persists the guard's unsupervised time for a later reattachment.
16
+ */
17
+ export const preparedAttemptsFor = (stop, warnParent, registered, guardSeed, recordUnsupervised) => Effect.sync(() => {
18
+ const attempts = new Map();
19
+ const cancel = Effect.fn("PreparedAttempt.cancel")(function* (attemptId) {
20
+ yield* Effect.annotateCurrentSpan({ attemptId });
21
+ const active = attempts.get(attemptId);
22
+ if (active === undefined) {
23
+ yield* Effect.logDebug("Attempt cancellation was a no-op", { attemptId });
24
+ return;
25
+ }
26
+ yield* Ref.set(active.unavailable, true);
27
+ // With a live session, stops it so its terminal envelope and typed errors are kept. Before
28
+ // registration, closing the scope interrupts preparation or process spawn.
29
+ if (!(yield* stop(attemptId, { _tag: "Cancel" }))) {
30
+ yield* Effect.logInfo("Canceling Attempt before its channel is ready", { attemptId });
31
+ yield* active.release(Exit.void);
32
+ }
33
+ else {
34
+ yield* active.released;
35
+ }
36
+ }, Effect.uninterruptible);
37
+ /** Runs preparation and the live session in one child scope that closes when the Attempt ends. */
38
+ const prepare = (acquire, request) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
39
+ const scope = yield* Scope.fork(yield* Effect.scope);
40
+ const unavailable = yield* Ref.make(false);
41
+ const released = yield* Deferred.make();
42
+ // Scope.close returns immediately when another fiber is already closing the scope, so the
43
+ // owner and a concurrent cancel both wait for `released`, set by the first-registered
44
+ // (last-run) finalizer.
45
+ const release = (exit) => Scope.close(scope, exit).pipe(Effect.andThen(Deferred.await(released)));
46
+ attempts.set(request.attemptId, {
47
+ unavailable,
48
+ release,
49
+ released: Deferred.await(released),
50
+ });
51
+ yield* Scope.addFinalizer(scope, Effect.suspend(() => {
52
+ attempts.delete(request.attemptId);
53
+ return Deferred.succeed(released, undefined);
54
+ }));
55
+ yield* Scope.addFinalizer(scope, Ref.set(unavailable, true));
56
+ // A separate child scope, so the acquiring fiber is interrupted before its resources are
57
+ // released.
58
+ const resources = yield* Scope.fork(scope);
59
+ const acquiring = yield* Effect.forkIn(Scope.provide(acquire, resources), scope);
60
+ const prepared = yield* restore(Fiber.join(acquiring)).pipe(Effect.onError((cause) => release(Exit.failCause(cause))));
61
+ /** Releases the Attempt's resources, then fails if its salvage couldn't be recorded. */
62
+ const releaseSalvaged = (exit) => release(exit).pipe(Effect.andThen(Effect.asVoid(prepared.salvaged)));
63
+ return {
64
+ workspace: prepared.workspace,
65
+ launch: Effect.fn("PreparedAttempt.launch")(function* (io) {
66
+ const attemptId = prepared.workspace.spec.attemptId;
67
+ yield* Effect.annotateCurrentSpan({ attemptId, worker: prepared.workspace.worker });
68
+ if (yield* Ref.getAndSet(unavailable, true)) {
69
+ yield* Effect.logWarning("Attempt was already launched or released", { attemptId });
70
+ return yield* new InfraFailure({
71
+ message: `Attempt ${attemptId} was already launched or released`,
72
+ cause: new Error("prepared attempt is no longer available"),
73
+ });
74
+ }
75
+ // If the seed can't be read, records salvage and releases the checkout without launching, so
76
+ // the Run's next Attempt starts from it. On interruption, such as process shutdown, the
77
+ // owner's scope handles the checkout.
78
+ const seed = yield* guardSeed(request, io).pipe(Effect.onExit((exit) => Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause)
79
+ ? Effect.void
80
+ : Effect.logWarning("The Attempt's runaway guard cannot start; releasing its checkout without launching it", { attemptId }).pipe(Effect.andThen(prepared.recordSalvage(io.salvage)), Effect.andThen(releaseSalvaged(exit)))));
81
+ const guard = yield* attemptGuard({
82
+ attemptId,
83
+ maxAttemptMinutes: request.maxAttemptMinutes,
84
+ maxAttemptTokens: request.maxAttemptTokens,
85
+ warn: (message) => warnParent(attemptId, new AttemptChannel.Intervention({
86
+ operator: "engine",
87
+ target: new AttemptChannel.ParentTarget(),
88
+ action: new AttemptChannel.Steer({ message }),
89
+ })),
90
+ recordUnsupervised: (unsupervised) => recordUnsupervised(attemptId, unsupervised),
91
+ }, io, seed);
92
+ // Starts the clock only after the launch registers its session. A remote launch first waits
93
+ // for the Worker's liveness watch, and a seed with no time left could trip before there's a
94
+ // session to stop.
95
+ const tripped = registered(attemptId).pipe(Effect.andThen(guard.tripped), Effect.tapError(({ ceiling }) => stop(attemptId, { _tag: "Runaway", ceiling }).pipe(Effect.tapError((error) => Effect.logWarning("Could not stop the runaway Attempt", {
96
+ attemptId,
97
+ ceiling,
98
+ errorTag: error._tag,
99
+ })), Effect.ignore)));
100
+ yield* prepared.recordSalvage(io.salvage);
101
+ // Closing the owner's scope interrupts the session before the checkout and credential
102
+ // finalizers. acquireUseRelease interrupts the forked fiber when the caller waiting on join
103
+ // is interrupted.
104
+ return yield* Effect.acquireUseRelease(Effect.forkIn(Effect.raceFirst(prepared.launch(guard.sink), tripped), scope), Fiber.join, Fiber.interrupt).pipe(Effect.onExit((exit) => releaseSalvaged(exit)));
105
+ }),
106
+ };
107
+ }));
108
+ return { prepare, cancel };
109
+ });
40
110
  //# sourceMappingURL=prepared-attempt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prepared-attempt.js","sourceRoot":"","sources":["../src/prepared-attempt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prepared-attempt.js","sourceRoot":"","sources":["../src/prepared-attempt.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
+ import type { AttemptFailure } from "@usine/core/attempt/failure";
2
+ import * as AttemptChannel from "@usine/core/attempt/channel";
3
+ import { type AttemptRecovery, type AttemptSalvage, SalvagePushed } from "@usine/core/attempt/salvage";
1
4
  import { RepositoryName } from "@usine/core/repository";
2
- import { Effect } from "effect";
5
+ import { Effect, Option } from "effect";
3
6
  import { ExecutorOperationError, AttemptWorkspace, type CheckoutRequest } from "./executor.ts";
4
- export declare const safeSegment: (value: string) => string;
5
- /** The bare cache directory name for a repository under `repos/`, shared by every adapter. */
7
+ /** Returns the bare cache directory name for `repository` under `repos/`. */
6
8
  export declare const cacheDirectoryName: (repository: RepositoryName) => string;
7
9
  /**
8
- * Whether a `repos/` entry is one of our bare caches, decided by the name we
9
- * mint rather than by probing contents: a `HEAD` lookup fails with ENOTDIR on
10
- * a stray file, and a `.cloning` leftover or a tool's scratch directory must
11
- * never reach `git worktree prune`.
10
+ * Returns whether a `repos/` entry is a bare cache, by name only. Probing `HEAD` fails with
11
+ * ENOTDIR on a stray file, and a `.cloning` leftover or another tool's directory must never be
12
+ * passed to `git worktree prune`.
12
13
  */
13
14
  export declare const isCacheDirectoryName: (entry: string) => boolean;
14
15
  interface WorkspaceLayout {
@@ -16,14 +17,16 @@ interface WorkspaceLayout {
16
17
  readonly staging: string;
17
18
  readonly worktree: string;
18
19
  }
19
- /** The adapter-specific primitives; everything else about provisioning is shared. */
20
+ /** Executor-specific operations for the shared provisioning code. */
20
21
  export interface WorkspaceOps<E> {
21
22
  readonly ensureRoots: Effect.Effect<void, E>;
22
23
  readonly removePath: (operation: "prepare" | "cleanup", target: string) => Effect.Effect<void, E>;
23
24
  readonly cacheExists: Effect.Effect<boolean, E>;
24
- /** Atomically promote the finished staging clone to the cache path. */
25
+ /** Atomically moves the finished staging clone to the cache path. */
25
26
  readonly promoteStaging: Effect.Effect<void, E>;
26
27
  readonly git: (operation: "prepare" | "cleanup", args: ReadonlyArray<string>) => Effect.Effect<string, E>;
28
+ /** Writes `content` to `path` on the Worker's machine, creating its directory, and makes it executable. */
29
+ readonly writeExecutable: (path: string, content: string) => Effect.Effect<void, E>;
27
30
  }
28
31
  export interface ProvisionOptions<E> {
29
32
  readonly request: CheckoutRequest;
@@ -32,12 +35,72 @@ export interface ProvisionOptions<E> {
32
35
  readonly layout: WorkspaceLayout;
33
36
  readonly ops: WorkspaceOps<E>;
34
37
  }
38
+ export interface ProvisionedWorktree {
39
+ readonly workspace: AttemptWorkspace;
40
+ /** Waits for the workspace scope to close and returns the salvage, or `None` after a successful Attempt or a clean worktree. */
41
+ readonly salvaged: Effect.Effect<Option.Option<AttemptSalvage>, AttemptFailure>;
42
+ /** Sets the salvage sink; call before launch. If salvage isn't recorded, cleanup keeps the worktree on disk. */
43
+ readonly recordSalvage: (record: AttemptChannel.Sink["salvage"]) => Effect.Effect<void>;
44
+ /**
45
+ * Skips checkout removal when the scope closes, so a restart can reattach the parent task or
46
+ * repeat a harvest whose result was not stored.
47
+ */
48
+ readonly keepForReattach: Effect.Effect<void>;
49
+ /**
50
+ * The hooks directory for the children's git (`withGitHooks`), set only for a
51
+ * `permissions: read` Loop. If `None`, the repository's own hooks apply.
52
+ */
53
+ readonly gitHooks: Option.Option<string>;
54
+ }
55
+ interface ReleaseOptions<E> {
56
+ readonly request: CheckoutRequest;
57
+ readonly host: string;
58
+ readonly layout: Pick<WorkspaceLayout, "cache" | "worktree">;
59
+ readonly ops: Pick<WorkspaceOps<E>, "cacheExists" | "git" | "removePath">;
60
+ }
61
+ /**
62
+ * Clones into a staging directory and promotes it on success, fetches the requested revision,
63
+ * and checks out a detached worktree at the resolved commit, applying the previous Attempt's
64
+ * salvage if the request resumes one. Registers the release finalizer before the first
65
+ * filesystem change.
66
+ */
67
+ export declare const provisionWorktree: <E extends Error>(options: ProvisionOptions<E>) => Effect.Effect<{
68
+ workspace: AttemptWorkspace;
69
+ salvaged: Effect.Effect<Option.Option<SalvagePushed | import("@usine/core/attempt/salvage").SalvageRetained>, import("@usine/core/attempt/failure").AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | import("@usine/core/attempt/failure").InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | import("@usine/core/attempt/failure").RunawayFailure, never>;
70
+ recordSalvage: (record: AttemptChannel.Sink["salvage"]) => Effect.Effect<void, never, never>;
71
+ keepForReattach: Effect.Effect<void, never, never>;
72
+ gitHooks: Option.Option<string>;
73
+ }, E | ExecutorOperationError, import("effect/Scope").Scope>;
74
+ export interface ReattachOptions<E> extends ReleaseOptions<E> {
75
+ readonly ops: Pick<WorkspaceOps<E>, "cacheExists" | "git" | "removePath" | "writeExecutable">;
76
+ /** The RunSpec from the parent's durable request. */
77
+ readonly spec: AttemptChannel.RunSpec;
78
+ }
79
+ /**
80
+ * Reuses an Attempt's checkout after an orchestrator restart without cloning, fetching, or
81
+ * checking out. The same release as a new checkout salvages and removes it when the Attempt
82
+ * ends.
83
+ */
84
+ export declare const reattachWorktree: <E extends Error>(options: ReattachOptions<E>) => Effect.Effect<{
85
+ workspace: AttemptWorkspace;
86
+ salvaged: Effect.Effect<Option.Option<SalvagePushed | import("@usine/core/attempt/salvage").SalvageRetained>, import("@usine/core/attempt/failure").AgentFailure | import("@usine/core/attempt/failure").BlockedFailure | import("@usine/core/attempt/failure").ChildSessionBudgetFailure | import("@usine/core/attempt/failure").DefectFailure | import("@usine/core/attempt/failure").InfraFailure | import("@usine/core/attempt/failure").QuestionFailure | import("@usine/core/attempt/failure").RunawayFailure, never>;
87
+ recordSalvage: (record: AttemptChannel.Sink["salvage"]) => Effect.Effect<void, never, never>;
88
+ keepForReattach: Effect.Effect<void, never, never>;
89
+ gitHooks: Option.Option<string>;
90
+ }, E, import("effect/Scope").Scope>;
91
+ export interface RecoveryOptions<E> {
92
+ readonly attempt: AttemptRecovery;
93
+ readonly host: string;
94
+ readonly worktree: string;
95
+ readonly cache: string;
96
+ /** Runs git with the Worker's repository credentials. */
97
+ readonly git: (args: ReadonlyArray<string>) => Effect.Effect<string, E>;
98
+ }
35
99
  /**
36
- * The clone-to-worktree sequence every Executor shares: clone into a staging
37
- * sibling and promote it on success, so an interrupted clone never leaves a
38
- * half-initialized cache that later fetches would poison; then fetch the
39
- * requested revision and check out a detached worktree at the resolved commit.
100
+ * Reconciles a checkout left by an earlier process: salvages it without a known base, records
101
+ * the result, and removes the worktree only after its work is pushed. Checkouts of succeeded
102
+ * Attempts and of `permissions: read` Loops are removed without salvage.
40
103
  */
41
- export declare const provisionWorktree: <E>(options: ProvisionOptions<E>) => Effect.Effect<AttemptWorkspace, E | ExecutorOperationError, import("effect/Scope").Scope>;
104
+ export declare const recoverWorktree: <E extends Error, RecordError>(options: RecoveryOptions<E>, record: (salvage: AttemptSalvage) => Effect.Effect<void, RecordError>) => Effect.Effect<void, E | RecordError, never>;
42
105
  export {};
43
106
  //# sourceMappingURL=provision.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../src/provision.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAa,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAEhB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAAoD,CAAC;AAIjG,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,eAAgB,cAAc,KAAG,MACT,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,UAAW,MAAM,KAAG,OACmC,CAAC;AAIzF,UAAU,eAAe;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAClG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAChD,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,GAAG,EAAE,CACZ,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;CAC/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAsD,CAAC,4HAwJnF,CAAC"}
1
+ {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../src/provision.ts"],"names":[],"mappings":"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"}