@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
@@ -3,11 +3,14 @@
3
3
  // `crew doctor --ticket <TICKET>` — single per-ticket diagnostic that covers
4
4
  // both the pre-dispatch question ("will this dispatch on the next tick?") and
5
5
  // the post-dispatch question ("what's already happened and what's left to
6
- // do?"). Verdict precedence runs strictly from post-dispatch states down:
6
+ // do?"). Verdict precedence starts with PR outcomes, then handles run-state
7
+ // exceptions before ordinary local recovery:
7
8
  //
8
- // pr-open > pr-merged > in-flight > recoverable
9
- // > unresolvable > ineligible > would-dispatch
10
- // > lost
9
+ // pr-open > pr-merged
10
+ // > failed-launch
11
+ // > interrupted (unless concrete recoverable git work exists)
12
+ // > in-flight > recoverable
13
+ // > unresolvable > ineligible > would-dispatch > lost
11
14
  //
12
15
  // When a post-dispatch verdict fires, the Resolution and Eligibility sections
13
16
  // are skipped — they describe pre-dispatch state that no longer matters.
@@ -15,8 +18,9 @@ import { existsSync } from "node:fs";
15
18
  import { join } from "node:path";
16
19
  import { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, resolveModelFor, resolveRepositoryFor, } from "../lib/boardSource.js";
17
20
  import { runCommandAsync } from "../lib/commandRunner.js";
18
- import { AGENT_ANY_MODEL, loadConfig } from "../lib/config.js";
21
+ import { AGENT_ANY_MODEL, findProjectBySlugId, loadConfig, } from "../lib/config.js";
19
22
  import { which } from "../lib/host.js";
23
+ import { readRunState } from "../lib/runState.js";
20
24
  import { getUsageByModel } from "../lib/usage.js";
21
25
  import { getLinearClient, lazyLinearClient, writeOutput } from "../lib/util.js";
22
26
  import { workspaces } from "../lib/workspaces.js";
