@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 +6 -1
- package/crew.config.example.ts +15 -4
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +88 -18
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +6 -1
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +2 -1
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +2 -1
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +13 -3
- package/dist/lib/adapters/shell/parseOutput.d.ts +23 -0
- package/dist/lib/adapters/shell/parseOutput.d.ts.map +1 -0
- package/dist/lib/adapters/shell/parseOutput.js +67 -0
- package/dist/lib/agentLaunch.d.ts +3 -0
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +33 -5
- package/dist/lib/launchCommand.d.ts +12 -0
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +13 -4
- package/dist/lib/taskSource.d.ts +13 -0
- package/dist/lib/taskSource.d.ts.map +1 -1
- package/dist/lib/taskSource.js +16 -0
- package/docs/adr/0002-one-task-source-path-linear-is-an-adapter.md +2 -2
- package/docs/commands.md +4 -4
- package/docs/configuration.md +33 -6
- package/docs/task-sources.md +5 -5
- package/package.json +4 -4
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
|
|
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.
|
package/crew.config.example.ts
CHANGED
|
@@ -52,13 +52,24 @@ export default {
|
|
|
52
52
|
},
|
|
53
53
|
agents: {
|
|
54
54
|
default: "claude",
|
|
55
|
-
// `definitions` is the enabled
|
|
56
|
-
// opt into the shipped command/color/usage preset. Add
|
|
57
|
-
// want both shipped agents
|
|
58
|
-
// `
|
|
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;
|
|
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"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
-
*
|
|
39
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
|
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;
|
|
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,
|
|
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,
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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":"
|
|
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"}
|
package/dist/lib/agentLaunch.js
CHANGED
|
@@ -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
|
|
402
|
-
const
|
|
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
|
package/dist/lib/taskSource.d.ts
CHANGED
|
@@ -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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"}
|
package/dist/lib/taskSource.js
CHANGED
|
@@ -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 `
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
73
|
+
--agent claude-fable \
|
|
74
74
|
--repo ClipboardHealth/groundcrew \
|
|
75
75
|
--id flaky-triage-1 \
|
|
76
76
|
--rec 2h \
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
248
|
-
| `agents.definitions.<name>.cmd` | preset for built-ins | Shell command launched for the agent. Required for custom
|
|
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
|
|
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. |
|
package/docs/task-sources.md
CHANGED
|
@@ -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
|
-
"
|
|
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`.
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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": "
|
|
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",
|