@clipboard-health/groundcrew 4.0.2 → 4.1.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 +32 -13
- package/crew.config.example.ts +5 -18
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +64 -10
- package/dist/commands/interruptWorkspace.d.ts.map +1 -1
- package/dist/commands/interruptWorkspace.js +3 -3
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +1 -2
- package/dist/commands/setupRepos.d.ts.map +1 -1
- package/dist/commands/setupRepos.js +2 -13
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +1 -7
- package/dist/lib/agentLaunch.d.ts +0 -6
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +1 -12
- package/dist/lib/cmuxAdapter.d.ts +8 -0
- package/dist/lib/cmuxAdapter.d.ts.map +1 -0
- package/dist/lib/cmuxAdapter.js +163 -0
- package/dist/lib/config.d.ts +2 -76
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +29 -102
- package/dist/lib/launchCommand.d.ts +3 -3
- package/dist/lib/sandboxName.d.ts +9 -0
- package/dist/lib/sandboxName.d.ts.map +1 -0
- package/dist/lib/sandboxName.js +12 -0
- package/dist/lib/tmuxAdapter.d.ts +9 -0
- package/dist/lib/tmuxAdapter.d.ts.map +1 -0
- package/dist/lib/tmuxAdapter.js +156 -0
- package/dist/lib/util.d.ts +11 -0
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +21 -0
- package/dist/lib/workspaceAdapter.d.ts +79 -0
- package/dist/lib/workspaceAdapter.d.ts.map +1 -0
- package/dist/lib/workspaceAdapter.js +17 -0
- package/dist/lib/workspaces.d.ts +7 -55
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +8 -404
- package/package.json +1 -2
- package/dist/commands/sandbox/auth.d.ts +0 -3
- package/dist/commands/sandbox/auth.d.ts.map +0 -1
- package/dist/commands/sandbox/auth.js +0 -227
- package/dist/commands/sandbox/index.d.ts +0 -2
- package/dist/commands/sandbox/index.d.ts.map +0 -1
- package/dist/commands/sandbox/index.js +0 -47
- package/dist/commands/sandbox/inspect.d.ts +0 -2
- package/dist/commands/sandbox/inspect.d.ts.map +0 -1
- package/dist/commands/sandbox/inspect.js +0 -18
- package/dist/commands/sandbox/lifecycle.d.ts +0 -7
- package/dist/commands/sandbox/lifecycle.d.ts.map +0 -1
- package/dist/commands/sandbox/lifecycle.js +0 -68
- package/dist/commands/sandbox/model.d.ts +0 -10
- package/dist/commands/sandbox/model.d.ts.map +0 -1
- package/dist/commands/sandbox/model.js +0 -37
- package/dist/commands/sandbox/picker.d.ts +0 -20
- package/dist/commands/sandbox/picker.d.ts.map +0 -1
- package/dist/commands/sandbox/picker.js +0 -23
- package/dist/lib/dockerSandbox.d.ts +0 -43
- package/dist/lib/dockerSandbox.d.ts.map +0 -1
- package/dist/lib/dockerSandbox.js +0 -69
- package/dist/lib/sandboxGitDefaults.d.ts +0 -10
- package/dist/lib/sandboxGitDefaults.d.ts.map +0 -1
- package/dist/lib/sandboxGitDefaults.js +0 -31
package/dist/lib/config.d.ts
CHANGED
|
@@ -41,16 +41,12 @@ export type LocalRunnerSetting = LocalRunner | "auto";
|
|
|
41
41
|
export declare const LOCAL_RUNNER_SETTINGS: readonly LocalRunnerSetting[];
|
|
42
42
|
/**
|
|
43
43
|
* Per-model Docker Sandboxes (sdx) binding. Required at launch when
|
|
44
|
-
* `local.runner` resolves to `sdx` so groundcrew knows which
|
|
45
|
-
*
|
|
44
|
+
* `local.runner` resolves to `sdx` so groundcrew knows which existing
|
|
45
|
+
* sbx sandbox to address.
|
|
46
46
|
*/
|
|
47
47
|
export interface SandboxDefinition {
|
|
48
48
|
/** sbx agent name (e.g. "claude", "codex"). */
|
|
49
49
|
agent: string;
|
|
50
|
-
/** Optional `sbx run --template` value. */
|
|
51
|
-
template?: string;
|
|
52
|
-
/** Optional `sbx run --kit` values (each passed as a separate flag). */
|
|
53
|
-
kits?: string[];
|
|
54
50
|
/**
|
|
55
51
|
* Setup command run **inside** the sandbox before the agent exec.
|
|
56
52
|
* Defaults to the shared `.groundcrew/setup.sh --deps-only` convention
|
|
@@ -58,42 +54,6 @@ export interface SandboxDefinition {
|
|
|
58
54
|
*/
|
|
59
55
|
setupCommand?: string;
|
|
60
56
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Recipe used by `crew sandbox auth <model>` to drive an interactive
|
|
63
|
-
* login flow inside a sbx sandbox and then verify it. The flow is
|
|
64
|
-
* picker-driven — no positional `<tool>` argument; the picker lists
|
|
65
|
-
* every recipe visible to the current sandbox.
|
|
66
|
-
*
|
|
67
|
-
* `binary` defaults to the recipe key (typically the agent or CLI name).
|
|
68
|
-
* `authenticatedPattern` matches against combined stdout+stderr from
|
|
69
|
-
* `statusArgs` — exit code alone isn't reliable because some CLIs
|
|
70
|
-
* report "not logged in" while still exiting 0.
|
|
71
|
-
* `kind` controls visibility in the interactive picker: `"agent"`
|
|
72
|
-
* recipes are scoped to a specific sbx agent and only appear when you
|
|
73
|
-
* `auth` against that agent's sandbox; `"tool"` recipes (default)
|
|
74
|
-
* appear in every sandbox's picker because they're cross-cutting
|
|
75
|
-
* (github, npm, gcloud, …). Defaults to `"tool"` when omitted.
|
|
76
|
-
*
|
|
77
|
-
* Ship-side recipes for `claude`, `codex`, and `cursor` live in
|
|
78
|
-
* `src/commands/sandbox/auth.ts`; users register additional tools
|
|
79
|
-
* under `sandbox.authRecipes` in their config.
|
|
80
|
-
*/
|
|
81
|
-
export interface AuthRecipe {
|
|
82
|
-
displayName: string;
|
|
83
|
-
binary?: string;
|
|
84
|
-
loginArgs: readonly string[];
|
|
85
|
-
statusArgs: readonly string[];
|
|
86
|
-
authenticatedPattern: RegExp;
|
|
87
|
-
kind?: "agent" | "tool";
|
|
88
|
-
/**
|
|
89
|
-
* Environment variables passed to `sbx exec` for both the login and
|
|
90
|
-
* status calls. Use this for CLIs whose default flow assumes a
|
|
91
|
-
* browser or other host-only feature — e.g. cursor-agent wants
|
|
92
|
-
* `NO_OPEN_BROWSER=1` to print a device code instead of trying to
|
|
93
|
-
* launch a browser inside the sandbox.
|
|
94
|
-
*/
|
|
95
|
-
env?: Record<string, string>;
|
|
96
|
-
}
|
|
97
57
|
export interface ModelDefinition {
|
|
98
58
|
/**
|
|
99
59
|
* Shell command launched for the model. Wrapped with Safehouse/clearance
|
|
@@ -201,32 +161,6 @@ export interface Config {
|
|
|
201
161
|
local?: {
|
|
202
162
|
runner?: LocalRunnerSetting;
|
|
203
163
|
};
|
|
204
|
-
/**
|
|
205
|
-
* Sandbox-wide settings. `authRecipes` lets users register additional
|
|
206
|
-
* tools (github, npm, gcloud, …) for `crew sandbox auth <model>` to
|
|
207
|
-
* authenticate inside the sandbox. The auth flow is picker-driven —
|
|
208
|
-
* registered recipes show up in the picker alongside the shipped ones,
|
|
209
|
-
* and a user recipe under the same key (e.g. "claude") overrides the
|
|
210
|
-
* shipped one.
|
|
211
|
-
*/
|
|
212
|
-
sandbox?: {
|
|
213
|
-
authRecipes?: Record<string, AuthRecipe>;
|
|
214
|
-
/**
|
|
215
|
-
* When true (default), every `crew sandbox ensure` / `auth` run applies
|
|
216
|
-
* a small set of git defaults inside the sandbox so robot commits push
|
|
217
|
-
* over `gh`-managed HTTPS regardless of how the user cloned the repo:
|
|
218
|
-
*
|
|
219
|
-
* - disable GPG signing for commits and tags
|
|
220
|
-
* - rewrite `git@github.com:` and `ssh://git@github.com/` URLs to
|
|
221
|
-
* `https://github.com/` so push uses gh's credential helper
|
|
222
|
-
* - after a successful `github` auth recipe login, run
|
|
223
|
-
* `gh auth setup-git` inside the sandbox
|
|
224
|
-
*
|
|
225
|
-
* Set `false` to skip both the git-config block and the post-login
|
|
226
|
-
* `gh auth setup-git` step.
|
|
227
|
-
*/
|
|
228
|
-
gitDefaults?: boolean;
|
|
229
|
-
};
|
|
230
164
|
logging?: {
|
|
231
165
|
/**
|
|
232
166
|
* Append-mode log file destination. `log()` and `logEvent()` tee here
|
|
@@ -281,14 +215,6 @@ export interface ResolvedConfig {
|
|
|
281
215
|
local: {
|
|
282
216
|
runner: LocalRunnerSetting;
|
|
283
217
|
};
|
|
284
|
-
/**
|
|
285
|
-
* Sandbox-wide settings. Always present after defaults; `authRecipes`
|
|
286
|
-
* is `{}` when the user provides none.
|
|
287
|
-
*/
|
|
288
|
-
sandbox: {
|
|
289
|
-
authRecipes: Record<string, AuthRecipe>;
|
|
290
|
-
gitDefaults: boolean;
|
|
291
|
-
};
|
|
292
218
|
logging: {
|
|
293
219
|
file: string;
|
|
294
220
|
};
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAIzD,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,CAAC;AAEtD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAKrD,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -126,29 +126,6 @@ function normalizeOptionalString(value, path) {
|
|
|
126
126
|
}
|
|
127
127
|
return value.trim();
|
|
128
128
|
}
|
|
129
|
-
function normalizeOptionalBoolean(value, path) {
|
|
130
|
-
if (value === undefined) {
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
if (typeof value !== "boolean") {
|
|
134
|
-
fail(`${path} must be a boolean`);
|
|
135
|
-
}
|
|
136
|
-
return value;
|
|
137
|
-
}
|
|
138
|
-
function normalizeOptionalStringArray(value, path) {
|
|
139
|
-
if (value === undefined) {
|
|
140
|
-
return undefined;
|
|
141
|
-
}
|
|
142
|
-
if (!Array.isArray(value)) {
|
|
143
|
-
fail(`${path} must be an array`);
|
|
144
|
-
}
|
|
145
|
-
return value.map((entry, index) => {
|
|
146
|
-
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
147
|
-
fail(`${path}[${index}] must be a non-empty string`);
|
|
148
|
-
}
|
|
149
|
-
return entry.trim();
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
129
|
function isWorkspaceKindSetting(value) {
|
|
153
130
|
return (typeof value === "string" && WORKSPACE_KIND_SETTINGS.includes(value));
|
|
154
131
|
}
|
|
@@ -177,27 +154,29 @@ function normalizeSandbox(value, path) {
|
|
|
177
154
|
if (!isPlainObject(value)) {
|
|
178
155
|
fail(`${path} must be an object`);
|
|
179
156
|
}
|
|
180
|
-
|
|
157
|
+
if (Object.hasOwn(value, "template")) {
|
|
158
|
+
failRemovedConfigKey(`${path}.template`, "Groundcrew no longer creates or re-templates sdx sandboxes.");
|
|
159
|
+
}
|
|
160
|
+
if (Object.hasOwn(value, "kits")) {
|
|
161
|
+
failRemovedConfigKey(`${path}.kits`, "Groundcrew no longer creates sdx sandboxes or applies sandbox kits.");
|
|
162
|
+
}
|
|
163
|
+
const { agent, setupCommand } = value;
|
|
181
164
|
requireString(agent, `${path}.agent`);
|
|
182
165
|
const trimmedAgent = agent.trim();
|
|
183
166
|
if (trimmedAgent.length === 0) {
|
|
184
167
|
fail(`${path}.agent must be a non-empty string (got ${JSON.stringify(agent)})`);
|
|
185
168
|
}
|
|
186
169
|
const sandbox = { agent: trimmedAgent };
|
|
187
|
-
const normalizedTemplate = normalizeOptionalString(template, `${path}.template`);
|
|
188
|
-
if (normalizedTemplate !== undefined) {
|
|
189
|
-
sandbox.template = normalizedTemplate;
|
|
190
|
-
}
|
|
191
|
-
const normalizedKits = normalizeOptionalStringArray(kits, `${path}.kits`);
|
|
192
|
-
if (normalizedKits !== undefined) {
|
|
193
|
-
sandbox.kits = normalizedKits;
|
|
194
|
-
}
|
|
195
170
|
const normalizedSetup = normalizeOptionalString(setupCommand, `${path}.setupCommand`);
|
|
196
171
|
if (normalizedSetup !== undefined) {
|
|
197
172
|
sandbox.setupCommand = normalizedSetup;
|
|
198
173
|
}
|
|
199
174
|
return sandbox;
|
|
200
175
|
}
|
|
176
|
+
function failRemovedConfigKey(path, reason) {
|
|
177
|
+
fail(`${path} is no longer supported: ${reason} ` +
|
|
178
|
+
"Provision and manage the sandbox yourself with `sbx` (for example `sbx create --name groundcrew-<agent> <agent> <projectDir>`), then keep only `models.definitions.<model>.sandbox.agent` plus optional `setupCommand` in crew.config.ts.");
|
|
179
|
+
}
|
|
201
180
|
function failIfLegacyModelKeys(name, override) {
|
|
202
181
|
if (!isPlainObject(override)) {
|
|
203
182
|
fail(`models.definitions.${name} must be an object`);
|
|
@@ -296,11 +275,6 @@ function requireObject(value, path) {
|
|
|
296
275
|
fail(`${path} must be an object (got ${JSON.stringify(value)})`);
|
|
297
276
|
}
|
|
298
277
|
}
|
|
299
|
-
function requireOptionalObject(value, path) {
|
|
300
|
-
if (value !== undefined && !isPlainObject(value)) {
|
|
301
|
-
fail(`${path} must be an object`);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
278
|
function failOnLegacyLinearShape(user) {
|
|
305
279
|
if (!Object.hasOwn(user, "linear")) {
|
|
306
280
|
return;
|
|
@@ -312,6 +286,21 @@ function failOnLegacyLinearShape(user) {
|
|
|
312
286
|
"If you only want a subset of your Linear tickets to be picked up, leave the unwanted tickets unassigned or remove their `agent-*` label.",
|
|
313
287
|
].join("\n"));
|
|
314
288
|
}
|
|
289
|
+
function failOnRemovedSandboxSettings(user) {
|
|
290
|
+
const { sandbox } = user;
|
|
291
|
+
if (sandbox === undefined) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (!isPlainObject(sandbox)) {
|
|
295
|
+
fail("sandbox must be an object");
|
|
296
|
+
}
|
|
297
|
+
if (Object.hasOwn(sandbox, "authRecipes")) {
|
|
298
|
+
failRemovedConfigKey("sandbox.authRecipes", "Groundcrew no longer drives in-sandbox auth flows.");
|
|
299
|
+
}
|
|
300
|
+
if (Object.hasOwn(sandbox, "gitDefaults")) {
|
|
301
|
+
failRemovedConfigKey("sandbox.gitDefaults", "Groundcrew no longer seeds git defaults inside sdx sandboxes.");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
315
304
|
function normalizeSources(raw) {
|
|
316
305
|
if (raw === undefined) {
|
|
317
306
|
return [];
|
|
@@ -348,71 +337,14 @@ function normalizeSources(raw) {
|
|
|
348
337
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- structural validation above guarantees array of {kind: string} entries; per-source Zod validation lives in buildSources
|
|
349
338
|
return raw;
|
|
350
339
|
}
|
|
351
|
-
function normalizeAuthRecipes(value, path) {
|
|
352
|
-
if (value === undefined) {
|
|
353
|
-
return {};
|
|
354
|
-
}
|
|
355
|
-
if (!isPlainObject(value)) {
|
|
356
|
-
fail(`${path} must be an object`);
|
|
357
|
-
}
|
|
358
|
-
const recipes = {};
|
|
359
|
-
for (const [key, raw] of Object.entries(value)) {
|
|
360
|
-
const recipePath = `${path}.${key}`;
|
|
361
|
-
if (!isPlainObject(raw)) {
|
|
362
|
-
fail(`${recipePath} must be an object`);
|
|
363
|
-
}
|
|
364
|
-
const { displayName, binary, loginArgs, statusArgs, authenticatedPattern, kind, env } = raw;
|
|
365
|
-
requireString(displayName, `${recipePath}.displayName`);
|
|
366
|
-
const loginArray = normalizeOptionalStringArray(loginArgs, `${recipePath}.loginArgs`);
|
|
367
|
-
const statusArray = normalizeOptionalStringArray(statusArgs, `${recipePath}.statusArgs`);
|
|
368
|
-
if (loginArray === undefined) {
|
|
369
|
-
fail(`${recipePath}.loginArgs is required`);
|
|
370
|
-
}
|
|
371
|
-
if (statusArray === undefined) {
|
|
372
|
-
fail(`${recipePath}.statusArgs is required`);
|
|
373
|
-
}
|
|
374
|
-
if (!(authenticatedPattern instanceof RegExp)) {
|
|
375
|
-
fail(`${recipePath}.authenticatedPattern must be a RegExp`);
|
|
376
|
-
}
|
|
377
|
-
const recipe = {
|
|
378
|
-
displayName,
|
|
379
|
-
loginArgs: loginArray,
|
|
380
|
-
statusArgs: statusArray,
|
|
381
|
-
authenticatedPattern,
|
|
382
|
-
};
|
|
383
|
-
const binaryString = normalizeOptionalString(binary, `${recipePath}.binary`);
|
|
384
|
-
if (binaryString !== undefined) {
|
|
385
|
-
recipe.binary = binaryString;
|
|
386
|
-
}
|
|
387
|
-
if (kind !== undefined) {
|
|
388
|
-
if (kind !== "agent" && kind !== "tool") {
|
|
389
|
-
fail(`${recipePath}.kind must be "agent" or "tool"`);
|
|
390
|
-
}
|
|
391
|
-
recipe.kind = kind;
|
|
392
|
-
}
|
|
393
|
-
if (env !== undefined) {
|
|
394
|
-
if (!isPlainObject(env)) {
|
|
395
|
-
fail(`${recipePath}.env must be an object`);
|
|
396
|
-
}
|
|
397
|
-
const normalizedEnv = {};
|
|
398
|
-
for (const [envKey, envValue] of Object.entries(env)) {
|
|
399
|
-
if (typeof envValue !== "string") {
|
|
400
|
-
fail(`${recipePath}.env.${envKey} must be a string`);
|
|
401
|
-
}
|
|
402
|
-
normalizedEnv[envKey] = envValue;
|
|
403
|
-
}
|
|
404
|
-
recipe.env = normalizedEnv;
|
|
405
|
-
}
|
|
406
|
-
recipes[key] = recipe;
|
|
407
|
-
}
|
|
408
|
-
return recipes;
|
|
409
|
-
}
|
|
410
340
|
function applyDefaults(user) {
|
|
411
341
|
// Guard the top-level shape before reading nested fields, so a
|
|
412
342
|
// malformed runtime config produces a `groundcrew config: ...` error
|
|
413
343
|
// instead of a raw `TypeError: Cannot read properties of undefined`.
|
|
414
344
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- `user` is loosely typed input from the loader; we narrow with requireObject below
|
|
415
|
-
|
|
345
|
+
const rawUser = user;
|
|
346
|
+
failOnLegacyLinearShape(rawUser);
|
|
347
|
+
failOnRemovedSandboxSettings(rawUser);
|
|
416
348
|
requireObject(user.workspace, "workspace");
|
|
417
349
|
if (isPlainObject(user.models) && Object.hasOwn(user.models, "isolation")) {
|
|
418
350
|
fail("models.isolation is no longer supported: set `local.runner` ('safehouse' | 'sdx' | 'none' | 'auto') instead");
|
|
@@ -420,7 +352,6 @@ function applyDefaults(user) {
|
|
|
420
352
|
if (Object.hasOwn(user, "remote")) {
|
|
421
353
|
fail("remote is no longer supported: groundcrew runs locally via safehouse/sdx/none; remove the remote block from your config");
|
|
422
354
|
}
|
|
423
|
-
requireOptionalObject(user.sandbox, "sandbox");
|
|
424
355
|
const userLocal = user.local;
|
|
425
356
|
if (userLocal !== undefined && !isPlainObject(userLocal)) {
|
|
426
357
|
fail("local must be an object");
|
|
@@ -445,10 +376,6 @@ function applyDefaults(user) {
|
|
|
445
376
|
local: {
|
|
446
377
|
runner: normalizeLocalRunner(userLocal?.runner, "local.runner") ?? "auto",
|
|
447
378
|
},
|
|
448
|
-
sandbox: {
|
|
449
|
-
authRecipes: normalizeAuthRecipes(user.sandbox?.authRecipes, "sandbox.authRecipes"),
|
|
450
|
-
gitDefaults: normalizeOptionalBoolean(user.sandbox?.gitDefaults, "sandbox.gitDefaults") ?? true,
|
|
451
|
-
},
|
|
452
379
|
logging: {
|
|
453
380
|
file: expandHome(normalizeOptionalString(user.logging?.file, "logging.file") ?? defaultLogFile()),
|
|
454
381
|
},
|
|
@@ -36,9 +36,9 @@ interface LaunchCommandArguments {
|
|
|
36
36
|
runner: LocalRunner;
|
|
37
37
|
/**
|
|
38
38
|
* sbx sandbox name when `runner === "sdx"`. Derived by the caller from
|
|
39
|
-
* `sandboxNameFor({
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* `sandboxNameFor({ agent })`. Required for sdx; ignored otherwise.
|
|
40
|
+
* Kept off the model definition so a model can launch under safehouse
|
|
41
|
+
* on one host and sdx on another without config edits.
|
|
42
42
|
*/
|
|
43
43
|
sandboxName?: string | undefined;
|
|
44
44
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the sbx sandbox name groundcrew expects for a given sbx agent.
|
|
3
|
+
* Groundcrew only addresses this existing sandbox at launch time; it does
|
|
4
|
+
* not probe, create, mutate, or remove it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sandboxNameFor(arguments_: {
|
|
7
|
+
agent: string;
|
|
8
|
+
}): string;
|
|
9
|
+
//# sourceMappingURL=sandboxName.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandboxName.d.ts","sourceRoot":"","sources":["../../src/lib/sandboxName.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMpE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the sbx sandbox name groundcrew expects for a given sbx agent.
|
|
3
|
+
* Groundcrew only addresses this existing sandbox at launch time; it does
|
|
4
|
+
* not probe, create, mutate, or remove it.
|
|
5
|
+
*/
|
|
6
|
+
export function sandboxNameFor(arguments_) {
|
|
7
|
+
const raw = `groundcrew-${arguments_.agent}`.toLowerCase();
|
|
8
|
+
return raw
|
|
9
|
+
.replaceAll(/[^a-z0-9.+-]+/g, "-")
|
|
10
|
+
.replaceAll(/-+/g, "-")
|
|
11
|
+
.replaceAll(/^-|-$/g, "");
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux Workspace backend. Workspaces live as windows inside one dedicated
|
|
3
|
+
* `groundcrew` tmux session; the window name is the ticket id. tmux can't
|
|
4
|
+
* paint status pills, so `open` silently drops `spec.status`. This is the
|
|
5
|
+
* Linux/WSL path where cmux is unavailable.
|
|
6
|
+
*/
|
|
7
|
+
import { type Adapter } from "./workspaceAdapter.ts";
|
|
8
|
+
export declare const tmuxAdapter: Adapter;
|
|
9
|
+
//# sourceMappingURL=tmuxAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmuxAdapter.d.ts","sourceRoot":"","sources":["../../src/lib/tmuxAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,OAAO,EAIb,MAAM,uBAAuB,CAAC;AAY/B,eAAO,MAAM,WAAW,EAAE,OAgEzB,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux Workspace backend. Workspaces live as windows inside one dedicated
|
|
3
|
+
* `groundcrew` tmux session; the window name is the ticket id. tmux can't
|
|
4
|
+
* paint status pills, so `open` silently drops `spec.status`. This is the
|
|
5
|
+
* Linux/WSL path where cmux is unavailable.
|
|
6
|
+
*/
|
|
7
|
+
import { isSignalAborted, runWorkspaceCommand, } from "./workspaceAdapter.js";
|
|
8
|
+
import { errorMessage, log, readEnvironmentVariable } from "./util.js";
|
|
9
|
+
const TMUX_SESSION = "groundcrew";
|
|
10
|
+
// `tmux new-session -d -s …` always creates one initial window. Without
|
|
11
|
+
// `-n`, that window is named after the running shell (e.g. "0" / "zsh") and
|
|
12
|
+
// would surface from `list()` as a phantom workspace. We name it with this
|
|
13
|
+
// sentinel and filter it out — it stays around as a placeholder so the
|
|
14
|
+
// session doesn't collapse when the last ticket window closes.
|
|
15
|
+
const TMUX_IDLE_WINDOW = "_groundcrew_idle";
|
|
16
|
+
export const tmuxAdapter = {
|
|
17
|
+
async open(spec, signal) {
|
|
18
|
+
await ensureTmuxSession(signal);
|
|
19
|
+
const target = tmuxTarget(spec.name);
|
|
20
|
+
const keepDeadWindowsEnv = readEnvironmentVariable("GROUNDCREW_KEEP_DEAD_WINDOWS");
|
|
21
|
+
const keepDeadWindows = keepDeadWindowsEnv !== undefined && keepDeadWindowsEnv.length > 0;
|
|
22
|
+
await runWorkspaceCommand("tmux", [
|
|
23
|
+
"new-window",
|
|
24
|
+
"-d",
|
|
25
|
+
"-t",
|
|
26
|
+
TMUX_SESSION,
|
|
27
|
+
"-n",
|
|
28
|
+
spec.name,
|
|
29
|
+
"-c",
|
|
30
|
+
spec.cwd,
|
|
31
|
+
spec.command,
|
|
32
|
+
";",
|
|
33
|
+
"set-window-option",
|
|
34
|
+
"-t",
|
|
35
|
+
target,
|
|
36
|
+
"remain-on-exit",
|
|
37
|
+
keepDeadWindows ? "on" : "off",
|
|
38
|
+
";",
|
|
39
|
+
"set-window-option",
|
|
40
|
+
"-t",
|
|
41
|
+
target,
|
|
42
|
+
"allow-rename",
|
|
43
|
+
"off",
|
|
44
|
+
], signal);
|
|
45
|
+
// tmux can't paint status pills; spec.status is silently dropped.
|
|
46
|
+
},
|
|
47
|
+
async list(signal) {
|
|
48
|
+
const probe = await probeTmuxList("#{window_name}\t#{pane_dead}", signal);
|
|
49
|
+
if (probe.status === "missing") {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
if (probe.status === "failed") {
|
|
53
|
+
log(`tmux list-windows failed: ${probe.reason}`);
|
|
54
|
+
// oxlint-disable-next-line unicorn/no-useless-undefined -- undefined marks the workspace backend as unavailable.
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return parseTmuxWindows(probe.output);
|
|
58
|
+
},
|
|
59
|
+
async close(name, signal) {
|
|
60
|
+
try {
|
|
61
|
+
await runWorkspaceCommand("tmux", ["kill-window", "-t", tmuxTarget(name)], signal);
|
|
62
|
+
return { kind: "closed" };
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (isSignalAborted(signal)) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
if (isTmuxNotFoundError(error)) {
|
|
69
|
+
return { kind: "missing" };
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
accessHint(name) {
|
|
75
|
+
return { kind: "attachCommand", command: `tmux attach -t ${tmuxTarget(name)}` };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
function tmuxTarget(name) {
|
|
79
|
+
return `${TMUX_SESSION}:${name}`;
|
|
80
|
+
}
|
|
81
|
+
function isTmuxNotFoundError(error) {
|
|
82
|
+
// runCommand surfaces the child's stderr in error.message, so the "no
|
|
83
|
+
// server" / "missing session" / "can't find window" signatures are visible
|
|
84
|
+
// without a separate stderr probe.
|
|
85
|
+
const message = errorMessage(error);
|
|
86
|
+
return (message.includes("no server running") ||
|
|
87
|
+
message.includes("can't find session") ||
|
|
88
|
+
message.includes("can't find window"));
|
|
89
|
+
}
|
|
90
|
+
async function probeTmuxList(format, signal) {
|
|
91
|
+
try {
|
|
92
|
+
return {
|
|
93
|
+
status: "ok",
|
|
94
|
+
output: await runWorkspaceCommand("tmux", ["list-windows", "-t", TMUX_SESSION, "-F", format], signal),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (isSignalAborted(signal)) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (isTmuxNotFoundError(error)) {
|
|
102
|
+
return { status: "missing" };
|
|
103
|
+
}
|
|
104
|
+
return { status: "failed", reason: errorMessage(error) };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function ensureTmuxSession(signal) {
|
|
108
|
+
try {
|
|
109
|
+
await runWorkspaceCommand("tmux", ["has-session", "-t", TMUX_SESSION], signal);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (isSignalAborted(signal)) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
/* session missing or server down; create it */
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await runWorkspaceCommand("tmux", ["new-session", "-d", "-s", TMUX_SESSION, "-n", TMUX_IDLE_WINDOW], signal);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (isSignalAborted(signal)) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await runWorkspaceCommand("tmux", ["has-session", "-t", TMUX_SESSION], signal);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function parseTmuxWindows(output) {
|
|
134
|
+
const items = [];
|
|
135
|
+
for (const line of output.split("\n")) {
|
|
136
|
+
if (line.length === 0) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const [name, deadFlag] = line.split("\t");
|
|
140
|
+
/* v8 ignore next 3 @preserve -- split on a non-empty string always yields a non-empty first element */
|
|
141
|
+
if (name === undefined || name.length === 0) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (name === TMUX_IDLE_WINDOW) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// pane_dead != 0 means the command exited and the window is a zombie
|
|
148
|
+
// (only happens when remain-on-exit is on; defense in depth in case a
|
|
149
|
+
// user-globally-set value beats our per-window override).
|
|
150
|
+
if (deadFlag !== undefined && deadFlag !== "0") {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
items.push({ name });
|
|
154
|
+
}
|
|
155
|
+
return items;
|
|
156
|
+
}
|
package/dist/lib/util.d.ts
CHANGED
|
@@ -34,6 +34,17 @@ export declare function lazyLinearClient(factory: () => LinearClient): () => Lin
|
|
|
34
34
|
* each subcommand's arg parser stays DRY.
|
|
35
35
|
*/
|
|
36
36
|
export declare function readTicketArgument(argv: string[], index: number, command: string): string;
|
|
37
|
+
export interface DryRunPositionals {
|
|
38
|
+
dryRun: boolean;
|
|
39
|
+
positionals: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parses an argv that accepts an optional `--dry-run` flag plus free
|
|
43
|
+
* positionals, rejecting any other dash-prefixed token. Shared by the
|
|
44
|
+
* subcommands whose only flag is `--dry-run` so each parser stays DRY; pass the
|
|
45
|
+
* command's `usage` string for the "Unknown option" error.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseDryRunPositionals(argv: string[], usage: string): DryRunPositionals;
|
|
37
48
|
export declare function errorMessage(error: unknown): string;
|
|
38
49
|
export {};
|
|
39
50
|
//# sourceMappingURL=util.d.ts.map
|
package/dist/lib/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
|
package/dist/lib/util.js
CHANGED
|
@@ -146,6 +146,27 @@ export function readTicketArgument(argv, index, command) {
|
|
|
146
146
|
}
|
|
147
147
|
return value;
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Parses an argv that accepts an optional `--dry-run` flag plus free
|
|
151
|
+
* positionals, rejecting any other dash-prefixed token. Shared by the
|
|
152
|
+
* subcommands whose only flag is `--dry-run` so each parser stays DRY; pass the
|
|
153
|
+
* command's `usage` string for the "Unknown option" error.
|
|
154
|
+
*/
|
|
155
|
+
export function parseDryRunPositionals(argv, usage) {
|
|
156
|
+
let dryRun = false;
|
|
157
|
+
const positionals = [];
|
|
158
|
+
for (const argument of argv) {
|
|
159
|
+
if (argument === "--dry-run") {
|
|
160
|
+
dryRun = true;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (argument.startsWith("-")) {
|
|
164
|
+
throw new Error(`Unknown option: ${argument}\nUsage: ${usage}`);
|
|
165
|
+
}
|
|
166
|
+
positionals.push(argument);
|
|
167
|
+
}
|
|
168
|
+
return { dryRun, positionals };
|
|
169
|
+
}
|
|
149
170
|
export function errorMessage(error) {
|
|
150
171
|
if (error instanceof Error) {
|
|
151
172
|
return error.message;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contract for Workspace backends. A Workspace is the host-side
|
|
3
|
+
* terminal session that runs an agent for one ticket; `Workspace.name` is
|
|
4
|
+
* the ticket id callers key on. The cmux and tmux adapters implement this
|
|
5
|
+
* interface in their own files (`cmuxAdapter.ts`, `tmuxAdapter.ts`);
|
|
6
|
+
* `workspaces.ts` resolves and fronts them. This is internal cleanup, not a
|
|
7
|
+
* plugin contract — nothing here is a published extension point.
|
|
8
|
+
*/
|
|
9
|
+
export type WorkspaceKind = "cmux" | "tmux";
|
|
10
|
+
export interface Workspace {
|
|
11
|
+
/** Ticket id; the join key callers use. */
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkspaceStatus {
|
|
15
|
+
text: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkspaceAccessHint {
|
|
20
|
+
kind: "attachCommand";
|
|
21
|
+
command: string;
|
|
22
|
+
}
|
|
23
|
+
export interface OpenSpec {
|
|
24
|
+
/** Ticket id; becomes the workspace's name. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Working directory the workspace runs in. */
|
|
27
|
+
cwd: string;
|
|
28
|
+
/** Shell string the workspace executes (host setup + agent exec). */
|
|
29
|
+
command: string;
|
|
30
|
+
/** Optional status painting. Adapters that can't paint silently drop it. */
|
|
31
|
+
status?: WorkspaceStatus;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `unavailable` is "we don't know" — never treat it as "empty," or callers
|
|
35
|
+
* would close every live workspace by deduction.
|
|
36
|
+
*/
|
|
37
|
+
export type WorkspaceProbe = {
|
|
38
|
+
kind: "ok";
|
|
39
|
+
names: Set<string>;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "unavailable";
|
|
42
|
+
error?: unknown;
|
|
43
|
+
};
|
|
44
|
+
export type WorkspaceInterruptResult = {
|
|
45
|
+
kind: "interrupted";
|
|
46
|
+
} | {
|
|
47
|
+
kind: "missing";
|
|
48
|
+
} | {
|
|
49
|
+
kind: "unavailable";
|
|
50
|
+
error?: unknown;
|
|
51
|
+
};
|
|
52
|
+
export type WorkspaceCloseResult = {
|
|
53
|
+
kind: "closed";
|
|
54
|
+
} | {
|
|
55
|
+
kind: "missing";
|
|
56
|
+
} | {
|
|
57
|
+
kind: "unavailable";
|
|
58
|
+
error?: unknown;
|
|
59
|
+
};
|
|
60
|
+
export interface Adapter {
|
|
61
|
+
open(spec: OpenSpec, signal?: AbortSignal): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Live workspaces only. Returns:
|
|
64
|
+
* - `Workspace[]` when the adapter probe succeeded (may be empty).
|
|
65
|
+
* - `undefined` when the adapter binary failed in a way that doesn't
|
|
66
|
+
* distinguish "no live workspaces" from "couldn't ask".
|
|
67
|
+
*/
|
|
68
|
+
list(signal?: AbortSignal): Promise<Workspace[] | undefined>;
|
|
69
|
+
/** Closes the workspace or confirms it is not present. */
|
|
70
|
+
close(name: string, signal?: AbortSignal): Promise<WorkspaceCloseResult>;
|
|
71
|
+
/**
|
|
72
|
+
* User-facing way to reach the workspace, or `undefined` when the backend
|
|
73
|
+
* has no concise external hint.
|
|
74
|
+
*/
|
|
75
|
+
accessHint(name: string): WorkspaceAccessHint | undefined;
|
|
76
|
+
}
|
|
77
|
+
export declare function runWorkspaceCommand(command: string, arguments_: readonly string[], signal?: AbortSignal): Promise<string>;
|
|
78
|
+
export declare function isSignalAborted(signal?: AbortSignal): boolean;
|
|
79
|
+
//# sourceMappingURL=workspaceAdapter.d.ts.map
|