@clipboard-health/groundcrew 4.48.0 → 4.48.2

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 (54) hide show
  1. package/clearance-allow-hosts +1 -0
  2. package/crew.config.example.ts +2 -2
  3. package/dist/commands/doctor.js +0 -25
  4. package/dist/commands/openWorkspace.d.ts.map +1 -1
  5. package/dist/commands/openWorkspace.js +14 -17
  6. package/dist/commands/resumeWorkspace.d.ts.map +1 -1
  7. package/dist/commands/resumeWorkspace.js +7 -15
  8. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  9. package/dist/commands/setupWorkspace.js +16 -18
  10. package/dist/lib/agentLaunch.d.ts +13 -11
  11. package/dist/lib/agentLaunch.d.ts.map +1 -1
  12. package/dist/lib/agentLaunch.js +102 -52
  13. package/dist/lib/cmuxAgentHookInstall.d.ts +17 -0
  14. package/dist/lib/cmuxAgentHookInstall.d.ts.map +1 -0
  15. package/dist/lib/cmuxAgentHookInstall.js +42 -0
  16. package/dist/lib/codexConfigRelocation.d.ts +60 -0
  17. package/dist/lib/codexConfigRelocation.d.ts.map +1 -0
  18. package/dist/lib/codexConfigRelocation.js +70 -0
  19. package/dist/lib/commandRunner.d.ts +9 -0
  20. package/dist/lib/commandRunner.d.ts.map +1 -1
  21. package/dist/lib/commandRunner.js +1 -0
  22. package/dist/lib/config.d.ts +8 -12
  23. package/dist/lib/config.d.ts.map +1 -1
  24. package/dist/lib/config.js +1 -2
  25. package/dist/lib/gitCommonDir.d.ts +7 -0
  26. package/dist/lib/gitCommonDir.d.ts.map +1 -0
  27. package/dist/lib/gitCommonDir.js +15 -0
  28. package/dist/lib/host.d.ts +0 -14
  29. package/dist/lib/host.d.ts.map +1 -1
  30. package/dist/lib/host.js +1 -8
  31. package/dist/lib/launchCommand.d.ts +32 -52
  32. package/dist/lib/launchCommand.d.ts.map +1 -1
  33. package/dist/lib/launchCommand.js +96 -187
  34. package/dist/lib/localRunner.d.ts +4 -5
  35. package/dist/lib/localRunner.d.ts.map +1 -1
  36. package/dist/lib/localRunner.js +4 -23
  37. package/docs/configuration.md +3 -3
  38. package/docs/runners.md +7 -50
  39. package/docs/setup-hooks.md +1 -1
  40. package/docs/spec/e2e-scenario-catalog.md +1 -1
  41. package/docs/spec/v2-design.md +7 -7
  42. package/docs/task-sources.md +3 -3
  43. package/package.json +2 -5
  44. package/static/demo.tape +0 -2
  45. package/static/render-demo.sh +0 -1
  46. package/dist/lib/clearanceHosts.d.ts +0 -31
  47. package/dist/lib/clearanceHosts.d.ts.map +0 -1
  48. package/dist/lib/clearanceHosts.js +0 -96
  49. package/dist/lib/srtLaunch.d.ts +0 -61
  50. package/dist/lib/srtLaunch.d.ts.map +0 -1
  51. package/dist/lib/srtLaunch.js +0 -107
  52. package/dist/lib/srtPolicy.d.ts +0 -118
  53. package/dist/lib/srtPolicy.d.ts.map +0 -1
  54. package/dist/lib/srtPolicy.js +0 -371
@@ -29,6 +29,7 @@ api.notion.com
29
29
  api.slack.com
30
30
  app.incident.io
31
31
  developers.notion.com
32
+ developers.openai.com
32
33
  linear.app
33
34
  mcp-proxy.anthropic.com
34
35
  mcp.datadoghq.com
