@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.
- package/clearance-allow-hosts +1 -0
- package/crew.config.example.ts +2 -2
- package/dist/commands/doctor.js +0 -25
- package/dist/commands/openWorkspace.d.ts.map +1 -1
- package/dist/commands/openWorkspace.js +14 -17
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +7 -15
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +16 -18
- package/dist/lib/agentLaunch.d.ts +13 -11
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +102 -52
- package/dist/lib/cmuxAgentHookInstall.d.ts +17 -0
- package/dist/lib/cmuxAgentHookInstall.d.ts.map +1 -0
- package/dist/lib/cmuxAgentHookInstall.js +42 -0
- package/dist/lib/codexConfigRelocation.d.ts +60 -0
- package/dist/lib/codexConfigRelocation.d.ts.map +1 -0
- package/dist/lib/codexConfigRelocation.js +70 -0
- package/dist/lib/commandRunner.d.ts +9 -0
- package/dist/lib/commandRunner.d.ts.map +1 -1
- package/dist/lib/commandRunner.js +1 -0
- package/dist/lib/config.d.ts +8 -12
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -2
- package/dist/lib/gitCommonDir.d.ts +7 -0
- package/dist/lib/gitCommonDir.d.ts.map +1 -0
- package/dist/lib/gitCommonDir.js +15 -0
- package/dist/lib/host.d.ts +0 -14
- package/dist/lib/host.d.ts.map +1 -1
- package/dist/lib/host.js +1 -8
- package/dist/lib/launchCommand.d.ts +32 -52
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +96 -187
- package/dist/lib/localRunner.d.ts +4 -5
- package/dist/lib/localRunner.d.ts.map +1 -1
- package/dist/lib/localRunner.js +4 -23
- package/docs/configuration.md +3 -3
- package/docs/runners.md +7 -50
- package/docs/setup-hooks.md +1 -1
- package/docs/spec/e2e-scenario-catalog.md +1 -1
- package/docs/spec/v2-design.md +7 -7
- package/docs/task-sources.md +3 -3
- package/package.json +2 -5
- package/static/demo.tape +0 -2
- package/static/render-demo.sh +0 -1
- package/dist/lib/clearanceHosts.d.ts +0 -31
- package/dist/lib/clearanceHosts.d.ts.map +0 -1
- package/dist/lib/clearanceHosts.js +0 -96
- package/dist/lib/srtLaunch.d.ts +0 -61
- package/dist/lib/srtLaunch.d.ts.map +0 -1
- package/dist/lib/srtLaunch.js +0 -107
- package/dist/lib/srtPolicy.d.ts +0 -118
- package/dist/lib/srtPolicy.d.ts.map +0 -1
- package/dist/lib/srtPolicy.js +0 -371
package/clearance-allow-hosts
CHANGED
package/crew.config.example.ts
CHANGED
|
@@ -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
|
|
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
|
-
// //
|
|
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
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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
|
|
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"
|
|
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
|
|
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
|
-
|
|
137
|
-
const launchCmd = stageWorkspaceLaunchCommand(promptDir,
|
|
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
|
-
|
|
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
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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":"
|
|
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"}
|
package/dist/lib/agentLaunch.js
CHANGED
|
@@ -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 {
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
25
|
+
task: input.task,
|
|
26
|
+
homeDir,
|
|
29
27
|
})
|
|
30
28
|
: undefined;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
72
|
-
*
|
|
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(
|
|
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:
|
|
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
|
+
}
|