@clipboard-health/groundcrew 3.1.2 → 3.1.4

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 (59) hide show
  1. package/README.md +47 -17
  2. package/crew.config.example.ts +25 -10
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +12 -0
  5. package/dist/commands/cleaner.d.ts.map +1 -1
  6. package/dist/commands/cleaner.js +6 -4
  7. package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
  8. package/dist/commands/cleanupWorkspace.js +2 -0
  9. package/dist/commands/dispatcher.d.ts.map +1 -1
  10. package/dist/commands/dispatcher.js +6 -6
  11. package/dist/commands/eligibility.d.ts.map +1 -1
  12. package/dist/commands/eligibility.js +2 -2
  13. package/dist/commands/interruptWorkspace.d.ts +8 -0
  14. package/dist/commands/interruptWorkspace.d.ts.map +1 -0
  15. package/dist/commands/interruptWorkspace.js +108 -0
  16. package/dist/commands/orchestrator.d.ts +4 -2
  17. package/dist/commands/orchestrator.d.ts.map +1 -1
  18. package/dist/commands/orchestrator.js +6 -105
  19. package/dist/commands/resumeWorkspace.d.ts +7 -0
  20. package/dist/commands/resumeWorkspace.d.ts.map +1 -0
  21. package/dist/commands/resumeWorkspace.js +163 -0
  22. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  23. package/dist/commands/setupWorkspace.js +78 -79
  24. package/dist/commands/ticketDoctor.d.ts +18 -3
  25. package/dist/commands/ticketDoctor.d.ts.map +1 -1
  26. package/dist/commands/ticketDoctor.js +105 -11
  27. package/dist/index.d.ts +6 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +5 -2
  30. package/dist/lib/agentLaunch.d.ts +29 -0
  31. package/dist/lib/agentLaunch.d.ts.map +1 -0
  32. package/dist/lib/agentLaunch.js +53 -0
  33. package/dist/lib/boardSource.d.ts +41 -5
  34. package/dist/lib/boardSource.d.ts.map +1 -1
  35. package/dist/lib/boardSource.js +211 -70
  36. package/dist/lib/config.d.ts +59 -25
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js +130 -22
  39. package/dist/lib/linearIssueStatus.d.ts +3 -1
  40. package/dist/lib/linearIssueStatus.d.ts.map +1 -1
  41. package/dist/lib/linearIssueStatus.js +0 -0
  42. package/dist/lib/runState.d.ts +46 -0
  43. package/dist/lib/runState.d.ts.map +1 -0
  44. package/dist/lib/runState.js +137 -0
  45. package/dist/lib/runStateCleanup.d.ts +4 -0
  46. package/dist/lib/runStateCleanup.d.ts.map +1 -0
  47. package/dist/lib/runStateCleanup.js +12 -0
  48. package/dist/lib/stagedLaunch.d.ts +32 -0
  49. package/dist/lib/stagedLaunch.d.ts.map +1 -0
  50. package/dist/lib/stagedLaunch.js +58 -0
  51. package/dist/lib/util.d.ts +0 -1
  52. package/dist/lib/util.d.ts.map +1 -1
  53. package/dist/lib/util.js +0 -4
  54. package/dist/lib/workspaces.d.ts +19 -1
  55. package/dist/lib/workspaces.d.ts.map +1 -1
  56. package/dist/lib/workspaces.js +29 -9
  57. package/dist/lib/worktrees.d.ts.map +1 -1
  58. package/dist/lib/worktrees.js +12 -4
  59. package/package.json +1 -1
@@ -1,36 +1,20 @@
1
1
  /**
2
- * groundcrew orchestrator — polls a Linear project and spins up
3
- * workspace + git-worktree pairs for ready tickets.
2
+ * groundcrew orchestrator — polls Linear projects and spins up workspace +
3
+ * git-worktree pairs for ready tickets. Each tick fetches the board, runs
4
+ * the cleaner, and runs the dispatcher; logging from those modules is the
5
+ * orchestrator's user-facing output.
4
6
  */
