@clipboard-health/groundcrew 3.1.2 → 3.1.3

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 (39) hide show
  1. package/README.md +31 -2
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -0
  4. package/dist/commands/cleaner.d.ts.map +1 -1
  5. package/dist/commands/cleaner.js +2 -0
  6. package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
  7. package/dist/commands/cleanupWorkspace.js +2 -0
  8. package/dist/commands/interruptWorkspace.d.ts +8 -0
  9. package/dist/commands/interruptWorkspace.d.ts.map +1 -0
  10. package/dist/commands/interruptWorkspace.js +108 -0
  11. package/dist/commands/resumeWorkspace.d.ts +7 -0
  12. package/dist/commands/resumeWorkspace.d.ts.map +1 -0
  13. package/dist/commands/resumeWorkspace.js +163 -0
  14. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  15. package/dist/commands/setupWorkspace.js +77 -79
  16. package/dist/commands/ticketDoctor.d.ts +18 -3
  17. package/dist/commands/ticketDoctor.d.ts.map +1 -1
  18. package/dist/commands/ticketDoctor.js +77 -8
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/lib/agentLaunch.d.ts +29 -0
  23. package/dist/lib/agentLaunch.d.ts.map +1 -0
  24. package/dist/lib/agentLaunch.js +53 -0
  25. package/dist/lib/runState.d.ts +46 -0
  26. package/dist/lib/runState.d.ts.map +1 -0
  27. package/dist/lib/runState.js +137 -0
  28. package/dist/lib/runStateCleanup.d.ts +4 -0
  29. package/dist/lib/runStateCleanup.d.ts.map +1 -0
  30. package/dist/lib/runStateCleanup.js +12 -0
  31. package/dist/lib/stagedLaunch.d.ts +32 -0
  32. package/dist/lib/stagedLaunch.d.ts.map +1 -0
  33. package/dist/lib/stagedLaunch.js +58 -0
  34. package/dist/lib/workspaces.d.ts +19 -1
  35. package/dist/lib/workspaces.d.ts.map +1 -1
  36. package/dist/lib/workspaces.js +29 -9
  37. package/dist/lib/worktrees.d.ts.map +1 -1
  38. package/dist/lib/worktrees.js +12 -4
  39. package/package.json +1 -1
@@ -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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"}
@@ -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.
@@ -17,6 +20,7 @@ import { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue,
17
20
  import { runCommandAsync } from "../lib/commandRunner.js";
18
21
  import { AGENT_ANY_MODEL, 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: "",
@@ -376,6 +403,29 @@ async function probeLinear(deps, upperTicket) {
376
403
  };
377
404
  }
378
405
  }