@@ -91,9 +95,11 @@ function verdictRecoverable(input) {
91
95
  * "ticket has moved past dispatch" cases. Returns `undefined` otherwise,
92
96
  * signalling that the caller should fall through to the pre-dispatch path.
93
97
  *
94
- * Precedence: PR > in-flight > recoverable. Inside `recoverable`, dirty
95
- * worktree beats clean-with-un-pushed-local beats remote-only beats stranded
96
- * local.
98
+ * Precedence: PR verdicts always win. Failed launches report before ordinary
99
+ * local recovery. Interrupted runs report concrete recoverable git work first
100
+ * when it exists, then fall back to `interrupted`. Ordinary post-dispatch cases
101
+ * report in-flight before recoverable. Inside `recoverable`, dirty worktree
102
+ * beats clean-with-un-pushed-local beats remote-only beats stranded local.
97
103
  */
98
104
  export function decidePostDispatchVerdict(input) {
99
105
  if (input.pullRequest.kind === "open") {
@@ -102,13 +108,34 @@ export function decidePostDispatchVerdict(input) {
102
108
  if (input.pullRequest.kind === "merged") {
103
109
  return { kind: "pr-merged", number: input.pullRequest.number, url: input.pullRequest.url };
104
110
  }
105
- return verdictInFlight(input) ?? verdictRecoverable(input);
111
+ const recoverable = verdictRecoverable(input);
112
+ if (input.runState?.state === "interrupted") {
113
+ if (recoverable !== undefined) {
114
+ return recoverable;
115
+ }
116
+ const detail = input.runState.reason ?? input.runState.detail ?? "workspace stopped";
117
+ return {
118
+ kind: "interrupted",
119
+ reason: detail,
120
+ nextStep: `run \`crew resume ${input.runState.ticket}\` or inspect ${input.runState.worktreeDir}`,
121
+ };
122
+ }
123
+ if (input.runState?.state === "failed-to-launch") {
124
+ const detail = input.runState.detail ?? "workspace launch failed";
125
+ return {
126
+ kind: "failed-launch",
127
+ reason: detail,
128
+ nextStep: `fix the launch failure, then run \`crew resume ${input.runState.ticket}\` or \`crew cleanup ${input.runState.ticket}\``,
129
+ };
130
+ }
131
+ return verdictInFlight(input) ?? recoverable;
106
132
  }
107
133
  function emptySkipReasons() {
108
134
  return {
109
135
  resolution: "",
110
136
  eligibility: "",
111
137
  worktree: "",
138
+ runState: "",
112
139
  workspace: "",
113
140
  localBranch: "",
114
141
  remoteBranch: "",
@@ -243,6 +270,8 @@ async function runEligibilityChecks(arguments_) {
243
270
  assignee: "",
244
271
  updatedAt: "",
245
272
  teamId: raw.teamId,
273
+ /* v8 ignore next @preserve -- probeLinear gates off-config projects upstream so raw.projectSlugId is always defined here */
274
+ projectSlugId: raw.projectSlugId ?? "",
246
275
  repository: resolvedRepository,
247
276
  model: resolvedModel,
248
277
  blockers: [...blockers],
@@ -345,8 +374,31 @@ async function probeLinear(deps, upperTicket) {
345
374
  }
346
375
  try {
347
376
  const raw = await deps.fetchRawIssue({ ticket: upperTicket });
348
- const isTerminal = deps.config.linear.statuses.terminal.includes(raw.stateName);
349
- const todoState = deps.config.linear.statuses.todo;
377
+ const project = raw.projectSlugId === undefined
378
+ ? undefined
379
+ : findProjectBySlugId(deps.config, raw.projectSlugId);
380
+ if (project === undefined) {
381
+ const configured = deps.config.linear.projects.map((entry) => entry.slugId).join(", ");
382
+ const detail = raw.projectSlugId === undefined
383
+ ? `ticket has no associated Linear project; configure linear.projects (${configured})`
384
+ : `project slugId "${raw.projectSlugId}" is not in linear.projects (configured: ${configured})`;
385
+ return {
386
+ linearStatus: { kind: "unresolvable", reason: detail },
387
+ resolution: [
388
+ { name: "Ticket exists in Linear", status: "ok", detail: `"${raw.title}"` },
389
+ {
390
+ name: "Project is configured",
391
+ status: "fail",
392
+ detail,
393
+ failureSummary: detail,
394
+ },
395
+ ],
396
+ title: raw.title,
397
+ raw,
398
+ };
399
+ }
400
+ const isTerminal = project.statuses.terminal.includes(raw.stateName);
401
+ const todoState = project.statuses.todo;
350
402
  const resolution = [
351
403
  { name: "Ticket exists in Linear", status: "ok", detail: `"${raw.title}"` },
352
404
  ];
@@ -376,6 +428,29 @@ async function probeLinear(deps, upperTicket) {
376
428
  };
377
429
  }
378
430
  }
431
+ function probeRunStateSection(deps, ticket) {
432
+ const state = deps.readRunState(ticket);
433
+ if (state === undefined) {
434
+ return {
435
+ checks: [{ name: "Local run state", status: "skipped", detail: "none found" }],
436
+ state: undefined,
437
+ };
438
+ }
439
+ const checks = [
440
+ { name: "Local run state", status: "ok", detail: state.state },
441
+ { name: "Recorded model", status: "ok", detail: state.model },
442
+ { name: "Recorded worktree", status: "ok", detail: state.worktreeDir },
443
+ { name: "Recorded branch", status: "ok", detail: state.branchName },
444
+ { name: "Resume count", status: "ok", detail: String(state.resumeCount) },
445
+ ];
446
+ if (state.reason !== undefined) {
447
+ checks.push({ name: "Last reason", status: "ok", detail: state.reason });
448
+ }
449
+ if (state.detail !== undefined) {
450
+ checks.push({ name: "Last detail", status: "ok", detail: state.detail });
451
+ }
452
+ return { checks, state };
453
+ }
379
454
  async function probeWorktreeSection(deps, ticket) {
380
455
  const entry = deps.findWorktree(ticket);
381
456
  if (entry === undefined) {
@@ -615,6 +690,7 @@ export async function ticketDoctor(dependencies) {
615
690
  const skipReasons = emptySkipReasons();
616
691
  const linearResult = await probeLinear(dependencies, upperTicket);
617
692
  const resolution = [...linearResult.resolution];
693
+ const runStateResult = probeRunStateSection(dependencies, lowerTicket);
618
694
  const worktreeResult = await probeWorktreeSection(dependencies, lowerTicket);
619
695
  const workspaceResult = await probeWorkspaceSection(dependencies, lowerTicket);
620
696
  const localResult = await probeLocalBranchSection(dependencies, worktreeResult.entry);
@@ -638,6 +714,7 @@ export async function ticketDoctor(dependencies) {
638
714
  branch: worktreeResult.entry?.branchName ?? lowerTicket,
639
715
  worktreeDir: worktreeResult.entry?.dir,
640
716
  workspaceName: workspaceResult.workspaceName,
717
+ runState: runStateResult.state,
641
718
  });
642
719
  if (postVerdict !== undefined) {
643
720
  skipReasons.resolution = "post-dispatch — pre-dispatch checks are irrelevant";
@@ -647,6 +724,7 @@ export async function ticketDoctor(dependencies) {
647
724
  title: linearResult.title,
648
725
  resolution,
649
726
  eligibility: [],
727
+ runStateResult,
650
728
  worktreeResult,
651
729
  workspaceResult,
652
730
  localResult,
@@ -667,6 +745,7 @@ export async function ticketDoctor(dependencies) {
667
745
  title: linearResult.title,
668
746
  resolution,
669
747
  eligibility: [],
748
+ runStateResult,
670
749
  worktreeResult,
671
750
  workspaceResult,
672
751
  localResult,
@@ -687,6 +766,7 @@ export async function ticketDoctor(dependencies) {
687
766
  title: linearResult.title,
688
767
  resolution,
689
768
  eligibility: [],
769
+ runStateResult,
690
770
  worktreeResult,
691
771
  workspaceResult,
692
772
  localResult,
@@ -725,6 +805,7 @@ export async function ticketDoctor(dependencies) {
725
805
  title: linearResult.title,
726
806
  resolution,
727
807
  eligibility: preDispatch.eligibility,
808
+ runStateResult,
728
809
  worktreeResult,
729
810
  workspaceResult,
730
811
  localResult,
@@ -740,6 +821,7 @@ function buildResult(input) {
740
821
  ...(input.title === undefined ? {} : { title: input.title }),
741
822
  resolution: input.resolution,
742
823
  eligibility: input.eligibility,
824
+ runState: input.runStateResult.checks,
743
825
  worktree: input.worktreeResult.checks,
744
826
  workspace: input.workspaceResult.checks,
745
827
  localBranch: input.localResult.checks,
@@ -778,6 +860,12 @@ function formatVerdict(verdict) {
778
860
  case "pr-merged": {
779
861
  return `→ pr-merged: ${verdict.url} (#${verdict.number})`;
780
862
  }
863
+ case "interrupted": {
864
+ return `→ interrupted: ${verdict.reason}; ${verdict.nextStep}`;
865
+ }
866
+ case "failed-launch": {
867
+ return `→ failed-launch: ${verdict.reason}; ${verdict.nextStep}`;
868
+ }
781
869
  case "in-flight": {
782
870
  return `→ in-flight: ${verdict.reason}`;
783
871
  }
@@ -818,6 +906,11 @@ export function renderTicketDoctorResult(result) {
818
906
  ? {}
819
907
  : { skipReason: result.skipReasons.eligibility }),
820
908
  },
909
+ {
910
+ name: "Run state",
911
+ checks: result.runState,
912
+ ...(result.skipReasons.runState === "" ? {} : { skipReason: result.skipReasons.runState }),
913
+ },
821
914
  {
822
915
  name: "Worktree",
823
916
  checks: result.worktree,
@@ -879,6 +972,7 @@ export async function runTicketDoctor(parsed) {
879
972
  probeLocalBranch: probeLocalBranchImpl,
880
973
  probeRemoteBranch: probeRemoteBranchImpl,
881
974
  probePullRequest: probePullRequestImpl,
975
+ readRunState: (ticket) => readRunState(config, ticket),
882
976
  doFetch: parsed.doFetch,
883
977
  });
884
978
  for (const line of renderTicketDoctorResult(result)) {
package/dist/index.d.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  export { run } from "./cli.ts";
2
2
  export { cleanupWorkspace, type CleanupWorkspaceOptions } from "./commands/cleanupWorkspace.ts";
3
3
  export { doctor } from "./commands/doctor.ts";
4
+ export { interruptWorkspace, type InterruptWorkspaceOptions, } from "./commands/interruptWorkspace.ts";
4
5
  export { orchestrate, type OrchestratorOptions } from "./commands/orchestrator.ts";
6
+ export { resumeWorkspace, type ResumeWorkspaceOptions } from "./commands/resumeWorkspace.ts";
5
7
  export { setupWorkspace, type SetupWorkspaceOptions } from "./commands/setupWorkspace.ts";
6
- export type { Config, ModelDefinition, ResolvedConfig } from "./lib/config.ts";
7
- export { loadConfig } from "./lib/config.ts";
8
- export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
8
+ export type { Config, ModelDefinition, ProjectConfig, ResolvedConfig, ResolvedProjectConfig, } from "./lib/config.ts";
9
+ export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/config.ts";
10
+ export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
11
+ export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
9
12
  export { getUsageByModel, type UsageByModel } from "./lib/usage.ts";
10
13
  export type { TicketCheck } from "./commands/ticketCheck.ts";
11
14
  export { ticketDoctor, type TicketDoctorDependencies, type TicketDoctorResult, type TicketDoctorVerdict, } from "./commands/ticketDoctor.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,MAAM,EACN,eAAe,EACf,aAAa,EACb,cAAc,EACd,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  export { run } from "./cli.js";
2
2
  export { cleanupWorkspace } from "./commands/cleanupWorkspace.js";
3
3
  export { doctor } from "./commands/doctor.js";
4
+ export { interruptWorkspace, } from "./commands/interruptWorkspace.js";
4
5
  export { orchestrate } from "./commands/orchestrator.js";
6
+ export { resumeWorkspace } from "./commands/resumeWorkspace.js";
5
7
  export { setupWorkspace } from "./commands/setupWorkspace.js";
6
- export { loadConfig } from "./lib/config.js";
7
- export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, } from "./lib/boardSource.js";
8
+ export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/config.js";
9
+ export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
10
+ export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, } from "./lib/boardSource.js";
8
11
  export { getUsageByModel } from "./lib/usage.js";
9
12
  export { ticketDoctor, } from "./commands/ticketDoctor.js";
@@ -0,0 +1,29 @@
1
+ import type { LocalRunner, ModelDefinition, ResolvedConfig } from "./config.ts";
2
+ interface PreparedAgentLaunch {
3
+ runner: LocalRunner;
4
+ sandboxName: string | undefined;
5
+ }
6
+ export declare function prepareAgentLaunch(input: {
7
+ config: ResolvedConfig;
8
+ model: string;
9
+ definition: ModelDefinition;
10
+ purpose: "runs" | "resumes";
11
+ signal?: AbortSignal;
12
+ }): Promise<PreparedAgentLaunch>;
13
+ export declare function ensureAgentSandbox(input: {
14
+ config: ResolvedConfig;
15
+ definition: ModelDefinition;
16
+ sandboxName: string | undefined;
17
+ signal?: AbortSignal;
18
+ }): Promise<void>;
19
+ export declare function openAgentWorkspace(input: {
20
+ config: ResolvedConfig;
21
+ name: string;
22
+ cwd: string;
23
+ command: string;
24
+ model: string;
25
+ color: string;
26
+ signal?: AbortSignal;
27
+ }): Promise<void>;
28
+ export {};
29
+ //# sourceMappingURL=agentLaunch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOhF,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;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,CA6B/B;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhB;AAED,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"}
@@ -0,0 +1,53 @@
1
+ import { resolve } from "node:path";
2
+ import { ensureClearance } from "@clipboard-health/clearance";
3
+ import { ensureSandbox, sandboxNameFor } from "./dockerSandbox.js";
4
+ import { detectHostCapabilities } from "./host.js";
5
+ import { assertLocalRunnerRequirements, resolveLocalRunner } from "./localRunner.js";
6
+ import { log, sleep } from "./util.js";
7
+ import { workspaces } from "./workspaces.js";
8
+ export async function prepareAgentLaunch(input) {
9
+ const host = await detectHostCapabilities(input.signal);
10
+ const runner = resolveLocalRunner(input.config.local.runner, host);
11
+ assertLocalRunnerRequirements(host, runner);
12
+ if (runner === "safehouse") {
13
+ await ensureClearance({
14
+ logger: log,
15
+ ...(input.signal === undefined
16
+ ? {}
17
+ : {
18
+ sleep: async (ms) => {
19
+ await sleep(ms, input.signal);
20
+ input.signal?.throwIfAborted();
21
+ },
22
+ }),
23
+ });
24
+ input.signal?.throwIfAborted();
25
+ }
26
+ if (runner === "sdx" && input.definition.sandbox === undefined) {
27
+ throw new Error(`Local groundcrew ${input.purpose} with the sdx runner require a sandbox config on model '${input.model}'.`);
28
+ }
29
+ const sandboxName = runner === "sdx" && input.definition.sandbox !== undefined
30
+ ? sandboxNameFor({ agent: input.definition.sandbox.agent })
31
+ : undefined;
32
+ return { runner, sandboxName };
33
+ }
34
+ export async function ensureAgentSandbox(input) {
35
+ if (input.sandboxName !== undefined && input.definition.sandbox !== undefined) {
36
+ await ensureSandbox({
37
+ sandboxName: input.sandboxName,
38
+ sandbox: input.definition.sandbox,
39
+ mountPath: resolve(input.config.workspace.projectDir),
40
+ }, input.signal);
41
+ }
42
+ }
43
+ export async function openAgentWorkspace(input) {
44
+ const spec = {
45
+ name: input.name,
46
+ cwd: input.cwd,
47
+ command: input.command,
48
+ status: { text: input.model, color: input.color, icon: "sparkle" },
49
+ };
50
+ await (input.signal === undefined
51
+ ? workspaces.open(input.config, spec)
52
+ : workspaces.open(input.config, spec, input.signal));
53
+ }
@@ -4,11 +4,19 @@
4
4
  * typed `BoardState` instead of raw nodes.
5
5
  */
6
6
  import type { LinearClient } from "@linear/sdk";
7
- import { type ResolvedConfig } from "./config.ts";
7
+ import { type ResolvedConfig, type ResolvedProjectConfig } from "./config.ts";
8
8
  export interface Blocker {
9
9
  id: string;
10
10
  title: string;
11
11
  status: string | undefined;
12
+ /**
13
+ * SlugId of the project the blocker lives in. `undefined` when Linear
14
+ * returned no project for the blocker (rare — issues can technically
15
+ * exist without a project). Drives `isTerminalStatusForBlocker`'s
16
+ * pick between the blocker's own project terminals and the global
17
+ * union fallback.
18
+ */
19
+ projectSlugId: string | undefined;
12
20
  }
13
21
  export interface Issue {
14
22
  id: string;
@@ -23,6 +31,8 @@ export interface Issue {
23
31
  /** `undefined` when the ticket has no `agent-*` label — i.e. not groundcrew's concern. */
24
32
  model: string | undefined;
25
33
  teamId: string;
34
+ /** SlugId of the Linear project the issue belongs to — always one of `linear.projects[*].slugId`. */
35
+ projectSlugId: string;
26
36
  blockers: Blocker[];
27
37
  hasMoreBlockers: boolean;
28
38
  }
@@ -46,10 +56,22 @@ export declare class RepositoryResolutionError extends Error {
46
56
  repositories: readonly string[];
47
57
  });
48
58
  }
59
+ export declare class UnknownProjectError extends Error {
60
+ readonly ticket: string;
61
+ readonly projectSlugId: string | undefined;
62
+ readonly configuredSlugIds: readonly string[];
63
+ constructor(arguments_: {
64
+ ticket: string;
65
+ projectSlugId: string | undefined;
66
+ configuredSlugIds: readonly string[];
67
+ });
68
+ }
49
69
  export interface BoardSource {
50
70
  /**
51
- * Look up the configured project and fail loudly if it isn't there. Run
52
- * once at startup so a misconfigured slug surfaces before the first tick.
71
+ * Look up the configured projects and warn loudly on any that aren't
72
+ * there. Throws only when zero projects resolve, so a typo in one of
73
+ * several entries doesn't abort the watch loop. Run once at startup
74
+ * so misconfigurations surface before the first tick.
53
75
  */
54
76
  verify(): Promise<void>;
55
77
  /** Fetch the current board snapshot. Paginates internally. */
@@ -60,7 +82,16 @@ interface BoardSourceDeps {
60
82
  client: LinearClient;
61
83
  }
62
84
  export declare function createBoardSource(deps: BoardSourceDeps): BoardSource;
63
- export declare function isTerminalStatus(status: string, config: ResolvedConfig): boolean;
85
+ export declare function projectFor(issue: Issue, config: ResolvedConfig): ResolvedProjectConfig;
86
+ export declare function isTerminalStatusForIssue(issue: Issue, config: ResolvedConfig): boolean;
87
+ /**
88
+ * Terminal check for a blocker. When the blocker lives in a configured
89
+ * project, we use that project's terminal list directly. Otherwise we
90
+ * fall back to the union of terminals across all configured projects —
91
+ * matches today's single-project "is this name in our terminal list?"
92
+ * behavior so off-config blockers don't regress.
93
+ */
94
+ export declare function isTerminalStatusForBlocker(blocker: Blocker, config: ResolvedConfig): boolean;
64
95
  interface ResolvedIssue {
65
96
  uuid: string;
66
97
  title: string;
@@ -68,12 +99,14 @@ interface ResolvedIssue {
68
99
  repository: string;
69
100
  model: string;
70
101
  teamId: string;
102
+ projectSlugId: string;
71
103
  }
72
104
  export interface RawLinearIssue {
73
105
  uuid: string;
74
106
  title: string;
75
107
  description: string;
76
108
  teamId: string;
109
+ projectSlugId: string | undefined;
77
110
  labels: {
78
111
  name: string;
79
112
  }[];
@@ -127,7 +160,10 @@ export declare function resolveModelFor(arguments_: {
127
160
  /**
128
161
  * `agent-any` collapses to `models.default` here — manual setup doesn't run
129
162
  * the usage-gated `any` resolver, so the caller gets a concrete model name
130
- * instead of a sentinel that downstream code can't interpret.
163
+ * instead of a sentinel that downstream code can't interpret. Throws
164
+ * `UnknownProjectError` when the ticket lives in a Linear project that
165
+ * isn't listed in `linear.projects`, so callers can surface the misconfiguration
166
+ * instead of silently using the wrong status names.
131
167
  */
132
168
  export declare function fetchResolvedIssue(arguments_: {
133
169
  client: LinearClient;
@@ -1 +1 @@
1
- {"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"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"}