@clipboard-health/groundcrew 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/configExample.ts +9 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +39 -12
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +68 -8
- package/dist/lib/config.d.ts +63 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +75 -7
- package/dist/lib/dockerSandbox.d.ts +40 -0
- package/dist/lib/dockerSandbox.d.ts.map +1 -0
- package/dist/lib/dockerSandbox.js +58 -0
- package/dist/lib/host.d.ts +10 -0
- package/dist/lib/host.d.ts.map +1 -1
- package/dist/lib/host.js +8 -3
- package/dist/lib/launchCommand.d.ts +17 -3
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +66 -8
- package/dist/lib/localRunner.d.ts +22 -1
- package/dist/lib/localRunner.d.ts.map +1 -1
- package/dist/lib/localRunner.js +48 -5
- package/dist/lib/workspaces.d.ts +5 -0
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +156 -42
- package/dist/lib/worktrees.d.ts +5 -0
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +14 -5
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { runCommandAsync } from "./commandRunner.js";
|
|
2
|
+
/**
|
|
3
|
+
* Derive a deterministic sbx sandbox name from the repository + model
|
|
4
|
+
* tuple so `crew sandbox auth <repo>` and the subsequent `crew local`
|
|
5
|
+
* launch agree on which sandbox to target. Lowercased and reduced to the
|
|
6
|
+
* sbx-safe charset (`a-z0-9.+-`) so unusual repo names still round-trip
|
|
7
|
+
* cleanly. Keep the prefix stable — doctor and teardown use it to
|
|
8
|
+
* identify groundcrew-owned sandboxes.
|
|
9
|
+
*/
|
|
10
|
+
export function sandboxNameFor(arguments_) {
|
|
11
|
+
const raw = `groundcrew-${arguments_.repository}-${arguments_.model}`.toLowerCase();
|
|
12
|
+
return raw
|
|
13
|
+
.replaceAll(/[^a-z0-9.+-]+/g, "-")
|
|
14
|
+
.replaceAll(/-+/g, "-")
|
|
15
|
+
.replaceAll(/^-|-$/g, "");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Probe `sbx ls` to see whether a sandbox with `sandboxName` already
|
|
19
|
+
* exists. Used by `crew sandbox auth` to switch between create vs reuse
|
|
20
|
+
* branches without surfacing the raw sbx error on first run.
|
|
21
|
+
*/
|
|
22
|
+
export async function sandboxExists(sandboxName, signal) {
|
|
23
|
+
const out = signal === undefined
|
|
24
|
+
? await runCommandAsync("sbx", ["ls"])
|
|
25
|
+
: await runCommandAsync("sbx", ["ls"], { signal });
|
|
26
|
+
return out.split("\n").some((line) => line.trim().split(/\s+/)[0] === sandboxName);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Idempotent guard: ensure a Docker Sandboxes container exists for the
|
|
30
|
+
* given repository + model. Probes `sbx ls`; if `sandboxName` is missing,
|
|
31
|
+
* calls `sbx create --name <name> [--template <t>] [--kit <k>]... <agent>
|
|
32
|
+
* <mountPath>` to provision it. First-time agent auth still happens inside
|
|
33
|
+
* the sandbox the first time `sbx exec` runs the agent — `create` only
|
|
34
|
+
* provisions the container, it does not attach.
|
|
35
|
+
*/
|
|
36
|
+
export async function ensureSandbox(arguments_, signal) {
|
|
37
|
+
if (await sandboxExists(arguments_.sandboxName, signal)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const createArguments = ["create", "--name", arguments_.sandboxName];
|
|
41
|
+
if (arguments_.sandbox.template !== undefined) {
|
|
42
|
+
createArguments.push("--template", arguments_.sandbox.template);
|
|
43
|
+
}
|
|
44
|
+
for (const kit of arguments_.sandbox.kits ?? []) {
|
|
45
|
+
createArguments.push("--kit", kit);
|
|
46
|
+
}
|
|
47
|
+
createArguments.push(arguments_.sandbox.agent, arguments_.mountPath);
|
|
48
|
+
const options = signal === undefined ? {} : { signal };
|
|
49
|
+
try {
|
|
50
|
+
await runCommandAsync("sbx", createArguments, options);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (await sandboxExists(arguments_.sandboxName, signal)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/lib/host.d.ts
CHANGED
|
@@ -6,18 +6,28 @@
|
|
|
6
6
|
export interface HostCapabilities {
|
|
7
7
|
/** True when the `safehouse` binary is on PATH. */
|
|
8
8
|
hasSafehouse: boolean;
|
|
9
|
+
/** True when the `sbx` (Docker Sandboxes) binary is on PATH. */
|
|
10
|
+
hasSbx: boolean;
|
|
9
11
|
/** True when the `cmux` binary is on PATH. */
|
|
10
12
|
hasCmux: boolean;
|
|
11
13
|
/** True when the `tmux` binary is on PATH. */
|
|
12
14
|
hasTmux: boolean;
|
|
13
15
|
/** True when the host platform is macOS. Safehouse is macOS-only. */
|
|
14
16
|
isMacOS: boolean;
|
|
17
|
+
/** True when the host platform is Linux. */
|
|
18
|
+
isLinux: boolean;
|
|
15
19
|
/**
|
|
16
20
|
* True when the host platform is one Safehouse supports. Safehouse is
|
|
17
21
|
* macOS-only at time of writing; local setup uses this to reject Linux
|
|
18
22
|
* or WSL before creating a worktree.
|
|
19
23
|
*/
|
|
20
24
|
isSafehouseSupported: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* True when sdx (Docker Sandboxes) is supportable on this platform —
|
|
27
|
+
* sbx is published for both macOS and Linux, so this stays in sync with
|
|
28
|
+
* "macOS || Linux". WSL inherits Linux capabilities transparently.
|
|
29
|
+
*/
|
|
30
|
+
isSdxSupported: boolean;
|
|
21
31
|
}
|
|
22
32
|
/**
|
|
23
33
|
* Resolves a binary on PATH the same way `which` does. Returns the first
|
package/dist/lib/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/lib/host.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/lib/host.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,MAAM,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAc1F;AAED,wBAAsB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB5F"}
|
package/dist/lib/host.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* current machine. Doctor and setup inject a capabilities object directly
|
|
4
4
|
* so tests don't have to mock `which`.
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
6
|
+
import process from "node:process";
|
|
7
7
|
import { runCommandAsync } from "./commandRunner.js";
|
|
8
8
|
/**
|
|
9
9
|
* Resolves a binary on PATH the same way `which` does. Returns the first
|
|
@@ -26,17 +26,22 @@ export async function which(cmd, signal) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
export async function detectHostCapabilities(signal) {
|
|
29
|
-
const isMacOS = platform === "darwin";
|
|
30
|
-
const
|
|
29
|
+
const isMacOS = process.platform === "darwin";
|
|
30
|
+
const isLinux = process.platform === "linux";
|
|
31
|
+
const [safehouse, sbx, cmux, tmux] = await Promise.all([
|
|
31
32
|
which("safehouse", signal),
|
|
33
|
+
which("sbx", signal),
|
|
32
34
|
which("cmux", signal),
|
|
33
35
|
which("tmux", signal),
|
|
34
36
|
]);
|
|
35
37
|
return {
|
|
36
38
|
hasSafehouse: safehouse !== undefined,
|
|
39
|
+
hasSbx: sbx !== undefined,
|
|
37
40
|
hasCmux: cmux !== undefined,
|
|
38
41
|
hasTmux: tmux !== undefined,
|
|
39
42
|
isMacOS,
|
|
43
|
+
isLinux,
|
|
40
44
|
isSafehouseSupported: isMacOS,
|
|
45
|
+
isSdxSupported: isMacOS || isLinux,
|
|
41
46
|
};
|
|
42
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ModelDefinition } from "./config.ts";
|
|
1
|
+
import { type LocalRunner, type ModelDefinition } from "./config.ts";
|
|
2
2
|
export { shellSingleQuote } from "./shell.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Resolve the shipped Safehouse proxy wrapper inside `@clipboard-health/clearance`
|
|
@@ -18,10 +18,24 @@ interface LaunchCommandArguments {
|
|
|
18
18
|
/**
|
|
19
19
|
* Optional path to a `KEY='value'` env file containing build-time
|
|
20
20
|
* secrets (see `BUILD_SECRET_NAMES`). Sourced on the host shell before
|
|
21
|
-
* setup
|
|
22
|
-
*
|
|
21
|
+
* setup; for the sdx runner the names are propagated into the sandbox
|
|
22
|
+
* via `sbx exec -e KEY`. Always unset before exec'ing the agent so the
|
|
23
|
+
* agent process never inherits them.
|
|
23
24
|
*/
|
|
24
25
|
secretsFile?: string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Concrete local isolation backend chosen for this launch. Resolved
|
|
28
|
+
* from `config.local.runner` via `resolveLocalRunner` before this
|
|
29
|
+
* function is called — `auto` is never seen here.
|
|
30
|
+
*/
|
|
31
|
+
runner: LocalRunner;
|
|
32
|
+
/**
|
|
33
|
+
* sbx sandbox name when `runner === "sdx"`. Derived by the caller from
|
|
34
|
+
* `sandboxNameFor({ repository, model })`. Required for sdx; ignored
|
|
35
|
+
* otherwise. Kept off the model definition so a model can launch under
|
|
36
|
+
* safehouse on one host and sdx on another without config edits.
|
|
37
|
+
*/
|
|
38
|
+
sandboxName?: string | undefined;
|
|
25
39
|
}
|
|
26
40
|
/**
|
|
27
41
|
* Build the shell command that runs inside the workspace. The prompt is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAmCD,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAK7E"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { BUILD_SECRET_NAMES, DEFAULT_HOST_SETUP_COMMAND } from "./config.js";
|
|
3
|
+
import { BUILD_SECRET_NAMES, DEFAULT_HOST_SETUP_COMMAND, DEFAULT_SANDBOX_SETUP_COMMAND, } from "./config.js";
|
|
4
4
|
import { shellSingleQuote } from "./shell.js";
|
|
5
5
|
export { shellSingleQuote } from "./shell.js";
|
|
6
6
|
/**
|
|
@@ -28,7 +28,7 @@ const SAFEHOUSE_CLEARANCE_WRAPPER_PATH = resolveSafehouseClearancePath();
|
|
|
28
28
|
function renderAgentCommand(arguments_) {
|
|
29
29
|
return arguments_.agentCmd
|
|
30
30
|
.replaceAll("{{worktree}}", shellSingleQuote(arguments_.worktreeDir))
|
|
31
|
-
.replaceAll("{{sandbox}}", shellSingleQuote(
|
|
31
|
+
.replaceAll("{{sandbox}}", shellSingleQuote(arguments_.sandboxName));
|
|
32
32
|
}
|
|
33
33
|
function setupWithStatusReporting(setupCommand) {
|
|
34
34
|
return [
|
|
@@ -57,17 +57,23 @@ function unsetSecretsLine() {
|
|
|
57
57
|
* prompt in hand.
|
|
58
58
|
*/
|
|
59
59
|
export function buildLaunchCommand(arguments_) {
|
|
60
|
+
if (arguments_.runner === "sdx") {
|
|
61
|
+
return buildSdxLaunchCommand(arguments_);
|
|
62
|
+
}
|
|
63
|
+
return buildHostLaunchCommand(arguments_);
|
|
64
|
+
}
|
|
65
|
+
function buildHostLaunchCommand(arguments_) {
|
|
60
66
|
const promptDir = dirname(arguments_.promptFile);
|
|
61
67
|
const agentCmd = renderAgentCommand({
|
|
62
68
|
agentCmd: arguments_.definition.cmd,
|
|
63
69
|
worktreeDir: arguments_.worktreeDir,
|
|
70
|
+
sandboxName: "",
|
|
71
|
+
});
|
|
72
|
+
const wrapped = wrapAgentForHostRunner({
|
|
73
|
+
runner: arguments_.runner,
|
|
74
|
+
rawCmd: arguments_.definition.cmd,
|
|
75
|
+
agentCmd,
|
|
64
76
|
});
|
|
65
|
-
// Skip the wrap if `cmd` already starts with `safehouse` so legacy
|
|
66
|
-
// configs don't double-wrap.
|
|
67
|
-
const cmdStartsWithSafehouse = /^safehouse(\s|$)/.test(arguments_.definition.cmd);
|
|
68
|
-
const wrapped = cmdStartsWithSafehouse
|
|
69
|
-
? agentCmd
|
|
70
|
-
: [shellSingleQuote(SAFEHOUSE_CLEARANCE_WRAPPER_PATH), agentCmd].join(" ");
|
|
71
77
|
const lines = [`cd ${shellSingleQuote(arguments_.worktreeDir)}`];
|
|
72
78
|
if (arguments_.secretsFile !== undefined) {
|
|
73
79
|
lines.push(sourceSecretsLine(arguments_.secretsFile));
|
|
@@ -79,3 +85,55 @@ export function buildLaunchCommand(arguments_) {
|
|
|
79
85
|
lines.push(`_p=$(cat ${shellSingleQuote(arguments_.promptFile)})`, `rm -rf ${shellSingleQuote(promptDir)}`, `exec ${wrapped} "$_p"`);
|
|
80
86
|
return lines.join(" && ");
|
|
81
87
|
}
|
|
88
|
+
function wrapAgentForHostRunner(arguments_) {
|
|
89
|
+
if (arguments_.runner === "none") {
|
|
90
|
+
return arguments_.agentCmd;
|
|
91
|
+
}
|
|
92
|
+
// buildLaunchCommand routes `sdx` through buildSdxLaunchCommand, so the
|
|
93
|
+
// only remaining shape here is `safehouse`. Treat the explicit branch as
|
|
94
|
+
// the safehouse wrap to keep this function readable; the `sdx` arm exists
|
|
95
|
+
// only to satisfy TS's exhaustiveness checker.
|
|
96
|
+
/* v8 ignore next 3 @preserve -- buildLaunchCommand short-circuits sdx before calling this helper */
|
|
97
|
+
if (arguments_.runner === "sdx") {
|
|
98
|
+
return arguments_.agentCmd;
|
|
99
|
+
}
|
|
100
|
+
// safehouse: skip the wrap if `cmd` already starts with `safehouse` so
|
|
101
|
+
// legacy configs don't double-wrap.
|
|
102
|
+
const cmdStartsWithSafehouse = /^safehouse(\s|$)/.test(arguments_.rawCmd);
|
|
103
|
+
if (cmdStartsWithSafehouse) {
|
|
104
|
+
return arguments_.agentCmd;
|
|
105
|
+
}
|
|
106
|
+
return [shellSingleQuote(SAFEHOUSE_CLEARANCE_WRAPPER_PATH), arguments_.agentCmd].join(" ");
|
|
107
|
+
}
|
|
108
|
+
function buildSdxLaunchCommand(arguments_) {
|
|
109
|
+
/* v8 ignore next 5 @preserve -- setupWorkspace passes sandboxName + sandbox config when picking sdx; missing fields are programmer errors */
|
|
110
|
+
if (arguments_.sandboxName === undefined || arguments_.definition.sandbox === undefined) {
|
|
111
|
+
throw new Error("buildLaunchCommand: runner='sdx' requires sandboxName and a model `sandbox` config block (set sandbox.agent on the model in config.ts).");
|
|
112
|
+
}
|
|
113
|
+
const promptDir = dirname(arguments_.promptFile);
|
|
114
|
+
const agentCmd = renderAgentCommand({
|
|
115
|
+
agentCmd: arguments_.definition.cmd,
|
|
116
|
+
worktreeDir: arguments_.worktreeDir,
|
|
117
|
+
sandboxName: arguments_.sandboxName,
|
|
118
|
+
});
|
|
119
|
+
const setupCommand = arguments_.definition.sandbox.setupCommand ?? DEFAULT_SANDBOX_SETUP_COMMAND;
|
|
120
|
+
const innerParts = [setupWithStatusReporting(setupCommand)];
|
|
121
|
+
if (arguments_.secretsFile !== undefined) {
|
|
122
|
+
innerParts.push(unsetSecretsLine());
|
|
123
|
+
}
|
|
124
|
+
innerParts.push(`exec ${agentCmd} "$@"`);
|
|
125
|
+
const innerCommand = innerParts.join("; ");
|
|
126
|
+
// Passthrough form (`-e KEY` without `=VALUE`): sbx reads each value
|
|
127
|
+
// from its own env at invocation time — populated by sourceSecretsLine
|
|
128
|
+
// a few lines up. Avoids `-e KEY="$KEY"`, which would embed the value
|
|
129
|
+
// in argv and break on `"`, `$`, or backticks in the token.
|
|
130
|
+
const sbxEnvironmentFlags = arguments_.secretsFile === undefined
|
|
131
|
+
? ""
|
|
132
|
+
: `${BUILD_SECRET_NAMES.map((name) => `-e ${name}`).join(" ")} `;
|
|
133
|
+
const lines = [];
|
|
134
|
+
if (arguments_.secretsFile !== undefined) {
|
|
135
|
+
lines.push(sourceSecretsLine(arguments_.secretsFile));
|
|
136
|
+
}
|
|
137
|
+
lines.push(`_p=$(cat ${shellSingleQuote(arguments_.promptFile)})`, `rm -rf ${shellSingleQuote(promptDir)}`, `exec sbx exec -it ${sbxEnvironmentFlags}-w ${shellSingleQuote(arguments_.worktreeDir)} ${shellSingleQuote(arguments_.sandboxName)} sh -lc ${shellSingleQuote(innerCommand)} sh "$_p"`);
|
|
138
|
+
return lines.join(" && ");
|
|
139
|
+
}
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import type { LocalRunner, LocalRunnerSetting } from "./config.ts";
|
|
1
2
|
import type { HostCapabilities } from "./host.ts";
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Resolve `local.runner` from config + host capabilities into a concrete
|
|
5
|
+
* backend. `auto` defaults to safehouse on macOS and sdx on Linux — both
|
|
6
|
+
* are the deny-first paths for their platform. `none` and the explicit
|
|
7
|
+
* names pass through unchanged so users always get exactly what they
|
|
8
|
+
* asked for. Pure: takes everything it needs as arguments so the
|
|
9
|
+
* dispatcher can test platform pivots without touching real hosts.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveLocalRunner(setting: LocalRunnerSetting, host: HostCapabilities): LocalRunner;
|
|
12
|
+
/**
|
|
13
|
+
* Verify the host can run the chosen local isolation backend before we
|
|
14
|
+
* create a worktree. The runner has already been resolved from
|
|
15
|
+
* `config.local.runner` (via `resolveLocalRunner`), so `auto` never gets
|
|
16
|
+
* here — the caller passes `safehouse`, `sdx`, or `none`.
|
|
17
|
+
*
|
|
18
|
+
* `none` is a deliberately unsafe escape hatch. It is never selected
|
|
19
|
+
* implicitly (`auto` picks `safehouse`/`sdx`); when the user has set it
|
|
20
|
+
* explicitly, this helper logs a single warning so the unsandboxed launch
|
|
21
|
+
* is visible in groundcrew's log, but does not throw.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertLocalRunnerRequirements(host: HostCapabilities, runner: LocalRunner): void;
|
|
3
24
|
//# sourceMappingURL=localRunner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,WAAW,CAQb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CA6B/F"}
|
package/dist/lib/localRunner.js
CHANGED
|
@@ -1,8 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { log } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve `local.runner` from config + host capabilities into a concrete
|
|
4
|
+
* backend. `auto` defaults to safehouse on macOS and sdx on Linux — both
|
|
5
|
+
* are the deny-first paths for their platform. `none` and the explicit
|
|
6
|
+
* names pass through unchanged so users always get exactly what they
|
|
7
|
+
* asked for. Pure: takes everything it needs as arguments so the
|
|
8
|
+
* dispatcher can test platform pivots without touching real hosts.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveLocalRunner(setting, host) {
|
|
11
|
+
if (setting !== "auto") {
|
|
12
|
+
return setting;
|
|
4
13
|
}
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
// macOS → safehouse; everything else (Linux/WSL, exotic platforms) → sdx.
|
|
15
|
+
// `assertLocalRunnerRequirements` then enforces sdx's platform/binary
|
|
16
|
+
// preconditions and surfaces a precise error on truly unsupported hosts.
|
|
17
|
+
return host.isMacOS ? "safehouse" : "sdx";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify the host can run the chosen local isolation backend before we
|
|
21
|
+
* create a worktree. The runner has already been resolved from
|
|
22
|
+
* `config.local.runner` (via `resolveLocalRunner`), so `auto` never gets
|
|
23
|
+
* here — the caller passes `safehouse`, `sdx`, or `none`.
|
|
24
|
+
*
|
|
25
|
+
* `none` is a deliberately unsafe escape hatch. It is never selected
|
|
26
|
+
* implicitly (`auto` picks `safehouse`/`sdx`); when the user has set it
|
|
27
|
+
* explicitly, this helper logs a single warning so the unsandboxed launch
|
|
28
|
+
* is visible in groundcrew's log, but does not throw.
|
|
29
|
+
*/
|
|
30
|
+
export function assertLocalRunnerRequirements(host, runner) {
|
|
31
|
+
if (runner === "safehouse") {
|
|
32
|
+
if (!host.isSafehouseSupported) {
|
|
33
|
+
throw new Error("Local groundcrew runs with the safehouse runner require macOS. On Linux/WSL, set local.runner to 'sdx' (default) or 'auto'.");
|
|
34
|
+
}
|
|
35
|
+
if (!host.hasSafehouse) {
|
|
36
|
+
throw new Error("Local groundcrew runs require `safehouse` on PATH. Install Safehouse from https://agent-safehouse.dev/ and retry.");
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (runner === "sdx") {
|
|
41
|
+
if (!host.isSdxSupported) {
|
|
42
|
+
throw new Error("Local groundcrew runs with the sdx runner require macOS or Linux.");
|
|
43
|
+
}
|
|
44
|
+
if (!host.hasSbx) {
|
|
45
|
+
throw new Error("Local groundcrew runs with the sdx runner require `sbx` (Docker Sandboxes) on PATH. Install from https://docs.docker.com/sandboxes/ and retry.");
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
7
48
|
}
|
|
49
|
+
// runner === "none"
|
|
50
|
+
log("WARNING: local.runner='none' — agent process will run on the host without sandboxing. Only use this when you understand the implications.");
|
|
8
51
|
}
|
package/dist/lib/workspaces.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface WorkspaceStatus {
|
|
|
16
16
|
color?: string;
|
|
17
17
|
icon?: string;
|
|
18
18
|
}
|
|
19
|
+
export interface WorkspaceAccessHint {
|
|
20
|
+
kind: "attachCommand";
|
|
21
|
+
command: string;
|
|
22
|
+
}
|
|
19
23
|
export interface OpenSpec {
|
|
20
24
|
/** Ticket id; becomes the workspace's name. */
|
|
21
25
|
name: string;
|
|
@@ -53,6 +57,7 @@ export declare const workspaces: {
|
|
|
53
57
|
open(config: ResolvedConfig, spec: OpenSpec, signal?: AbortSignal): Promise<void>;
|
|
54
58
|
probe: typeof probeWorkspaces;
|
|
55
59
|
close(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<void>;
|
|
60
|
+
accessHint(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceAccessHint | undefined>;
|
|
56
61
|
};
|
|
57
62
|
export {};
|
|
58
63
|
//# sourceMappingURL=workspaces.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAgU7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AA+ND,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SAAS,cAAc,QAAQ,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhF,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}
|