@usine/executor 0.0.1 → 0.2.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/README.md +85 -2
- package/dist/child-command.d.ts +21 -0
- package/dist/child-command.d.ts.map +1 -0
- package/dist/child-command.js +50 -0
- package/dist/child-command.js.map +1 -0
- package/dist/exe-dev/channel.d.ts +11 -0
- package/dist/exe-dev/channel.d.ts.map +1 -0
- package/dist/exe-dev/channel.js +41 -0
- package/dist/exe-dev/channel.js.map +1 -0
- package/dist/exe-dev/commands.d.ts +22 -0
- package/dist/exe-dev/commands.d.ts.map +1 -0
- package/dist/exe-dev/commands.js +66 -0
- package/dist/exe-dev/commands.js.map +1 -0
- package/dist/exe-dev/exe-dev.d.ts +8 -0
- package/dist/exe-dev/exe-dev.d.ts.map +1 -0
- package/dist/exe-dev/exe-dev.js +153 -0
- package/dist/exe-dev/exe-dev.js.map +1 -0
- package/dist/exe-dev/grants.d.ts +8 -0
- package/dist/exe-dev/grants.d.ts.map +1 -0
- package/dist/exe-dev/grants.js +104 -0
- package/dist/exe-dev/grants.js.map +1 -0
- package/dist/exe-dev/model.d.ts +43 -0
- package/dist/exe-dev/model.d.ts.map +1 -0
- package/dist/exe-dev/model.js +26 -0
- package/dist/exe-dev/model.js.map +1 -0
- package/dist/exe-dev/preflight.d.ts +10 -0
- package/dist/exe-dev/preflight.d.ts.map +1 -0
- package/dist/exe-dev/preflight.js +82 -0
- package/dist/exe-dev/preflight.js.map +1 -0
- package/dist/exe-dev/shell.d.ts +12 -0
- package/dist/exe-dev/shell.d.ts.map +1 -0
- package/dist/exe-dev/shell.js +31 -0
- package/dist/exe-dev/shell.js.map +1 -0
- package/dist/exe-dev/sweep.d.ts +10 -0
- package/dist/exe-dev/sweep.d.ts.map +1 -0
- package/dist/exe-dev/sweep.js +117 -0
- package/dist/exe-dev/sweep.js.map +1 -0
- package/dist/executor.d.ts +118 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +98 -0
- package/dist/executor.js.map +1 -0
- package/dist/harness-preflight.d.ts +6 -0
- package/dist/harness-preflight.d.ts.map +1 -0
- package/dist/harness-preflight.js +50 -0
- package/dist/harness-preflight.js.map +1 -0
- package/dist/invalid-frame.d.ts +17 -0
- package/dist/invalid-frame.d.ts.map +1 -0
- package/dist/invalid-frame.js +111 -0
- package/dist/invalid-frame.js.map +1 -0
- package/dist/keyed-lock.d.ts +11 -0
- package/dist/keyed-lock.d.ts.map +1 -0
- package/dist/keyed-lock.js +21 -0
- package/dist/keyed-lock.js.map +1 -0
- package/dist/local/local.d.ts +20 -0
- package/dist/local/local.d.ts.map +1 -0
- package/dist/local/local.js +273 -0
- package/dist/local/local.js.map +1 -0
- package/dist/prepared-attempt.d.ts +5 -0
- package/dist/prepared-attempt.d.ts.map +1 -0
- package/dist/prepared-attempt.js +40 -0
- package/dist/prepared-attempt.js.map +1 -0
- package/dist/provision.d.ts +43 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +147 -0
- package/dist/provision.js.map +1 -0
- package/dist/run-host-session.d.ts +26 -0
- package/dist/run-host-session.d.ts.map +1 -0
- package/dist/run-host-session.js +241 -0
- package/dist/run-host-session.js.map +1 -0
- package/package.json +21 -5
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
3
|
+
import { WorkerName } from "@usine/core/worker";
|
|
4
|
+
import { Effect, Ref, Scope } from "effect";
|
|
5
|
+
import { CredentialUnavailableError } from "../executor.js";
|
|
6
|
+
import { keyedLockFor } from "../keyed-lock.js";
|
|
7
|
+
import { CredentialGrantMinutes, } from "./model.js";
|
|
8
|
+
const grantKey = (worker, integration) => `${worker} ${integration}`;
|
|
9
|
+
export const credentialGrantsFor = Effect.fn("ExeDevExecutor.credentialGrantsFor")(function* (options, commands) {
|
|
10
|
+
const locks = yield* keyedLockFor();
|
|
11
|
+
const counts = yield* Ref.make(new Map());
|
|
12
|
+
const grantMinutes = options.credentialGrantMinutes ?? CredentialGrantMinutes.make(65);
|
|
13
|
+
const acquire = Effect.fn("ExeDevExecutor.acquireGrant")(function* (attemptId, worker, repository, integration) {
|
|
14
|
+
yield* Effect.annotateCurrentSpan({ attemptId, worker, repository, integration });
|
|
15
|
+
const scope = yield* Effect.scope;
|
|
16
|
+
const key = grantKey(worker, integration);
|
|
17
|
+
yield* locks.withLock(key)(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
18
|
+
const current = (yield* Ref.get(counts)).get(key) ?? 0;
|
|
19
|
+
// Own a reference before attempting the attachment: a failed SSH reply
|
|
20
|
+
// may still mean the edge accepted it. This reservation is released once
|
|
21
|
+
// by this scope, and never borrows a sibling's reference on interruption.
|
|
22
|
+
yield* Ref.update(counts, (currentCounts) => new Map(currentCounts).set(key, current + 1));
|
|
23
|
+
yield* Scope.addFinalizer(scope, release(attemptId, worker, integration).pipe(Effect.tapError((error) => Effect.logError("Attempt credential cleanup failed", {
|
|
24
|
+
attemptId,
|
|
25
|
+
worker,
|
|
26
|
+
repository,
|
|
27
|
+
integration,
|
|
28
|
+
message: error.message,
|
|
29
|
+
})), Effect.ignore));
|
|
30
|
+
yield* restore(commands
|
|
31
|
+
.lobby("prepare", [
|
|
32
|
+
"integrations",
|
|
33
|
+
"attach",
|
|
34
|
+
integration,
|
|
35
|
+
`vm:${worker}`,
|
|
36
|
+
"--for",
|
|
37
|
+
`${grantMinutes}m`,
|
|
38
|
+
])
|
|
39
|
+
.pipe(Effect.mapError((cause) => new CredentialUnavailableError({
|
|
40
|
+
repository,
|
|
41
|
+
message: `Could not attach exe.dev GitHub integration ${integration}`,
|
|
42
|
+
cause,
|
|
43
|
+
}))));
|
|
44
|
+
yield* Effect.logInfo("Attached or refreshed time-boxed exe.dev GitHub integration", {
|
|
45
|
+
attemptId,
|
|
46
|
+
worker,
|
|
47
|
+
repository,
|
|
48
|
+
integration,
|
|
49
|
+
grantMinutes,
|
|
50
|
+
activeGrants: current + 1,
|
|
51
|
+
});
|
|
52
|
+
})));
|
|
53
|
+
});
|
|
54
|
+
const release = Effect.fn("ExeDevExecutor.releaseGrant")(function* (attemptId, worker, integration) {
|
|
55
|
+
yield* Effect.annotateCurrentSpan({ attemptId, worker, integration });
|
|
56
|
+
const key = grantKey(worker, integration);
|
|
57
|
+
yield* locks.withLock(key)(Effect.gen(function* () {
|
|
58
|
+
const current = (yield* Ref.get(counts)).get(key) ?? 0;
|
|
59
|
+
if (current === 0) {
|
|
60
|
+
yield* Effect.logDebug("Credential cleanup was already complete", {
|
|
61
|
+
attemptId,
|
|
62
|
+
worker,
|
|
63
|
+
integration,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (current > 1) {
|
|
68
|
+
yield* Ref.update(counts, (currentCounts) => {
|
|
69
|
+
const next = new Map(currentCounts);
|
|
70
|
+
next.set(key, current - 1);
|
|
71
|
+
return next;
|
|
72
|
+
});
|
|
73
|
+
yield* Effect.logDebug("Kept the shared exe.dev GitHub integration", {
|
|
74
|
+
attemptId,
|
|
75
|
+
worker,
|
|
76
|
+
integration,
|
|
77
|
+
activeGrants: current - 1,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
yield* commands
|
|
82
|
+
.lobby("cleanup", ["integrations", "detach", integration, `vm:${worker}`])
|
|
83
|
+
.pipe(Effect.tapError((error) => Effect.logWarning("Could not detach the exe.dev GitHub integration", {
|
|
84
|
+
attemptId,
|
|
85
|
+
worker,
|
|
86
|
+
integration,
|
|
87
|
+
grantMinutes,
|
|
88
|
+
lapse: `the grant stays attached until its ${grantMinutes}m time-box expires`,
|
|
89
|
+
message: error.message,
|
|
90
|
+
})), Effect.ensuring(Ref.update(counts, (currentCounts) => {
|
|
91
|
+
const next = new Map(currentCounts);
|
|
92
|
+
next.delete(key);
|
|
93
|
+
return next;
|
|
94
|
+
})));
|
|
95
|
+
yield* Effect.logInfo("Detached exe.dev GitHub integration", {
|
|
96
|
+
attemptId,
|
|
97
|
+
worker,
|
|
98
|
+
integration,
|
|
99
|
+
});
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
return { acquire };
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../../src/exe-dev/grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Schema, type Effect } from "effect";
|
|
2
|
+
import type { RunHostCommand } from "../executor.ts";
|
|
3
|
+
/** The name of one exe.dev edge-injected integration. */
|
|
4
|
+
export declare const IntegrationName: Schema.brand<Schema.String, "ExeDevIntegrationName">;
|
|
5
|
+
export type IntegrationName = typeof IntegrationName.Type;
|
|
6
|
+
/**
|
|
7
|
+
* An SSH destination handed to the local `ssh` client as argv, so anything
|
|
8
|
+
* that could read as an option (`-oProxyCommand=...`) is unrepresentable.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SshDestination: Schema.brand<Schema.String, "ExeDevSshDestination">;
|
|
11
|
+
export type SshDestination = typeof SshDestination.Type;
|
|
12
|
+
/** An absolute remote home directory that cannot smuggle bytes past shell quoting. */
|
|
13
|
+
export declare const HomePath: Schema.brand<Schema.String, "ExeDevHomePath">;
|
|
14
|
+
export type HomePath = typeof HomePath.Type;
|
|
15
|
+
declare const GitHubIntegration_base: Schema.Class<GitHubIntegration, Schema.Struct<{
|
|
16
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
17
|
+
readonly name: Schema.brand<Schema.String, "ExeDevIntegrationName">;
|
|
18
|
+
}>, {}>;
|
|
19
|
+
/** One standing exe.dev GitHub integration; only its VM attachment is per Attempt. */
|
|
20
|
+
export declare class GitHubIntegration extends GitHubIntegration_base {
|
|
21
|
+
}
|
|
22
|
+
/** How long an exe.dev Worker's repository credential attachment may remain active, in minutes. */
|
|
23
|
+
export declare const CredentialGrantMinutes: Schema.brand<Schema.Int, "ExeDevCredentialGrantMinutes">;
|
|
24
|
+
export type CredentialGrantMinutes = typeof CredentialGrantMinutes.Type;
|
|
25
|
+
/** Repository access and run-host settings for the configured exe.dev Workers. */
|
|
26
|
+
export interface ExeDevExecutorOptions {
|
|
27
|
+
/** Existing native GitHub integrations, one per repository this Worker may receive. */
|
|
28
|
+
readonly githubIntegrations: ReadonlyArray<GitHubIntegration>;
|
|
29
|
+
/** Installed in the baked image; defaults to the published worker executable. */
|
|
30
|
+
readonly runHost?: RunHostCommand;
|
|
31
|
+
/** Local SSH executable and its control-plane-only arguments. */
|
|
32
|
+
readonly ssh?: RunHostCommand;
|
|
33
|
+
/** Time-box for the edge credential attachment. Defaults to 65 minutes. */
|
|
34
|
+
readonly credentialGrantMinutes?: CredentialGrantMinutes;
|
|
35
|
+
/** Grace for delivering a stop request, then for the run host to exit; each wait is bounded separately. Defaults to 10 seconds. */
|
|
36
|
+
readonly interruptGrace?: Parameters<typeof Effect.timeoutOption>[1];
|
|
37
|
+
}
|
|
38
|
+
export interface VmState {
|
|
39
|
+
readonly home: HomePath;
|
|
40
|
+
readonly sshDest: SshDestination;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/exe-dev/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,yDAAyD;AACzD,eAAO,MAAM,eAAe,sDAIiB,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,cAAc,qDAIiB,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,sFAAsF;AACtF,eAAO,MAAM,QAAQ,+CAIiB,CAAC;AACvC,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;;;;;AAE5C,sFAAsF;AACtF,qBAAa,iBAAkB,SAAQ,sBAKrC;CAAG;AAEL,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,0DAElC,CAAC;AACF,MAAM,MAAM,sBAAsB,GAAG,OAAO,sBAAsB,CAAC,IAAI,CAAC;AAExE,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,uFAAuF;IACvF,QAAQ,CAAC,kBAAkB,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IACzD,mIAAmI;IACnI,QAAQ,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
/** The name of one exe.dev edge-injected integration. */
|
|
4
|
+
export const IntegrationName = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/u, {
|
|
5
|
+
message: "Expected an exe.dev integration name safe for the lobby command boundary",
|
|
6
|
+
})).pipe(Schema.brand("ExeDevIntegrationName"));
|
|
7
|
+
/**
|
|
8
|
+
* An SSH destination handed to the local `ssh` client as argv, so anything
|
|
9
|
+
* that could read as an option (`-oProxyCommand=...`) is unrepresentable.
|
|
10
|
+
*/
|
|
11
|
+
export const SshDestination = Schema.String.check(Schema.isPattern(/^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/u, {
|
|
12
|
+
message: "Expected an exe.dev SSH hostname, optionally prefixed by a user",
|
|
13
|
+
})).pipe(Schema.brand("ExeDevSshDestination"));
|
|
14
|
+
/** An absolute remote home directory that cannot smuggle bytes past shell quoting. */
|
|
15
|
+
export const HomePath = Schema.String.check(Schema.isPattern(/^\/[^\0\r\n]*$/u, {
|
|
16
|
+
message: "Expected an absolute remote home directory without control bytes",
|
|
17
|
+
})).pipe(Schema.brand("ExeDevHomePath"));
|
|
18
|
+
/** One standing exe.dev GitHub integration; only its VM attachment is per Attempt. */
|
|
19
|
+
export class GitHubIntegration extends Schema.Class("@usine/executor/ExeDevGitHubIntegration")({
|
|
20
|
+
repository: RepositoryName,
|
|
21
|
+
name: IntegrationName,
|
|
22
|
+
}) {
|
|
23
|
+
}
|
|
24
|
+
/** How long an exe.dev Worker's repository credential attachment may remain active, in minutes. */
|
|
25
|
+
export const CredentialGrantMinutes = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).pipe(Schema.brand("ExeDevCredentialGrantMinutes"));
|
|
26
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/exe-dev/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAG7C,yDAAyD;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAChD,MAAM,CAAC,SAAS,CAAC,uCAAuC,EAAE;IACxD,OAAO,EAAE,0EAA0E;CACpF,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;AAG9C;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAC/C,MAAM,CAAC,SAAS,CAAC,mEAAmE,EAAE;IACpF,OAAO,EAAE,iEAAiE;CAC3E,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;AAG7C,sFAAsF;AACtF,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CACzC,MAAM,CAAC,SAAS,CAAC,iBAAiB,EAAE;IAClC,OAAO,EAAE,kEAAkE;CAC5E,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAGvC,sFAAsF;AACtF,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,KAAK,CACjD,yCAAyC,CAC1C,CAAC;IACA,UAAU,EAAE,cAAc;IAC1B,IAAI,EAAE,eAAe;CACtB,CAAC;CAAG;AAEL,mGAAmG;AACnG,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC3F,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAC7C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BrainSpec } from "@usine/core/agent/brain";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { ExecutorOperationError, type RunHostCommand, WorkerCapabilityError } from "../executor.ts";
|
|
4
|
+
import type { ExeDevCommands } from "./commands.ts";
|
|
5
|
+
import { type ExeDevExecutorOptions, type VmState } from "./model.ts";
|
|
6
|
+
export declare const preflightFor: (options: ExeDevExecutorOptions, commands: ExeDevCommands, runHost: RunHostCommand) => Effect.Effect<{
|
|
7
|
+
preflight: (worker: string & import("effect/Brand").Brand<"WorkerName">, brains: readonly BrainSpec[]) => Effect.Effect<undefined, import("../executor.ts").CredentialUnavailableError | ExecutorOperationError | WorkerCapabilityError, never>;
|
|
8
|
+
vmFor: (worker: string & import("effect/Brand").Brand<"WorkerName">) => Effect.Effect<VmState, ExecutorOperationError | WorkerCapabilityError, never>;
|
|
9
|
+
}, never, never>;
|
|
10
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/exe-dev/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGzD,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC7C,OAAO,EACL,sBAAsB,EAEtB,KAAK,cAAc,EACnB,qBAAqB,EAEtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAA4B,KAAK,qBAAqB,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAoBhG,eAAO,MAAM,YAAY;;;gBA2JvB,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as AttemptChannel from "@usine/core/attempt/channel";
|
|
2
|
+
import { WorkerName } from "@usine/core/worker";
|
|
3
|
+
import { Effect, Ref, Schema } from "effect";
|
|
4
|
+
import { ExecutorOperationError, logPreflightFailure, WorkerCapabilityError, } from "../executor.js";
|
|
5
|
+
import { verifyHarnesses } from "../harness-preflight.js";
|
|
6
|
+
import { HomePath, SshDestination } from "./model.js";
|
|
7
|
+
const MINIMUM_NODE_MAJOR = 22;
|
|
8
|
+
const Vm = Schema.Struct({
|
|
9
|
+
vmName: Schema.NonEmptyString,
|
|
10
|
+
sshDest: SshDestination,
|
|
11
|
+
status: Schema.NonEmptyString,
|
|
12
|
+
}).pipe(Schema.encodeKeys({ vmName: "vm_name", sshDest: "ssh_dest" }));
|
|
13
|
+
const VmList = Schema.Struct({ vms: Schema.Array(Vm) });
|
|
14
|
+
const decodeVmList = Schema.decodeUnknownEffect(Schema.fromJsonString(VmList));
|
|
15
|
+
const decodeHome = Schema.decodeUnknownEffect(HomePath);
|
|
16
|
+
const capabilityError = (worker, capability, message, cause) => new WorkerCapabilityError({ worker, capability, message, cause });
|
|
17
|
+
export const preflightFor = Effect.fn("ExeDevExecutor.preflightFor")(function* (options, commands, runHost) {
|
|
18
|
+
const workers = yield* Ref.make(new Map());
|
|
19
|
+
const resolveVm = Effect.fn("ExeDevExecutor.resolveVm")(function* (worker) {
|
|
20
|
+
const response = yield* commands.lobbyJson("preflight", ["ls", worker]);
|
|
21
|
+
const decoded = yield* decodeVmList(response).pipe(Effect.mapError((cause) => capabilityError(worker, "machine", `exe.dev returned invalid machine metadata for Worker ${worker}`, cause)));
|
|
22
|
+
const vm = decoded.vms.find(({ vmName }) => vmName === worker);
|
|
23
|
+
if (vm === undefined) {
|
|
24
|
+
return yield* capabilityError(worker, "machine", `exe.dev has no VM named ${worker}`, new Error("worker VM missing"));
|
|
25
|
+
}
|
|
26
|
+
if (vm.status !== "running") {
|
|
27
|
+
return yield* capabilityError(worker, "machine", `exe.dev VM ${worker} is ${vm.status}, not running`, new Error(`VM status: ${vm.status}`));
|
|
28
|
+
}
|
|
29
|
+
const reported = (yield* commands.remote("preflight", vm.sshDest, "printenv", ["HOME"])).trim();
|
|
30
|
+
const home = yield* decodeHome(reported).pipe(Effect.mapError((cause) => capabilityError(worker, "toolchain", `exe.dev VM ${worker} returned an invalid home directory`, cause)));
|
|
31
|
+
return { home, sshDest: vm.sshDest };
|
|
32
|
+
});
|
|
33
|
+
const asCapability = (worker, capability, message, effect) => effect.pipe(Effect.mapError((cause) => capabilityError(worker, capability, message, cause)));
|
|
34
|
+
const preflight = Effect.fn("ExeDevExecutor.preflight")(function* (worker, brains) {
|
|
35
|
+
const vm = yield* resolveVm(worker).pipe(Effect.catchTag("ExecutorOperationError", (cause) => capabilityError(worker, "machine", `Could not reach exe.dev Worker ${worker}`, cause)));
|
|
36
|
+
yield* Effect.annotateCurrentSpan({ worker, host: vm.sshDest });
|
|
37
|
+
const gitVersion = (yield* asCapability(worker, "toolchain", `Worker ${worker} cannot run Git`, commands.remote("preflight", vm.sshDest, "git", ["--version"]))).trim();
|
|
38
|
+
if (!gitVersion.startsWith("git version ")) {
|
|
39
|
+
return yield* capabilityError(worker, "toolchain", `Worker ${worker} returned an unrecognized Git version`, new Error(gitVersion));
|
|
40
|
+
}
|
|
41
|
+
const nodeVersion = (yield* asCapability(worker, "toolchain", `Worker ${worker} cannot run Node.js`, commands.remote("preflight", vm.sshDest, "node", ["--version"]))).trim();
|
|
42
|
+
const nodeMajor = Number(/^v(?<major>\d+)/u.exec(nodeVersion)?.groups?.major ?? Number.NaN);
|
|
43
|
+
if (!Number.isSafeInteger(nodeMajor) || nodeMajor < MINIMUM_NODE_MAJOR) {
|
|
44
|
+
return yield* capabilityError(worker, "toolchain", `Worker ${worker} has Node.js ${nodeVersion || "unknown"}; expected ${MINIMUM_NODE_MAJOR}+`, new Error("Node.js version mismatch"));
|
|
45
|
+
}
|
|
46
|
+
const protocol = (yield* asCapability(worker, "run-host", `Worker ${worker} cannot run ${runHost.command}`, commands.remote("preflight", vm.sshDest, runHost.command, [
|
|
47
|
+
...runHost.args,
|
|
48
|
+
"--protocol-version",
|
|
49
|
+
]))).trim();
|
|
50
|
+
if (protocol !== String(AttemptChannel.PROTOCOL_VERSION)) {
|
|
51
|
+
return yield* capabilityError(worker, "run-host", `Worker ${worker} has run-host protocol ${protocol || "unknown"}; expected ${AttemptChannel.PROTOCOL_VERSION}`, new Error("run-host protocol mismatch"));
|
|
52
|
+
}
|
|
53
|
+
yield* verifyHarnesses(worker, brains, runHost, (command, args) => commands.remote("preflight", vm.sshDest, command, args));
|
|
54
|
+
for (const integration of options.githubIntegrations) {
|
|
55
|
+
yield* asCapability(worker, "credentials", `exe.dev GitHub integration ${integration.name} is not ready for ${integration.repository}`, commands.lobby("preflight", ["integrations", "test", integration.name]));
|
|
56
|
+
}
|
|
57
|
+
yield* Ref.update(workers, (current) => {
|
|
58
|
+
const next = new Map(current);
|
|
59
|
+
next.set(worker, vm);
|
|
60
|
+
return next;
|
|
61
|
+
});
|
|
62
|
+
yield* Effect.logInfo("exe.dev Worker preflight verified", {
|
|
63
|
+
worker,
|
|
64
|
+
host: vm.sshDest,
|
|
65
|
+
gitVersion,
|
|
66
|
+
nodeVersion,
|
|
67
|
+
protocolVersion: AttemptChannel.PROTOCOL_VERSION,
|
|
68
|
+
harnesses: [...new Set(brains.map(({ harness }) => harness))].join(","),
|
|
69
|
+
integrations: options.githubIntegrations.length,
|
|
70
|
+
});
|
|
71
|
+
}, logPreflightFailure);
|
|
72
|
+
// Both resolveVm failures are members of the ExecutorError union prepare
|
|
73
|
+
// reports, so a stopped VM keeps its typed capability shape.
|
|
74
|
+
const vmFor = Effect.fn("ExeDevExecutor.vmFor")(function* (worker) {
|
|
75
|
+
const cached = (yield* Ref.get(workers)).get(worker);
|
|
76
|
+
if (cached !== undefined)
|
|
77
|
+
return cached;
|
|
78
|
+
return yield* resolveVm(worker);
|
|
79
|
+
});
|
|
80
|
+
return { preflight, vmFor };
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/exe-dev/preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Quotes a command and its arguments into one string safe for OpenSSH's remote-command boundary. */
|
|
2
|
+
export declare const shellCommand: (command: string, args: ReadonlyArray<string>) => string;
|
|
3
|
+
/** The pid file sits beside the worktree so the sweep reclaims both together. */
|
|
4
|
+
export declare const runHostPidFile: (worktree: string) => string;
|
|
5
|
+
/** The remote command that starts the run host in its worktree with the edge GitHub host selected. */
|
|
6
|
+
export declare const runHostShellCommand: (worktree: string, command: string, args: ReadonlyArray<string>) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Confirms a recorded run host is dead before workspace removal: TERM, a
|
|
9
|
+
* bounded wait, then KILL. `$1` is the pid file; stdout reports the outcome.
|
|
10
|
+
*/
|
|
11
|
+
export declare const RUN_HOST_KILL_SCRIPT: string;
|
|
12
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/exe-dev/shell.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,eAAO,MAAM,YAAY,YAAa,MAAM,QAAQ,aAAa,CAAC,MAAM,CAAC,KAAG,MACzB,CAAC;AAEpD,iFAAiF;AACjF,eAAO,MAAM,cAAc,aAAc,MAAM,KAAG,MAA2B,CAAC;AAE9E,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,aACpB,MAAM,WACP,MAAM,QACT,aAAa,CAAC,MAAM,CAAC,KAC1B,MAIsJ,CAAC;AAE1J;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAczB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const quote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
2
|
+
/** Quotes a command and its arguments into one string safe for OpenSSH's remote-command boundary. */
|
|
3
|
+
export const shellCommand = (command, args) => `exec ${[command, ...args].map(quote).join(" ")}`;
|
|
4
|
+
/** The pid file sits beside the worktree so the sweep reclaims both together. */
|
|
5
|
+
export const runHostPidFile = (worktree) => `${worktree}.pid`;
|
|
6
|
+
/** The remote command that starts the run host in its worktree with the edge GitHub host selected. */
|
|
7
|
+
export const runHostShellCommand = (worktree, command, args) =>
|
|
8
|
+
// `$$` names the remote shell that `exec` replaces with the run host, so the
|
|
9
|
+
// pid file identifies the run host itself. Cleanup uses it to confirm the
|
|
10
|
+
// process is gone before removing the worktree.
|
|
11
|
+
`cd ${quote(worktree)} && export GH_HOST=github.int.exe.xyz && printf '%s' "$$" > ${quote(runHostPidFile(worktree))} && ${shellCommand(command, args)}`;
|
|
12
|
+
/**
|
|
13
|
+
* Confirms a recorded run host is dead before workspace removal: TERM, a
|
|
14
|
+
* bounded wait, then KILL. `$1` is the pid file; stdout reports the outcome.
|
|
15
|
+
*/
|
|
16
|
+
export const RUN_HOST_KILL_SCRIPT = `
|
|
17
|
+
if test -f "$1"; then
|
|
18
|
+
pid=$(cat "$1")
|
|
19
|
+
if kill -TERM "$pid" 2>/dev/null; then
|
|
20
|
+
n=0
|
|
21
|
+
while kill -0 "$pid" 2>/dev/null && [ "$n" -lt 10 ]; do sleep 0.5; n=$((n+1)); done
|
|
22
|
+
if kill -0 "$pid" 2>/dev/null; then kill -KILL "$pid" 2>/dev/null; echo killed; else echo stopped; fi
|
|
23
|
+
else
|
|
24
|
+
echo absent
|
|
25
|
+
fi
|
|
26
|
+
rm -f "$1"
|
|
27
|
+
else
|
|
28
|
+
echo no-pidfile
|
|
29
|
+
fi
|
|
30
|
+
`.trim();
|
|
31
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/exe-dev/shell.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAE/E,qGAAqG;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAe,EAAE,IAA2B,EAAU,EAAE,CACnF,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAEpD,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,GAAG,QAAQ,MAAM,CAAC;AAE9E,sGAAsG;AACtG,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,QAAgB,EAChB,OAAe,EACf,IAA2B,EACnB,EAAE;AACV,6EAA6E;AAC7E,0EAA0E;AAC1E,gDAAgD;AAChD,MAAM,KAAK,CAAC,QAAQ,CAAC,+DAA+D,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAE1J;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;CAcnC,CAAC,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { ExeDevCommands } from "./commands.ts";
|
|
3
|
+
import type { VmState } from "./model.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Reclaims crash leftovers once per Worker before this process starts work on
|
|
6
|
+
* its VM. Each removal is isolated and each failure is logged, so one immovable
|
|
7
|
+
* leftover never blocks the sweep's siblings or a future prepare.
|
|
8
|
+
*/
|
|
9
|
+
export declare const abandonedWorktreeSweepFor: (commands: ExeDevCommands) => Effect.Effect<(attemptId: string & import("effect/Brand").Brand<"AttemptId">, worker: string & import("effect/Brand").Brand<"WorkerName">, vm: VmState, root: string) => Effect.Effect<void, never, never>, never, never>;
|
|
10
|
+
//# sourceMappingURL=sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/exe-dev/sweep.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAwC1C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,yPAiIrC,CAAC"}
|
|
@@ -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
|
+
const directChildren = (parent, output) => Schema.decodeUnknownEffect(Schema.Array(directChildSchema(parent)))(output.split("\0").filter((value) => value.length > 0)).pipe(Effect.mapError((cause) => new ExecutorOperationError({
|
|
19
|
+
operation: "prepare",
|
|
20
|
+
message: `Worker returned an invalid path while sweeping ${parent}`,
|
|
21
|
+
cause,
|
|
22
|
+
})));
|
|
23
|
+
/**
|
|
24
|
+
* Reclaims crash leftovers once per Worker before this process starts work on
|
|
25
|
+
* its VM. Each removal is isolated and each failure is logged, so one immovable
|
|
26
|
+
* leftover never blocks the sweep's siblings or a future prepare.
|
|
27
|
+
*/
|
|
28
|
+
export const abandonedWorktreeSweepFor = Effect.fn("ExeDevExecutor.abandonedWorktreeSweepFor")(function* (commands) {
|
|
29
|
+
const locks = yield* keyedLockFor();
|
|
30
|
+
const swept = yield* Ref.make(new Set());
|
|
31
|
+
const sweep = Effect.fn("ExeDevExecutor.sweepAbandonedWorktrees")(function* (attemptId, worker, vm, root) {
|
|
32
|
+
yield* Effect.annotateCurrentSpan({ attemptId, worker, host: vm.sshDest });
|
|
33
|
+
yield* locks.withLock(worker)(Effect.gen(function* () {
|
|
34
|
+
if ((yield* Ref.get(swept)).has(worker)) {
|
|
35
|
+
yield* Effect.logDebug("exe.dev abandoned-worktree sweep was already complete", {
|
|
36
|
+
attemptId,
|
|
37
|
+
worker,
|
|
38
|
+
host: vm.sshDest,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Mark the sweep done before it runs. A partial sweep must not retry in
|
|
43
|
+
// front of every prepare; the next process start reclaims what failed.
|
|
44
|
+
yield* Ref.update(swept, (current) => new Set(current).add(worker));
|
|
45
|
+
const attemptsRoot = `${root}/attempts`;
|
|
46
|
+
const reposRoot = `${root}/repos`;
|
|
47
|
+
yield* Effect.logInfo("Sweeping abandoned exe.dev Attempt worktrees", {
|
|
48
|
+
attemptId,
|
|
49
|
+
worker,
|
|
50
|
+
host: vm.sshDest,
|
|
51
|
+
});
|
|
52
|
+
yield* commands.remote("prepare", vm.sshDest, "mkdir", ["-p", attemptsRoot, reposRoot]);
|
|
53
|
+
const attempts = yield* commands
|
|
54
|
+
.remote("prepare", vm.sshDest, "find", [
|
|
55
|
+
attemptsRoot,
|
|
56
|
+
"-mindepth",
|
|
57
|
+
"1",
|
|
58
|
+
"-maxdepth",
|
|
59
|
+
"1",
|
|
60
|
+
"-print0",
|
|
61
|
+
])
|
|
62
|
+
.pipe(Effect.flatMap((output) => directChildren(attemptsRoot, output)));
|
|
63
|
+
const failures = yield* Ref.make(0);
|
|
64
|
+
yield* Effect.forEach(attempts, (worktree) => Effect.logInfo("Sweeping abandoned exe.dev Attempt worktree", {
|
|
65
|
+
attemptId,
|
|
66
|
+
worker,
|
|
67
|
+
host: vm.sshDest,
|
|
68
|
+
worktree,
|
|
69
|
+
}).pipe(Effect.andThen(commands.remote("prepare", vm.sshDest, "rm", ["-rf", worktree])), Effect.tapError((error) => Effect.logWarning("Could not remove an abandoned exe.dev worktree", {
|
|
70
|
+
attemptId,
|
|
71
|
+
worker,
|
|
72
|
+
host: vm.sshDest,
|
|
73
|
+
worktree,
|
|
74
|
+
message: error.message,
|
|
75
|
+
}).pipe(Effect.andThen(Ref.update(failures, (count) => count + 1)))), Effect.ignore), { concurrency: SWEEP_CONCURRENCY, discard: true });
|
|
76
|
+
const caches = yield* commands
|
|
77
|
+
.remote("prepare", vm.sshDest, "find", [
|
|
78
|
+
reposRoot,
|
|
79
|
+
"-mindepth",
|
|
80
|
+
"1",
|
|
81
|
+
"-maxdepth",
|
|
82
|
+
"1",
|
|
83
|
+
"-type",
|
|
84
|
+
"d",
|
|
85
|
+
"-name",
|
|
86
|
+
"*.git",
|
|
87
|
+
"-print0",
|
|
88
|
+
])
|
|
89
|
+
.pipe(Effect.flatMap((output) => directChildren(reposRoot, output)));
|
|
90
|
+
yield* Effect.forEach(caches, (cache) => commands
|
|
91
|
+
.remote("prepare", vm.sshDest, "git", ["--git-dir", cache, "worktree", "prune"])
|
|
92
|
+
.pipe(Effect.tapError((error) => Effect.logWarning("Could not prune an exe.dev cache during the sweep", {
|
|
93
|
+
attemptId,
|
|
94
|
+
worker,
|
|
95
|
+
host: vm.sshDest,
|
|
96
|
+
cache,
|
|
97
|
+
message: error.message,
|
|
98
|
+
})), Effect.ignore), { concurrency: SWEEP_CONCURRENCY, discard: true });
|
|
99
|
+
const failed = yield* Ref.get(failures);
|
|
100
|
+
yield* Effect.logInfo("exe.dev abandoned-worktree sweep completed", {
|
|
101
|
+
attemptId,
|
|
102
|
+
worker,
|
|
103
|
+
host: vm.sshDest,
|
|
104
|
+
removed: attempts.length - failed,
|
|
105
|
+
failed,
|
|
106
|
+
caches: caches.length,
|
|
107
|
+
});
|
|
108
|
+
}).pipe(Effect.tapError((error) => Effect.logWarning("exe.dev abandoned-worktree sweep failed; continuing", {
|
|
109
|
+
attemptId,
|
|
110
|
+
worker,
|
|
111
|
+
host: vm.sshDest,
|
|
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/exe-dev/sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Agent } from "@usine/core/agent";
|
|
2
|
+
import { BrainSpec } from "@usine/core/agent/brain";
|
|
3
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
4
|
+
import * as AttemptChannel from "@usine/core/attempt/channel";
|
|
5
|
+
import { AttemptFailure } from "@usine/core/attempt/failure";
|
|
6
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
7
|
+
import { WorkerName, WorkerKind } from "@usine/core/worker";
|
|
8
|
+
import { StepResult } from "@usine/core/step/result";
|
|
9
|
+
import { Context, Effect, Redacted, Schema, Scope } from "effect";
|
|
10
|
+
/** The Worker operation that failed during readiness checks or an Attempt's lifecycle. */
|
|
11
|
+
export declare const ExecutorOperation: Schema.Literals<readonly ["preflight", "prepare", "launch", "cancel", "cleanup"]>;
|
|
12
|
+
export type ExecutorOperation = typeof ExecutorOperation.Type;
|
|
13
|
+
/** A prerequisite a Worker must satisfy before it can accept Attempts. */
|
|
14
|
+
export declare const WorkerCapability: Schema.Literals<readonly ["machine", "toolchain", "run-host", "harness-login", "credentials"]>;
|
|
15
|
+
export type WorkerCapability = typeof WorkerCapability.Type;
|
|
16
|
+
declare const ExecutorOperationError_base: Schema.Class<ExecutorOperationError, Schema.TaggedStruct<"ExecutorOperationError", {
|
|
17
|
+
readonly operation: Schema.Literals<readonly ["preflight", "prepare", "launch", "cancel", "cleanup"]>;
|
|
18
|
+
readonly message: Schema.NonEmptyString;
|
|
19
|
+
readonly cause: Schema.Defect;
|
|
20
|
+
}>, import("effect/Cause").YieldableError>;
|
|
21
|
+
/** A Worker operation failed before its requested work completed. */
|
|
22
|
+
export declare class ExecutorOperationError extends ExecutorOperationError_base {
|
|
23
|
+
}
|
|
24
|
+
declare const CredentialUnavailableError_base: Schema.Class<CredentialUnavailableError, Schema.TaggedStruct<"CredentialUnavailableError", {
|
|
25
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
26
|
+
readonly message: Schema.NonEmptyString;
|
|
27
|
+
readonly cause: Schema.Defect;
|
|
28
|
+
}>, import("effect/Cause").YieldableError>;
|
|
29
|
+
/** A Worker could not obtain access to the repository assigned to an Attempt. */
|
|
30
|
+
export declare class CredentialUnavailableError extends CredentialUnavailableError_base {
|
|
31
|
+
}
|
|
32
|
+
declare const WorkerCapabilityError_base: Schema.Class<WorkerCapabilityError, Schema.TaggedStruct<"WorkerCapabilityError", {
|
|
33
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
34
|
+
readonly capability: Schema.Literals<readonly ["machine", "toolchain", "run-host", "harness-login", "credentials"]>;
|
|
35
|
+
readonly message: Schema.NonEmptyString;
|
|
36
|
+
readonly cause: Schema.Defect;
|
|
37
|
+
}>, import("effect/Cause").YieldableError>;
|
|
38
|
+
/** A verify-only preflight found a Worker capability missing or incompatible. */
|
|
39
|
+
export declare class WorkerCapabilityError extends WorkerCapabilityError_base {
|
|
40
|
+
}
|
|
41
|
+
/** A readiness, access, or lifecycle failure reported by a Worker operation. */
|
|
42
|
+
export declare const ExecutorError: Schema.Union<readonly [typeof ExecutorOperationError, typeof CredentialUnavailableError, typeof WorkerCapabilityError]>;
|
|
43
|
+
export type ExecutorError = typeof ExecutorError.Type;
|
|
44
|
+
/** Logs every preflight refusal so a missing capability is explainable from logs alone. */
|
|
45
|
+
export declare const logPreflightFailure: <A, R>(effect: Effect.Effect<A, ExecutorError, R>) => Effect.Effect<A, ExecutorError, R>;
|
|
46
|
+
/** Process command for a one-shot run host or a preflight capability probe. */
|
|
47
|
+
export interface RunHostCommand {
|
|
48
|
+
readonly command: string;
|
|
49
|
+
readonly args: ReadonlyArray<string>;
|
|
50
|
+
}
|
|
51
|
+
declare const CheckoutRequest_base: Schema.Class<CheckoutRequest, Schema.Struct<{
|
|
52
|
+
readonly attemptId: Schema.brand<Schema.NonEmptyString, "AttemptId">;
|
|
53
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
54
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
55
|
+
readonly revision: Schema.brand<Schema.String, "RepositoryRevision">;
|
|
56
|
+
readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
|
|
57
|
+
readonly task: Schema.brand<Schema.String, "AttemptTask">;
|
|
58
|
+
readonly agent: typeof Agent;
|
|
59
|
+
readonly parentModel: Schema.Option<Schema.brand<Schema.String, "ModelId">>;
|
|
60
|
+
readonly maxChildren: Schema.brand<Schema.Int, "ChildSessionBudget">;
|
|
61
|
+
/** Maximum minutes of live run-host execution; preparation and resource cleanup are outside this budget. */
|
|
62
|
+
readonly maxAttemptMinutes: Schema.Int;
|
|
63
|
+
readonly parentEffort: Schema.Literals<readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"]>;
|
|
64
|
+
}>, {}>;
|
|
65
|
+
/** The repository, revision, and Agent task assigned to a Worker for one fresh Attempt checkout. */
|
|
66
|
+
export declare class CheckoutRequest extends CheckoutRequest_base {
|
|
67
|
+
}
|
|
68
|
+
/** One Attempt's disposable checkout directory. */
|
|
69
|
+
export declare const WorktreePath: Schema.brand<Schema.NonEmptyString, "WorktreePath">;
|
|
70
|
+
export type WorktreePath = typeof WorktreePath.Type;
|
|
71
|
+
declare const AttemptWorkspace_base: Schema.Class<AttemptWorkspace, Schema.Struct<{
|
|
72
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
73
|
+
readonly spec: typeof AttemptChannel.RunSpec;
|
|
74
|
+
readonly worktree: Schema.brand<Schema.NonEmptyString, "WorktreePath">;
|
|
75
|
+
}>, {}>;
|
|
76
|
+
/** The serializable location and RunSpec of an Attempt checkout. Owning this record does not grant access to live resources or credentials. */
|
|
77
|
+
export declare class AttemptWorkspace extends AttemptWorkspace_base {
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* One prepared Attempt with a scoped checkout and repository access. Use it within
|
|
81
|
+
* the scope that prepared it. Closing that scope stops any active run host before
|
|
82
|
+
* removing the checkout and releasing credentials, including when launch never starts.
|
|
83
|
+
*/
|
|
84
|
+
export interface PreparedAttempt {
|
|
85
|
+
/** Checkout metadata suitable for inspection or serialization; contains no credentials. */
|
|
86
|
+
readonly workspace: AttemptWorkspace;
|
|
87
|
+
/**
|
|
88
|
+
* Runs this Attempt once, recording channel activity through `io`. Completion,
|
|
89
|
+
* failure, or interruption releases its resources before returning. Cleanup
|
|
90
|
+
* failures are logged without replacing the agent's outcome or retrying its work.
|
|
91
|
+
* Reusing an Attempt after launch or release fails with an infrastructure error.
|
|
92
|
+
*/
|
|
93
|
+
readonly launch: (io: AttemptChannel.Sink) => Effect.Effect<StepResult, AttemptFailure>;
|
|
94
|
+
}
|
|
95
|
+
declare const CredentialProvider_base: Context.ServiceClass<CredentialProvider, "@usine/executor/CredentialProvider", {
|
|
96
|
+
readonly issue: (repository: RepositoryName, attemptId: AttemptId) => Effect.Effect<Redacted.Redacted<string>, CredentialUnavailableError>;
|
|
97
|
+
}>;
|
|
98
|
+
/** Mints exactly one repository-scoped credential for each prepared Attempt. */
|
|
99
|
+
export declare class CredentialProvider extends CredentialProvider_base {
|
|
100
|
+
}
|
|
101
|
+
declare const Executor_base: Context.ServiceClass<Executor, "@usine/executor/Executor", {
|
|
102
|
+
readonly kind: WorkerKind;
|
|
103
|
+
/** Verifies Git, the run host, and each rostered brain in the Worker environment; never installs tools or logs in. */
|
|
104
|
+
readonly preflight: (worker: WorkerName, brains: ReadonlyArray<BrainSpec>) => Effect.Effect<void, ExecutorError>;
|
|
105
|
+
/**
|
|
106
|
+
* Verifies the assigned brain before acquiring a fresh Attempt in the caller's scope. Clone, fetch, and lock waits
|
|
107
|
+
* remain interruptible; partial preparation releases everything already acquired
|
|
108
|
+
* before reporting failure. Each request must identify a fresh Attempt.
|
|
109
|
+
*/
|
|
110
|
+
readonly prepare: (request: CheckoutRequest) => Effect.Effect<PreparedAttempt, ExecutorError, Scope.Scope>;
|
|
111
|
+
/** Requests an active Attempt to stop, allowing a grace period before forcing termination. */
|
|
112
|
+
readonly cancel: (attemptId: AttemptId) => Effect.Effect<void, ExecutorError>;
|
|
113
|
+
}>;
|
|
114
|
+
/** Prepares isolated checkouts, runs Attempts on a Worker, and releases their resources (ADR 0006). */
|
|
115
|
+
export declare class Executor extends Executor_base {
|
|
116
|
+
}
|
|
117
|
+
export {};
|
|
118
|
+
//# sourceMappingURL=executor.d.ts.map
|