@clipboard-health/groundcrew 4.50.3 → 4.50.5

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 (35) hide show
  1. package/README.md +46 -2
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/agentLaunchCleanup.d.ts +5 -0
  4. package/dist/commands/agentLaunchCleanup.d.ts.map +1 -0
  5. package/dist/commands/agentLaunchCleanup.js +9 -0
  6. package/dist/commands/openWorkspace.d.ts.map +1 -1
  7. package/dist/commands/openWorkspace.js +36 -18
  8. package/dist/commands/resumeWorkspace.d.ts.map +1 -1
  9. package/dist/commands/resumeWorkspace.js +44 -18
  10. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  11. package/dist/commands/setupWorkspace.js +2 -1
  12. package/dist/commands/status.d.ts +16 -0
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +205 -320
  15. package/dist/commands/statusCollect.d.ts +96 -0
  16. package/dist/commands/statusCollect.d.ts.map +1 -0
  17. package/dist/commands/statusCollect.js +463 -0
  18. package/dist/lib/atomicJson.d.ts +15 -0
  19. package/dist/lib/atomicJson.d.ts.map +1 -0
  20. package/dist/lib/atomicJson.js +32 -0
  21. package/dist/lib/pullRequests.d.ts.map +1 -1
  22. package/dist/lib/pullRequests.js +7 -1
  23. package/dist/lib/runState.d.ts.map +1 -1
  24. package/dist/lib/runState.js +3 -6
  25. package/dist/lib/statusJoin.d.ts +41 -0
  26. package/dist/lib/statusJoin.d.ts.map +1 -0
  27. package/dist/lib/statusJoin.js +59 -0
  28. package/dist/lib/statusSnapshot.d.ts +220 -0
  29. package/dist/lib/statusSnapshot.d.ts.map +1 -0
  30. package/dist/lib/statusSnapshot.js +178 -0
  31. package/dist/lib/worktrees.d.ts.map +1 -1
  32. package/dist/lib/worktrees.js +5 -1
  33. package/docs/commands.md +10 -1
  34. package/docs/development.md +1 -1
  35. package/package.json +6 -6
package/README.md CHANGED
@@ -32,7 +32,7 @@ Groundcrew watches assigned tasks, creates isolated worktrees, launches agent CL
32
32
 
33
33
  `crew doctor` checks all of these, so you can install as you go.
34
34
 
