@clipboard-health/groundcrew 4.35.0 → 4.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,10 +69,15 @@ crew run --watch
69
69
 
70
70
  Linear works out of the box: assign tasks to yourself and add an `agent-*` label.
71
71
 
72
- - `agent-claude`, `agent-codex`, or `agent-<name>` routes to that agent.
72
+ - `agent-claude`, `agent-codex`, or `agent-<name>` routes to that enabled launch profile.
73
73
  - `agent-any` routes to the enabled agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
74
74
  - Tasks without an `agent-*` label are ignored by `crew run`; dispatch one manually with `crew start <TASK>`.
75
75
 
76
+ Agent names are launch profiles, so you can use them to pick model tiers. For example,
77
+ define `claude-fable` with `claude --model claude-fable-5 --permission-mode auto`
78
+ and `claude-opus` with `claude --model claude-opus-4-8 --permission-mode auto`,
79
+ then label tasks with `agent-claude-fable` or `agent-claude-opus`.
80
+
76
81
  Groundcrew scans `workspace.knownRepositories` to infer which repo a task belongs to.
77
82
 
78
83
  A task blocked by non-terminal blockers is skipped until those blockers are done.
@@ -52,13 +52,24 @@ export default {
52
52
  },
53
53
  agents: {
54
54
  default: "claude",
55
- // `definitions` is the enabled agent set. Built-in keys can use `{}` to
56
- // opt into the shipped command/color/usage preset. Add `codex: {}` if you
57
- // want both shipped agents, or add a custom entry and tag tasks with
58
- // `agent-<name>`.
55
+ // `definitions` is the enabled launch profile set. Built-in keys can use
56
+ // `{}` to opt into the shipped command/color/usage preset. Add
57
+ // `codex: {}` if you want both shipped agents. Agent names are launch
58
+ // profiles: add custom entries such as `claude-fable` or `claude-opus` to
59
+ // pin a model per task, then tag tasks with `agent-<name>`.
59
60
  definitions: {
60
61
  claude: {},
61
62
  // codex: {},
63
+ // "claude-fable": {
64
+ // cmd: "claude --model claude-fable-5 --permission-mode auto",
65
+ // color: "#C15F3C",
66
+ // usage: { codexbar: { provider: "claude" } },
67
+ // },
68
+ // "claude-opus": {
69
+ // cmd: "claude --model claude-opus-4-8 --permission-mode auto",
70
+ // color: "#8A4FFF",
71
+ // usage: { codexbar: { provider: "claude" } },
72
+ // },
62
73
  // cursor: {
63
74
  // cmd: "cursor-agent",
64
75
  // color: "#929292",
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyLH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAmF/C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuQH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAmF/C"}
@@ -3,12 +3,12 @@
3
3
  * Returns true if every required check passes; false otherwise.
4
4
  */
5
5
  import { existsSync, statSync } from "node:fs";
6
- import { createBoard } from "../lib/board.js";
7
6
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
8
7
  import { loadConfigWithSource, worktreeBaseDir, } from "../lib/config.js";
9
8
  import { detectHostCapabilities, which } from "../lib/host.js";
10
9
  import { isEnvironmentAssignment } from "../lib/launchCommand.js";
11
10
  import { resolveLocalRunner } from "../lib/localRunner.js";
11
+ import { naturalIdFromCanonical } from "../lib/taskSource.js";
12
12
  import { gatedAgents } from "../lib/usage.js";
13
13
  import { errorMessage, writeOutput } from "../lib/util.js";
14
14
  import { resolveWorkspaceKind } from "../lib/workspaces.js";
@@ -34,27 +34,97 @@ async function checkCmd(cmd, required, hint) {
34
34
  }
35
35
  return result;
36
36
  }
37
+ function isRecord(value) {
38
+ return typeof value === "object" && value !== null;
39
+ }
37
40
  /**
38
- * Source-agnostic reachability check: build every configured task source
39
- * and run the Board's `verify()` fan-out. Replaces the old Linear-only
40
- * "api key + reachability" probe so a misconfigured shell (or future Jira)
41
- * source surfaces here too. A missing Linear API key still fails verify with
42
- * its own user-facing message, so the prior behavior is preserved.
41
+ * True when a raw source config entry declares `kind: "shell"`. Gates the
42
+ * shell-only read probe.
43
43
  */
44
- async function checkSourceProbe(config) {
44
+ function isShellSource(raw) {
45
+ if (!isRecord(raw)) {
46
+ return false;
47
+ }
48
+ const { kind } = raw;
49
+ return kind === "shell";
50
+ }
51
+ function hasExplicitGetTaskCommand(raw) {
52
+ const { commands } = raw;
53
+ if (!isRecord(commands)) {
54
+ return false;
55
+ }
56
+ const { getTask, resolveOne } = commands;
57
+ return typeof getTask === "string" || typeof resolveOne === "string";
58
+ }
59
+ function shellReadProbeFor(raw) {
60
+ if (!isShellSource(raw)) {
61
+ return "none";
62
+ }
63
+ return hasExplicitGetTaskCommand(raw) ? "listTasksAndGetTask" : "listTasks";
64
+ }
65
+ /**
66
+ * Probe each configured task source independently and emit one `Check` per
67
+ * source (named `source: <name>`), so a single broken source is attributable
68
+ * instead of hidden behind an aggregate "N source(s) verified" line.
69
+ *
70
+ * Every source runs its `verify()`. Shell sources are additionally deep-probed
71
+ * via `listTasks()`: their `verify` command can discard stdout (e.g.
72
+ * `... fetch >/dev/null`), so a malformed task payload sails through verify and
73
+ * only blows up later in `crew run`. Running listTasks here surfaces a
74
+ * wrong-shape payload (a `TaskSourceOutputError` with a readable message) at
75
+ * doctor time. Non-shell sources (Linear) are left to `verify()` alone — their
76
+ * fetch is an expensive network call that verify already exercises.
77
+ */
78
+ async function checkSourceProbes(config) {
79
+ const rawSources = sourcesFromConfig(config);
80
+ let sources;
45
81
  try {
46
- const sources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
47
- const board = createBoard(sources);
48
- await board.verify();
49
- return {
50
- name: "source probe",
51
- ok: true,
52
- required: true,
53
- hint: `${sources.length} source(s) verified`,
54
- };
82
+ sources = await buildSources(rawSources, { globalConfig: config });
83
+ }
84
+ catch (error) {
85
+ // Building sources failed before any individual probe (bad config / unknown
86
+ // kind). Surface it as a single failed check rather than per-source.
87
+ return [{ name: "sources", ok: false, required: true, hint: errorMessage(error) }];
88
+ }
89
+ if (sources.length === 0) {
90
+ return [{ name: "sources", ok: false, required: true, hint: "no task sources configured" }];
91
+ }
92
+ const checks = [];
93
+ for (const [index, source] of sources.entries()) {
94
+ const shellReadProbe = shellReadProbeFor(rawSources[index]);
95
+ // oxlint-disable-next-line no-await-in-loop -- sequential keeps each verdict attributable to one source
96
+ checks.push(await probeSource(source, shellReadProbe));
97
+ }
98
+ return checks;
99
+ }
100
+ async function probeSource(source, shellReadProbe) {
101
+ const name = `source: ${source.name}`;
102
+ try {
103
+ await source.verify();
104
+ if (shellReadProbe === "none") {
105
+ return { name, ok: true, required: true, hint: "verified" };
106
+ }
107
+ const tasks = await source.listTasks();
108
+ const parts = [`fetched ${tasks.length} task(s)`];
109
+ // Read-path symmetry: an id listTasks emitted must resolve via getTask.
110
+ // Skipped when getTask is only the adapter's listTasks fallback, since that
111
+ // would just re-run listTasks and can race changing source output.
112
+ const [first] = tasks;
113
+ if (first !== undefined && shellReadProbe === "listTasksAndGetTask") {
114
+ const naturalId = naturalIdFromCanonical(first.id);
115
+ const resolved = await source.getTask(naturalId);
116
+ if (resolved === null) {
117
+ throw new Error(`getTask("${naturalId}") returned nothing, but listTasks emitted it`);
118
+ }
119
+ if (resolved.id !== first.id) {
120
+ throw new Error(`getTask("${naturalId}") resolved "${resolved.id}", but listTasks emitted "${first.id}"`);
121
+ }
122
+ parts.push("getTask round-trips");
123
+ }
124
+ return { name, ok: true, required: true, hint: `verified; ${parts.join("; ")}` };
55
125
  }
56
126
  catch (error) {
57
- return { name: "source probe", ok: false, required: true, hint: errorMessage(error) };
127
+ return { name, ok: false, required: true, hint: errorMessage(error) };
58
128
  }
59
129
  }
60
130
  function checkDir(path, label) {
@@ -190,7 +260,7 @@ export async function doctor() {
190
260
  const workspaceOutcome = resolveWorkspaceOutcome(config, host);
191
261
  reportWorkspaceKind(config, workspaceOutcome);
192
262
  const checks = [
193
- await checkSourceProbe(config),
263
+ ...(await checkSourceProbes(config)),
194
264
  await checkCmd("git", true, "https://git-scm.com/"),
195
265
  ...(await workspaceChecks(workspaceOutcome)),
196
266
  checkDir(config.workspace.projectDir, "workspace.projectDir"),
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2DH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE7E"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoEH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE7E"}
@@ -8,7 +8,7 @@ import { createBoard } from "../lib/board.js";
8
8
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
9
9
  import { loadConfigWithSource } from "../lib/config.js";
10
10
  import { findPullRequestsForBranch } from "../lib/pullRequests.js";
11
- import { RepositoryResolutionError } from "../lib/taskSource.js";
11
+ import { RepositoryResolutionError, TaskSourceOutputError, } from "../lib/taskSource.js";
12
12
  import { getUsageByAgent } from "../lib/usage.js";
13
13
  import { errorMessage, log, sleep, writeOutput } from "../lib/util.js";
14
14
  import { worktrees } from "../lib/worktrees.js";
@@ -30,6 +30,11 @@ async function withRetry(function_, signal, maxRetries = RETRY_MAX_ATTEMPTS, bas
30
30
  if (error instanceof RepositoryResolutionError) {
31
31
  throw error;
32
32
  }
33
+ // A source returned unparseable output — deterministic, so retrying just
34
+ // delays a guaranteed failure behind confusing "Retrying in Ns" lines.
35
+ if (error instanceof TaskSourceOutputError) {
36
+ throw error;
37
+ }
33
38
  if (attempt === maxRetries) {
34
39
  throw error;
35
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AA0ID,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Ef;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,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AA0ID,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -119,7 +119,7 @@ export async function resumeWorkspace(config, options) {
119
119
  if (definition === undefined) {
120
120
  throw new Error(`Unknown agent: ${context.agent}`);
121
121
  }
122
- const { runner, sandboxName, ensureReady } = await prepareAgentLaunch({
122
+ const { runner, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
123
123
  config,
124
124
  agent: context.agent,
125
125
  definition,
@@ -150,6 +150,7 @@ export async function resumeWorkspace(config, options) {
150
150
  workingDir: launchDir,
151
151
  secretsFile,
152
152
  sandboxName,
153
+ workspaceKind,
153
154
  workerEnvironment: workerEnvironmentForTask(context.completionTaskId),
154
155
  }));
155
156
  const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAuBnE,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,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,CA8Hf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAuBnE,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,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,CA+Hf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -32,7 +32,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
32
32
  if (!definition) {
33
33
  throw new Error(`Unknown agent: ${agent}`);
34
34
  }
35
- const { runner, sandboxName, ensureReady } = await prepareAgentLaunch({
35
+ const { runner, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
36
36
  config,
37
37
  agent,
38
38
  definition,
@@ -92,6 +92,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
92
92
  secretsFile,
93
93
  prepareWorktreeCommand,
94
94
  sandboxName,
95
+ workspaceKind,
95
96
  workerEnvironment: workerEnvironmentForTask(completionTaskId),
96
97
  });
97
98
  srtSettingsDir = stagedSrtSettingsDir;
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAyErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AA8BD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA6LZ"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAGhB,MAAM,aAAa,CAAC;AAyErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AA8BD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,CAsMZ"}
@@ -22,6 +22,7 @@
22
22
  import { toCanonicalId, } from "../../taskSource.js";
23
23
  import { errorMessage, writeError } from "../../util.js";
24
24
  import { invokeShellCommand } from "./invoke.js";
25
+ import { parseShellJson } from "./parseOutput.js";
25
26
  import { shellFetchOutputSchema, shellIssueSchema, shellValidateOutputSchema, } from "./schema.js";
26
27
  const DEFAULT_TIMEOUTS = {
27
28
  verify: 10_000,
@@ -134,7 +135,10 @@ export function createShellTaskSource(config, _context) {
134
135
  env: config.env,
135
136
  sourceName,
136
137
  });
137
- const parsed = shellFetchOutputSchema.parse(JSON.parse(stdout));
138
+ const parsed = parseShellJson(shellFetchOutputSchema, stdout, {
139
+ sourceName,
140
+ command: "listTasks",
141
+ });
138
142
  return parsed.map((si) => toCanonicalIssue(si, sourceName));
139
143
  }
140
144
  async function getTask(naturalId) {
@@ -158,7 +162,10 @@ export function createShellTaskSource(config, _context) {
158
162
  if (result.exitCode === 3 || result.stdout.trim().length === 0) {
159
163
  return null;
160
164
  }
161
- const parsed = shellIssueSchema.parse(JSON.parse(result.stdout));
165
+ const parsed = parseShellJson(shellIssueSchema, result.stdout, {
166
+ sourceName,
167
+ command: "getTask",
168
+ });
162
169
  return toCanonicalIssue(parsed, sourceName);
163
170
  }
164
171
  // Shared by markInProgress / markInReview: both pipe the canonical issue's
@@ -259,7 +266,10 @@ export function createShellTaskSource(config, _context) {
259
266
  if (trimmed.length === 0) {
260
267
  throw new Error(`shell source "${sourceName}" createTask command produced no output (expected one ShellIssue JSON)`);
261
268
  }
262
- const parsed = shellIssueSchema.parse(JSON.parse(trimmed));
269
+ const parsed = parseShellJson(shellIssueSchema, trimmed, {
270
+ sourceName,
271
+ command: "createTask",
272
+ });
263
273
  return toCanonicalIssue(parsed, sourceName);
264
274
  };
265
275
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Friendly stdout parsing for the shell adapter. A shell source emits JSON on
3
+ * stdout; both `JSON.parse` and the Zod schema can reject it. A raw `ZodError`
4
+ * stringifies to a JSON array of issues — cryptic, unattributed, and (for a
5
+ * fetch array) repeated once per task. This module collapses that into a
6
+ * single user-facing `TaskSourceOutputError` that names the source, the
7
+ * command, and the offending field(s), with a targeted hint for the common
8
+ * "missing `agent`" mistake.
9
+ */
10
+ import type { z } from "zod";
11
+ export interface ShellParseContext {
12
+ /** The source's configured `name`, surfaced so the user knows which source broke. */
13
+ sourceName: string;
14
+ /** The contract method that produced the output, e.g. `"listTasks"` or `"getTask"`. */
15
+ command: string;
16
+ }
17
+ /**
18
+ * Parse `stdout` as JSON and validate it against `schema`. On any failure,
19
+ * throws a `TaskSourceOutputError` with a readable, source-attributed message
20
+ * instead of a raw `SyntaxError` / `ZodError`.
21
+ */
22
+ export declare function parseShellJson<T>(schema: z.ZodType<T>, stdout: string, context: ShellParseContext): T;
23
+ //# sourceMappingURL=parseOutput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseOutput.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/parseOutput.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAK7B,MAAM,WAAW,iBAAiB;IAChC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACzB,CAAC,CAcH"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Friendly stdout parsing for the shell adapter. A shell source emits JSON on
3
+ * stdout; both `JSON.parse` and the Zod schema can reject it. A raw `ZodError`
4
+ * stringifies to a JSON array of issues — cryptic, unattributed, and (for a
5
+ * fetch array) repeated once per task. This module collapses that into a
6
+ * single user-facing `TaskSourceOutputError` that names the source, the
7
+ * command, and the offending field(s), with a targeted hint for the common
8
+ * "missing `agent`" mistake.
9
+ */
10
+ import { TaskSourceOutputError } from "../../taskSource.js";
11
+ import { errorMessage } from "../../util.js";
12
+ /**
13
+ * Parse `stdout` as JSON and validate it against `schema`. On any failure,
14
+ * throws a `TaskSourceOutputError` with a readable, source-attributed message
15
+ * instead of a raw `SyntaxError` / `ZodError`.
16
+ */
17
+ export function parseShellJson(schema, stdout, context) {
18
+ let json;
19
+ try {
20
+ json = JSON.parse(stdout);
21
+ }
22
+ catch (error) {
23
+ throw new TaskSourceOutputError(`source "${context.sourceName}": the ${context.command} command did not return valid JSON (${errorMessage(error)}).`);
24
+ }
25
+ const result = schema.safeParse(json);
26
+ if (result.success) {
27
+ return result.data;
28
+ }
29
+ throw new TaskSourceOutputError(formatSchemaError(result.error, context));
30
+ }
31
+ /** The last string segment of an issue path — the field name, ignoring array indices. */
32
+ function fieldName(path) {
33
+ for (let index = path.length - 1; index >= 0; index -= 1) {
34
+ const segment = path[index];
35
+ if (typeof segment === "string") {
36
+ return segment;
37
+ }
38
+ }
39
+ return undefined;
40
+ }
41
+ /** One human-readable phrase describing what's wrong with a single issue. */
42
+ function describeIssue(issue) {
43
+ const field = fieldName(issue.path);
44
+ const label = field === undefined ? "field" : `"${field}"`;
45
+ // Zod v4 reports a missing required key as an `invalid_type` whose message
46
+ // ends in "received undefined"; there is no separate `received` property to
47
+ // branch on, so we match the message.
48
+ if (issue.code === "invalid_type" && issue.message.includes("received undefined")) {
49
+ return `are missing the required ${label} field`;
50
+ }
51
+ return `have an invalid ${label} field: ${issue.message}`;
52
+ }
53
+ function formatSchemaError(error, context) {
54
+ // A fetch array yields one issue per task (48 tasks missing `agent` → 48
55
+ // identical issues). Collapse identical phrasings into a single counted line,
56
+ // preserving first-seen order via the Map's insertion order.
57
+ const counts = new Map();
58
+ for (const issue of error.issues) {
59
+ const description = describeIssue(issue);
60
+ counts.set(description, (counts.get(description) ?? 0) + 1);
61
+ }
62
+ const lines = [...counts].map(([description, count]) => ` • ${count} issue(s) ${description}`);
63
+ return [
64
+ `source "${context.sourceName}": the ${context.command} command returned task JSON that doesn't match the expected shape:`,
65
+ ...lines,
66
+ ].join("\n");
67
+ }
@@ -1,5 +1,6 @@
1
1
  import { type LocalRunner, type AgentDefinition, type ResolvedConfig } from "./config.ts";
2
2
  import { type WorkerEnvironment } from "./launchCommand.ts";
3
+ import type { WorkspaceKind } from "./workspaceAdapter.ts";
3
4
  /**
4
5
  * Stage any srt settings and build the workspace launch command — the assembly
5
6
  * shared verbatim by `setupWorkspace` (fresh runs) and `resumeWorkspace`
@@ -17,6 +18,7 @@ export declare function composeAgentLaunch(input: {
17
18
  secretsFile?: string | undefined;
18
19
  prepareWorktreeCommand?: string | undefined;
19
20
  sandboxName?: string | undefined;
21
+ workspaceKind: WorkspaceKind;
20
22
  workerEnvironment?: WorkerEnvironment | undefined;
21
23
  }): {
22
24
  launchCommand: string;
@@ -25,6 +27,7 @@ export declare function composeAgentLaunch(input: {
25
27
  interface PreparedAgentLaunch {
26
28
  runner: LocalRunner;
27
29
  sandboxName: string | undefined;
30
+ workspaceKind: WorkspaceKind;
28
31
  ensureReady: () => Promise<void>;
29
32
  }
30
33
  export declare function prepareAgentLaunch(input: {
@@ -1 +1 @@
1
- {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAOhF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,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,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CA2BhE;AAsBD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,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,CAqD/B;AAwBD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,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,CAUhB"}
1
+ {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,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,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,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAgChE;AAmDD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,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,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,CAUhB"}
@@ -1,13 +1,13 @@
1
- import { ensureClearance } from "@clipboard-health/clearance";
1
+ import { ensureClearance, resolveSafehouseCmuxIntegration, safehouseCmuxIntegrationWarningLines, } from "@clipboard-health/clearance";
2
2
  import { clearanceAllowHostsFilesFromEnvironment } from "./clearanceAllowlist.js";
3
3
  import { hasPreLaunchEnv, } from "./config.js";
4
4
  import { detectHostCapabilities } from "./host.js";
5
- import { buildLaunchCommand } from "./launchCommand.js";
5
+ import { buildLaunchCommand, inferAgentCommandName, } from "./launchCommand.js";
6
6
  import { assertLocalRunnerRequirements, resolveLocalRunner } from "./localRunner.js";
7
7
  import { sandboxNameFor } from "./sandboxName.js";
8
8
  import { buildAndStageSrtLaunch, resolveGitCommonDir } from "./srtLaunch.js";
9
- import { debug, sleep } from "./util.js";
10
- import { workspaces } from "./workspaces.js";
9
+ import { debug, sleep, writeError } from "./util.js";
10
+ import { resolveWorkspaceKind, workspaces } from "./workspaces.js";
11
11
  /**
12
12
  * Stage any srt settings and build the workspace launch command — the assembly
13
13
  * shared verbatim by `setupWorkspace` (fresh runs) and `resumeWorkspace`
@@ -23,6 +23,9 @@ export function composeAgentLaunch(input) {
23
23
  definition: input.definition,
24
24
  })
25
25
  : undefined;
26
+ const safehouseAgentIntegration = input.runner === "safehouse"
27
+ ? safehouseAgentIntegrationFor(input.workspaceKind, input.definition)
28
+ : undefined;
26
29
  const launchCommand = buildLaunchCommand({
27
30
  definition: input.definition,
28
31
  promptFile: input.promptFile,
@@ -38,6 +41,7 @@ export function composeAgentLaunch(input) {
38
41
  srtAgentConfigDirEnv: staged?.agentConfigDirEnv,
39
42
  workerEnvironment: input.workerEnvironment,
40
43
  safehouseAddDirs: input.runner === "safehouse" ? resolveSafehouseAddDirs(input.worktreeDir) : undefined,
44
+ safehouseAgentIntegration,
41
45
  });
42
46
  return { launchCommand, srtSettingsDir: staged?.directory };
43
47
  }
@@ -60,9 +64,33 @@ export function composeAgentLaunch(input) {
60
64
  function resolveSafehouseAddDirs(worktreeDir) {
61
65
  return [...new Set([worktreeDir, resolveGitCommonDir(worktreeDir)])];
62
66
  }
67
+ function safehouseAgentIntegrationFor(workspaceKind, definition) {
68
+ if (workspaceKind !== "cmux") {
69
+ return undefined;
70
+ }
71
+ const isClaudeAgent = inferAgentCommandName(definition.cmd) === "claude";
72
+ const cmuxIntegration = resolveSafehouseCmuxIntegration();
73
+ if (isClaudeAgent) {
74
+ warnOnCmuxIntegrationDrift({ unreviewedEnvNames: cmuxIntegration.unreviewedEnvNames });
75
+ }
76
+ return {
77
+ addDirsReadOnly: cmuxIntegration.addDirsReadOnly,
78
+ envPass: cmuxIntegration.envPass,
79
+ commandPreludes: isClaudeAgent ? [cmuxIntegration.claudeCommandPrelude] : [],
80
+ };
81
+ }
82
+ function warnOnCmuxIntegrationDrift(input) {
83
+ for (const warningLine of safehouseCmuxIntegrationWarningLines({
84
+ commandName: "groundcrew",
85
+ unreviewedEnvNames: input.unreviewedEnvNames,
86
+ })) {
87
+ writeError(warningLine);
88
+ }
89
+ }
63
90
  export async function prepareAgentLaunch(input) {
64
91
  const host = await detectHostCapabilities(input.signal);
65
92
  const runner = resolveLocalRunner(input.config.local.runner, host);
93
+ const workspaceKind = resolveWorkspaceKind({ config: input.config, host }).resolved;
66
94
  assertLocalRunnerRequirements(host, runner);
67
95
  const ensureReady = runner === "safehouse"
68
96
  ? async () => {
@@ -96,7 +124,7 @@ export async function prepareAgentLaunch(input) {
96
124
  const sandboxName = runner === "sdx" && input.definition.sandbox !== undefined
97
125
  ? sandboxNameFor({ agent: input.definition.sandbox.agent })
98
126
  : undefined;
99
- return { runner, sandboxName, ensureReady };
127
+ return { runner, sandboxName, workspaceKind, ensureReady };
100
128
  }
101
129
  async function alreadyReady() {
102
130
  await Promise.resolve();
@@ -41,6 +41,11 @@ declare const WORKER_ENVIRONMENT_NAMES: readonly ["GROUNDCREW_TASK_ID", "GROUNDC
41
41
  type WorkerEnvironmentName = (typeof WORKER_ENVIRONMENT_NAMES)[number];
42
42
  export type WorkerEnvironment = Readonly<Record<WorkerEnvironmentName, string>>;
43
43
  export declare function workerEnvironmentForTask(taskId: string): WorkerEnvironment;
44
+ export interface SafehouseAgentIntegration {
45
+ addDirsReadOnly: readonly string[];
46
+ envPass: readonly string[];
47
+ commandPreludes: readonly string[];
48
+ }
44
49
  interface LaunchCommandArguments {
45
50
  definition: AgentDefinition;
46
51
  promptFile: string;
@@ -115,6 +120,13 @@ interface LaunchCommandArguments {
115
120
  * pre-existing behavior). Only consumed by the safehouse wrap.
116
121
  */
117
122
  safehouseAddDirs?: readonly string[] | undefined;
123
+ /**
124
+ * Extra host-terminal integration surface granted only to the Safehouse agent
125
+ * wrap. The agent may need to execute host shims and reach their sockets
126
+ * while repo-controlled prepareWorktree hooks should not inherit those paths
127
+ * or env vars.
128
+ */
129
+ safehouseAgentIntegration?: SafehouseAgentIntegration | undefined;
118
130
  /**
119
131
  * Groundcrew-managed task metadata exposed to the launched worker. Forwarded
120
132
  * to the agent process, not the prepareWorktree hook.
@@ -1 +1 @@
1
- {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMvF;AAsMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED,QAAA,MAAM,wBAAwB,YAAI,oBAAoB,EAAE,qBAAqB,CAAU,CAAC;AAExF,KAAK,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAK1E;AAsBD,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAkC7E"}
1
+ {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMvF;AAsMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED,QAAA,MAAM,wBAAwB,YAAI,oBAAoB,EAAE,qBAAqB,CAAU,CAAC;AAExF,KAAK,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAK1E;AAsBD,MAAM,WAAW,yBAAyB;IACxC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAClE;;;OAGG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAkC7E"}
@@ -381,7 +381,12 @@ function buildUnwrappedHostLaunchCommand(arguments_) {
381
381
  function buildSafehouseLaunchCommand(arguments_) {
382
382
  const promptDir = path.dirname(arguments_.promptFile);
383
383
  const safehouseCommandName = inferAgentCommandName(arguments_.definition.cmd);
384
- const { agentCommand, prepareWorktreeCommand } = renderPrepareAndAgentCommand(arguments_);
384
+ const { agentCommand: rawAgentCommand, prepareWorktreeCommand } = renderPrepareAndAgentCommand(arguments_);
385
+ const { safehouseAgentIntegration } = arguments_;
386
+ const agentCommand = [
387
+ ...(safehouseAgentIntegration?.commandPreludes ?? []),
388
+ rawAgentCommand,
389
+ ].join("; ");
385
390
  // Split --env-pass per wrap: the prepareWorktree wrap only needs build secrets (so
386
391
  // `npm install` etc. can authenticate); the agent wrap only needs the
387
392
  // user's preLaunchEnv (build secrets are `unset` on the host between the
@@ -392,14 +397,15 @@ function buildSafehouseLaunchCommand(arguments_) {
392
397
  const agentEnvPassFlag = envPassFlag([
393
398
  ...(arguments_.definition.preLaunchEnv ?? []),
394
399
  ...workerEnvironmentNames(arguments_.workerEnvironment),
400
+ ...(safehouseAgentIntegration?.envPass ?? []),
395
401
  ]);
396
402
  // safehouse reads colon-separated paths from `--add-dirs`; both wraps get the
397
403
  // same grant so the prepareWorktree hook and the agent can each reach git.
398
404
  // Quote the whole value so shell-special chars survive; the trailing space
399
405
  // separates it from the next argv token. See `resolveSafehouseAddDirs` for
400
406
  // which paths these are and why.
401
- const addDirs = arguments_.safehouseAddDirs ?? [];
402
- const safehouseAddDirsFlag = addDirs.length === 0 ? "" : `--add-dirs=${shellSingleQuote(addDirs.join(":"))} `;
407
+ const safehouseAddDirsFlag = safehousePathListFlag("--add-dirs", arguments_.safehouseAddDirs ?? []);
408
+ const safehouseAgentAddDirsReadOnlyFlag = safehousePathListFlag("--add-dirs-ro", safehouseAgentIntegration?.addDirsReadOnly ?? []);
403
409
  const safehouseWrapper = safehouseClearanceWrapperCommand();
404
410
  // Defensive shim+promptDir trap: by the time we arm it, `rm -rf <promptDir>`
405
411
  // has already run (line below) so the promptDir wipe is a no-op on the happy
@@ -429,9 +435,12 @@ function buildSafehouseLaunchCommand(arguments_) {
429
435
  // Running the real launch chain as `sh -c` would make it see `sh`, so use
430
436
  // an agent-named symlink to /bin/sh. This preserves per-agent profile
431
437
  // selection without enabling every agent profile.
432
- `{ ${safehouseWrapper} ${safehouseAddDirsFlag}${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh "$_p"; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"; trap - EXIT; exit "$_safehouse_status"; }`);
438
+ `{ ${safehouseWrapper} ${safehouseAddDirsFlag}${safehouseAgentAddDirsReadOnlyFlag}${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh "$_p"; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"; trap - EXIT; exit "$_safehouse_status"; }`);
433
439
  return lines.join(" && ");
434
440
  }
441
+ function safehousePathListFlag(flagName, paths) {
442
+ return paths.length === 0 ? "" : `${flagName}=${shellSingleQuote(paths.join(":"))} `;
443
+ }
435
444
  /**
436
445
  * Benign baseline env the srt wraps run under (via `env -i`). This is an
437
446
  * allowlist on purpose: srt's CLI spawns its child with the *inherited* host
@@ -210,6 +210,19 @@ export declare class RepositoryResolutionError extends Error {
210
210
  repositories: readonly string[];
211
211
  });
212
212
  }
213
+ /**
214
+ * A task source returned output that groundcrew could not parse — non-JSON
215
+ * stdout, or JSON that doesn't match the source's contract (e.g. a shell
216
+ * script's `listTasks` payload missing the required `agent` field). Carries a
217
+ * user-facing message that names the source and what was wrong.
218
+ *
219
+ * Marked deterministic: re-running the same command yields the same bad
220
+ * output, so the orchestrator's `withRetry` must NOT retry it (retrying only
221
+ * delays a guaranteed failure behind confusing "Retrying in Ns" lines).
222
+ */
223
+ export declare class TaskSourceOutputError extends Error {
224
+ constructor(message: string);
225
+ }
213
226
  export declare class AmbiguousTaskError extends Error {
214
227
  constructor(arguments_: {
215
228
  naturalId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"taskSource.d.ts","sourceRoot":"","sources":["../../src/lib/taskSource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"taskSource.d.ts","sourceRoot":"","sources":["../../src/lib/taskSource.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,22 @@ export class RepositoryResolutionError extends Error {
17
17
  this.name = "RepositoryResolutionError";
18
18
  }
19
19
  }
20
+ /**
21
+ * A task source returned output that groundcrew could not parse — non-JSON
22
+ * stdout, or JSON that doesn't match the source's contract (e.g. a shell
23
+ * script's `listTasks` payload missing the required `agent` field). Carries a
24
+ * user-facing message that names the source and what was wrong.
25
+ *
26
+ * Marked deterministic: re-running the same command yields the same bad
27
+ * output, so the orchestrator's `withRetry` must NOT retry it (retrying only
28
+ * delays a guaranteed failure behind confusing "Retrying in Ns" lines).
29
+ */
30
+ export class TaskSourceOutputError extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "TaskSourceOutputError";
34
+ }
35
+ }
20
36
  export class AmbiguousTaskError extends Error {
21
37
  constructor(arguments_) {
22
38
  const { naturalId, matches } = arguments_;
@@ -10,8 +10,8 @@ There is a single path from board state to dispatch: `Source[] → Board → Dis
10
10
 
11
11
  ## Consequences
12
12
 
13
- - The canonical seam is the `Issue` contract: a source emits `model` and `repository`, or the task is ignored (`isGroundcrewIssue` keys off exactly that). Consumers branch on the canonical `CanonicalStatus` enum, never on a source's native status names.
13
+ - The canonical seam is the `Issue` contract: a source emits `agent` and `repository`, or the task is ignored (`isGroundcrewIssue` keys off exactly that). Consumers branch on the canonical `CanonicalStatus` enum, never on a source's native status names.
14
14
  - **Linear-specific** concepts live in the adapter: `agent-*` label parsing, `agent-any` routing, sub-issue/parent detection, assigned-to-viewer + label selection policy.
15
- - **Canonical** concepts stay in eligibility so every source benefits: blocker classification (sources populate `blockers[]`) and exhausted-model gating (sources pick a `model`).
15
+ - **Canonical** concepts stay in eligibility so every source benefits: blocker classification (sources populate `blockers[]`) and exhausted-agent gating (sources pick an `agent`).
16
16
  - This was a pure internal refactor with no user-visible change — Linear keeps working identically — so it carried no migration cost and landed before the breaking v5 cuts.
17
17
  - Changing the Linear selection mechanism (assigned + labeled) is now an adapter-local change that does not touch the engine.
package/docs/commands.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ```bash
8
8
  crew task list
9
9
  crew task list --source todo --status todo --unblocked
10
- crew task list --agent codex --repo ClipboardHealth/api --json
10
+ crew task list --agent claude-fable --repo ClipboardHealth/api --json
11
11
  ```
12
12
 
13
13
  `crew task get <task-id>` prints one normalized task. Canonical IDs such as `todo:GC-20260608-001` route directly to the named source. Natural IDs can be resolved with `--source <name>` or, when unique, by searching all configured sources. If more than one source matches, the command fails and asks for a canonical ID or `--source`.
@@ -23,7 +23,7 @@ crew task get todo:GC-20260608-001 --prompt
23
23
  ```bash
24
24
  crew task create "Fix cancellation retry race" \
25
25
  --source todo \
26
- --agent codex \
26
+ --agent claude-fable \
27
27
  --repo ClipboardHealth/api \
28
28
  --project marketplace \
29
29
  --context backend \
@@ -35,7 +35,7 @@ Linear creation creates a Todo issue assigned to the current Linear API viewer,
35
35
  ```bash
36
36
  crew task create "Fix cancellation retry race" \
37
37
  --source linear \
38
- --agent codex \
38
+ --agent claude-fable \
39
39
  --team ENG \
40
40
  --repo ClipboardHealth/api \
41
41
  --description "Investigate retry handling."
@@ -70,7 +70,7 @@ done and schedules the next `status:todo` recurrence itself.
70
70
  ```bash
71
71
  crew task create "Run flaky triage sweep" \
72
72
  --source todo \
73
- --agent codex \
73
+ --agent claude-fable \
74
74
  --repo ClipboardHealth/groundcrew \
75
75
  --id flaky-triage-1 \
76
76
  --rec 2h \
@@ -92,13 +92,40 @@ The "Loaded config from ..." line at startup tells you which config won.
92
92
 
93
93
  ## Agent Label Routing
94
94
 
95
- - `agent-claude`, `agent-codex`, `agent-<name>` routes to that enabled agent.
95
+ - `agent-claude`, `agent-codex`, `agent-<name>` routes to that enabled launch profile.
96
96
  - `agent-any` routes to the agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
97
97
  - Unknown `agent-<name>` falls back to `agents.default`.
98
98
  - A built-in `agent-<name>` label whose agent is not enabled falls back to `agents.default` with a warning.
99
99
  - No `agent-*` label is ignored by `crew run`. Dispatch on demand with `crew start <TASK>`, which falls back to `agents.default`.
100
100
  - Todo tasks blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
101
101
 
102
+ Agent names are launch profiles, not just vendor names. To choose a model per
103
+ task, define model-specific profiles and route tasks to them:
104
+
105
+ ```ts
106
+ export default {
107
+ agents: {
108
+ default: "claude-fable",
109
+ definitions: {
110
+ "claude-fable": {
111
+ cmd: "claude --model claude-fable-5 --permission-mode auto",
112
+ color: "#C15F3C",
113
+ usage: { codexbar: { provider: "claude" } },
114
+ },
115
+ "claude-opus": {
116
+ cmd: "claude --model claude-opus-4-8 --permission-mode auto",
117
+ color: "#8A4FFF",
118
+ usage: { codexbar: { provider: "claude" } },
119
+ },
120
+ },
121
+ },
122
+ };
123
+ ```
124
+
125
+ Use the same profile name in every source: Linear label `agent-claude-fable`,
126
+ todo.txt token `agent:claude-fable`, shell JSON `"agent": "claude-fable"`,
127
+ or `crew task create ... --agent claude-fable`.
128
+
102
129
  Status classification uses Linear's default status names `In Progress` and `In Review` to disambiguate multiple `started` workflow states. Statuses that do not match those names fall back to Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so broad lifecycle classification still works without configuration. Parent issues with children are ignored; sub-issues are the work items.
103
130
 
104
131
  If your Linear workflow uses different names, explicitly declare the built-in Linear source and override only the names you need:
@@ -167,9 +194,9 @@ export default {
167
194
 
168
195
  Rules:
169
196
 
170
- - `agents.definitions` is the enabled agent set; `crew doctor` only probes listed agents.
197
+ - `agents.definitions` is the enabled launch profile set; `crew doctor` only probes listed profiles.
171
198
  - Built-in entries can be `{}` or partial overrides such as `{ cmd: "..." }`.
172
- - Custom agent names must provide `cmd` and `color`.
199
+ - Custom launch profile names must provide `cmd` and `color`.
173
200
  - `agents.default` must point at an enabled agent.
174
201
  - Legacy agent entries like `codex: { disabled: true }` are rejected with migration guidance; remove unwanted entries instead.
175
202
 
@@ -244,9 +271,9 @@ and hook contract.
244
271
  | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
245
272
  | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. An agent whose codexbar session window exceeds this percentage is skipped that tick. Agents are also skipped when codexbar reports weekly usage over the current weekly paced budget. |
246
273
  | `agents.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `agents.definitions`. If you enable only `codex`, set `default: "codex"`. |
247
- | `agents.definitions` | **required** | Enabled agent set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom agent names must provide `cmd` and `color`. |
248
- | `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom agents. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
249
- | `agents.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom agents. |
274
+ | `agents.definitions` | **required** | Enabled launch profile set. Built-in keys (`claude`, `codex`) can use `{}` to opt into the shipped preset. Custom profile names must provide `cmd` and `color`; use custom profiles such as `claude-fable` and `claude-opus` to select model-specific commands per task. |
275
+ | `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom profiles. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
276
+ | `agents.definitions.<name>.color` | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom profiles. |
250
277
  | `agents.definitions.<name>.usage` | preset for built-ins | If set, codexbar usage is fetched for this agent and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the agent enabled. |
251
278
  | `agents.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the agent. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
252
279
  | `agents.definitions.<name>.preLaunch` | optional | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1. |
@@ -53,7 +53,7 @@ state is expected or explicitly allowed.
53
53
  "description": "Task body",
54
54
  "status": "todo",
55
55
  "repository": "your-org/your-repo",
56
- "model": "claude",
56
+ "agent": "claude-fable",
57
57
  "assignee": "Alice",
58
58
  "updatedAt": "2026-05-22T15:00:00Z",
59
59
  "blockers": [{ "id": "JIRA-122", "title": "Schema migration", "status": "done" }],
@@ -63,7 +63,7 @@ state is expected or explicitly allowed.
63
63
  ]
64
64
  ```
65
65
 
66
- Allowed `status` values are `todo`, `in-progress`, `in-review`, `done`, and `other`. Use `null` for `repository` or `model` when a task should not be groundcrew-eligible. `hasMoreBlockers` is optional and defaults to `false`; `sourceRef` is opaque data that groundcrew passes back to your writeback command.
66
+ Allowed `status` values are `todo`, `in-progress`, `in-review`, `done`, and `other`. Omit `repository` or `agent` when a task should not be groundcrew-eligible. `hasMoreBlockers` is optional and defaults to `false`; `sourceRef` is opaque data that groundcrew passes back to your writeback command.
67
67
 
68
68
  ## Todo.txt
69
69
 
@@ -89,7 +89,7 @@ Creating a todo task appends a line with `status:todo` as the final meaningful t
89
89
  ```bash
90
90
  crew task create "Fix cancellation retry race" \
91
91
  --source todo \
92
- --agent codex \
92
+ --agent claude-fable \
93
93
  --repo ClipboardHealth/api \
94
94
  --project marketplace \
95
95
  --context backend \
@@ -97,7 +97,7 @@ crew task create "Fix cancellation retry race" \
97
97
  ```
98
98
 
99
99
  ```txt
100
- Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
100
+ Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:claude-fable status:todo
101
101
  ```
102
102
 
103
103
  For hand-written todo lines, a non-empty title is enough prompt text when `.tasks/<id>.md` is absent. Omit `agent:` to default to `agent:any`:
@@ -126,7 +126,7 @@ export default {
126
126
  ```bash
127
127
  crew task create "Fix cancellation retry race" \
128
128
  --source linear \
129
- --agent codex \
129
+ --agent claude-fable \
130
130
  --repo ClipboardHealth/api \
131
131
  --description "Investigate retry handling."
132
132
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.35.0",
3
+ "version": "4.36.0",
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",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@anthropic-ai/sandbox-runtime": "0.0.52",
72
- "@clipboard-health/clearance": "1.2.0",
72
+ "@clipboard-health/clearance": "1.3.2",
73
73
  "@linear/sdk": "86.0.0",
74
74
  "cosmiconfig": "9.0.1",
75
75
  "tslib": "2.8.1",
@@ -82,12 +82,12 @@
82
82
  "@tsconfig/node24": "24.0.4",
83
83
  "@tsconfig/strictest": "2.0.8",
84
84
  "@types/node": "25.9.2",
85
- "@typescript/native-preview": "7.0.0-dev.20260606.1",
85
+ "@typescript/native-preview": "7.0.0-dev.20260608.1",
86
86
  "@vitest/coverage-v8": "4.1.8",
87
87
  "cspell": "10.0.1",
88
88
  "dependency-cruiser": "17.4.3",
89
89
  "husky": "9.1.7",
90
- "jscpd": "4.2.4",
90
+ "jscpd": "5.0.5",
91
91
  "knip": "6.15.0",
92
92
  "lint-staged": "17.0.7",
93
93
  "markdownlint-cli2": "0.22.1",