@clipboard-health/groundcrew 3.1.2 → 3.1.3

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.
Files changed (39) hide show
  1. package/README.md +31 -2
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -0
  4. package/dist/commands/cleaner.d.ts.map +1 -1
  5. package/dist/commands/cleaner.js +2 -0
  6. package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
  7. package/dist/commands/cleanupWorkspace.js +2 -0
  8. package/dist/commands/interruptWorkspace.d.ts +8 -0
  9. package/dist/commands/interruptWorkspace.d.ts.map +1 -0
  10. package/dist/commands/interruptWorkspace.js +108 -0
  11. package/dist/commands/resumeWorkspace.d.ts +7 -0
  12. package/dist/commands/resumeWorkspace.d.ts.map +1 -0
  13. package/dist/commands/resumeWorkspace.js +163 -0
  14. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  15. package/dist/commands/setupWorkspace.js +77 -79
  16. package/dist/commands/ticketDoctor.d.ts +18 -3
  17. package/dist/commands/ticketDoctor.d.ts.map +1 -1
  18. package/dist/commands/ticketDoctor.js +77 -8
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/lib/agentLaunch.d.ts +29 -0
  23. package/dist/lib/agentLaunch.d.ts.map +1 -0
  24. package/dist/lib/agentLaunch.js +53 -0
  25. package/dist/lib/runState.d.ts +46 -0
  26. package/dist/lib/runState.d.ts.map +1 -0
  27. package/dist/lib/runState.js +137 -0
  28. package/dist/lib/runStateCleanup.d.ts +4 -0
  29. package/dist/lib/runStateCleanup.d.ts.map +1 -0
  30. package/dist/lib/runStateCleanup.js +12 -0
  31. package/dist/lib/stagedLaunch.d.ts +32 -0
  32. package/dist/lib/stagedLaunch.d.ts.map +1 -0
  33. package/dist/lib/stagedLaunch.js +58 -0
  34. package/dist/lib/workspaces.d.ts +19 -1
  35. package/dist/lib/workspaces.d.ts.map +1 -1
  36. package/dist/lib/workspaces.js +29 -9
  37. package/dist/lib/worktrees.d.ts.map +1 -1
  38. package/dist/lib/worktrees.js +12 -4
  39. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { BUILD_SECRET_NAMES } from "./config.js";
