@clipboard-health/groundcrew 4.26.0 → 4.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cli.js +2 -2
  2. package/dist/commands/dispatcher.d.ts +2 -2
  3. package/dist/commands/dispatcher.js +19 -19
  4. package/dist/commands/doctor.js +14 -14
  5. package/dist/commands/eligibility.d.ts +19 -19
  6. package/dist/commands/eligibility.d.ts.map +1 -1
  7. package/dist/commands/eligibility.js +21 -21
  8. package/dist/commands/init.d.ts +4 -4
  9. package/dist/commands/init.js +17 -17
  10. package/dist/commands/interruptWorkspace.js +3 -3
  11. package/dist/commands/orchestrator.js +2 -2
  12. package/dist/commands/resumeWorkspace.js +9 -9
  13. package/dist/commands/setupWorkspace.d.ts +1 -1
  14. package/dist/commands/setupWorkspace.js +14 -14
  15. package/dist/commands/status.js +4 -4
  16. package/dist/commands/task.js +8 -8
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.js +2 -2
  19. package/dist/lib/adapterDefinition.d.ts +1 -1
  20. package/dist/lib/adapters/linear/create.js +1 -1
  21. package/dist/lib/adapters/linear/factory.js +2 -2
  22. package/dist/lib/adapters/linear/fetch.d.ts +6 -6
  23. package/dist/lib/adapters/linear/fetch.js +28 -28
  24. package/dist/lib/adapters/linear/parsing.d.ts +7 -7
  25. package/dist/lib/adapters/linear/parsing.d.ts.map +1 -1
  26. package/dist/lib/adapters/linear/parsing.js +14 -14
  27. package/dist/lib/adapters/shell/factory.js +1 -1
  28. package/dist/lib/adapters/shell/schema.d.ts +3 -3
  29. package/dist/lib/adapters/shell/schema.js +2 -2
  30. package/dist/lib/adapters/todo-txt/normalizer.js +3 -3
  31. package/dist/lib/adapters/todo-txt/source.d.ts.map +1 -1
  32. package/dist/lib/adapters/todo-txt/source.js +5 -7
  33. package/dist/lib/agentLaunch.d.ts +4 -4
  34. package/dist/lib/agentLaunch.js +7 -7
  35. package/dist/lib/cmuxAdapter.js +1 -1
  36. package/dist/lib/config.d.ts +22 -22
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js +47 -44
  39. package/dist/lib/launchCommand.d.ts +4 -4
  40. package/dist/lib/launchCommand.js +4 -4
  41. package/dist/lib/runState.d.ts +2 -2
  42. package/dist/lib/runState.js +4 -4
  43. package/dist/lib/srtLaunch.d.ts +2 -2
  44. package/dist/lib/taskSource.d.ts +4 -4
  45. package/dist/lib/taskSource.d.ts.map +1 -1
  46. package/dist/lib/taskSource.js +1 -1
  47. package/dist/lib/testing/canonicalFixtures.js +2 -2
  48. package/dist/lib/usage.d.ts +7 -7
  49. package/dist/lib/usage.js +20 -20
  50. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ import { errorMessage, parseDryRunPositionals, readEnvironmentVariable, readTask
14
14
  const REMOVED_SANDBOX_COMMAND_MESSAGE = [
15
15
  "`crew sandbox` is no longer supported.",
16
16
  "Groundcrew now launches agents inside existing sbx sandboxes but does not list, create, regenerate, authenticate, or remove them.",
17
- "Use the manual `sbx` workflow in README.md#docker-sandboxes-sdx-setup, then keep `models.definitions.<model>.sandbox.agent` in crew.config.ts so launches can address the existing sandbox.",
17
+ "Use the manual `sbx` workflow in README.md#docker-sandboxes-sdx-setup, then keep `agents.definitions.<agent>.sandbox.agent` in crew.config.ts so launches can address the existing sandbox.",
18
18
  ].join("\n");
19
19
  const requireFromCli = createRequire(import.meta.url);
20
20
  const SETUP_REPOS_REMOVED_MESSAGE = [
@@ -112,7 +112,7 @@ async function doctorCli(argv) {
112
112
  const SUBCOMMANDS = {
113
113
  init: {
114
114
  summary: "Create a crew.config.ts in the cwd (or --global into the XDG config dir)",
115
- usage: "[--global | --local] [--force] [--dry-run] [--project-dir <dir>] [--repo <repo>]... [--runner <runner>] [--model <model>]",
115
+ usage: "[--global | --local] [--force] [--dry-run] [--project-dir <dir>] [--repo <repo>]... [--runner <runner>] [--agent <agent>]",
116
116
  invoke: initConfigCli,
117
117
  },
118
118
  run: {
@@ -9,7 +9,7 @@
9
9
  import type { Board } from "../lib/board.ts";
10
10
  import type { ResolvedConfig } from "../lib/config.ts";
11
11
  import { type BoardState, type Issue } from "../lib/taskSource.ts";
12
- import type { UsageByModel } from "../lib/usage.ts";
12
+ import type { UsageByAgent } from "../lib/usage.ts";
13
13
  import type { WorktreeEntry } from "../lib/worktrees.ts";
14
14
  interface DispatcherDeps {
15
15
  config: ResolvedConfig;
@@ -20,7 +20,7 @@ export interface Dispatcher {
20
20
  state: BoardState;
21
21
  worktreeEntries: readonly WorktreeEntry[];
22
22
  /** Lazy so dispatcher can early-return on idle ticks without paying the codexbar shell-out. */
23
- usage: (signal?: AbortSignal) => Promise<UsageByModel>;
23
+ usage: (signal?: AbortSignal) => Promise<UsageByAgent>;
24
24
  dryRun: boolean;
25
25
  signal?: AbortSignal;
26
26
  /**
@@ -19,18 +19,18 @@ function logSkip(verdict) {
19
19
  reason: verdict.eventReason,
20
20
  task: naturalIdFromCanonical(verdict.issue.id),
21
21
  blockers: verdict.blockers,
22
- model: verdict.model,
22
+ agent: verdict.agent,
23
23
  });
24
24
  }
25
- function logMissingRepositorySkip(issue, model, knownRepositories) {
25
+ function logMissingRepositorySkip(issue, agent, knownRepositories) {
26
26
  const task = naturalIdFromCanonical(issue.id);
27
- log(styleWarning(`WARNING: ${issue.id} has agent "${model}" but no repository; skipping dispatch. Add --repo <repo> when creating the task, add repo:<repo> to the task line, or set defaultRepository on source "${issue.source}". Known repositories: ${formatKnownRepositories(knownRepositories)}`));
27
+ log(styleWarning(`WARNING: ${issue.id} has agent "${agent}" but no repository; skipping dispatch. Add --repo <repo> when creating the task, add repo:<repo> to the task line, or set defaultRepository on source "${issue.source}". Known repositories: ${formatKnownRepositories(knownRepositories)}`));
28
28
  logEvent("dispatch", {
29
29
  outcome: "skipped",
30
30
  reason: "missing_repository",
31
31
  task,
32
32
  source: issue.source,
33
- model,
33
+ agent,
34
34
  });
35
35
  }
36
36
  export function createDispatcher(deps) {
@@ -38,7 +38,7 @@ export function createDispatcher(deps) {
38
38
  function buildExhaustedSet(usage) {
39
39
  const exhausted = new Set();
40
40
  for (const exhaustion of classifyUsageExhaustion(config, usage)) {
41
- exhausted.add(exhaustion.model);
41
+ exhausted.add(exhaustion.agent);
42
42
  log(formatUsageExhaustion(exhaustion));
43
43
  }
44
44
  return exhausted;
@@ -47,17 +47,17 @@ export function createDispatcher(deps) {
47
47
  const { issue, recovery } = start;
48
48
  const taskId = naturalIdFromCanonical(issue.id);
49
49
  if (start.resolvedFromAny) {
50
- log(`Resolved agent-any for ${taskId} → ${issue.model}`);
50
+ log(`Resolved agent-any for ${taskId} → ${issue.agent}`);
51
51
  }
52
52
  if (dryRun) {
53
53
  log(
54
54
  /* v8 ignore next @preserve -- classifyTodo forces recovery=false in dry-run, so the resume branch can't fire here */
55
- `[dry-run] Would ${recovery ? "resume" : "start"} ${taskId} in ${issue.repository} (${issue.model})`);
55
+ `[dry-run] Would ${recovery ? "resume" : "start"} ${taskId} in ${issue.repository} (${issue.agent})`);
56
56
  logEvent("dispatch", {
57
57
  outcome: "skipped",
58
58
  reason: "dry_run",
59
59
  task: taskId,
60
- model: issue.model,
60
+ agent: issue.agent,
61
61
  repository: issue.repository,
62
62
  });
63
63
  return;
@@ -70,7 +70,7 @@ export function createDispatcher(deps) {
70
70
  const setupOptions = {
71
71
  repository: issue.repository,
72
72
  task: taskId,
73
- model: issue.model,
73
+ agent: issue.agent,
74
74
  details: {
75
75
  title: issue.title,
76
76
  description: issue.description,
@@ -85,7 +85,7 @@ export function createDispatcher(deps) {
85
85
  logEvent("dispatch", {
86
86
  outcome: recovery ? "resumed" : "started",
87
87
  task: taskId,
88
- model: issue.model,
88
+ agent: issue.agent,
89
89
  repository: issue.repository,
90
90
  });
91
91
  }
@@ -94,7 +94,7 @@ export function createDispatcher(deps) {
94
94
  logEvent("dispatch", {
95
95
  outcome: "failed",
96
96
  task: taskId,
97
- model: issue.model,
97
+ agent: issue.agent,
98
98
  repository: issue.repository,
99
99
  error: errorMessage(error),
100
100
  });
@@ -122,9 +122,9 @@ export function createDispatcher(deps) {
122
122
  const slots = config.orchestrator.maximumInProgress - activeCount;
123
123
  const rawTodo = state.issues.filter((issue) => issue.status === "todo");
124
124
  for (const issue of rawTodo) {
125
- const { model, repository } = issue;
126
- if (model !== undefined && repository === undefined) {
127
- logMissingRepositorySkip(issue, model, config.workspace.knownRepositories);
125
+ const { agent, repository } = issue;
126
+ if (agent !== undefined && repository === undefined) {
127
+ logMissingRepositorySkip(issue, agent, config.workspace.knownRepositories);
128
128
  }
129
129
  }
130
130
  // Narrow queued work to tasks that opted in with an agent and resolved a
@@ -212,10 +212,10 @@ export function createDispatcher(deps) {
212
212
  return;
213
213
  }
214
214
  const dispatchable = starts;
215
- log(`Slots ${activeCount}/${config.orchestrator.maximumInProgress} used${formatActiveSlotList(active)}, starting ${dispatchable.length} task(s): ${dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}(${issue.model})`).join(", ")}`);
215
+ log(`Slots ${activeCount}/${config.orchestrator.maximumInProgress} used${formatActiveSlotList(active)}, starting ${dispatchable.length} task(s): ${dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}(${issue.agent})`).join(", ")}`);
216
216
  logEvent("dispatch", {
217
217
  outcome: "starting",
218
- tasks: dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}:${issue.model}`),
218
+ tasks: dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}:${issue.agent}`),
219
219
  });
220
220
  for (const start of dispatchable) {
221
221
  // oxlint-disable-next-line no-await-in-loop -- one workspace at a time avoids racing on git
@@ -233,9 +233,9 @@ function hasRecoverableCandidate(issues, worktreeEntries) {
233
233
  function formatUsageExhaustion(exhaustion) {
234
234
  if (exhaustion.kind === "session") {
235
235
  const mins = exhaustion.resetMinutes ?? "?";
236
- return `${exhaustion.model} session at ${exhaustion.usedPercentage.toFixed(0)}% (> ${exhaustion.limitPercentage}%), resets in ${mins}m — skipping its tasks`;
236
+ return `${exhaustion.agent} session at ${exhaustion.usedPercentage.toFixed(0)}% (> ${exhaustion.limitPercentage}%), resets in ${mins}m — skipping its tasks`;
237
237
  }
238
- return `${exhaustion.model} weekly at ${exhaustion.usedPercentage.toFixed(1)}% (> ${exhaustion.allowedPercentage.toFixed(1)}% paced budget), resets in ${exhaustion.resetMinutes}m — skipping its tasks`;
238
+ return `${exhaustion.agent} weekly at ${exhaustion.usedPercentage.toFixed(1)}% (> ${exhaustion.allowedPercentage.toFixed(1)}% paced budget), resets in ${exhaustion.resetMinutes}m — skipping its tasks`;
239
239
  }
240
240
  /** Undefined priority sorts last. */
241
241
  function prioritySortKey(priority) {
@@ -246,7 +246,7 @@ export function formatActiveSlotList(active) {
246
246
  return "";
247
247
  }
248
248
  const entries = active
249
- .map((issue) => `${naturalIdFromCanonical(issue.id)}(${issue.model ?? "?"})`)
249
+ .map((issue) => `${naturalIdFromCanonical(issue.id)}(${issue.agent ?? "?"})`)
250
250
  .join(", ");
251
251
  return ` [${entries}]`;
252
252
  }
@@ -8,13 +8,13 @@ import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
8
8
  import { loadConfigWithSource, worktreeBaseDir, } from "../lib/config.js";
9
9
  import { detectHostCapabilities, which } from "../lib/host.js";
10
10
  import { resolveLocalRunner } from "../lib/localRunner.js";
11
- import { gatedModels } from "../lib/usage.js";
11
+ import { gatedAgents } from "../lib/usage.js";
12
12
  import { errorMessage, writeOutput } from "../lib/util.js";
13
13
  import { resolveWorkspaceKind } from "../lib/workspaces.js";
14
14
  // Tokenization stops after this many non-flag tokens. Two is enough to
15
15
  // catch wrapper + wrapped CLI commands like `safehouse claude --foo`.
16
16
  const MAX_TOKENS_PER_CMD = 2;
17
- const BUILT_IN_MODEL_NAMES = ["claude", "codex"];
17
+ const BUILT_IN_AGENT_NAMES = ["claude", "codex"];
18
18
  const CONFIG_SOURCE_LABELS = {
19
19
  env: "GROUNDCREW_CONFIG",
20
20
  project: "project",
@@ -74,7 +74,7 @@ function checkDir(path, label) {
74
74
  };
75
75
  }
76
76
  /**
77
- * Tokens worth checking against PATH from a model's `cmd`:
77
+ * Tokens worth checking against PATH from an agent's `cmd`:
78
78
  * the executable name (first non-flag token), and any subsequent
79
79
  * non-flag, non-flag-value token until a flag is hit. Flag tokens are
80
80
  * dropped along with the token immediately following them (treated as
@@ -111,9 +111,9 @@ function commandTokensToCheck(cmd) {
111
111
  }
112
112
  function gatherToolTargets(config) {
113
113
  const all = new Map();
114
- for (const [modelName, definition] of Object.entries(config.models.definitions)) {
114
+ for (const [agentName, definition] of Object.entries(config.agents.definitions)) {
115
115
  for (const token of commandTokensToCheck(definition.cmd)) {
116
- const hint = modelCliHint(modelName, token);
116
+ const hint = agentCliHint(agentName, token);
117
117
  if (!all.has(token) || all.get(token) === undefined) {
118
118
  all.set(token, hint);
119
119
  }
@@ -121,16 +121,16 @@ function gatherToolTargets(config) {
121
121
  }
122
122
  return [...all].map(([token, hint]) => (hint === undefined ? { token } : { token, hint }));
123
123
  }
124
- function modelCliHint(modelName, token) {
125
- if (token !== modelName) {
124
+ function agentCliHint(agentName, token) {
125
+ if (token !== agentName) {
126
126
  return undefined;
127
127
  }
128
- if (!isBuiltInModelName(modelName)) {
128
+ if (!isBuiltInAgentName(agentName)) {
129
129
  return undefined;
130
130
  }
131
- return `install ${token} or remove \`models.definitions.${modelName}\` from crew.config.ts`;
131
+ return `install ${token} or remove \`agents.definitions.${agentName}\` from crew.config.ts`;
132
132
  }
133
- function isBuiltInModelName(value) {
133
+ function isBuiltInAgentName(value) {
134
134
  return value === "claude" || value === "codex";
135
135
  }
136
136
  function format(check) {
@@ -196,16 +196,16 @@ export async function doctor() {
196
196
  const check = await checkCmd(token, required, required ? hint : "required for local runs");
197
197
  checks.push(check);
198
198
  }
199
- const usageGatedModels = gatedModels(config);
200
- if (usageGatedModels.length > 0) {
199
+ const usageGatedAgents = gatedAgents(config);
200
+ if (usageGatedAgents.length > 0) {
201
201
  const codexbarPath = await which("codexbar");
202
202
  if (codexbarPath === undefined) {
203
- const modelList = usageGatedModels.map((name) => `\`${name}\``).join(", ");
203
+ const agentList = usageGatedAgents.map((name) => `\`${name}\``).join(", ");
204
204
  checks.push({
205
205
  name: "codexbar",
206
206
  ok: false,
207
207
  required: true,
208
- hint: `required for usage gating on ${modelList} — install codexbar, or set \`models.definitions.<name>.usage\` to disable gating`,
208
+ hint: `required for usage gating on ${agentList} — install codexbar, or set \`agents.definitions.<name>.usage\` to disable gating`,
209
209
  });
210
210
  }
211
211
  else {
@@ -8,15 +8,15 @@
8
8
  */
9
9
  import { type ResolvedConfig } from "../lib/config.ts";
10
10
  import { type GroundcrewIssue } from "../lib/taskSource.ts";
11
- import type { UsageByModel } from "../lib/usage.ts";
11
+ import type { UsageByAgent } from "../lib/usage.ts";
12
12
  import type { WorkspaceProbe } from "../lib/workspaces.ts";
13
13
  import type { WorktreeEntry } from "../lib/worktrees.ts";
14
- type SkipReason = "blocked" | "blockers_paginated" | "agent_any_capacity" | "model_exhausted" | "workspace_list_unavailable" | "workspace_missing";
14
+ type SkipReason = "blocked" | "blockers_paginated" | "agent_any_capacity" | "agent_exhausted" | "workspace_list_unavailable" | "workspace_missing";
15
15
  export interface StartVerdict {
16
16
  kind: "start";
17
17
  issue: GroundcrewIssue;
18
18
  recovery: boolean;
19
- /** Set when the verdict resolved an `agent-any` label to a concrete model. */
19
+ /** Set when the verdict resolved an `agent-any` label to a concrete agent. */
20
20
  resolvedFromAny: boolean;
21
21
  }
22
22
  export interface SkipVerdict {
@@ -29,23 +29,23 @@ export interface SkipVerdict {
29
29
  /** Set for `blocked` and `blockers_paginated`. */
30
30
  blockers?: string[];
31
31
  /**
32
- * Set when the skip event should carry the resolved model (i.e. the
33
- * verdict knew which model would have run). Omitted for blocker skips
34
- * and `agent_any_capacity` where the model was either unresolved or
32
+ * Set when the skip event should carry the resolved agent (i.e. the
33
+ * verdict knew which agent would have run). Omitted for blocker skips
34
+ * and `agent_any_capacity` where the agent was either unresolved or
35
35
  * irrelevant.
36
36
  */
37
- model?: string;
37
+ agent?: string;
38
38
  }
39
39
  type Verdict = StartVerdict | SkipVerdict;
40
- export type ModelUsageExhaustion = {
40
+ export type AgentUsageExhaustion = {
41
41
  kind: "session";
42
- model: string;
42
+ agent: string;
43
43
  usedPercentage: number;
44
44
  limitPercentage: number;
45
45
  resetMinutes: number | null;
46
46
  } | {
47
47
  kind: "weekly";
48
- model: string;
48
+ agent: string;
49
49
  usedPercentage: number;
50
50
  allowedPercentage: number;
51
51
  resetMinutes: number;
@@ -61,8 +61,8 @@ export interface ClassifyArguments {
61
61
  unblocked: readonly GroundcrewIssue[];
62
62
  worktreeEntries: readonly WorktreeEntry[];
63
63
  workspaceProbe: WorkspaceProbe;
64
- usage: UsageByModel;
65
- /** Models flagged over `sessionLimitPercentage`. */
64
+ usage: UsageByAgent;
65
+ /** Agents flagged over `sessionLimitPercentage`. */
66
66
  exhausted: Set<string>;
67
67
  /** Maximum number of `start` verdicts to produce. */
68
68
  slots: number;
@@ -73,15 +73,15 @@ interface BlockerClassification {
73
73
  skips: SkipVerdict[];
74
74
  }
75
75
  /**
76
- * Pick the configured model with the most available session capacity.
77
- * Models flagged exhausted (over `sessionLimitPercentage`) are excluded.
78
- * Score is `usage[model].session` with `null`/missing treated as 0
79
- * (maximum headroom), so when no usage data is available every model
80
- * ties at 0 and the default model wins the tiebreak — `agent-any` then
76
+ * Pick the configured agent with the most available session capacity.
77
+ * Agents flagged exhausted (over `sessionLimitPercentage`) are excluded.
78
+ * Score is `usage[agent].session` with `null`/missing treated as 0
79
+ * (maximum headroom), so when no usage data is available every agent
80
+ * ties at 0 and the default agent wins the tiebreak — `agent-any` then
81
81
  * falls back to the default predictably.
82
82
  */
83
- export declare function pickBestModel(config: ResolvedConfig, usage: UsageByModel, exhausted: Set<string>): string | undefined;
84
- export declare function classifyUsageExhaustion(config: ResolvedConfig, usage: UsageByModel): ModelUsageExhaustion[];
83
+ export declare function pickBestAgent(config: ResolvedConfig, usage: UsageByAgent, exhausted: Set<string>): string | undefined;
84
+ export declare function classifyUsageExhaustion(config: ResolvedConfig, usage: UsageByAgent): AgentUsageExhaustion[];
85
85
  /**
86
86
  * Cheap pre-pass — partitions Todo into unblocked issues and blocker
87
87
  * skip verdicts. Runs before the dispatcher fetches usage or probes the
@@ -1 +1 @@
1
- {"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/commands/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAwC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAClG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,KAAK,UAAU,GACX,SAAS,GACT,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,4BAA4B,GAC5B,mBAAmB,CAAC;AAExB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,EAAE,UAAU,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAgCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,GAAG,SAAS,CAepB;AAaD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,GAClB,oBAAoB,EAAE,CAmCxB;AA6CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,GAAG,qBAAqB,CAYxF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,EAAE,CAgE5E"}
1
+ {"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/commands/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAwC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAClG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,KAAK,UAAU,GACX,SAAS,GACT,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,4BAA4B,GAC5B,mBAAmB,CAAC;AAExB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,EAAE,UAAU,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAgCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,GAAG,SAAS,CAepB;AAaD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,GAClB,oBAAoB,EAAE,CAmCxB;AA6CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,GAAG,qBAAqB,CAYxF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,EAAE,CAgE5E"}
@@ -6,7 +6,7 @@
6
6
  * The Dispatcher consumes the verdict list to drive logging and side
7
7
  * effects.
8
8
  */
9
- import { AGENT_ANY_MODEL } from "../lib/config.js";
9
+ import { AGENT_ANY } from "../lib/config.js";
10
10
  import { naturalIdFromCanonical } from "../lib/taskSource.js";
11
11
  const PERCENT_FRACTION_DIVISOR = 100;
12
12
  const DAYS_PER_WEEK = 7;
@@ -40,15 +40,15 @@ function blockerVerdictFor(issue) {
40
40
  };
41
41
  }
42
42
  /**
43
- * Pick the configured model with the most available session capacity.
44
- * Models flagged exhausted (over `sessionLimitPercentage`) are excluded.
45
- * Score is `usage[model].session` with `null`/missing treated as 0
46
- * (maximum headroom), so when no usage data is available every model
47
- * ties at 0 and the default model wins the tiebreak — `agent-any` then
43
+ * Pick the configured agent with the most available session capacity.
44
+ * Agents flagged exhausted (over `sessionLimitPercentage`) are excluded.
45
+ * Score is `usage[agent].session` with `null`/missing treated as 0
46
+ * (maximum headroom), so when no usage data is available every agent
47
+ * ties at 0 and the default agent wins the tiebreak — `agent-any` then
48
48
  * falls back to the default predictably.
49
49
  */
50
- export function pickBestModel(config, usage, exhausted) {
51
- const candidates = Object.keys(config.models.definitions).filter((name) => !exhausted.has(name));
50
+ export function pickBestAgent(config, usage, exhausted) {
51
+ const candidates = Object.keys(config.agents.definitions).filter((name) => !exhausted.has(name));
52
52
  if (candidates.length === 0) {
53
53
  return undefined;
54
54
  }
@@ -57,7 +57,7 @@ export function pickBestModel(config, usage, exhausted) {
57
57
  if (candidate.score < best.score) {
58
58
  return candidate;
59
59
  }
60
- if (candidate.score === best.score && candidate.name === config.models.default) {
60
+ if (candidate.score === best.score && candidate.name === config.agents.default) {
61
61
  return candidate;
62
62
  }
63
63
  return best;
@@ -72,11 +72,11 @@ function weeklyPacedBudgetPercentage(weekEndDuration) {
72
72
  export function classifyUsageExhaustion(config, usage) {
73
73
  const exhausted = [];
74
74
  const sessionLimit = config.orchestrator.sessionLimitPercentage;
75
- for (const [model, snapshot] of Object.entries(usage)) {
75
+ for (const [agent, snapshot] of Object.entries(usage)) {
76
76
  if (snapshot.session !== null && snapshot.session * PERCENT_FRACTION_DIVISOR > sessionLimit) {
77
77
  exhausted.push({
78
78
  kind: "session",
79
- model,
79
+ agent,
80
80
  usedPercentage: snapshot.session * PERCENT_FRACTION_DIVISOR,
81
81
  limitPercentage: sessionLimit,
82
82
  resetMinutes: snapshot.sessionEndDuration,
@@ -93,7 +93,7 @@ export function classifyUsageExhaustion(config, usage) {
93
93
  if (usedPercentage > allowedPercentage) {
94
94
  exhausted.push({
95
95
  kind: "weekly",
96
- model,
96
+ agent,
97
97
  usedPercentage,
98
98
  allowedPercentage,
99
99
  resetMinutes: snapshot.weekEndDuration,
@@ -172,27 +172,27 @@ export function classifyEligibility(arguments_) {
172
172
  }
173
173
  let resolved = original;
174
174
  let resolvedFromAny = false;
175
- if (original.model === AGENT_ANY_MODEL) {
176
- const picked = pickBestModel(config, usage, exhausted);
175
+ if (original.agent === AGENT_ANY) {
176
+ const picked = pickBestAgent(config, usage, exhausted);
177
177
  if (picked === undefined) {
178
178
  verdicts.push({
179
179
  kind: "skip",
180
180
  issue: original,
181
- message: `Skipping ${original.id}: agent-any but no model has available capacity`,
181
+ message: `Skipping ${original.id}: agent-any but no agent has available capacity`,
182
182
  eventReason: "agent_any_capacity",
183
183
  });
184
184
  continue;
185
185
  }
186
- resolved = { ...original, model: picked };
186
+ resolved = { ...original, agent: picked };
187
187
  resolvedFromAny = true;
188
188
  }
189
- if (exhausted.has(resolved.model)) {
189
+ if (exhausted.has(resolved.agent)) {
190
190
  verdicts.push({
191
191
  kind: "skip",
192
192
  issue: resolved,
193
- message: `Skipping ${resolved.id} (${resolved.model} session exhausted)`,
194
- eventReason: "model_exhausted",
195
- model: resolved.model,
193
+ message: `Skipping ${resolved.id} (${resolved.agent} session exhausted)`,
194
+ eventReason: "agent_exhausted",
195
+ agent: resolved.agent,
196
196
  });
197
197
  continue;
198
198
  }
@@ -203,7 +203,7 @@ export function classifyEligibility(arguments_) {
203
203
  dryRun,
204
204
  });
205
205
  if (recovery.kind === "skip") {
206
- verdicts.push({ ...recovery, model: resolved.model });
206
+ verdicts.push({ ...recovery, agent: resolved.agent });
207
207
  continue;
208
208
  }
209
209
  verdicts.push({
@@ -5,9 +5,9 @@
5
5
  * `cp` dance documented in the README.
6
6
  */
7
7
  import { type LocalRunnerSetting } from "../lib/config.ts";
8
- declare const INIT_MODELS: readonly ["claude", "codex"];
8
+ declare const INIT_AGENTS: readonly ["claude", "codex"];
9
9
  type InitConfigScope = "global" | "local";
10
- type InitModel = (typeof INIT_MODELS)[number];
10
+ type InitAgent = (typeof INIT_AGENTS)[number];
11
11
  interface InitConfigOptions {
12
12
  /** Where to write the config. Defaults to "local" (cwd). */
13
13
  scope?: InitConfigScope;
@@ -23,8 +23,8 @@ interface InitConfigOptions {
23
23
  repositories?: string[];
24
24
  /** Pre-fill local.runner in the generated config. */
25
25
  runner?: LocalRunnerSetting;
26
- /** Choose the single built-in model preset enabled by the generated config. */
27
- model?: InitModel;
26
+ /** Choose the single built-in agent preset enabled by the generated config. */
27
+ agent?: InitAgent;
28
28
  /** Override the source template path. */
29
29
  examplePath?: string;
30
30
  }
@@ -13,8 +13,8 @@ import { xdgConfigPath } from "../lib/xdg.js";
13
13
  const CONFIG_FILE_NAME = "crew.config.ts";
14
14
  const EXAMPLE_FILE_NAME = "crew.config.example.ts";
15
15
  const DEFAULT_EXAMPLE_PROJECT_DIR = "~/dev/groundcrew";
16
- const INIT_USAGE = "Usage: crew init [--global | --local] [--force] [--dry-run] [--project-dir <dir>] [--repo <owner/repo>]... [--runner <auto|safehouse|sdx|none>] [--model <claude|codex>]";
17
- const INIT_MODELS = ["claude", "codex"];
16
+ const INIT_USAGE = "Usage: crew init [--global | --local] [--force] [--dry-run] [--project-dir <dir>] [--repo <owner/repo>]... [--runner <auto|safehouse|sdx|none>] [--agent <claude|codex>]";
17
+ const INIT_AGENTS = ["claude", "codex"];
18
18
  export function initConfig(options = {}) {
19
19
  const scope = options.scope ?? "local";
20
20
  const cwd = options.cwd ?? process.cwd();
@@ -51,7 +51,7 @@ function parseArguments(argv) {
51
51
  let projectDir;
52
52
  const repositories = [];
53
53
  let runner;
54
- let model;
54
+ let agent;
55
55
  for (let index = 0; index < argv.length; index += 1) {
56
56
  const argument = argv[index];
57
57
  /* v8 ignore next 3 @preserve -- loop bounds keep argv[index] defined */
@@ -89,8 +89,8 @@ function parseArguments(argv) {
89
89
  index += 1;
90
90
  continue;
91
91
  }
92
- if (argument === "--model") {
93
- model = parseModel(readOptionValue(argv, index, argument));
92
+ if (argument === "--agent") {
93
+ agent = parseAgent(readOptionValue(argv, index, argument));
94
94
  index += 1;
95
95
  continue;
96
96
  }
@@ -108,8 +108,8 @@ function parseArguments(argv) {
108
108
  if (runner !== undefined) {
109
109
  parsed.runner = runner;
110
110
  }
111
- if (model !== undefined) {
112
- parsed.model = model;
111
+ if (agent !== undefined) {
112
+ parsed.agent = agent;
113
113
  }
114
114
  return parsed;
115
115
  }
@@ -137,16 +137,16 @@ function parseRunner(value) {
137
137
  }
138
138
  throw new Error(`crew init --runner must be one of ${LOCAL_RUNNER_SETTINGS.join(", ")}`);
139
139
  }
140
- function parseModel(value) {
141
- if (isInitModel(value)) {
140
+ function parseAgent(value) {
141
+ if (isInitAgent(value)) {
142
142
  return value;
143
143
  }
144
- throw new Error(`crew init --model must be one of ${INIT_MODELS.join(", ")}`);
144
+ throw new Error(`crew init --agent must be one of ${INIT_AGENTS.join(", ")}`);
145
145
  }
146
146
  function isLocalRunnerSetting(value) {
147
147
  return value === "auto" || value === "safehouse" || value === "sdx" || value === "none";
148
148
  }
149
- function isInitModel(value) {
149
+ function isInitAgent(value) {
150
150
  return value === "claude" || value === "codex";
151
151
  }
152
152
  function tsString(value) {
@@ -163,15 +163,15 @@ function renderConfig(source, options) {
163
163
  if (options.runner !== undefined) {
164
164
  contents = replaceRequired(contents, ` // local: { runner: "auto" },`, ` local: { runner: ${tsString(options.runner)} },`, "--runner");
165
165
  }
166
- if (options.model !== undefined) {
167
- contents = replaceRequired(contents, ` default: "claude",`, ` default: ${tsString(options.model)},`, "--model");
168
- contents = replaceRequired(contents, " claude: {},", ` ${options.model}: {},`, "--model");
169
- contents = removeDuplicateModelDefinitionLines(contents, options.model);
166
+ if (options.agent !== undefined) {
167
+ contents = replaceRequired(contents, ` default: "claude",`, ` default: ${tsString(options.agent)},`, "--agent");
168
+ contents = replaceRequired(contents, " claude: {},", ` ${options.agent}: {},`, "--agent");
169
+ contents = removeDuplicateAgentDefinitionLines(contents, options.agent);
170
170
  }
171
171
  return contents;
172
172
  }
173
- function removeDuplicateModelDefinitionLines(contents, model) {
174
- const linePattern = new RegExp(`^\\s*(?://\\s*)?${escapeRegExp(model)}:\\s*\\{\\},\\s*$`);
173
+ function removeDuplicateAgentDefinitionLines(contents, agent) {
174
+ const linePattern = new RegExp(`^\\s*(?://\\s*)?${escapeRegExp(agent)}:\\s*\\{\\},\\s*$`);
175
175
  let hasActiveEntry = false;
176
176
  return contents
177
177
  .split("\n")
@@ -36,7 +36,7 @@ function sourceFromState(state) {
36
36
  return {
37
37
  task: state.task,
38
38
  repository: state.repository,
39
- model: state.model,
39
+ agent: state.agent,
40
40
  worktreeDir: state.worktreeDir,
41
41
  branchName: state.branchName,
42
42
  workspaceName: state.workspaceName,
@@ -47,7 +47,7 @@ function sourceFromWorktree(config, task, entry) {
47
47
  return {
48
48
  task,
49
49
  repository: entry.repository,
50
- model: config.models.default,
50
+ agent: config.agents.default,
51
51
  worktreeDir: entry.dir,
52
52
  branchName: entry.branchName,
53
53
  workspaceName: task,
@@ -89,7 +89,7 @@ export async function interruptWorkspace(config, options) {
89
89
  state: {
90
90
  task,
91
91
  repository: source.repository,
92
- model: source.model,
92
+ agent: source.agent,
93
93
  worktreeDir: source.worktreeDir,
94
94
  branchName: source.branchName,
95
95
  workspaceName: source.workspaceName,
@@ -9,7 +9,7 @@ import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
9
9
  import { loadConfigWithSource } from "../lib/config.js";
10
10
  import { findPullRequestsForBranch } from "../lib/pullRequests.js";
11
11
  import { RepositoryResolutionError } from "../lib/taskSource.js";
12
- import { getUsageByModel } from "../lib/usage.js";
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";
15
15
  import { createCleaner } from "./cleaner.js";
@@ -55,7 +55,7 @@ class WatchLoopShutdownError extends Error {
55
55
  }
56
56
  async function fetchUsageOrEmpty(config, signal) {
57
57
  try {
58
- return await getUsageByModel(config, signal);
58
+ return await getUsageByAgent(config, signal);
59
59
  }
60
60
  catch (error) {
61
61
  if (signal?.aborted === true) {