@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.
- package/README.md +31 -2
- 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 +2 -0
- package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
- package/dist/commands/cleanupWorkspace.js +2 -0
- 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/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 +77 -79
- package/dist/commands/ticketDoctor.d.ts +18 -3
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +77 -8
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- 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/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/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
|
@@ -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
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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":"
|
|
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
|
|
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.
|
|
@@ -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
|
|
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: "",
|
|
@@ -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";
|
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,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 @@
|
|
|
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"}
|