@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.
- package/README.md +47 -17
- package/crew.config.example.ts +25 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -0
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +6 -4
- package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
- package/dist/commands/cleanupWorkspace.js +2 -0
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +6 -6
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +2 -2
- package/dist/commands/interruptWorkspace.d.ts +8 -0
- package/dist/commands/interruptWorkspace.d.ts.map +1 -0
- package/dist/commands/interruptWorkspace.js +108 -0
- package/dist/commands/orchestrator.d.ts +4 -2
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +6 -105
- package/dist/commands/resumeWorkspace.d.ts +7 -0
- package/dist/commands/resumeWorkspace.d.ts.map +1 -0
- package/dist/commands/resumeWorkspace.js +163 -0
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +78 -79
- package/dist/commands/ticketDoctor.d.ts +18 -3
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +105 -11
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/agentLaunch.d.ts +29 -0
- package/dist/lib/agentLaunch.d.ts.map +1 -0
- package/dist/lib/agentLaunch.js +53 -0
- package/dist/lib/boardSource.d.ts +41 -5
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +211 -70
- package/dist/lib/config.d.ts +59 -25
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +130 -22
- package/dist/lib/linearIssueStatus.d.ts +3 -1
- package/dist/lib/linearIssueStatus.d.ts.map +1 -1
- package/dist/lib/linearIssueStatus.js +0 -0
- package/dist/lib/runState.d.ts +46 -0
- package/dist/lib/runState.d.ts.map +1 -0
- package/dist/lib/runState.js +137 -0
- package/dist/lib/runStateCleanup.d.ts +4 -0
- package/dist/lib/runStateCleanup.d.ts.map +1 -0
- package/dist/lib/runStateCleanup.js +12 -0
- package/dist/lib/stagedLaunch.d.ts +32 -0
- package/dist/lib/stagedLaunch.d.ts.map +1 -0
- package/dist/lib/stagedLaunch.js +58 -0
- package/dist/lib/util.d.ts +0 -1
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +0 -4
- package/dist/lib/workspaces.d.ts +19 -1
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +29 -9
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +12 -4
- 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
|
|
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
|
|
9
|
-
// >
|
|
10
|
-
//
|
|
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
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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
|
-
|
|
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
|
|
349
|
-
|
|
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 {
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 {
|
|
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
|
|
52
|
-
*
|
|
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
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,
|
|
1
|
+
{"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"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"}
|