@drisp/cli 0.6.8 → 0.6.9
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/dist/{WorkflowInstallWizard-TQ4SF5KB.js → WorkflowInstallWizard-NZFBSBLY.js} +2 -2
- package/dist/{chunk-MP5EWDIQ.js → chunk-2DLFGWJV.js} +2 -2
- package/dist/chunk-2JL4QT55.js +21866 -0
- package/dist/{chunk-HN3WNQVY.js → chunk-YUVMVYUF.js} +23174 -39331
- package/dist/cli.js +1352 -48
- package/dist/dashboard-daemon.js +3 -3
- package/dist/runner.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-CIPO2P2Q.js +0 -4016
package/dist/cli.js
CHANGED
|
@@ -8,16 +8,23 @@ import {
|
|
|
8
8
|
PORTABLE_PROVIDER_ENV_VARS,
|
|
9
9
|
RUN_EXIT_CODE,
|
|
10
10
|
attachRuntimeEventLoop,
|
|
11
|
+
behindMain,
|
|
11
12
|
bootstrapRuntimeConfig,
|
|
12
13
|
buildApiKeyHelperSettings,
|
|
13
14
|
buildPhaseFeedEvent,
|
|
15
|
+
buildPlanSchema,
|
|
14
16
|
buildPostByToolUseId,
|
|
15
17
|
buildProbeConfigs,
|
|
16
18
|
centerAnsi,
|
|
19
|
+
childWorktreePath,
|
|
17
20
|
classifyFailure,
|
|
21
|
+
commandSchema,
|
|
22
|
+
commitBuildFiles,
|
|
18
23
|
compactText,
|
|
19
24
|
computeInputRows,
|
|
20
25
|
connectAlreadyConnectedSchema,
|
|
26
|
+
connectCodeResponseSchema,
|
|
27
|
+
connectPendingResponseSchema,
|
|
21
28
|
connectRedeemResponseSchema,
|
|
22
29
|
connectStartResponseSchema,
|
|
23
30
|
createFeedMapper,
|
|
@@ -26,10 +33,15 @@ import {
|
|
|
26
33
|
createSessionStore,
|
|
27
34
|
credentialHelperKey,
|
|
28
35
|
cursorToVisualPosition,
|
|
36
|
+
defaultWorktreesRoot,
|
|
29
37
|
detectClaudeVersion,
|
|
38
|
+
ensureBuildWorktree,
|
|
39
|
+
ensureChildWorktree,
|
|
30
40
|
expandHome,
|
|
31
41
|
extractFriendlyServerName,
|
|
32
42
|
extractPermissionSnapshot,
|
|
43
|
+
fallbackBranch,
|
|
44
|
+
finalizePlan,
|
|
33
45
|
fit,
|
|
34
46
|
fitAnsi,
|
|
35
47
|
forgetRunner,
|
|
@@ -46,12 +58,18 @@ import {
|
|
|
46
58
|
getLatestRunForSession,
|
|
47
59
|
getMostRecentAthenaSession,
|
|
48
60
|
getSessionMeta,
|
|
61
|
+
git,
|
|
62
|
+
hasUncommittedWork,
|
|
49
63
|
hintGlyphs,
|
|
64
|
+
historyRecordSchema,
|
|
50
65
|
hubHttpUrl,
|
|
51
66
|
identityHubOrigin,
|
|
52
67
|
isBashToolResponse,
|
|
53
68
|
isCommandPrefix,
|
|
69
|
+
isGitHubRepository,
|
|
54
70
|
isScopedPermissionsRequest,
|
|
71
|
+
isStartAnswer,
|
|
72
|
+
lastCommitTouching,
|
|
55
73
|
listAwaitingAttentionRuns,
|
|
56
74
|
listHarnessAdapters,
|
|
57
75
|
listHarnessCapabilities,
|
|
@@ -66,6 +84,8 @@ import {
|
|
|
66
84
|
overriddenIdentityPath,
|
|
67
85
|
parseFrontmatter,
|
|
68
86
|
parseToolName,
|
|
87
|
+
planBuild,
|
|
88
|
+
planInputSchema,
|
|
69
89
|
platformRunnerServices,
|
|
70
90
|
probeSkipReason,
|
|
71
91
|
processRegistry,
|
|
@@ -74,6 +94,8 @@ import {
|
|
|
74
94
|
registerCleanupOnExit,
|
|
75
95
|
releaseMcpAsset,
|
|
76
96
|
renderInputLines,
|
|
97
|
+
repoLayoutAt,
|
|
98
|
+
repositoryBindingSchema,
|
|
77
99
|
require_string_width,
|
|
78
100
|
resolveClaudeBinary,
|
|
79
101
|
resolveClaudeSettingsSurfacePaths,
|
|
@@ -88,12 +110,18 @@ import {
|
|
|
88
110
|
runExec,
|
|
89
111
|
runMachineRunners,
|
|
90
112
|
runProbe,
|
|
113
|
+
runViewSchema,
|
|
114
|
+
runnerAdoptRequestSchema,
|
|
91
115
|
runnerConfigDirectory,
|
|
92
116
|
runnerConfigSchema,
|
|
117
|
+
runnerDisconnectPreviewSchema,
|
|
93
118
|
runnerDisconnectResponseSchema,
|
|
94
119
|
runnerIdentities,
|
|
120
|
+
runnerListEntrySchema,
|
|
121
|
+
runnerListSchema,
|
|
95
122
|
runnerServiceUnit,
|
|
96
123
|
runnerSoftwareVersion,
|
|
124
|
+
runnerUpdateRequestSchema,
|
|
97
125
|
serviceInstallationId,
|
|
98
126
|
sessionsDir,
|
|
99
127
|
sliceAnsi,
|
|
@@ -101,12 +129,14 @@ import {
|
|
|
101
129
|
startDashboardDecisionDrain,
|
|
102
130
|
startWorkflowExecution,
|
|
103
131
|
supportsSessionApproval,
|
|
132
|
+
teamDefaultRequestSchema,
|
|
133
|
+
teamDefaultSchema,
|
|
104
134
|
teamIdentityPath,
|
|
105
135
|
todoGlyphSet,
|
|
106
136
|
visualPositionToOffset,
|
|
107
137
|
wrapText,
|
|
108
138
|
writeIdentityOnce
|
|
109
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-2JL4QT55.js";
|
|
110
140
|
import {
|
|
111
141
|
generateId as generateId2
|
|
112
142
|
} from "./chunk-PQIB66SG.js";
|
|
@@ -120,9 +150,21 @@ import {
|
|
|
120
150
|
useTheme
|
|
121
151
|
} from "./chunk-KPDCVR6Q.js";
|
|
122
152
|
import {
|
|
153
|
+
BUILD_STRINGS,
|
|
154
|
+
CANNOT_PASS_TEMPLATE,
|
|
155
|
+
DESCRIPTION_TEMPLATE,
|
|
156
|
+
GATES,
|
|
157
|
+
LAYOUT_MARKERS,
|
|
158
|
+
NO_GATE_TEXT_TEMPLATE,
|
|
159
|
+
ORCHESTRATOR_LIMITS,
|
|
160
|
+
QA_SECTIONS,
|
|
161
|
+
RENDERER_RULES,
|
|
162
|
+
STAGE_TEMPLATE,
|
|
163
|
+
WORKFLOW_MD_TEMPLATE,
|
|
123
164
|
collectMcpServersWithOptions,
|
|
124
165
|
createSteerQueue,
|
|
125
166
|
deserializeRunMemory,
|
|
167
|
+
external_exports,
|
|
126
168
|
findMarketplaceRepoDir,
|
|
127
169
|
gatherMarketplaceWorkflowSources,
|
|
128
170
|
hasProjectWorkflow,
|
|
@@ -133,6 +175,7 @@ import {
|
|
|
133
175
|
listMarketplaceWorkflows,
|
|
134
176
|
listMarketplaceWorkflowsFromRepo,
|
|
135
177
|
listWorkflows,
|
|
178
|
+
parseWorkflowConfig,
|
|
136
179
|
projectConfigPath,
|
|
137
180
|
pullMarketplaceRepo,
|
|
138
181
|
readConfig,
|
|
@@ -145,10 +188,12 @@ import {
|
|
|
145
188
|
resolveWorkflowMarketplaceSource,
|
|
146
189
|
updateWorkflow,
|
|
147
190
|
updateWorkflows,
|
|
191
|
+
verifyRendered,
|
|
148
192
|
wakesFreshAfterHandover,
|
|
193
|
+
workflowRegistryDir,
|
|
149
194
|
writeGlobalConfig,
|
|
150
195
|
writeProjectConfig
|
|
151
|
-
} from "./chunk-
|
|
196
|
+
} from "./chunk-YUVMVYUF.js";
|
|
152
197
|
import {
|
|
153
198
|
__toESM
|
|
154
199
|
} from "./chunk-G3PMV62Z.js";
|
|
@@ -156,10 +201,10 @@ import {
|
|
|
156
201
|
// src/app/entry/cli.tsx
|
|
157
202
|
import { render } from "ink";
|
|
158
203
|
import meow from "meow";
|
|
159
|
-
import
|
|
204
|
+
import fs9 from "fs";
|
|
160
205
|
import { createRequire } from "module";
|
|
161
206
|
import os5 from "os";
|
|
162
|
-
import
|
|
207
|
+
import path9 from "path";
|
|
163
208
|
import { fileURLToPath } from "url";
|
|
164
209
|
|
|
165
210
|
// src/app/shell/sessionUiState.ts
|
|
@@ -5278,14 +5323,14 @@ function extractWebSearch(_input, response) {
|
|
|
5278
5323
|
return { type: "text", content: extractTextContent(response), maxLines: 10 };
|
|
5279
5324
|
}
|
|
5280
5325
|
function extractNotebookEdit(input, _response) {
|
|
5281
|
-
const
|
|
5326
|
+
const path10 = typeof input["notebook_path"] === "string" ? input["notebook_path"] : "";
|
|
5282
5327
|
const mode = typeof input["edit_mode"] === "string" ? input["edit_mode"] : "replace";
|
|
5283
5328
|
const source = typeof input["new_source"] === "string" ? input["new_source"] : "";
|
|
5284
|
-
if (!source) return { type: "text", content: `${mode} cell in ${
|
|
5329
|
+
if (!source) return { type: "text", content: `${mode} cell in ${path10}` };
|
|
5285
5330
|
return {
|
|
5286
5331
|
type: "code",
|
|
5287
5332
|
content: source,
|
|
5288
|
-
language: detectLanguage(
|
|
5333
|
+
language: detectLanguage(path10),
|
|
5289
5334
|
maxLines: 10
|
|
5290
5335
|
};
|
|
5291
5336
|
}
|
|
@@ -5811,10 +5856,10 @@ function extractToolSubject(toolName, toolInput) {
|
|
|
5811
5856
|
const parts = [];
|
|
5812
5857
|
const pattern = str(input, "pattern");
|
|
5813
5858
|
const glob = str(input, "glob");
|
|
5814
|
-
const
|
|
5859
|
+
const path10 = str(input, "path");
|
|
5815
5860
|
if (pattern) parts.push(`"${pattern}"`);
|
|
5816
5861
|
if (glob) parts.push(glob);
|
|
5817
|
-
if (
|
|
5862
|
+
if (path10) parts.push(`in ${path10}`);
|
|
5818
5863
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
5819
5864
|
}
|
|
5820
5865
|
case "Skill":
|
|
@@ -5970,8 +6015,8 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
|
|
|
5970
6015
|
event.data.tool_input
|
|
5971
6016
|
);
|
|
5972
6017
|
const header = buildCompactHeader(event, width, { subject, theme });
|
|
5973
|
-
const
|
|
5974
|
-
const requestLines = highlightCode(
|
|
6018
|
+
const json2 = JSON.stringify(event.data.tool_input, null, 2);
|
|
6019
|
+
const requestLines = highlightCode(json2, cw, "json");
|
|
5975
6020
|
return buildResult(header, requestLines, true);
|
|
5976
6021
|
}
|
|
5977
6022
|
case "subagent.start": {
|
|
@@ -5994,8 +6039,8 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
|
|
|
5994
6039
|
} else if (prompt) {
|
|
5995
6040
|
content = renderMarkdownToLines(prompt, cw);
|
|
5996
6041
|
} else {
|
|
5997
|
-
const
|
|
5998
|
-
content = highlightCode(
|
|
6042
|
+
const json2 = JSON.stringify(event.data, null, 2);
|
|
6043
|
+
content = highlightCode(json2, cw, "json");
|
|
5999
6044
|
}
|
|
6000
6045
|
return buildResult(header, content, !prompt && !response);
|
|
6001
6046
|
}
|
|
@@ -6104,8 +6149,8 @@ Named by Turn ${event.data.turn} of Workflow Run \`${event.data.runId}\`.`,
|
|
|
6104
6149
|
case "elicitation.result":
|
|
6105
6150
|
case "artifacts.manifest": {
|
|
6106
6151
|
const header = buildCompactHeader(event, width, { theme });
|
|
6107
|
-
const
|
|
6108
|
-
const content = highlightCode(
|
|
6152
|
+
const json2 = JSON.stringify(event.raw ?? event.data, null, 2);
|
|
6153
|
+
const content = highlightCode(json2, cw, "json");
|
|
6109
6154
|
return buildResult(header, content, true);
|
|
6110
6155
|
}
|
|
6111
6156
|
default: {
|
|
@@ -13038,7 +13083,7 @@ var thisMachine = (installationId) => ({
|
|
|
13038
13083
|
os: `${platform()} ${release()}`.slice(0, 64),
|
|
13039
13084
|
softwareVersion: runnerSoftwareVersion
|
|
13040
13085
|
});
|
|
13041
|
-
var post = (origin,
|
|
13086
|
+
var post = (origin, path10, body) => fetch(new URL(path10, origin), {
|
|
13042
13087
|
method: "POST",
|
|
13043
13088
|
headers: { "Content-Type": "application/json" },
|
|
13044
13089
|
body: JSON.stringify(body)
|
|
@@ -13057,8 +13102,8 @@ async function alreadyConnected(existing, { forTeam, ownIdentity }) {
|
|
|
13057
13102
|
const problem = own ? await credentialAccepted(own.config) ? void 0 : "lapsed" : forTeam(existing.orgId).length ? "other_runner" : "missing";
|
|
13058
13103
|
const runner = { orgId: existing.orgId, runnerId: existing.runnerId, name: existing.name };
|
|
13059
13104
|
if (own && problem === void 0) return { status: "already_connected", ...runner, path: own.path };
|
|
13060
|
-
const
|
|
13061
|
-
return { status: "identity_unusable", ...runner, problem, ...
|
|
13105
|
+
const path10 = (own ?? forTeam(existing.orgId)[0])?.path;
|
|
13106
|
+
return { status: "identity_unusable", ...runner, problem, ...path10 ? { path: path10 } : {} };
|
|
13062
13107
|
}
|
|
13063
13108
|
async function connectWithCode({
|
|
13064
13109
|
code,
|
|
@@ -13130,20 +13175,20 @@ async function saveIdentity(origin, credential, { single, forTeam }, directory)
|
|
|
13130
13175
|
name: credential.name
|
|
13131
13176
|
});
|
|
13132
13177
|
const replaced = [];
|
|
13133
|
-
for (const { path:
|
|
13134
|
-
const aside = `${
|
|
13135
|
-
await rename(
|
|
13178
|
+
for (const { path: path11 } of forTeam(credential.orgId)) {
|
|
13179
|
+
const aside = `${path11}.replaced-${Date.now()}`;
|
|
13180
|
+
await rename(path11, aside);
|
|
13136
13181
|
replaced.push(aside);
|
|
13137
13182
|
}
|
|
13138
|
-
const
|
|
13183
|
+
const path10 = single ?? teamIdentityPath(origin, credential.orgId, directory);
|
|
13139
13184
|
try {
|
|
13140
|
-
await writeIdentityOnce(
|
|
13185
|
+
await writeIdentityOnce(path10, JSON.stringify(config, null, 2) + "\n");
|
|
13141
13186
|
} catch (error) {
|
|
13142
13187
|
if (error.code === "EEXIST")
|
|
13143
|
-
throw new Error(`Another Connect wrote a Runner identity for this Team at ${
|
|
13188
|
+
throw new Error(`Another Connect wrote a Runner identity for this Team at ${path10} meanwhile; it was left unchanged.`);
|
|
13144
13189
|
throw error;
|
|
13145
13190
|
}
|
|
13146
|
-
return { status: "connected", config, path:
|
|
13191
|
+
return { status: "connected", config, path: path10, replaced };
|
|
13147
13192
|
}
|
|
13148
13193
|
function pathSegment(value) {
|
|
13149
13194
|
const segment = encodeURIComponent(value);
|
|
@@ -13183,7 +13228,7 @@ async function refuseExisting(target) {
|
|
|
13183
13228
|
throw new Error(`This machine already has a Runner${name ? ` (${name})` : ""} configured at ${target}.`);
|
|
13184
13229
|
}
|
|
13185
13230
|
async function disconnectRunner({
|
|
13186
|
-
identity: { path:
|
|
13231
|
+
identity: { path: path10, config },
|
|
13187
13232
|
serviceManager,
|
|
13188
13233
|
othersRemain
|
|
13189
13234
|
}) {
|
|
@@ -13216,7 +13261,7 @@ async function disconnectRunner({
|
|
|
13216
13261
|
await restore();
|
|
13217
13262
|
throw new Error(`The Hub refused to disconnect ${name} (${body.error ?? response.status}); it is still connected.`);
|
|
13218
13263
|
}
|
|
13219
|
-
await forgetRunner(
|
|
13264
|
+
await forgetRunner(path10);
|
|
13220
13265
|
if (othersRemain) await restore();
|
|
13221
13266
|
else await serviceManager?.remove();
|
|
13222
13267
|
return result;
|
|
@@ -13350,18 +13395,18 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13350
13395
|
`This machine is already connected to this Team as ${outcome.name}, but ${why}. ` + (code ? `The code was not used: connect a new Runner in its place with ${retry}. ` : `Connect a new Runner in its place with a Connect code from Settings \u2192 Runners: ${retry}. `) + `The old ${outcome.name} stays listed in Settings \u2192 Runners, offline.`
|
|
13351
13396
|
);
|
|
13352
13397
|
}
|
|
13353
|
-
const { config, path:
|
|
13398
|
+
const { config, path: path10, replaced } = outcome;
|
|
13354
13399
|
for (const aside of replaced)
|
|
13355
13400
|
process.stdout.write(
|
|
13356
13401
|
`The Hub no longer recognised this machine's earlier identity for this Team; it was moved aside to ${aside}.
|
|
13357
13402
|
`
|
|
13358
13403
|
);
|
|
13359
13404
|
process.stdout.write(
|
|
13360
|
-
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${
|
|
13405
|
+
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${path10}.
|
|
13361
13406
|
`
|
|
13362
13407
|
);
|
|
13363
13408
|
if (services) {
|
|
13364
|
-
const service = await ensureService(services, config,
|
|
13409
|
+
const service = await ensureService(services, config, path10);
|
|
13365
13410
|
process.stdout.write(`${serviceStarted(service)}
|
|
13366
13411
|
`);
|
|
13367
13412
|
return 0;
|
|
@@ -13370,7 +13415,7 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13370
13415
|
process.stdout.write(
|
|
13371
13416
|
"No launchd or systemd here to run the Runner as a background service; running it in the foreground instead.\n"
|
|
13372
13417
|
);
|
|
13373
|
-
await runConfiguredRunner(
|
|
13418
|
+
await runConfiguredRunner(path10, options.signal);
|
|
13374
13419
|
return 0;
|
|
13375
13420
|
}
|
|
13376
13421
|
case "disconnect": {
|
|
@@ -13444,9 +13489,9 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13444
13489
|
configured: true,
|
|
13445
13490
|
// One entry per Team this machine serves; credentials are never printed.
|
|
13446
13491
|
teams: identities.map(
|
|
13447
|
-
({ path:
|
|
13492
|
+
({ path: path10, config: { token: _token, ...config } }) => ({
|
|
13448
13493
|
...config,
|
|
13449
|
-
path:
|
|
13494
|
+
path: path10
|
|
13450
13495
|
})
|
|
13451
13496
|
),
|
|
13452
13497
|
service: service ? {
|
|
@@ -13513,14 +13558,1248 @@ async function recoveryIdentity(runId) {
|
|
|
13513
13558
|
throw new Error(`No Runner on this machine holds Run ${runId}.`);
|
|
13514
13559
|
}
|
|
13515
13560
|
|
|
13561
|
+
// src/app/entry/buildCommand.ts
|
|
13562
|
+
import { execFileSync } from "child_process";
|
|
13563
|
+
|
|
13564
|
+
// src/core/build/planCommand.ts
|
|
13565
|
+
import fs6 from "fs";
|
|
13566
|
+
import path6 from "path";
|
|
13567
|
+
|
|
13568
|
+
// src/core/build/io.ts
|
|
13569
|
+
import fs5 from "fs";
|
|
13570
|
+
import path5 from "path";
|
|
13571
|
+
var BuildUsageError = class extends Error {
|
|
13572
|
+
};
|
|
13573
|
+
var PlanFailure = class extends Error {
|
|
13574
|
+
};
|
|
13575
|
+
function requireEnv(env2, name) {
|
|
13576
|
+
const value = env2[name];
|
|
13577
|
+
if (!value)
|
|
13578
|
+
throw new BuildUsageError(
|
|
13579
|
+
`${name} is not set: drisp build runs inside an Orchestrator Run.`
|
|
13580
|
+
);
|
|
13581
|
+
return value;
|
|
13582
|
+
}
|
|
13583
|
+
function readJson(file) {
|
|
13584
|
+
try {
|
|
13585
|
+
return JSON.parse(fs5.readFileSync(file, "utf-8"));
|
|
13586
|
+
} catch {
|
|
13587
|
+
return void 0;
|
|
13588
|
+
}
|
|
13589
|
+
}
|
|
13590
|
+
function writeFile(file, content) {
|
|
13591
|
+
fs5.mkdirSync(path5.dirname(file), { recursive: true });
|
|
13592
|
+
fs5.writeFileSync(file, content, "utf-8");
|
|
13593
|
+
}
|
|
13594
|
+
var json = (value) => `${JSON.stringify(value, null, 2)}
|
|
13595
|
+
`;
|
|
13596
|
+
function readInput(file) {
|
|
13597
|
+
const raw = readJson(file);
|
|
13598
|
+
if (raw === void 0)
|
|
13599
|
+
throw new PlanFailure(`Could not read plan-input.json at ${file}.`);
|
|
13600
|
+
const parsed = planInputSchema.safeParse(raw);
|
|
13601
|
+
if (!parsed.success) {
|
|
13602
|
+
const issue = parsed.error.issues[0];
|
|
13603
|
+
throw new PlanFailure(
|
|
13604
|
+
`plan-input.json is invalid at ${issue.path.join(".") || "(root)"}: ${issue.message}`
|
|
13605
|
+
);
|
|
13606
|
+
}
|
|
13607
|
+
return parsed.data;
|
|
13608
|
+
}
|
|
13609
|
+
|
|
13610
|
+
// src/core/workflows/builtins/build-child/version.ts
|
|
13611
|
+
import { createHash } from "crypto";
|
|
13612
|
+
function computeTemplateVersion() {
|
|
13613
|
+
const parts = [
|
|
13614
|
+
WORKFLOW_MD_TEMPLATE,
|
|
13615
|
+
STAGE_TEMPLATE,
|
|
13616
|
+
CANNOT_PASS_TEMPLATE,
|
|
13617
|
+
NO_GATE_TEXT_TEMPLATE,
|
|
13618
|
+
DESCRIPTION_TEMPLATE,
|
|
13619
|
+
JSON.stringify(QA_SECTIONS),
|
|
13620
|
+
JSON.stringify(GATES),
|
|
13621
|
+
JSON.stringify(RENDERER_RULES),
|
|
13622
|
+
JSON.stringify(LAYOUT_MARKERS)
|
|
13623
|
+
];
|
|
13624
|
+
const hash = createHash("sha256");
|
|
13625
|
+
for (const part of parts) hash.update(`${part.length}:${part}`);
|
|
13626
|
+
return hash.digest("hex");
|
|
13627
|
+
}
|
|
13628
|
+
var templateVersion = computeTemplateVersion();
|
|
13629
|
+
|
|
13630
|
+
// src/core/build/planCommand.ts
|
|
13631
|
+
function checkGenerated(directory, workflow, host, layout) {
|
|
13632
|
+
const workflowJson = fs6.readFileSync(
|
|
13633
|
+
path6.join(directory, "workflow.json"),
|
|
13634
|
+
"utf-8"
|
|
13635
|
+
);
|
|
13636
|
+
const workflowMd = fs6.readFileSync(
|
|
13637
|
+
path6.join(directory, "workflow.md"),
|
|
13638
|
+
"utf-8"
|
|
13639
|
+
);
|
|
13640
|
+
try {
|
|
13641
|
+
parseWorkflowConfig(JSON.parse(workflowJson), {
|
|
13642
|
+
instructions: workflowMd,
|
|
13643
|
+
host
|
|
13644
|
+
});
|
|
13645
|
+
} catch (error) {
|
|
13646
|
+
throw new PlanFailure(
|
|
13647
|
+
`${workflow.identifier}: ${error instanceof Error ? error.message : String(error)}`
|
|
13648
|
+
);
|
|
13649
|
+
}
|
|
13650
|
+
const verified = verifyRendered({
|
|
13651
|
+
fill: workflow.fill,
|
|
13652
|
+
layout,
|
|
13653
|
+
workflowName: workflow.workflowName,
|
|
13654
|
+
workflowJson,
|
|
13655
|
+
workflowMd
|
|
13656
|
+
});
|
|
13657
|
+
if (!verified.ok)
|
|
13658
|
+
throw new PlanFailure(
|
|
13659
|
+
`${workflow.identifier}: the written ${verified.file} does not match what its fill renders.`
|
|
13660
|
+
);
|
|
13661
|
+
}
|
|
13662
|
+
function runBuildPlan(deps) {
|
|
13663
|
+
const orchestratorRunId = requireEnv(deps.env, "DRISP_RUN_ID");
|
|
13664
|
+
const runnerId = requireEnv(deps.env, "DRISP_RUNNER_ID");
|
|
13665
|
+
const artifacts = requireEnv(deps.env, "DRISP_RUN_ARTIFACTS");
|
|
13666
|
+
const fixtureInput = deps.env["DRISP_BUILD_PLAN_INPUT"];
|
|
13667
|
+
const inputPath = fixtureInput ?? deps.inputPath;
|
|
13668
|
+
if (!inputPath)
|
|
13669
|
+
throw new BuildUsageError("Usage: drisp build plan --input <path>");
|
|
13670
|
+
const fail = (reason) => {
|
|
13671
|
+
writeFile(path6.join(artifacts, "build_plan_error.json"), json({ reason }));
|
|
13672
|
+
return { status: "failed", reason };
|
|
13673
|
+
};
|
|
13674
|
+
try {
|
|
13675
|
+
if (!isGitHubRepository(deps.folder))
|
|
13676
|
+
throw new PlanFailure(BUILD_STRINGS.notAGitHubRepository);
|
|
13677
|
+
const input = readInput(path6.resolve(deps.folder, inputPath));
|
|
13678
|
+
const worktree = ensureBuildWorktree({
|
|
13679
|
+
folder: deps.folder,
|
|
13680
|
+
slugId: input.project.slugId,
|
|
13681
|
+
worktreesRoot: deps.worktreesRoot
|
|
13682
|
+
});
|
|
13683
|
+
const designDir = path6.join(
|
|
13684
|
+
worktree.path,
|
|
13685
|
+
".athena",
|
|
13686
|
+
"workflows",
|
|
13687
|
+
input.project.slugId
|
|
13688
|
+
);
|
|
13689
|
+
writeFile(path6.join(designDir, "plan-input.json"), json(input));
|
|
13690
|
+
const fixtureDir = fixtureInput ? path6.dirname(path6.resolve(fixtureInput)) : void 0;
|
|
13691
|
+
const fillPath = (identifier) => path6.join(designDir, identifier, "fill.json");
|
|
13692
|
+
const fills = Object.fromEntries(
|
|
13693
|
+
input.issues.map((issue) => {
|
|
13694
|
+
const fixture = fixtureDir ? path6.join(fixtureDir, "fills", `${issue.identifier}.json`) : void 0;
|
|
13695
|
+
const file = fixture && fs6.existsSync(fixture) ? fixture : fillPath(issue.identifier);
|
|
13696
|
+
return [issue.identifier, readJson(file)];
|
|
13697
|
+
})
|
|
13698
|
+
);
|
|
13699
|
+
const fixturePullRequests = fixtureDir ? readJson(path6.join(fixtureDir, "pull-requests.json")) : void 0;
|
|
13700
|
+
const pullRequests = fixturePullRequests ?? Object.fromEntries(
|
|
13701
|
+
input.issues.filter(
|
|
13702
|
+
(issue) => issue.branchName && issue.state.type !== "canceled" && !issue.labels.includes("ready-for-human")
|
|
13703
|
+
).map((issue) => [
|
|
13704
|
+
issue.branchName,
|
|
13705
|
+
deps.pullRequests(deps.folder, issue.branchName)
|
|
13706
|
+
])
|
|
13707
|
+
);
|
|
13708
|
+
const layout = repoLayoutAt(worktree.path, "HEAD");
|
|
13709
|
+
const result = planBuild({
|
|
13710
|
+
input,
|
|
13711
|
+
fills,
|
|
13712
|
+
pullRequests,
|
|
13713
|
+
orchestratorRunId,
|
|
13714
|
+
folder: deps.folder,
|
|
13715
|
+
runnerId,
|
|
13716
|
+
templateVersion,
|
|
13717
|
+
layout
|
|
13718
|
+
});
|
|
13719
|
+
if (result.status === "failed") return fail(result.reason);
|
|
13720
|
+
if (result.status === "needs_fills")
|
|
13721
|
+
return {
|
|
13722
|
+
status: "needs_fills",
|
|
13723
|
+
templateVersion,
|
|
13724
|
+
fills: result.fills.map((fill) => ({
|
|
13725
|
+
...fill,
|
|
13726
|
+
path: fillPath(fill.identifier)
|
|
13727
|
+
}))
|
|
13728
|
+
};
|
|
13729
|
+
const host = deps.hostCapabilities();
|
|
13730
|
+
for (const workflow of result.workflows) {
|
|
13731
|
+
const directory = path6.join(designDir, workflow.identifier);
|
|
13732
|
+
writeFile(path6.join(directory, "fill.json"), json(workflow.fill));
|
|
13733
|
+
writeFile(path6.join(directory, "workflow.json"), workflow.workflowJson);
|
|
13734
|
+
writeFile(path6.join(directory, "workflow.md"), workflow.workflowMd);
|
|
13735
|
+
checkGenerated(directory, workflow, host, layout);
|
|
13736
|
+
}
|
|
13737
|
+
const relativeDesignDir = path6.relative(worktree.path, designDir);
|
|
13738
|
+
commitBuildFiles(
|
|
13739
|
+
worktree.path,
|
|
13740
|
+
[relativeDesignDir],
|
|
13741
|
+
`drisp build: Generated Workflows for ${input.project.slugId}`
|
|
13742
|
+
);
|
|
13743
|
+
const commit = lastCommitTouching(worktree.path, relativeDesignDir, [
|
|
13744
|
+
path6.join(relativeDesignDir, "plan.json")
|
|
13745
|
+
]);
|
|
13746
|
+
const plan = finalizePlan(result.plan, commit);
|
|
13747
|
+
writeFile(path6.join(designDir, "plan.json"), json(plan));
|
|
13748
|
+
commitBuildFiles(
|
|
13749
|
+
worktree.path,
|
|
13750
|
+
[path6.join(relativeDesignDir, "plan.json")],
|
|
13751
|
+
`drisp build: Run plan for ${input.project.slugId}`
|
|
13752
|
+
);
|
|
13753
|
+
writeFile(path6.join(artifacts, "build_plan.json"), json(plan));
|
|
13754
|
+
return {
|
|
13755
|
+
status: "planned",
|
|
13756
|
+
entries: plan.groups.reduce((sum, group) => sum + group.issues.length, 0),
|
|
13757
|
+
skipped: plan.skipped.length
|
|
13758
|
+
};
|
|
13759
|
+
} catch (error) {
|
|
13760
|
+
if (error instanceof BuildUsageError) throw error;
|
|
13761
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
13762
|
+
}
|
|
13763
|
+
}
|
|
13764
|
+
|
|
13765
|
+
// src/core/build/tick.ts
|
|
13766
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
13767
|
+
import fs7 from "fs";
|
|
13768
|
+
import path7 from "path";
|
|
13769
|
+
|
|
13770
|
+
// ../../packages/client/src/index.ts
|
|
13771
|
+
var runArtifactSchema = external_exports.object({ name: external_exports.string(), seq: external_exports.number().int(), json: external_exports.json() });
|
|
13772
|
+
var githubRepositorySchema = external_exports.object({
|
|
13773
|
+
installationId: external_exports.number().int().positive(),
|
|
13774
|
+
repositoryId: external_exports.number().int().positive(),
|
|
13775
|
+
owner: external_exports.string(),
|
|
13776
|
+
name: external_exports.string(),
|
|
13777
|
+
defaultBranch: external_exports.string()
|
|
13778
|
+
});
|
|
13779
|
+
var ClientError = class extends Error {
|
|
13780
|
+
constructor(code, status) {
|
|
13781
|
+
super(code);
|
|
13782
|
+
this.code = code;
|
|
13783
|
+
this.status = status;
|
|
13784
|
+
}
|
|
13785
|
+
code;
|
|
13786
|
+
status;
|
|
13787
|
+
};
|
|
13788
|
+
var DrispClient = class _DrispClient {
|
|
13789
|
+
constructor(baseUrl, orgId, token) {
|
|
13790
|
+
this.baseUrl = baseUrl;
|
|
13791
|
+
this.orgId = orgId;
|
|
13792
|
+
this.token = token;
|
|
13793
|
+
const url = new URL(baseUrl);
|
|
13794
|
+
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
|
|
13795
|
+
throw new Error("TLS_required");
|
|
13796
|
+
}
|
|
13797
|
+
baseUrl;
|
|
13798
|
+
orgId;
|
|
13799
|
+
token;
|
|
13800
|
+
/**
|
|
13801
|
+
* A client that authenticates with a Run's delegation token (root ADR-0008), from the agent's environment:
|
|
13802
|
+
* `hubUrl` is `DRISP_HUB_URL`, the Team-scoped base `https://<hub>/v2/orgs/<orgId>`, and `token` is
|
|
13803
|
+
* `DRISP_RUN_TOKEN`. The token starts, cancels and reads only its own Run's children.
|
|
13804
|
+
*/
|
|
13805
|
+
static delegated(hubUrl, token) {
|
|
13806
|
+
const url = new URL(hubUrl);
|
|
13807
|
+
const match = /^\/v2\/orgs\/([^/]+)\/?$/.exec(url.pathname);
|
|
13808
|
+
if (!match) throw new Error("invalid_hub_url");
|
|
13809
|
+
return new _DrispClient(url.origin, decodeURIComponent(match[1]), async () => token);
|
|
13810
|
+
}
|
|
13811
|
+
async request(path10, body) {
|
|
13812
|
+
const response = await fetch(
|
|
13813
|
+
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${path10}`,
|
|
13814
|
+
{
|
|
13815
|
+
method: body === void 0 ? "GET" : "POST",
|
|
13816
|
+
headers: {
|
|
13817
|
+
Authorization: `Bearer ${await this.token()}`,
|
|
13818
|
+
"Content-Type": "application/json"
|
|
13819
|
+
},
|
|
13820
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
13821
|
+
}
|
|
13822
|
+
);
|
|
13823
|
+
const result = await response.json();
|
|
13824
|
+
if (!response.ok)
|
|
13825
|
+
throw new ClientError(result.error ?? `HTTP ${response.status}`, response.status);
|
|
13826
|
+
return result;
|
|
13827
|
+
}
|
|
13828
|
+
submit(command) {
|
|
13829
|
+
return this.request("/commands", commandSchema.parse(command));
|
|
13830
|
+
}
|
|
13831
|
+
/** The recent Runs; with a filter, every Run carrying that key or every child of that Run, newest first. */
|
|
13832
|
+
async runs(filter) {
|
|
13833
|
+
const query = !filter ? "" : "key" in filter ? `?key=${encodeURIComponent(filter.key)}` : `?parent=${encodeURIComponent(filter.parent)}`;
|
|
13834
|
+
return external_exports.object({ hubEpoch: external_exports.string(), runs: external_exports.array(runViewSchema) }).parse(await this.request(`/runs${query}`));
|
|
13835
|
+
}
|
|
13836
|
+
/** The newest `run.artifact` named `name` that the Run reported; `not_found` when it reported none. */
|
|
13837
|
+
async artifact(runId, name) {
|
|
13838
|
+
return runArtifactSchema.parse(
|
|
13839
|
+
await this.request(`/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(name)}`)
|
|
13840
|
+
);
|
|
13841
|
+
}
|
|
13842
|
+
async run(runId) {
|
|
13843
|
+
return runViewSchema.parse(await this.request(`/runs/${encodeURIComponent(runId)}`));
|
|
13844
|
+
}
|
|
13845
|
+
history(runId, after = 0) {
|
|
13846
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/history?after=${after}`);
|
|
13847
|
+
}
|
|
13848
|
+
async runners() {
|
|
13849
|
+
return runnerListSchema.parse(await this.request("/runners"));
|
|
13850
|
+
}
|
|
13851
|
+
/** A one-use Connect code bound to the signed-in member, for `drisp runner connect <code>` (root ADR-0005). */
|
|
13852
|
+
async createConnectCode(hubEpoch) {
|
|
13853
|
+
return connectCodeResponseSchema.parse(await this.request("/runners/connect-codes", { hubEpoch }));
|
|
13854
|
+
}
|
|
13855
|
+
/** Rename a Runner, set its capacity, or change its Audience (root ADR-0005). */
|
|
13856
|
+
async updateRunner(hubEpoch, runnerId, update) {
|
|
13857
|
+
const body = runnerUpdateRequestSchema.parse({ hubEpoch, ...update });
|
|
13858
|
+
return runnerListEntrySchema.parse(await this.request(`/runners/${encodeURIComponent(runnerId)}`, body));
|
|
13859
|
+
}
|
|
13860
|
+
/** Admin only: where a member's Runs go when they have no ready Personal Runner; `null` clears it. */
|
|
13861
|
+
async setTeamDefault(hubEpoch, teamDefault) {
|
|
13862
|
+
const body = teamDefaultRequestSchema.parse({ hubEpoch, teamDefault });
|
|
13863
|
+
return external_exports.object({ teamDefault: teamDefaultSchema.nullable() }).parse(await this.request("/team-default", body));
|
|
13864
|
+
}
|
|
13865
|
+
/** The Runs a Disconnect of `runnerId` would stop, for its confirmation. */
|
|
13866
|
+
async disconnectPreview(runnerId) {
|
|
13867
|
+
return runnerDisconnectPreviewSchema.parse(
|
|
13868
|
+
await this.request(`/runners/${encodeURIComponent(runnerId)}/disconnect`)
|
|
13869
|
+
);
|
|
13870
|
+
}
|
|
13871
|
+
/** Disconnect (root ADR-0005): the Runner leaves the Team and its open Runs are Stopped. */
|
|
13872
|
+
async disconnectRunner(hubEpoch, runnerId) {
|
|
13873
|
+
return runnerDisconnectResponseSchema.parse(
|
|
13874
|
+
await this.request(`/runners/${encodeURIComponent(runnerId)}/disconnect`, { hubEpoch })
|
|
13875
|
+
);
|
|
13876
|
+
}
|
|
13877
|
+
/** Admin only: take over a Runner paused as "Needs an owner" because its member left, so it serves again. */
|
|
13878
|
+
async adoptRunner(hubEpoch, runnerId) {
|
|
13879
|
+
const body = runnerAdoptRequestSchema.parse({ hubEpoch });
|
|
13880
|
+
return runnerListEntrySchema.parse(await this.request(`/runners/${encodeURIComponent(runnerId)}/adopt`, body));
|
|
13881
|
+
}
|
|
13882
|
+
submitManaged(profileId, command, repository) {
|
|
13883
|
+
return this.request("/managed-runs", {
|
|
13884
|
+
profileId,
|
|
13885
|
+
...repository,
|
|
13886
|
+
command: commandSchema.parse(command)
|
|
13887
|
+
});
|
|
13888
|
+
}
|
|
13889
|
+
async repositories() {
|
|
13890
|
+
const schema = external_exports.object({
|
|
13891
|
+
configured: external_exports.boolean(),
|
|
13892
|
+
installationUrl: external_exports.string().url().optional(),
|
|
13893
|
+
repositories: external_exports.array(repositoryBindingSchema)
|
|
13894
|
+
});
|
|
13895
|
+
try {
|
|
13896
|
+
return schema.parse(await this.request("/repositories"));
|
|
13897
|
+
} catch (error) {
|
|
13898
|
+
if (error instanceof ClientError && error.status === 404 && error.code === "not_found")
|
|
13899
|
+
return schema.parse({ configured: false, repositories: [] });
|
|
13900
|
+
throw error;
|
|
13901
|
+
}
|
|
13902
|
+
}
|
|
13903
|
+
async connectGitHub(hubEpoch) {
|
|
13904
|
+
return external_exports.object({
|
|
13905
|
+
connectionId: external_exports.string(),
|
|
13906
|
+
userCode: external_exports.string(),
|
|
13907
|
+
verificationUri: external_exports.string().url(),
|
|
13908
|
+
expiresAt: external_exports.number(),
|
|
13909
|
+
interval: external_exports.number(),
|
|
13910
|
+
installationUrl: external_exports.string().url()
|
|
13911
|
+
}).parse(await this.request("/github/device", { hubEpoch }));
|
|
13912
|
+
}
|
|
13913
|
+
async pollGitHub(hubEpoch, connectionId) {
|
|
13914
|
+
return external_exports.discriminatedUnion("state", [
|
|
13915
|
+
external_exports.object({ state: external_exports.literal("pending"), interval: external_exports.number() }),
|
|
13916
|
+
external_exports.object({
|
|
13917
|
+
state: external_exports.literal("authorized"),
|
|
13918
|
+
authorizationId: external_exports.string(),
|
|
13919
|
+
expiresAt: external_exports.number(),
|
|
13920
|
+
repositories: external_exports.array(githubRepositorySchema)
|
|
13921
|
+
})
|
|
13922
|
+
]).parse(await this.request("/github/device/poll", { hubEpoch, connectionId }));
|
|
13923
|
+
}
|
|
13924
|
+
async bindRepository(hubEpoch, authorizationId, repository, settings) {
|
|
13925
|
+
return repositoryBindingSchema.parse(
|
|
13926
|
+
await this.request("/repositories", {
|
|
13927
|
+
hubEpoch,
|
|
13928
|
+
authorizationId,
|
|
13929
|
+
installationId: repository.installationId,
|
|
13930
|
+
repositoryId: repository.repositoryId,
|
|
13931
|
+
...settings
|
|
13932
|
+
})
|
|
13933
|
+
);
|
|
13934
|
+
}
|
|
13935
|
+
async updateRepository(hubEpoch, id, settings) {
|
|
13936
|
+
return repositoryBindingSchema.parse(
|
|
13937
|
+
await this.request(`/repositories/${encodeURIComponent(id)}`, {
|
|
13938
|
+
hubEpoch,
|
|
13939
|
+
...settings
|
|
13940
|
+
})
|
|
13941
|
+
);
|
|
13942
|
+
}
|
|
13943
|
+
disconnectRepository(hubEpoch, id) {
|
|
13944
|
+
return this.request(`/repositories/${encodeURIComponent(id)}/disconnect`, { hubEpoch });
|
|
13945
|
+
}
|
|
13946
|
+
receipt(commandId) {
|
|
13947
|
+
return this.request(`/commands/${encodeURIComponent(commandId)}`);
|
|
13948
|
+
}
|
|
13949
|
+
deleteSession(hubEpoch, sessionId) {
|
|
13950
|
+
return this.request("/sessions/delete", { hubEpoch, sessionId });
|
|
13951
|
+
}
|
|
13952
|
+
async pendingDevice(code) {
|
|
13953
|
+
return external_exports.object({ name: external_exports.string() }).parse(await this.request(`/auth/device?code=${encodeURIComponent(code)}`));
|
|
13954
|
+
}
|
|
13955
|
+
/** Approves the macOS app's sign-in for this client's Team: the token is bound to it. */
|
|
13956
|
+
approveDevice(userCode) {
|
|
13957
|
+
return this.request("/auth/device/approve", { userCode });
|
|
13958
|
+
}
|
|
13959
|
+
/** The machine asking to Connect with a code it printed (`drisp runner connect` with no code; root ADR-0005). */
|
|
13960
|
+
async pendingConnect(code) {
|
|
13961
|
+
return connectPendingResponseSchema.parse(await this.request(`/runners/connect/pending?code=${encodeURIComponent(code)}`));
|
|
13962
|
+
}
|
|
13963
|
+
/** Connects the machine that printed `code` to this client's Team, as a Runner with `audience`. Team needs an admin. */
|
|
13964
|
+
approveConnect(code, audience, hubEpoch) {
|
|
13965
|
+
return this.request("/runners/connect/approve", { code, audience, ...hubEpoch === void 0 ? {} : { hubEpoch } });
|
|
13966
|
+
}
|
|
13967
|
+
/** Refuses the machine that printed `code`; its wait ends without a credential. */
|
|
13968
|
+
denyConnect(code) {
|
|
13969
|
+
return this.request("/runners/connect/deny", { code });
|
|
13970
|
+
}
|
|
13971
|
+
async historyPage(runId, after = 0) {
|
|
13972
|
+
const page = external_exports.object({
|
|
13973
|
+
through: external_exports.number(),
|
|
13974
|
+
archives: external_exports.array(
|
|
13975
|
+
external_exports.object({
|
|
13976
|
+
id: external_exports.string(),
|
|
13977
|
+
checksum: external_exports.string(),
|
|
13978
|
+
first_seq: external_exports.number(),
|
|
13979
|
+
last_seq: external_exports.number()
|
|
13980
|
+
})
|
|
13981
|
+
),
|
|
13982
|
+
batches: external_exports.array(external_exports.object({ firstSeq: external_exports.number(), records: external_exports.array(historyRecordSchema) }))
|
|
13983
|
+
}).parse(await this.history(runId, after));
|
|
13984
|
+
const records = [];
|
|
13985
|
+
const archive = page.archives[0];
|
|
13986
|
+
if (archive) {
|
|
13987
|
+
const response = await fetch(
|
|
13988
|
+
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}/runs/${encodeURIComponent(runId)}/objects/${encodeURIComponent(archive.id)}`,
|
|
13989
|
+
{ headers: { Authorization: `Bearer ${await this.token()}` } }
|
|
13990
|
+
);
|
|
13991
|
+
if (!response.ok) throw new Error(`history_download_${response.status}`);
|
|
13992
|
+
const bytes = await response.arrayBuffer();
|
|
13993
|
+
const checksum = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13994
|
+
if (checksum !== archive.checksum) throw new Error("history_integrity_failure");
|
|
13995
|
+
const text = await new Response(
|
|
13996
|
+
new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"))
|
|
13997
|
+
).text();
|
|
13998
|
+
for (const line of text.trim().split("\n"))
|
|
13999
|
+
records.push(
|
|
14000
|
+
external_exports.object({ seq: external_exports.number(), record: historyRecordSchema }).parse(JSON.parse(line))
|
|
14001
|
+
);
|
|
14002
|
+
}
|
|
14003
|
+
for (const batch of page.batches)
|
|
14004
|
+
batch.records.forEach(
|
|
14005
|
+
(record, index) => records.push({ seq: batch.firstSeq + index, record })
|
|
14006
|
+
);
|
|
14007
|
+
records.sort((a, b) => a.seq - b.seq);
|
|
14008
|
+
let through = after;
|
|
14009
|
+
const contiguous = [];
|
|
14010
|
+
for (const row of records) {
|
|
14011
|
+
if (row.seq <= through) continue;
|
|
14012
|
+
if (row.seq !== through + 1) break;
|
|
14013
|
+
contiguous.push(row);
|
|
14014
|
+
through = row.seq;
|
|
14015
|
+
}
|
|
14016
|
+
if (through === after && page.through > after) throw new Error("history_gap");
|
|
14017
|
+
return { through, availableThrough: page.through, records: contiguous };
|
|
14018
|
+
}
|
|
14019
|
+
watch(hubEpoch, runIds, update, error) {
|
|
14020
|
+
let stopped = false;
|
|
14021
|
+
let socket;
|
|
14022
|
+
let timer;
|
|
14023
|
+
let delay = 250;
|
|
14024
|
+
const runs = /* @__PURE__ */ new Map();
|
|
14025
|
+
const connect = async () => {
|
|
14026
|
+
try {
|
|
14027
|
+
const { ticket } = external_exports.object({ ticket: external_exports.string() }).parse(await this.request("/connect-ticket", { hubEpoch }));
|
|
14028
|
+
if (stopped) return;
|
|
14029
|
+
const endpoint = new URL(
|
|
14030
|
+
`/v2/orgs/${encodeURIComponent(this.orgId)}/connect`,
|
|
14031
|
+
this.baseUrl
|
|
14032
|
+
);
|
|
14033
|
+
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
|
|
14034
|
+
const ws = new WebSocket(endpoint, ["drisp-v2", `ticket.${ticket}`]);
|
|
14035
|
+
socket = ws;
|
|
14036
|
+
ws.onopen = () => ws.send(JSON.stringify({ type: "watch", hubEpoch, runIds }));
|
|
14037
|
+
ws.onmessage = (event) => {
|
|
14038
|
+
try {
|
|
14039
|
+
if (typeof event.data !== "string" || new TextEncoder().encode(event.data).length > 8 * 1024 * 1024)
|
|
14040
|
+
throw new Error("frame_too_large");
|
|
14041
|
+
const message = JSON.parse(String(event.data));
|
|
14042
|
+
if (message.type === "error") throw new Error(message.code);
|
|
14043
|
+
if (message.hubEpoch !== hubEpoch) throw new Error("recovery_required");
|
|
14044
|
+
const header = external_exports.object({ subscriptionId: external_exports.string(), type: external_exports.enum(["snapshot", "change"]) }).parse(message);
|
|
14045
|
+
let through;
|
|
14046
|
+
if (header.type === "snapshot") {
|
|
14047
|
+
const snapshot = external_exports.object({
|
|
14048
|
+
controlThrough: external_exports.number().int().nonnegative(),
|
|
14049
|
+
runs: external_exports.array(runViewSchema)
|
|
14050
|
+
}).parse(message);
|
|
14051
|
+
runs.clear();
|
|
14052
|
+
for (const run of snapshot.runs) runs.set(run.runId, run);
|
|
14053
|
+
through = snapshot.controlThrough;
|
|
14054
|
+
} else {
|
|
14055
|
+
const change = external_exports.object({
|
|
14056
|
+
through: external_exports.number().int().nonnegative(),
|
|
14057
|
+
runs: external_exports.array(external_exports.object({ seq: external_exports.number(), run: runViewSchema }))
|
|
14058
|
+
}).parse(message);
|
|
14059
|
+
for (const { run } of change.runs)
|
|
14060
|
+
if ((runs.get(run.runId)?.recordRevision ?? 0) < run.recordRevision)
|
|
14061
|
+
runs.set(run.runId, run);
|
|
14062
|
+
through = change.through;
|
|
14063
|
+
}
|
|
14064
|
+
update([...runs.values()]);
|
|
14065
|
+
delay = 250;
|
|
14066
|
+
ws.send(
|
|
14067
|
+
JSON.stringify({ type: "watch.ack", subscriptionId: header.subscriptionId, through })
|
|
14068
|
+
);
|
|
14069
|
+
} catch (cause) {
|
|
14070
|
+
if (cause instanceof Error && cause.message === "reset_required") {
|
|
14071
|
+
ws.close();
|
|
14072
|
+
return;
|
|
14073
|
+
}
|
|
14074
|
+
stopped = true;
|
|
14075
|
+
error(cause);
|
|
14076
|
+
ws.close();
|
|
14077
|
+
}
|
|
14078
|
+
};
|
|
14079
|
+
ws.onclose = () => {
|
|
14080
|
+
if (!stopped) {
|
|
14081
|
+
timer = setTimeout(() => {
|
|
14082
|
+
void connect();
|
|
14083
|
+
}, delay);
|
|
14084
|
+
delay = Math.min(delay * 2, 3e4);
|
|
14085
|
+
}
|
|
14086
|
+
};
|
|
14087
|
+
} catch (cause) {
|
|
14088
|
+
if (stopped) return;
|
|
14089
|
+
error(cause);
|
|
14090
|
+
if (cause instanceof ClientError && cause.status < 500 && cause.status !== 429) return;
|
|
14091
|
+
timer = setTimeout(() => {
|
|
14092
|
+
void connect();
|
|
14093
|
+
}, delay);
|
|
14094
|
+
delay = Math.min(delay * 2, 3e4);
|
|
14095
|
+
}
|
|
14096
|
+
};
|
|
14097
|
+
void connect();
|
|
14098
|
+
return () => {
|
|
14099
|
+
stopped = true;
|
|
14100
|
+
if (timer) clearTimeout(timer);
|
|
14101
|
+
socket?.close();
|
|
14102
|
+
};
|
|
14103
|
+
}
|
|
14104
|
+
};
|
|
14105
|
+
|
|
14106
|
+
// src/core/build/state.ts
|
|
14107
|
+
var ATTEMPT_OUTCOMES = [
|
|
14108
|
+
"completed",
|
|
14109
|
+
"failed",
|
|
14110
|
+
"cancelled",
|
|
14111
|
+
"template_mismatch"
|
|
14112
|
+
];
|
|
14113
|
+
var attemptSchema = external_exports.strictObject({
|
|
14114
|
+
n: external_exports.number().int().min(1).max(2),
|
|
14115
|
+
runId: external_exports.string().min(1),
|
|
14116
|
+
tokenCap: external_exports.number().int().positive(),
|
|
14117
|
+
startedAt: external_exports.string(),
|
|
14118
|
+
/** Where the attempt worked (chosen while building slice 2): later ticks check its PR and remove its worktree. */
|
|
14119
|
+
branch: external_exports.string().min(1),
|
|
14120
|
+
worktree: external_exports.string().min(1),
|
|
14121
|
+
outcome: external_exports.enum(ATTEMPT_OUTCOMES).optional()
|
|
14122
|
+
});
|
|
14123
|
+
var buildStateSchema = external_exports.strictObject({
|
|
14124
|
+
version: external_exports.literal(1),
|
|
14125
|
+
orchestratorRunId: external_exports.string().min(1),
|
|
14126
|
+
projectId: external_exports.string().min(1),
|
|
14127
|
+
slugId: external_exports.string().regex(/^[a-z0-9]+$/),
|
|
14128
|
+
templateVersion: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
14129
|
+
approvedAt: external_exports.string(),
|
|
14130
|
+
approved: external_exports.strictObject({ groups: external_exports.array(external_exports.array(external_exports.string())) }),
|
|
14131
|
+
attempts: external_exports.record(external_exports.string(), external_exports.array(attemptSchema)),
|
|
14132
|
+
needsPerson: external_exports.record(external_exports.string(), external_exports.string()),
|
|
14133
|
+
tickCount: external_exports.number().int().nonnegative(),
|
|
14134
|
+
lastTickAt: external_exports.string(),
|
|
14135
|
+
/**
|
|
14136
|
+
* Additive (chosen while building slice 3): the entries a tick had seen done, for rebasing parked children's
|
|
14137
|
+
* branches after a merge. No longer written since slice 4 (a parked child rebases itself), still read so a state
|
|
14138
|
+
* written before parses.
|
|
14139
|
+
*/
|
|
14140
|
+
done: external_exports.array(external_exports.string()).optional(),
|
|
14141
|
+
/** The merged issues the Orchestrator was already asked to repair in Linear, so it is asked once. */
|
|
14142
|
+
repaired: external_exports.array(external_exports.string()).optional()
|
|
14143
|
+
});
|
|
14144
|
+
|
|
14145
|
+
// src/core/build/childStart.ts
|
|
14146
|
+
function childRunId(orchestratorRunId, identifier, attempt) {
|
|
14147
|
+
return `${orchestratorRunId}:${identifier}:a${attempt}`;
|
|
14148
|
+
}
|
|
14149
|
+
function childPrompt(child) {
|
|
14150
|
+
return [
|
|
14151
|
+
`${child.identifier} ${child.title}`,
|
|
14152
|
+
`Linear: ${child.url}`,
|
|
14153
|
+
`Branch: ${child.branch}`,
|
|
14154
|
+
`Worktree: ${child.worktree}`,
|
|
14155
|
+
`Build: ${child.orchestratorRunId}`
|
|
14156
|
+
].join("\n");
|
|
14157
|
+
}
|
|
14158
|
+
function childStartIntent(child, parent) {
|
|
14159
|
+
const runId = childRunId(
|
|
14160
|
+
child.orchestratorRunId,
|
|
14161
|
+
child.identifier,
|
|
14162
|
+
child.attempt
|
|
14163
|
+
);
|
|
14164
|
+
return {
|
|
14165
|
+
runId,
|
|
14166
|
+
intent: {
|
|
14167
|
+
kind: "start",
|
|
14168
|
+
sessionId: runId,
|
|
14169
|
+
runnerId: parent.runnerId,
|
|
14170
|
+
scope: parent.scope,
|
|
14171
|
+
key: `issue:${child.issueId}`,
|
|
14172
|
+
parent: { runId: child.orchestratorRunId, tokenCap: child.tokenCap },
|
|
14173
|
+
spec: {
|
|
14174
|
+
prompt: childPrompt(child),
|
|
14175
|
+
projectDir: child.worktree,
|
|
14176
|
+
harness: "claude-code",
|
|
14177
|
+
workflow: child.workflowName
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
};
|
|
14181
|
+
}
|
|
14182
|
+
|
|
14183
|
+
// src/core/build/tickDecision.ts
|
|
14184
|
+
var MAX_ATTEMPTS = 2;
|
|
14185
|
+
function mayRetry(attempts) {
|
|
14186
|
+
return attempts.length < MAX_ATTEMPTS && attempts.at(-1)?.outcome === "failed";
|
|
14187
|
+
}
|
|
14188
|
+
var STOPPED_CLOSE_REASONS = /* @__PURE__ */ new Set(["cancelled_before_start", "abandoned"]);
|
|
14189
|
+
function outcomeOf(view) {
|
|
14190
|
+
if (view.closeReason === "workflow_template_mismatch")
|
|
14191
|
+
return "template_mismatch";
|
|
14192
|
+
const status = view.execution?.status;
|
|
14193
|
+
if (status === "completed" || status === "failed" || status === "cancelled")
|
|
14194
|
+
return status;
|
|
14195
|
+
if (view.cancellation || STOPPED_CLOSE_REASONS.has(view.closeReason ?? ""))
|
|
14196
|
+
return "cancelled";
|
|
14197
|
+
return "failed";
|
|
14198
|
+
}
|
|
14199
|
+
function buildLimits(own, delegation) {
|
|
14200
|
+
if (!delegation) return own;
|
|
14201
|
+
return {
|
|
14202
|
+
...own,
|
|
14203
|
+
maxChildren: Math.min(own.maxChildren, delegation.maxChildren),
|
|
14204
|
+
tokenBudget: Math.min(own.tokenBudget, delegation.tokenBudget)
|
|
14205
|
+
};
|
|
14206
|
+
}
|
|
14207
|
+
function occupies(view) {
|
|
14208
|
+
return !view || !view.closed && view.execution?.status !== "awaiting_attention";
|
|
14209
|
+
}
|
|
14210
|
+
function assessTick(facts) {
|
|
14211
|
+
const { entries, orchestratorRunId } = facts;
|
|
14212
|
+
const state = structuredClone(facts.state);
|
|
14213
|
+
const views = new Map(facts.children.map((view) => [view.runId, view]));
|
|
14214
|
+
const issues = new Map(facts.issues.map((issue) => [issue.id, issue]));
|
|
14215
|
+
const attemptsOf = (id) => state.attempts[id] ?? [];
|
|
14216
|
+
for (const entry of entries) {
|
|
14217
|
+
const prefix = `${orchestratorRunId}:${entry.identifier}:a`;
|
|
14218
|
+
for (const view of facts.children) {
|
|
14219
|
+
if (!view.runId.startsWith(prefix)) continue;
|
|
14220
|
+
const n = Number(view.runId.slice(prefix.length));
|
|
14221
|
+
if (!(n === 1 || n === 2)) continue;
|
|
14222
|
+
if (attemptsOf(entry.id).some((a) => a.runId === view.runId)) continue;
|
|
14223
|
+
state.attempts[entry.id] = [
|
|
14224
|
+
...attemptsOf(entry.id),
|
|
14225
|
+
{
|
|
14226
|
+
n,
|
|
14227
|
+
runId: view.runId,
|
|
14228
|
+
tokenCap: view.parent?.tokenCap ?? entry.tokenCap,
|
|
14229
|
+
startedAt: facts.now,
|
|
14230
|
+
...facts.locate(entry)
|
|
14231
|
+
}
|
|
14232
|
+
].sort((a, b) => a.n - b.n);
|
|
14233
|
+
}
|
|
14234
|
+
}
|
|
14235
|
+
for (const attempts of Object.values(state.attempts))
|
|
14236
|
+
for (const attempt of attempts) {
|
|
14237
|
+
const view = views.get(attempt.runId);
|
|
14238
|
+
if (view?.closed && !attempt.outcome) attempt.outcome = outcomeOf(view);
|
|
14239
|
+
}
|
|
14240
|
+
const branchOf = (entry) => attemptsOf(entry.id).at(-1)?.branch ?? facts.locate(entry).branch;
|
|
14241
|
+
for (const identifier of facts.rebaseFailed) {
|
|
14242
|
+
const entry = entries.find((each) => each.identifier === identifier);
|
|
14243
|
+
if (entry)
|
|
14244
|
+
state.needsPerson[entry.id] = BUILD_STRINGS.rebaseFailed(branchOf(entry));
|
|
14245
|
+
}
|
|
14246
|
+
const pullRequests = /* @__PURE__ */ new Map();
|
|
14247
|
+
const prsOf = (entry) => {
|
|
14248
|
+
const branch = attemptsOf(entry.id).at(-1)?.branch ?? issues.get(entry.id)?.branchName;
|
|
14249
|
+
if (!branch) return void 0;
|
|
14250
|
+
if (!pullRequests.has(branch))
|
|
14251
|
+
pullRequests.set(branch, facts.pullRequests(branch));
|
|
14252
|
+
return pullRequests.get(branch);
|
|
14253
|
+
};
|
|
14254
|
+
const status = /* @__PURE__ */ new Map();
|
|
14255
|
+
const skippedBlocker = (id) => {
|
|
14256
|
+
const linear = issues.get(id)?.state.type;
|
|
14257
|
+
return linear === "completed" || linear === "canceled" ? "done" : "blocked";
|
|
14258
|
+
};
|
|
14259
|
+
const repair = [];
|
|
14260
|
+
const repaired = new Set(state.repaired ?? []);
|
|
14261
|
+
for (const entry of entries) {
|
|
14262
|
+
const attempts = attemptsOf(entry.id);
|
|
14263
|
+
const newest = attempts.at(-1);
|
|
14264
|
+
const view = newest && views.get(newest.runId);
|
|
14265
|
+
const linear = issues.get(entry.id)?.state.type;
|
|
14266
|
+
const statusOf = () => {
|
|
14267
|
+
if (linear === "completed" || linear === "canceled") return "done";
|
|
14268
|
+
const prs = prsOf(entry);
|
|
14269
|
+
if (prs?.merged) {
|
|
14270
|
+
if (!repaired.has(entry.id) && !(newest && occupies(view))) {
|
|
14271
|
+
const prUrl = prs.mergedUrl ?? branchOf(entry);
|
|
14272
|
+
repair.push({
|
|
14273
|
+
id: entry.id,
|
|
14274
|
+
identifier: entry.identifier,
|
|
14275
|
+
prUrl,
|
|
14276
|
+
comment: BUILD_STRINGS.repairComment(prUrl, orchestratorRunId)
|
|
14277
|
+
});
|
|
14278
|
+
repaired.add(entry.id);
|
|
14279
|
+
}
|
|
14280
|
+
return "done";
|
|
14281
|
+
}
|
|
14282
|
+
if (newest && (!view || !view.closed)) return "live";
|
|
14283
|
+
if (newest?.outcome === "template_mismatch") return "replan";
|
|
14284
|
+
if (newest?.outcome === "completed")
|
|
14285
|
+
state.needsPerson[entry.id] ??= BUILD_STRINGS.completedUnmerged;
|
|
14286
|
+
if (state.needsPerson[entry.id] || newest && !mayRetry(attempts))
|
|
14287
|
+
return "needs_person";
|
|
14288
|
+
const blockers = entry.blockedBy.map(
|
|
14289
|
+
(id) => status.get(id) ?? skippedBlocker(id)
|
|
14290
|
+
);
|
|
14291
|
+
if (blockers.some(
|
|
14292
|
+
(b) => b === "needs_person" || b === "replan" || b === "blocked"
|
|
14293
|
+
))
|
|
14294
|
+
return "blocked";
|
|
14295
|
+
return blockers.every((b) => b === "done") ? "ready" : "pending";
|
|
14296
|
+
};
|
|
14297
|
+
status.set(entry.id, statusOf());
|
|
14298
|
+
if (status.get(entry.id) === "done" && state.needsPerson[entry.id] === BUILD_STRINGS.completedUnmerged)
|
|
14299
|
+
delete state.needsPerson[entry.id];
|
|
14300
|
+
}
|
|
14301
|
+
if (repaired.size > 0) state.repaired = [...repaired];
|
|
14302
|
+
let parkReason = facts.templateVersion !== state.templateVersion ? "template" : state.tickCount >= facts.limits.tickAllowance ? "budget" : void 0;
|
|
14303
|
+
const occupying = entries.filter((entry) => {
|
|
14304
|
+
const newest = attemptsOf(entry.id).at(-1);
|
|
14305
|
+
return newest !== void 0 && occupies(views.get(newest.runId));
|
|
14306
|
+
}).length;
|
|
14307
|
+
const queue = [];
|
|
14308
|
+
if (!parkReason) {
|
|
14309
|
+
let capped = Object.values(state.attempts).flat().reduce((sum, attempt) => sum + attempt.tokenCap, 0);
|
|
14310
|
+
for (const entry of entries) {
|
|
14311
|
+
if (status.get(entry.id) !== "ready") continue;
|
|
14312
|
+
if (capped + entry.tokenCap > facts.limits.tokenBudget) {
|
|
14313
|
+
parkReason = "budget";
|
|
14314
|
+
break;
|
|
14315
|
+
}
|
|
14316
|
+
queue.push(entry);
|
|
14317
|
+
capped += entry.tokenCap;
|
|
14318
|
+
}
|
|
14319
|
+
}
|
|
14320
|
+
return {
|
|
14321
|
+
state,
|
|
14322
|
+
status,
|
|
14323
|
+
repair,
|
|
14324
|
+
queue,
|
|
14325
|
+
slots: Math.max(0, facts.limits.maxChildren - occupying),
|
|
14326
|
+
...parkReason ? { parkReason } : {}
|
|
14327
|
+
};
|
|
14328
|
+
}
|
|
14329
|
+
function tickAction(input) {
|
|
14330
|
+
const live = input.children.filter((view) => !view.closed);
|
|
14331
|
+
if (input.started > 0 || live.some((view) => occupies(view)))
|
|
14332
|
+
return { action: "wait" };
|
|
14333
|
+
const remaining = input.entries.some(
|
|
14334
|
+
(entry) => ["ready", "pending"].includes(input.status.get(entry.id))
|
|
14335
|
+
);
|
|
14336
|
+
if (input.parkReason && remaining)
|
|
14337
|
+
return {
|
|
14338
|
+
action: "park",
|
|
14339
|
+
reason: input.parkReason,
|
|
14340
|
+
message: input.parkReason === "budget" ? BUILD_STRINGS.budgetPark : BUILD_STRINGS.templatePark
|
|
14341
|
+
};
|
|
14342
|
+
if (live.length > 0) return { action: "wait" };
|
|
14343
|
+
const done = input.entries.filter(
|
|
14344
|
+
(entry) => input.status.get(entry.id) === "done"
|
|
14345
|
+
).length;
|
|
14346
|
+
return {
|
|
14347
|
+
action: "complete",
|
|
14348
|
+
message: `Built ${done} of ${input.entries.length}`
|
|
14349
|
+
};
|
|
14350
|
+
}
|
|
14351
|
+
|
|
14352
|
+
// src/core/build/tick.ts
|
|
14353
|
+
var UNAVAILABLE_RETRY_MS = 2e3;
|
|
14354
|
+
var UNAVAILABLE_RETRY_FOR_MS = 3e4;
|
|
14355
|
+
var NEVER = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
14356
|
+
var notStarted = () => ({
|
|
14357
|
+
action: "complete",
|
|
14358
|
+
message: BUILD_STRINGS.notStarted
|
|
14359
|
+
});
|
|
14360
|
+
function pushBranch(worktree, branch) {
|
|
14361
|
+
git(worktree, ["push", "--quiet", "origin", `${branch}:${branch}`]);
|
|
14362
|
+
}
|
|
14363
|
+
async function startChild(deps, hubEpoch, start) {
|
|
14364
|
+
for (let waited = 0; ; waited += UNAVAILABLE_RETRY_MS) {
|
|
14365
|
+
try {
|
|
14366
|
+
await deps.hub.submit({
|
|
14367
|
+
commandId: randomUUID2(),
|
|
14368
|
+
hubEpoch,
|
|
14369
|
+
runId: start.runId,
|
|
14370
|
+
intent: start.intent
|
|
14371
|
+
});
|
|
14372
|
+
return { kind: "started" };
|
|
14373
|
+
} catch (error) {
|
|
14374
|
+
if (!(error instanceof ClientError) || error.status >= 500)
|
|
14375
|
+
return { kind: "unreachable" };
|
|
14376
|
+
if (error.code === "key_busy") return { kind: "key_busy" };
|
|
14377
|
+
if (error.code === "delegation_budget_exhausted") return { kind: "budget" };
|
|
14378
|
+
if (error.code !== "workflow_unavailable")
|
|
14379
|
+
return { kind: "refused", code: error.code };
|
|
14380
|
+
if (waited >= UNAVAILABLE_RETRY_FOR_MS) return { kind: "unavailable" };
|
|
14381
|
+
await deps.sleep(UNAVAILABLE_RETRY_MS);
|
|
14382
|
+
}
|
|
14383
|
+
}
|
|
14384
|
+
}
|
|
14385
|
+
function currentPlan(plan, state) {
|
|
14386
|
+
const { needsPerson: _previous, ...approved } = plan;
|
|
14387
|
+
const entries = plan.groups.flatMap((group) => group.issues);
|
|
14388
|
+
const needsPerson = entries.filter((entry) => state.needsPerson[entry.id]).map((entry) => ({ id: entry.id, reason: state.needsPerson[entry.id] }));
|
|
14389
|
+
return needsPerson.length > 0 ? { ...approved, needsPerson } : approved;
|
|
14390
|
+
}
|
|
14391
|
+
async function runBuildTick(deps) {
|
|
14392
|
+
const orchestratorRunId = requireEnv(deps.env, "DRISP_RUN_ID");
|
|
14393
|
+
const runnerId = requireEnv(deps.env, "DRISP_RUNNER_ID");
|
|
14394
|
+
const artifacts = requireEnv(deps.env, "DRISP_RUN_ARTIFACTS");
|
|
14395
|
+
if (!deps.inputPath)
|
|
14396
|
+
throw new BuildUsageError("Usage: drisp build tick --input <path>");
|
|
14397
|
+
let input;
|
|
14398
|
+
try {
|
|
14399
|
+
input = readInput(path7.resolve(deps.folder, deps.inputPath));
|
|
14400
|
+
} catch (error) {
|
|
14401
|
+
if (error instanceof PlanFailure) throw new BuildUsageError(error.message);
|
|
14402
|
+
throw error;
|
|
14403
|
+
}
|
|
14404
|
+
const worktree = ensureBuildWorktree({
|
|
14405
|
+
folder: deps.folder,
|
|
14406
|
+
slugId: input.project.slugId,
|
|
14407
|
+
worktreesRoot: deps.worktreesRoot
|
|
14408
|
+
});
|
|
14409
|
+
const designDir = path7.join(
|
|
14410
|
+
worktree.path,
|
|
14411
|
+
".athena",
|
|
14412
|
+
"workflows",
|
|
14413
|
+
input.project.slugId
|
|
14414
|
+
);
|
|
14415
|
+
const relativeDesignDir = path7.relative(worktree.path, designDir);
|
|
14416
|
+
const statePath = path7.join(designDir, "build-state.json");
|
|
14417
|
+
const planPath = path7.join(designDir, "plan.json");
|
|
14418
|
+
const parsedPlan = buildPlanSchema.safeParse(readJson(planPath));
|
|
14419
|
+
if (!parsedPlan.success || parsedPlan.data.orchestratorRunId !== orchestratorRunId)
|
|
14420
|
+
return notStarted();
|
|
14421
|
+
const plan = parsedPlan.data;
|
|
14422
|
+
const push = deps.push ?? pushBranch;
|
|
14423
|
+
let stored;
|
|
14424
|
+
const parsedState = buildStateSchema.safeParse(readJson(statePath));
|
|
14425
|
+
if (parsedState.success && parsedState.data.orchestratorRunId === orchestratorRunId) {
|
|
14426
|
+
stored = parsedState.data;
|
|
14427
|
+
} else {
|
|
14428
|
+
if (!isStartAnswer(readJson(path7.join(artifacts, "answer.json"))))
|
|
14429
|
+
return notStarted();
|
|
14430
|
+
stored = {
|
|
14431
|
+
version: 1,
|
|
14432
|
+
orchestratorRunId,
|
|
14433
|
+
projectId: plan.projectId,
|
|
14434
|
+
slugId: input.project.slugId,
|
|
14435
|
+
templateVersion: plan.templateVersion,
|
|
14436
|
+
approvedAt: deps.now().toISOString(),
|
|
14437
|
+
approved: {
|
|
14438
|
+
groups: plan.groups.map((group) => group.issues.map((issue) => issue.id))
|
|
14439
|
+
},
|
|
14440
|
+
attempts: {},
|
|
14441
|
+
needsPerson: {},
|
|
14442
|
+
tickCount: 0,
|
|
14443
|
+
lastTickAt: NEVER
|
|
14444
|
+
};
|
|
14445
|
+
push(worktree.path, worktree.branch);
|
|
14446
|
+
}
|
|
14447
|
+
if (!(Date.parse(input.readAt) > Date.parse(stored.lastTickAt)))
|
|
14448
|
+
return { action: "refresh_input" };
|
|
14449
|
+
stored.tickCount += 1;
|
|
14450
|
+
stored.lastTickAt = deps.now().toISOString();
|
|
14451
|
+
const parent = await deps.hub.run(orchestratorRunId);
|
|
14452
|
+
const children = await deps.hub.runs({ parent: orchestratorRunId });
|
|
14453
|
+
const approved = new Set(stored.approved.groups.flat());
|
|
14454
|
+
const entries = plan.groups.flatMap((group) => group.issues).filter((entry) => approved.has(entry.id));
|
|
14455
|
+
const issues = new Map(input.issues.map((issue) => [issue.id, issue]));
|
|
14456
|
+
const assessment = assessTick({
|
|
14457
|
+
orchestratorRunId,
|
|
14458
|
+
entries,
|
|
14459
|
+
issues: input.issues,
|
|
14460
|
+
state: stored,
|
|
14461
|
+
children: children.runs,
|
|
14462
|
+
templateVersion,
|
|
14463
|
+
limits: buildLimits(ORCHESTRATOR_LIMITS, parent.delegation),
|
|
14464
|
+
pullRequests: (branch) => deps.pullRequests(deps.folder, branch),
|
|
14465
|
+
locate: (entry) => locate(deps, entry, issues.get(entry.id)?.branchName),
|
|
14466
|
+
rebaseFailed: deps.rebaseFailed ?? [],
|
|
14467
|
+
now: deps.now().toISOString()
|
|
14468
|
+
});
|
|
14469
|
+
const { state, status } = assessment;
|
|
14470
|
+
let parkReason = assessment.parkReason;
|
|
14471
|
+
const record = () => {
|
|
14472
|
+
writeFile(statePath, json(state));
|
|
14473
|
+
const files = [path7.join(relativeDesignDir, "build-state.json")];
|
|
14474
|
+
const emitted = json(currentPlan(plan, state));
|
|
14475
|
+
if (emitted !== fs7.readFileSync(planPath, "utf-8")) {
|
|
14476
|
+
writeFile(planPath, emitted);
|
|
14477
|
+
writeFile(path7.join(artifacts, "build_plan.json"), emitted);
|
|
14478
|
+
files.push(path7.join(relativeDesignDir, "plan.json"));
|
|
14479
|
+
}
|
|
14480
|
+
if (commitBuildFiles(
|
|
14481
|
+
worktree.path,
|
|
14482
|
+
files,
|
|
14483
|
+
`drisp build: tick ${state.tickCount} for ${state.slugId}`
|
|
14484
|
+
))
|
|
14485
|
+
push(worktree.path, worktree.branch);
|
|
14486
|
+
};
|
|
14487
|
+
if (assessment.repair.length > 0) {
|
|
14488
|
+
record();
|
|
14489
|
+
return { action: "repair", issues: assessment.repair };
|
|
14490
|
+
}
|
|
14491
|
+
const deferred = [];
|
|
14492
|
+
const behind = [];
|
|
14493
|
+
let started = 0;
|
|
14494
|
+
if (assessment.queue.length > 0 && assessment.slots > 0) {
|
|
14495
|
+
for (const entry of assessment.queue) {
|
|
14496
|
+
if (started >= assessment.slots) break;
|
|
14497
|
+
const issue = issues.get(entry.id);
|
|
14498
|
+
if (!issue) continue;
|
|
14499
|
+
const child = ensureChildWorktree({
|
|
14500
|
+
folder: deps.folder,
|
|
14501
|
+
worktreesRoot: deps.worktreesRoot,
|
|
14502
|
+
issue
|
|
14503
|
+
});
|
|
14504
|
+
if (child.status === "needs_person") {
|
|
14505
|
+
state.needsPerson[entry.id] = child.reason;
|
|
14506
|
+
status.set(entry.id, "needs_person");
|
|
14507
|
+
continue;
|
|
14508
|
+
}
|
|
14509
|
+
if (child.existed && !hasUncommittedWork(child.path) && behindMain(deps.folder, child.path)) {
|
|
14510
|
+
behind.push({
|
|
14511
|
+
identifier: entry.identifier,
|
|
14512
|
+
worktree: child.path,
|
|
14513
|
+
branch: child.branch
|
|
14514
|
+
});
|
|
14515
|
+
continue;
|
|
14516
|
+
}
|
|
14517
|
+
const entryDir = path7.join(relativeDesignDir, entry.identifier);
|
|
14518
|
+
git(worktree.path, ["checkout", plan.commit, "--", entryDir]);
|
|
14519
|
+
installWorkflowFromSource(
|
|
14520
|
+
{
|
|
14521
|
+
kind: "filesystem",
|
|
14522
|
+
workflowPath: path7.join(worktree.path, entryDir, "workflow.json"),
|
|
14523
|
+
version: plan.commit
|
|
14524
|
+
},
|
|
14525
|
+
entry.workflowName
|
|
14526
|
+
);
|
|
14527
|
+
const attempt = (state.attempts[entry.id]?.length ?? 0) + 1;
|
|
14528
|
+
const start = childStartIntent(
|
|
14529
|
+
{
|
|
14530
|
+
orchestratorRunId,
|
|
14531
|
+
identifier: entry.identifier,
|
|
14532
|
+
issueId: entry.id,
|
|
14533
|
+
title: issue.title,
|
|
14534
|
+
url: issue.url,
|
|
14535
|
+
branch: child.branch,
|
|
14536
|
+
worktree: child.path,
|
|
14537
|
+
workflowName: entry.workflowName,
|
|
14538
|
+
tokenCap: entry.tokenCap,
|
|
14539
|
+
attempt
|
|
14540
|
+
},
|
|
14541
|
+
{ runnerId, scope: parent.scope }
|
|
14542
|
+
);
|
|
14543
|
+
const result = await startChild(deps, children.hubEpoch, start);
|
|
14544
|
+
if (result.kind === "started") {
|
|
14545
|
+
(state.attempts[entry.id] ??= []).push({
|
|
14546
|
+
n: attempt,
|
|
14547
|
+
runId: start.runId,
|
|
14548
|
+
tokenCap: entry.tokenCap,
|
|
14549
|
+
startedAt: deps.now().toISOString(),
|
|
14550
|
+
branch: child.branch,
|
|
14551
|
+
worktree: child.path
|
|
14552
|
+
});
|
|
14553
|
+
status.set(entry.id, "live");
|
|
14554
|
+
started += 1;
|
|
14555
|
+
} else if (result.kind === "key_busy")
|
|
14556
|
+
deferred.push({ entry, reason: BUILD_STRINGS.keyBusy(entry.identifier) });
|
|
14557
|
+
else if (result.kind === "unavailable")
|
|
14558
|
+
deferred.push({
|
|
14559
|
+
entry,
|
|
14560
|
+
reason: BUILD_STRINGS.workflowUnavailable(entry.identifier)
|
|
14561
|
+
});
|
|
14562
|
+
else if (result.kind === "unreachable")
|
|
14563
|
+
deferred.push({
|
|
14564
|
+
entry,
|
|
14565
|
+
reason: BUILD_STRINGS.hubUnreachable(entry.identifier)
|
|
14566
|
+
});
|
|
14567
|
+
else if (result.kind === "budget") {
|
|
14568
|
+
parkReason = "budget";
|
|
14569
|
+
break;
|
|
14570
|
+
} else {
|
|
14571
|
+
state.needsPerson[entry.id] = BUILD_STRINGS.startRefused(result.code);
|
|
14572
|
+
status.set(entry.id, "needs_person");
|
|
14573
|
+
}
|
|
14574
|
+
}
|
|
14575
|
+
}
|
|
14576
|
+
const anyLive = started > 0 || children.runs.some((view) => !view.closed) || behind.length > 0;
|
|
14577
|
+
if (!anyLive)
|
|
14578
|
+
for (const { entry, reason } of deferred) {
|
|
14579
|
+
state.needsPerson[entry.id] = reason;
|
|
14580
|
+
status.set(entry.id, "needs_person");
|
|
14581
|
+
}
|
|
14582
|
+
const views = new Map(children.runs.map((view) => [view.runId, view]));
|
|
14583
|
+
for (const entry of entries) {
|
|
14584
|
+
const newest = state.attempts[entry.id]?.at(-1);
|
|
14585
|
+
if (!newest || status.get(entry.id) !== "done") continue;
|
|
14586
|
+
if (!views.get(newest.runId)?.closed) continue;
|
|
14587
|
+
const installed = fs7.existsSync(
|
|
14588
|
+
path7.join(workflowRegistryDir(), entry.workflowName)
|
|
14589
|
+
);
|
|
14590
|
+
if (!installed && !fs7.existsSync(newest.worktree)) continue;
|
|
14591
|
+
if (!deps.pullRequests(deps.folder, newest.branch).merged) continue;
|
|
14592
|
+
if (fs7.existsSync(newest.worktree)) {
|
|
14593
|
+
try {
|
|
14594
|
+
git(deps.folder, ["worktree", "remove", newest.worktree]);
|
|
14595
|
+
} catch {
|
|
14596
|
+
}
|
|
14597
|
+
}
|
|
14598
|
+
if (installed) removeWorkflow(entry.workflowName);
|
|
14599
|
+
}
|
|
14600
|
+
record();
|
|
14601
|
+
if (behind.length > 0) return { action: "rebase", items: behind };
|
|
14602
|
+
return tickAction({
|
|
14603
|
+
entries,
|
|
14604
|
+
status,
|
|
14605
|
+
children: children.runs,
|
|
14606
|
+
started,
|
|
14607
|
+
...parkReason ? { parkReason } : {}
|
|
14608
|
+
});
|
|
14609
|
+
}
|
|
14610
|
+
function locate(deps, entry, branchName) {
|
|
14611
|
+
const worktree = childWorktreePath(
|
|
14612
|
+
deps.worktreesRoot,
|
|
14613
|
+
deps.folder,
|
|
14614
|
+
entry.identifier
|
|
14615
|
+
);
|
|
14616
|
+
if (fs7.existsSync(path7.join(worktree, ".git")))
|
|
14617
|
+
return {
|
|
14618
|
+
worktree,
|
|
14619
|
+
branch: git(worktree, ["rev-parse", "--abbrev-ref", "HEAD"])
|
|
14620
|
+
};
|
|
14621
|
+
return {
|
|
14622
|
+
worktree,
|
|
14623
|
+
branch: branchName ?? fallbackBranch(entry.identifier, entry.title)
|
|
14624
|
+
};
|
|
14625
|
+
}
|
|
14626
|
+
|
|
14627
|
+
// src/core/build/mergeGate.ts
|
|
14628
|
+
var PASSED = /* @__PURE__ */ new Set([
|
|
14629
|
+
"success",
|
|
14630
|
+
"skipped",
|
|
14631
|
+
"neutral"
|
|
14632
|
+
]);
|
|
14633
|
+
function zeroStepFailure(run) {
|
|
14634
|
+
const failedJobs = run.jobs.filter((job) => !PASSED.has(job.conclusion));
|
|
14635
|
+
return failedJobs.length > 0 && failedJobs.every((job) => Array.isArray(job.steps) && job.steps.length === 0);
|
|
14636
|
+
}
|
|
14637
|
+
function evaluateRemoteChecks(runs) {
|
|
14638
|
+
const verdict = {
|
|
14639
|
+
ok: true,
|
|
14640
|
+
failed: [],
|
|
14641
|
+
ignored: [],
|
|
14642
|
+
pending: []
|
|
14643
|
+
};
|
|
14644
|
+
for (const run of runs) {
|
|
14645
|
+
if (run.status !== "completed") verdict.pending.push(run.name);
|
|
14646
|
+
else if (PASSED.has(run.conclusion)) continue;
|
|
14647
|
+
else if (zeroStepFailure(run)) verdict.ignored.push(run.name);
|
|
14648
|
+
else verdict.failed.push(run.name);
|
|
14649
|
+
}
|
|
14650
|
+
verdict.ok = verdict.failed.length === 0 && verdict.pending.length === 0;
|
|
14651
|
+
return verdict;
|
|
14652
|
+
}
|
|
14653
|
+
|
|
14654
|
+
// src/app/entry/buildCommand.ts
|
|
14655
|
+
var USAGE3 = `Usage: drisp build plan --input <path>
|
|
14656
|
+
drisp build tick --input <path> [--rebase-failed <IDENTIFIER>]...
|
|
14657
|
+
drisp build checks
|
|
14658
|
+
plan and tick run inside an Orchestrator Run (DRISP_RUN_ID, DRISP_RUNNER_ID and DRISP_RUN_ARTIFACTS set; tick also
|
|
14659
|
+
DRISP_RUN_TOKEN and DRISP_HUB_URL). DRISP_BUILD_PLAN_INPUT=<path> stands in for plan's --input, with
|
|
14660
|
+
fills/<IDENTIFIER>.json beside it. checks runs in a child's worktree: the merge gate's verdict on HEAD's GitHub runs.`;
|
|
14661
|
+
function githubPullRequests(folder, branch) {
|
|
14662
|
+
const output = execFileSync(
|
|
14663
|
+
"gh",
|
|
14664
|
+
[
|
|
14665
|
+
"pr",
|
|
14666
|
+
"list",
|
|
14667
|
+
"--head",
|
|
14668
|
+
branch,
|
|
14669
|
+
"--state",
|
|
14670
|
+
"all",
|
|
14671
|
+
"--json",
|
|
14672
|
+
"state,url",
|
|
14673
|
+
"--limit",
|
|
14674
|
+
"50"
|
|
14675
|
+
],
|
|
14676
|
+
{ cwd: folder, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
14677
|
+
);
|
|
14678
|
+
return parsePullRequests(output);
|
|
14679
|
+
}
|
|
14680
|
+
function parsePullRequests(output) {
|
|
14681
|
+
const prs = JSON.parse(output);
|
|
14682
|
+
const merged = prs.find((pr) => pr.state === "MERGED");
|
|
14683
|
+
return {
|
|
14684
|
+
open: prs.some((pr) => pr.state === "OPEN"),
|
|
14685
|
+
merged: merged !== void 0,
|
|
14686
|
+
...merged ? { mergedUrl: merged.url } : {}
|
|
14687
|
+
};
|
|
14688
|
+
}
|
|
14689
|
+
function hostCapabilities(folder) {
|
|
14690
|
+
const effective = resolveEffectiveCapabilities({
|
|
14691
|
+
globalConfig: readGlobalConfig(),
|
|
14692
|
+
projectConfig: readConfig(folder)
|
|
14693
|
+
});
|
|
14694
|
+
return {
|
|
14695
|
+
skills: effective.skills.map((skill) => skill.name),
|
|
14696
|
+
mcpServers: effective.mcpServers.map((server) => server.name)
|
|
14697
|
+
};
|
|
14698
|
+
}
|
|
14699
|
+
var defaultChecks = {
|
|
14700
|
+
head: (cwd) => git(cwd, ["rev-parse", "HEAD"]),
|
|
14701
|
+
gh: (args, cwd) => execFileSync("gh", args, {
|
|
14702
|
+
cwd,
|
|
14703
|
+
encoding: "utf8",
|
|
14704
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
14705
|
+
})
|
|
14706
|
+
};
|
|
14707
|
+
function runBuildChecks(cwd, deps) {
|
|
14708
|
+
const head = deps.head(cwd);
|
|
14709
|
+
const runs = JSON.parse(
|
|
14710
|
+
deps.gh(
|
|
14711
|
+
[
|
|
14712
|
+
"run",
|
|
14713
|
+
"list",
|
|
14714
|
+
"--commit",
|
|
14715
|
+
head,
|
|
14716
|
+
"--json",
|
|
14717
|
+
"databaseId,name,status,conclusion",
|
|
14718
|
+
"--limit",
|
|
14719
|
+
"100"
|
|
14720
|
+
],
|
|
14721
|
+
cwd
|
|
14722
|
+
)
|
|
14723
|
+
);
|
|
14724
|
+
const withJobs = runs.map((run) => ({
|
|
14725
|
+
name: run.name,
|
|
14726
|
+
status: run.status,
|
|
14727
|
+
conclusion: run.conclusion,
|
|
14728
|
+
jobs: run.status === "completed" && !PASSED.has(run.conclusion) ? JSON.parse(
|
|
14729
|
+
deps.gh(
|
|
14730
|
+
["run", "view", String(run.databaseId), "--json", "jobs"],
|
|
14731
|
+
cwd
|
|
14732
|
+
)
|
|
14733
|
+
).jobs : []
|
|
14734
|
+
}));
|
|
14735
|
+
return evaluateRemoteChecks(withJobs);
|
|
14736
|
+
}
|
|
14737
|
+
async function runBuildCommand(input) {
|
|
14738
|
+
const stdout = input.stdout ?? process.stdout;
|
|
14739
|
+
const stderr = input.stderr ?? process.stderr;
|
|
14740
|
+
const env2 = input.env ?? process.env;
|
|
14741
|
+
try {
|
|
14742
|
+
let output;
|
|
14743
|
+
switch (input.subcommand) {
|
|
14744
|
+
case "plan":
|
|
14745
|
+
output = runBuildPlan({
|
|
14746
|
+
env: env2,
|
|
14747
|
+
folder: input.cwd,
|
|
14748
|
+
...input.flags.input ? { inputPath: input.flags.input } : {},
|
|
14749
|
+
worktreesRoot: defaultWorktreesRoot(),
|
|
14750
|
+
hostCapabilities: () => hostCapabilities(input.cwd),
|
|
14751
|
+
pullRequests: githubPullRequests,
|
|
14752
|
+
...input.dependencies
|
|
14753
|
+
});
|
|
14754
|
+
break;
|
|
14755
|
+
case "tick": {
|
|
14756
|
+
requireEnv(env2, "DRISP_RUN_ID");
|
|
14757
|
+
requireEnv(env2, "DRISP_RUNNER_ID");
|
|
14758
|
+
requireEnv(env2, "DRISP_RUN_ARTIFACTS");
|
|
14759
|
+
const token = requireEnv(env2, "DRISP_RUN_TOKEN");
|
|
14760
|
+
const hubUrl = requireEnv(env2, "DRISP_HUB_URL");
|
|
14761
|
+
output = await runBuildTick({
|
|
14762
|
+
env: env2,
|
|
14763
|
+
folder: input.cwd,
|
|
14764
|
+
...input.flags.input ? { inputPath: input.flags.input } : {},
|
|
14765
|
+
...input.flags.rebaseFailed?.length ? { rebaseFailed: input.flags.rebaseFailed } : {},
|
|
14766
|
+
worktreesRoot: defaultWorktreesRoot(),
|
|
14767
|
+
hub: DrispClient.delegated(hubUrl, token),
|
|
14768
|
+
pullRequests: githubPullRequests,
|
|
14769
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
14770
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
14771
|
+
...input.tick
|
|
14772
|
+
});
|
|
14773
|
+
break;
|
|
14774
|
+
}
|
|
14775
|
+
case "checks":
|
|
14776
|
+
output = runBuildChecks(input.cwd, input.checks ?? defaultChecks);
|
|
14777
|
+
break;
|
|
14778
|
+
default:
|
|
14779
|
+
stderr.write(`${USAGE3}
|
|
14780
|
+
`);
|
|
14781
|
+
return 2;
|
|
14782
|
+
}
|
|
14783
|
+
stdout.write(`${JSON.stringify(output)}
|
|
14784
|
+
`);
|
|
14785
|
+
return 0;
|
|
14786
|
+
} catch (error) {
|
|
14787
|
+
if (!(error instanceof BuildUsageError)) throw error;
|
|
14788
|
+
stderr.write(`${error.message}
|
|
14789
|
+
${USAGE3}
|
|
14790
|
+
`);
|
|
14791
|
+
return 2;
|
|
14792
|
+
}
|
|
14793
|
+
}
|
|
14794
|
+
|
|
13516
14795
|
// src/app/entry/doctorCommand.ts
|
|
13517
14796
|
import process7 from "process";
|
|
13518
14797
|
|
|
13519
14798
|
// src/harnesses/claude/system/doctorEnvironment.ts
|
|
13520
|
-
import { execFileSync } from "child_process";
|
|
13521
|
-
import * as
|
|
14799
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
14800
|
+
import * as fs8 from "fs";
|
|
13522
14801
|
import * as os4 from "os";
|
|
13523
|
-
import * as
|
|
14802
|
+
import * as path8 from "path";
|
|
13524
14803
|
var MANAGED_POLICY_KEYS = [
|
|
13525
14804
|
"forceLoginMethod",
|
|
13526
14805
|
"forceLoginOrgUUID",
|
|
@@ -13595,7 +14874,7 @@ function readEnforcement(scopes) {
|
|
|
13595
14874
|
}
|
|
13596
14875
|
function tryRichAuth(claudeBinary) {
|
|
13597
14876
|
try {
|
|
13598
|
-
const output =
|
|
14877
|
+
const output = execFileSync2(claudeBinary, ["auth", "status"], {
|
|
13599
14878
|
timeout: 1e4,
|
|
13600
14879
|
encoding: "utf-8",
|
|
13601
14880
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -13624,8 +14903,8 @@ function collectEnvironment(options = {}) {
|
|
|
13624
14903
|
const detectVersion = options.detectClaudeVersionFn ?? detectClaudeVersion;
|
|
13625
14904
|
const runAuthStatus = options.runClaudeAuthStatusFn ?? runClaudeAuthStatus;
|
|
13626
14905
|
const resolveForwarder = options.resolveHookForwarderCommandFn ?? resolveHookForwarderCommand;
|
|
13627
|
-
const statFn = options.statFn ?? ((p) =>
|
|
13628
|
-
const readFileFn = options.readFileFn ?? ((p) =>
|
|
14906
|
+
const statFn = options.statFn ?? ((p) => fs8.statSync(p));
|
|
14907
|
+
const readFileFn = options.readFileFn ?? ((p) => fs8.readFileSync(p, "utf8"));
|
|
13629
14908
|
const settingsPaths = resolveClaudeSettingsSurfacePaths(
|
|
13630
14909
|
homeDir,
|
|
13631
14910
|
cwd,
|
|
@@ -13670,7 +14949,7 @@ function collectEnvironment(options = {}) {
|
|
|
13670
14949
|
});
|
|
13671
14950
|
let globalConfigPresent = false;
|
|
13672
14951
|
try {
|
|
13673
|
-
statFn(
|
|
14952
|
+
statFn(path8.join(homeDir, ".claude.json"));
|
|
13674
14953
|
globalConfigPresent = true;
|
|
13675
14954
|
} catch {
|
|
13676
14955
|
globalConfigPresent = false;
|
|
@@ -14150,13 +15429,13 @@ function runRunsCommand(input) {
|
|
|
14150
15429
|
// src/app/entry/cli.tsx
|
|
14151
15430
|
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
14152
15431
|
function resolvePackageJsonPath(entryUrl) {
|
|
14153
|
-
let currentDir =
|
|
15432
|
+
let currentDir = path9.dirname(fileURLToPath(entryUrl));
|
|
14154
15433
|
for (; ; ) {
|
|
14155
|
-
const candidatePath =
|
|
14156
|
-
if (
|
|
15434
|
+
const candidatePath = path9.join(currentDir, "package.json");
|
|
15435
|
+
if (fs9.existsSync(candidatePath)) {
|
|
14157
15436
|
return candidatePath;
|
|
14158
15437
|
}
|
|
14159
|
-
const parentDir =
|
|
15438
|
+
const parentDir = path9.dirname(currentDir);
|
|
14160
15439
|
if (parentDir === currentDir) {
|
|
14161
15440
|
throw new Error("Could not locate package.json for CLI metadata.");
|
|
14162
15441
|
}
|
|
@@ -14178,6 +15457,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
14178
15457
|
"skill",
|
|
14179
15458
|
"marketplace",
|
|
14180
15459
|
"runner",
|
|
15460
|
+
"build",
|
|
14181
15461
|
// Deprecated alias of `runner` (#188); removed in 0.7.0.
|
|
14182
15462
|
"dashboard",
|
|
14183
15463
|
"telemetry",
|
|
@@ -14324,6 +15604,9 @@ var cli = meow(
|
|
|
14324
15604
|
marketplace <sub> Manage marketplace sources (add, refresh, remove, list)
|
|
14325
15605
|
runner [sub] Run the runner that connects this machine to a Team and executes its Runs
|
|
14326
15606
|
(no subcommand: foreground; connect [code] --hub <url> [--replace], disconnect [team], status, recover, help)
|
|
15607
|
+
build plan --input <path> Plan a Build inside an Orchestrator Run (prints one JSON object)
|
|
15608
|
+
build tick --input <path> Run one step of a started Build (prints the next action; [--rebase-failed <ID>]...)
|
|
15609
|
+
build checks The merge gate's verdict on HEAD's GitHub runs (in a child's worktree)
|
|
14327
15610
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
14328
15611
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
14329
15612
|
|
|
@@ -14448,6 +15731,13 @@ var cli = meow(
|
|
|
14448
15731
|
hub: {
|
|
14449
15732
|
type: "string"
|
|
14450
15733
|
},
|
|
15734
|
+
input: {
|
|
15735
|
+
type: "string"
|
|
15736
|
+
},
|
|
15737
|
+
rebaseFailed: {
|
|
15738
|
+
type: "string",
|
|
15739
|
+
isMultiple: true
|
|
15740
|
+
},
|
|
14451
15741
|
foreground: {
|
|
14452
15742
|
type: "boolean",
|
|
14453
15743
|
default: false
|
|
@@ -14487,7 +15777,7 @@ var cli = meow(
|
|
|
14487
15777
|
}
|
|
14488
15778
|
);
|
|
14489
15779
|
async function main() {
|
|
14490
|
-
const projectDir =
|
|
15780
|
+
const projectDir = path9.resolve(cli.flags.projectDir);
|
|
14491
15781
|
const [command, ...commandArgs] = cli.input;
|
|
14492
15782
|
const isRunCommand = command === "run" || command === "exec";
|
|
14493
15783
|
if (command && !KNOWN_COMMANDS.has(command)) {
|
|
@@ -14537,7 +15827,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14537
15827
|
await exitWith(1);
|
|
14538
15828
|
return;
|
|
14539
15829
|
}
|
|
14540
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
15830
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-NZFBSBLY.js");
|
|
14541
15831
|
const { waitUntilExit } = render(
|
|
14542
15832
|
/* @__PURE__ */ jsx24(
|
|
14543
15833
|
WorkflowInstallWizard,
|
|
@@ -14614,6 +15904,20 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14614
15904
|
);
|
|
14615
15905
|
return;
|
|
14616
15906
|
}
|
|
15907
|
+
if (command === "build") {
|
|
15908
|
+
const [subcommand = ""] = commandArgs;
|
|
15909
|
+
await exitWith(
|
|
15910
|
+
await runBuildCommand({
|
|
15911
|
+
subcommand,
|
|
15912
|
+
flags: {
|
|
15913
|
+
...typeof cli.flags.input === "string" ? { input: cli.flags.input } : {},
|
|
15914
|
+
...cli.flags.rebaseFailed?.length ? { rebaseFailed: cli.flags.rebaseFailed } : {}
|
|
15915
|
+
},
|
|
15916
|
+
cwd: process.cwd()
|
|
15917
|
+
})
|
|
15918
|
+
);
|
|
15919
|
+
return;
|
|
15920
|
+
}
|
|
14617
15921
|
if (command === "runs") {
|
|
14618
15922
|
await exitWith(
|
|
14619
15923
|
runRunsCommand({
|
|
@@ -14687,8 +15991,8 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14687
15991
|
const showSetup = isRunCommand ? false : shouldShowSetup({
|
|
14688
15992
|
cliInput: cli.input,
|
|
14689
15993
|
setupComplete: globalConfig.setupComplete,
|
|
14690
|
-
globalConfigExists:
|
|
14691
|
-
|
|
15994
|
+
globalConfigExists: fs9.existsSync(
|
|
15995
|
+
path9.join(os5.homedir(), ".config", "athena", "config.json")
|
|
14692
15996
|
)
|
|
14693
15997
|
});
|
|
14694
15998
|
let runtimeConfig;
|