5
+ import { shellSingleQuote } from "./launchCommand.js";
6
+ import { readEnvironmentVariable } from "./util.js";
7
+ function renderPromptTemplate(template, variables) {
8
+ return template
9
+ .replaceAll("{{ticket}}", variables.ticket)
10
+ .replaceAll("{{worktree}}", variables.worktree)
11
+ .replaceAll("{{title}}", variables.title)
12
+ .replaceAll("{{description}}", variables.description);
13
+ }
14
+ export function stagePromptText(input) {
15
+ const promptDir = mkdtempSync(join(tmpdir(), `${input.prefix}-${input.ticket}-`));
16
+ const promptFile = join(promptDir, "prompt.txt");
17
+ writeFileSync(promptFile, input.text);
18
+ return { directory: promptDir, file: promptFile };
19
+ }
20
+ export function stagePromptFromTemplate(input) {
21
+ return stagePromptText({
22
+ prefix: input.prefix,
23
+ ticket: input.ticket,
24
+ text: renderPromptTemplate(input.config.prompts.initial, input.variables),
25
+ });
26
+ }
27
+ /**
28
+ * Stage a `KEY='value'` env file for any populated build-time secret so
29
+ * the launch command can source it. Returns `undefined` when groundcrew
30
+ * has nothing to forward, leaving the launch command unchanged.
31
+ */
32
+ export function stageBuildSecrets(promptDir) {
33
+ const lines = [];
34
+ for (const name of BUILD_SECRET_NAMES) {
35
+ const value = readEnvironmentVariable(name);
36
+ if (value === undefined || value.length === 0) {
37
+ continue;
38
+ }
39
+ lines.push(`${name}=${shellSingleQuote(value)}`);
40
+ }
41
+ if (lines.length === 0) {
42
+ return undefined;
43
+ }
44
+ const secretsFile = join(promptDir, "secrets.env");
45
+ writeFileSync(secretsFile, `${lines.join("\n")}\n`, { mode: 0o600 });
46
+ return secretsFile;
47
+ }
48
+ function stageLaunchScript(promptDir, command) {
49
+ const launcherFile = join(promptDir, "launch.sh");
50
+ writeFileSync(launcherFile, `#!/usr/bin/env bash\n${command}\n`, { mode: 0o700 });
51
+ return launcherFile;
52
+ }
53
+ export function stageWorkspaceLaunchCommand(promptDir, command) {
54
+ return `bash ${shellSingleQuote(stageLaunchScript(promptDir, command))}`;
55
+ }
56
+ export function removeStagedPrompt(directory) {
57
+ rmSync(directory, { recursive: true, force: true });
58
+ }
@@ -41,6 +41,22 @@ export type WorkspaceProbe = {
41
41
  kind: "unavailable";
42
42
  error?: unknown;
43
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
+ };
44
60
  export interface WorkspaceResolution {
45
61
  requested: WorkspaceKindSetting;
46
62
  resolved: WorkspaceKind;
@@ -53,10 +69,12 @@ interface ResolveArguments {
53
69
  }
54
70
  export declare function resolveWorkspaceKind(arguments_: ResolveArguments): WorkspaceResolution;
55
71
  declare function probeWorkspaces(config: ResolvedConfig, signal?: AbortSignal): Promise<WorkspaceProbe>;
72
+ declare function interruptWorkspace(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceInterruptResult>;
56
73
  export declare const workspaces: {
57
74
  open(config: ResolvedConfig, spec: OpenSpec, signal?: AbortSignal): Promise<void>;
58
75
  probe: typeof probeWorkspaces;
59
- close(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<void>;
76
+ close(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceCloseResult>;
77
+ interrupt: typeof interruptWorkspace;
60
78
  accessHint(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceAccessHint | undefined>;
61
79
  };
62
80
  export {};
@@ -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;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;AAsU7C,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"}
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;AAE7C,MAAM,MAAM,wBAAwB,GAChC;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAwU7C,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;AAgOD,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,iBAAe,kBAAkB,CAC/B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAenC;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SACD,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC;IAIhC,SAAS;IACH,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}
@@ -226,25 +226,27 @@ const cmuxAdapter = {
226
226
  // would always fail. The list failure has already been logged by
227
227
  // `listCmuxRaw`; bail rather than guarantee a downstream error.
228
228
  log(`cmux close-workspace skipped for ${name}: list-workspaces failed, no usable id`);
229
- return;
229
+ return { kind: "unavailable" };
230
230
  }
231
231
  const match = raw.find((ws) => ws.title === name);
232
232
  if (match === undefined) {
233
- return;
233
+ return { kind: "missing" };
234
234
  }
235
235
  try {
236
236
  await closeCmuxWorkspace(match.id, signal);
237
+ return { kind: "closed" };
237
238
  }
238
239
  catch (error) {
239
240
  if (isSignalAborted(signal)) {
240
241
  throw error;
241
242
  }
242
243
  const remaining = await listCmuxRaw(signal);
243
- if (remaining !== undefined) {
244
- const isStillPresent = remaining.some((ws) => ws.title === name);
245
- if (!isStillPresent) {
246
- return;
247
- }
244
+ if (remaining === undefined) {
245
+ return { kind: "unavailable", error };
246
+ }
247
+ const isStillPresent = remaining.some((ws) => ws.title === name);
248
+ if (!isStillPresent) {
249
+ return { kind: "closed" };
248
250
  }
249
251
  throw error;
250
252
  }
@@ -420,13 +422,14 @@ const tmuxAdapter = {
420
422
  async close(name, signal) {
421
423
  try {
422
424
  await runWorkspaceCommand("tmux", ["kill-window", "-t", tmuxTarget(name)], signal);
425
+ return { kind: "closed" };
423
426
  }
424
427
  catch (error) {
425
428
  if (isSignalAborted(signal)) {
426
429
  throw error;
427
430
  }
428
431
  if (isTmuxNotFoundError(error)) {
429
- return;
432
+ return { kind: "missing" };
430
433
  }
431
434
  throw error;
432
435
  }
@@ -469,6 +472,22 @@ async function probeWorkspaces(config, signal) {
469
472
  }
470
473
  return { kind: "ok", names: new Set(raw.map((ws) => ws.name)) };
471
474
  }
475
+ async function interruptWorkspace(config, name, signal) {
476
+ const probe = await probeWorkspaces(config, signal);
477
+ if (probe.kind === "unavailable") {
478
+ return { kind: "unavailable", ...(probe.error === undefined ? {} : { error: probe.error }) };
479
+ }
480
+ if (!probe.names.has(name)) {
481
+ return { kind: "missing" };
482
+ }
483
+ const result = await workspaces.close(config, name, signal);
484
+ if (result.kind === "unavailable") {
485
+ return result.error === undefined
486
+ ? { kind: "unavailable" }
487
+ : { kind: "unavailable", error: result.error };
488
+ }
489
+ return { kind: "interrupted" };
490
+ }
472
491
  export const workspaces = {
473
492
  async open(config, spec, signal) {
474
493
  const adapter = await adapterFor(config, signal);
@@ -477,8 +496,9 @@ export const workspaces = {
477
496
  probe: probeWorkspaces,
478
497
  async close(config, name, signal) {
479
498
  const adapter = await adapterFor(config, signal);
480
- await adapter.close(name, signal);
499
+ return await adapter.close(name, signal);
481
500
  },
501
+ interrupt: interruptWorkspace,
482
502
  async accessHint(config, name, signal) {
483
503
  const adapter = await adapterFor(config, signal);
484
504
  return adapter.accessHint(name);
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAqOD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AA6ExB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAKD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAkDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
1
+ {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAqOD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AA6ExB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE7E;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAyBD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
@@ -284,6 +284,13 @@ async function remove(config, entry, options) {
284
284
  ...signalProperty(options?.signal),
285
285
  });
286
286
  }
287
+ async function closeWorkspaceForTeardown(config, ticket, signal) {
288
+ const closeResult = await workspaces.close(config, ticket, signal);
289
+ return closeResult.kind === "closed";
290
+ }
291
+ function shouldCloseWorkspaceForTeardown(ticket, workspaceProbe, liveNames, closedTickets) {
292
+ return (!closedTickets.has(ticket) && (workspaceProbe.kind === "unavailable" || liveNames.has(ticket)));
293
+ }
287
294
  // A flaky cmux/tmux must not abort the batch — otherwise every on-disk
288
295
  // worktree gets stranded. The probe verdict is captured on the result and
289
296
  // removal proceeds with no live-workspace knowledge (so no close attempts).
@@ -307,12 +314,13 @@ async function teardown(config, entries, options) {
307
314
  workspaceProbe,
308
315
  };
309
316
  for (const entry of entries) {
310
- if (!closedTickets.has(entry.ticket) &&
311
- (workspaceProbe.kind === "unavailable" || liveNames.has(entry.ticket))) {
317
+ if (shouldCloseWorkspaceForTeardown(entry.ticket, workspaceProbe, liveNames, closedTickets)) {
312
318
  try {
313
319
  // oxlint-disable-next-line no-await-in-loop -- teardown is intentionally sequential per ticket
314
- await workspaces.close(config, entry.ticket, options?.signal);
315
- result.closed.push(entry.ticket);
320
+ const closed = await closeWorkspaceForTeardown(config, entry.ticket, options?.signal);
321
+ if (closed) {
322
+ result.closed.push(entry.ticket);
323
+ }
316
324
  }
317
325
  catch (error) {
318
326
  if (options?.signal?.aborted === true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",