@@ -131,7 +131,7 @@ export default {
131
131
  // // Install via task-sources/jira (see task-sources/jira/README.md):
132
132
  // // cp task-sources/jira/jira.sh ~/.config/groundcrew/jira.sh
133
133
  // // Open local task-store directories for read/write inside the
134
- // // safehouse/srt sandbox when this source owns the launched task.
134
+ // // safehouse sandbox when this source owns the launched task.
135
135
  // sandboxWritePaths: ["~/plans"],
136
136
  // commands: {
137
137
  // verify: "~/.config/groundcrew/jira.sh verify",
@@ -179,7 +179,7 @@ export default {
179
179
  // // deny-by-default policy. Examples: `agent-browser` so the
180
180
  // // chrome-devtools MCP server can drive a headless Chrome;
181
181
  // // `browser-native-messaging` for `claude --chrome`. Ignored by the
182
- // // srt/sdx/none runners.
182
+ // // sdx/none runners.
183
183
  // local: { safehouse: { enable: ["agent-browser"] } },
184
184
  //
185
185
  // // Groundcrew does not create or authenticate sdx sandboxes. For an sdx
@@ -343,22 +343,6 @@ function localCapabilityCheck(host, resolved) {
343
343
  : "sdx runner requires `sbx` (Docker Sandboxes) on PATH (install from https://docs.docker.com/ai/sandboxes/)",
344
344
  };
345
345
  }
346
- if (resolved === "srt") {
347
- const missingLinuxDeps = host.isLinux
348
- ? [
349
- host.hasBubblewrap ? undefined : "bubblewrap",
350
- host.hasSocat ? undefined : "socat",
351
- host.hasRipgrep ? undefined : "ripgrep (rg)",
352
- ].filter((name) => name !== undefined)
353
- : [];
354
- const ok = host.isSrtSupported && missingLinuxDeps.length === 0;
355
- return {
356
- name: "local runner (srt)",
357
- ok,
358
- required: false,
359
- hint: srtRunnerHint(ok, host.isSrtSupported, missingLinuxDeps),
360
- };
361
- }
362
346
  // resolved === "none"
363
347
  return {
364
348
  name: "local runner (none)",
@@ -367,15 +351,6 @@ function localCapabilityCheck(host, resolved) {
367
351
  hint: "WARNING: local.runner='none' — agent runs unsandboxed on the host. Only use this when you understand the implications.",
368
352
  };
369
353
  }
370
- function srtRunnerHint(ok, isSrtSupported, missingLinuxDeps) {
371
- if (ok) {
372
- return "ready (beta: @anthropic-ai/sandbox-runtime is a research preview)";
373
- }
374
- if (!isSrtSupported) {
375
- return "srt runner requires macOS or Linux/WSL";
376
- }
377
- return `srt runner on Linux requires ${missingLinuxDeps.join(", ")} on PATH (Debian/Ubuntu: \`apt install bubblewrap socat ripgrep\`; on Ubuntu 24.04+ also \`sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\`)`;
378
- }
379
354
  function reportLocalCapability(arguments_) {
380
355
  writeOutput();
381
356
  writeOutput("Local runner");
@@ -1 +1 @@
1
- {"version":3,"file":"openWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/openWorkspace.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAetF,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA6JD,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA0Hf;AA4ID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAE3E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE"}
1
+ {"version":3,"file":"openWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/openWorkspace.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBtF,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAwJD,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA2Hf;AA4ID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAE3E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE"}
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, rmSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { composeAgentLaunch, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
4
  import { inferAgentCommandName } from "../lib/launchCommand.js";
@@ -7,7 +7,7 @@ import { resolvePullRequest } from "../lib/pullRequests.js";
7
7
  import { resolvePrepareWorktreeCommand } from "../lib/repositoryHooks.js";
8
8
  import { recordRunState, readRunState } from "../lib/runState.js";
9
9
  import { seedLaunchWorkspaceTrust } from "../lib/seedLaunchWorkspaceTrust.js";
10
- import { stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
10
+ import { removeStagedPrompt, stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
11
11
  import { normalizePlainTaskId } from "../lib/taskId.js";
12
12
  import { debug, errorMessage, log, okMark } from "../lib/util.js";
13
13
  import { failIfWorkspaceAlreadyLive } from "../lib/workspaceLiveness.js";
@@ -106,15 +106,11 @@ async function rollback(arguments_) {
106
106
  catch (error) {
107
107
  log(`Worktree teardown failed during rollback: ${errorMessage(error)}`);
108
108
  }
109
- for (const dir of [arguments_.promptDir, arguments_.srtSettingsDir]) {
110
- if (dir !== undefined) {
111
- try {
112
- rmSync(dir, { recursive: true, force: true });
113
- }
114
- catch {
115
- // already gone
116
- }
117
- }
109
+ try {
110
+ removeStagedPrompt(arguments_.promptDir);
111
+ }
112
+ catch {
113
+ // already gone
118
114
  }
119
115
  }
120
116
  export async function openWorkspace(config, options) {
@@ -154,7 +150,7 @@ export async function openWorkspace(config, options) {
154
150
  task: target.task,
155
151
  text: options.promptText ?? "",
156
152
  });
157
- let srtSettingsDir;
153
+ let cleanupAgentLaunch;
158
154
  try {
159
155
  const repositoryEntry = config.workspace.repositories.find((entry) => entry.name === repository);
160
156
  const prepareWorktreeCommand = resolvePrepareWorktreeCommand({
@@ -174,8 +170,7 @@ export async function openWorkspace(config, options) {
174
170
  agentCommandName: inferAgentCommandName(definition.cmd),
175
171
  launchDir,
176
172
  });
177
- let launchCommand;
178
- ({ launchCommand, srtSettingsDir } = composeAgentLaunch({
173
+ const launch = composeAgentLaunch({
179
174
  runner,
180
175
  networkEgress,
181
176
  task: target.task,
@@ -190,8 +185,9 @@ export async function openWorkspace(config, options) {
190
185
  workspaceKind,
191
186
  readOnlyDirs: config.local.readOnlyDirs,
192
187
  omitPromptArgument,
193
- }));
194
- const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
188
+ });
189
+ cleanupAgentLaunch = launch.cleanup;
190
+ const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launch.command);
195
191
  await openAgentWorkspace({
196
192
  config,
197
193
  name: target.task,
@@ -203,7 +199,8 @@ export async function openWorkspace(config, options) {
203
199
  });
204
200
  }
205
201
  catch (error) {
206
- await rollback({ config, entry: created, promptDir: stagedPrompt.directory, srtSettingsDir });
202
+ cleanupAgentLaunch?.();
203
+ await rollback({ config, entry: created, promptDir: stagedPrompt.directory });
207
204
  throw error;
208
205
  }
209
206
  recordRunState({
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAsBzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAyKD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAyGf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
1
+ {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAsBzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAyKD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiGf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -170,14 +170,9 @@ export async function resumeWorkspace(config, options) {
170
170
  text: renderResumePrompt(context),
171
171
  });
172
172
  const secretsFile = stageBuildSecrets(stagedPrompt.directory);
173
- // Resume stages srt settings exactly like setup (a relocating agent such as
174
- // codex needs its config home re-seeded to authenticate on the resumed launch).
175
- // Composition runs inside the try so a pre-launch failure still cleans up the
176
- // staged prompt (and any srt settings) dir.
177
- let srtSettingsDir;
173
+ let cleanupAgentLaunch;
178
174
  try {
179
- let launchCommand;
180
- const taskSourceWritePaths = runner === "safehouse" || runner === "srt"
175
+ const taskSourceWritePaths = runner === "safehouse"
181
176
  ? taskSourceWritePathsForCompletion({
182
177
  config,
183
178
  taskId: context.completionTaskId,
@@ -188,7 +183,7 @@ export async function resumeWorkspace(config, options) {
188
183
  agentCommandName: inferAgentCommandName(launchDefinition.cmd),
189
184
  launchDir,
190
185
  });
191
- ({ launchCommand, srtSettingsDir } = composeAgentLaunch({
186
+ const launch = composeAgentLaunch({
192
187
  runner,
193
188
  networkEgress,
194
189
  task,
@@ -206,8 +201,9 @@ export async function resumeWorkspace(config, options) {
206
201
  }),
207
202
  taskSourceWritePaths,
208
203
  safehouseEnableFeatures: config.local.safehouse.enable,
209
- }));
210
- const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
204
+ });
205
+ cleanupAgentLaunch = launch.cleanup;
206
+ const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launch.command);
211
207
  await openAgentWorkspace({
212
208
  config,
213
209
  name: task,
@@ -219,12 +215,8 @@ export async function resumeWorkspace(config, options) {
219
215
  });
220
216
  }
221
217
  catch (error) {
218
+ cleanupAgentLaunch?.();
222
219
  removeStagedPrompt(stagedPrompt.directory);
223
- // The launch command tears down the settings dir after srt exits; on the
224
- // pre-launch failure path it never ran, so clean it up here.
225
- if (srtSettingsDir !== undefined) {
226
- removeStagedPrompt(srtSettingsDir);
227
- }
228
220
  throw error;
229
221
  }
230
222
  recordRunState({
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA0BnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA2Jf;AA8MD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA2BnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA4Jf;AA2MD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
@@ -1,4 +1,3 @@
1
- import { rmSync } from "node:fs";
2
1
  import { loadConfig } from "../lib/config.js";
3
2
  import { composeAgentLaunch, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
3
  import { inferAgentCommandName, workerEnvironmentForTask } from "../lib/launchCommand.js";
@@ -8,7 +7,7 @@ import { resolvePrepareWorktreeCommand } from "../lib/repositoryHooks.js";
8
7
  import { recordRunState } from "../lib/runState.js";
9
8
  import { seedLaunchWorkspaceTrust } from "../lib/seedLaunchWorkspaceTrust.js";
10
9
  import { sourceSupportsMarkDone } from "../lib/sourceCapabilities.js";
11
- import { stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
10
+ import { removeStagedPrompt, stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
12
11
  import { taskSourceWritePathsForCompletion } from "../lib/taskSourceFilesystem.js";
13
12
  import { naturalIdFromCanonical } from "../lib/taskSource.js";
14
13
  import { debug, errorMessage, log, okMark } from "../lib/util.js";
@@ -72,7 +71,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
72
71
  // Without rollback the next tick hits "Worktree already exists" and
73
72
  // the task strands forever.
74
73
  let promptDir;
75
- let srtSettingsDir;
74
+ let cleanupAgentLaunch;
76
75
  try {
77
76
  await assertLaunchReady(readinessPromise);
78
77
  const taskDetails = options.details;
@@ -101,7 +100,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
101
100
  : stageBuildSecrets(promptDir);
102
101
  const completionTaskId = options.completionTaskId ?? task;
103
102
  const completionMarkDoneSupported = options.completionMarkDoneSupported ?? true;
104
- const taskSourceWritePaths = runner === "safehouse" || runner === "srt"
103
+ const taskSourceWritePaths = runner === "safehouse"
105
104
  ? taskSourceWritePathsForCompletion({
106
105
  config,
107
106
  taskId: completionTaskId,
@@ -112,7 +111,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
112
111
  agentCommandName: inferAgentCommandName(definition.cmd),
113
112
  launchDir,
114
113
  });
115
- const { launchCommand, srtSettingsDir: stagedSrtSettingsDir } = composeAgentLaunch({
114
+ const launch = composeAgentLaunch({
116
115
  runner,
117
116
  networkEgress,
118
117
  task,
@@ -133,8 +132,8 @@ export async function setupWorkspace(config, options, runOptions = {}) {
133
132
  taskSourceWritePaths,
134
133
  safehouseEnableFeatures: config.local.safehouse.enable,
135
134
  });
136
- srtSettingsDir = stagedSrtSettingsDir;
137
- const launchCmd = stageWorkspaceLaunchCommand(promptDir, launchCommand);
135
+ cleanupAgentLaunch = launch.cleanup;
136
+ const launchCmd = stageWorkspaceLaunchCommand(promptDir, launch.command);
138
137
  debug("Opening workspace...");
139
138
  await openAgentWorkspace({
140
139
  config,
@@ -167,7 +166,8 @@ export async function setupWorkspace(config, options, runOptions = {}) {
167
166
  }
168
167
  }
169
168
  catch (error) {
170
- await rollbackWorktree({ config, entry: created, promptDir, srtSettingsDir });
169
+ cleanupAgentLaunch?.();
170
+ await rollbackWorktree({ config, entry: created, promptDir });
171
171
  recordFailedToLaunch({ config, options, paths: { worktreeDir, branchName }, error });
172
172
  throw error;
173
173
  }
@@ -300,16 +300,14 @@ async function rollbackWorktree(arguments_) {
300
300
  log(`Worktree teardown failed during rollback: ${errorMessage(error)}`);
301
301
  }
302
302
  finally {
303
- // Both temp dirs are normally removed by the launch command; clean them
304
- // here for the pre-launch failure path. Silent on retry races.
305
- for (const dir of [arguments_.promptDir, arguments_.srtSettingsDir]) {
306
- if (dir !== undefined) {
307
- try {
308
- rmSync(dir, { recursive: true, force: true });
309
- }
310
- catch {
311
- // already gone
312
- }
303
+ // The prompt dir is normally removed by the launch command; clean it here
304
+ // for the pre-launch failure path. Silent on retry races.
305
+ if (arguments_.promptDir !== undefined) {
306
+ try {
307
+ removeStagedPrompt(arguments_.promptDir);
308
+ }
309
+ catch {
310
+ // already gone
313
311
  }
314
312
  }
315
313
  }
@@ -1,13 +1,11 @@
1
1
  import { type LocalRunner, type AgentDefinition, type NetworkEgressSetting, type ResolvedConfig } from "./config.ts";
2
2
  import { type WorkerEnvironment } from "./launchCommand.ts";
3
3
  import type { WorkspaceKind } from "./workspaceAdapter.ts";
4
- /**
5
- * Stage any srt settings and build the workspace launch command — the assembly
6
- * shared verbatim by `setupWorkspace` (fresh runs) and `resumeWorkspace`
7
- * (resumes). `worktreeDir` is the checkout root (srt grants + `{{worktree}}`);
8
- * `workingDir` is the agent cwd (the worktree root, or its `workdir` subproject).
9
- * Returns `srtSettingsDir` so callers can tear it down on a pre-launch failure.
10
- */
4
+ export interface ComposedAgentLaunch {
5
+ command: string;
6
+ cleanup: () => void;
7
+ }
8
+ /** Build the workspace launch command shared by fresh runs and resumes. */
11
9
  export declare function composeAgentLaunch(input: {
12
10
  runner: LocalRunner;
13
11
  networkEgress: NetworkEgressSetting;
@@ -26,10 +24,14 @@ export declare function composeAgentLaunch(input: {
26
24
  taskSourceWritePaths?: readonly string[] | undefined;
27
25
  safehouseEnableFeatures?: readonly string[] | undefined;
28
26
  readOnlyDirs?: readonly string[] | undefined;
29
- }): {
30
- launchCommand: string;
31
- srtSettingsDir: string | undefined;
32
- };
27
+ /**
28
+ * Test-only seam: overrides `os.homedir()` when staging a relocated agent
29
+ * config home (codex's `CODEX_HOME` under safehouse). Production callers
30
+ * must omit this so specs can seed a fixture home instead of touching the
31
+ * real `~/.codex`.
32
+ */
33
+ homeDir?: string;
34
+ }): ComposedAgentLaunch;
33
35
  interface PreparedAgentLaunch {
34
36
  runner: LocalRunner;
35
37
  /** Resolved `config.local.networkEgress`, threaded into `composeAgentLaunch`. */
@@ -1 +1 @@
1
- {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAM5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,oBAAoB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,iCAAiC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAClD,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACrD,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACxD,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC9C,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CA4ChE;AA4DD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,iFAAiF;IACjF,aAAa,EAAE,oBAAoB,CAAC;IACpC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsD/B;AAwBD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
1
+ {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,oBAAoB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,iCAAiC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAClD,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACrD,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACxD,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,mBAAmB,CAgDtB;AAkHD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,iFAAiF;IACjF,aAAa,EAAE,oBAAoB,CAAC;IACpC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsD/B;AAwBD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
@@ -1,61 +1,65 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
2
4
  import { ensureClearance, resolveSafehouseCmuxIntegration, safehouseCmuxIntegrationWarningLines, } from "@clipboard-health/clearance";
3
5
  import { clearanceAllowHostsFilesFromEnvironment } from "./clearanceAllowlist.js";
6
+ import { installCmuxAgentHooks } from "./cmuxAgentHookInstall.js";
4
7
  import { cmuxAgentHookSettingsJson } from "./cmuxAgentHooks.js";
5
8
  import { shellSingleQuote } from "./shell.js";
6
9
  import { hasPreLaunchEnv, } from "./config.js";
7
10
  import { detectHostCapabilities } from "./host.js";
8
11
  import { buildLaunchCommand, inferAgentCommandName, } from "./launchCommand.js";
12
+ import { agentConfigRelocation, stageRelocatedAgentConfigHome, } from "./codexConfigRelocation.js";
13
+ import { resolveGitCommonDir } from "./gitCommonDir.js";
9
14
  import { assertLocalRunnerRequirements, resolveLocalRunner } from "./localRunner.js";
10
15
  import { sandboxNameFor } from "./sandboxName.js";
11
- import { buildAndStageSrtLaunch, resolveGitCommonDir } from "./srtLaunch.js";
12
- import { debug, sleep, writeError } from "./util.js";
16
+ import { debug, readEnvironmentVariable, sleep, writeError } from "./util.js";
13
17
  import { resolveWorkspaceKind, workspaces } from "./workspaces.js";
14
- /**
15
- * Stage any srt settings and build the workspace launch command — the assembly
16
- * shared verbatim by `setupWorkspace` (fresh runs) and `resumeWorkspace`
17
- * (resumes). `worktreeDir` is the checkout root (srt grants + `{{worktree}}`);
18
- * `workingDir` is the agent cwd (the worktree root, or its `workdir` subproject).
19
- * Returns `srtSettingsDir` so callers can tear it down on a pre-launch failure.
20
- */
18
+ /** Build the workspace launch command shared by fresh runs and resumes. */
21
19
  export function composeAgentLaunch(input) {
22
- const staged = input.runner === "srt"
23
- ? buildAndStageSrtLaunch({
24
- task: input.task,
25
- worktreeDir: input.worktreeDir,
20
+ const homeDir = input.homeDir ?? os.homedir();
21
+ const safehouseAgentIntegration = input.runner === "safehouse"
22
+ ? safehouseAgentIntegrationFor({
23
+ workspaceKind: input.workspaceKind,
26
24
  definition: input.definition,
27
- taskSourceWritePaths: input.taskSourceWritePaths,
28
- readOnlyDirs: input.readOnlyDirs,
25
+ task: input.task,
26
+ homeDir,
29
27
  })
30
28
  : undefined;
31
- const safehouseAgentIntegration = input.runner === "safehouse"
32
- ? safehouseAgentIntegrationFor(input.workspaceKind, input.definition)
33
- : undefined;
34
- const launchCommand = buildLaunchCommand({
35
- definition: input.definition,
36
- promptFile: input.promptFile,
37
- worktreeDir: input.worktreeDir,
38
- workingDir: input.workingDir,
39
- secretsFile: input.secretsFile,
40
- prepareWorktreeCommand: input.prepareWorktreeCommand,
41
- prepareWorktreeUnsandboxedCommand: input.prepareWorktreeUnsandboxedCommand,
42
- runner: input.runner,
43
- networkEgress: input.networkEgress,
44
- sandboxName: input.sandboxName,
45
- srtPrepareSettingsFile: staged?.prepareFile,
46
- srtAgentSettingsFile: staged?.agentFile,
47
- srtSettingsDir: staged?.directory,
48
- srtAgentConfigDirEnv: staged?.agentConfigDirEnv,
49
- workerEnvironment: input.workerEnvironment,
50
- omitPromptArgument: input.omitPromptArgument,
51
- safehouseAddDirs: input.runner === "safehouse" ? resolveSafehouseAddDirs(input.worktreeDir) : undefined,
52
- safehouseAgentAddDirs: input.runner === "safehouse" ? (input.taskSourceWritePaths ?? []) : undefined,
53
- safehouseEnableFeatures: input.runner === "safehouse" ? input.safehouseEnableFeatures : undefined,
54
- // Safehouse rejects nonexistent --add-dirs-ro paths, so drop absent ones.
55
- safehouseAgentAddDirsReadOnly: input.runner === "safehouse" ? (input.readOnlyDirs ?? []).filter(existsSync) : undefined,
56
- safehouseAgentIntegration,
57
- });
58
- return { launchCommand, srtSettingsDir: staged?.directory };
29
+ function cleanup() {
30
+ for (const teardownPath of safehouseAgentIntegration?.teardownPaths ?? []) {
31
+ rmSync(teardownPath, { recursive: true, force: true });
32
+ }
33
+ }
34
+ try {
35
+ return {
36
+ command: buildLaunchCommand({
37
+ definition: input.definition,
38
+ promptFile: input.promptFile,
39
+ worktreeDir: input.worktreeDir,
40
+ workingDir: input.workingDir,
41
+ secretsFile: input.secretsFile,
42
+ prepareWorktreeCommand: input.prepareWorktreeCommand,
43
+ prepareWorktreeUnsandboxedCommand: input.prepareWorktreeUnsandboxedCommand,
44
+ runner: input.runner,
45
+ networkEgress: input.networkEgress,
46
+ sandboxName: input.sandboxName,
47
+ workerEnvironment: input.workerEnvironment,
48
+ omitPromptArgument: input.omitPromptArgument,
49
+ safehouseAddDirs: input.runner === "safehouse" ? resolveSafehouseAddDirs(input.worktreeDir) : undefined,
50
+ safehouseAgentAddDirs: input.runner === "safehouse" ? (input.taskSourceWritePaths ?? []) : undefined,
51
+ safehouseEnableFeatures: input.runner === "safehouse" ? input.safehouseEnableFeatures : undefined,
52
+ // Safehouse rejects nonexistent --add-dirs-ro paths, so drop absent ones.
53
+ safehouseAgentAddDirsReadOnly: input.runner === "safehouse" ? (input.readOnlyDirs ?? []).filter(existsSync) : undefined,
54
+ safehouseAgentIntegration,
55
+ }),
56
+ cleanup,
57
+ };
58
+ }
59
+ catch (error) {
60
+ cleanup();
61
+ throw error;
62
+ }
59
63
  }
60
64
  /**
61
65
  * Filesystem paths the safehouse sandbox must be granted (read/write) beyond
@@ -68,28 +72,38 @@ export function composeAgentLaunch(input) {
68
72
  * whose store lives outside the worktree tree (e.g. graft's `~/carrot/.git`)
69
73
  * gets git access. This is the path the bare cwd grant fundamentally cannot
70
74
  * cover, and the reason this resolution exists.
71
- * Gated to the safehouse runner at the call site (srt fences its own equivalent
72
- * surface worktree root + git common dir through its settings file; sdx/none
73
- * don't use it). Deduped defensively in case git resolves either path to the
74
- * same directory in an unusual checkout shape.
75
+ * Gated to the safehouse runner at the call site. Deduped defensively in case
76
+ * git resolves either path to the same directory in an unusual checkout shape.
75
77
  */
76
78
  function resolveSafehouseAddDirs(worktreeDir) {
77
79
  return [...new Set([worktreeDir, resolveGitCommonDir(worktreeDir)])];
78
80
  }
79
- function safehouseAgentIntegrationFor(workspaceKind, definition) {
80
- if (workspaceKind !== "cmux") {
81
+ function safehouseAgentIntegrationFor(input) {
82
+ if (input.workspaceKind !== "cmux") {
81
83
  return undefined;
82
84
  }
83
- const agentCommandName = inferAgentCommandName(definition.cmd);
85
+ const agentCommandName = inferAgentCommandName(input.definition.cmd);
84
86
  const isClaudeAgent = agentCommandName === "claude";
85
87
  const cmuxIntegration = resolveSafehouseCmuxIntegration();
86
88
  if (isClaudeAgent) {
87
89
  warnOnCmuxIntegrationDrift({ unreviewedEnvNames: cmuxIntegration.unreviewedEnvNames });
88
90
  }
91
+ const relocatedCmuxHooksHome = stageSafehouseCmuxHooksHome({
92
+ agent: agentCommandName,
93
+ task: input.task,
94
+ homeDir: input.homeDir,
95
+ });
89
96
  return {
90
97
  addDirsReadOnly: cmuxIntegration.addDirsReadOnly,
91
98
  envPass: cmuxIntegration.envPass,
92
- commandPreludes: isClaudeAgent ? [cmuxIntegration.claudeCommandPrelude] : [],
99
+ commandPreludes: [
100
+ ...(isClaudeAgent ? [cmuxIntegration.claudeCommandPrelude] : []),
101
+ ...(relocatedCmuxHooksHome === undefined
102
+ ? []
103
+ : [
104
+ `export ${relocatedCmuxHooksHome.configDirEnv.name}=${shellSingleQuote(relocatedCmuxHooksHome.configDirEnv.value)}`,
105
+ ]),
106
+ ],
93
107
  ...(isClaudeAgent
94
108
  ? {
95
109
  agentArgs: [
@@ -98,8 +112,44 @@ function safehouseAgentIntegrationFor(workspaceKind, definition) {
98
112
  ],
99
113
  }
100
114
  : {}),
115
+ ...(relocatedCmuxHooksHome === undefined
116
+ ? {}
117
+ : {
118
+ addDirs: [relocatedCmuxHooksHome.configDir],
119
+ writeBackFiles: relocatedCmuxHooksHome.writeBackFiles,
120
+ teardownPaths: [relocatedCmuxHooksHome.parentDir],
121
+ }),
101
122
  };
102
123
  }
124
+ /**
125
+ * Relocate + seed a writable config home for a cmux-hosted agent whose hook
126
+ * activation is file-driven (codex reads `$CODEX_HOME/config.toml` +
127
+ * `hooks.json`, unlike Claude's `--settings` flag) and best-effort install
128
+ * cmux's lifecycle hooks into it. Undefined for agents with no registered
129
+ * relocation (claude), which keep reporting status via `--settings` instead.
130
+ */
131
+ function stageSafehouseCmuxHooksHome(input) {
132
+ const relocation = agentConfigRelocation(input.agent);
133
+ if (relocation === undefined) {
134
+ return undefined;
135
+ }
136
+ const parentDir = mkdtempSync(path.join(os.tmpdir(), `groundcrew-safehouse-${input.task}-`));
137
+ try {
138
+ const staged = stageRelocatedAgentConfigHome({
139
+ agent: input.agent,
140
+ relocation,
141
+ parentDir,
142
+ sourceConfigDir: readEnvironmentVariable(relocation.configDirEnv) ??
143
+ path.join(input.homeDir, relocation.sourceHomeRelativeDir),
144
+ });
145
+ installCmuxAgentHooks({ agent: input.agent, configDir: staged.configDir });
146
+ return { ...staged, parentDir };
147
+ }
148
+ catch (error) {
149
+ rmSync(parentDir, { recursive: true, force: true });
150
+ throw error;
151
+ }
152
+ }
103
153
  function warnOnCmuxIntegrationDrift(input) {
104
154
  for (const warningLine of safehouseCmuxIntegrationWarningLines({
105
155
  commandName: "groundcrew",
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Best-effort `cmux hooks <agent> install --yes` against a relocated,
3
+ * writable config home (e.g. codex's `CODEX_HOME`) so a file-driven agent —
4
+ * whose hook activation reads `$CODEX_HOME/config.toml` + `hooks.json` rather
5
+ * than an `agentArgs` flag — reports its lifecycle to the cmux sidebar the
6
+ * same way Claude already does via `--settings`.
7
+ *
8
+ * Never throws: an agent with no registered config relocation, a missing
9
+ * `cmux` CLI, or a non-zero install exit all degrade to that agent simply
10
+ * emitting no live status — never to a failed launch. A short timeout keeps a
11
+ * hung `cmux` binary from stalling every launch on this agent.
12
+ */
13
+ export declare function installCmuxAgentHooks(input: {
14
+ agent: string;
15
+ configDir: string;
16
+ }): void;
17
+ //# sourceMappingURL=cmuxAgentHookInstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cmuxAgentHookInstall.d.ts","sourceRoot":"","sources":["../../src/lib/cmuxAgentHookInstall.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0BvF"}
@@ -0,0 +1,42 @@
1
+ import { agentConfigRelocation } from "./codexConfigRelocation.js";
2
+ import { runCommand } from "./commandRunner.js";
3
+ import { errorMessage, logEvent, styleWarning, writeError } from "./util.js";
4
+ const CMUX_HOOKS_INSTALL_EVENT = "cmux-agent-hooks-install";
5
+ const CMUX_HOOKS_INSTALL_TIMEOUT_MS = 10_000;
6
+ /**
7
+ * Best-effort `cmux hooks <agent> install --yes` against a relocated,
8
+ * writable config home (e.g. codex's `CODEX_HOME`) so a file-driven agent —
9
+ * whose hook activation reads `$CODEX_HOME/config.toml` + `hooks.json` rather
10
+ * than an `agentArgs` flag — reports its lifecycle to the cmux sidebar the
11
+ * same way Claude already does via `--settings`.
12
+ *
13
+ * Never throws: an agent with no registered config relocation, a missing
14
+ * `cmux` CLI, or a non-zero install exit all degrade to that agent simply
15
+ * emitting no live status — never to a failed launch. A short timeout keeps a
16
+ * hung `cmux` binary from stalling every launch on this agent.
17
+ */
18
+ export function installCmuxAgentHooks(input) {
19
+ const logContext = { agent: input.agent };
20
+ const relocation = agentConfigRelocation(input.agent);
21
+ if (relocation === undefined) {
22
+ logEvent(CMUX_HOOKS_INSTALL_EVENT, { ...logContext, outcome: "skipped" });
23
+ return;
24
+ }
25
+ try {
26
+ runCommand("cmux", ["hooks", input.agent, "install", "--yes"], {
27
+ // oxlint-disable-next-line node/no-process-env -- the cmux CLI must inherit PATH etc.; only the agent's config-dir var is overridden
28
+ env: { ...process.env, [relocation.configDirEnv]: input.configDir },
29
+ timeoutMs: CMUX_HOOKS_INSTALL_TIMEOUT_MS,
30
+ });
31
+ logEvent(CMUX_HOOKS_INSTALL_EVENT, { ...logContext, outcome: "installed" });
32
+ }
33
+ catch (error) {
34
+ const message = errorMessage(error);
35
+ writeError(styleWarning(`groundcrew: cmux hooks install failed for ${input.agent}: ${message}`));
36
+ logEvent(CMUX_HOOKS_INSTALL_EVENT, {
37
+ ...logContext,
38
+ outcome: "error",
39
+ errorMessage: message,
40
+ });
41
+ }
42
+ }