406
+ function probeRunStateSection(deps, ticket) {
407
+ const state = deps.readRunState(ticket);
408
+ if (state === undefined) {
409
+ return {
410
+ checks: [{ name: "Local run state", status: "skipped", detail: "none found" }],
411
+ state: undefined,
412
+ };
413
+ }
414
+ const checks = [
415
+ { name: "Local run state", status: "ok", detail: state.state },
416
+ { name: "Recorded model", status: "ok", detail: state.model },
417
+ { name: "Recorded worktree", status: "ok", detail: state.worktreeDir },
418
+ { name: "Recorded branch", status: "ok", detail: state.branchName },
419
+ { name: "Resume count", status: "ok", detail: String(state.resumeCount) },
420
+ ];
421
+ if (state.reason !== undefined) {
422
+ checks.push({ name: "Last reason", status: "ok", detail: state.reason });
423
+ }
424
+ if (state.detail !== undefined) {
425
+ checks.push({ name: "Last detail", status: "ok", detail: state.detail });
426
+ }
427
+ return { checks, state };
428
+ }
379
429
  async function probeWorktreeSection(deps, ticket) {
380
430
  const entry = deps.findWorktree(ticket);
381
431
  if (entry === undefined) {
@@ -615,6 +665,7 @@ export async function ticketDoctor(dependencies) {
615
665
  const skipReasons = emptySkipReasons();
616
666
  const linearResult = await probeLinear(dependencies, upperTicket);
617
667
  const resolution = [...linearResult.resolution];
668
+ const runStateResult = probeRunStateSection(dependencies, lowerTicket);
618
669
  const worktreeResult = await probeWorktreeSection(dependencies, lowerTicket);
619
670
  const workspaceResult = await probeWorkspaceSection(dependencies, lowerTicket);
620
671
  const localResult = await probeLocalBranchSection(dependencies, worktreeResult.entry);
@@ -638,6 +689,7 @@ export async function ticketDoctor(dependencies) {
638
689
  branch: worktreeResult.entry?.branchName ?? lowerTicket,
639
690
  worktreeDir: worktreeResult.entry?.dir,
640
691
  workspaceName: workspaceResult.workspaceName,
692
+ runState: runStateResult.state,
641
693
  });
642
694
  if (postVerdict !== undefined) {
643
695
  skipReasons.resolution = "post-dispatch — pre-dispatch checks are irrelevant";
@@ -647,6 +699,7 @@ export async function ticketDoctor(dependencies) {
647
699
  title: linearResult.title,
648
700
  resolution,
649
701
  eligibility: [],
702
+ runStateResult,
650
703
  worktreeResult,
651
704
  workspaceResult,
652
705
  localResult,
@@ -667,6 +720,7 @@ export async function ticketDoctor(dependencies) {
667
720
  title: linearResult.title,
668
721
  resolution,
669
722
  eligibility: [],
723
+ runStateResult,
670
724
  worktreeResult,
671
725
  workspaceResult,
672
726
  localResult,
@@ -687,6 +741,7 @@ export async function ticketDoctor(dependencies) {
687
741
  title: linearResult.title,
688
742
  resolution,
689
743
  eligibility: [],
744
+ runStateResult,
690
745
  worktreeResult,
691
746
  workspaceResult,
692
747
  localResult,
@@ -725,6 +780,7 @@ export async function ticketDoctor(dependencies) {
725
780
  title: linearResult.title,
726
781
  resolution,
727
782
  eligibility: preDispatch.eligibility,
783
+ runStateResult,
728
784
  worktreeResult,
729
785
  workspaceResult,
730
786
  localResult,
@@ -740,6 +796,7 @@ function buildResult(input) {
740
796
  ...(input.title === undefined ? {} : { title: input.title }),
741
797
  resolution: input.resolution,
742
798
  eligibility: input.eligibility,
799
+ runState: input.runStateResult.checks,
743
800
  worktree: input.worktreeResult.checks,
744
801
  workspace: input.workspaceResult.checks,
745
802
  localBranch: input.localResult.checks,
@@ -778,6 +835,12 @@ function formatVerdict(verdict) {
778
835
  case "pr-merged": {
779
836
  return `→ pr-merged: ${verdict.url} (#${verdict.number})`;
780
837
  }
838
+ case "interrupted": {
839
+ return `→ interrupted: ${verdict.reason}; ${verdict.nextStep}`;
840
+ }
841
+ case "failed-launch": {
842
+ return `→ failed-launch: ${verdict.reason}; ${verdict.nextStep}`;
843
+ }
781
844
  case "in-flight": {
782
845
  return `→ in-flight: ${verdict.reason}`;
783
846
  }
@@ -818,6 +881,11 @@ export function renderTicketDoctorResult(result) {
818
881
  ? {}
819
882
  : { skipReason: result.skipReasons.eligibility }),
820
883
  },
884
+ {
885
+ name: "Run state",
886
+ checks: result.runState,
887
+ ...(result.skipReasons.runState === "" ? {} : { skipReason: result.skipReasons.runState }),
888
+ },
821
889
  {
822
890
  name: "Worktree",
823
891
  checks: result.worktree,
@@ -879,6 +947,7 @@ export async function runTicketDoctor(parsed) {
879
947
  probeLocalBranch: probeLocalBranchImpl,
880
948
  probeRemoteBranch: probeRemoteBranchImpl,
881
949
  probePullRequest: probePullRequestImpl,
950
+ readRunState: (ticket) => readRunState(config, ticket),
882
951
  doFetch: parsed.doFetch,
883
952
  });
884
953
  for (const line of renderTicketDoctorResult(result)) {
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
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
8
  export type { Config, ModelDefinition, ResolvedConfig } from "./lib/config.ts";
7
9
  export { loadConfig } from "./lib/config.ts";
10
+ export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
8
11
  export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, 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";
@@ -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,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,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,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"}
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
8
  export { loadConfig } from "./lib/config.js";
9
+ export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
7
10
  export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, } 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
+ }
@@ -0,0 +1,46 @@
1
+ import type { ResolvedConfig } from "./config.ts";
2
+ export type RunLifecycleState = "running" | "interrupted" | "resumed" | "failed-to-launch";
3
+ export interface RunState {
4
+ ticket: string;
5
+ repository: string;
6
+ model: string;
7
+ worktreeDir: string;
8
+ branchName: string;
9
+ workspaceName: string;
10
+ state: RunLifecycleState;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ resumeCount: number;
14
+ reason?: string;
15
+ detail?: string;
16
+ }
17
+ export interface RunStateDraft {
18
+ ticket: string;
19
+ repository: string;
20
+ model: string;
21
+ worktreeDir: string;
22
+ branchName: string;
23
+ workspaceName: string;
24
+ state: RunLifecycleState;
25
+ reason?: string;
26
+ detail?: string;
27
+ resumeCount?: number;
28
+ }
29
+ export interface RecordRunStateInput {
30
+ config: ResolvedConfig;
31
+ state: RunStateDraft;
32
+ }
33
+ export interface UpdateRunStateInput {
34
+ config: ResolvedConfig;
35
+ ticket: string;
36
+ patch: Partial<Omit<RunState, "createdAt" | "ticket">> & {
37
+ state: RunLifecycleState;
38
+ };
39
+ }
40
+ export declare function runStateDirectory(config: Pick<ResolvedConfig, "logging">): string;
41
+ export declare function runStatePath(config: Pick<ResolvedConfig, "logging">, ticket: string): string;
42
+ export declare function readRunState(config: ResolvedConfig, ticket: string): RunState | undefined;
43
+ export declare function recordRunState(input: RecordRunStateInput): RunState;
44
+ export declare function updateRunState(input: UpdateRunStateInput): RunState | undefined;
45
+ export declare function removeRunState(config: ResolvedConfig, ticket: string): void;
46
+ //# sourceMappingURL=runState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC,GAAG;QACvD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5F;AA+ED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYzF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CAmBnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE3E"}
@@ -0,0 +1,137 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ const TICKET_RE = /^[a-z][\da-z]*-\d+$/;
4
+ const RUN_STATE_DIRECTORY_NAME = "runs";
5
+ function ticketKey(ticket) {
6
+ const normalized = ticket.toLowerCase();
7
+ if (!TICKET_RE.test(normalized)) {
8
+ throw new Error(`Invalid ticket "${ticket}": must be a plain ticket id`);
9
+ }
10
+ return normalized;
11
+ }
12
+ export function runStateDirectory(config) {
13
+ return resolve(dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
14
+ }
15
+ export function runStatePath(config, ticket) {
16
+ return resolve(runStateDirectory(config), `${ticketKey(ticket)}.json`);
17
+ }
18
+ function nowIso() {
19
+ return new Date().toISOString();
20
+ }
21
+ function isPlainObject(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function stringField(value, key) {
25
+ const field = value[key];
26
+ return typeof field === "string" && field.length > 0 ? field : undefined;
27
+ }
28
+ function isRunLifecycleState(value) {
29
+ return (value === "running" ||
30
+ value === "interrupted" ||
31
+ value === "resumed" ||
32
+ value === "failed-to-launch");
33
+ }
34
+ function parseRunState(value) {
35
+ if (!isPlainObject(value)) {
36
+ return undefined;
37
+ }
38
+ const ticket = stringField(value, "ticket");
39
+ const repository = stringField(value, "repository");
40
+ const model = stringField(value, "model");
41
+ const worktreeDir = stringField(value, "worktreeDir");
42
+ const branchName = stringField(value, "branchName");
43
+ const workspaceName = stringField(value, "workspaceName");
44
+ const { state, resumeCount } = value;
45
+ const createdAt = stringField(value, "createdAt");
46
+ const updatedAt = stringField(value, "updatedAt");
47
+ const reason = stringField(value, "reason");
48
+ const detail = stringField(value, "detail");
49
+ if (ticket === undefined ||
50
+ repository === undefined ||
51
+ model === undefined ||
52
+ worktreeDir === undefined ||
53
+ branchName === undefined ||
54
+ workspaceName === undefined ||
55
+ !isRunLifecycleState(state) ||
56
+ createdAt === undefined ||
57
+ updatedAt === undefined ||
58
+ typeof resumeCount !== "number" ||
59
+ !Number.isInteger(resumeCount) ||
60
+ resumeCount < 0) {
61
+ return undefined;
62
+ }
63
+ return {
64
+ ticket,
65
+ repository,
66
+ model,
67
+ worktreeDir,
68
+ branchName,
69
+ workspaceName,
70
+ state,
71
+ createdAt,
72
+ updatedAt,
73
+ resumeCount,
74
+ ...(reason === undefined ? {} : { reason }),
75
+ ...(detail === undefined ? {} : { detail }),
76
+ };
77
+ }
78
+ function writeState(config, state) {
79
+ const path = runStatePath(config, state.ticket);
80
+ mkdirSync(dirname(path), { recursive: true });
81
+ const tmpPath = `${path}.${process.pid}.tmp`;
82
+ writeFileSync(tmpPath, `${JSON.stringify(state, undefined, 2)}\n`, { mode: 0o600 });
83
+ renameSync(tmpPath, path);
84
+ }
85
+ export function readRunState(config, ticket) {
86
+ let raw;
87
+ try {
88
+ raw = readFileSync(runStatePath(config, ticket), "utf8");
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ try {
94
+ return parseRunState(JSON.parse(raw));
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ }
100
+ export function recordRunState(input) {
101
+ const existing = readRunState(input.config, input.state.ticket);
102
+ const timestamp = nowIso();
103
+ const state = {
104
+ ticket: ticketKey(input.state.ticket),
105
+ repository: input.state.repository,
106
+ model: input.state.model,
107
+ worktreeDir: input.state.worktreeDir,
108
+ branchName: input.state.branchName,
109
+ workspaceName: input.state.workspaceName,
110
+ state: input.state.state,
111
+ createdAt: existing?.createdAt ?? timestamp,
112
+ updatedAt: timestamp,
113
+ resumeCount: input.state.resumeCount ?? existing?.resumeCount ?? 0,
114
+ ...(input.state.reason === undefined ? {} : { reason: input.state.reason }),
115
+ ...(input.state.detail === undefined ? {} : { detail: input.state.detail }),
116
+ };
117
+ writeState(input.config, state);
118
+ return state;
119
+ }
120
+ export function updateRunState(input) {
121
+ const existing = readRunState(input.config, input.ticket);
122
+ if (existing === undefined) {
123
+ return undefined;
124
+ }
125
+ const state = {
126
+ ...existing,
127
+ ...input.patch,
128
+ ticket: existing.ticket,
129
+ createdAt: existing.createdAt,
130
+ updatedAt: nowIso(),
131
+ };
132
+ writeState(input.config, state);
133
+ return state;
134
+ }
135
+ export function removeRunState(config, ticket) {
136
+ rmSync(runStatePath(config, ticket), { force: true });
137
+ }
@@ -0,0 +1,4 @@
1
+ import type { ResolvedConfig } from "./config.ts";
2
+ import type { WorktreeEntry } from "./worktrees.ts";
3
+ export declare function recordCleanedUpRuns(config: ResolvedConfig, entries: readonly WorktreeEntry[]): void;
4
+ //# sourceMappingURL=runStateCleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runStateCleanup.d.ts","sourceRoot":"","sources":["../../src/lib/runStateCleanup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,GAChC,IAAI,CAQN"}
@@ -0,0 +1,12 @@
1
+ import { removeRunState } from "./runState.js";
2
+ import { errorMessage, log } from "./util.js";
3
+ export function recordCleanedUpRuns(config, entries) {
4
+ for (const entry of entries) {
5
+ try {
6
+ removeRunState(config, entry.ticket);
7
+ }
8
+ catch (error) {
9
+ log(`Run state cleanup failed for ${entry.ticket}: ${errorMessage(error)}`);
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,32 @@
1
+ import { type ResolvedConfig } from "./config.ts";
2
+ export interface StagedPrompt {
3
+ directory: string;
4
+ file: string;
5
+ }
6
+ interface PromptTemplateVariables {
7
+ ticket: string;
8
+ worktree: string;
9
+ title: string;
10
+ description: string;
11
+ }
12
+ export declare function stagePromptText(input: {
13
+ prefix: string;
14
+ ticket: string;
15
+ text: string;
16
+ }): StagedPrompt;
17
+ export declare function stagePromptFromTemplate(input: {
18
+ config: ResolvedConfig;
19
+ prefix: string;
20
+ ticket: string;
21
+ variables: PromptTemplateVariables;
22
+ }): StagedPrompt;
23
+ /**
24
+ * Stage a `KEY='value'` env file for any populated build-time secret so
25
+ * the launch command can source it. Returns `undefined` when groundcrew
26
+ * has nothing to forward, leaving the launch command unchanged.
27
+ */
28
+ export declare function stageBuildSecrets(promptDir: string): string | undefined;
29
+ export declare function stageWorkspaceLaunchCommand(promptDir: string, command: string): string;
30
+ export declare function removeStagedPrompt(directory: string): void;
31
+ export {};
32
+ //# sourceMappingURL=stagedLaunch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stagedLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/stagedLaunch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAItE,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,YAAY,CAKf;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,uBAAuB,CAAC;CACpC,GAAG,YAAY,CAMf;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAevE;AAQD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE1D"}