@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,39 @@
|
|
|
1
|
+
import { Effect, FileSystem, Redacted } from "effect";
|
|
2
|
+
import { credentialEnvironment, GH_CONFIG, ghHostsConfig } from "../credential-environment.js";
|
|
3
|
+
import { CredentialProvider, ExecutorOperationError } from "../executor.js";
|
|
4
|
+
import { renewableCredential } from "../renewable-credential.js";
|
|
5
|
+
/**
|
|
6
|
+
* Writes the Attempt's renewed credential to a private temporary directory outside the
|
|
7
|
+
* checkout, removed when the Attempt scope closes.
|
|
8
|
+
*/
|
|
9
|
+
export const localCredentialsFor = Effect.gen(function* () {
|
|
10
|
+
const provider = yield* CredentialProvider;
|
|
11
|
+
const fs = yield* FileSystem.FileSystem;
|
|
12
|
+
return Effect.fn("LocalExecutor.credentials")(function* (repository, attemptId, permissions, baseEnvironment) {
|
|
13
|
+
yield* Effect.annotateCurrentSpan({ repository, attemptId, permissions });
|
|
14
|
+
const directory = yield* fs.makeTempDirectoryScoped({ prefix: "usine-credentials-" });
|
|
15
|
+
yield* fs.chmod(directory, 0o700);
|
|
16
|
+
yield* fs.writeFileString(`${directory}/config.yml`, GH_CONFIG, { mode: 0o600 });
|
|
17
|
+
const { refresh, scrub } = yield* renewableCredential(provider.issue, repository, attemptId, permissions, Effect.fn("LocalExecutor.writeCredential")(function* ({ value }) {
|
|
18
|
+
// Writes then renames so readers never see a partial token.
|
|
19
|
+
yield* fs.writeFileString(`${directory}/token.next`, Redacted.value(value), {
|
|
20
|
+
mode: 0o600,
|
|
21
|
+
});
|
|
22
|
+
yield* fs.rename(`${directory}/token.next`, `${directory}/token`);
|
|
23
|
+
yield* fs.writeFileString(`${directory}/hosts.next`, ghHostsConfig(Redacted.value(value)), {
|
|
24
|
+
mode: 0o600,
|
|
25
|
+
});
|
|
26
|
+
yield* fs.rename(`${directory}/hosts.next`, `${directory}/hosts.yml`);
|
|
27
|
+
}));
|
|
28
|
+
return {
|
|
29
|
+
refresh,
|
|
30
|
+
scrub,
|
|
31
|
+
environment: { ...baseEnvironment, ...credentialEnvironment(directory) },
|
|
32
|
+
};
|
|
33
|
+
}, (effect) => effect.pipe(Effect.catchTag("PlatformError", (cause) => Effect.fail(new ExecutorOperationError({
|
|
34
|
+
operation: "prepare",
|
|
35
|
+
message: "Can't write the attempt's credential files",
|
|
36
|
+
cause,
|
|
37
|
+
})))));
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/local/credentials.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAyB,MAAM,gBAAgB,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC;IAC3C,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IACxC,OAAO,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAC3C,QAAQ,CAAC,EACP,UAA0B,EAC1B,SAAoB,EACpB,WAA4B,EAC5B,eAAuC;QAEvC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;QACtF,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClC,KAAK,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,SAAS,aAAa,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACjF,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC,mBAAmB,CACnD,QAAQ,CAAC,KAAK,EACd,UAAU,EACV,SAAS,EACT,WAAW,EACX,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAoB;YAC/E,4DAA4D;YAC5D,KAAK,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,SAAS,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;gBAC1E,IAAI,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,SAAS,aAAa,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;YAClE,KAAK,CAAC,CAAC,EAAE,CAAC,eAAe,CACvB,GAAG,SAAS,aAAa,EACzB,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EACpC;gBACE,IAAI,EAAE,KAAK;aACZ,CACF,CAAC;YACF,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,SAAS,aAAa,EAAE,GAAG,SAAS,YAAY,CAAC,CAAC;QACxE,CAAC,CAAC,CACH,CAAC;QACF,OAAO;YACL,OAAO;YACP,KAAK;YACL,WAAW,EAAE,EAAE,GAAG,eAAe,EAAE,GAAG,qBAAqB,CAAC,SAAS,CAAC,EAAE;SACzE,CAAC;IACJ,CAAC,EACD,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CACzC,MAAM,CAAC,IAAI,CACT,IAAI,sBAAsB,CAAC;QACzB,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,4CAA4C;QACrD,KAAK;KACN,CAAC,CACH,CACF,CACF,CACJ,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkerName } from "@usine/core/worker";
|
|
2
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
3
|
+
/**
|
|
4
|
+
* Lists checkout paths under each local Worker's `attempts` directory, once per Worker per
|
|
5
|
+
* executor. Returns an empty set if the directory doesn't exist.
|
|
6
|
+
*/
|
|
7
|
+
export declare const workerLeftoversFor: (workersRoot: string) => Effect.Effect<(worker: WorkerName) => Effect.Effect<ReadonlySet<string>, import("effect/PlatformError").PlatformError, never>, never, FileSystem.FileSystem | Path.Path>;
|
|
8
|
+
//# sourceMappingURL=leftovers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leftovers.d.ts","sourceRoot":"","sources":["../../src/local/leftovers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,mMAqB7B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
import { listedOncePerWorker } from "../leftovers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Lists checkout paths under each local Worker's `attempts` directory, once per Worker per
|
|
5
|
+
* executor. Returns an empty set if the directory doesn't exist.
|
|
6
|
+
*/
|
|
7
|
+
export const workerLeftoversFor = Effect.fn("LocalExecutor.workerLeftoversFor")(function* (workersRoot) {
|
|
8
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
9
|
+
const path = yield* Path.Path;
|
|
10
|
+
return yield* listedOncePerWorker(Effect.fn("LocalExecutor.listWorkerLeftovers")(function* (worker) {
|
|
11
|
+
yield* Effect.annotateCurrentSpan({ worker, host: "local" });
|
|
12
|
+
const root = path.join(workersRoot, worker, "attempts");
|
|
13
|
+
const entries = (yield* fileSystem.exists(root)) ? yield* fileSystem.readDirectory(root) : [];
|
|
14
|
+
const checkouts = new Set(entries.map((entry) => path.join(root, entry)));
|
|
15
|
+
yield* Effect.logInfo("Listed local checkouts left for reconciliation", {
|
|
16
|
+
worker,
|
|
17
|
+
host: "local",
|
|
18
|
+
checkouts: checkouts.size,
|
|
19
|
+
});
|
|
20
|
+
return checkouts;
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=leftovers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leftovers.js","sourceRoot":"","sources":["../../src/local/leftovers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAC,kCAAkC,CAAC,CAAC,QAAQ,CAAC,EACvF,WAAmB;IAEnB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,OAAO,KAAK,CAAC,CAAC,mBAAmB,CAC/B,MAAM,CAAC,EAAE,CAAC,mCAAmC,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAkB;QAC1E,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,MAAM,SAAS,GAAwB,IAAI,GAAG,CAC5C,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAC/C,CAAC;QACF,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,gDAAgD,EAAE;YACtE,MAAM;YACN,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,SAAS,CAAC,IAAI;SAC1B,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/dist/local/local.d.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
+
import { ClaudeLoginLock } from "@usine/agents/runtime";
|
|
1
2
|
import { RepositoryName } from "@usine/core/repository";
|
|
2
3
|
import { InstanceDirectory } from "@usine/core/instance";
|
|
4
|
+
import type { AttemptRuntime } from "@usine/durable/attempt-runtime";
|
|
3
5
|
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
4
|
-
import { ChildProcessSpawner } from "effect/
|
|
5
|
-
import { CredentialProvider, Executor
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
7
|
+
import { CredentialProvider, Executor } from "../executor.ts";
|
|
8
|
+
import { type HarnessProbe } from "../harness-preflight.ts";
|
|
9
|
+
export type { HarnessProbe } from "../harness-preflight.ts";
|
|
8
10
|
export interface LocalExecutorOptions {
|
|
9
|
-
/**
|
|
11
|
+
/** The directory for local Worker caches and Attempt checkouts. */
|
|
10
12
|
readonly instanceDirectory: InstanceDirectory;
|
|
11
|
-
/**
|
|
12
|
-
readonly runHost: RunHostCommand;
|
|
13
|
-
/** Repository fetch URL override, for example a local mirror; defaults to the GitHub HTTPS clone URL. */
|
|
13
|
+
/** Returns the fetch URL for `repository`, for example a local mirror. Defaults to the GitHub HTTPS clone URL. */
|
|
14
14
|
readonly repositoryUrl?: (repository: RepositoryName) => string;
|
|
15
|
-
/**
|
|
15
|
+
/** The timeout for the parent task's cancellation, then separately for the Attempt to finish. Defaults to two seconds. */
|
|
16
16
|
readonly interruptGrace?: Parameters<typeof Effect.timeoutOption>[1];
|
|
17
|
+
/** Defaults to the `@usine/agents/preflight` checks, which send no prompt. */
|
|
18
|
+
readonly harnessReadiness?: HarnessProbe;
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The executor for the local machine. Each Attempt runs in its own checkout with its Pi parent
|
|
22
|
+
* as a durable task in this process (`AttemptRuntime`), so a live Attempt reattaches after an
|
|
23
|
+
* orchestrator restart. Requires Node.
|
|
24
|
+
*/
|
|
25
|
+
export declare const layer: (options: LocalExecutorOptions) => Layer.Layer<Executor, never, AttemptRuntime | ClaudeLoginLock | CredentialProvider | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
20
26
|
//# sourceMappingURL=local.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/local/local.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/local/local.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAKxD,OAAO,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAOzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EACL,MAAM,EACN,UAAU,EACV,KAAK,EAEL,IAAI,EAIL,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAqB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIxE,OAAO,EAGL,kBAAkB,EAClB,QAAQ,EAOT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,yBAAyB,CAAC;AAqBjC,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,kHAAkH;IAClH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,KAAK,MAAM,CAAC;IAChE,0HAA0H;IAC1H,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,YAAY,CAAC;CAC1C;AA0gBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,YACP,oBAAoB,KAC5B,KAAK,CAAC,KAAK,CACZ,QAAQ,EACR,KAAK,EACH,cAAc,GACd,eAAe,GACf,kBAAkB,GAClB,UAAU,CAAC,UAAU,GACrB,IAAI,CAAC,IAAI,GACT,mBAAmB,CAAC,mBAAmB,CACY,CAAC"}
|
package/dist/local/local.js
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
+
import { workerLeftoversFor } from "./leftovers.js";
|
|
2
|
+
import { localCredentialsFor } from "./credentials.js";
|
|
3
|
+
import { LocalProcess } from "@usine/agents/child-host";
|
|
4
|
+
import { ClaudeLoginLock } from "@usine/agents/runtime";
|
|
1
5
|
import * as AttemptChannel from "@usine/core/attempt/channel";
|
|
2
|
-
import {
|
|
6
|
+
import { InfraFailure } from "@usine/core/attempt/failure";
|
|
7
|
+
import { RepositoryName, safeSegment } from "@usine/core/repository";
|
|
3
8
|
import { InstanceDirectory } from "@usine/core/instance";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
9
|
+
import { harvestFacts, harvestPostedComments, } from "@usine/core/step/harvest";
|
|
10
|
+
import { Effect, FileSystem, Layer, Option, Path, Result, Schema, } from "effect";
|
|
11
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
12
|
+
import { platform, totalmem } from "node:os";
|
|
6
13
|
import { allowlistedEnvironment, collectCommand, requireSuccess } from "../child-command.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
14
|
+
import { durableSessionFor } from "../durable-session.js";
|
|
15
|
+
import { CheckoutRequest, CredentialProvider, Executor, ExecutorError, ExecutorOperationError, fullPreflight, logPreflightFailure, WorkerCapabilityError, } from "../executor.js";
|
|
16
|
+
import { agentsHarnessProbe, verifyHarnessReadiness, } from "../harness-preflight.js";
|
|
17
|
+
import { childEnvironment, commitIdentity } from "../child-environment.js";
|
|
10
18
|
import { keyedLockFor } from "../keyed-lock.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
19
|
+
import { SwapOnDemand, verifyLinuxMemory, verifyWorkerMemory, WorkerMemory, } from "../memory-preflight.js";
|
|
20
|
+
import { cacheDirectoryName, isCacheDirectoryName, keptCheckoutRecovery, linkedWorktree, provisionWorktree, reattachWorktree, recoverWorktree, recoveryFields, } from "../provision.js";
|
|
13
21
|
const isExecutorError = Schema.is(ExecutorError);
|
|
14
|
-
/**
|
|
15
|
-
* The run host inherits only these variables. The App private key and every
|
|
16
|
-
* other orchestrator secret stay out; the single-repo worker token is the only
|
|
17
|
-
* credential it receives (ADR 0011).
|
|
18
|
-
*/
|
|
22
|
+
/** The only process variables passed to an Attempt's git and gh commands (ADR 0011). */
|
|
19
23
|
const HOST_ENVIRONMENT_ALLOWLIST = [
|
|
20
24
|
"HOME",
|
|
21
25
|
"LANG",
|
|
@@ -26,31 +30,20 @@ const HOST_ENVIRONMENT_ALLOWLIST = [
|
|
|
26
30
|
"USER",
|
|
27
31
|
];
|
|
28
32
|
const executorError = (operation, message, cause) => new ExecutorOperationError({ operation, message, cause });
|
|
29
|
-
/** Removes the credential from text before it can reach a persisted error message. */
|
|
30
|
-
const scrubbed = (text, credential) => text.replaceAll(Redacted.value(credential), "[REDACTED]");
|
|
31
|
-
const credentialEnvironment = (base, credential) => {
|
|
32
|
-
const token = Redacted.value(credential);
|
|
33
|
-
return {
|
|
34
|
-
...base,
|
|
35
|
-
GH_TOKEN: token,
|
|
36
|
-
GITHUB_TOKEN: token,
|
|
37
|
-
GIT_CONFIG_COUNT: "1",
|
|
38
|
-
GIT_CONFIG_KEY_0: "credential.helper",
|
|
39
|
-
GIT_CONFIG_VALUE_0: "!f() { echo username=x-access-token; echo password=$GITHUB_TOKEN; }; f",
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
33
|
const makeLocalExecutor = (options) => Effect.gen(function* () {
|
|
43
|
-
const credentials = yield*
|
|
34
|
+
const credentials = yield* localCredentialsFor;
|
|
44
35
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
45
36
|
const path = yield* Path.Path;
|
|
46
37
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
47
38
|
const fetches = yield* keyedLockFor();
|
|
39
|
+
const logins = yield* ClaudeLoginLock;
|
|
48
40
|
const baseEnvironment = yield* allowlistedEnvironment(HOST_ENVIRONMENT_ALLOWLIST);
|
|
41
|
+
const harnessReadiness = options.harnessReadiness ?? agentsHarnessProbe(spawner, new LocalProcess(), baseEnvironment);
|
|
49
42
|
const repositoryUrl = options.repositoryUrl ??
|
|
50
43
|
((repository) => `https://github.com/${repository}.git`);
|
|
51
|
-
const runHost = options.runHost;
|
|
52
44
|
const interruptGrace = options.interruptGrace ?? "2 seconds";
|
|
53
45
|
const workersRoot = path.join(options.instanceDirectory, ".usine", "workers");
|
|
46
|
+
const session = yield* durableSessionFor({ interruptGrace });
|
|
54
47
|
const executeCommand = Effect.fn("LocalExecutor.executeCommand")(function* (operation, command, args, commandOptions = {}) {
|
|
55
48
|
yield* Effect.annotateCurrentSpan({ host: "local", operation });
|
|
56
49
|
const result = yield* collectCommand(spawner, operation, command, args, {
|
|
@@ -63,27 +56,30 @@ const makeLocalExecutor = (options) => Effect.gen(function* () {
|
|
|
63
56
|
const run = Effect.fn("LocalExecutor.run")(function* (operation, command, args, commandOptions = {}) {
|
|
64
57
|
yield* executeCommand(operation, command, args, commandOptions);
|
|
65
58
|
});
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
59
|
+
// Git can print the token in diagnostics, so output is scrubbed before it's put in an error or
|
|
60
|
+
// parsed.
|
|
61
|
+
const credentialedGit = (credential, author) => {
|
|
62
|
+
const environment = { ...credential.environment, ...commitIdentity(author) };
|
|
63
|
+
return (operation, args) => Effect.gen(function* () {
|
|
64
|
+
yield* credential.refresh();
|
|
65
|
+
const result = yield* collectCommand(spawner, operation, "git", args, {
|
|
66
|
+
env: environment,
|
|
67
|
+
extendEnv: false,
|
|
68
|
+
});
|
|
69
|
+
return yield* requireSuccess(operation, "local", "git", args, {
|
|
70
|
+
...result,
|
|
71
|
+
stdout: credential.scrub(result.stdout),
|
|
72
|
+
stderr: credential.scrub(result.stderr),
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
// Startup only prunes worktree registrations whose directories are gone; recovery removes
|
|
77
|
+
// checkouts. Unknown leftovers and checkouts awaiting reattachment stay on disk.
|
|
69
78
|
const sweepAbandonedWorktrees = Effect.fn("LocalExecutor.sweepAbandonedWorktrees")(function* () {
|
|
70
79
|
if (!(yield* fileSystem.exists(workersRoot)))
|
|
71
80
|
return;
|
|
72
81
|
const workers = yield* fileSystem.readDirectory(workersRoot);
|
|
73
82
|
for (const worker of workers) {
|
|
74
|
-
const attemptsRoot = path.join(workersRoot, worker, "attempts");
|
|
75
|
-
if (yield* fileSystem.exists(attemptsRoot)) {
|
|
76
|
-
const leftovers = yield* fileSystem.readDirectory(attemptsRoot);
|
|
77
|
-
for (const leftover of leftovers) {
|
|
78
|
-
const target = path.join(attemptsRoot, leftover);
|
|
79
|
-
yield* Effect.logInfo("Sweeping abandoned Attempt worktree", {
|
|
80
|
-
worker,
|
|
81
|
-
host: "local",
|
|
82
|
-
worktree: target,
|
|
83
|
-
});
|
|
84
|
-
yield* fileSystem.remove(target, { recursive: true, force: true });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
83
|
const reposRoot = path.join(workersRoot, worker, "repos");
|
|
88
84
|
if (yield* fileSystem.exists(reposRoot)) {
|
|
89
85
|
const entries = yield* fileSystem.readDirectory(reposRoot);
|
|
@@ -131,144 +127,221 @@ const makeLocalExecutor = (options) => Effect.gen(function* () {
|
|
|
131
127
|
yield* sweepAbandonedWorktrees().pipe(Effect.tapError((error) => Effect.logWarning("Abandoned-worktree sweep failed; continuing", {
|
|
132
128
|
message: String(error),
|
|
133
129
|
})), Effect.ignore);
|
|
134
|
-
const
|
|
135
|
-
|
|
130
|
+
const workerLeftovers = yield* workerLeftoversFor(workersRoot);
|
|
131
|
+
const harvestCommand = (operation, worktree, credential, environment) => (command, args) => Effect.gen(function* () {
|
|
132
|
+
yield* credential.refresh();
|
|
133
|
+
const result = yield* collectCommand(spawner, operation, command, args, {
|
|
134
|
+
cwd: worktree,
|
|
135
|
+
env: environment,
|
|
136
|
+
extendEnv: false,
|
|
137
|
+
});
|
|
138
|
+
const stdout = yield* requireSuccess(operation, "local", command, args, {
|
|
139
|
+
...result,
|
|
140
|
+
stdout: credential.scrub(result.stdout),
|
|
141
|
+
stderr: credential.scrub(result.stderr),
|
|
142
|
+
});
|
|
143
|
+
return stdout === "" ? [] : stdout.replace(/\n$/u, "").split("\n");
|
|
144
|
+
});
|
|
145
|
+
const recover = Effect.fn("LocalExecutor.recover")(function* (attempt, record, commitAuthor) {
|
|
146
|
+
yield* Effect.annotateCurrentSpan({ ...recoveryFields(attempt), host: "local" });
|
|
147
|
+
yield* session.stopForRecovery(attempt);
|
|
148
|
+
const root = path.join(workersRoot, attempt.worker);
|
|
149
|
+
const worktree = path.join(root, "attempts", safeSegment(attempt.attemptId));
|
|
150
|
+
const present = (yield* workerLeftovers(attempt.worker)).has(worktree) &&
|
|
151
|
+
(yield* fileSystem.exists(worktree));
|
|
152
|
+
yield* session.recoverPostedComments(attempt, present
|
|
153
|
+
? Option.some((spec, windows) => Effect.scoped(Effect.gen(function* () {
|
|
154
|
+
const credential = yield* credentials(attempt.repository, attempt.attemptId, attempt.permissions, baseEnvironment);
|
|
155
|
+
return yield* harvestPostedComments(spec, harvestCommand("cleanup", worktree, credential, credential.environment), windows);
|
|
156
|
+
})))
|
|
157
|
+
: Option.none(), record);
|
|
158
|
+
if (!present)
|
|
159
|
+
return;
|
|
160
|
+
yield* reconcileCheckout(attempt, worktree, record.salvage, commitAuthor);
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* Salvages `attempt`'s checkout at `worktree` with a new credential, records the salvage
|
|
164
|
+
* through `record`, and removes the checkout once its work is pushed. See `recoverWorktree`.
|
|
165
|
+
*/
|
|
166
|
+
const reconcileCheckout = Effect.fn("LocalExecutor.reconcileCheckout")(function* (attempt, worktree, record, commitAuthor) {
|
|
167
|
+
yield* Effect.annotateCurrentSpan({ attemptId: attempt.attemptId, worker: attempt.worker });
|
|
168
|
+
const cache = path.join(workersRoot, attempt.worker, "repos", cacheDirectoryName(attempt.repository));
|
|
169
|
+
const removeCheckout = (git) => Effect.asVoid(git(["--git-dir", cache, "worktree", "remove", "--force", worktree]));
|
|
170
|
+
if (attempt.succeeded || attempt.permissions === "read") {
|
|
171
|
+
const git = (args) => executeCommand("cleanup", "git", args);
|
|
172
|
+
return yield* recoverWorktree({ attempt, host: "local", worktree, git, removeCheckout: removeCheckout(git) }, record);
|
|
173
|
+
}
|
|
174
|
+
const author = yield* commitAuthor;
|
|
175
|
+
yield* Effect.scoped(Effect.gen(function* () {
|
|
176
|
+
const credential = yield* credentials(attempt.repository, attempt.attemptId, attempt.permissions, baseEnvironment);
|
|
177
|
+
const git = credentialedGit(credential, author);
|
|
178
|
+
const inCheckout = (args) => git("cleanup", args);
|
|
179
|
+
yield* recoverWorktree({
|
|
180
|
+
attempt,
|
|
181
|
+
host: "local",
|
|
182
|
+
worktree,
|
|
183
|
+
git: inCheckout,
|
|
184
|
+
removeCheckout: removeCheckout(inCheckout),
|
|
185
|
+
}, record);
|
|
186
|
+
}));
|
|
187
|
+
});
|
|
188
|
+
/**
|
|
189
|
+
* Reconciles the checkout at `worktree` that the launch of `request` kept while its parent
|
|
190
|
+
* task's stop was unconfirmed. Does nothing if the checkout is gone.
|
|
191
|
+
*/
|
|
192
|
+
const reconcileKept = Effect.fn("LocalExecutor.reconcileKept")(function* (request, worktree, record) {
|
|
193
|
+
yield* Effect.annotateCurrentSpan({ attemptId: request.attemptId, worker: request.worker });
|
|
194
|
+
if (!(yield* fileSystem.exists(worktree))) {
|
|
195
|
+
yield* Effect.logInfo("The kept checkout is gone; nothing to reconcile", {
|
|
196
|
+
attemptId: request.attemptId,
|
|
197
|
+
worker: request.worker,
|
|
198
|
+
worktree,
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
yield* reconcileCheckout(keptCheckoutRecovery(request), worktree, record, Effect.succeed(request.commitAuthor));
|
|
203
|
+
}, (effect) => effect.pipe(Effect.catchTag("PlatformError", (cause) => Effect.fail(executorError("cleanup", "Can't reconcile the kept local checkout", cause)))));
|
|
204
|
+
const preflight = Effect.fn("LocalExecutor.preflight")(function* (worker, brains, maxConcurrentAttempts) {
|
|
205
|
+
yield* Effect.annotateCurrentSpan({ worker, host: "local", maxConcurrentAttempts });
|
|
136
206
|
const gitVersion = (yield* executeCommand("preflight", "git", ["--version"])).trim();
|
|
137
207
|
if (!gitVersion.startsWith("git version ")) {
|
|
138
208
|
return yield* new WorkerCapabilityError({
|
|
139
209
|
worker,
|
|
140
210
|
capability: "toolchain",
|
|
141
|
-
message: `Worker ${worker} returned an unrecognized
|
|
211
|
+
message: `Worker "${worker}" returned an unrecognized Git version`,
|
|
142
212
|
cause: new Error(gitVersion),
|
|
143
213
|
});
|
|
144
214
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return yield* new WorkerCapabilityError({
|
|
151
|
-
worker,
|
|
152
|
-
capability: "run-host",
|
|
153
|
-
message: `Worker ${worker} has run-host protocol ${reported || "unknown"}; expected ${AttemptChannel.PROTOCOL_VERSION}`,
|
|
154
|
-
cause: new Error("run-host protocol mismatch"),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
yield* verifyHarnesses(worker, brains, runHost, (command, args) => executeCommand("preflight", command, args));
|
|
215
|
+
// macOS has no /proc/meminfo and adds swap files as memory runs short.
|
|
216
|
+
yield* platform() === "darwin"
|
|
217
|
+
? verifyWorkerMemory(worker, maxConcurrentAttempts, new WorkerMemory({ totalBytes: totalmem(), swap: SwapOnDemand.make({}) }))
|
|
218
|
+
: verifyLinuxMemory(worker, maxConcurrentAttempts, executeCommand("preflight", "cat", ["/proc/meminfo"]));
|
|
219
|
+
yield* verifyHarnessReadiness(worker, Option.none(), brains, harnessReadiness, logins);
|
|
158
220
|
yield* Effect.logInfo("Worker preflight verified", {
|
|
159
221
|
worker,
|
|
160
222
|
host: "local",
|
|
161
223
|
gitVersion,
|
|
162
|
-
protocolVersion: AttemptChannel.PROTOCOL_VERSION,
|
|
163
224
|
});
|
|
225
|
+
return fullPreflight;
|
|
164
226
|
}, logPreflightFailure);
|
|
227
|
+
// Attempt commands receive only the allowlist and the Attempt's credential, never this
|
|
228
|
+
// process's environment, which contains the App private key (ADR 0011).
|
|
229
|
+
const attemptEnvironment = (workspace, credential) => ({
|
|
230
|
+
...credential.environment,
|
|
231
|
+
// Turbo writes its cache next to the git common dir, which for a linked worktree is the bare
|
|
232
|
+
// cache. One cache per Worker, outside the directory the startup sweep treats as cache storage.
|
|
233
|
+
TURBO_CACHE_DIR: path.join(workersRoot, workspace.worker, "turbo"),
|
|
234
|
+
});
|
|
235
|
+
const harvest = (workspace, credential, environment, windows) => harvestFacts(workspace.spec, harvestCommand("launch", workspace.worktree, credential, environment), windows).pipe(Effect.mapError((error) => new InfraFailure({
|
|
236
|
+
message: `Can't harvest the attempt's checkout: ${error.message}`,
|
|
237
|
+
cause: error,
|
|
238
|
+
})));
|
|
165
239
|
const prepare = Effect.fn("LocalExecutor.prepare")(function* (request) {
|
|
166
240
|
yield* Effect.annotateCurrentSpan({
|
|
167
241
|
attemptId: request.attemptId,
|
|
168
242
|
worker: request.worker,
|
|
169
243
|
host: "local",
|
|
170
244
|
repository: request.repository,
|
|
245
|
+
loop: request.loop,
|
|
246
|
+
permissions: request.permissions,
|
|
247
|
+
reattach: request.reattach,
|
|
171
248
|
});
|
|
172
|
-
yield*
|
|
173
|
-
const credential = yield*
|
|
174
|
-
Redacted.wipeUnsafe(token);
|
|
175
|
-
}), { interruptible: true });
|
|
249
|
+
yield* verifyHarnessReadiness(request.worker, Option.none(), [request.agent.brain], harnessReadiness, logins);
|
|
250
|
+
const credential = yield* credentials(request.repository, request.attemptId, request.permissions, baseEnvironment);
|
|
176
251
|
const workerRoot = path.join(workersRoot, request.worker);
|
|
177
252
|
const cache = path.join(workerRoot, "repos", cacheDirectoryName(request.repository));
|
|
178
253
|
const worktree = path.join(workerRoot, "attempts", safeSegment(request.attemptId));
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
254
|
+
yield* Effect.logInfo(request.reattach
|
|
255
|
+
? "Reattaching the Attempt to its checkout"
|
|
256
|
+
: "Preparing Attempt workspace", {
|
|
182
257
|
attemptId: request.attemptId,
|
|
183
258
|
worker: request.worker,
|
|
184
259
|
repository: request.repository,
|
|
185
260
|
revision: request.revision,
|
|
261
|
+
loop: request.loop,
|
|
262
|
+
permissions: request.permissions,
|
|
186
263
|
cache,
|
|
187
264
|
worktree,
|
|
188
265
|
});
|
|
189
266
|
const remove = (_operation, target) => fileSystem.remove(target, { recursive: true, force: true });
|
|
190
|
-
const
|
|
191
|
-
ensureRoots: Effect.andThen(fileSystem.makeDirectory(path.dirname(cache), { recursive: true }), fileSystem.makeDirectory(path.dirname(worktree), { recursive: true })),
|
|
267
|
+
const host = {
|
|
192
268
|
removePath: remove,
|
|
193
269
|
cacheExists: fileSystem.exists(cache),
|
|
270
|
+
git: credentialedGit(credential, request.commitAuthor),
|
|
271
|
+
};
|
|
272
|
+
const ops = {
|
|
273
|
+
...host,
|
|
274
|
+
ensureRoots: Effect.andThen(fileSystem.makeDirectory(path.dirname(cache), { recursive: true }), fileSystem.makeDirectory(path.dirname(worktree), { recursive: true })),
|
|
194
275
|
promoteStaging: fileSystem.rename(`${cache}.cloning`, cache),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const result = yield* collectCommand(spawner, operation, "git", args, commandOptions);
|
|
199
|
-
return yield* requireSuccess(operation, "local", "git", args, {
|
|
200
|
-
...result,
|
|
201
|
-
stdout: scrubbed(result.stdout, credential),
|
|
202
|
-
stderr: scrubbed(result.stderr, credential),
|
|
203
|
-
});
|
|
204
|
-
}),
|
|
276
|
+
checkout: linkedWorktree(host, { cache, worktree }, (target, content) => fileSystem
|
|
277
|
+
.makeDirectory(path.dirname(target), { recursive: true })
|
|
278
|
+
.pipe(Effect.andThen(fileSystem.writeFileString(target, content, { mode: 0o755 })))),
|
|
205
279
|
};
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
280
|
+
const acquire = Effect.gen(function* () {
|
|
281
|
+
if (!request.reattach)
|
|
282
|
+
return yield* fetches.withLock(request.repository)(provisionWorktree({
|
|
283
|
+
request,
|
|
284
|
+
url: repositoryUrl(request.repository),
|
|
285
|
+
host: "local",
|
|
286
|
+
layout: { cache, staging: `${cache}.cloning`, worktree },
|
|
287
|
+
ops,
|
|
288
|
+
}));
|
|
289
|
+
if (!(yield* fileSystem.exists(worktree)))
|
|
290
|
+
return yield* executorError("prepare", "The reattached attempt's checkout no longer exists", new Error(worktree));
|
|
291
|
+
const { spec } = yield* session.storedRequest(request.attemptId);
|
|
292
|
+
return yield* reattachWorktree({
|
|
293
|
+
request,
|
|
294
|
+
host: "local",
|
|
295
|
+
layout: { worktree },
|
|
296
|
+
ops,
|
|
297
|
+
spec,
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
const prepared = yield* acquire.pipe(Effect.mapError((error) => isExecutorError(error)
|
|
215
301
|
? error
|
|
216
|
-
: executorError("prepare", "
|
|
302
|
+
: executorError("prepare", "Can't prepare the attempt workspace", error)));
|
|
217
303
|
yield* Effect.logInfo("Attempt workspace ready", {
|
|
218
304
|
attemptId: request.attemptId,
|
|
219
|
-
baseCommit: prepared.spec.baseCommit,
|
|
305
|
+
baseCommit: prepared.workspace.spec.baseCommit,
|
|
306
|
+
resume: Option.map(prepared.workspace.spec.resume, ({ ref }) => ref),
|
|
307
|
+
reattach: request.reattach,
|
|
220
308
|
worktree,
|
|
221
309
|
});
|
|
310
|
+
const environment = childEnvironment(attemptEnvironment(prepared.workspace, credential), request.commitAuthor, prepared.gitHooks);
|
|
222
311
|
return {
|
|
223
|
-
workspace: prepared,
|
|
224
|
-
launch: (io) => session.launch(prepared, io,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// directory the startup sweep treats as cache storage.
|
|
239
|
-
const turboCache = path.join(workersRoot, prepared.worker, "turbo");
|
|
240
|
-
yield* Effect.logInfo("Launching the run host", {
|
|
241
|
-
attemptId,
|
|
242
|
-
worker: prepared.worker,
|
|
243
|
-
host: "local",
|
|
244
|
-
command: runHost.command,
|
|
245
|
-
worktree: prepared.worktree,
|
|
246
|
-
turboCache,
|
|
247
|
-
});
|
|
248
|
-
const handle = yield* spawner.spawn(ChildProcess.make(runHost.command, runHost.args, {
|
|
249
|
-
cwd: prepared.worktree,
|
|
250
|
-
env: {
|
|
251
|
-
...credentialEnvironment(baseEnvironment, credential),
|
|
252
|
-
TURBO_CACHE_DIR: turboCache,
|
|
253
|
-
},
|
|
254
|
-
extendEnv: false,
|
|
255
|
-
stdin: { stream: Stream.fromQueue(input), endOnDone: true },
|
|
256
|
-
}));
|
|
257
|
-
return {
|
|
258
|
-
handle,
|
|
259
|
-
host: "local",
|
|
260
|
-
scrub: (text) => scrubbed(text, credential),
|
|
312
|
+
workspace: prepared.workspace,
|
|
313
|
+
launch: (io) => session.launch(prepared.workspace, io, {
|
|
314
|
+
host: new LocalProcess(),
|
|
315
|
+
machine: Option.none(),
|
|
316
|
+
environment,
|
|
317
|
+
// Local Workers have no Gateway; every harness uses its own login.
|
|
318
|
+
gatewayCredential: Effect.succeedNone,
|
|
319
|
+
harvest: (windows) => harvest(prepared.workspace, credential, environment, windows),
|
|
320
|
+
harvestComments: (windows) => harvestPostedComments(prepared.workspace.spec, harvestCommand("launch", prepared.workspace.worktree, credential, environment), windows),
|
|
321
|
+
keepForReattach: prepared.keepForReattach,
|
|
322
|
+
keepUntilStopped: prepared.keepUntilStopped,
|
|
323
|
+
reconcileKept: (record) => reconcileKept(request, worktree, record),
|
|
324
|
+
}),
|
|
325
|
+
salvaged: prepared.salvaged,
|
|
326
|
+
recordSalvage: prepared.recordSalvage,
|
|
261
327
|
};
|
|
262
|
-
});
|
|
263
|
-
const session = yield* runHostSessionFor({ interruptGrace });
|
|
328
|
+
}, session.prepare);
|
|
264
329
|
return Executor.of({
|
|
265
330
|
kind: "local",
|
|
331
|
+
recover: (attempt, record, commitAuthor) => recover(attempt, record, commitAuthor).pipe(Effect.catchTag("PlatformError", (cause) => Effect.fail(executorError("cleanup", "Can't reconcile the local salvage", cause)))),
|
|
266
332
|
cancel: session.cancel,
|
|
267
333
|
intervene: session.intervene,
|
|
334
|
+
isLive: session.isLive,
|
|
335
|
+
outcome: session.outcome,
|
|
268
336
|
preflight,
|
|
269
337
|
prepare,
|
|
338
|
+
sweep: Effect.void,
|
|
270
339
|
});
|
|
271
340
|
});
|
|
272
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* The executor for the local machine. Each Attempt runs in its own checkout with its Pi parent
|
|
343
|
+
* as a durable task in this process (`AttemptRuntime`), so a live Attempt reattaches after an
|
|
344
|
+
* orchestrator restart. Requires Node.
|
|
345
|
+
*/
|
|
273
346
|
export const layer = (options) => Layer.effect(Executor, makeLocalExecutor(options));
|
|
274
347
|
//# sourceMappingURL=local.js.map
|