5
- import { createBoardSource, isTerminalStatus, RepositoryResolutionError, } from "../lib/boardSource.js";
7
+ import { createBoardSource, RepositoryResolutionError, } from "../lib/boardSource.js";
6
8
  import { loadConfig } from "../lib/config.js";
7
9
  import { getUsageByModel } from "../lib/usage.js";
8
- import { clearOutput, errorMessage, getLinearClient, log, sleep, writeOutput, } from "../lib/util.js";
10
+ import { errorMessage, getLinearClient, log, sleep } from "../lib/util.js";
9
11
  import { worktrees } from "../lib/worktrees.js";
10
12
  import { createCleaner } from "./cleaner.js";
11
13
  import { createDispatcher } from "./dispatcher.js";
12
14
  const RATE_LIMIT_DELAY_MS = 60_000;
13
15
  const RETRY_BASE_DELAY_MS = 1000;
14
16
  const RETRY_MAX_ATTEMPTS = 3;
15
- const STATUS_CARD_TITLE_WIDTH = 42;
16
- const STATUS_CARD_ID_WIDTH = 8;
17
- const STATUS_CARD_LIMIT = 10;
18
- const HEADER_BAR_WIDTH = 70;
19
- const SECTION_BAR_WIDTH = 50;
20
17
  const MS_PER_SECOND = 1000;
