@drisp/cli 0.6.8 → 0.6.10
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 +2 -1
- package/dist/{WorkflowInstallWizard-TQ4SF5KB.js → WorkflowInstallWizard-NZFBSBLY.js} +2 -2
- package/dist/chunk-BTHLAJV7.js +21883 -0
- package/dist/{chunk-MP5EWDIQ.js → chunk-LRMKGH2R.js} +2 -2
- package/dist/{chunk-HN3WNQVY.js → chunk-YUVMVYUF.js} +23174 -39331
- package/dist/cli.js +1442 -75
- 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
|
@@ -7,17 +7,25 @@ import {
|
|
|
7
7
|
IndexedTimeline,
|
|
8
8
|
PORTABLE_PROVIDER_ENV_VARS,
|
|
9
9
|
RUN_EXIT_CODE,
|
|
10
|
+
ServiceNotEnabledError,
|
|
10
11
|
attachRuntimeEventLoop,
|
|
12
|
+
behindMain,
|
|
11
13
|
bootstrapRuntimeConfig,
|
|
12
14
|
buildApiKeyHelperSettings,
|
|
13
15
|
buildPhaseFeedEvent,
|
|
16
|
+
buildPlanSchema,
|
|
14
17
|
buildPostByToolUseId,
|
|
15
18
|
buildProbeConfigs,
|
|
16
19
|
centerAnsi,
|
|
20
|
+
childWorktreePath,
|
|
17
21
|
classifyFailure,
|
|
22
|
+
commandSchema,
|
|
23
|
+
commitBuildFiles,
|
|
18
24
|
compactText,
|
|
19
25
|
computeInputRows,
|
|
20
26
|
connectAlreadyConnectedSchema,
|
|
27
|
+
connectCodeResponseSchema,
|
|
28
|
+
connectPendingResponseSchema,
|
|
21
29
|
connectRedeemResponseSchema,
|
|
22
30
|
connectStartResponseSchema,
|
|
23
31
|
createFeedMapper,
|
|
@@ -26,10 +34,15 @@ import {
|
|
|
26
34
|
createSessionStore,
|
|
27
35
|
credentialHelperKey,
|
|
28
36
|
cursorToVisualPosition,
|
|
37
|
+
defaultWorktreesRoot,
|
|
29
38
|
detectClaudeVersion,
|
|
39
|
+
ensureBuildWorktree,
|
|
40
|
+
ensureChildWorktree,
|
|
30
41
|
expandHome,
|
|
31
42
|
extractFriendlyServerName,
|
|
32
43
|
extractPermissionSnapshot,
|
|
44
|
+
fallbackBranch,
|
|
45
|
+
finalizePlan,
|
|
33
46
|
fit,
|
|
34
47
|
fitAnsi,
|
|
35
48
|
forgetRunner,
|
|
@@ -46,12 +59,18 @@ import {
|
|
|
46
59
|
getLatestRunForSession,
|
|
47
60
|
getMostRecentAthenaSession,
|
|
48
61
|
getSessionMeta,
|
|
62
|
+
git,
|
|
63
|
+
hasUncommittedWork,
|
|
49
64
|
hintGlyphs,
|
|
65
|
+
historyRecordSchema,
|
|
50
66
|
hubHttpUrl,
|
|
51
67
|
identityHubOrigin,
|
|
52
68
|
isBashToolResponse,
|
|
53
69
|
isCommandPrefix,
|
|
70
|
+
isGitHubRepository,
|
|
54
71
|
isScopedPermissionsRequest,
|
|
72
|
+
isStartAnswer,
|
|
73
|
+
lastCommitTouching,
|
|
55
74
|
listAwaitingAttentionRuns,
|
|
56
75
|
listHarnessAdapters,
|
|
57
76
|
listHarnessCapabilities,
|
|
@@ -66,6 +85,8 @@ import {
|
|
|
66
85
|
overriddenIdentityPath,
|
|
67
86
|
parseFrontmatter,
|
|
68
87
|
parseToolName,
|
|
88
|
+
planBuild,
|
|
89
|
+
planInputSchema,
|
|
69
90
|
platformRunnerServices,
|
|
70
91
|
probeSkipReason,
|
|
71
92
|
processRegistry,
|
|
@@ -74,6 +95,8 @@ import {
|
|
|
74
95
|
registerCleanupOnExit,
|
|
75
96
|
releaseMcpAsset,
|
|
76
97
|
renderInputLines,
|
|
98
|
+
repoLayoutAt,
|
|
99
|
+
repositoryBindingSchema,
|
|
77
100
|
require_string_width,
|
|
78
101
|
resolveClaudeBinary,
|
|
79
102
|
resolveClaudeSettingsSurfacePaths,
|
|
@@ -88,12 +111,18 @@ import {
|
|
|
88
111
|
runExec,
|
|
89
112
|
runMachineRunners,
|
|
90
113
|
runProbe,
|
|
114
|
+
runViewSchema,
|
|
115
|
+
runnerAdoptRequestSchema,
|
|
91
116
|
runnerConfigDirectory,
|
|
92
117
|
runnerConfigSchema,
|
|
118
|
+
runnerDisconnectPreviewSchema,
|
|
93
119
|
runnerDisconnectResponseSchema,
|
|
94
120
|
runnerIdentities,
|
|
121
|
+
runnerListEntrySchema,
|
|
122
|
+
runnerListSchema,
|
|
95
123
|
runnerServiceUnit,
|
|
96
124
|
runnerSoftwareVersion,
|
|
125
|
+
runnerUpdateRequestSchema,
|
|
97
126
|
serviceInstallationId,
|
|
98
127
|
sessionsDir,
|
|
99
128
|
sliceAnsi,
|
|
@@ -101,12 +130,14 @@ import {
|
|
|
101
130
|
startDashboardDecisionDrain,
|
|
102
131
|
startWorkflowExecution,
|
|
103
132
|
supportsSessionApproval,
|
|
133
|
+
teamDefaultRequestSchema,
|
|
134
|
+
teamDefaultSchema,
|
|
104
135
|
teamIdentityPath,
|
|
105
136
|
todoGlyphSet,
|
|
106
137
|
visualPositionToOffset,
|
|
107
138
|
wrapText,
|
|
108
139
|
writeIdentityOnce
|
|
109
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-BTHLAJV7.js";
|
|
110
141
|
import {
|
|
111
142
|
generateId as generateId2
|
|
112
143
|
} from "./chunk-PQIB66SG.js";
|
|
@@ -120,9 +151,21 @@ import {
|
|
|
120
151
|
useTheme
|
|
121
152
|
} from "./chunk-KPDCVR6Q.js";
|
|
122
153
|
import {
|
|
154
|
+
BUILD_STRINGS,
|
|
155
|
+
CANNOT_PASS_TEMPLATE,
|
|
156
|
+
DESCRIPTION_TEMPLATE,
|
|
157
|
+
GATES,
|
|
158
|
+
LAYOUT_MARKERS,
|
|
159
|
+
NO_GATE_TEXT_TEMPLATE,
|
|
160
|
+
ORCHESTRATOR_LIMITS,
|
|
161
|
+
QA_SECTIONS,
|
|
162
|
+
RENDERER_RULES,
|
|
163
|
+
STAGE_TEMPLATE,
|
|
164
|
+
WORKFLOW_MD_TEMPLATE,
|
|
123
165
|
collectMcpServersWithOptions,
|
|
124
166
|
createSteerQueue,
|
|
125
167
|
deserializeRunMemory,
|
|
168
|
+
external_exports,
|
|
126
169
|
findMarketplaceRepoDir,
|
|
127
170
|
gatherMarketplaceWorkflowSources,
|
|
128
171
|
hasProjectWorkflow,
|
|
@@ -133,6 +176,7 @@ import {
|
|
|
133
176
|
listMarketplaceWorkflows,
|
|
134
177
|
listMarketplaceWorkflowsFromRepo,
|
|
135
178
|
listWorkflows,
|
|
179
|
+
parseWorkflowConfig,
|
|
136
180
|
projectConfigPath,
|
|
137
181
|
pullMarketplaceRepo,
|
|
138
182
|
readConfig,
|
|
@@ -145,10 +189,12 @@ import {
|
|
|
145
189
|
resolveWorkflowMarketplaceSource,
|
|
146
190
|
updateWorkflow,
|
|
147
191
|
updateWorkflows,
|
|
192
|
+
verifyRendered,
|
|
148
193
|
wakesFreshAfterHandover,
|
|
194
|
+
workflowRegistryDir,
|
|
149
195
|
writeGlobalConfig,
|
|
150
196
|
writeProjectConfig
|
|
151
|
-
} from "./chunk-
|
|
197
|
+
} from "./chunk-YUVMVYUF.js";
|
|
152
198
|
import {
|
|
153
199
|
__toESM
|
|
154
200
|
} from "./chunk-G3PMV62Z.js";
|
|
@@ -156,10 +202,10 @@ import {
|
|
|
156
202
|
// src/app/entry/cli.tsx
|
|
157
203
|
import { render } from "ink";
|
|
158
204
|
import meow from "meow";
|
|
159
|
-
import
|
|
205
|
+
import fs9 from "fs";
|
|
160
206
|
import { createRequire } from "module";
|
|
161
207
|
import os5 from "os";
|
|
162
|
-
import
|
|
208
|
+
import path9 from "path";
|
|
163
209
|
import { fileURLToPath } from "url";
|
|
164
210
|
|
|
165
211
|
// src/app/shell/sessionUiState.ts
|
|
@@ -5278,14 +5324,14 @@ function extractWebSearch(_input, response) {
|
|
|
5278
5324
|
return { type: "text", content: extractTextContent(response), maxLines: 10 };
|
|
5279
5325
|
}
|
|
5280
5326
|
function extractNotebookEdit(input, _response) {
|
|
5281
|
-
const
|
|
5327
|
+
const path10 = typeof input["notebook_path"] === "string" ? input["notebook_path"] : "";
|
|
5282
5328
|
const mode = typeof input["edit_mode"] === "string" ? input["edit_mode"] : "replace";
|
|
5283
5329
|
const source = typeof input["new_source"] === "string" ? input["new_source"] : "";
|
|
5284
|
-
if (!source) return { type: "text", content: `${mode} cell in ${
|
|
5330
|
+
if (!source) return { type: "text", content: `${mode} cell in ${path10}` };
|
|
5285
5331
|
return {
|
|
5286
5332
|
type: "code",
|
|
5287
5333
|
content: source,
|
|
5288
|
-
language: detectLanguage(
|
|
5334
|
+
language: detectLanguage(path10),
|
|
5289
5335
|
maxLines: 10
|
|
5290
5336
|
};
|
|
5291
5337
|
}
|
|
@@ -5811,10 +5857,10 @@ function extractToolSubject(toolName, toolInput) {
|
|
|
5811
5857
|
const parts = [];
|
|
5812
5858
|
const pattern = str(input, "pattern");
|
|
5813
5859
|
const glob = str(input, "glob");
|
|
5814
|
-
const
|
|
5860
|
+
const path10 = str(input, "path");
|
|
5815
5861
|
if (pattern) parts.push(`"${pattern}"`);
|
|
5816
5862
|
if (glob) parts.push(glob);
|
|
5817
|
-
if (
|
|
5863
|
+
if (path10) parts.push(`in ${path10}`);
|
|
5818
5864
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
5819
5865
|
}
|
|
5820
5866
|
case "Skill":
|
|
@@ -5916,6 +5962,14 @@ function eventLabel(event) {
|
|
|
5916
5962
|
return event.data.task_subject || "Task Completed";
|
|
5917
5963
|
case "config.change":
|
|
5918
5964
|
return "Config Change";
|
|
5965
|
+
case "instructions.loaded":
|
|
5966
|
+
return "Instructions Loaded";
|
|
5967
|
+
case "worktree.create":
|
|
5968
|
+
return "Worktree Created";
|
|
5969
|
+
case "worktree.remove":
|
|
5970
|
+
return "Worktree Removed";
|
|
5971
|
+
case "artifacts.manifest":
|
|
5972
|
+
return "Artifacts Manifest";
|
|
5919
5973
|
default:
|
|
5920
5974
|
return "Event";
|
|
5921
5975
|
}
|
|
@@ -5970,8 +6024,8 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
|
|
|
5970
6024
|
event.data.tool_input
|
|
5971
6025
|
);
|
|
5972
6026
|
const header = buildCompactHeader(event, width, { subject, theme });
|
|
5973
|
-
const
|
|
5974
|
-
const requestLines = highlightCode(
|
|
6027
|
+
const json2 = JSON.stringify(event.data.tool_input, null, 2);
|
|
6028
|
+
const requestLines = highlightCode(json2, cw, "json");
|
|
5975
6029
|
return buildResult(header, requestLines, true);
|
|
5976
6030
|
}
|
|
5977
6031
|
case "subagent.start": {
|
|
@@ -5994,8 +6048,8 @@ function renderDetailLines(event, width, pairedPostEvent, theme = darkTheme) {
|
|
|
5994
6048
|
} else if (prompt) {
|
|
5995
6049
|
content = renderMarkdownToLines(prompt, cw);
|
|
5996
6050
|
} else {
|
|
5997
|
-
const
|
|
5998
|
-
content = highlightCode(
|
|
6051
|
+
const json2 = JSON.stringify(event.data, null, 2);
|
|
6052
|
+
content = highlightCode(json2, cw, "json");
|
|
5999
6053
|
}
|
|
6000
6054
|
return buildResult(header, content, !prompt && !response);
|
|
6001
6055
|
}
|
|
@@ -6104,8 +6158,8 @@ Named by Turn ${event.data.turn} of Workflow Run \`${event.data.runId}\`.`,
|
|
|
6104
6158
|
case "elicitation.result":
|
|
6105
6159
|
case "artifacts.manifest": {
|
|
6106
6160
|
const header = buildCompactHeader(event, width, { theme });
|
|
6107
|
-
const
|
|
6108
|
-
const content = highlightCode(
|
|
6161
|
+
const json2 = JSON.stringify(event.raw ?? event.data, null, 2);
|
|
6162
|
+
const content = highlightCode(json2, cw, "json");
|
|
6109
6163
|
return buildResult(header, content, true);
|
|
6110
6164
|
}
|
|
6111
6165
|
default: {
|
|
@@ -8411,8 +8465,8 @@ function renderHeaderLines(model, width, hasColor, theme) {
|
|
|
8411
8465
|
const harnessText = `${style("Harness: ", palette.label)}${style(model.harness, palette.value)}`;
|
|
8412
8466
|
const effortSuffix = model.effort_level ? style(` with ${model.effort_level} effort`, palette.label) : "";
|
|
8413
8467
|
const modelText = `${style("Model: ", palette.label)}${style(formatModelName(model.model_name), palette.value)}${effortSuffix}`;
|
|
8414
|
-
const tokenLabel = model.token_label
|
|
8415
|
-
const runLabel = model.run_label
|
|
8468
|
+
const tokenLabel = model.token_label;
|
|
8469
|
+
const runLabel = model.run_label;
|
|
8416
8470
|
const leftTokens = [
|
|
8417
8471
|
{ text: athena, priority: 100 },
|
|
8418
8472
|
{ text: divider, priority: 5 },
|
|
@@ -12561,11 +12615,10 @@ function parseArgs(args, context) {
|
|
|
12561
12615
|
} else if (arg === "--global") {
|
|
12562
12616
|
global = true;
|
|
12563
12617
|
} else if (arg === "--env") {
|
|
12564
|
-
|
|
12565
|
-
if (value === void 0) {
|
|
12618
|
+
if (i + 1 >= args.length) {
|
|
12566
12619
|
return { error: `mcp ${context}: --env requires a KEY=VALUE argument` };
|
|
12567
12620
|
}
|
|
12568
|
-
env2.push(
|
|
12621
|
+
env2.push(args[i + 1]);
|
|
12569
12622
|
i++;
|
|
12570
12623
|
} else if (arg.startsWith("--")) {
|
|
12571
12624
|
return { error: `Unknown flag for mcp ${context}: ${arg}` };
|
|
@@ -13038,7 +13091,7 @@ var thisMachine = (installationId) => ({
|
|
|
13038
13091
|
os: `${platform()} ${release()}`.slice(0, 64),
|
|
13039
13092
|
softwareVersion: runnerSoftwareVersion
|
|
13040
13093
|
});
|
|
13041
|
-
var post = (origin,
|
|
13094
|
+
var post = (origin, path10, body) => fetch(new URL(path10, origin), {
|
|
13042
13095
|
method: "POST",
|
|
13043
13096
|
headers: { "Content-Type": "application/json" },
|
|
13044
13097
|
body: JSON.stringify(body)
|
|
@@ -13057,8 +13110,8 @@ async function alreadyConnected(existing, { forTeam, ownIdentity }) {
|
|
|
13057
13110
|
const problem = own ? await credentialAccepted(own.config) ? void 0 : "lapsed" : forTeam(existing.orgId).length ? "other_runner" : "missing";
|
|
13058
13111
|
const runner = { orgId: existing.orgId, runnerId: existing.runnerId, name: existing.name };
|
|
13059
13112
|
if (own && problem === void 0) return { status: "already_connected", ...runner, path: own.path };
|
|
13060
|
-
const
|
|
13061
|
-
return { status: "identity_unusable", ...runner, problem, ...
|
|
13113
|
+
const path10 = (own ?? forTeam(existing.orgId)[0])?.path;
|
|
13114
|
+
return { status: "identity_unusable", ...runner, problem, ...path10 ? { path: path10 } : {} };
|
|
13062
13115
|
}
|
|
13063
13116
|
async function connectWithCode({
|
|
13064
13117
|
code,
|
|
@@ -13130,20 +13183,20 @@ async function saveIdentity(origin, credential, { single, forTeam }, directory)
|
|
|
13130
13183
|
name: credential.name
|
|
13131
13184
|
});
|
|
13132
13185
|
const replaced = [];
|
|
13133
|
-
for (const { path:
|
|
13134
|
-
const aside = `${
|
|
13135
|
-
await rename(
|
|
13186
|
+
for (const { path: path11 } of forTeam(credential.orgId)) {
|
|
13187
|
+
const aside = `${path11}.replaced-${Date.now()}`;
|
|
13188
|
+
await rename(path11, aside);
|
|
13136
13189
|
replaced.push(aside);
|
|
13137
13190
|
}
|
|
13138
|
-
const
|
|
13191
|
+
const path10 = single ?? teamIdentityPath(origin, credential.orgId, directory);
|
|
13139
13192
|
try {
|
|
13140
|
-
await writeIdentityOnce(
|
|
13193
|
+
await writeIdentityOnce(path10, JSON.stringify(config, null, 2) + "\n");
|
|
13141
13194
|
} catch (error) {
|
|
13142
13195
|
if (error.code === "EEXIST")
|
|
13143
|
-
throw new Error(`Another Connect wrote a Runner identity for this Team at ${
|
|
13196
|
+
throw new Error(`Another Connect wrote a Runner identity for this Team at ${path10} meanwhile; it was left unchanged.`);
|
|
13144
13197
|
throw error;
|
|
13145
13198
|
}
|
|
13146
|
-
return { status: "connected", config, path:
|
|
13199
|
+
return { status: "connected", config, path: path10, replaced };
|
|
13147
13200
|
}
|
|
13148
13201
|
function pathSegment(value) {
|
|
13149
13202
|
const segment = encodeURIComponent(value);
|
|
@@ -13183,7 +13236,7 @@ async function refuseExisting(target) {
|
|
|
13183
13236
|
throw new Error(`This machine already has a Runner${name ? ` (${name})` : ""} configured at ${target}.`);
|
|
13184
13237
|
}
|
|
13185
13238
|
async function disconnectRunner({
|
|
13186
|
-
identity: { path:
|
|
13239
|
+
identity: { path: path10, config },
|
|
13187
13240
|
serviceManager,
|
|
13188
13241
|
othersRemain
|
|
13189
13242
|
}) {
|
|
@@ -13216,14 +13269,14 @@ async function disconnectRunner({
|
|
|
13216
13269
|
await restore();
|
|
13217
13270
|
throw new Error(`The Hub refused to disconnect ${name} (${body.error ?? response.status}); it is still connected.`);
|
|
13218
13271
|
}
|
|
13219
|
-
await forgetRunner(
|
|
13272
|
+
await forgetRunner(path10);
|
|
13220
13273
|
if (othersRemain) await restore();
|
|
13221
13274
|
else await serviceManager?.remove();
|
|
13222
13275
|
return result;
|
|
13223
13276
|
}
|
|
13224
13277
|
|
|
13225
13278
|
// src/app/entry/runnerCommandV2.ts
|
|
13226
|
-
var usage = `Usage: drisp runner [connect [<code>] --hub <url> [--foreground] [--replace] | disconnect [<team>] | status | recover | help]
|
|
13279
|
+
var usage = `Usage: drisp runner [connect [<code>] --hub <url> [--foreground] [--replace] | disconnect [<team>] | status | restart | recover | help]
|
|
13227
13280
|
|
|
13228
13281
|
Connect this machine from Settings \u2192 Runners in the web console, which shows the
|
|
13229
13282
|
command to paste here:
|
|
@@ -13239,8 +13292,11 @@ that runs this machine's Runner for every Team it serves, at login. Connecting a
|
|
|
13239
13292
|
the same Team reports the Runner the machine already is and changes nothing. When this
|
|
13240
13293
|
machine can no longer serve that Runner (its credential is gone or has lapsed), Connect
|
|
13241
13294
|
says so without using the code, and --replace connects a new Runner in its place.
|
|
13242
|
-
drisp runner status lists the Teams and the service's state.
|
|
13243
|
-
service
|
|
13295
|
+
drisp runner status lists the Teams and the service's state. drisp runner restart
|
|
13296
|
+
rewrites the service definition for the CLI installed now and restarts the service
|
|
13297
|
+
(on Linux it also turns start-at-login back on), so run it after
|
|
13298
|
+
npm install -g @drisp/cli@latest; it never installs one, and it fails any Run in
|
|
13299
|
+
progress, so restart when no Run is executing. --foreground skips the service and runs the Runner in this terminal, for containers and CI; drisp runner runs
|
|
13244
13300
|
every Team's Runner there again later. DRISP_HUB_URL may stand in for --hub, and
|
|
13245
13301
|
DRISP_RUNNER_CONFIG selects one identity file of a separate installation instead, which
|
|
13246
13302
|
gets its own service.
|
|
@@ -13267,6 +13323,7 @@ The code works once and expires in ${minutes} minutes. Waiting for approval\u202
|
|
|
13267
13323
|
);
|
|
13268
13324
|
}
|
|
13269
13325
|
var serviceStarted = (service) => `The Runner runs as a background service (${service.kind} ${service.name}) and starts again at login.`;
|
|
13326
|
+
var notConnected = "This machine is not connected to a Team. Settings \u2192 Runners shows the command to connect it.";
|
|
13270
13327
|
var servicesFor = (options) => options.services === void 0 ? platformRunnerServices() : options.services;
|
|
13271
13328
|
async function serviceInstallation(config) {
|
|
13272
13329
|
const single = overriddenIdentityPath();
|
|
@@ -13275,25 +13332,35 @@ async function serviceInstallation(config) {
|
|
|
13275
13332
|
...single ? { configPath: single } : {}
|
|
13276
13333
|
};
|
|
13277
13334
|
}
|
|
13335
|
+
async function installAndRestart(service, configPath, onInstalled = () => {
|
|
13336
|
+
}) {
|
|
13337
|
+
await service.install(runnerServiceUnit(configPath));
|
|
13338
|
+
onInstalled();
|
|
13339
|
+
await service.restart();
|
|
13340
|
+
}
|
|
13278
13341
|
async function ensureService(services, config, identityPath) {
|
|
13279
13342
|
let service;
|
|
13280
|
-
|
|
13343
|
+
const progress = { installed: false };
|
|
13281
13344
|
try {
|
|
13282
13345
|
const { installationId, configPath } = await serviceInstallation(config);
|
|
13283
13346
|
service = services(installationId);
|
|
13284
|
-
await service
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
await service.start();
|
|
13347
|
+
await installAndRestart(service, configPath, () => {
|
|
13348
|
+
progress.installed = true;
|
|
13349
|
+
});
|
|
13288
13350
|
return service;
|
|
13289
13351
|
} catch (error) {
|
|
13352
|
+
if (error instanceof ServiceNotEnabledError && service)
|
|
13353
|
+
throw new Error(
|
|
13354
|
+
`Connected, and the Runner runs as a background service (${service.kind} ${service.name}). ${error.message}
|
|
13355
|
+
Run drisp runner restart to enable it again.`
|
|
13356
|
+
);
|
|
13290
13357
|
throw new Error(
|
|
13291
13358
|
[
|
|
13292
13359
|
`Connected, but the background service did not start: ${error instanceof Error ? error.message : String(error)}`,
|
|
13293
13360
|
`The identity is saved to ${identityPath}: run drisp runner to run the Runner in the foreground.`,
|
|
13294
13361
|
// launchd loads an installed LaunchAgent at the next login and RunAtLoad starts it. A systemd unit
|
|
13295
|
-
// is enabled only by the
|
|
13296
|
-
...installed && service?.kind === "launchd" ? [
|
|
13362
|
+
// is enabled only by the restart that just failed, so it has no such fallback.
|
|
13363
|
+
...progress.installed && service?.kind === "launchd" ? [
|
|
13297
13364
|
`The LaunchAgent ${service.name} is installed, so launchd starts the Runner at your next login.`
|
|
13298
13365
|
] : []
|
|
13299
13366
|
].join("\n")
|
|
@@ -13301,6 +13368,10 @@ async function ensureService(services, config, identityPath) {
|
|
|
13301
13368
|
}
|
|
13302
13369
|
}
|
|
13303
13370
|
async function runRunnerCommandV2(input, options = {}) {
|
|
13371
|
+
if (input.flags.help || input.subcommand === "help") {
|
|
13372
|
+
process.stdout.write(usage);
|
|
13373
|
+
return 0;
|
|
13374
|
+
}
|
|
13304
13375
|
try {
|
|
13305
13376
|
if (input.flags.detach)
|
|
13306
13377
|
throw new Error(
|
|
@@ -13308,7 +13379,8 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13308
13379
|
);
|
|
13309
13380
|
switch (input.subcommand) {
|
|
13310
13381
|
case "connect": {
|
|
13311
|
-
const
|
|
13382
|
+
const code = input.subcommandArgs.at(0);
|
|
13383
|
+
const extra = input.subcommandArgs.at(1);
|
|
13312
13384
|
if (extra) throw new Error(`Unexpected argument ${extra}.`);
|
|
13313
13385
|
const hub = input.flags.hub ?? process.env["DRISP_HUB_URL"];
|
|
13314
13386
|
if (!hub)
|
|
@@ -13350,18 +13422,18 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13350
13422
|
`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
13423
|
);
|
|
13352
13424
|
}
|
|
13353
|
-
const { config, path:
|
|
13425
|
+
const { config, path: path10, replaced } = outcome;
|
|
13354
13426
|
for (const aside of replaced)
|
|
13355
13427
|
process.stdout.write(
|
|
13356
13428
|
`The Hub no longer recognised this machine's earlier identity for this Team; it was moved aside to ${aside}.
|
|
13357
13429
|
`
|
|
13358
13430
|
);
|
|
13359
13431
|
process.stdout.write(
|
|
13360
|
-
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${
|
|
13432
|
+
`Connected as ${config.name ?? config.runnerId}. Identity saved to ${path10}.
|
|
13361
13433
|
`
|
|
13362
13434
|
);
|
|
13363
13435
|
if (services) {
|
|
13364
|
-
const service = await ensureService(services, config,
|
|
13436
|
+
const service = await ensureService(services, config, path10);
|
|
13365
13437
|
process.stdout.write(`${serviceStarted(service)}
|
|
13366
13438
|
`);
|
|
13367
13439
|
return 0;
|
|
@@ -13370,7 +13442,7 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13370
13442
|
process.stdout.write(
|
|
13371
13443
|
"No launchd or systemd here to run the Runner as a background service; running it in the foreground instead.\n"
|
|
13372
13444
|
);
|
|
13373
|
-
await runConfiguredRunner(
|
|
13445
|
+
await runConfiguredRunner(path10, options.signal);
|
|
13374
13446
|
return 0;
|
|
13375
13447
|
}
|
|
13376
13448
|
case "disconnect": {
|
|
@@ -13430,10 +13502,7 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13430
13502
|
}
|
|
13431
13503
|
case "status": {
|
|
13432
13504
|
const identities = await runnerIdentities();
|
|
13433
|
-
if (!identities.length)
|
|
13434
|
-
throw new Error(
|
|
13435
|
-
"This machine is not connected to a Team. Settings \u2192 Runners shows the command to connect it."
|
|
13436
|
-
);
|
|
13505
|
+
if (!identities.length) throw new Error(notConnected);
|
|
13437
13506
|
const services = servicesFor(options);
|
|
13438
13507
|
const service = services ? services(
|
|
13439
13508
|
(await serviceInstallation(identities[0].config)).installationId
|
|
@@ -13444,9 +13513,9 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13444
13513
|
configured: true,
|
|
13445
13514
|
// One entry per Team this machine serves; credentials are never printed.
|
|
13446
13515
|
teams: identities.map(
|
|
13447
|
-
({ path:
|
|
13516
|
+
({ path: path10, config: { token: _token, ...config } }) => ({
|
|
13448
13517
|
...config,
|
|
13449
|
-
path:
|
|
13518
|
+
path: path10
|
|
13450
13519
|
})
|
|
13451
13520
|
),
|
|
13452
13521
|
service: service ? {
|
|
@@ -13462,15 +13531,24 @@ async function runRunnerCommandV2(input, options = {}) {
|
|
|
13462
13531
|
);
|
|
13463
13532
|
return 0;
|
|
13464
13533
|
}
|
|
13534
|
+
case "restart": {
|
|
13535
|
+
const [extra] = input.subcommandArgs;
|
|
13536
|
+
if (extra) throw new Error(`Unexpected argument ${extra}.`);
|
|
13537
|
+
const { service, configPath } = await installedService(
|
|
13538
|
+
servicesFor(options)
|
|
13539
|
+
);
|
|
13540
|
+
await installAndRestart(service, configPath);
|
|
13541
|
+
process.stdout.write(
|
|
13542
|
+
`Restarted the background service ${service.kind} ${service.name}.
|
|
13543
|
+
`
|
|
13544
|
+
);
|
|
13545
|
+
return 0;
|
|
13546
|
+
}
|
|
13465
13547
|
case "":
|
|
13466
13548
|
await runMachineRunners(options.signal, {
|
|
13467
13549
|
services: servicesFor(options)
|
|
13468
13550
|
});
|
|
13469
13551
|
return 0;
|
|
13470
|
-
case "help":
|
|
13471
|
-
case "--help":
|
|
13472
|
-
process.stdout.write(usage);
|
|
13473
|
-
return 0;
|
|
13474
13552
|
default:
|
|
13475
13553
|
throw new Error(
|
|
13476
13554
|
`Unsupported v2 runner command: ${input.subcommand}.
|
|
@@ -13485,6 +13563,25 @@ ${usage}`
|
|
|
13485
13563
|
return 1;
|
|
13486
13564
|
}
|
|
13487
13565
|
}
|
|
13566
|
+
async function installedService(services) {
|
|
13567
|
+
const identities = await runnerIdentities();
|
|
13568
|
+
if (!identities.length) throw new Error(notConnected);
|
|
13569
|
+
const whose = overriddenIdentityPath() ? "the Runner installation DRISP_RUNNER_CONFIG selects" : "this machine's Runner";
|
|
13570
|
+
const foreground = "drisp runner runs it in the foreground meanwhile.";
|
|
13571
|
+
if (!services)
|
|
13572
|
+
throw new Error(
|
|
13573
|
+
`No background service is installed for ${whose}: there is no launchd or systemd --user here. ${foreground}`
|
|
13574
|
+
);
|
|
13575
|
+
const { installationId, configPath } = await serviceInstallation(
|
|
13576
|
+
identities[0].config
|
|
13577
|
+
);
|
|
13578
|
+
const service = services(installationId);
|
|
13579
|
+
if (!(await service.status()).installed)
|
|
13580
|
+
throw new Error(
|
|
13581
|
+
`No background service is installed for ${whose} (${service.kind} ${service.name}). drisp runner connect installs one when it connects to a Team (Settings \u2192 Runners shows the command); restart never installs one. ${foreground}`
|
|
13582
|
+
);
|
|
13583
|
+
return { service, ...configPath ? { configPath } : {} };
|
|
13584
|
+
}
|
|
13488
13585
|
async function recoveryIdentity(runId) {
|
|
13489
13586
|
const identities = await runnerIdentities();
|
|
13490
13587
|
if (!identities.length)
|
|
@@ -13513,14 +13610,1248 @@ async function recoveryIdentity(runId) {
|
|
|
13513
13610
|
throw new Error(`No Runner on this machine holds Run ${runId}.`);
|
|
13514
13611
|
}
|
|
13515
13612
|
|
|
13613
|
+
// src/app/entry/buildCommand.ts
|
|
13614
|
+
import { execFileSync } from "child_process";
|
|
13615
|
+
|
|
13616
|
+
// src/core/build/planCommand.ts
|
|
13617
|
+
import fs6 from "fs";
|
|
13618
|
+
import path6 from "path";
|
|
13619
|
+
|
|
13620
|
+
// src/core/build/io.ts
|
|
13621
|
+
import fs5 from "fs";
|
|
13622
|
+
import path5 from "path";
|
|
13623
|
+
var BuildUsageError = class extends Error {
|
|
13624
|
+
};
|
|
13625
|
+
var PlanFailure = class extends Error {
|
|
13626
|
+
};
|
|
13627
|
+
function requireEnv(env2, name) {
|
|
13628
|
+
const value = env2[name];
|
|
13629
|
+
if (!value)
|
|
13630
|
+
throw new BuildUsageError(
|
|
13631
|
+
`${name} is not set: drisp build runs inside an Orchestrator Run.`
|
|
13632
|
+
);
|
|
13633
|
+
return value;
|
|
13634
|
+
}
|
|
13635
|
+
function readJson(file) {
|
|
13636
|
+
try {
|
|
13637
|
+
return JSON.parse(fs5.readFileSync(file, "utf-8"));
|
|
13638
|
+
} catch {
|
|
13639
|
+
return void 0;
|
|
13640
|
+
}
|
|
13641
|
+
}
|
|
13642
|
+
function writeFile(file, content) {
|
|
13643
|
+
fs5.mkdirSync(path5.dirname(file), { recursive: true });
|
|
13644
|
+
fs5.writeFileSync(file, content, "utf-8");
|
|
13645
|
+
}
|
|
13646
|
+
var json = (value) => `${JSON.stringify(value, null, 2)}
|
|
13647
|
+
`;
|
|
13648
|
+
function readInput(file) {
|
|
13649
|
+
const raw = readJson(file);
|
|
13650
|
+
if (raw === void 0)
|
|
13651
|
+
throw new PlanFailure(`Could not read plan-input.json at ${file}.`);
|
|
13652
|
+
const parsed = planInputSchema.safeParse(raw);
|
|
13653
|
+
if (!parsed.success) {
|
|
13654
|
+
const issue = parsed.error.issues[0];
|
|
13655
|
+
throw new PlanFailure(
|
|
13656
|
+
`plan-input.json is invalid at ${issue.path.join(".") || "(root)"}: ${issue.message}`
|
|
13657
|
+
);
|
|
13658
|
+
}
|
|
13659
|
+
return parsed.data;
|
|
13660
|
+
}
|
|
13661
|
+
|
|
13662
|
+
// src/core/workflows/builtins/build-child/version.ts
|
|
13663
|
+
import { createHash } from "crypto";
|
|
13664
|
+
function computeTemplateVersion() {
|
|
13665
|
+
const parts = [
|
|
13666
|
+
WORKFLOW_MD_TEMPLATE,
|
|
13667
|
+
STAGE_TEMPLATE,
|
|
13668
|
+
CANNOT_PASS_TEMPLATE,
|
|
13669
|
+
NO_GATE_TEXT_TEMPLATE,
|
|
13670
|
+
DESCRIPTION_TEMPLATE,
|
|
13671
|
+
JSON.stringify(QA_SECTIONS),
|
|
13672
|
+
JSON.stringify(GATES),
|
|
13673
|
+
JSON.stringify(RENDERER_RULES),
|
|
13674
|
+
JSON.stringify(LAYOUT_MARKERS)
|
|
13675
|
+
];
|
|
13676
|
+
const hash = createHash("sha256");
|
|
13677
|
+
for (const part of parts) hash.update(`${part.length}:${part}`);
|
|
13678
|
+
return hash.digest("hex");
|
|
13679
|
+
}
|
|
13680
|
+
var templateVersion = computeTemplateVersion();
|
|
13681
|
+
|
|
13682
|
+
// src/core/build/planCommand.ts
|
|
13683
|
+
function checkGenerated(directory, workflow, host, layout) {
|
|
13684
|
+
const workflowJson = fs6.readFileSync(
|
|
13685
|
+
path6.join(directory, "workflow.json"),
|
|
13686
|
+
"utf-8"
|
|
13687
|
+
);
|
|
13688
|
+
const workflowMd = fs6.readFileSync(
|
|
13689
|
+
path6.join(directory, "workflow.md"),
|
|
13690
|
+
"utf-8"
|
|
13691
|
+
);
|
|
13692
|
+
try {
|
|
13693
|
+
parseWorkflowConfig(JSON.parse(workflowJson), {
|
|
13694
|
+
instructions: workflowMd,
|
|
13695
|
+
host
|
|
13696
|
+
});
|
|
13697
|
+
} catch (error) {
|
|
13698
|
+
throw new PlanFailure(
|
|
13699
|
+
`${workflow.identifier}: ${error instanceof Error ? error.message : String(error)}`
|
|
13700
|
+
);
|
|
13701
|
+
}
|
|
13702
|
+
const verified = verifyRendered({
|
|
13703
|
+
fill: workflow.fill,
|
|
13704
|
+
layout,
|
|
13705
|
+
workflowName: workflow.workflowName,
|
|
13706
|
+
workflowJson,
|
|
13707
|
+
workflowMd
|
|
13708
|
+
});
|
|
13709
|
+
if (!verified.ok)
|
|
13710
|
+
throw new PlanFailure(
|
|
13711
|
+
`${workflow.identifier}: the written ${verified.file} does not match what its fill renders.`
|
|
13712
|
+
);
|
|
13713
|
+
}
|
|
13714
|
+
function runBuildPlan(deps) {
|
|
13715
|
+
const orchestratorRunId = requireEnv(deps.env, "DRISP_RUN_ID");
|
|
13716
|
+
const runnerId = requireEnv(deps.env, "DRISP_RUNNER_ID");
|
|
13717
|
+
const artifacts = requireEnv(deps.env, "DRISP_RUN_ARTIFACTS");
|
|
13718
|
+
const fixtureInput = deps.env["DRISP_BUILD_PLAN_INPUT"];
|
|
13719
|
+
const inputPath = fixtureInput ?? deps.inputPath;
|
|
13720
|
+
if (!inputPath)
|
|
13721
|
+
throw new BuildUsageError("Usage: drisp build plan --input <path>");
|
|
13722
|
+
const fail = (reason) => {
|
|
13723
|
+
writeFile(path6.join(artifacts, "build_plan_error.json"), json({ reason }));
|
|
13724
|
+
return { status: "failed", reason };
|
|
13725
|
+
};
|
|
13726
|
+
try {
|
|
13727
|
+
if (!isGitHubRepository(deps.folder))
|
|
13728
|
+
throw new PlanFailure(BUILD_STRINGS.notAGitHubRepository);
|
|
13729
|
+
const input = readInput(path6.resolve(deps.folder, inputPath));
|
|
13730
|
+
const worktree = ensureBuildWorktree({
|
|
13731
|
+
folder: deps.folder,
|
|
13732
|
+
slugId: input.project.slugId,
|
|
13733
|
+
worktreesRoot: deps.worktreesRoot
|
|
13734
|
+
});
|
|
13735
|
+
const designDir = path6.join(
|
|
13736
|
+
worktree.path,
|
|
13737
|
+
".athena",
|
|
13738
|
+
"workflows",
|
|
13739
|
+
input.project.slugId
|
|
13740
|
+
);
|
|
13741
|
+
writeFile(path6.join(designDir, "plan-input.json"), json(input));
|
|
13742
|
+
const fixtureDir = fixtureInput ? path6.dirname(path6.resolve(fixtureInput)) : void 0;
|
|
13743
|
+
const fillPath = (identifier) => path6.join(designDir, identifier, "fill.json");
|
|
13744
|
+
const fills = Object.fromEntries(
|
|
13745
|
+
input.issues.map((issue) => {
|
|
13746
|
+
const fixture = fixtureDir ? path6.join(fixtureDir, "fills", `${issue.identifier}.json`) : void 0;
|
|
13747
|
+
const file = fixture && fs6.existsSync(fixture) ? fixture : fillPath(issue.identifier);
|
|
13748
|
+
return [issue.identifier, readJson(file)];
|
|
13749
|
+
})
|
|
13750
|
+
);
|
|
13751
|
+
const fixturePullRequests = fixtureDir ? readJson(path6.join(fixtureDir, "pull-requests.json")) : void 0;
|
|
13752
|
+
const pullRequests = fixturePullRequests ?? Object.fromEntries(
|
|
13753
|
+
input.issues.filter(
|
|
13754
|
+
(issue) => issue.branchName && issue.state.type !== "canceled" && !issue.labels.includes("ready-for-human")
|
|
13755
|
+
).map((issue) => [
|
|
13756
|
+
issue.branchName,
|
|
13757
|
+
deps.pullRequests(deps.folder, issue.branchName)
|
|
13758
|
+
])
|
|
13759
|
+
);
|
|
13760
|
+
const layout = repoLayoutAt(worktree.path, "HEAD");
|
|
13761
|
+
const result = planBuild({
|
|
13762
|
+
input,
|
|
13763
|
+
fills,
|
|
13764
|
+
pullRequests,
|
|
13765
|
+
orchestratorRunId,
|
|
13766
|
+
folder: deps.folder,
|
|
13767
|
+
runnerId,
|
|
13768
|
+
templateVersion,
|
|
13769
|
+
layout
|
|
13770
|
+
});
|
|
13771
|
+
if (result.status === "failed") return fail(result.reason);
|
|
13772
|
+
if (result.status === "needs_fills")
|
|
13773
|
+
return {
|
|
13774
|
+
status: "needs_fills",
|
|
13775
|
+
templateVersion,
|
|
13776
|
+
fills: result.fills.map((fill) => ({
|
|
13777
|
+
...fill,
|
|
13778
|
+
path: fillPath(fill.identifier)
|
|
13779
|
+
}))
|
|
13780
|
+
};
|
|
13781
|
+
const host = deps.hostCapabilities();
|
|
13782
|
+
for (const workflow of result.workflows) {
|
|
13783
|
+
const directory = path6.join(designDir, workflow.identifier);
|
|
13784
|
+
writeFile(path6.join(directory, "fill.json"), json(workflow.fill));
|
|
13785
|
+
writeFile(path6.join(directory, "workflow.json"), workflow.workflowJson);
|
|
13786
|
+
writeFile(path6.join(directory, "workflow.md"), workflow.workflowMd);
|
|
13787
|
+
checkGenerated(directory, workflow, host, layout);
|
|
13788
|
+
}
|
|
13789
|
+
const relativeDesignDir = path6.relative(worktree.path, designDir);
|
|
13790
|
+
commitBuildFiles(
|
|
13791
|
+
worktree.path,
|
|
13792
|
+
[relativeDesignDir],
|
|
13793
|
+
`drisp build: Generated Workflows for ${input.project.slugId}`
|
|
13794
|
+
);
|
|
13795
|
+
const commit = lastCommitTouching(worktree.path, relativeDesignDir, [
|
|
13796
|
+
path6.join(relativeDesignDir, "plan.json")
|
|
13797
|
+
]);
|
|
13798
|
+
const plan = finalizePlan(result.plan, commit);
|
|
13799
|
+
writeFile(path6.join(designDir, "plan.json"), json(plan));
|
|
13800
|
+
commitBuildFiles(
|
|
13801
|
+
worktree.path,
|
|
13802
|
+
[path6.join(relativeDesignDir, "plan.json")],
|
|
13803
|
+
`drisp build: Run plan for ${input.project.slugId}`
|
|
13804
|
+
);
|
|
13805
|
+
writeFile(path6.join(artifacts, "build_plan.json"), json(plan));
|
|
13806
|
+
return {
|
|
13807
|
+
status: "planned",
|
|
13808
|
+
entries: plan.groups.reduce((sum, group) => sum + group.issues.length, 0),
|
|
13809
|
+
skipped: plan.skipped.length
|
|
13810
|
+
};
|
|
13811
|
+
} catch (error) {
|
|
13812
|
+
if (error instanceof BuildUsageError) throw error;
|
|
13813
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
13814
|
+
}
|
|
13815
|
+
}
|
|
13816
|
+
|
|
13817
|
+
// src/core/build/tick.ts
|
|
13818
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
13819
|
+
import fs7 from "fs";
|
|
13820
|
+
import path7 from "path";
|
|
13821
|
+
|
|
13822
|
+
// ../../packages/client/src/index.ts
|
|
13823
|
+
var runArtifactSchema = external_exports.object({ name: external_exports.string(), seq: external_exports.number().int(), json: external_exports.json() });
|
|
13824
|
+
var githubRepositorySchema = external_exports.object({
|
|
13825
|
+
installationId: external_exports.number().int().positive(),
|
|
13826
|
+
repositoryId: external_exports.number().int().positive(),
|
|
13827
|
+
owner: external_exports.string(),
|
|
13828
|
+
name: external_exports.string(),
|
|
13829
|
+
defaultBranch: external_exports.string()
|
|
13830
|
+
});
|
|
13831
|
+
var ClientError = class extends Error {
|
|
13832
|
+
constructor(code, status) {
|
|
13833
|
+
super(code);
|
|
13834
|
+
this.code = code;
|
|
13835
|
+
this.status = status;
|
|
13836
|
+
}
|
|
13837
|
+
code;
|
|
13838
|
+
status;
|
|
13839
|
+
};
|
|
13840
|
+
var DrispClient = class _DrispClient {
|
|
13841
|
+
constructor(baseUrl, orgId, token) {
|
|
13842
|
+
this.baseUrl = baseUrl;
|
|
13843
|
+
this.orgId = orgId;
|
|
13844
|
+
this.token = token;
|
|
13845
|
+
const url = new URL(baseUrl);
|
|
13846
|
+
if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
|
|
13847
|
+
throw new Error("TLS_required");
|
|
13848
|
+
}
|
|
13849
|
+
baseUrl;
|
|
13850
|
+
orgId;
|
|
13851
|
+
token;
|
|
13852
|
+
/**
|
|
13853
|
+
* A client that authenticates with a Run's delegation token (root ADR-0008), from the agent's environment:
|
|
13854
|
+
* `hubUrl` is `DRISP_HUB_URL`, the Team-scoped base `https://<hub>/v2/orgs/<orgId>`, and `token` is
|
|
13855
|
+
* `DRISP_RUN_TOKEN`. The token starts, cancels and reads only its own Run's children.
|
|
13856
|
+
*/
|
|
13857
|
+
static delegated(hubUrl, token) {
|
|
13858
|
+
const url = new URL(hubUrl);
|
|
13859
|
+
const match = /^\/v2\/orgs\/([^/]+)\/?$/.exec(url.pathname);
|
|
13860
|
+
if (!match) throw new Error("invalid_hub_url");
|
|
13861
|
+
return new _DrispClient(url.origin, decodeURIComponent(match[1]), async () => token);
|
|
13862
|
+
}
|
|
13863
|
+
async request(path10, body) {
|
|
13864
|
+
const response = await fetch(
|
|
13865
|
+
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}${path10}`,
|
|
13866
|
+
{
|
|
13867
|
+
method: body === void 0 ? "GET" : "POST",
|
|
13868
|
+
headers: {
|
|
13869
|
+
Authorization: `Bearer ${await this.token()}`,
|
|
13870
|
+
"Content-Type": "application/json"
|
|
13871
|
+
},
|
|
13872
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
13873
|
+
}
|
|
13874
|
+
);
|
|
13875
|
+
const result = await response.json();
|
|
13876
|
+
if (!response.ok)
|
|
13877
|
+
throw new ClientError(result.error ?? `HTTP ${response.status}`, response.status);
|
|
13878
|
+
return result;
|
|
13879
|
+
}
|
|
13880
|
+
submit(command) {
|
|
13881
|
+
return this.request("/commands", commandSchema.parse(command));
|
|
13882
|
+
}
|
|
13883
|
+
/** The recent Runs; with a filter, every Run carrying that key or every child of that Run, newest first. */
|
|
13884
|
+
async runs(filter) {
|
|
13885
|
+
const query = !filter ? "" : "key" in filter ? `?key=${encodeURIComponent(filter.key)}` : `?parent=${encodeURIComponent(filter.parent)}`;
|
|
13886
|
+
return external_exports.object({ hubEpoch: external_exports.string(), runs: external_exports.array(runViewSchema) }).parse(await this.request(`/runs${query}`));
|
|
13887
|
+
}
|
|
13888
|
+
/** The newest `run.artifact` named `name` that the Run reported; `not_found` when it reported none. */
|
|
13889
|
+
async artifact(runId, name) {
|
|
13890
|
+
return runArtifactSchema.parse(
|
|
13891
|
+
await this.request(`/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(name)}`)
|
|
13892
|
+
);
|
|
13893
|
+
}
|
|
13894
|
+
async run(runId) {
|
|
13895
|
+
return runViewSchema.parse(await this.request(`/runs/${encodeURIComponent(runId)}`));
|
|
13896
|
+
}
|
|
13897
|
+
history(runId, after = 0) {
|
|
13898
|
+
return this.request(`/runs/${encodeURIComponent(runId)}/history?after=${after}`);
|
|
13899
|
+
}
|
|
13900
|
+
async runners() {
|
|
13901
|
+
return runnerListSchema.parse(await this.request("/runners"));
|
|
13902
|
+
}
|
|
13903
|
+
/** A one-use Connect code bound to the signed-in member, for `drisp runner connect <code>` (root ADR-0005). */
|
|
13904
|
+
async createConnectCode(hubEpoch) {
|
|
13905
|
+
return connectCodeResponseSchema.parse(await this.request("/runners/connect-codes", { hubEpoch }));
|
|
13906
|
+
}
|
|
13907
|
+
/** Rename a Runner, set its capacity, or change its Audience (root ADR-0005). */
|
|
13908
|
+
async updateRunner(hubEpoch, runnerId, update) {
|
|
13909
|
+
const body = runnerUpdateRequestSchema.parse({ hubEpoch, ...update });
|
|
13910
|
+
return runnerListEntrySchema.parse(await this.request(`/runners/${encodeURIComponent(runnerId)}`, body));
|
|
13911
|
+
}
|
|
13912
|
+
/** Admin only: where a member's Runs go when they have no ready Personal Runner; `null` clears it. */
|
|
13913
|
+
async setTeamDefault(hubEpoch, teamDefault) {
|
|
13914
|
+
const body = teamDefaultRequestSchema.parse({ hubEpoch, teamDefault });
|
|
13915
|
+
return external_exports.object({ teamDefault: teamDefaultSchema.nullable() }).parse(await this.request("/team-default", body));
|
|
13916
|
+
}
|
|
13917
|
+
/** The Runs a Disconnect of `runnerId` would stop, for its confirmation. */
|
|
13918
|
+
async disconnectPreview(runnerId) {
|
|
13919
|
+
return runnerDisconnectPreviewSchema.parse(
|
|
13920
|
+
await this.request(`/runners/${encodeURIComponent(runnerId)}/disconnect`)
|
|
13921
|
+
);
|
|
13922
|
+
}
|
|
13923
|
+
/** Disconnect (root ADR-0005): the Runner leaves the Team and its open Runs are Stopped. */
|
|
13924
|
+
async disconnectRunner(hubEpoch, runnerId) {
|
|
13925
|
+
return runnerDisconnectResponseSchema.parse(
|
|
13926
|
+
await this.request(`/runners/${encodeURIComponent(runnerId)}/disconnect`, { hubEpoch })
|
|
13927
|
+
);
|
|
13928
|
+
}
|
|
13929
|
+
/** Admin only: take over a Runner paused as "Needs an owner" because its member left, so it serves again. */
|
|
13930
|
+
async adoptRunner(hubEpoch, runnerId) {
|
|
13931
|
+
const body = runnerAdoptRequestSchema.parse({ hubEpoch });
|
|
13932
|
+
return runnerListEntrySchema.parse(await this.request(`/runners/${encodeURIComponent(runnerId)}/adopt`, body));
|
|
13933
|
+
}
|
|
13934
|
+
submitManaged(profileId, command, repository) {
|
|
13935
|
+
return this.request("/managed-runs", {
|
|
13936
|
+
profileId,
|
|
13937
|
+
...repository,
|
|
13938
|
+
command: commandSchema.parse(command)
|
|
13939
|
+
});
|
|
13940
|
+
}
|
|
13941
|
+
async repositories() {
|
|
13942
|
+
const schema = external_exports.object({
|
|
13943
|
+
configured: external_exports.boolean(),
|
|
13944
|
+
installationUrl: external_exports.string().url().optional(),
|
|
13945
|
+
repositories: external_exports.array(repositoryBindingSchema)
|
|
13946
|
+
});
|
|
13947
|
+
try {
|
|
13948
|
+
return schema.parse(await this.request("/repositories"));
|
|
13949
|
+
} catch (error) {
|
|
13950
|
+
if (error instanceof ClientError && error.status === 404 && error.code === "not_found")
|
|
13951
|
+
return schema.parse({ configured: false, repositories: [] });
|
|
13952
|
+
throw error;
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13955
|
+
async connectGitHub(hubEpoch) {
|
|
13956
|
+
return external_exports.object({
|
|
13957
|
+
connectionId: external_exports.string(),
|
|
13958
|
+
userCode: external_exports.string(),
|
|
13959
|
+
verificationUri: external_exports.string().url(),
|
|
13960
|
+
expiresAt: external_exports.number(),
|
|
13961
|
+
interval: external_exports.number(),
|
|
13962
|
+
installationUrl: external_exports.string().url()
|
|
13963
|
+
}).parse(await this.request("/github/device", { hubEpoch }));
|
|
13964
|
+
}
|
|
13965
|
+
async pollGitHub(hubEpoch, connectionId) {
|
|
13966
|
+
return external_exports.discriminatedUnion("state", [
|
|
13967
|
+
external_exports.object({ state: external_exports.literal("pending"), interval: external_exports.number() }),
|
|
13968
|
+
external_exports.object({
|
|
13969
|
+
state: external_exports.literal("authorized"),
|
|
13970
|
+
authorizationId: external_exports.string(),
|
|
13971
|
+
expiresAt: external_exports.number(),
|
|
13972
|
+
repositories: external_exports.array(githubRepositorySchema)
|
|
13973
|
+
})
|
|
13974
|
+
]).parse(await this.request("/github/device/poll", { hubEpoch, connectionId }));
|
|
13975
|
+
}
|
|
13976
|
+
async bindRepository(hubEpoch, authorizationId, repository, settings) {
|
|
13977
|
+
return repositoryBindingSchema.parse(
|
|
13978
|
+
await this.request("/repositories", {
|
|
13979
|
+
hubEpoch,
|
|
13980
|
+
authorizationId,
|
|
13981
|
+
installationId: repository.installationId,
|
|
13982
|
+
repositoryId: repository.repositoryId,
|
|
13983
|
+
...settings
|
|
13984
|
+
})
|
|
13985
|
+
);
|
|
13986
|
+
}
|
|
13987
|
+
async updateRepository(hubEpoch, id, settings) {
|
|
13988
|
+
return repositoryBindingSchema.parse(
|
|
13989
|
+
await this.request(`/repositories/${encodeURIComponent(id)}`, {
|
|
13990
|
+
hubEpoch,
|
|
13991
|
+
...settings
|
|
13992
|
+
})
|
|
13993
|
+
);
|
|
13994
|
+
}
|
|
13995
|
+
disconnectRepository(hubEpoch, id) {
|
|
13996
|
+
return this.request(`/repositories/${encodeURIComponent(id)}/disconnect`, { hubEpoch });
|
|
13997
|
+
}
|
|
13998
|
+
receipt(commandId) {
|
|
13999
|
+
return this.request(`/commands/${encodeURIComponent(commandId)}`);
|
|
14000
|
+
}
|
|
14001
|
+
deleteSession(hubEpoch, sessionId) {
|
|
14002
|
+
return this.request("/sessions/delete", { hubEpoch, sessionId });
|
|
14003
|
+
}
|
|
14004
|
+
async pendingDevice(code) {
|
|
14005
|
+
return external_exports.object({ name: external_exports.string() }).parse(await this.request(`/auth/device?code=${encodeURIComponent(code)}`));
|
|
14006
|
+
}
|
|
14007
|
+
/** Approves the macOS app's sign-in for this client's Team: the token is bound to it. */
|
|
14008
|
+
approveDevice(userCode) {
|
|
14009
|
+
return this.request("/auth/device/approve", { userCode });
|
|
14010
|
+
}
|
|
14011
|
+
/** The machine asking to Connect with a code it printed (`drisp runner connect` with no code; root ADR-0005). */
|
|
14012
|
+
async pendingConnect(code) {
|
|
14013
|
+
return connectPendingResponseSchema.parse(await this.request(`/runners/connect/pending?code=${encodeURIComponent(code)}`));
|
|
14014
|
+
}
|
|
14015
|
+
/** Connects the machine that printed `code` to this client's Team, as a Runner with `audience`. Team needs an admin. */
|
|
14016
|
+
approveConnect(code, audience, hubEpoch) {
|
|
14017
|
+
return this.request("/runners/connect/approve", { code, audience, ...hubEpoch === void 0 ? {} : { hubEpoch } });
|
|
14018
|
+
}
|
|
14019
|
+
/** Refuses the machine that printed `code`; its wait ends without a credential. */
|
|
14020
|
+
denyConnect(code) {
|
|
14021
|
+
return this.request("/runners/connect/deny", { code });
|
|
14022
|
+
}
|
|
14023
|
+
async historyPage(runId, after = 0) {
|
|
14024
|
+
const page = external_exports.object({
|
|
14025
|
+
through: external_exports.number(),
|
|
14026
|
+
archives: external_exports.array(
|
|
14027
|
+
external_exports.object({
|
|
14028
|
+
id: external_exports.string(),
|
|
14029
|
+
checksum: external_exports.string(),
|
|
14030
|
+
first_seq: external_exports.number(),
|
|
14031
|
+
last_seq: external_exports.number()
|
|
14032
|
+
})
|
|
14033
|
+
),
|
|
14034
|
+
batches: external_exports.array(external_exports.object({ firstSeq: external_exports.number(), records: external_exports.array(historyRecordSchema) }))
|
|
14035
|
+
}).parse(await this.history(runId, after));
|
|
14036
|
+
const records = [];
|
|
14037
|
+
const archive = page.archives[0];
|
|
14038
|
+
if (archive) {
|
|
14039
|
+
const response = await fetch(
|
|
14040
|
+
`${this.baseUrl}/v2/orgs/${encodeURIComponent(this.orgId)}/runs/${encodeURIComponent(runId)}/objects/${encodeURIComponent(archive.id)}`,
|
|
14041
|
+
{ headers: { Authorization: `Bearer ${await this.token()}` } }
|
|
14042
|
+
);
|
|
14043
|
+
if (!response.ok) throw new Error(`history_download_${response.status}`);
|
|
14044
|
+
const bytes = await response.arrayBuffer();
|
|
14045
|
+
const checksum = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14046
|
+
if (checksum !== archive.checksum) throw new Error("history_integrity_failure");
|
|
14047
|
+
const text = await new Response(
|
|
14048
|
+
new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"))
|
|
14049
|
+
).text();
|
|
14050
|
+
for (const line of text.trim().split("\n"))
|
|
14051
|
+
records.push(
|
|
14052
|
+
external_exports.object({ seq: external_exports.number(), record: historyRecordSchema }).parse(JSON.parse(line))
|
|
14053
|
+
);
|
|
14054
|
+
}
|
|
14055
|
+
for (const batch of page.batches)
|
|
14056
|
+
batch.records.forEach(
|
|
14057
|
+
(record, index) => records.push({ seq: batch.firstSeq + index, record })
|
|
14058
|
+
);
|
|
14059
|
+
records.sort((a, b) => a.seq - b.seq);
|
|
14060
|
+
let through = after;
|
|
14061
|
+
const contiguous = [];
|
|
14062
|
+
for (const row of records) {
|
|
14063
|
+
if (row.seq <= through) continue;
|
|
14064
|
+
if (row.seq !== through + 1) break;
|
|
14065
|
+
contiguous.push(row);
|
|
14066
|
+
through = row.seq;
|
|
14067
|
+
}
|
|
14068
|
+
if (through === after && page.through > after) throw new Error("history_gap");
|
|
14069
|
+
return { through, availableThrough: page.through, records: contiguous };
|
|
14070
|
+
}
|
|
14071
|
+
watch(hubEpoch, runIds, update, error) {
|
|
14072
|
+
let stopped = false;
|
|
14073
|
+
let socket;
|
|
14074
|
+
let timer;
|
|
14075
|
+
let delay = 250;
|
|
14076
|
+
const runs = /* @__PURE__ */ new Map();
|
|
14077
|
+
const connect = async () => {
|
|
14078
|
+
try {
|
|
14079
|
+
const { ticket } = external_exports.object({ ticket: external_exports.string() }).parse(await this.request("/connect-ticket", { hubEpoch }));
|
|
14080
|
+
if (stopped) return;
|
|
14081
|
+
const endpoint = new URL(
|
|
14082
|
+
`/v2/orgs/${encodeURIComponent(this.orgId)}/connect`,
|
|
14083
|
+
this.baseUrl
|
|
14084
|
+
);
|
|
14085
|
+
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:";
|
|
14086
|
+
const ws = new WebSocket(endpoint, ["drisp-v2", `ticket.${ticket}`]);
|
|
14087
|
+
socket = ws;
|
|
14088
|
+
ws.onopen = () => ws.send(JSON.stringify({ type: "watch", hubEpoch, runIds }));
|
|
14089
|
+
ws.onmessage = (event) => {
|
|
14090
|
+
try {
|
|
14091
|
+
if (typeof event.data !== "string" || new TextEncoder().encode(event.data).length > 8 * 1024 * 1024)
|
|
14092
|
+
throw new Error("frame_too_large");
|
|
14093
|
+
const message = JSON.parse(String(event.data));
|
|
14094
|
+
if (message.type === "error") throw new Error(message.code);
|
|
14095
|
+
if (message.hubEpoch !== hubEpoch) throw new Error("recovery_required");
|
|
14096
|
+
const header = external_exports.object({ subscriptionId: external_exports.string(), type: external_exports.enum(["snapshot", "change"]) }).parse(message);
|
|
14097
|
+
let through;
|
|
14098
|
+
if (header.type === "snapshot") {
|
|
14099
|
+
const snapshot = external_exports.object({
|
|
14100
|
+
controlThrough: external_exports.number().int().nonnegative(),
|
|
14101
|
+
runs: external_exports.array(runViewSchema)
|
|
14102
|
+
}).parse(message);
|
|
14103
|
+
runs.clear();
|
|
14104
|
+
for (const run of snapshot.runs) runs.set(run.runId, run);
|
|
14105
|
+
through = snapshot.controlThrough;
|
|
14106
|
+
} else {
|
|
14107
|
+
const change = external_exports.object({
|
|
14108
|
+
through: external_exports.number().int().nonnegative(),
|
|
14109
|
+
runs: external_exports.array(external_exports.object({ seq: external_exports.number(), run: runViewSchema }))
|
|
14110
|
+
}).parse(message);
|
|
14111
|
+
for (const { run } of change.runs)
|
|
14112
|
+
if ((runs.get(run.runId)?.recordRevision ?? 0) < run.recordRevision)
|
|
14113
|
+
runs.set(run.runId, run);
|
|
14114
|
+
through = change.through;
|
|
14115
|
+
}
|
|
14116
|
+
update([...runs.values()]);
|
|
14117
|
+
delay = 250;
|
|
14118
|
+
ws.send(
|
|
14119
|
+
JSON.stringify({ type: "watch.ack", subscriptionId: header.subscriptionId, through })
|
|
14120
|
+
);
|
|
14121
|
+
} catch (cause) {
|
|
14122
|
+
if (cause instanceof Error && cause.message === "reset_required") {
|
|
14123
|
+
ws.close();
|
|
14124
|
+
return;
|
|
14125
|
+
}
|
|
14126
|
+
stopped = true;
|
|
14127
|
+
error(cause);
|
|
14128
|
+
ws.close();
|
|
14129
|
+
}
|
|
14130
|
+
};
|
|
14131
|
+
ws.onclose = () => {
|
|
14132
|
+
if (!stopped) {
|
|
14133
|
+
timer = setTimeout(() => {
|
|
14134
|
+
void connect();
|
|
14135
|
+
}, delay);
|
|
14136
|
+
delay = Math.min(delay * 2, 3e4);
|
|
14137
|
+
}
|
|
14138
|
+
};
|
|
14139
|
+
} catch (cause) {
|
|
14140
|
+
if (stopped) return;
|
|
14141
|
+
error(cause);
|
|
14142
|
+
if (cause instanceof ClientError && cause.status < 500 && cause.status !== 429) return;
|
|
14143
|
+
timer = setTimeout(() => {
|
|
14144
|
+
void connect();
|
|
14145
|
+
}, delay);
|
|
14146
|
+
delay = Math.min(delay * 2, 3e4);
|
|
14147
|
+
}
|
|
14148
|
+
};
|
|
14149
|
+
void connect();
|
|
14150
|
+
return () => {
|
|
14151
|
+
stopped = true;
|
|
14152
|
+
if (timer) clearTimeout(timer);
|
|
14153
|
+
socket?.close();
|
|
14154
|
+
};
|
|
14155
|
+
}
|
|
14156
|
+
};
|
|
14157
|
+
|
|
14158
|
+
// src/core/build/state.ts
|
|
14159
|
+
var ATTEMPT_OUTCOMES = [
|
|
14160
|
+
"completed",
|
|
14161
|
+
"failed",
|
|
14162
|
+
"cancelled",
|
|
14163
|
+
"template_mismatch"
|
|
14164
|
+
];
|
|
14165
|
+
var attemptSchema = external_exports.strictObject({
|
|
14166
|
+
n: external_exports.number().int().min(1).max(2),
|
|
14167
|
+
runId: external_exports.string().min(1),
|
|
14168
|
+
tokenCap: external_exports.number().int().positive(),
|
|
14169
|
+
startedAt: external_exports.string(),
|
|
14170
|
+
/** Where the attempt worked (chosen while building slice 2): later ticks check its PR and remove its worktree. */
|
|
14171
|
+
branch: external_exports.string().min(1),
|
|
14172
|
+
worktree: external_exports.string().min(1),
|
|
14173
|
+
outcome: external_exports.enum(ATTEMPT_OUTCOMES).optional()
|
|
14174
|
+
});
|
|
14175
|
+
var buildStateSchema = external_exports.strictObject({
|
|
14176
|
+
version: external_exports.literal(1),
|
|
14177
|
+
orchestratorRunId: external_exports.string().min(1),
|
|
14178
|
+
projectId: external_exports.string().min(1),
|
|
14179
|
+
slugId: external_exports.string().regex(/^[a-z0-9]+$/),
|
|
14180
|
+
templateVersion: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
14181
|
+
approvedAt: external_exports.string(),
|
|
14182
|
+
approved: external_exports.strictObject({ groups: external_exports.array(external_exports.array(external_exports.string())) }),
|
|
14183
|
+
attempts: external_exports.record(external_exports.string(), external_exports.array(attemptSchema)),
|
|
14184
|
+
needsPerson: external_exports.record(external_exports.string(), external_exports.string()),
|
|
14185
|
+
tickCount: external_exports.number().int().nonnegative(),
|
|
14186
|
+
lastTickAt: external_exports.string(),
|
|
14187
|
+
/**
|
|
14188
|
+
* Additive (chosen while building slice 3): the entries a tick had seen done, for rebasing parked children's
|
|
14189
|
+
* branches after a merge. No longer written since slice 4 (a parked child rebases itself), still read so a state
|
|
14190
|
+
* written before parses.
|
|
14191
|
+
*/
|
|
14192
|
+
done: external_exports.array(external_exports.string()).optional(),
|
|
14193
|
+
/** The merged issues the Orchestrator was already asked to repair in Linear, so it is asked once. */
|
|
14194
|
+
repaired: external_exports.array(external_exports.string()).optional()
|
|
14195
|
+
});
|
|
14196
|
+
|
|
14197
|
+
// src/core/build/childStart.ts
|
|
14198
|
+
function childRunId(orchestratorRunId, identifier, attempt) {
|
|
14199
|
+
return `${orchestratorRunId}:${identifier}:a${attempt}`;
|
|
14200
|
+
}
|
|
14201
|
+
function childPrompt(child) {
|
|
14202
|
+
return [
|
|
14203
|
+
`${child.identifier} ${child.title}`,
|
|
14204
|
+
`Linear: ${child.url}`,
|
|
14205
|
+
`Branch: ${child.branch}`,
|
|
14206
|
+
`Worktree: ${child.worktree}`,
|
|
14207
|
+
`Build: ${child.orchestratorRunId}`
|
|
14208
|
+
].join("\n");
|
|
14209
|
+
}
|
|
14210
|
+
function childStartIntent(child, parent) {
|
|
14211
|
+
const runId = childRunId(
|
|
14212
|
+
child.orchestratorRunId,
|
|
14213
|
+
child.identifier,
|
|
14214
|
+
child.attempt
|
|
14215
|
+
);
|
|
14216
|
+
return {
|
|
14217
|
+
runId,
|
|
14218
|
+
intent: {
|
|
14219
|
+
kind: "start",
|
|
14220
|
+
sessionId: runId,
|
|
14221
|
+
runnerId: parent.runnerId,
|
|
14222
|
+
scope: parent.scope,
|
|
14223
|
+
key: `issue:${child.issueId}`,
|
|
14224
|
+
parent: { runId: child.orchestratorRunId, tokenCap: child.tokenCap },
|
|
14225
|
+
spec: {
|
|
14226
|
+
prompt: childPrompt(child),
|
|
14227
|
+
projectDir: child.worktree,
|
|
14228
|
+
harness: "claude-code",
|
|
14229
|
+
workflow: child.workflowName
|
|
14230
|
+
}
|
|
14231
|
+
}
|
|
14232
|
+
};
|
|
14233
|
+
}
|
|
14234
|
+
|
|
14235
|
+
// src/core/build/tickDecision.ts
|
|
14236
|
+
var MAX_ATTEMPTS = 2;
|
|
14237
|
+
function mayRetry(attempts) {
|
|
14238
|
+
return attempts.length < MAX_ATTEMPTS && attempts.at(-1)?.outcome === "failed";
|
|
14239
|
+
}
|
|
14240
|
+
var STOPPED_CLOSE_REASONS = /* @__PURE__ */ new Set(["cancelled_before_start", "abandoned"]);
|
|
14241
|
+
function outcomeOf(view) {
|
|
14242
|
+
if (view.closeReason === "workflow_template_mismatch")
|
|
14243
|
+
return "template_mismatch";
|
|
14244
|
+
const status = view.execution?.status;
|
|
14245
|
+
if (status === "completed" || status === "failed" || status === "cancelled")
|
|
14246
|
+
return status;
|
|
14247
|
+
if (view.cancellation || STOPPED_CLOSE_REASONS.has(view.closeReason ?? ""))
|
|
14248
|
+
return "cancelled";
|
|
14249
|
+
return "failed";
|
|
14250
|
+
}
|
|
14251
|
+
function buildLimits(own, delegation) {
|
|
14252
|
+
if (!delegation) return own;
|
|
14253
|
+
return {
|
|
14254
|
+
...own,
|
|
14255
|
+
maxChildren: Math.min(own.maxChildren, delegation.maxChildren),
|
|
14256
|
+
tokenBudget: Math.min(own.tokenBudget, delegation.tokenBudget)
|
|
14257
|
+
};
|
|
14258
|
+
}
|
|
14259
|
+
function occupies(view) {
|
|
14260
|
+
return !view || !view.closed && view.execution?.status !== "awaiting_attention";
|
|
14261
|
+
}
|
|
14262
|
+
function assessTick(facts) {
|
|
14263
|
+
const { entries, orchestratorRunId } = facts;
|
|
14264
|
+
const state = structuredClone(facts.state);
|
|
14265
|
+
const views = new Map(facts.children.map((view) => [view.runId, view]));
|
|
14266
|
+
const issues = new Map(facts.issues.map((issue) => [issue.id, issue]));
|
|
14267
|
+
const attemptsOf = (id) => state.attempts[id] ?? [];
|
|
14268
|
+
for (const entry of entries) {
|
|
14269
|
+
const prefix = `${orchestratorRunId}:${entry.identifier}:a`;
|
|
14270
|
+
for (const view of facts.children) {
|
|
14271
|
+
if (!view.runId.startsWith(prefix)) continue;
|
|
14272
|
+
const n = Number(view.runId.slice(prefix.length));
|
|
14273
|
+
if (!(n === 1 || n === 2)) continue;
|
|
14274
|
+
if (attemptsOf(entry.id).some((a) => a.runId === view.runId)) continue;
|
|
14275
|
+
state.attempts[entry.id] = [
|
|
14276
|
+
...attemptsOf(entry.id),
|
|
14277
|
+
{
|
|
14278
|
+
n,
|
|
14279
|
+
runId: view.runId,
|
|
14280
|
+
tokenCap: view.parent?.tokenCap ?? entry.tokenCap,
|
|
14281
|
+
startedAt: facts.now,
|
|
14282
|
+
...facts.locate(entry)
|
|
14283
|
+
}
|
|
14284
|
+
].sort((a, b) => a.n - b.n);
|
|
14285
|
+
}
|
|
14286
|
+
}
|
|
14287
|
+
for (const attempts of Object.values(state.attempts))
|
|
14288
|
+
for (const attempt of attempts) {
|
|
14289
|
+
const view = views.get(attempt.runId);
|
|
14290
|
+
if (view?.closed && !attempt.outcome) attempt.outcome = outcomeOf(view);
|
|
14291
|
+
}
|
|
14292
|
+
const branchOf = (entry) => attemptsOf(entry.id).at(-1)?.branch ?? facts.locate(entry).branch;
|
|
14293
|
+
for (const identifier of facts.rebaseFailed) {
|
|
14294
|
+
const entry = entries.find((each) => each.identifier === identifier);
|
|
14295
|
+
if (entry)
|
|
14296
|
+
state.needsPerson[entry.id] = BUILD_STRINGS.rebaseFailed(branchOf(entry));
|
|
14297
|
+
}
|
|
14298
|
+
const pullRequests = /* @__PURE__ */ new Map();
|
|
14299
|
+
const prsOf = (entry) => {
|
|
14300
|
+
const branch = attemptsOf(entry.id).at(-1)?.branch ?? issues.get(entry.id)?.branchName;
|
|
14301
|
+
if (!branch) return void 0;
|
|
14302
|
+
if (!pullRequests.has(branch))
|
|
14303
|
+
pullRequests.set(branch, facts.pullRequests(branch));
|
|
14304
|
+
return pullRequests.get(branch);
|
|
14305
|
+
};
|
|
14306
|
+
const status = /* @__PURE__ */ new Map();
|
|
14307
|
+
const skippedBlocker = (id) => {
|
|
14308
|
+
const linear = issues.get(id)?.state.type;
|
|
14309
|
+
return linear === "completed" || linear === "canceled" ? "done" : "blocked";
|
|
14310
|
+
};
|
|
14311
|
+
const repair = [];
|
|
14312
|
+
const repaired = new Set(state.repaired ?? []);
|
|
14313
|
+
for (const entry of entries) {
|
|
14314
|
+
const attempts = attemptsOf(entry.id);
|
|
14315
|
+
const newest = attempts.at(-1);
|
|
14316
|
+
const view = newest && views.get(newest.runId);
|
|
14317
|
+
const linear = issues.get(entry.id)?.state.type;
|
|
14318
|
+
const statusOf = () => {
|
|
14319
|
+
if (linear === "completed" || linear === "canceled") return "done";
|
|
14320
|
+
const prs = prsOf(entry);
|
|
14321
|
+
if (prs?.merged) {
|
|
14322
|
+
if (!repaired.has(entry.id) && !(newest && occupies(view))) {
|
|
14323
|
+
const prUrl = prs.mergedUrl ?? branchOf(entry);
|
|
14324
|
+
repair.push({
|
|
14325
|
+
id: entry.id,
|
|
14326
|
+
identifier: entry.identifier,
|
|
14327
|
+
prUrl,
|
|
14328
|
+
comment: BUILD_STRINGS.repairComment(prUrl, orchestratorRunId)
|
|
14329
|
+
});
|
|
14330
|
+
repaired.add(entry.id);
|
|
14331
|
+
}
|
|
14332
|
+
return "done";
|
|
14333
|
+
}
|
|
14334
|
+
if (newest && (!view || !view.closed)) return "live";
|
|
14335
|
+
if (newest?.outcome === "template_mismatch") return "replan";
|
|
14336
|
+
if (newest?.outcome === "completed")
|
|
14337
|
+
state.needsPerson[entry.id] ??= BUILD_STRINGS.completedUnmerged;
|
|
14338
|
+
if (state.needsPerson[entry.id] || newest && !mayRetry(attempts))
|
|
14339
|
+
return "needs_person";
|
|
14340
|
+
const blockers = entry.blockedBy.map(
|
|
14341
|
+
(id) => status.get(id) ?? skippedBlocker(id)
|
|
14342
|
+
);
|
|
14343
|
+
if (blockers.some(
|
|
14344
|
+
(b) => b === "needs_person" || b === "replan" || b === "blocked"
|
|
14345
|
+
))
|
|
14346
|
+
return "blocked";
|
|
14347
|
+
return blockers.every((b) => b === "done") ? "ready" : "pending";
|
|
14348
|
+
};
|
|
14349
|
+
status.set(entry.id, statusOf());
|
|
14350
|
+
if (status.get(entry.id) === "done" && state.needsPerson[entry.id] === BUILD_STRINGS.completedUnmerged)
|
|
14351
|
+
delete state.needsPerson[entry.id];
|
|
14352
|
+
}
|
|
14353
|
+
if (repaired.size > 0) state.repaired = [...repaired];
|
|
14354
|
+
let parkReason = facts.templateVersion !== state.templateVersion ? "template" : state.tickCount >= facts.limits.tickAllowance ? "budget" : void 0;
|
|
14355
|
+
const occupying = entries.filter((entry) => {
|
|
14356
|
+
const newest = attemptsOf(entry.id).at(-1);
|
|
14357
|
+
return newest !== void 0 && occupies(views.get(newest.runId));
|
|
14358
|
+
}).length;
|
|
14359
|
+
const queue = [];
|
|
14360
|
+
if (!parkReason) {
|
|
14361
|
+
let capped = Object.values(state.attempts).flat().reduce((sum, attempt) => sum + attempt.tokenCap, 0);
|
|
14362
|
+
for (const entry of entries) {
|
|
14363
|
+
if (status.get(entry.id) !== "ready") continue;
|
|
14364
|
+
if (capped + entry.tokenCap > facts.limits.tokenBudget) {
|
|
14365
|
+
parkReason = "budget";
|
|
14366
|
+
break;
|
|
14367
|
+
}
|
|
14368
|
+
queue.push(entry);
|
|
14369
|
+
capped += entry.tokenCap;
|
|
14370
|
+
}
|
|
14371
|
+
}
|
|
14372
|
+
return {
|
|
14373
|
+
state,
|
|
14374
|
+
status,
|
|
14375
|
+
repair,
|
|
14376
|
+
queue,
|
|
14377
|
+
slots: Math.max(0, facts.limits.maxChildren - occupying),
|
|
14378
|
+
...parkReason ? { parkReason } : {}
|
|
14379
|
+
};
|
|
14380
|
+
}
|
|
14381
|
+
function tickAction(input) {
|
|
14382
|
+
const live = input.children.filter((view) => !view.closed);
|
|
14383
|
+
if (input.started > 0 || live.some((view) => occupies(view)))
|
|
14384
|
+
return { action: "wait" };
|
|
14385
|
+
const remaining = input.entries.some(
|
|
14386
|
+
(entry) => ["ready", "pending"].includes(input.status.get(entry.id))
|
|
14387
|
+
);
|
|
14388
|
+
if (input.parkReason && remaining)
|
|
14389
|
+
return {
|
|
14390
|
+
action: "park",
|
|
14391
|
+
reason: input.parkReason,
|
|
14392
|
+
message: input.parkReason === "budget" ? BUILD_STRINGS.budgetPark : BUILD_STRINGS.templatePark
|
|
14393
|
+
};
|
|
14394
|
+
if (live.length > 0) return { action: "wait" };
|
|
14395
|
+
const done = input.entries.filter(
|
|
14396
|
+
(entry) => input.status.get(entry.id) === "done"
|
|
14397
|
+
).length;
|
|
14398
|
+
return {
|
|
14399
|
+
action: "complete",
|
|
14400
|
+
message: `Built ${done} of ${input.entries.length}`
|
|
14401
|
+
};
|
|
14402
|
+
}
|
|
14403
|
+
|
|
14404
|
+
// src/core/build/tick.ts
|
|
14405
|
+
var UNAVAILABLE_RETRY_MS = 2e3;
|
|
14406
|
+
var UNAVAILABLE_RETRY_FOR_MS = 3e4;
|
|
14407
|
+
var NEVER = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
14408
|
+
var notStarted = () => ({
|
|
14409
|
+
action: "complete",
|
|
14410
|
+
message: BUILD_STRINGS.notStarted
|
|
14411
|
+
});
|
|
14412
|
+
function pushBranch(worktree, branch) {
|
|
14413
|
+
git(worktree, ["push", "--quiet", "origin", `${branch}:${branch}`]);
|
|
14414
|
+
}
|
|
14415
|
+
async function startChild(deps, hubEpoch, start) {
|
|
14416
|
+
for (let waited = 0; ; waited += UNAVAILABLE_RETRY_MS) {
|
|
14417
|
+
try {
|
|
14418
|
+
await deps.hub.submit({
|
|
14419
|
+
commandId: randomUUID2(),
|
|
14420
|
+
hubEpoch,
|
|
14421
|
+
runId: start.runId,
|
|
14422
|
+
intent: start.intent
|
|
14423
|
+
});
|
|
14424
|
+
return { kind: "started" };
|
|
14425
|
+
} catch (error) {
|
|
14426
|
+
if (!(error instanceof ClientError) || error.status >= 500)
|
|
14427
|
+
return { kind: "unreachable" };
|
|
14428
|
+
if (error.code === "key_busy") return { kind: "key_busy" };
|
|
14429
|
+
if (error.code === "delegation_budget_exhausted") return { kind: "budget" };
|
|
14430
|
+
if (error.code !== "workflow_unavailable")
|
|
14431
|
+
return { kind: "refused", code: error.code };
|
|
14432
|
+
if (waited >= UNAVAILABLE_RETRY_FOR_MS) return { kind: "unavailable" };
|
|
14433
|
+
await deps.sleep(UNAVAILABLE_RETRY_MS);
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
}
|
|
14437
|
+
function currentPlan(plan, state) {
|
|
14438
|
+
const { needsPerson: _previous, ...approved } = plan;
|
|
14439
|
+
const entries = plan.groups.flatMap((group) => group.issues);
|
|
14440
|
+
const needsPerson = entries.filter((entry) => state.needsPerson[entry.id]).map((entry) => ({ id: entry.id, reason: state.needsPerson[entry.id] }));
|
|
14441
|
+
return needsPerson.length > 0 ? { ...approved, needsPerson } : approved;
|
|
14442
|
+
}
|
|
14443
|
+
async function runBuildTick(deps) {
|
|
14444
|
+
const orchestratorRunId = requireEnv(deps.env, "DRISP_RUN_ID");
|
|
14445
|
+
const runnerId = requireEnv(deps.env, "DRISP_RUNNER_ID");
|
|
14446
|
+
const artifacts = requireEnv(deps.env, "DRISP_RUN_ARTIFACTS");
|
|
14447
|
+
if (!deps.inputPath)
|
|
14448
|
+
throw new BuildUsageError("Usage: drisp build tick --input <path>");
|
|
14449
|
+
let input;
|
|
14450
|
+
try {
|
|
14451
|
+
input = readInput(path7.resolve(deps.folder, deps.inputPath));
|
|
14452
|
+
} catch (error) {
|
|
14453
|
+
if (error instanceof PlanFailure) throw new BuildUsageError(error.message);
|
|
14454
|
+
throw error;
|
|
14455
|
+
}
|
|
14456
|
+
const worktree = ensureBuildWorktree({
|
|
14457
|
+
folder: deps.folder,
|
|
14458
|
+
slugId: input.project.slugId,
|
|
14459
|
+
worktreesRoot: deps.worktreesRoot
|
|
14460
|
+
});
|
|
14461
|
+
const designDir = path7.join(
|
|
14462
|
+
worktree.path,
|
|
14463
|
+
".athena",
|
|
14464
|
+
"workflows",
|
|
14465
|
+
input.project.slugId
|
|
14466
|
+
);
|
|
14467
|
+
const relativeDesignDir = path7.relative(worktree.path, designDir);
|
|
14468
|
+
const statePath = path7.join(designDir, "build-state.json");
|
|
14469
|
+
const planPath = path7.join(designDir, "plan.json");
|
|
14470
|
+
const parsedPlan = buildPlanSchema.safeParse(readJson(planPath));
|
|
14471
|
+
if (!parsedPlan.success || parsedPlan.data.orchestratorRunId !== orchestratorRunId)
|
|
14472
|
+
return notStarted();
|
|
14473
|
+
const plan = parsedPlan.data;
|
|
14474
|
+
const push = deps.push ?? pushBranch;
|
|
14475
|
+
let stored;
|
|
14476
|
+
const parsedState = buildStateSchema.safeParse(readJson(statePath));
|
|
14477
|
+
if (parsedState.success && parsedState.data.orchestratorRunId === orchestratorRunId) {
|
|
14478
|
+
stored = parsedState.data;
|
|
14479
|
+
} else {
|
|
14480
|
+
if (!isStartAnswer(readJson(path7.join(artifacts, "answer.json"))))
|
|
14481
|
+
return notStarted();
|
|
14482
|
+
stored = {
|
|
14483
|
+
version: 1,
|
|
14484
|
+
orchestratorRunId,
|
|
14485
|
+
projectId: plan.projectId,
|
|
14486
|
+
slugId: input.project.slugId,
|
|
14487
|
+
templateVersion: plan.templateVersion,
|
|
14488
|
+
approvedAt: deps.now().toISOString(),
|
|
14489
|
+
approved: {
|
|
14490
|
+
groups: plan.groups.map((group) => group.issues.map((issue) => issue.id))
|
|
14491
|
+
},
|
|
14492
|
+
attempts: {},
|
|
14493
|
+
needsPerson: {},
|
|
14494
|
+
tickCount: 0,
|
|
14495
|
+
lastTickAt: NEVER
|
|
14496
|
+
};
|
|
14497
|
+
push(worktree.path, worktree.branch);
|
|
14498
|
+
}
|
|
14499
|
+
if (!(Date.parse(input.readAt) > Date.parse(stored.lastTickAt)))
|
|
14500
|
+
return { action: "refresh_input" };
|
|
14501
|
+
stored.tickCount += 1;
|
|
14502
|
+
stored.lastTickAt = deps.now().toISOString();
|
|
14503
|
+
const parent = await deps.hub.run(orchestratorRunId);
|
|
14504
|
+
const children = await deps.hub.runs({ parent: orchestratorRunId });
|
|
14505
|
+
const approved = new Set(stored.approved.groups.flat());
|
|
14506
|
+
const entries = plan.groups.flatMap((group) => group.issues).filter((entry) => approved.has(entry.id));
|
|
14507
|
+
const issues = new Map(input.issues.map((issue) => [issue.id, issue]));
|
|
14508
|
+
const assessment = assessTick({
|
|
14509
|
+
orchestratorRunId,
|
|
14510
|
+
entries,
|
|
14511
|
+
issues: input.issues,
|
|
14512
|
+
state: stored,
|
|
14513
|
+
children: children.runs,
|
|
14514
|
+
templateVersion,
|
|
14515
|
+
limits: buildLimits(ORCHESTRATOR_LIMITS, parent.delegation),
|
|
14516
|
+
pullRequests: (branch) => deps.pullRequests(deps.folder, branch),
|
|
14517
|
+
locate: (entry) => locate(deps, entry, issues.get(entry.id)?.branchName),
|
|
14518
|
+
rebaseFailed: deps.rebaseFailed ?? [],
|
|
14519
|
+
now: deps.now().toISOString()
|
|
14520
|
+
});
|
|
14521
|
+
const { state, status } = assessment;
|
|
14522
|
+
let parkReason = assessment.parkReason;
|
|
14523
|
+
const record = () => {
|
|
14524
|
+
writeFile(statePath, json(state));
|
|
14525
|
+
const files = [path7.join(relativeDesignDir, "build-state.json")];
|
|
14526
|
+
const emitted = json(currentPlan(plan, state));
|
|
14527
|
+
if (emitted !== fs7.readFileSync(planPath, "utf-8")) {
|
|
14528
|
+
writeFile(planPath, emitted);
|
|
14529
|
+
writeFile(path7.join(artifacts, "build_plan.json"), emitted);
|
|
14530
|
+
files.push(path7.join(relativeDesignDir, "plan.json"));
|
|
14531
|
+
}
|
|
14532
|
+
if (commitBuildFiles(
|
|
14533
|
+
worktree.path,
|
|
14534
|
+
files,
|
|
14535
|
+
`drisp build: tick ${state.tickCount} for ${state.slugId}`
|
|
14536
|
+
))
|
|
14537
|
+
push(worktree.path, worktree.branch);
|
|
14538
|
+
};
|
|
14539
|
+
if (assessment.repair.length > 0) {
|
|
14540
|
+
record();
|
|
14541
|
+
return { action: "repair", issues: assessment.repair };
|
|
14542
|
+
}
|
|
14543
|
+
const deferred = [];
|
|
14544
|
+
const behind = [];
|
|
14545
|
+
let started = 0;
|
|
14546
|
+
if (assessment.queue.length > 0 && assessment.slots > 0) {
|
|
14547
|
+
for (const entry of assessment.queue) {
|
|
14548
|
+
if (started >= assessment.slots) break;
|
|
14549
|
+
const issue = issues.get(entry.id);
|
|
14550
|
+
if (!issue) continue;
|
|
14551
|
+
const child = ensureChildWorktree({
|
|
14552
|
+
folder: deps.folder,
|
|
14553
|
+
worktreesRoot: deps.worktreesRoot,
|
|
14554
|
+
issue
|
|
14555
|
+
});
|
|
14556
|
+
if (child.status === "needs_person") {
|
|
14557
|
+
state.needsPerson[entry.id] = child.reason;
|
|
14558
|
+
status.set(entry.id, "needs_person");
|
|
14559
|
+
continue;
|
|
14560
|
+
}
|
|
14561
|
+
if (child.existed && !hasUncommittedWork(child.path) && behindMain(deps.folder, child.path)) {
|
|
14562
|
+
behind.push({
|
|
14563
|
+
identifier: entry.identifier,
|
|
14564
|
+
worktree: child.path,
|
|
14565
|
+
branch: child.branch
|
|
14566
|
+
});
|
|
14567
|
+
continue;
|
|
14568
|
+
}
|
|
14569
|
+
const entryDir = path7.join(relativeDesignDir, entry.identifier);
|
|
14570
|
+
git(worktree.path, ["checkout", plan.commit, "--", entryDir]);
|
|
14571
|
+
installWorkflowFromSource(
|
|
14572
|
+
{
|
|
14573
|
+
kind: "filesystem",
|
|
14574
|
+
workflowPath: path7.join(worktree.path, entryDir, "workflow.json"),
|
|
14575
|
+
version: plan.commit
|
|
14576
|
+
},
|
|
14577
|
+
entry.workflowName
|
|
14578
|
+
);
|
|
14579
|
+
const attempt = (state.attempts[entry.id]?.length ?? 0) + 1;
|
|
14580
|
+
const start = childStartIntent(
|
|
14581
|
+
{
|
|
14582
|
+
orchestratorRunId,
|
|
14583
|
+
identifier: entry.identifier,
|
|
14584
|
+
issueId: entry.id,
|
|
14585
|
+
title: issue.title,
|
|
14586
|
+
url: issue.url,
|
|
14587
|
+
branch: child.branch,
|
|
14588
|
+
worktree: child.path,
|
|
14589
|
+
workflowName: entry.workflowName,
|
|
14590
|
+
tokenCap: entry.tokenCap,
|
|
14591
|
+
attempt
|
|
14592
|
+
},
|
|
14593
|
+
{ runnerId, scope: parent.scope }
|
|
14594
|
+
);
|
|
14595
|
+
const result = await startChild(deps, children.hubEpoch, start);
|
|
14596
|
+
if (result.kind === "started") {
|
|
14597
|
+
(state.attempts[entry.id] ??= []).push({
|
|
14598
|
+
n: attempt,
|
|
14599
|
+
runId: start.runId,
|
|
14600
|
+
tokenCap: entry.tokenCap,
|
|
14601
|
+
startedAt: deps.now().toISOString(),
|
|
14602
|
+
branch: child.branch,
|
|
14603
|
+
worktree: child.path
|
|
14604
|
+
});
|
|
14605
|
+
status.set(entry.id, "live");
|
|
14606
|
+
started += 1;
|
|
14607
|
+
} else if (result.kind === "key_busy")
|
|
14608
|
+
deferred.push({ entry, reason: BUILD_STRINGS.keyBusy(entry.identifier) });
|
|
14609
|
+
else if (result.kind === "unavailable")
|
|
14610
|
+
deferred.push({
|
|
14611
|
+
entry,
|
|
14612
|
+
reason: BUILD_STRINGS.workflowUnavailable(entry.identifier)
|
|
14613
|
+
});
|
|
14614
|
+
else if (result.kind === "unreachable")
|
|
14615
|
+
deferred.push({
|
|
14616
|
+
entry,
|
|
14617
|
+
reason: BUILD_STRINGS.hubUnreachable(entry.identifier)
|
|
14618
|
+
});
|
|
14619
|
+
else if (result.kind === "budget") {
|
|
14620
|
+
parkReason = "budget";
|
|
14621
|
+
break;
|
|
14622
|
+
} else {
|
|
14623
|
+
state.needsPerson[entry.id] = BUILD_STRINGS.startRefused(result.code);
|
|
14624
|
+
status.set(entry.id, "needs_person");
|
|
14625
|
+
}
|
|
14626
|
+
}
|
|
14627
|
+
}
|
|
14628
|
+
const anyLive = started > 0 || children.runs.some((view) => !view.closed) || behind.length > 0;
|
|
14629
|
+
if (!anyLive)
|
|
14630
|
+
for (const { entry, reason } of deferred) {
|
|
14631
|
+
state.needsPerson[entry.id] = reason;
|
|
14632
|
+
status.set(entry.id, "needs_person");
|
|
14633
|
+
}
|
|
14634
|
+
const views = new Map(children.runs.map((view) => [view.runId, view]));
|
|
14635
|
+
for (const entry of entries) {
|
|
14636
|
+
const newest = state.attempts[entry.id]?.at(-1);
|
|
14637
|
+
if (!newest || status.get(entry.id) !== "done") continue;
|
|
14638
|
+
if (!views.get(newest.runId)?.closed) continue;
|
|
14639
|
+
const installed = fs7.existsSync(
|
|
14640
|
+
path7.join(workflowRegistryDir(), entry.workflowName)
|
|
14641
|
+
);
|
|
14642
|
+
if (!installed && !fs7.existsSync(newest.worktree)) continue;
|
|
14643
|
+
if (!deps.pullRequests(deps.folder, newest.branch).merged) continue;
|
|
14644
|
+
if (fs7.existsSync(newest.worktree)) {
|
|
14645
|
+
try {
|
|
14646
|
+
git(deps.folder, ["worktree", "remove", newest.worktree]);
|
|
14647
|
+
} catch {
|
|
14648
|
+
}
|
|
14649
|
+
}
|
|
14650
|
+
if (installed) removeWorkflow(entry.workflowName);
|
|
14651
|
+
}
|
|
14652
|
+
record();
|
|
14653
|
+
if (behind.length > 0) return { action: "rebase", items: behind };
|
|
14654
|
+
return tickAction({
|
|
14655
|
+
entries,
|
|
14656
|
+
status,
|
|
14657
|
+
children: children.runs,
|
|
14658
|
+
started,
|
|
14659
|
+
...parkReason ? { parkReason } : {}
|
|
14660
|
+
});
|
|
14661
|
+
}
|
|
14662
|
+
function locate(deps, entry, branchName) {
|
|
14663
|
+
const worktree = childWorktreePath(
|
|
14664
|
+
deps.worktreesRoot,
|
|
14665
|
+
deps.folder,
|
|
14666
|
+
entry.identifier
|
|
14667
|
+
);
|
|
14668
|
+
if (fs7.existsSync(path7.join(worktree, ".git")))
|
|
14669
|
+
return {
|
|
14670
|
+
worktree,
|
|
14671
|
+
branch: git(worktree, ["rev-parse", "--abbrev-ref", "HEAD"])
|
|
14672
|
+
};
|
|
14673
|
+
return {
|
|
14674
|
+
worktree,
|
|
14675
|
+
branch: branchName ?? fallbackBranch(entry.identifier, entry.title)
|
|
14676
|
+
};
|
|
14677
|
+
}
|
|
14678
|
+
|
|
14679
|
+
// src/core/build/mergeGate.ts
|
|
14680
|
+
var PASSED = /* @__PURE__ */ new Set([
|
|
14681
|
+
"success",
|
|
14682
|
+
"skipped",
|
|
14683
|
+
"neutral"
|
|
14684
|
+
]);
|
|
14685
|
+
function zeroStepFailure(run) {
|
|
14686
|
+
const failedJobs = run.jobs.filter((job) => !PASSED.has(job.conclusion));
|
|
14687
|
+
return failedJobs.length > 0 && failedJobs.every((job) => Array.isArray(job.steps) && job.steps.length === 0);
|
|
14688
|
+
}
|
|
14689
|
+
function evaluateRemoteChecks(runs) {
|
|
14690
|
+
const verdict = {
|
|
14691
|
+
ok: true,
|
|
14692
|
+
failed: [],
|
|
14693
|
+
ignored: [],
|
|
14694
|
+
pending: []
|
|
14695
|
+
};
|
|
14696
|
+
for (const run of runs) {
|
|
14697
|
+
if (run.status !== "completed") verdict.pending.push(run.name);
|
|
14698
|
+
else if (PASSED.has(run.conclusion)) continue;
|
|
14699
|
+
else if (zeroStepFailure(run)) verdict.ignored.push(run.name);
|
|
14700
|
+
else verdict.failed.push(run.name);
|
|
14701
|
+
}
|
|
14702
|
+
verdict.ok = verdict.failed.length === 0 && verdict.pending.length === 0;
|
|
14703
|
+
return verdict;
|
|
14704
|
+
}
|
|
14705
|
+
|
|
14706
|
+
// src/app/entry/buildCommand.ts
|
|
14707
|
+
var USAGE3 = `Usage: drisp build plan --input <path>
|
|
14708
|
+
drisp build tick --input <path> [--rebase-failed <IDENTIFIER>]...
|
|
14709
|
+
drisp build checks
|
|
14710
|
+
plan and tick run inside an Orchestrator Run (DRISP_RUN_ID, DRISP_RUNNER_ID and DRISP_RUN_ARTIFACTS set; tick also
|
|
14711
|
+
DRISP_RUN_TOKEN and DRISP_HUB_URL). DRISP_BUILD_PLAN_INPUT=<path> stands in for plan's --input, with
|
|
14712
|
+
fills/<IDENTIFIER>.json beside it. checks runs in a child's worktree: the merge gate's verdict on HEAD's GitHub runs.`;
|
|
14713
|
+
function githubPullRequests(folder, branch) {
|
|
14714
|
+
const output = execFileSync(
|
|
14715
|
+
"gh",
|
|
14716
|
+
[
|
|
14717
|
+
"pr",
|
|
14718
|
+
"list",
|
|
14719
|
+
"--head",
|
|
14720
|
+
branch,
|
|
14721
|
+
"--state",
|
|
14722
|
+
"all",
|
|
14723
|
+
"--json",
|
|
14724
|
+
"state,url",
|
|
14725
|
+
"--limit",
|
|
14726
|
+
"50"
|
|
14727
|
+
],
|
|
14728
|
+
{ cwd: folder, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
14729
|
+
);
|
|
14730
|
+
return parsePullRequests(output);
|
|
14731
|
+
}
|
|
14732
|
+
function parsePullRequests(output) {
|
|
14733
|
+
const prs = JSON.parse(output);
|
|
14734
|
+
const merged = prs.find((pr) => pr.state === "MERGED");
|
|
14735
|
+
return {
|
|
14736
|
+
open: prs.some((pr) => pr.state === "OPEN"),
|
|
14737
|
+
merged: merged !== void 0,
|
|
14738
|
+
...merged ? { mergedUrl: merged.url } : {}
|
|
14739
|
+
};
|
|
14740
|
+
}
|
|
14741
|
+
function hostCapabilities(folder) {
|
|
14742
|
+
const effective = resolveEffectiveCapabilities({
|
|
14743
|
+
globalConfig: readGlobalConfig(),
|
|
14744
|
+
projectConfig: readConfig(folder)
|
|
14745
|
+
});
|
|
14746
|
+
return {
|
|
14747
|
+
skills: effective.skills.map((skill) => skill.name),
|
|
14748
|
+
mcpServers: effective.mcpServers.map((server) => server.name)
|
|
14749
|
+
};
|
|
14750
|
+
}
|
|
14751
|
+
var defaultChecks = {
|
|
14752
|
+
head: (cwd) => git(cwd, ["rev-parse", "HEAD"]),
|
|
14753
|
+
gh: (args, cwd) => execFileSync("gh", args, {
|
|
14754
|
+
cwd,
|
|
14755
|
+
encoding: "utf8",
|
|
14756
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
14757
|
+
})
|
|
14758
|
+
};
|
|
14759
|
+
function runBuildChecks(cwd, deps) {
|
|
14760
|
+
const head = deps.head(cwd);
|
|
14761
|
+
const runs = JSON.parse(
|
|
14762
|
+
deps.gh(
|
|
14763
|
+
[
|
|
14764
|
+
"run",
|
|
14765
|
+
"list",
|
|
14766
|
+
"--commit",
|
|
14767
|
+
head,
|
|
14768
|
+
"--json",
|
|
14769
|
+
"databaseId,name,status,conclusion",
|
|
14770
|
+
"--limit",
|
|
14771
|
+
"100"
|
|
14772
|
+
],
|
|
14773
|
+
cwd
|
|
14774
|
+
)
|
|
14775
|
+
);
|
|
14776
|
+
const withJobs = runs.map((run) => ({
|
|
14777
|
+
name: run.name,
|
|
14778
|
+
status: run.status,
|
|
14779
|
+
conclusion: run.conclusion,
|
|
14780
|
+
jobs: run.status === "completed" && !PASSED.has(run.conclusion) ? JSON.parse(
|
|
14781
|
+
deps.gh(
|
|
14782
|
+
["run", "view", String(run.databaseId), "--json", "jobs"],
|
|
14783
|
+
cwd
|
|
14784
|
+
)
|
|
14785
|
+
).jobs : []
|
|
14786
|
+
}));
|
|
14787
|
+
return evaluateRemoteChecks(withJobs);
|
|
14788
|
+
}
|
|
14789
|
+
async function runBuildCommand(input) {
|
|
14790
|
+
const stdout = input.stdout ?? process.stdout;
|
|
14791
|
+
const stderr = input.stderr ?? process.stderr;
|
|
14792
|
+
const env2 = input.env ?? process.env;
|
|
14793
|
+
try {
|
|
14794
|
+
let output;
|
|
14795
|
+
switch (input.subcommand) {
|
|
14796
|
+
case "plan":
|
|
14797
|
+
output = runBuildPlan({
|
|
14798
|
+
env: env2,
|
|
14799
|
+
folder: input.cwd,
|
|
14800
|
+
...input.flags.input ? { inputPath: input.flags.input } : {},
|
|
14801
|
+
worktreesRoot: defaultWorktreesRoot(),
|
|
14802
|
+
hostCapabilities: () => hostCapabilities(input.cwd),
|
|
14803
|
+
pullRequests: githubPullRequests,
|
|
14804
|
+
...input.dependencies
|
|
14805
|
+
});
|
|
14806
|
+
break;
|
|
14807
|
+
case "tick": {
|
|
14808
|
+
requireEnv(env2, "DRISP_RUN_ID");
|
|
14809
|
+
requireEnv(env2, "DRISP_RUNNER_ID");
|
|
14810
|
+
requireEnv(env2, "DRISP_RUN_ARTIFACTS");
|
|
14811
|
+
const token = requireEnv(env2, "DRISP_RUN_TOKEN");
|
|
14812
|
+
const hubUrl = requireEnv(env2, "DRISP_HUB_URL");
|
|
14813
|
+
output = await runBuildTick({
|
|
14814
|
+
env: env2,
|
|
14815
|
+
folder: input.cwd,
|
|
14816
|
+
...input.flags.input ? { inputPath: input.flags.input } : {},
|
|
14817
|
+
...input.flags.rebaseFailed?.length ? { rebaseFailed: input.flags.rebaseFailed } : {},
|
|
14818
|
+
worktreesRoot: defaultWorktreesRoot(),
|
|
14819
|
+
hub: DrispClient.delegated(hubUrl, token),
|
|
14820
|
+
pullRequests: githubPullRequests,
|
|
14821
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
14822
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
14823
|
+
...input.tick
|
|
14824
|
+
});
|
|
14825
|
+
break;
|
|
14826
|
+
}
|
|
14827
|
+
case "checks":
|
|
14828
|
+
output = runBuildChecks(input.cwd, input.checks ?? defaultChecks);
|
|
14829
|
+
break;
|
|
14830
|
+
default:
|
|
14831
|
+
stderr.write(`${USAGE3}
|
|
14832
|
+
`);
|
|
14833
|
+
return 2;
|
|
14834
|
+
}
|
|
14835
|
+
stdout.write(`${JSON.stringify(output)}
|
|
14836
|
+
`);
|
|
14837
|
+
return 0;
|
|
14838
|
+
} catch (error) {
|
|
14839
|
+
if (!(error instanceof BuildUsageError)) throw error;
|
|
14840
|
+
stderr.write(`${error.message}
|
|
14841
|
+
${USAGE3}
|
|
14842
|
+
`);
|
|
14843
|
+
return 2;
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
|
|
13516
14847
|
// src/app/entry/doctorCommand.ts
|
|
13517
14848
|
import process7 from "process";
|
|
13518
14849
|
|
|
13519
14850
|
// src/harnesses/claude/system/doctorEnvironment.ts
|
|
13520
|
-
import { execFileSync } from "child_process";
|
|
13521
|
-
import * as
|
|
14851
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
14852
|
+
import * as fs8 from "fs";
|
|
13522
14853
|
import * as os4 from "os";
|
|
13523
|
-
import * as
|
|
14854
|
+
import * as path8 from "path";
|
|
13524
14855
|
var MANAGED_POLICY_KEYS = [
|
|
13525
14856
|
"forceLoginMethod",
|
|
13526
14857
|
"forceLoginOrgUUID",
|
|
@@ -13595,7 +14926,7 @@ function readEnforcement(scopes) {
|
|
|
13595
14926
|
}
|
|
13596
14927
|
function tryRichAuth(claudeBinary) {
|
|
13597
14928
|
try {
|
|
13598
|
-
const output =
|
|
14929
|
+
const output = execFileSync2(claudeBinary, ["auth", "status"], {
|
|
13599
14930
|
timeout: 1e4,
|
|
13600
14931
|
encoding: "utf-8",
|
|
13601
14932
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -13624,8 +14955,8 @@ function collectEnvironment(options = {}) {
|
|
|
13624
14955
|
const detectVersion = options.detectClaudeVersionFn ?? detectClaudeVersion;
|
|
13625
14956
|
const runAuthStatus = options.runClaudeAuthStatusFn ?? runClaudeAuthStatus;
|
|
13626
14957
|
const resolveForwarder = options.resolveHookForwarderCommandFn ?? resolveHookForwarderCommand;
|
|
13627
|
-
const statFn = options.statFn ?? ((p) =>
|
|
13628
|
-
const readFileFn = options.readFileFn ?? ((p) =>
|
|
14958
|
+
const statFn = options.statFn ?? ((p) => fs8.statSync(p));
|
|
14959
|
+
const readFileFn = options.readFileFn ?? ((p) => fs8.readFileSync(p, "utf8"));
|
|
13629
14960
|
const settingsPaths = resolveClaudeSettingsSurfacePaths(
|
|
13630
14961
|
homeDir,
|
|
13631
14962
|
cwd,
|
|
@@ -13670,7 +15001,7 @@ function collectEnvironment(options = {}) {
|
|
|
13670
15001
|
});
|
|
13671
15002
|
let globalConfigPresent = false;
|
|
13672
15003
|
try {
|
|
13673
|
-
statFn(
|
|
15004
|
+
statFn(path8.join(homeDir, ".claude.json"));
|
|
13674
15005
|
globalConfigPresent = true;
|
|
13675
15006
|
} catch {
|
|
13676
15007
|
globalConfigPresent = false;
|
|
@@ -14150,13 +15481,13 @@ function runRunsCommand(input) {
|
|
|
14150
15481
|
// src/app/entry/cli.tsx
|
|
14151
15482
|
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
14152
15483
|
function resolvePackageJsonPath(entryUrl) {
|
|
14153
|
-
let currentDir =
|
|
15484
|
+
let currentDir = path9.dirname(fileURLToPath(entryUrl));
|
|
14154
15485
|
for (; ; ) {
|
|
14155
|
-
const candidatePath =
|
|
14156
|
-
if (
|
|
15486
|
+
const candidatePath = path9.join(currentDir, "package.json");
|
|
15487
|
+
if (fs9.existsSync(candidatePath)) {
|
|
14157
15488
|
return candidatePath;
|
|
14158
15489
|
}
|
|
14159
|
-
const parentDir =
|
|
15490
|
+
const parentDir = path9.dirname(currentDir);
|
|
14160
15491
|
if (parentDir === currentDir) {
|
|
14161
15492
|
throw new Error("Could not locate package.json for CLI metadata.");
|
|
14162
15493
|
}
|
|
@@ -14178,6 +15509,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
14178
15509
|
"skill",
|
|
14179
15510
|
"marketplace",
|
|
14180
15511
|
"runner",
|
|
15512
|
+
"build",
|
|
14181
15513
|
// Deprecated alias of `runner` (#188); removed in 0.7.0.
|
|
14182
15514
|
"dashboard",
|
|
14183
15515
|
"telemetry",
|
|
@@ -14323,7 +15655,10 @@ var cli = meow(
|
|
|
14323
15655
|
skill <sub> Manage personal skills (install, remove, list)
|
|
14324
15656
|
marketplace <sub> Manage marketplace sources (add, refresh, remove, list)
|
|
14325
15657
|
runner [sub] Run the runner that connects this machine to a Team and executes its Runs
|
|
14326
|
-
(no subcommand: foreground; connect [code] --hub <url> [--replace], disconnect [team], status, recover, help)
|
|
15658
|
+
(no subcommand: foreground; connect [code] --hub <url> [--replace], disconnect [team], status, restart, recover, help)
|
|
15659
|
+
build plan --input <path> Plan a Build inside an Orchestrator Run (prints one JSON object)
|
|
15660
|
+
build tick --input <path> Run one step of a started Build (prints the next action; [--rebase-failed <ID>]...)
|
|
15661
|
+
build checks The merge gate's verdict on HEAD's GitHub runs (in a child's worktree)
|
|
14327
15662
|
telemetry [action] Manage anonymous telemetry (enable/disable/status)
|
|
14328
15663
|
doctor Diagnose Claude headless setup (use with --harness=claude)
|
|
14329
15664
|
|
|
@@ -14354,7 +15689,7 @@ var cli = meow(
|
|
|
14354
15689
|
--dry-run Print resolved bootstrap (workflow, isolation, plugins, harness) and exit (run mode)
|
|
14355
15690
|
--project Scope workflow command to project config (workflow use)
|
|
14356
15691
|
--global Scope workflow command to global config (workflow use, default)
|
|
14357
|
-
--help
|
|
15692
|
+
--help, -h Show command help (runner shows its own)
|
|
14358
15693
|
--version Show CLI version
|
|
14359
15694
|
|
|
14360
15695
|
Note: All isolation modes use --setting-sources "" to completely isolate
|
|
@@ -14389,6 +15724,12 @@ var cli = meow(
|
|
|
14389
15724
|
importMeta: import.meta,
|
|
14390
15725
|
allowUnknownFlags: false,
|
|
14391
15726
|
flags: {
|
|
15727
|
+
// Declared so -h works too; meow alone handles only a bare --help (DRI-193).
|
|
15728
|
+
help: {
|
|
15729
|
+
type: "boolean",
|
|
15730
|
+
shortFlag: "h",
|
|
15731
|
+
default: false
|
|
15732
|
+
},
|
|
14392
15733
|
projectDir: {
|
|
14393
15734
|
type: "string",
|
|
14394
15735
|
default: process.cwd()
|
|
@@ -14448,6 +15789,13 @@ var cli = meow(
|
|
|
14448
15789
|
hub: {
|
|
14449
15790
|
type: "string"
|
|
14450
15791
|
},
|
|
15792
|
+
input: {
|
|
15793
|
+
type: "string"
|
|
15794
|
+
},
|
|
15795
|
+
rebaseFailed: {
|
|
15796
|
+
type: "string",
|
|
15797
|
+
isMultiple: true
|
|
15798
|
+
},
|
|
14451
15799
|
foreground: {
|
|
14452
15800
|
type: "boolean",
|
|
14453
15801
|
default: false
|
|
@@ -14487,7 +15835,7 @@ var cli = meow(
|
|
|
14487
15835
|
}
|
|
14488
15836
|
);
|
|
14489
15837
|
async function main() {
|
|
14490
|
-
const projectDir =
|
|
15838
|
+
const projectDir = path9.resolve(cli.flags.projectDir);
|
|
14491
15839
|
const [command, ...commandArgs] = cli.input;
|
|
14492
15840
|
const isRunCommand = command === "run" || command === "exec";
|
|
14493
15841
|
if (command && !KNOWN_COMMANDS.has(command)) {
|
|
@@ -14498,6 +15846,10 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14498
15846
|
await exitWith(1);
|
|
14499
15847
|
return;
|
|
14500
15848
|
}
|
|
15849
|
+
if (cli.flags.help && command !== "runner" && command !== "dashboard") {
|
|
15850
|
+
cli.showHelp(0);
|
|
15851
|
+
return;
|
|
15852
|
+
}
|
|
14501
15853
|
if (command === "exec") {
|
|
14502
15854
|
console.error(
|
|
14503
15855
|
"drisp exec is deprecated and is removed in 0.7.0; use drisp run instead."
|
|
@@ -14537,7 +15889,7 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14537
15889
|
await exitWith(1);
|
|
14538
15890
|
return;
|
|
14539
15891
|
}
|
|
14540
|
-
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-
|
|
15892
|
+
const { default: WorkflowInstallWizard } = await import("./WorkflowInstallWizard-NZFBSBLY.js");
|
|
14541
15893
|
const { waitUntilExit } = render(
|
|
14542
15894
|
/* @__PURE__ */ jsx24(
|
|
14543
15895
|
WorkflowInstallWizard,
|
|
@@ -14608,12 +15960,27 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14608
15960
|
...cli.flags.detach ? { detach: true } : {},
|
|
14609
15961
|
...typeof cli.flags.hub === "string" ? { hub: cli.flags.hub } : {},
|
|
14610
15962
|
...cli.flags.foreground ? { foreground: true } : {},
|
|
14611
|
-
...cli.flags.replace ? { replace: true } : {}
|
|
15963
|
+
...cli.flags.replace ? { replace: true } : {},
|
|
15964
|
+
...cli.flags.help ? { help: true } : {}
|
|
14612
15965
|
}
|
|
14613
15966
|
})
|
|
14614
15967
|
);
|
|
14615
15968
|
return;
|
|
14616
15969
|
}
|
|
15970
|
+
if (command === "build") {
|
|
15971
|
+
const [subcommand = ""] = commandArgs;
|
|
15972
|
+
await exitWith(
|
|
15973
|
+
await runBuildCommand({
|
|
15974
|
+
subcommand,
|
|
15975
|
+
flags: {
|
|
15976
|
+
...typeof cli.flags.input === "string" ? { input: cli.flags.input } : {},
|
|
15977
|
+
...cli.flags.rebaseFailed?.length ? { rebaseFailed: cli.flags.rebaseFailed } : {}
|
|
15978
|
+
},
|
|
15979
|
+
cwd: process.cwd()
|
|
15980
|
+
})
|
|
15981
|
+
);
|
|
15982
|
+
return;
|
|
15983
|
+
}
|
|
14617
15984
|
if (command === "runs") {
|
|
14618
15985
|
await exitWith(
|
|
14619
15986
|
runRunsCommand({
|
|
@@ -14687,8 +16054,8 @@ Available commands: ${[...KNOWN_COMMANDS].join(", ")}`
|
|
|
14687
16054
|
const showSetup = isRunCommand ? false : shouldShowSetup({
|
|
14688
16055
|
cliInput: cli.input,
|
|
14689
16056
|
setupComplete: globalConfig.setupComplete,
|
|
14690
|
-
globalConfigExists:
|
|
14691
|
-
|
|
16057
|
+
globalConfigExists: fs9.existsSync(
|
|
16058
|
+
path9.join(os5.homedir(), ".config", "athena", "config.json")
|
|
14692
16059
|
)
|
|
14693
16060
|
});
|
|
14694
16061
|
let runtimeConfig;
|