35
- - **Node >= 24:** [nvm](https://github.com/nvm-sh/nvm): `nvm install 24`.
35
+ - **Node 24.14.1:** [nvm](https://github.com/nvm-sh/nvm): `nvm install 24.14.1`.
36
36
  - **git:** e.g., `brew install git`, `apt install git`.
37
37
  - **A terminal multiplexer:** [tmux](https://github.com/tmux/tmux/wiki/Installing) (cross-platform), [cmux](https://cmux.com/) (macOS), or [zellij](https://zellij.dev/).
38
38
  - **An agent CLI:** [Claude Code](https://code.claude.com/docs/en/quickstart), [Codex](https://developers.openai.com/codex/quickstart?setup=cli), the [Cursor CLI](https://docs.cursor.com/en/cli/overview) (`cursor-agent`, for the `cursor` and `cursor-grok` presets), and/or [Pi](https://pi.dev/).
@@ -112,7 +112,7 @@ crew task get <TASK> [--source <name>] [--prompt] # inspect one task or i
112
112
  crew task create "Title" --source <name> [--agent <name>] # create a source task
113
113
  crew task done <TASK> [--allow-dirty] # mark a no-PR task done
114
114
  crew task validate [<source>] # validate task content
115
- crew status [<TASK>] # inspect current state or one task
115
+ crew status [<TASK>] [--json [--local-only]] # inspect current state, or emit it as JSON
116
116
  crew run [--watch] # one-shot or --watch forever
117
117
  crew start <TASK> # provision + launch one task now
118
118
  crew stop <TASK> [--reason <text>] # stop workspace, keep worktree
@@ -127,6 +127,50 @@ crew completions <bash|zsh|fish> # print a shell completi
127
127
 
128
128
  See [command details](./docs/commands.md) for status output, doctor behavior, and the stop/resume workflow.
129
129
 
130
+ ### Status snapshots for external monitors
131
+
132
+ `crew status --json` prints two documents and writes them beside the log file:
133
+
134
+ ```text
135
+ <state-dir>/status-local.json # worktrees, run states, sessions, git status
136
+ <state-dir>/status-remote.json # board, pull requests
137
+ ```
138
+
139
+ The two are split by cost. The local tier is local subprocess work, so it is
140
+ safe to poll every few seconds. The remote tier is network-bound and
141
+ rate-limited, so poll it near your `pollIntervalMilliseconds`.
142
+
143
+ ```bash
144
+ crew status --json # both tiers
145
+ crew status --json --local-only # local tier only; never touches the network
146
+ ```
147
+
148
+ `--local-only` is a guarantee about that invocation, not a computed outcome, so
149
+ a monitor's fast loop cannot stall on a slow board or a `gh` timeout.
150
+
151
+ Three rules a reader must honor:
152
+
153
+ - **Subtract locally.** `status-remote.json` ships board classification without
154
+ the local worktree subtraction. Remove tasks present in the local document
155
+ from `inProgress`, `queueReady`, and `queueBlocked` yourself. Precomputing
156
+ that join would report a just-dispatched task as still queued until the next
157
+ slow poll, which is a false statement rather than stale data.
158
+ - **Join pull requests on the worktree directory.** `pullRequestsByWorktree` is
159
+ keyed by absolute worktree path, not by task, because a task with two
160
+ worktrees has two branches. An empty or missing entry means no pull requests
161
+ were found, or the `gh` lookup for that worktree failed; the two are not
162
+ distinguishable.
163
+ - **Read the right timestamp.** `payload.capturedAt` describes the last
164
+ successful fetch; `lastAttemptAt` describes the most recent try. Read
165
+ `lastAttemptStatus` to answer "is the board healthy", and `capturedAt` to
166
+ answer "how old is this queue". A failed fetch keeps the previous payload, so
167
+ last-known-good data survives an outage while the document still says the
168
+ board is unreachable.
169
+
170
+ Durations are never stored, only start instants, so a reader must derive
171
+ elapsed time itself. A cached duration would show a frozen clock, which reads
172
+ as "the agent stopped working" when it did not.
173
+
130
174
  ## Configuration
131
175
 
132
176
  Workspace settings and at least one enabled agent are required; everything else has a default.
package/dist/cli.js CHANGED
@@ -144,7 +144,7 @@ const SUBCOMMANDS = {
144
144
  },
145
145
  status: {
146
146
  summary: "Print read-only groundcrew state, or one task's local/Linear status",
147
- usage: "[<task>]",
147
+ usage: "[<task>] [--json [--local-only]]",
148
148
  invoke: statusCli,
149
149
  },
150
150
  cleanup: {
@@ -0,0 +1,5 @@
1
+ export declare function cleanupAgentLaunchBestEffort(arguments_: {
2
+ cleanup: (() => void) | undefined;
3
+ context: string;
4
+ }): void;
5
+ //# sourceMappingURL=agentLaunchCleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentLaunchCleanup.d.ts","sourceRoot":"","sources":["../../src/commands/agentLaunchCleanup.ts"],"names":[],"mappings":"AAEA,wBAAgB,4BAA4B,CAAC,UAAU,EAAE;IACvD,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,IAAI,CAMP"}
@@ -0,0 +1,9 @@
1
+ import { errorMessage, log } from "../lib/util.js";
2
+ export function cleanupAgentLaunchBestEffort(arguments_) {
3
+ try {
4
+ arguments_.cleanup?.();
5
+ }
6
+ catch (error) {
7
+ log(`Agent launch cleanup failed during ${arguments_.context}: ${errorMessage(error)}`);
8
+ }
9
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"openWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/openWorkspace.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBtF,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAwJD,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAoHf;AA4ID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAE3E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE"}
1
+ {"version":3,"file":"openWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/openWorkspace.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBtF,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0KD,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA0Hf;AA4ID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAE3E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE"}
@@ -12,6 +12,7 @@ import { normalizePlainTaskId } from "../lib/taskId.js";
12
12
  import { debug, errorMessage, log, okMark } from "../lib/util.js";
13
13
  import { failIfWorkspaceAlreadyLive } from "../lib/workspaceLiveness.js";
14
14
  import { resolveLaunchDir, worktrees } from "../lib/worktrees.js";
15
+ import { cleanupAgentLaunchBestEffort } from "./agentLaunchCleanup.js";
15
16
  const OPEN_USAGE = "Usage: crew open <pr> | --branch <name> [--repo <owner/repo>] [--agent <agent>] [--prompt <text> | --prompt-file <path>] [--task <id>] [--dry-run]";
16
17
  const PULL_REQUEST_URL_PATTERN = /^https?:\/\/github\.com\/(?<repository>[^/]+\/[^/]+)\/pull\/(?<pr>\d+)/;
17
18
  function parsePullRequestReference(reference) {
@@ -101,7 +102,17 @@ async function resolveTarget(config, options, repository) {
101
102
  async function rollback(arguments_) {
102
103
  log(`Open failed; rolling back worktree ${arguments_.entry.repository}-${arguments_.entry.task}...`);
103
104
  try {
104
- await worktrees.teardown(arguments_.config, [arguments_.entry], { force: true });
105
+ const result = await worktrees.teardown(arguments_.config, [arguments_.entry], { force: true });
106
+ for (const failure of result.failures) {
107
+ log(`Worktree teardown ${failure.step} failed during rollback: ${errorMessage(failure.error)}`);
108
+ }
109
+ const workspaceAbsent = result.workspaceProbe.kind === "ok" &&
110
+ !result.workspaceProbe.names.has(arguments_.entry.task);
111
+ if (arguments_.workspaceOpened &&
112
+ !workspaceAbsent &&
113
+ !result.closed.includes(arguments_.entry.task)) {
114
+ log(`Workspace close was not confirmed during open rollback for ${arguments_.entry.task}. Close it manually in the configured workspace backend.`);
115
+ }
105
116
  }
106
117
  catch (error) {
107
118
  log(`Worktree teardown failed during rollback: ${errorMessage(error)}`);
@@ -151,6 +162,7 @@ export async function openWorkspace(config, options) {
151
162
  text: options.promptText ?? "",
152
163
  });
153
164
  let cleanupAgentLaunch;
165
+ let workspaceOpened = false;
154
166
  try {
155
167
  const { prepareWorktreeCommand, prepareWorktreeUnsandboxedCommand } = resolveRepositoryPreparationCommands({
156
168
  config,
@@ -191,27 +203,33 @@ export async function openWorkspace(config, options) {
191
203
  agent,
192
204
  color: definition.color,
193
205
  });
206
+ workspaceOpened = true;
207
+ recordRunState({
208
+ config,
209
+ state: {
210
+ task: target.task,
211
+ repository,
212
+ agent,
213
+ worktreeDir: created.dir,
214
+ branchName: target.branch,
215
+ workspaceName: target.task,
216
+ state: "running",
217
+ title: target.title,
218
+ adoptedBranch: true,
219
+ ...(target.url === undefined ? {} : { url: target.url }),
220
+ },
221
+ });
194
222
  }
195
223
  catch (error) {
196
- cleanupAgentLaunch?.();
197
- await rollback({ config, entry: created, promptDir: stagedPrompt.directory });
224
+ cleanupAgentLaunchBestEffort({ cleanup: cleanupAgentLaunch, context: "open rollback" });
225
+ await rollback({
226
+ config,
227
+ entry: created,
228
+ promptDir: stagedPrompt.directory,
229
+ workspaceOpened,
230
+ });
198
231
  throw error;
199
232
  }
200
- recordRunState({
201
- config,
202
- state: {
203
- task: target.task,
204
- repository,
205
- agent,
206
- worktreeDir: created.dir,
207
- branchName: target.branch,
208
- workspaceName: target.task,
209
- state: "running",
210
- title: target.title,
211
- adoptedBranch: true,
212
- ...(target.url === undefined ? {} : { url: target.url }),
213
- },
214
- });
215
233
  log(`${okMark()} "${target.task}" opened on branch ${target.branch} (${agent})`);
216
234
  debug(` Worktree: ${launchDir}`);
217
235
  if (omitPromptArgument) {
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAsBzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAoMD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAmGf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
1
+ {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAwBzF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAoMD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgIf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -12,7 +12,9 @@ import { taskSourceWritePathsForCompletion } from "../lib/taskSourceFilesystem.j
12
12
  import { naturalIdFromCanonical, toCanonicalId } from "../lib/taskSource.js";
13
13
  import { errorMessage, log } from "../lib/util.js";
14
14
  import { failIfWorkspaceAlreadyLive } from "../lib/workspaceLiveness.js";
15
+ import { workspaces } from "../lib/workspaces.js";
15
16
  import { resolveLaunchDir, worktrees } from "../lib/worktrees.js";
17
+ import { cleanupAgentLaunchBestEffort } from "./agentLaunchCleanup.js";
16
18
  function parseArguments(argv) {
17
19
  let fresh = false;
18
20
  const positionals = [];
@@ -192,6 +194,7 @@ export async function resumeWorkspace(config, options) {
192
194
  });
193
195
  const secretsFile = stageBuildSecrets(stagedPrompt.directory);
194
196
  let cleanupAgentLaunch;
197
+ let workspaceOpened = false;
195
198
  try {
196
199
  const taskSourceWritePaths = runner === "safehouse"
197
200
  ? taskSourceWritePathsForCompletion({
@@ -235,28 +238,51 @@ export async function resumeWorkspace(config, options) {
235
238
  agent: context.agent,
236
239
  color: definition.color,
237
240
  });
241
+ workspaceOpened = true;
242
+ recordRunState({
243
+ config,
244
+ state: {
245
+ task,
246
+ repository: context.repository,
247
+ agent: context.agent,
248
+ worktreeDir: context.worktree.dir,
249
+ branchName: context.worktree.branchName,
250
+ workspaceName: task,
251
+ state: "resumed",
252
+ resumeCount: context.resumeCount + 1,
253
+ completionTaskId: context.completionTaskId,
254
+ ...(context.url === undefined ? {} : { url: context.url }),
255
+ ...(context.reason === undefined ? {} : { reason: context.reason }),
256
+ },
257
+ });
238
258
  }
239
259
  catch (error) {
240
- cleanupAgentLaunch?.();
241
- removeStagedPrompt(stagedPrompt.directory);
260
+ if (workspaceOpened) {
261
+ try {
262
+ const result = await workspaces.close(config, task);
263
+ if (result.kind === "unavailable") {
264
+ const detail = result.error === undefined
265
+ ? "workspace backend unavailable"
266
+ : errorMessage(result.error);
267
+ log(`Workspace close was not confirmed during resume rollback for ${task}: ${detail}. Close it manually in the configured workspace backend.`);
268
+ }
269
+ }
270
+ catch (closeError) {
271
+ log(`Workspace close failed during resume rollback for ${task}: ${errorMessage(closeError)}. Close it manually in the configured workspace backend.`);
272
+ }
273
+ }
274
+ cleanupAgentLaunchBestEffort({
275
+ cleanup: cleanupAgentLaunch,
276
+ context: `resume rollback for ${task}`,
277
+ });
278
+ try {
279
+ removeStagedPrompt(stagedPrompt.directory);
280
+ }
281
+ catch (cleanupError) {
282
+ log(`Staged prompt cleanup failed during resume rollback for ${task}: ${errorMessage(cleanupError)}`);
283
+ }
242
284
  throw error;
243
285
  }
244
- recordRunState({
245
- config,
246
- state: {
247
- task,
248
- repository: context.repository,
249
- agent: context.agent,
250
- worktreeDir: context.worktree.dir,
251
- branchName: context.worktree.branchName,
252
- workspaceName: task,
253
- state: "resumed",
254
- resumeCount: context.resumeCount + 1,
255
- completionTaskId: context.completionTaskId,
256
- ...(context.url === undefined ? {} : { url: context.url }),
257
- ...(context.reason === undefined ? {} : { reason: context.reason }),
258
- },
259
- });
260
286
  log(`Resumed ${task} in ${context.worktree.dir} (${context.agent})`);
261
287
  }
262
288
  export async function resumeWorkspaceCli(argv) {
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBnE,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUxF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,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,CA2Jf;AAiOD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAyDf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBnE,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAWxF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,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,CA2Jf;AAiOD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAyDf"}
@@ -13,6 +13,7 @@ import { naturalIdFromCanonical } from "../lib/taskSource.js";
13
13
  import { debug, errorMessage, log, okMark } from "../lib/util.js";
14
14
  import { workspaces } from "../lib/workspaces.js";
15
15
  import { resolveLaunchDir, WorktreeAlreadyExistsError, worktrees, } from "../lib/worktrees.js";
16
+ import { cleanupAgentLaunchBestEffort } from "./agentLaunchCleanup.js";
16
17
  function stagePrompt(input) {
17
18
  return stagePromptFromTemplate({
18
19
  config: input.config,
@@ -166,7 +167,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
166
167
  }
167
168
  }
168
169
  catch (error) {
169
- cleanupAgentLaunch?.();
170
+ cleanupAgentLaunchBestEffort({ cleanup: cleanupAgentLaunch, context: "setup rollback" });
170
171
  await rollbackWorktree({ config, entry: created, promptDir });
171
172
  recordFailedToLaunch({ config, options, paths: { worktreeDir, branchName }, error });
172
173
  throw error;
@@ -1,6 +1,22 @@
1
+ /**
2
+ * Renderers for `crew status`. Three output modes share this file.
3
+ *
4
+ * With no task it renders the inventory: it calls the collectors in
5
+ * statusCollect.ts, joins the two tiers with joinStatus, and prints text.
6
+ * With a task it renders that one task, doing its own I/O rather than using
7
+ * the collectors, because it reads the whole log where they read a tail.
8
+ * With --json it prints and persists the wire documents from statusSnapshot.ts.
9
+ * Text mode persists nothing.
10
+ *
11
+ * @see README.md#status-snapshots-for-external-monitors for the reader contract.
12
+ */
1
13
  import { type ResolvedConfig } from "../lib/config.ts";
2
14
  export interface StatusOptions {
3
15
  task?: string;
16
+ /** Emit the snapshot documents as JSON and persist them. */
17
+ json?: boolean;
18
+ /** Collect the local tier only. Guarantees this run makes no network call. */
19
+ localOnly?: boolean;
4
20
  }
5
21
  export declare function status(config: ResolvedConfig, options?: StatusOptions): Promise<void>;
6
22
  export declare function statusCli(argv: string[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAosBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA0CnE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA0hBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7D"}