21
- const STATUS_ICON_DEFAULT = " ";
22
- function statusIconFor(status, config) {
23
- if (status === config.linear.statuses.inProgress) {
24
- return ">>";
25
- }
26
- if (status === config.linear.statuses.todo) {
27
- return "--";
28
- }
29
- if (isTerminalStatus(status, config)) {
30
- return "ok";
31
- }
32
- return STATUS_ICON_DEFAULT;
33
- }
34
18
  async function withRetry(function_, signal, maxRetries = RETRY_MAX_ATTEMPTS, baseDelayMs = RETRY_BASE_DELAY_MS) {
35
19
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
36
20
  try {
@@ -64,86 +48,6 @@ class WatchLoopShutdownError extends Error {
64
48
  this.name = "WatchLoopShutdownError";
65
49
  }
66
50
  }
67
- function groupByStatus(issues, knownOrder) {
68
- const groups = new Map();
69
- for (const status of knownOrder) {
70
- groups.set(status, []);
71
- }
72
- for (const issue of issues) {
73
- /* v8 ignore next @preserve -- knownOrder seeds an entry for each issue.status returned by buildStatusOrder */
74
- const group = groups.get(issue.status) ?? [];
75
- group.push(issue);
76
- groups.set(issue.status, group);
77
- }
78
- return groups;
79
- }
80
- function buildStatusOrder(state, config) {
81
- const head = [
82
- ...new Set([
83
- config.linear.statuses.inProgress,
84
- config.linear.statuses.todo,
85
- config.linear.statuses.done,
86
- ...config.linear.statuses.terminal,
87
- ]),
88
- ];
89
- const seen = new Set(head);
90
- const tail = [];
91
- for (const issue of state.issues) {
92
- if (!seen.has(issue.status)) {
93
- seen.add(issue.status);
94
- tail.push(issue.status);
95
- }
96
- }
97
- return [...head, ...tail];
98
- }
99
- function render(state, config, previous) {
100
- const order = buildStatusOrder(state, config);
101
- const grouped = groupByStatus(state.issues, order);
102
- const previousGrouped = previous ? groupByStatus(previous.issues, order) : undefined;
103
- const previousById = previous
104
- ? new Map(previous.issues.map((issue) => [issue.id, issue]))
105
- : undefined;
106
- clearOutput();
107
- writeOutput(`groundcrew — ${config.linear.projectSlug} — ${new Date(state.timestamp).toLocaleTimeString()}`);
108
- writeOutput(`Max in progress: ${config.orchestrator.maximumInProgress}`);
109
- writeOutput("=".repeat(HEADER_BAR_WIDTH));
110
- writeOutput();
111
- for (const [status, issues] of grouped) {
112
- if (issues.length === 0) {
113
- continue;
114
- }
115
- const previousCount = previousGrouped?.get(status)?.length ?? issues.length;
116
- const delta = issues.length === previousCount
117
- ? ""
118
- : ` (${issues.length > previousCount ? "+" : ""}${issues.length - previousCount})`;
119
- writeOutput(`${statusIconFor(status, config)} ${status} (${issues.length})${delta}`);
120
- writeOutput("-".repeat(SECTION_BAR_WIDTH));
121
- // Cap each status at the N most recent so a backlog of hundreds of Done
122
- // tickets doesn't clog the terminal. Sort only when truncating so smaller
123
- // statuses keep whatever order Linear returned.
124
- const visible = issues.length > STATUS_CARD_LIMIT
125
- ? issues
126
- .toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt))
127
- .slice(0, STATUS_CARD_LIMIT)
128
- : issues;
129
- for (const issue of visible) {
130
- const previousIssue = previousById?.get(issue.id);
131
- const changed = previousIssue && previousIssue.status !== issue.status
132
- ? ` [was: ${previousIssue.status}]`
133
- : "";
134
- writeOutput(` ${issue.id.padEnd(STATUS_CARD_ID_WIDTH)} ${issue.title.slice(0, STATUS_CARD_TITLE_WIDTH).padEnd(STATUS_CARD_TITLE_WIDTH)} ${issue.assignee}${changed}`);
135
- }
136
- if (issues.length > STATUS_CARD_LIMIT) {
137
- writeOutput(` … showing ${STATUS_CARD_LIMIT} most recent of ${issues.length}; ${issues.length - STATUS_CARD_LIMIT} older hidden`);
138
- }
139
- writeOutput();
140
- }
141
- const total = state.issues.length;
142
- const done = state.issues.filter((issue) => isTerminalStatus(issue.status, config)).length;
143
- /* v8 ignore next @preserve -- grouped has all known statuses pre-seeded by groupByStatus */
144
- const active = grouped.get(config.linear.statuses.inProgress)?.length ?? 0;
145
- writeOutput(`Total: ${total} | Active: ${active}/${config.orchestrator.maximumInProgress} | Done: ${done} | Remaining: ${total - done}`);
146
- }
147
51
  async function fetchUsageOrEmpty(config, signal) {
148
52
  try {
149
53
  return await getUsageByModel(config, signal);
@@ -163,10 +67,8 @@ export async function orchestrate(options) {
163
67
  await boardSource.verify();
164
68
  const cleaner = createCleaner({ config });
165
69
  const dispatcher = createDispatcher({ config, client });
166
- let previous;
167
70
  const tick = async (signal) => {
168
71
  const state = await withRetry(async () => await boardSource.fetch(), signal);
169
- render(state, config, previous);
170
72
  const worktreeEntries = worktrees.list(config);
171
73
  const tickArguments = {
172
74
  state,
@@ -181,7 +83,6 @@ export async function orchestrate(options) {
181
83
  // an idle board doesn't burn a codexbar shell-out per tick.
182
84
  usage: async (usageSignal) => await fetchUsageOrEmpty(config, usageSignal),
183
85
  });
184
- previous = state;
185
86
  };
186
87
  await (options.watch ? runWatchLoop(tick, config) : tick());
187
88
  }
@@ -0,0 +1,7 @@
1
+ import { type ResolvedConfig } from "../lib/config.ts";
2
+ export interface ResumeWorkspaceOptions {
3
+ ticket: string;
4
+ }
5
+ export declare function resumeWorkspace(config: ResolvedConfig, options: ResumeWorkspaceOptions): Promise<void>;
6
+ export declare function resumeWorkspaceCli(argv: string[]): Promise<void>;
7
+ //# sourceMappingURL=resumeWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Df;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -0,0 +1,163 @@
1
+ import { fetchResolvedIssue } from "../lib/boardSource.js";
2
+ import { loadConfig } from "../lib/config.js";
3
+ import { ensureAgentSandbox, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
+ import { buildLaunchCommand } from "../lib/launchCommand.js";
5
+ import { readRunState, recordRunState } from "../lib/runState.js";
6
+ import { removeStagedPrompt, stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
7
+ import { errorMessage, getLinearClient, log } from "../lib/util.js";
8
+ import { workspaces } from "../lib/workspaces.js";
9
+ import { worktrees } from "../lib/worktrees.js";
10
+ function parseArguments(argv) {
11
+ const [ticket, ...extras] = argv;
12
+ if (ticket === undefined || ticket.length === 0 || extras.length > 0 || ticket.startsWith("-")) {
13
+ throw new Error("Usage: crew resume <ticket>");
14
+ }
15
+ return { ticket: ticket.toLowerCase() };
16
+ }
17
+ async function fetchTicketDetails(ticket) {
18
+ try {
19
+ const issue = await getLinearClient().issue(ticket.toUpperCase());
20
+ return {
21
+ title: issue.title,
22
+ description: issue.description ?? "",
23
+ };
24
+ }
25
+ catch (error) {
26
+ log(`Resume Linear detail lookup failed for ${ticket}: ${errorMessage(error)}`);
27
+ return undefined;
28
+ }
29
+ }
30
+ async function contextFromLinear(config, ticket, worktree) {
31
+ const resolved = await fetchResolvedIssue({ client: getLinearClient(), config, ticket });
32
+ return {
33
+ ticket,
34
+ repository: resolved.repository,
35
+ model: resolved.model,
36
+ worktree,
37
+ title: resolved.title,
38
+ description: resolved.description,
39
+ resumeCount: 0,
40
+ };
41
+ }
42
+ async function contextFromState(ticket, state, worktree) {
43
+ const details = await fetchTicketDetails(ticket);
44
+ return {
45
+ ticket,
46
+ repository: state.repository,
47
+ model: state.model,
48
+ worktree,
49
+ title: details?.title ?? ticket.toUpperCase(),
50
+ description: details?.description ?? "",
51
+ ...(state.reason === undefined ? {} : { reason: state.reason }),
52
+ resumeCount: state.resumeCount,
53
+ };
54
+ }
55
+ async function buildResumeContext(config, ticket) {
56
+ const state = readRunState(config, ticket);
57
+ const entries = worktrees.findByTicket(config, ticket);
58
+ const worktree = state === undefined
59
+ ? entries[0]
60
+ : (entries.find((entry) => entry.repository === state.repository) ?? entries[0]);
61
+ if (worktree === undefined) {
62
+ throw new Error(`No worktree found for ${ticket}; cannot resume.`);
63
+ }
64
+ if (state !== undefined) {
65
+ return await contextFromState(ticket, state, worktree);
66
+ }
67
+ return await contextFromLinear(config, ticket, worktree);
68
+ }
69
+ function renderResumePrompt(context) {
70
+ return [
71
+ `You are resuming Groundcrew ticket ${context.ticket} (${context.title}) in an existing worktree.`,
72
+ "",
73
+ "Ticket description:",
74
+ "",
75
+ context.description,
76
+ "",
77
+ "## Continuation context",
78
+ "",
79
+ `- Worktree: ${context.worktree.dir}`,
80
+ `- Branch: ${context.worktree.branchName}`,
81
+ context.reason === undefined
82
+ ? "- Previous interrupt reason: none recorded"
83
+ : `- Previous interrupt reason: ${context.reason}`,
84
+ "",
85
+ "Before editing, inspect the current git status and diff. Continue from the work already present in this worktree; do not restart from scratch unless the diff proves that is necessary.",
86
+ "",
87
+ "Run the repository's documented verification before stopping, then leave the branch ready or open a PR when possible.",
88
+ ].join("\n");
89
+ }
90
+ async function failIfWorkspaceAlreadyLive(config, ticket) {
91
+ const probe = await workspaces.probe(config);
92
+ if (probe.kind === "unavailable") {
93
+ const detail = probe.error === undefined ? "" : `: ${errorMessage(probe.error)}`;
94
+ throw new Error(`Could not verify whether workspace for ${ticket} is already live${detail}. Retry or inspect the workspace backend manually before resuming.`);
95
+ }
96
+ if (probe.names.has(ticket)) {
97
+ throw new Error(`Workspace for ${ticket} is already live; attach to it instead of resuming.`);
98
+ }
99
+ }
100
+ export async function resumeWorkspace(config, options) {
101
+ const ticket = options.ticket.toLowerCase();
102
+ await failIfWorkspaceAlreadyLive(config, ticket);
103
+ const context = await buildResumeContext(config, ticket);
104
+ const definition = config.models.definitions[context.model];
105
+ if (definition === undefined) {
106
+ throw new Error(`Unknown model: ${context.model}`);
107
+ }
108
+ const { runner, sandboxName } = await prepareAgentLaunch({
109
+ config,
110
+ model: context.model,
111
+ definition,
112
+ purpose: "resumes",
113
+ });
114
+ const stagedPrompt = stagePromptText({
115
+ prefix: "groundcrew-resume",
116
+ ticket,
117
+ text: renderResumePrompt(context),
118
+ });
119
+ const secretsFile = stageBuildSecrets(stagedPrompt.directory);
120
+ await ensureAgentSandbox({ config, definition, sandboxName });
121
+ const launchCommand = buildLaunchCommand({
122
+ definition,
123
+ promptFile: stagedPrompt.file,
124
+ worktreeDir: context.worktree.dir,
125
+ secretsFile,
126
+ runner,
127
+ sandboxName,
128
+ });
129
+ const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
130
+ try {
131
+ await openAgentWorkspace({
132
+ config,
133
+ name: ticket,
134
+ cwd: context.worktree.dir,
135
+ command: launchCmd,
136
+ model: context.model,
137
+ color: definition.color,
138
+ });
139
+ }
140
+ catch (error) {
141
+ removeStagedPrompt(stagedPrompt.directory);
142
+ throw error;
143
+ }
144
+ recordRunState({
145
+ config,
146
+ state: {
147
+ ticket,
148
+ repository: context.repository,
149
+ model: context.model,
150
+ worktreeDir: context.worktree.dir,
151
+ branchName: context.worktree.branchName,
152
+ workspaceName: ticket,
153
+ state: "resumed",
154
+ resumeCount: context.resumeCount + 1,
155
+ ...(context.reason === undefined ? {} : { reason: context.reason }),
156
+ },
157
+ });
158
+ log(`Resumed ${ticket} in ${context.worktree.dir} (${context.model})`);
159
+ }
160
+ export async function resumeWorkspaceCli(argv) {
161
+ const config = await loadConfig();
162
+ await resumeWorkspace(config, parseArguments(argv));
163
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAOA,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAUvF,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAmED,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAuGf;AA0FD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAqBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA6Gf;AAwHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -1,15 +1,12 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import { ensureClearance } from "@clipboard-health/clearance";
1
+ import { rmSync } from "node:fs";
5
2
  import { fetchResolvedIssue } from "../lib/boardSource.js";
6
- import { BUILD_SECRET_NAMES, loadConfig } from "../lib/config.js";
7
- import { ensureSandbox, sandboxNameFor } from "../lib/dockerSandbox.js";
8
- import { detectHostCapabilities } from "../lib/host.js";
9
- import { buildLaunchCommand, shellSingleQuote } from "../lib/launchCommand.js";
3
+ import { loadConfig } from "../lib/config.js";
4
+ import { ensureAgentSandbox, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
5
+ import { buildLaunchCommand } from "../lib/launchCommand.js";
10
6
  import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
11
- import { assertLocalRunnerRequirements, resolveLocalRunner } from "../lib/localRunner.js";
12
- import { errorMessage, getLinearClient, log, readEnvironmentVariable } from "../lib/util.js";
7
+ import { recordRunState } from "../lib/runState.js";
8
+ import { stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
9
+ import { errorMessage, getLinearClient, log } from "../lib/util.js";
13
10
  import { workspaces } from "../lib/workspaces.js";
14
11
  import { isWorktreeAlreadyExistsError, worktrees } from "../lib/worktrees.js";
15
12
  async function fetchTicket(ticket) {
@@ -20,54 +17,18 @@ async function fetchTicket(ticket) {
20
17
  description: issue.description ?? "",
21
18
  };
22
19
  }
23
- function renderPrompt(template, variables) {
24
- return template
25
- .replaceAll("{{ticket}}", variables.ticket)
26
- .replaceAll("{{worktree}}", variables.worktree)
27
- .replaceAll("{{title}}", variables.title)
28
- .replaceAll("{{description}}", variables.description);
29
- }
30
- /**
31
- * Stage a `KEY='value'` env file for any populated build-time secret so
32
- * the launch command can source it. Returns `undefined` when groundcrew
33
- * has nothing to forward, leaving the launch command unchanged. The temp
34
- * dir is `rm -rf`'d by the launch command (and rollback path), so cleanup
35
- * is already handled.
36
- */
37
- function stageBuildSecrets(promptDir) {
38
- const lines = [];
39
- for (const name of BUILD_SECRET_NAMES) {
40
- const value = readEnvironmentVariable(name);
41
- if (value === undefined || value.length === 0) {
42
- continue;
43
- }
44
- lines.push(`${name}=${shellSingleQuote(value)}`);
45
- }
46
- if (lines.length === 0) {
47
- return undefined;
48
- }
49
- const secretsFile = join(promptDir, "secrets.env");
50
- writeFileSync(secretsFile, `${lines.join("\n")}\n`, { mode: 0o600 });
51
- return secretsFile;
52
- }
53
- function stageLaunchScript(promptDir, command) {
54
- const launcherFile = join(promptDir, "launch.sh");
55
- writeFileSync(launcherFile, `#!/usr/bin/env bash\n${command}\n`, { mode: 0o700 });
56
- return launcherFile;
57
- }
58
- function stageWorkspaceLaunchCommand(promptDir, command) {
59
- return `bash ${shellSingleQuote(stageLaunchScript(promptDir, command))}`;
60
- }
61
20
  function stagePrompt(input) {
62
- const promptDir = mkdtempSync(join(tmpdir(), `groundcrew-${input.ticket}-`));
63
- const promptFile = join(promptDir, "prompt.txt");
64
- writeFileSync(promptFile, renderPrompt(input.config.prompts.initial, {
21
+ return stagePromptFromTemplate({
22
+ config: input.config,
23
+ prefix: "groundcrew",
65
24
  ticket: input.ticket,
66
- worktree: input.worktreeName,
67
- title: input.ticketDetails.title,
68
- description: input.ticketDetails.description,
69
- }));
70
- return { directory: promptDir, file: promptFile };
25
+ variables: {
26
+ ticket: input.ticket,
27
+ worktree: input.worktreeName,
28
+ title: input.ticketDetails.title,
29
+ description: input.ticketDetails.description,
30
+ },
31
+ });
71
32
  }
72
33
  export async function setupWorkspace(config, options, runOptions = {}) {
73
34
  const { ticket, repository, model } = options;
@@ -76,16 +37,13 @@ export async function setupWorkspace(config, options, runOptions = {}) {
76
37
  if (!definition) {
77
38
  throw new Error(`Unknown model: ${model}`);
78
39
  }
79
- const host = await detectHostCapabilities(signal);
80
- const runner = resolveLocalRunner(config.local.runner, host);
81
- assertLocalRunnerRequirements(host, runner);
82
- if (runner === "safehouse") {
83
- await ensureClearance({ logger: log });
84
- }
85
- if (runner === "sdx" && definition.sandbox === undefined) {
86
- throw new Error(`Local groundcrew runs with the sdx runner require a sandbox config on model '${model}'. ` +
87
- "Add `sandbox: { agent: '<sbx-agent-name>' }` to the model in your config.ts.");
88
- }
40
+ const { runner, sandboxName } = await prepareAgentLaunch({
41
+ config,
42
+ model,
43
+ definition,
44
+ purpose: "runs",
45
+ ...(signal === undefined ? {} : { signal }),
46
+ });
89
47
  const spec = { repository, ticket };
90
48
  let created;
91
49
  try {
@@ -122,16 +80,12 @@ export async function setupWorkspace(config, options, runOptions = {}) {
122
80
  const stagedPrompt = stagePrompt({ config, ticket, ticketDetails, worktreeName });
123
81
  promptDir = stagedPrompt.directory;
124
82
  const secretsFile = stageBuildSecrets(promptDir);
125
- const sandboxName = runner === "sdx" && definition.sandbox !== undefined
126
- ? sandboxNameFor({ agent: definition.sandbox.agent })
127
- : undefined;
128
- if (runner === "sdx" && sandboxName !== undefined && definition.sandbox !== undefined) {
129
- await ensureSandbox({
130
- sandboxName,
131
- sandbox: definition.sandbox,
132
- mountPath: resolve(config.workspace.projectDir),
133
- }, signal);
134
- }
83
+ await ensureAgentSandbox({
84
+ config,
85
+ definition,
86
+ sandboxName,
87
+ ...(signal === undefined ? {} : { signal }),
88
+ });
135
89
  const launchCommand = buildLaunchCommand({
136
90
  definition,
137
91
  promptFile: stagedPrompt.file,
@@ -142,12 +96,25 @@ export async function setupWorkspace(config, options, runOptions = {}) {
142
96
  });
143
97
  const launchCmd = stageWorkspaceLaunchCommand(promptDir, launchCommand);
144
98
  log("Opening workspace...");
145
- await workspaces.open(config, {
99
+ await openAgentWorkspace({
100
+ config,
146
101
  name: ticket,
147
102
  cwd: launchDir,
148
103
  command: launchCmd,
149
- status: { text: model, color: definition.color, icon: "sparkle" },
150
- }, signal);
104
+ model,
105
+ color: definition.color,
106
+ ...(signal === undefined ? {} : { signal }),
107
+ });
108
+ recordRunStateBestEffort({
109
+ config,
110
+ ticket,
111
+ repository,
112
+ model,
113
+ worktreeDir: launchDir,
114
+ branchName,
115
+ workspaceName: ticket,
116
+ state: "running",
117
+ });
151
118
  log(`Workspace "${ticket}" launched (${model})`);
152
119
  log(` Worktree: ${launchDir}`);
153
120
  log(` Branch: ${branchName}`);
@@ -155,6 +122,17 @@ export async function setupWorkspace(config, options, runOptions = {}) {
155
122
  }
156
123
  catch (error) {
157
124
  await rollbackWorktree({ config, entry: created, promptDir });
125
+ recordRunStateBestEffort({
126
+ config,
127
+ ticket,
128
+ repository,
129
+ model,
130
+ worktreeDir: launchDir,
131
+ branchName,
132
+ workspaceName: ticket,
133
+ state: "failed-to-launch",
134
+ detail: errorMessage(error),
135
+ });
158
136
  throw error;
159
137
  }
160
138
  }
@@ -188,6 +166,26 @@ async function logWorkspaceAccessHint(arguments_) {
188
166
  function logAccessHint(accessHint) {
189
167
  log(` Attach: ${accessHint.command}`);
190
168
  }
169
+ function recordRunStateBestEffort(arguments_) {
170
+ try {
171
+ recordRunState({
172
+ config: arguments_.config,
173
+ state: {
174
+ ticket: arguments_.ticket,
175
+ repository: arguments_.repository,
176
+ model: arguments_.model,
177
+ worktreeDir: arguments_.worktreeDir,
178
+ branchName: arguments_.branchName,
179
+ workspaceName: arguments_.workspaceName,
180
+ state: arguments_.state,
181
+ ...(arguments_.detail === undefined ? {} : { detail: arguments_.detail }),
182
+ },
183
+ });
184
+ }
185
+ catch (error) {
186
+ log(`Run state update failed for ${arguments_.ticket}: ${errorMessage(error)}`);
187
+ }
188
+ }
191
189
  async function rollbackWorktree(arguments_) {
192
190
  log(`Setup failed; rolling back worktree ${arguments_.entry.repository}-${arguments_.entry.ticket}...`);
193
191
  let result;
@@ -243,5 +241,6 @@ export async function setupWorkspaceCli(ticket, options = {}) {
243
241
  id: ticket.toLowerCase(),
244
242
  uuid: resolved.uuid,
245
243
  teamId: resolved.teamId,
244
+ projectSlugId: resolved.projectSlugId,
246
245
  });
247
246
  }
@@ -1,5 +1,6 @@
1
1
  import { type Blocker, type RawLinearIssue } from "../lib/boardSource.ts";
2
2
  import { type ResolvedConfig } from "../lib/config.ts";
3
+ import { type RunState } from "../lib/runState.ts";
3
4
  import { type UsageByModel } from "../lib/usage.ts";
4
5
  import { type WorkspaceAccessHint, type WorkspaceProbe } from "../lib/workspaces.ts";
5
6
  import { type WorktreeDirtiness, type WorktreeEntry } from "../lib/worktrees.ts";
@@ -12,6 +13,14 @@ export type TicketDoctorVerdict = {
12
13
  kind: "pr-merged";
13
14
  number: number;
14
15
  url: string;
16
+ } | {
17
+ kind: "interrupted";
18
+ reason: string;
19
+ nextStep: string;
20
+ } | {
21
+ kind: "failed-launch";
22
+ reason: string;
23
+ nextStep: string;
15
24
  } | {
16
25
  kind: "in-flight";
17
26
  reason: string;
@@ -99,15 +108,18 @@ export interface DecideVerdictInput {
99
108
  branch: string;
100
109
  worktreeDir: string | undefined;
101
110
  workspaceName: string | undefined;
111
+ runState: RunState | undefined;
102
112
  }
103
113
  /**
104
114
  * Returns a post-dispatch verdict if the probe bundle matches one of the
105
115
  * "ticket has moved past dispatch" cases. Returns `undefined` otherwise,
106
116
  * signalling that the caller should fall through to the pre-dispatch path.
107
117
  *
108
- * Precedence: PR > in-flight > recoverable. Inside `recoverable`, dirty
109
- * worktree beats clean-with-un-pushed-local beats remote-only beats stranded
110
- * local.
118
+ * Precedence: PR verdicts always win. Failed launches report before ordinary
119
+ * local recovery. Interrupted runs report concrete recoverable git work first
120
+ * when it exists, then fall back to `interrupted`. Ordinary post-dispatch cases
121
+ * report in-flight before recoverable. Inside `recoverable`, dirty worktree
122
+ * beats clean-with-un-pushed-local beats remote-only beats stranded local.
111
123
  */
112
124
  export declare function decidePostDispatchVerdict(input: DecideVerdictInput): TicketDoctorVerdict | undefined;
113
125
  export interface TicketDoctorDependencies {
@@ -147,6 +159,7 @@ export interface TicketDoctorDependencies {
147
159
  repoDir: string;
148
160
  branch: string;
149
161
  }) => Promise<PullRequestProbe>;
162
+ readRunState: (ticket: string) => RunState | undefined;
150
163
  doFetch: boolean;
151
164
  }
152
165
  export interface TicketDoctorResult {
@@ -154,6 +167,7 @@ export interface TicketDoctorResult {
154
167
  title?: string;
155
168
  resolution: TicketCheck[];
156
169
  eligibility: TicketCheck[];
170
+ runState: TicketCheck[];
157
171
  worktree: TicketCheck[];
158
172
  workspace: TicketCheck[];
159
173
  localBranch: TicketCheck[];
@@ -163,6 +177,7 @@ export interface TicketDoctorResult {
163
177
  resolution: string;
164
178
  eligibility: string;
165
179
  worktree: string;
180
+ runState: string;
166
181
  workspace: string;
167
182
  localBranch: string;
168
183
  remoteBranch: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"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"}