@usine/executor 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -21
- package/README.md +212 -37
- package/dist/child-command.d.ts +5 -8
- package/dist/child-command.d.ts.map +1 -1
- package/dist/child-command.js +10 -14
- package/dist/child-command.js.map +1 -1
- package/dist/child-environment.d.ts +19 -0
- package/dist/child-environment.d.ts.map +1 -0
- package/dist/child-environment.js +18 -0
- package/dist/child-environment.js.map +1 -0
- package/dist/claude-login-lock.d.ts +9 -0
- package/dist/claude-login-lock.d.ts.map +1 -0
- package/dist/claude-login-lock.js +15 -0
- package/dist/claude-login-lock.js.map +1 -0
- package/dist/cloudflare/client.d.ts +50 -0
- package/dist/cloudflare/client.d.ts.map +1 -0
- package/dist/cloudflare/client.js +108 -0
- package/dist/cloudflare/client.js.map +1 -0
- package/dist/cloudflare/cloudflare.d.ts +17 -0
- package/dist/cloudflare/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare/cloudflare.js +704 -0
- package/dist/cloudflare/cloudflare.js.map +1 -0
- package/dist/cloudflare/model.d.ts +42 -0
- package/dist/cloudflare/model.d.ts.map +1 -0
- package/dist/cloudflare/model.js +2 -0
- package/dist/cloudflare/model.js.map +1 -0
- package/dist/cloudflare/shell/environment.d.ts +12 -0
- package/dist/cloudflare/shell/environment.d.ts.map +1 -0
- package/dist/cloudflare/shell/environment.js +12 -0
- package/dist/cloudflare/shell/environment.js.map +1 -0
- package/dist/cloudflare/shell/shell.d.ts +2 -0
- package/dist/cloudflare/shell/shell.d.ts.map +1 -0
- package/dist/cloudflare/shell/shell.js +155 -0
- package/dist/cloudflare/shell/shell.js.map +1 -0
- package/dist/credential-environment.d.ts +20 -0
- package/dist/credential-environment.d.ts.map +1 -0
- package/dist/credential-environment.js +20 -0
- package/dist/credential-environment.js.map +1 -0
- package/dist/durable-session.d.ts +81 -0
- package/dist/durable-session.d.ts.map +1 -0
- package/dist/durable-session.js +550 -0
- package/dist/durable-session.js.map +1 -0
- package/dist/exe-dev/commands.d.ts +23 -13
- package/dist/exe-dev/commands.d.ts.map +1 -1
- package/dist/exe-dev/commands.js +11 -57
- package/dist/exe-dev/commands.js.map +1 -1
- package/dist/exe-dev/exe-dev.d.ts +9 -3
- package/dist/exe-dev/exe-dev.d.ts.map +1 -1
- package/dist/exe-dev/exe-dev.js +61 -128
- package/dist/exe-dev/exe-dev.js.map +1 -1
- package/dist/exe-dev/grants.d.ts.map +1 -1
- package/dist/exe-dev/grants.js +35 -15
- package/dist/exe-dev/grants.js.map +1 -1
- package/dist/exe-dev/model.d.ts +15 -19
- package/dist/exe-dev/model.d.ts.map +1 -1
- package/dist/exe-dev/model.js +9 -12
- package/dist/exe-dev/model.js.map +1 -1
- package/dist/exe-dev/preflight.d.ts +8 -4
- package/dist/exe-dev/preflight.d.ts.map +1 -1
- package/dist/exe-dev/preflight.js +26 -35
- package/dist/exe-dev/preflight.js.map +1 -1
- package/dist/executor.d.ts +292 -44
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +187 -19
- package/dist/executor.js.map +1 -1
- package/dist/guard.d.ts +57 -7
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +274 -59
- package/dist/guard.js.map +1 -1
- package/dist/harness-preflight.d.ts +27 -5
- package/dist/harness-preflight.d.ts.map +1 -1
- package/dist/harness-preflight.js +32 -31
- package/dist/harness-preflight.js.map +1 -1
- package/dist/keyed-lock.d.ts +2 -3
- package/dist/keyed-lock.d.ts.map +1 -1
- package/dist/keyed-lock.js +2 -3
- package/dist/keyed-lock.js.map +1 -1
- package/dist/leftovers.d.ts +9 -0
- package/dist/leftovers.d.ts.map +1 -0
- package/dist/leftovers.js +14 -0
- package/dist/leftovers.js.map +1 -0
- package/dist/local/credentials.d.ts +21 -0
- package/dist/local/credentials.d.ts.map +1 -0
- package/dist/local/credentials.js +39 -0
- package/dist/local/credentials.js.map +1 -0
- package/dist/local/leftovers.d.ts +8 -0
- package/dist/local/leftovers.d.ts.map +1 -0
- package/dist/local/leftovers.js +23 -0
- package/dist/local/leftovers.js.map +1 -0
- package/dist/local/local.d.ts +17 -11
- package/dist/local/local.d.ts.map +1 -1
- package/dist/local/local.js +205 -132
- package/dist/local/local.js.map +1 -1
- package/dist/memory-preflight.d.ts +27 -0
- package/dist/memory-preflight.d.ts.map +1 -0
- package/dist/memory-preflight.js +83 -0
- package/dist/memory-preflight.js.map +1 -0
- package/dist/pre-push-hook.d.ts +20 -0
- package/dist/pre-push-hook.d.ts.map +1 -0
- package/dist/pre-push-hook.js +33 -0
- package/dist/pre-push-hook.js.map +1 -0
- package/dist/prepared-attempt.d.ts +41 -3
- package/dist/prepared-attempt.d.ts.map +1 -1
- package/dist/prepared-attempt.js +109 -31
- package/dist/prepared-attempt.js.map +1 -1
- package/dist/provision.d.ts +129 -15
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +428 -69
- package/dist/provision.js.map +1 -1
- package/dist/remote/commands.d.ts +51 -0
- package/dist/remote/commands.d.ts.map +1 -0
- package/dist/remote/commands.js +94 -0
- package/dist/remote/commands.js.map +1 -0
- package/dist/remote/container.d.ts +90 -0
- package/dist/remote/container.d.ts.map +1 -0
- package/dist/remote/container.js +480 -0
- package/dist/remote/container.js.map +1 -0
- package/dist/remote/leftovers.d.ts +14 -0
- package/dist/remote/leftovers.d.ts.map +1 -0
- package/dist/remote/leftovers.js +22 -0
- package/dist/remote/leftovers.js.map +1 -0
- package/dist/remote/liveness.d.ts +33 -0
- package/dist/remote/liveness.d.ts.map +1 -0
- package/dist/remote/liveness.js +196 -0
- package/dist/remote/liveness.js.map +1 -0
- package/dist/remote/machine.d.ts +110 -0
- package/dist/remote/machine.d.ts.map +1 -0
- package/dist/remote/machine.js +2 -0
- package/dist/remote/machine.js.map +1 -0
- package/dist/remote/remote.d.ts +28 -0
- package/dist/remote/remote.d.ts.map +1 -0
- package/dist/remote/remote.js +556 -0
- package/dist/remote/remote.js.map +1 -0
- package/dist/remote/sweep.d.ts +17 -0
- package/dist/remote/sweep.d.ts.map +1 -0
- package/dist/remote/sweep.js +117 -0
- package/dist/remote/sweep.js.map +1 -0
- package/dist/renewable-credential.d.ts +17 -0
- package/dist/renewable-credential.d.ts.map +1 -0
- package/dist/renewable-credential.js +58 -0
- package/dist/renewable-credential.js.map +1 -0
- package/dist/salvage.d.ts +38 -0
- package/dist/salvage.d.ts.map +1 -0
- package/dist/salvage.js +198 -0
- package/dist/salvage.js.map +1 -0
- package/dist/ssh/api-keys.d.ts +36 -0
- package/dist/ssh/api-keys.d.ts.map +1 -0
- package/dist/ssh/api-keys.js +72 -0
- package/dist/ssh/api-keys.js.map +1 -0
- package/dist/ssh/codex-homes.d.ts +21 -0
- package/dist/ssh/codex-homes.d.ts.map +1 -0
- package/dist/ssh/codex-homes.js +78 -0
- package/dist/ssh/codex-homes.js.map +1 -0
- package/dist/ssh/container.d.ts +32 -0
- package/dist/ssh/container.d.ts.map +1 -0
- package/dist/ssh/container.js +282 -0
- package/dist/ssh/container.js.map +1 -0
- package/dist/ssh/credentials.d.ts +43 -0
- package/dist/ssh/credentials.d.ts.map +1 -0
- package/dist/ssh/credentials.js +86 -0
- package/dist/ssh/credentials.js.map +1 -0
- package/dist/ssh/gateway/admin.d.ts +45 -0
- package/dist/ssh/gateway/admin.d.ts.map +1 -0
- package/dist/ssh/gateway/admin.js +129 -0
- package/dist/ssh/gateway/admin.js.map +1 -0
- package/dist/ssh/gateway/attachments.d.ts +31 -0
- package/dist/ssh/gateway/attachments.d.ts.map +1 -0
- package/dist/ssh/gateway/attachments.js +168 -0
- package/dist/ssh/gateway/attachments.js.map +1 -0
- package/dist/ssh/gateway/environment.d.ts +71 -0
- package/dist/ssh/gateway/environment.d.ts.map +1 -0
- package/dist/ssh/gateway/environment.js +148 -0
- package/dist/ssh/gateway/environment.js.map +1 -0
- package/dist/ssh/gateway/forward.d.ts +74 -0
- package/dist/ssh/gateway/forward.d.ts.map +1 -0
- package/dist/ssh/gateway/forward.js +247 -0
- package/dist/ssh/gateway/forward.js.map +1 -0
- package/dist/ssh/gateway/preflight.d.ts +67 -0
- package/dist/ssh/gateway/preflight.d.ts.map +1 -0
- package/dist/ssh/gateway/preflight.js +320 -0
- package/dist/ssh/gateway/preflight.js.map +1 -0
- package/dist/ssh/limits.d.ts +60 -0
- package/dist/ssh/limits.d.ts.map +1 -0
- package/dist/ssh/limits.js +84 -0
- package/dist/ssh/limits.js.map +1 -0
- package/dist/ssh/model.d.ts +153 -0
- package/dist/ssh/model.d.ts.map +1 -0
- package/dist/ssh/model.js +2 -0
- package/dist/ssh/model.js.map +1 -0
- package/dist/ssh/preflight.d.ts +35 -0
- package/dist/ssh/preflight.d.ts.map +1 -0
- package/dist/ssh/preflight.js +87 -0
- package/dist/ssh/preflight.js.map +1 -0
- package/dist/ssh/provisioner/boat.d.ts +33 -0
- package/dist/ssh/provisioner/boat.d.ts.map +1 -0
- package/dist/ssh/provisioner/boat.js +247 -0
- package/dist/ssh/provisioner/boat.js.map +1 -0
- package/dist/ssh/provisioner/hetzner.d.ts +32 -0
- package/dist/ssh/provisioner/hetzner.d.ts.map +1 -0
- package/dist/ssh/provisioner/hetzner.js +337 -0
- package/dist/ssh/provisioner/hetzner.js.map +1 -0
- package/dist/ssh/provisioner/host-key.d.ts +11 -0
- package/dist/ssh/provisioner/host-key.d.ts.map +1 -0
- package/dist/ssh/provisioner/host-key.js +57 -0
- package/dist/ssh/provisioner/host-key.js.map +1 -0
- package/dist/ssh/provisioner/http.d.ts +45 -0
- package/dist/ssh/provisioner/http.d.ts.map +1 -0
- package/dist/ssh/provisioner/http.js +72 -0
- package/dist/ssh/provisioner/http.js.map +1 -0
- package/dist/ssh/provisioner/machines.d.ts +48 -0
- package/dist/ssh/provisioner/machines.d.ts.map +1 -0
- package/dist/ssh/provisioner/machines.js +686 -0
- package/dist/ssh/provisioner/machines.js.map +1 -0
- package/dist/ssh/provisioner/model.d.ts +92 -0
- package/dist/ssh/provisioner/model.d.ts.map +1 -0
- package/dist/ssh/provisioner/model.js +37 -0
- package/dist/ssh/provisioner/model.js.map +1 -0
- package/dist/ssh/ssh.d.ts +23 -0
- package/dist/ssh/ssh.d.ts.map +1 -0
- package/dist/ssh/ssh.js +145 -0
- package/dist/ssh/ssh.js.map +1 -0
- package/dist/ssh/worker-records.d.ts +18 -0
- package/dist/ssh/worker-records.d.ts.map +1 -0
- package/dist/ssh/worker-records.js +100 -0
- package/dist/ssh/worker-records.js.map +1 -0
- package/dist/ssh/workers.d.ts +80 -0
- package/dist/ssh/workers.d.ts.map +1 -0
- package/dist/ssh/workers.js +247 -0
- package/dist/ssh/workers.js.map +1 -0
- package/package.json +25 -9
- package/dist/exe-dev/channel.d.ts +0 -12
- package/dist/exe-dev/channel.d.ts.map +0 -1
- package/dist/exe-dev/channel.js +0 -42
- package/dist/exe-dev/channel.js.map +0 -1
- package/dist/exe-dev/shell.d.ts +0 -12
- package/dist/exe-dev/shell.d.ts.map +0 -1
- package/dist/exe-dev/shell.js +0 -31
- package/dist/exe-dev/shell.js.map +0 -1
- package/dist/exe-dev/sweep.d.ts +0 -10
- package/dist/exe-dev/sweep.d.ts.map +0 -1
- package/dist/exe-dev/sweep.js +0 -117
- package/dist/exe-dev/sweep.js.map +0 -1
- package/dist/invalid-frame.d.ts +0 -17
- package/dist/invalid-frame.d.ts.map +0 -1
- package/dist/invalid-frame.js +0 -111
- package/dist/invalid-frame.js.map +0 -1
- package/dist/run-host-session.d.ts +0 -27
- package/dist/run-host-session.d.ts.map +0 -1
- package/dist/run-host-session.js +0 -303
- package/dist/run-host-session.js.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { WorkerName } from "@usine/core/worker";
|
|
3
|
+
import { Effect, Ref, Schema } from "effect";
|
|
4
|
+
import { ExecutorOperationError } from "../executor.js";
|
|
5
|
+
import { keyedLockFor } from "../keyed-lock.js";
|
|
6
|
+
const SWEEP_CONCURRENCY = 4;
|
|
7
|
+
const directChildSchema = (parent) => {
|
|
8
|
+
const prefix = `${parent}/`;
|
|
9
|
+
return Schema.String.check(Schema.makeFilter((value) => {
|
|
10
|
+
const segment = value.slice(prefix.length);
|
|
11
|
+
return (value.startsWith(prefix) &&
|
|
12
|
+
segment.length > 0 &&
|
|
13
|
+
segment !== "." &&
|
|
14
|
+
segment !== ".." &&
|
|
15
|
+
!segment.includes("/"));
|
|
16
|
+
}, { message: `Expected a direct child of ${parent}` }));
|
|
17
|
+
};
|
|
18
|
+
/** Returns the NUL-separated paths in `output`. Fails if one isn't directly under `parent`. */
|
|
19
|
+
export const directChildren = (operation, parent, output) => Schema.decodeUnknownEffect(Schema.Array(directChildSchema(parent)))(output.split("\0").filter((value) => value.length > 0)).pipe(Effect.mapError((cause) => new ExecutorOperationError({
|
|
20
|
+
operation,
|
|
21
|
+
message: `Worker returned an invalid path while listing ${parent}`,
|
|
22
|
+
cause,
|
|
23
|
+
})));
|
|
24
|
+
/**
|
|
25
|
+
* Lists paths directly under a Worker's `attempts` directory, creating it first so a new Worker
|
|
26
|
+
* lists as empty. Verifies that each path is a direct child of `attemptsRoot`.
|
|
27
|
+
*/
|
|
28
|
+
export const attemptPathsOn = Effect.fn("RemoteExecutor.attemptPathsOn")(function* (commands, operation, shell, attemptsRoot) {
|
|
29
|
+
yield* Effect.annotateCurrentSpan({ host: shell.destination, operation, attemptsRoot });
|
|
30
|
+
yield* commands.run(operation, shell, "mkdir", ["-p", attemptsRoot]);
|
|
31
|
+
const output = yield* commands.run(operation, shell, "find", [
|
|
32
|
+
attemptsRoot,
|
|
33
|
+
"-mindepth",
|
|
34
|
+
"1",
|
|
35
|
+
"-maxdepth",
|
|
36
|
+
"1",
|
|
37
|
+
"-print0",
|
|
38
|
+
]);
|
|
39
|
+
return yield* directChildren(operation, attemptsRoot, output);
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Returns a sweep that prunes stale cache registrations once per Worker and keeps crash
|
|
43
|
+
* leftovers. Only durable Attempt recovery removes an existing checkout.
|
|
44
|
+
*/
|
|
45
|
+
export const abandonedWorktreeSweepFor = Effect.fn("RemoteExecutor.abandonedWorktreeSweepFor")(function* (commands) {
|
|
46
|
+
const locks = yield* keyedLockFor();
|
|
47
|
+
const swept = yield* Ref.make(new Set());
|
|
48
|
+
const sweep = Effect.fn("RemoteExecutor.sweepAbandonedWorktrees")(function* (attemptId, worker, shell, workerDirectory) {
|
|
49
|
+
const host = shell.destination;
|
|
50
|
+
yield* Effect.annotateCurrentSpan({ attemptId, worker, host });
|
|
51
|
+
yield* locks.withLock(worker)(Effect.gen(function* () {
|
|
52
|
+
if ((yield* Ref.get(swept)).has(worker)) {
|
|
53
|
+
yield* Effect.logDebug("Remote abandoned-worktree sweep was already complete", {
|
|
54
|
+
attemptId,
|
|
55
|
+
worker,
|
|
56
|
+
host,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Marked done before running so a failed sweep is not retried before every prepare; the
|
|
61
|
+
// next process start retries it.
|
|
62
|
+
yield* Ref.update(swept, (current) => new Set(current).add(worker));
|
|
63
|
+
const attemptsRoot = `${workerDirectory}/attempts`;
|
|
64
|
+
const reposRoot = `${workerDirectory}/repos`;
|
|
65
|
+
yield* Effect.logInfo("Sweeping abandoned Attempt worktrees on the remote Worker", {
|
|
66
|
+
attemptId,
|
|
67
|
+
worker,
|
|
68
|
+
host,
|
|
69
|
+
});
|
|
70
|
+
yield* commands.run("prepare", shell, "mkdir", ["-p", reposRoot]);
|
|
71
|
+
const attempts = yield* attemptPathsOn(commands, "prepare", shell, attemptsRoot);
|
|
72
|
+
// An unrecognized path can contain unsalvaged work from a crash; only recovery from the
|
|
73
|
+
// durable Attempt record removes it.
|
|
74
|
+
yield* Effect.forEach(attempts, (worktree) => Effect.logWarning("Preserving abandoned remote path for salvage reconciliation", {
|
|
75
|
+
attemptId,
|
|
76
|
+
worker,
|
|
77
|
+
host,
|
|
78
|
+
worktree,
|
|
79
|
+
}), { discard: true });
|
|
80
|
+
const caches = yield* commands
|
|
81
|
+
.run("prepare", shell, "find", [
|
|
82
|
+
reposRoot,
|
|
83
|
+
"-mindepth",
|
|
84
|
+
"1",
|
|
85
|
+
"-maxdepth",
|
|
86
|
+
"1",
|
|
87
|
+
"-type",
|
|
88
|
+
"d",
|
|
89
|
+
"-name",
|
|
90
|
+
"*.git",
|
|
91
|
+
"-print0",
|
|
92
|
+
])
|
|
93
|
+
.pipe(Effect.flatMap((output) => directChildren("prepare", reposRoot, output)));
|
|
94
|
+
yield* Effect.forEach(caches, (cache) => commands.run("prepare", shell, "git", ["--git-dir", cache, "worktree", "prune"]).pipe(Effect.tapError((error) => Effect.logWarning("Could not prune a remote cache during the sweep", {
|
|
95
|
+
attemptId,
|
|
96
|
+
worker,
|
|
97
|
+
host,
|
|
98
|
+
cache,
|
|
99
|
+
message: error.message,
|
|
100
|
+
})), Effect.ignore), { concurrency: SWEEP_CONCURRENCY, discard: true });
|
|
101
|
+
yield* Effect.logInfo("Remote abandoned-worktree sweep completed", {
|
|
102
|
+
attemptId,
|
|
103
|
+
worker,
|
|
104
|
+
host,
|
|
105
|
+
retained: attempts.length,
|
|
106
|
+
caches: caches.length,
|
|
107
|
+
});
|
|
108
|
+
}).pipe(Effect.tapError((error) => Effect.logWarning("Remote abandoned-worktree sweep failed; continuing", {
|
|
109
|
+
attemptId,
|
|
110
|
+
worker,
|
|
111
|
+
host,
|
|
112
|
+
message: error.message,
|
|
113
|
+
})), Effect.ignore));
|
|
114
|
+
});
|
|
115
|
+
return sweep;
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.js","sourceRoot":"","sources":["../../src/remote/sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import type { LoopPermissions } from "@usine/core/loop";
|
|
3
|
+
import type { RepositoryName } from "@usine/core/repository";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import type { CredentialUnavailableError, WorkerCredential } from "./executor.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Issues the Attempt's single-repository token with its Loop `permissions`, passes it to
|
|
8
|
+
* `publish`, and publishes a new one before each expiry until the caller's scope closes. Then
|
|
9
|
+
* wipes every issued token, so `Redacted.value` throws. Register cleanup for what `publish`
|
|
10
|
+
* writes before calling this, so the cleanup runs after renewal stops.
|
|
11
|
+
*/
|
|
12
|
+
export declare const renewableCredential: <A, E>(issue: (repository: RepositoryName, attemptId: AttemptId, permissions: LoopPermissions) => Effect.Effect<WorkerCredential, CredentialUnavailableError>, repository: string & import("effect/Brand").Brand<"RepositoryName">, attemptId: string & import("effect/Brand").Brand<"AttemptId">, permissions: "read" | "write", publish: (credential: WorkerCredential) => Effect.Effect<A, E>) => Effect.Effect<{
|
|
13
|
+
published: A;
|
|
14
|
+
refresh: () => Effect.Effect<void, E | CredentialUnavailableError, never>;
|
|
15
|
+
scrub: (text: string) => string;
|
|
16
|
+
}, E | CredentialUnavailableError, import("effect/Scope").Scope>;
|
|
17
|
+
//# sourceMappingURL=renewable-credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renewable-credential.d.ts","sourceRoot":"","sources":["../src/renewable-credential.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAmB,MAAM,EAA6B,MAAM,QAAQ,CAAC;AAC5E,OAAO,KAAK,EAEV,0BAA0B,EAC1B,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAcvB;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAwD,CAAC,EAAE,CAAC,mVAKpE,gBAAgB,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;;;kBAoD9C,MAAM,KAAG,MAAM;gEAK/B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Clock, DateTime, Effect, Redacted, SynchronizedRef } from "effect";
|
|
2
|
+
/** The remaining lifetime, in milliseconds, below which a new token is issued. */
|
|
3
|
+
const RENEWAL_WINDOW_MILLIS = 5 * 60_000;
|
|
4
|
+
/**
|
|
5
|
+
* Issues the Attempt's single-repository token with its Loop `permissions`, passes it to
|
|
6
|
+
* `publish`, and publishes a new one before each expiry until the caller's scope closes. Then
|
|
7
|
+
* wipes every issued token, so `Redacted.value` throws. Register cleanup for what `publish`
|
|
8
|
+
* writes before calling this, so the cleanup runs after renewal stops.
|
|
9
|
+
*/
|
|
10
|
+
export const renewableCredential = Effect.fn("Executor.renewableCredential")(function* (issue, repository, attemptId, permissions, publish) {
|
|
11
|
+
yield* Effect.annotateCurrentSpan({ repository, attemptId, permissions });
|
|
12
|
+
const issued = new Set();
|
|
13
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
14
|
+
for (const token of issued)
|
|
15
|
+
Redacted.wipeUnsafe(token);
|
|
16
|
+
}));
|
|
17
|
+
const publishFresh = Effect.fn("Executor.publishCredential")(function* () {
|
|
18
|
+
yield* Effect.annotateCurrentSpan({ repository, attemptId, permissions });
|
|
19
|
+
const credential = yield* issue(repository, attemptId, permissions);
|
|
20
|
+
issued.add(credential.value);
|
|
21
|
+
const published = yield* publish(credential);
|
|
22
|
+
yield* Effect.logInfo("Published renewed Attempt repository credential", {
|
|
23
|
+
repository,
|
|
24
|
+
attemptId,
|
|
25
|
+
permissions,
|
|
26
|
+
expiresAt: DateTime.formatIso(credential.expiresAt),
|
|
27
|
+
});
|
|
28
|
+
return { expiresAt: credential.expiresAt, published };
|
|
29
|
+
});
|
|
30
|
+
const first = yield* publishFresh();
|
|
31
|
+
const expiry = yield* SynchronizedRef.make(first.expiresAt);
|
|
32
|
+
const refresh = Effect.fn("Executor.refreshCredential")(function* () {
|
|
33
|
+
yield* Effect.annotateCurrentSpan({ repository, attemptId });
|
|
34
|
+
yield* SynchronizedRef.updateEffect(expiry, (expiresAt) => Effect.gen(function* () {
|
|
35
|
+
const now = yield* Clock.currentTimeMillis;
|
|
36
|
+
if (DateTime.toEpochMillis(expiresAt) - now > RENEWAL_WINDOW_MILLIS)
|
|
37
|
+
return expiresAt;
|
|
38
|
+
return (yield* publishFresh()).expiresAt;
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
// Forked in a scope opened before the checkout finalizers, so renewal continues through
|
|
42
|
+
// salvage and stops before the published credential is removed.
|
|
43
|
+
yield* Effect.forever(Effect.sleep("30 seconds").pipe(Effect.andThen(refresh()), Effect.tapError((error) => Effect.logWarning("Attempt credential renewal failed; will retry", {
|
|
44
|
+
repository,
|
|
45
|
+
attemptId,
|
|
46
|
+
message: String(error),
|
|
47
|
+
})), Effect.ignore)).pipe(Effect.forkScoped);
|
|
48
|
+
return {
|
|
49
|
+
published: first.published,
|
|
50
|
+
refresh,
|
|
51
|
+
scrub: (text) => {
|
|
52
|
+
for (const token of issued)
|
|
53
|
+
text = text.replaceAll(Redacted.value(token), "[REDACTED]");
|
|
54
|
+
return text;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=renewable-credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renewable-credential.js","sourceRoot":"","sources":["../src/renewable-credential.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAO5E,kFAAkF;AAClF,MAAM,qBAAqB,GAAG,CAAC,GAAG,MAAM,CAAC;AAWzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,8BAA8B,CAAC,CAAC,QAAQ,CAAC,EACpF,KAA6C,EAC7C,UAA0B,EAC1B,SAAoB,EACpB,WAA4B,EAC5B,OAA8D;IAE9D,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;IACpD,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACf,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CACH,CAAC;IACF,MAAM,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,4BAA4B,CAAC,CAAC,QAAQ,CAAC;QACpE,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,iDAAiD,EAAE;YACvE,UAAU;YACV,SAAS;YACT,WAAW;YACX,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC;SACpD,CAAC,CAAC;QACH,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,4BAA4B,CAAC,CAAC,QAAQ,CAAC;QAC/D,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,KAAK,CAAC,CAAC,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CACxD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAC3C,IAAI,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,GAAG,GAAG,qBAAqB;gBAAE,OAAO,SAAS,CAAC;YACtF,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,SAAS,CAAC;QAC3C,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,wFAAwF;IACxF,gEAAgE;IAChE,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CACnB,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EACzB,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,UAAU,CAAC,+CAA+C,EAAE;QACjE,UAAU;QACV,SAAS;QACT,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;KACvB,CAAC,CACH,EACD,MAAM,CAAC,MAAM,CACd,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1B,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO;QACP,KAAK,EAAE,CAAC,IAAY,EAAU,EAAE;YAC9B,KAAK,MAAM,KAAK,IAAI,MAAM;gBAAE,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;YACxF,OAAO,IAAI,CAAC;QACd,CAAC;KACkC,CAAC;AACxC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { type SalvageRef, SalvagePartlyPushed, SalvagePushed, SalvageRejected, SalvageRetained } from "@usine/core/attempt/salvage";
|
|
3
|
+
import { CommitSha } from "@usine/core/repository";
|
|
4
|
+
import type { WorkerName } from "@usine/core/worker";
|
|
5
|
+
import { Effect, Option } from "effect";
|
|
6
|
+
export interface SalvageOptions<E extends Error> {
|
|
7
|
+
readonly attemptId: AttemptId;
|
|
8
|
+
readonly worker: WorkerName;
|
|
9
|
+
readonly host: string;
|
|
10
|
+
readonly worktree: string;
|
|
11
|
+
readonly baseCommit: Option.Option<CommitSha>;
|
|
12
|
+
readonly ref: SalvageRef;
|
|
13
|
+
/** An earlier salvage that the remote rejected; its commit isn't pushed again. */
|
|
14
|
+
readonly rejected: Option.Option<SalvageRejected>;
|
|
15
|
+
/**
|
|
16
|
+
* Runs git with the Worker's repository credentials and the Attempt's `commitIdentity`, which
|
|
17
|
+
* authors and commits the salvage commit. `salvageWorktree` prepends `-C <worktree>`.
|
|
18
|
+
*/
|
|
19
|
+
readonly git: (args: ReadonlyArray<string>) => Effect.Effect<string, E>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the operator-facing line for the run drawer: the failure's message followed by its
|
|
23
|
+
* cause's message, if the cause has one. For a failed command, the cause is the stderr, which
|
|
24
|
+
* local and SSH executors scrub of the Attempt's token.
|
|
25
|
+
*/
|
|
26
|
+
export declare const failureMessage: (failure: Error) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Commits the changes in an unsuccessful Attempt's checkout and pushes HEAD to its salvage ref
|
|
29
|
+
* before the worktree is removed. The commit leaves out package stores and build caches, even if
|
|
30
|
+
* the Attempt staged them, and what Git ignores. Skips a checkout with nothing to push beyond its
|
|
31
|
+
* base commit, or whose commit the remote rejected in `rejected`. If the push fails, or the
|
|
32
|
+
* commit leaves out a file larger than GitHub accepts, keeps the worktree and reports its path.
|
|
33
|
+
* If the inspection or the commit fails while the checkout's head can still be the rejected
|
|
34
|
+
* commit, returns `rejected` again, so that the next recovery doesn't push that commit either.
|
|
35
|
+
* Never fails; skipped and failed stages are logged.
|
|
36
|
+
*/
|
|
37
|
+
export declare const salvageWorktree: <E extends Error>(options: SalvageOptions<E>) => Effect.Effect<Option.Option<SalvagePartlyPushed | SalvagePushed | SalvageRejected | SalvageRetained>, never, never>;
|
|
38
|
+
//# sourceMappingURL=salvage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salvage.d.ts","sourceRoot":"","sources":["../src/salvage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAEL,KAAK,UAAU,EACf,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,eAAe,EAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAwBxD,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,KAAK;IAC7C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAClD;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CACzE;AAMD;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAAa,KAAK,KAAG,MAM/C,CAAC;AA+BF;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAoD,CAAC,SAAS,KAAK,oJAmL7F,CAAC"}
|
package/dist/salvage.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { SalvagePartlyPushed, SalvagePushed, SalvageRejected, SalvageRetained, } from "@usine/core/attempt/salvage";
|
|
2
|
+
import { CommitSha } from "@usine/core/repository";
|
|
3
|
+
import { Effect, Option, Result, Schema } from "effect";
|
|
4
|
+
const PUSH_TIMEOUT = "2 minutes";
|
|
5
|
+
/** GitHub refuses a push with a file larger than 100 MiB. */
|
|
6
|
+
const GITHUB_FILE_LIMIT_BYTES = 100 * 1024 * 1024;
|
|
7
|
+
/** GitHub's code for a push refused for a file over its size limit, in the push's stderr. */
|
|
8
|
+
const LARGE_FILES_REFUSAL = /\bGH001\b/u;
|
|
9
|
+
/** The package stores and build caches that the salvage commit leaves out at any depth. */
|
|
10
|
+
const CACHE_DIRECTORIES = [".pnpm-store", "node_modules", ".turbo"];
|
|
11
|
+
const CACHE_PATHS = CACHE_DIRECTORIES.map((directory) => `:(glob)**/${directory}/**`);
|
|
12
|
+
/**
|
|
13
|
+
* The checkout's paths that the salvage commits: everything that Git doesn't ignore, except
|
|
14
|
+
* package stores and build caches.
|
|
15
|
+
*/
|
|
16
|
+
const SALVAGED_PATHS = [
|
|
17
|
+
".",
|
|
18
|
+
...CACHE_DIRECTORIES.map((directory) => `:(exclude,glob)**/${directory}/**`),
|
|
19
|
+
];
|
|
20
|
+
const decodeCommit = Schema.decodeUnknownEffect(CommitSha);
|
|
21
|
+
const decodeCauseMessage = Schema.decodeUnknownOption(Schema.Struct({ message: Schema.String }));
|
|
22
|
+
/**
|
|
23
|
+
* Returns the operator-facing line for the run drawer: the failure's message followed by its
|
|
24
|
+
* cause's message, if the cause has one. For a failed command, the cause is the stderr, which
|
|
25
|
+
* local and SSH executors scrub of the Attempt's token.
|
|
26
|
+
*/
|
|
27
|
+
export const failureMessage = (failure) => {
|
|
28
|
+
const detail = Option.match(decodeCauseMessage(failure.cause), {
|
|
29
|
+
onNone: () => "",
|
|
30
|
+
onSome: ({ message }) => message.trim(),
|
|
31
|
+
});
|
|
32
|
+
return detail.length === 0 ? failure.message : `${failure.message}: ${detail}`;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Returns the staged files in the `ls-tree -r -l -z` listing of the index's tree that are larger
|
|
36
|
+
* than GitHub accepts.
|
|
37
|
+
*/
|
|
38
|
+
const oversizedIn = (listing, staged) => listing.split("\0").flatMap((entry) => {
|
|
39
|
+
// <mode> SP <type> SP <object> SP <padded size> TAB <path>
|
|
40
|
+
const tab = entry.indexOf("\t");
|
|
41
|
+
if (tab < 0)
|
|
42
|
+
return [];
|
|
43
|
+
const [, type, , size] = entry.slice(0, tab).split(/ +/u);
|
|
44
|
+
const path = entry.slice(tab + 1);
|
|
45
|
+
return type === "blob" && Number(size) > GITHUB_FILE_LIMIT_BYTES && staged.has(path)
|
|
46
|
+
? [{ path, size: Number(size) }]
|
|
47
|
+
: [];
|
|
48
|
+
});
|
|
49
|
+
const describeFiles = (files) => files.map(({ path, size }) => `${path} (${(size / 1024 / 1024).toFixed(1)} MiB)`).join(", ");
|
|
50
|
+
/** Returns the operator-facing reason that a salvage keeps the checkout of `files`. */
|
|
51
|
+
const leftOutMessage = (files) => `The checkout has files larger than GitHub's 100 MiB limit, which the salvage can't push: ${describeFiles(files)}`;
|
|
52
|
+
/**
|
|
53
|
+
* Commits the changes in an unsuccessful Attempt's checkout and pushes HEAD to its salvage ref
|
|
54
|
+
* before the worktree is removed. The commit leaves out package stores and build caches, even if
|
|
55
|
+
* the Attempt staged them, and what Git ignores. Skips a checkout with nothing to push beyond its
|
|
56
|
+
* base commit, or whose commit the remote rejected in `rejected`. If the push fails, or the
|
|
57
|
+
* commit leaves out a file larger than GitHub accepts, keeps the worktree and reports its path.
|
|
58
|
+
* If the inspection or the commit fails while the checkout's head can still be the rejected
|
|
59
|
+
* commit, returns `rejected` again, so that the next recovery doesn't push that commit either.
|
|
60
|
+
* Never fails; skipped and failed stages are logged.
|
|
61
|
+
*/
|
|
62
|
+
export const salvageWorktree = Effect.fn("Executor.salvageWorktree")(function* (options) {
|
|
63
|
+
const fields = {
|
|
64
|
+
attemptId: options.attemptId,
|
|
65
|
+
worker: options.worker,
|
|
66
|
+
host: options.host,
|
|
67
|
+
worktree: options.worktree,
|
|
68
|
+
};
|
|
69
|
+
yield* Effect.annotateCurrentSpan(fields);
|
|
70
|
+
const git = (args) => options.git(["-C", options.worktree, ...args]);
|
|
71
|
+
const head = () => git(["rev-parse", "HEAD"]).pipe(Effect.flatMap((output) => decodeCommit(output.trim())));
|
|
72
|
+
/**
|
|
73
|
+
* Returns what the salvage keeps of a checkout that it couldn't inspect or commit, at `current`
|
|
74
|
+
* if its head is known. While `current` can be the commit of the recorded rejection, that's the
|
|
75
|
+
* rejection; otherwise it's the retained checkout, at the base commit if the head is unknown.
|
|
76
|
+
*/
|
|
77
|
+
const unsalvaged = (message, current) => {
|
|
78
|
+
const rejected = Option.filter(options.rejected, ({ commit }) => Option.isNone(current) || Option.contains(current, commit));
|
|
79
|
+
if (Option.isSome(rejected))
|
|
80
|
+
return Effect.logWarning("Keeping the recorded rejection of the Attempt's salvage, so that recovery doesn't push its commit again", { ...fields, ref: options.ref, commit: rejected.value.commit, message }).pipe(Effect.as(Option.some(rejected.value)));
|
|
81
|
+
const commit = Option.orElse(current, () => options.baseCommit);
|
|
82
|
+
return Effect.succeed(Option.some(new SalvageRetained({ path: options.worktree, commit, message })));
|
|
83
|
+
};
|
|
84
|
+
const inspected = yield* Effect.all({
|
|
85
|
+
head: head(),
|
|
86
|
+
status: git(["status", "--porcelain", "--untracked-files=all", "--", ...SALVAGED_PATHS]),
|
|
87
|
+
}).pipe(Effect.result);
|
|
88
|
+
if (Result.isFailure(inspected)) {
|
|
89
|
+
const message = failureMessage(inspected.failure);
|
|
90
|
+
yield* Effect.logError("Could not inspect the Attempt worktree for salvage", {
|
|
91
|
+
...fields,
|
|
92
|
+
message,
|
|
93
|
+
});
|
|
94
|
+
return yield* unsalvaged(message, Option.none());
|
|
95
|
+
}
|
|
96
|
+
const dirty = inspected.success.status.trim().length > 0;
|
|
97
|
+
/**
|
|
98
|
+
* Commits the salvaged paths without the files larger than GitHub accepts, and returns the
|
|
99
|
+
* checkout's head and the files left out.
|
|
100
|
+
*/
|
|
101
|
+
const snapshot = (current) => Effect.gen(function* () {
|
|
102
|
+
yield* git(["add", "--all", "--", ...SALVAGED_PATHS]);
|
|
103
|
+
// Exclusion pathspecs don't unstage the caches that the Attempt's own `git add` staged.
|
|
104
|
+
yield* git(["reset", "--quiet", "--", ...CACHE_PATHS]);
|
|
105
|
+
const staged = (yield* git(["diff", "--cached", "--name-only", "--no-renames", "-z"]))
|
|
106
|
+
.split("\0")
|
|
107
|
+
.filter((path) => path.length > 0);
|
|
108
|
+
if (staged.length === 0)
|
|
109
|
+
return { commit: current, leftOut: [] };
|
|
110
|
+
const tree = (yield* git(["write-tree"])).trim();
|
|
111
|
+
const oversized = oversizedIn(yield* git(["ls-tree", "-r", "-l", "-z", tree]), new Set(staged));
|
|
112
|
+
if (oversized.length > 0) {
|
|
113
|
+
yield* git([
|
|
114
|
+
"--literal-pathspecs",
|
|
115
|
+
"reset",
|
|
116
|
+
"--quiet",
|
|
117
|
+
"--",
|
|
118
|
+
...oversized.map(({ path }) => path),
|
|
119
|
+
]);
|
|
120
|
+
yield* Effect.logWarning("Left files larger than GitHub's 100 MiB limit out of the salvage commit", { ...fields, files: describeFiles(oversized) });
|
|
121
|
+
}
|
|
122
|
+
if (staged.length === oversized.length)
|
|
123
|
+
return { commit: current, leftOut: oversized };
|
|
124
|
+
yield* git([
|
|
125
|
+
"-c",
|
|
126
|
+
"commit.gpgsign=false",
|
|
127
|
+
"commit",
|
|
128
|
+
"--quiet",
|
|
129
|
+
"--no-verify",
|
|
130
|
+
"--message",
|
|
131
|
+
`wip(usine): salvage attempt ${options.attemptId}`,
|
|
132
|
+
]);
|
|
133
|
+
return { commit: yield* head(), leftOut: oversized };
|
|
134
|
+
});
|
|
135
|
+
let commit = inspected.success.head;
|
|
136
|
+
let leftOut = [];
|
|
137
|
+
if (dirty) {
|
|
138
|
+
const committed = yield* snapshot(commit).pipe(Effect.result);
|
|
139
|
+
if (Result.isFailure(committed)) {
|
|
140
|
+
const message = failureMessage(committed.failure);
|
|
141
|
+
yield* Effect.logError("Could not commit the Attempt's uncommitted changes", {
|
|
142
|
+
...fields,
|
|
143
|
+
message,
|
|
144
|
+
});
|
|
145
|
+
return yield* unsalvaged(message, Option.some(commit));
|
|
146
|
+
}
|
|
147
|
+
({ commit, leftOut } = committed.success);
|
|
148
|
+
}
|
|
149
|
+
if (Option.contains(options.baseCommit, commit)) {
|
|
150
|
+
if (leftOut.length === 0) {
|
|
151
|
+
yield* Effect.logInfo("Nothing to salvage from the Attempt worktree", fields);
|
|
152
|
+
return Option.none();
|
|
153
|
+
}
|
|
154
|
+
const message = leftOutMessage(leftOut);
|
|
155
|
+
yield* Effect.logWarning("Nothing to push but files larger than GitHub's limit; retaining the Attempt worktree", { ...fields, commit, message });
|
|
156
|
+
return Option.some(new SalvageRetained({ path: options.worktree, commit: Option.some(commit), message }));
|
|
157
|
+
}
|
|
158
|
+
const rejected = Option.filter(options.rejected, (salvage) => salvage.commit === commit);
|
|
159
|
+
if (Option.isSome(rejected)) {
|
|
160
|
+
yield* Effect.logInfo("The remote rejected this salvage commit before; retaining the Attempt worktree without pushing it again", { ...fields, ref: options.ref, commit });
|
|
161
|
+
return Option.some(rejected.value);
|
|
162
|
+
}
|
|
163
|
+
const pushFailure = yield* git([
|
|
164
|
+
"push",
|
|
165
|
+
"--force",
|
|
166
|
+
"--no-verify",
|
|
167
|
+
"origin",
|
|
168
|
+
`${commit}:refs/heads/${options.ref}`,
|
|
169
|
+
]).pipe(Effect.timeoutOption(PUSH_TIMEOUT), Effect.match({
|
|
170
|
+
onFailure: (failure) => Option.some(failureMessage(failure)),
|
|
171
|
+
onSuccess: Option.match({
|
|
172
|
+
onNone: () => Option.some(`git push didn't finish within ${PUSH_TIMEOUT}`),
|
|
173
|
+
onSome: () => Option.none(),
|
|
174
|
+
}),
|
|
175
|
+
}));
|
|
176
|
+
if (Option.isSome(pushFailure)) {
|
|
177
|
+
const message = pushFailure.value;
|
|
178
|
+
if (LARGE_FILES_REFUSAL.test(message)) {
|
|
179
|
+
yield* Effect.logError("GitHub refused the salvage push for a file over its size limit; retaining the Attempt worktree, which recovery doesn't push again until its commit changes", { ...fields, ref: options.ref, commit, message });
|
|
180
|
+
return Option.some(new SalvageRejected({ path: options.worktree, commit, message }));
|
|
181
|
+
}
|
|
182
|
+
yield* Effect.logError("Salvage push failed; retaining the Attempt worktree", {
|
|
183
|
+
...fields,
|
|
184
|
+
ref: options.ref,
|
|
185
|
+
commit,
|
|
186
|
+
message,
|
|
187
|
+
});
|
|
188
|
+
return Option.some(new SalvageRetained({ path: options.worktree, commit: Option.some(commit), message }));
|
|
189
|
+
}
|
|
190
|
+
if (leftOut.length > 0) {
|
|
191
|
+
const message = leftOutMessage(leftOut);
|
|
192
|
+
yield* Effect.logWarning("Salvaged the Attempt's work without files larger than GitHub's limit; retaining the Attempt worktree, which has their only copy", { ...fields, ref: options.ref, commit, message });
|
|
193
|
+
return Option.some(new SalvagePartlyPushed({ ref: options.ref, commit, path: options.worktree, message }));
|
|
194
|
+
}
|
|
195
|
+
yield* Effect.logInfo("Salvaged the Attempt's work", { ...fields, ref: options.ref, commit });
|
|
196
|
+
return Option.some(new SalvagePushed({ ref: options.ref, commit }));
|
|
197
|
+
});
|
|
198
|
+
//# sourceMappingURL=salvage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"salvage.js","sourceRoot":"","sources":["../src/salvage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Harness } from "@usine/core/agent/brain";
|
|
2
|
+
import { Effect, Option, Redacted } from "effect";
|
|
3
|
+
import { type RemoteCommands } from "../remote/commands.ts";
|
|
4
|
+
import type { RemoteAccess } from "../remote/machine.ts";
|
|
5
|
+
import type { MachineGateway, SshMachine, SshWorkerAddress } from "./model.ts";
|
|
6
|
+
/** Returns whether the Gateway holds a credential for `harness`'s provider. */
|
|
7
|
+
export declare const gatewayHolds: (gateway: Option.Option<MachineGateway>, harness: Harness) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the API key that each Attempt on a Worker's machine gets for `harness`, or `None` if
|
|
10
|
+
* the Worker isn't provisioned, the provisioner sets no key, or the Gateway has the provider's
|
|
11
|
+
* credential.
|
|
12
|
+
*/
|
|
13
|
+
export declare const provisionedApiKey: (worker: {
|
|
14
|
+
readonly address: SshWorkerAddress;
|
|
15
|
+
readonly gateway: Option.Option<MachineGateway>;
|
|
16
|
+
}, harness: Harness) => Option.Option<Redacted.Redacted<string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns `access` with the provisioner's API keys among the Attempt's secrets. With an OpenAI
|
|
19
|
+
* key, also writes the Attempt's Codex home, which `holdCodexHome` gives it, with
|
|
20
|
+
* `CODEX_API_KEY_CONFIG`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const withApiKeys: (commands: RemoteCommands, holdCodexHome: (machine: SshMachine, attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<string, never, import("effect/Scope").Scope>, machine: SshMachine, attemptId: string & import("effect/Brand").Brand<"AttemptId">, access: RemoteAccess, operation: "cancel" | "cleanup" | "launch" | "preflight" | "prepare") => Effect.Effect<RemoteAccess | {
|
|
23
|
+
readOnlyPaths: ReadonlyArray<string>;
|
|
24
|
+
gitEnvironment: Readonly<Record<string, string>>;
|
|
25
|
+
refresh: Effect.Effect<void, import("../executor.ts").ExecutorError>;
|
|
26
|
+
shell: import("@usine/agents/child-host").RemoteShell;
|
|
27
|
+
secrets: {
|
|
28
|
+
[x: string]: Redacted.Redacted<string>;
|
|
29
|
+
};
|
|
30
|
+
writablePaths: string[];
|
|
31
|
+
environment: Readonly<Record<string, string>> | {
|
|
32
|
+
CODEX_HOME: string;
|
|
33
|
+
};
|
|
34
|
+
scrub: (text: string) => string;
|
|
35
|
+
}, import("../executor.ts").ExecutorOperationError, import("effect/Scope").Scope>;
|
|
36
|
+
//# sourceMappingURL=api-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/ssh/api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGvD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA4B/E,+EAA+E;AAC/E,eAAO,MAAM,YAAY,YAAa,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,WAAW,OAAO,KAAG,OAKrF,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,WACpB;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;CAAE,WACtF,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAOzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;kBAsCN,MAAM;iFAMtB,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Effect, Option, Redacted } from "effect";
|
|
2
|
+
import { sendingShell } from "../remote/commands.js";
|
|
3
|
+
import { writeMachineFiles } from "./gateway/attachments.js";
|
|
4
|
+
/** The provider each harness needs a credential from. Pi children run only on local Workers. */
|
|
5
|
+
const PROVIDER_OF_HARNESS = {
|
|
6
|
+
claude: Option.some("anthropic"),
|
|
7
|
+
codex: Option.some("openai"),
|
|
8
|
+
pi: Option.none(),
|
|
9
|
+
};
|
|
10
|
+
/** The variable with the OpenAI API key that Codex's `model_provider` reads (`env_key`). */
|
|
11
|
+
const OPENAI_KEY_VARIABLE = "OPENAI_API_KEY";
|
|
12
|
+
/**
|
|
13
|
+
* Codex's `config.toml` for an OpenAI API key in `OPENAI_API_KEY`: a `model_provider` that
|
|
14
|
+
* sends the Responses API to `api.openai.com` and needs no login. The file contains no key.
|
|
15
|
+
*/
|
|
16
|
+
const CODEX_API_KEY_CONFIG = [
|
|
17
|
+
'model_provider = "usine-openai"',
|
|
18
|
+
"",
|
|
19
|
+
"[model_providers.usine-openai]",
|
|
20
|
+
'name = "OpenAI API key"',
|
|
21
|
+
'base_url = "https://api.openai.com/v1"',
|
|
22
|
+
`env_key = "${OPENAI_KEY_VARIABLE}"`,
|
|
23
|
+
'wire_api = "responses"',
|
|
24
|
+
"requires_openai_auth = false",
|
|
25
|
+
"",
|
|
26
|
+
].join("\n");
|
|
27
|
+
/** Returns whether the Gateway holds a credential for `harness`'s provider. */
|
|
28
|
+
export const gatewayHolds = (gateway, harness) => Option.exists(gateway, ({ settings }) => Option.exists(PROVIDER_OF_HARNESS[harness], (provider) => settings.llmProviders.includes(provider)));
|
|
29
|
+
/**
|
|
30
|
+
* Returns the API key that each Attempt on a Worker's machine gets for `harness`, or `None` if
|
|
31
|
+
* the Worker isn't provisioned, the provisioner sets no key, or the Gateway has the provider's
|
|
32
|
+
* credential.
|
|
33
|
+
*/
|
|
34
|
+
export const provisionedApiKey = (worker, harness) => {
|
|
35
|
+
const { machine } = worker.address;
|
|
36
|
+
if (machine._tag !== "Provisioned" || gatewayHolds(worker.gateway, harness))
|
|
37
|
+
return Option.none();
|
|
38
|
+
return Option.flatMap(PROVIDER_OF_HARNESS[harness], (provider) => machine.provisioner.apiKeys[provider]);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Returns `access` with the provisioner's API keys among the Attempt's secrets. With an OpenAI
|
|
42
|
+
* key, also writes the Attempt's Codex home, which `holdCodexHome` gives it, with
|
|
43
|
+
* `CODEX_API_KEY_CONFIG`.
|
|
44
|
+
*/
|
|
45
|
+
export const withApiKeys = Effect.fn("SshExecutor.withApiKeys")(function* (commands, holdCodexHome, machine, attemptId, access, operation) {
|
|
46
|
+
const anthropic = provisionedApiKey(machine, "claude");
|
|
47
|
+
const openai = provisionedApiKey(machine, "codex");
|
|
48
|
+
const secrets = Object.fromEntries([
|
|
49
|
+
...Option.toArray(Option.map(anthropic, (key) => ["ANTHROPIC_API_KEY", key])),
|
|
50
|
+
...Option.toArray(Option.map(openai, (key) => [OPENAI_KEY_VARIABLE, key])),
|
|
51
|
+
]);
|
|
52
|
+
if (Object.keys(secrets).length === 0)
|
|
53
|
+
return access;
|
|
54
|
+
const codexHome = yield* Effect.transposeOption(Option.map(openai, () => holdCodexHome(machine, attemptId).pipe(Effect.tap((home) => writeMachineFiles(commands, operation, machine.shell, [
|
|
55
|
+
[`${home}/config.toml`, CODEX_API_KEY_CONFIG],
|
|
56
|
+
])))));
|
|
57
|
+
const values = Object.values(secrets).map(Redacted.value);
|
|
58
|
+
return {
|
|
59
|
+
...access,
|
|
60
|
+
// An Attempt container gets the secrets when it starts; without one, the SSH connection
|
|
61
|
+
// sends them.
|
|
62
|
+
shell: sendingShell(access.shell, secrets),
|
|
63
|
+
secrets: { ...access.secrets, ...secrets },
|
|
64
|
+
writablePaths: [...access.writablePaths, ...Option.toArray(codexHome)],
|
|
65
|
+
environment: Option.match(codexHome, {
|
|
66
|
+
onNone: () => access.environment,
|
|
67
|
+
onSome: (home) => ({ ...access.environment, CODEX_HOME: home }),
|
|
68
|
+
}),
|
|
69
|
+
scrub: (text) => values.reduce((scrubbed, value) => scrubbed.replaceAll(value, "<redacted>"), access.scrub(text)),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/ssh/api-keys.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAuB,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,gGAAgG;AAChG,MAAM,mBAAmB,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE;CACoD,CAAC;AAExE,4FAA4F;AAC5F,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAE7C;;;GAGG;AACH,MAAM,oBAAoB,GAAG;IAC3B,iCAAiC;IACjC,EAAE;IACF,gCAAgC;IAChC,yBAAyB;IACzB,wCAAwC;IACxC,cAAc,mBAAmB,GAAG;IACpC,wBAAwB;IACxB,8BAA8B;IAC9B,EAAE;CACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAsC,EAAE,OAAgB,EAAW,EAAE,CAChG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CACtC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CACvD,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CACzC,CACF,CAAC;AAEJ;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAA+F,EAC/F,OAAgB,EAC0B,EAAE;IAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;IACnC,IAAI,OAAO,CAAC,IAAI,KAAK,aAAa,IAAI,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IAClG,OAAO,MAAM,CAAC,OAAO,CACnB,mBAAmB,CAAC,OAAO,CAAC,EAC5B,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CACpD,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,EACvE,QAAwB,EACxB,aAAiC,EACjC,OAAmB,EACnB,SAAoB,EACpB,MAAoB,EACpB,SAA4B;IAE5B,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC;QACjC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,mBAAmB,EAAE,GAAG,CAAU,CAAC,CAAC;QACtF,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,mBAAmB,EAAE,GAAG,CAAU,CAAC,CAAC;KACpF,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACrD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,eAAe,CAC7C,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CACtB,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,CACpC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAClB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE;QACpD,CAAC,GAAG,IAAI,cAAc,EAAE,oBAAoB,CAAC;KAC9C,CAAC,CACH,CACF,CACF,CACF,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO;QACL,GAAG,MAAM;QACT,wFAAwF;QACxF,cAAc;QACd,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC;QAC1C,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE;QAC1C,aAAa,EAAE,CAAC,GAAG,MAAM,CAAC,aAAa,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE;YACnC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW;YAChC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;SAChE,CAAC;QACF,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CACtB,MAAM,CAAC,MAAM,CACX,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,EAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CACnB;KACmB,CAAC;AAC3B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import type { RemoteCommands } from "../remote/commands.ts";
|
|
4
|
+
import type { SshMachine } from "./model.ts";
|
|
5
|
+
/** Returns the Codex home of `attemptId` in the Worker directory `workerDirectory`. */
|
|
6
|
+
export declare const codexHomeIn: (workerDirectory: string, attemptId: AttemptId) => string;
|
|
7
|
+
/** Returns the Attempt's Codex home on `machine`, which Codex also writes its sessions to. */
|
|
8
|
+
export declare const attemptCodexHome: (machine: SshMachine, attemptId: AttemptId) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Tracks the Codex homes that Attempts in this process use. `hold` gives an Attempt its Codex
|
|
11
|
+
* home and removes the directory when the caller's scope closes. `sweep` removes the Codex homes
|
|
12
|
+
* on a long-lived machine that no Attempt holds and whose Attempt has no checkout there, which
|
|
13
|
+
* a crash or a failed removal left.
|
|
14
|
+
*/
|
|
15
|
+
export declare const codexHomesFor: (commands: RemoteCommands) => Effect.Effect<{
|
|
16
|
+
hold: (machine: SshMachine, attemptId: string & import("effect/Brand").Brand<"AttemptId">) => Effect.Effect<string, never, import("effect/Scope").Scope>;
|
|
17
|
+
sweep: (machine: SshMachine) => Effect.Effect<void, import("../executor.ts").ExecutorOperationError, never>;
|
|
18
|
+
}, never, never>;
|
|
19
|
+
/** The Codex homes of the SSH executor's Attempts. See `codexHomesFor`. */
|
|
20
|
+
export type CodexHomes = Effect.Success<ReturnType<typeof codexHomesFor>>;
|
|
21
|
+
//# sourceMappingURL=codex-homes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-homes.d.ts","sourceRoot":"","sources":["../../src/ssh/codex-homes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAe7C,uFAAuF;AACvF,eAAO,MAAM,WAAW,oBAAqB,MAAM,aAAa,SAAS,KAAG,MACtB,CAAC;AAEvD,8FAA8F;AAC9F,eAAO,MAAM,gBAAgB,YAAa,UAAU,aAAa,SAAS,KAAG,MAC5B,CAAC;AAElD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;gBAsFxB,CAAC;AAEH,2EAA2E;AAC3E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC"}
|