@gh-symphony/cli 0.0.20 → 0.0.22
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 +66 -2
- package/dist/chunk-2TSM3INR.js +1085 -0
- package/dist/chunk-2UW7NQLX.js +684 -0
- package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
- package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
- package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
- package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
- package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
- package/dist/chunk-EEQQWTXS.js +3257 -0
- package/dist/chunk-GDE6FYN4.js +26 -0
- package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
- package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
- package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
- package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
- package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
- package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
- package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
- package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
- package/dist/index.js +112 -24
- package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
- package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
- package/dist/project-RMYMZSFV.js +25 -0
- package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
- package/dist/repo-WI7GF6XQ.js +749 -0
- package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
- package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
- package/dist/start-RTAHQMR2.js +19 -0
- package/dist/status-F4D52OVK.js +12 -0
- package/dist/stop-MDKMJPVR.js +10 -0
- package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
- package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
- package/dist/worker-entry.js +848 -693
- package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
- package/package.json +4 -2
- package/dist/chunk-M3IFVLQS.js +0 -1155
- package/dist/project-UUVHS3ZR.js +0 -22
- package/dist/repo-HDDE7OUI.js +0 -321
- package/dist/start-ENFLZUI6.js +0 -16
- package/dist/status-QSCFVGRQ.js +0 -11
- package/dist/stop-7MFCBQVW.js +0 -9
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
createStore,
|
|
6
6
|
releaseProjectLock,
|
|
7
7
|
resolveOrchestratorLogLevel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PUDXVBSN.js";
|
|
9
|
+
import {
|
|
10
|
+
getGhToken
|
|
11
|
+
} from "./chunk-C67H3OUL.js";
|
|
9
12
|
import {
|
|
10
13
|
deriveIssueWorkspaceKeyFromIdentifier,
|
|
11
14
|
isFileMissing,
|
|
@@ -14,40 +17,41 @@ import {
|
|
|
14
17
|
parseRecentEvents,
|
|
15
18
|
readJsonFile,
|
|
16
19
|
safeReadDir
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
getGhToken
|
|
20
|
-
} from "./chunk-TILHWBP6.js";
|
|
20
|
+
} from "./chunk-EEQQWTXS.js";
|
|
21
21
|
import {
|
|
22
22
|
bold,
|
|
23
23
|
cyan,
|
|
24
24
|
dim,
|
|
25
|
+
formatRepositoryDisplay,
|
|
25
26
|
green,
|
|
26
27
|
red,
|
|
27
28
|
setNoColor,
|
|
28
29
|
yellow
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-36KYEDEO.js";
|
|
30
31
|
import {
|
|
31
32
|
resolveRuntimeRoot
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-IWFX2FMA.js";
|
|
34
|
+
import {
|
|
35
|
+
rejectRemovedProjectId
|
|
36
|
+
} from "./chunk-GDE6FYN4.js";
|
|
33
37
|
import {
|
|
34
38
|
handleMissingManagedProjectConfig,
|
|
35
39
|
resolveManagedProjectConfig
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-DDL4BWSL.js";
|
|
37
41
|
import {
|
|
38
42
|
daemonPidPath,
|
|
39
43
|
httpStatusPath,
|
|
40
44
|
orchestratorLogPath,
|
|
41
45
|
writeJsonFile
|
|
42
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-QIRE2VXS.js";
|
|
43
47
|
|
|
44
48
|
// src/commands/start.ts
|
|
45
|
-
import { writeFile, mkdir, readFile, rm } from "fs/promises";
|
|
46
|
-
import { dirname, join as
|
|
49
|
+
import { writeFile, mkdir, readFile as readFile2, rm } from "fs/promises";
|
|
50
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
47
51
|
import { spawn } from "child_process";
|
|
48
|
-
import { createServer as
|
|
52
|
+
import { createServer as createServer3 } from "http";
|
|
49
53
|
|
|
50
|
-
// ../dashboard/
|
|
54
|
+
// ../dashboard/src/store.ts
|
|
51
55
|
import { open } from "fs/promises";
|
|
52
56
|
import { join, resolve } from "path";
|
|
53
57
|
var DEFAULT_RECENT_EVENT_LIMIT = 20;
|
|
@@ -55,24 +59,30 @@ var RECENT_EVENT_CHUNK_SIZE = 4096;
|
|
|
55
59
|
var MAX_RECENT_EVENT_SCAN_BYTES = 64 * 1024;
|
|
56
60
|
var RUN_RECORD_LOAD_CONCURRENCY = 8;
|
|
57
61
|
var DashboardFsReader = class {
|
|
58
|
-
runtimeRoot
|
|
59
|
-
projectId;
|
|
60
|
-
resolvedRuntimeRoot;
|
|
61
|
-
constructor(runtimeRoot, projectId) {
|
|
62
|
+
constructor(runtimeRoot) {
|
|
62
63
|
this.runtimeRoot = runtimeRoot;
|
|
63
|
-
this.projectId = projectId;
|
|
64
|
-
assertValidDashboardProjectId(projectId);
|
|
65
64
|
this.resolvedRuntimeRoot = resolve(runtimeRoot);
|
|
66
65
|
}
|
|
66
|
+
resolvedRuntimeRoot;
|
|
67
67
|
projectDir() {
|
|
68
|
-
return
|
|
68
|
+
return this.resolvedRuntimeRoot;
|
|
69
69
|
}
|
|
70
70
|
runDir(runId) {
|
|
71
71
|
assertValidDashboardRunId(runId);
|
|
72
|
-
return join(this.
|
|
72
|
+
return join(this.resolvedRuntimeRoot, "runs", runId);
|
|
73
73
|
}
|
|
74
74
|
async loadProjectStatus() {
|
|
75
|
-
|
|
75
|
+
const snapshot = await readJsonFile(join(this.projectDir(), "status.json"));
|
|
76
|
+
if (!snapshot) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const status = { ...snapshot };
|
|
80
|
+
delete status.projectId;
|
|
81
|
+
delete status.slug;
|
|
82
|
+
if (!isRepositoryRef(status.repository)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return status;
|
|
76
86
|
}
|
|
77
87
|
async loadProjectState() {
|
|
78
88
|
const snapshot = await this.loadProjectStatus();
|
|
@@ -87,7 +97,9 @@ var DashboardFsReader = class {
|
|
|
87
97
|
};
|
|
88
98
|
}
|
|
89
99
|
async loadProjectIssueOrchestrations() {
|
|
90
|
-
const issues = await readJsonFile(
|
|
100
|
+
const issues = await readJsonFile(
|
|
101
|
+
join(this.projectDir(), "issues.json")
|
|
102
|
+
);
|
|
91
103
|
if (issues) {
|
|
92
104
|
return issues.map((issue) => ({
|
|
93
105
|
...issue,
|
|
@@ -99,7 +111,9 @@ var DashboardFsReader = class {
|
|
|
99
111
|
return legacyLeases.map((lease) => ({
|
|
100
112
|
issueId: lease.issueId,
|
|
101
113
|
identifier: lease.issueIdentifier,
|
|
102
|
-
workspaceKey: deriveIssueWorkspaceKeyFromIdentifier(
|
|
114
|
+
workspaceKey: deriveIssueWorkspaceKeyFromIdentifier(
|
|
115
|
+
lease.issueIdentifier
|
|
116
|
+
),
|
|
103
117
|
completedOnce: false,
|
|
104
118
|
failureRetryCount: 0,
|
|
105
119
|
state: lease.status === "active" ? "claimed" : "released",
|
|
@@ -109,29 +123,39 @@ var DashboardFsReader = class {
|
|
|
109
123
|
}));
|
|
110
124
|
}
|
|
111
125
|
async loadRun(runId) {
|
|
112
|
-
return readJsonFile(
|
|
126
|
+
return readJsonFile(
|
|
127
|
+
join(this.runDir(runId), "run.json")
|
|
128
|
+
);
|
|
113
129
|
}
|
|
114
130
|
async loadAllRuns() {
|
|
115
131
|
const runIds = await safeReadDir(join(this.projectDir(), "runs"));
|
|
116
|
-
const runs = await mapWithConcurrency(
|
|
132
|
+
const runs = await mapWithConcurrency(
|
|
133
|
+
runIds,
|
|
134
|
+
RUN_RECORD_LOAD_CONCURRENCY,
|
|
135
|
+
(runId) => this.loadRun(runId)
|
|
136
|
+
);
|
|
117
137
|
return runs.filter((run) => Boolean(run));
|
|
118
138
|
}
|
|
119
139
|
async loadRunsForIssue(issueId, issueIdentifier) {
|
|
120
140
|
const runIds = await safeReadDir(join(this.projectDir(), "runs"));
|
|
121
|
-
const runs = await mapWithConcurrency(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
const runs = await mapWithConcurrency(
|
|
142
|
+
runIds,
|
|
143
|
+
RUN_RECORD_LOAD_CONCURRENCY,
|
|
144
|
+
async (runId) => {
|
|
145
|
+
try {
|
|
146
|
+
const run = await this.loadRun(runId);
|
|
147
|
+
if (!run) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return run.issueId === issueId || run.issueIdentifier === issueIdentifier ? run : null;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (isFileMissing(error)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
130
155
|
return null;
|
|
131
156
|
}
|
|
132
|
-
return null;
|
|
133
157
|
}
|
|
134
|
-
|
|
158
|
+
);
|
|
135
159
|
return runs.filter((run) => Boolean(run));
|
|
136
160
|
}
|
|
137
161
|
async loadRecentRunEvents(runId, limit = DEFAULT_RECENT_EVENT_LIMIT) {
|
|
@@ -148,7 +172,11 @@ var DashboardFsReader = class {
|
|
|
148
172
|
let newlineCount = 0;
|
|
149
173
|
const chunks = [];
|
|
150
174
|
while (position > 0 && bytesScanned < MAX_RECENT_EVENT_SCAN_BYTES && newlineCount <= limit) {
|
|
151
|
-
const readSize = Math.min(
|
|
175
|
+
const readSize = Math.min(
|
|
176
|
+
position,
|
|
177
|
+
RECENT_EVENT_CHUNK_SIZE,
|
|
178
|
+
MAX_RECENT_EVENT_SCAN_BYTES - bytesScanned
|
|
179
|
+
);
|
|
152
180
|
position -= readSize;
|
|
153
181
|
const chunk = Buffer.allocUnsafe(readSize);
|
|
154
182
|
const { bytesRead } = await handle.read(chunk, 0, readSize, position);
|
|
@@ -160,9 +188,13 @@ var DashboardFsReader = class {
|
|
|
160
188
|
bytesScanned += bytesRead;
|
|
161
189
|
newlineCount += countNewlines(populatedChunk);
|
|
162
190
|
}
|
|
163
|
-
return parseRecentEvents(
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
return parseRecentEvents(
|
|
192
|
+
Buffer.concat(chunks).toString("utf8"),
|
|
193
|
+
limit,
|
|
194
|
+
{
|
|
195
|
+
allowPartialFirstLine: position > 0
|
|
196
|
+
}
|
|
197
|
+
);
|
|
166
198
|
} finally {
|
|
167
199
|
await handle.close();
|
|
168
200
|
}
|
|
@@ -185,12 +217,18 @@ function countNewlines(chunk) {
|
|
|
185
217
|
}
|
|
186
218
|
async function statusForIssue(reader, issueIdentifier) {
|
|
187
219
|
const issueRecords = await reader.loadProjectIssueOrchestrations();
|
|
188
|
-
const issueRecord = issueRecords.find(
|
|
220
|
+
const issueRecord = issueRecords.find(
|
|
221
|
+
(record) => record.identifier === issueIdentifier
|
|
222
|
+
);
|
|
189
223
|
if (!issueRecord) {
|
|
190
224
|
return null;
|
|
191
225
|
}
|
|
192
226
|
const currentRunCandidate = issueRecord.currentRunId ? await reader.loadRun(issueRecord.currentRunId) : null;
|
|
193
|
-
const currentRun = isMatchingIssueRun(
|
|
227
|
+
const currentRun = isMatchingIssueRun(
|
|
228
|
+
currentRunCandidate,
|
|
229
|
+
issueRecord.issueId,
|
|
230
|
+
issueIdentifier
|
|
231
|
+
) ? currentRunCandidate : null;
|
|
194
232
|
const issueRuns = currentRun === null ? await reader.loadRunsForIssue(issueRecord.issueId, issueIdentifier) : currentRun.tokenUsage ? await reader.loadRunsForIssue(issueRecord.issueId, issueIdentifier) : null;
|
|
195
233
|
const resolvedRun = currentRun ?? findLatestRunForIssue(issueRuns ?? []);
|
|
196
234
|
const recentEvents = resolvedRun === null ? [] : await reader.loadRecentRunEvents(resolvedRun.runId);
|
|
@@ -252,30 +290,39 @@ async function statusForIssue(reader, issueIdentifier) {
|
|
|
252
290
|
};
|
|
253
291
|
}
|
|
254
292
|
function aggregateIssueTokenUsage(runs) {
|
|
255
|
-
return runs.reduce(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
293
|
+
return runs.reduce(
|
|
294
|
+
(total, run) => ({
|
|
295
|
+
inputTokens: total.inputTokens + (run.tokenUsage?.inputTokens ?? 0),
|
|
296
|
+
outputTokens: total.outputTokens + (run.tokenUsage?.outputTokens ?? 0),
|
|
297
|
+
totalTokens: total.totalTokens + (run.tokenUsage?.totalTokens ?? 0)
|
|
298
|
+
}),
|
|
299
|
+
{
|
|
300
|
+
inputTokens: 0,
|
|
301
|
+
outputTokens: 0,
|
|
302
|
+
totalTokens: 0
|
|
303
|
+
}
|
|
304
|
+
);
|
|
264
305
|
}
|
|
265
306
|
function findLatestRunForIssue(matchingRuns) {
|
|
266
|
-
const sortedRuns = [...matchingRuns].sort(
|
|
307
|
+
const sortedRuns = [...matchingRuns].sort(
|
|
308
|
+
(left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime()
|
|
309
|
+
);
|
|
267
310
|
return sortedRuns[0] ?? null;
|
|
268
311
|
}
|
|
269
|
-
function assertValidDashboardProjectId(projectId) {
|
|
270
|
-
if (projectId.length === 0 || projectId === "." || projectId === ".." || projectId.includes("/") || projectId.includes("\\")) {
|
|
271
|
-
throw new Error(`Invalid project ID "${projectId}". Project IDs must not contain path separators or traversal segments.`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
312
|
function assertValidDashboardRunId(runId) {
|
|
275
313
|
if (runId.length === 0 || runId === "." || runId === ".." || runId.includes("/") || runId.includes("\\")) {
|
|
276
|
-
throw new Error(
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Invalid run ID "${runId}". Run IDs must not contain path separators or traversal segments.`
|
|
316
|
+
);
|
|
277
317
|
}
|
|
278
318
|
}
|
|
319
|
+
function isRepositoryRef(value) {
|
|
320
|
+
if (!value || typeof value !== "object") {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
const repository = value;
|
|
324
|
+
return typeof repository.owner === "string" && repository.owner.length > 0 && typeof repository.name === "string" && repository.name.length > 0 && typeof repository.cloneUrl === "string";
|
|
325
|
+
}
|
|
279
326
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
280
327
|
const results = new Array(items.length);
|
|
281
328
|
let nextIndex = 0;
|
|
@@ -291,8 +338,10 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
291
338
|
return results;
|
|
292
339
|
}
|
|
293
340
|
|
|
294
|
-
// ../dashboard/
|
|
295
|
-
import {
|
|
341
|
+
// ../dashboard/src/server.ts
|
|
342
|
+
import {
|
|
343
|
+
createServer
|
|
344
|
+
} from "http";
|
|
296
345
|
async function resolveDashboardResponse(options) {
|
|
297
346
|
const method = options.method ?? "GET";
|
|
298
347
|
if (options.pathname === "/healthz") {
|
|
@@ -371,6 +420,267 @@ async function resolveDashboardResponse(options) {
|
|
|
371
420
|
};
|
|
372
421
|
}
|
|
373
422
|
|
|
423
|
+
// ../control-plane/src/server.ts
|
|
424
|
+
import {
|
|
425
|
+
createServer as createServer2
|
|
426
|
+
} from "http";
|
|
427
|
+
import { readFile, stat } from "fs/promises";
|
|
428
|
+
import { dirname, extname, join as join2, resolve as resolve2, sep } from "path";
|
|
429
|
+
import { fileURLToPath } from "url";
|
|
430
|
+
var CLIENT_DIST_DIR = join2(
|
|
431
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
432
|
+
"../client/dist"
|
|
433
|
+
);
|
|
434
|
+
var BUNDLED_CLIENT_DIST_DIR = join2(
|
|
435
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
436
|
+
"../../control-plane/client/dist"
|
|
437
|
+
);
|
|
438
|
+
var WORKSPACE_CLIENT_DIST_DIR = join2(
|
|
439
|
+
process.cwd(),
|
|
440
|
+
"packages/control-plane/client/dist"
|
|
441
|
+
);
|
|
442
|
+
var NODE_MODULES_CLIENT_DIST_DIR = join2(
|
|
443
|
+
process.cwd(),
|
|
444
|
+
"node_modules/@gh-symphony/control-plane/client/dist"
|
|
445
|
+
);
|
|
446
|
+
var CLIENT_DIST_DIR_CANDIDATES = [
|
|
447
|
+
CLIENT_DIST_DIR,
|
|
448
|
+
BUNDLED_CLIENT_DIST_DIR,
|
|
449
|
+
WORKSPACE_CLIENT_DIST_DIR,
|
|
450
|
+
NODE_MODULES_CLIENT_DIST_DIR
|
|
451
|
+
];
|
|
452
|
+
var clientDistDirPromise;
|
|
453
|
+
var TEXT_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
454
|
+
"application/javascript",
|
|
455
|
+
"application/json",
|
|
456
|
+
"image/svg+xml",
|
|
457
|
+
"text/css",
|
|
458
|
+
"text/html",
|
|
459
|
+
"text/plain"
|
|
460
|
+
]);
|
|
461
|
+
var CONTENT_TYPES = {
|
|
462
|
+
".css": "text/css",
|
|
463
|
+
".gif": "image/gif",
|
|
464
|
+
".html": "text/html",
|
|
465
|
+
".ico": "image/x-icon",
|
|
466
|
+
".jpeg": "image/jpeg",
|
|
467
|
+
".jpg": "image/jpeg",
|
|
468
|
+
".js": "application/javascript",
|
|
469
|
+
".json": "application/json",
|
|
470
|
+
".map": "application/json",
|
|
471
|
+
".png": "image/png",
|
|
472
|
+
".svg": "image/svg+xml",
|
|
473
|
+
".txt": "text/plain",
|
|
474
|
+
".webp": "image/webp"
|
|
475
|
+
};
|
|
476
|
+
function createControlPlaneHandler(options) {
|
|
477
|
+
return async (request, response) => {
|
|
478
|
+
try {
|
|
479
|
+
const method = request.method ?? "GET";
|
|
480
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
481
|
+
if (url.pathname === "/api/v1/refresh") {
|
|
482
|
+
await handleRefreshRequest(method, request, response, options);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (isDashboardRequest(url.pathname)) {
|
|
486
|
+
const resolved = await resolveDashboardResponse({
|
|
487
|
+
pathname: url.pathname,
|
|
488
|
+
method,
|
|
489
|
+
reader: options.reader
|
|
490
|
+
});
|
|
491
|
+
respondJson(response, resolved.status, resolved.payload);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (!isStaticRequestMethod(method)) {
|
|
495
|
+
respondJson(response, 405, { error: "Method not allowed" });
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const asset = await resolveStaticAsset(url.pathname);
|
|
499
|
+
if (!asset) {
|
|
500
|
+
respondJson(response, 404, { error: "Not found" });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (asset.kind === "error") {
|
|
504
|
+
respondJson(response, asset.status, { error: "Bad request" });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
await respondFile(response, asset.path, method, asset.fallback);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error("Control plane request failed.", error);
|
|
510
|
+
if (!response.headersSent) {
|
|
511
|
+
respondJson(response, 500, { error: "Internal server error" });
|
|
512
|
+
} else {
|
|
513
|
+
response.end();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async function startControlPlaneServer(options) {
|
|
519
|
+
const reader = new DashboardFsReader(options.runtimeRoot);
|
|
520
|
+
const handler2 = createControlPlaneHandler({
|
|
521
|
+
reader,
|
|
522
|
+
onRefreshRequest: options.onRefreshRequest
|
|
523
|
+
});
|
|
524
|
+
for (let port = options.port; port <= 65535; port += 1) {
|
|
525
|
+
const server = createServer2((request, response) => {
|
|
526
|
+
void handler2(request, response);
|
|
527
|
+
});
|
|
528
|
+
try {
|
|
529
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
530
|
+
const cleanup = () => {
|
|
531
|
+
server.off("listening", handleListening);
|
|
532
|
+
server.off("error", handleError);
|
|
533
|
+
};
|
|
534
|
+
const handleListening = () => {
|
|
535
|
+
cleanup();
|
|
536
|
+
resolveReady();
|
|
537
|
+
};
|
|
538
|
+
const handleError = (error) => {
|
|
539
|
+
cleanup();
|
|
540
|
+
rejectReady(error);
|
|
541
|
+
};
|
|
542
|
+
server.once("listening", handleListening);
|
|
543
|
+
server.once("error", handleError);
|
|
544
|
+
server.listen(port, options.host);
|
|
545
|
+
});
|
|
546
|
+
const address = server.address();
|
|
547
|
+
const boundPort = address && typeof address !== "string" ? address.port : port;
|
|
548
|
+
return {
|
|
549
|
+
server,
|
|
550
|
+
port: boundPort,
|
|
551
|
+
url: formatBoundUrl(server)
|
|
552
|
+
};
|
|
553
|
+
} catch (error) {
|
|
554
|
+
await closeServer(server).catch(() => {
|
|
555
|
+
});
|
|
556
|
+
if (error.code === "EADDRINUSE") {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
throw new Error(
|
|
563
|
+
`Unable to bind control plane server starting from port ${options.port}`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
async function handleRefreshRequest(method, request, response, options) {
|
|
567
|
+
if (method !== "POST") {
|
|
568
|
+
respondJson(response, 405, { error: "Method not allowed" });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
request.resume();
|
|
572
|
+
options.onRefreshRequest?.();
|
|
573
|
+
respondJson(response, 202, { ok: true });
|
|
574
|
+
}
|
|
575
|
+
function isDashboardRequest(pathname) {
|
|
576
|
+
return pathname === "/healthz" || pathname === "/api/v1/state" || pathname.startsWith("/api/v1/");
|
|
577
|
+
}
|
|
578
|
+
function isStaticRequestMethod(method) {
|
|
579
|
+
return method === "GET" || method === "HEAD";
|
|
580
|
+
}
|
|
581
|
+
async function resolveStaticAsset(pathname) {
|
|
582
|
+
const clientDistDir = await resolveClientDistDir();
|
|
583
|
+
if (!clientDistDir) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
const indexPath = join2(clientDistDir, "index.html");
|
|
587
|
+
if (pathname === "/") {
|
|
588
|
+
return await existsAsFile(indexPath) ? { kind: "asset", path: indexPath, fallback: true } : null;
|
|
589
|
+
}
|
|
590
|
+
let decodedPathname;
|
|
591
|
+
try {
|
|
592
|
+
decodedPathname = decodeURIComponent(pathname);
|
|
593
|
+
} catch {
|
|
594
|
+
return { kind: "error", status: 400 };
|
|
595
|
+
}
|
|
596
|
+
const resolvedPath = resolve2(clientDistDir, `.${decodedPathname}`);
|
|
597
|
+
if (!isPathInsideClientDist(clientDistDir, resolvedPath)) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
if (await existsAsFile(resolvedPath)) {
|
|
601
|
+
return { kind: "asset", path: resolvedPath, fallback: false };
|
|
602
|
+
}
|
|
603
|
+
if (hasFileExtension(decodedPathname)) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
return await existsAsFile(indexPath) ? { kind: "asset", path: indexPath, fallback: true } : null;
|
|
607
|
+
}
|
|
608
|
+
function isPathInsideClientDist(clientDistDir, path) {
|
|
609
|
+
return path === clientDistDir || path.startsWith(`${clientDistDir}${sep}`);
|
|
610
|
+
}
|
|
611
|
+
function hasFileExtension(pathname) {
|
|
612
|
+
const lastSegment = pathname.split("/").pop() ?? "";
|
|
613
|
+
return lastSegment.includes(".");
|
|
614
|
+
}
|
|
615
|
+
async function existsAsFile(path) {
|
|
616
|
+
try {
|
|
617
|
+
return (await stat(path)).isFile();
|
|
618
|
+
} catch {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function resolveClientDistDir() {
|
|
623
|
+
clientDistDirPromise ??= (async () => {
|
|
624
|
+
for (const candidate of CLIENT_DIST_DIR_CANDIDATES) {
|
|
625
|
+
try {
|
|
626
|
+
if ((await stat(candidate)).isDirectory()) {
|
|
627
|
+
return candidate;
|
|
628
|
+
}
|
|
629
|
+
} catch {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
})();
|
|
635
|
+
return clientDistDirPromise;
|
|
636
|
+
}
|
|
637
|
+
async function respondFile(response, path, method, fallback) {
|
|
638
|
+
const contentType = contentTypeForPath(path);
|
|
639
|
+
const body = method === "HEAD" ? void 0 : await readFile(path);
|
|
640
|
+
const cacheControl = fallback || path.endsWith(`${sep}index.html`) ? "no-cache" : "public, max-age=31536000, immutable";
|
|
641
|
+
response.writeHead(200, {
|
|
642
|
+
"cache-control": cacheControl,
|
|
643
|
+
"content-type": contentType
|
|
644
|
+
});
|
|
645
|
+
response.end(body);
|
|
646
|
+
}
|
|
647
|
+
function contentTypeForPath(path) {
|
|
648
|
+
const contentType = CONTENT_TYPES[extname(path).toLowerCase()];
|
|
649
|
+
if (!contentType) {
|
|
650
|
+
return "application/octet-stream";
|
|
651
|
+
}
|
|
652
|
+
if (TEXT_CONTENT_TYPES.has(contentType)) {
|
|
653
|
+
return `${contentType}; charset=utf-8`;
|
|
654
|
+
}
|
|
655
|
+
return contentType;
|
|
656
|
+
}
|
|
657
|
+
function respondJson(response, status, payload) {
|
|
658
|
+
response.writeHead(status, {
|
|
659
|
+
"content-type": "application/json; charset=utf-8"
|
|
660
|
+
});
|
|
661
|
+
response.end(JSON.stringify(payload));
|
|
662
|
+
}
|
|
663
|
+
function formatBoundUrl(server) {
|
|
664
|
+
const address = server.address();
|
|
665
|
+
if (!address || typeof address === "string") {
|
|
666
|
+
return "http://localhost";
|
|
667
|
+
}
|
|
668
|
+
const host = address.address === "::" || address.address === "::1" || address.address === "0.0.0.0" || address.address === "127.0.0.1" ? "localhost" : address.address;
|
|
669
|
+
const urlHost = host.includes(":") ? `[${host}]` : host;
|
|
670
|
+
return `http://${urlHost}:${address.port}`;
|
|
671
|
+
}
|
|
672
|
+
async function closeServer(server) {
|
|
673
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
674
|
+
server.close((error) => {
|
|
675
|
+
if (error) {
|
|
676
|
+
rejectClose(error);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
resolveClose();
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
374
684
|
// src/commands/start.ts
|
|
375
685
|
function timestamp() {
|
|
376
686
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -410,13 +720,13 @@ function parseStartArgs(args) {
|
|
|
410
720
|
i += 1;
|
|
411
721
|
continue;
|
|
412
722
|
}
|
|
413
|
-
if (arg === "--
|
|
723
|
+
if (arg === "--web") {
|
|
414
724
|
const value = args[i + 1];
|
|
415
725
|
if (!value || value.startsWith("-")) {
|
|
416
|
-
parsed.
|
|
417
|
-
|
|
726
|
+
parsed.webPort = DEFAULT_HTTP_PORT;
|
|
727
|
+
continue;
|
|
418
728
|
}
|
|
419
|
-
parsed.
|
|
729
|
+
parsed.webPort = parsePort(value, arg);
|
|
420
730
|
i += 1;
|
|
421
731
|
continue;
|
|
422
732
|
}
|
|
@@ -435,6 +745,9 @@ function parseStartArgs(args) {
|
|
|
435
745
|
return parsed;
|
|
436
746
|
}
|
|
437
747
|
}
|
|
748
|
+
if (parsed.httpPort !== void 0 && parsed.webPort !== void 0) {
|
|
749
|
+
parsed.error = "Options '--http' and '--web' cannot be used together";
|
|
750
|
+
}
|
|
438
751
|
return parsed;
|
|
439
752
|
}
|
|
440
753
|
function logTickResult(snapshot, prevSnapshot, isFirst) {
|
|
@@ -442,7 +755,9 @@ function logTickResult(snapshot, prevSnapshot, isFirst) {
|
|
|
442
755
|
const healthColor = snapshot.health === "degraded" ? red : snapshot.health === "running" ? green : cyan;
|
|
443
756
|
logLine(
|
|
444
757
|
green("\u25CF"),
|
|
445
|
-
`
|
|
758
|
+
`Repository ${bold(formatRepositoryDisplay(snapshot))} connected ${dim(
|
|
759
|
+
"("
|
|
760
|
+
)}${healthColor(snapshot.health)}${dim(")")}`
|
|
446
761
|
);
|
|
447
762
|
if (snapshot.summary.activeRuns > 0) {
|
|
448
763
|
logLine(cyan("\u25B8"), `${snapshot.summary.activeRuns} active run(s)`);
|
|
@@ -530,13 +845,13 @@ function parsePort(value, optionName) {
|
|
|
530
845
|
}
|
|
531
846
|
return parsed;
|
|
532
847
|
}
|
|
533
|
-
function
|
|
848
|
+
function respondJson2(response, status, payload) {
|
|
534
849
|
response.writeHead(status, {
|
|
535
850
|
"content-type": "application/json"
|
|
536
851
|
});
|
|
537
852
|
response.end(JSON.stringify(payload));
|
|
538
853
|
}
|
|
539
|
-
function
|
|
854
|
+
function formatBoundUrl2(server) {
|
|
540
855
|
const address = server.address();
|
|
541
856
|
if (!address || typeof address === "string") {
|
|
542
857
|
return `http://${HTTP_HOST}`;
|
|
@@ -571,16 +886,16 @@ async function removeHttpBindingState(configDir, projectId) {
|
|
|
571
886
|
await rm(httpStatusPath(configDir, projectId), { force: true });
|
|
572
887
|
}
|
|
573
888
|
async function startHttpServer(input) {
|
|
574
|
-
const reader = new DashboardFsReader(input.runtimeRoot
|
|
889
|
+
const reader = new DashboardFsReader(input.runtimeRoot);
|
|
575
890
|
for (let port = input.initialPort; port <= 65535; port += 1) {
|
|
576
|
-
const server =
|
|
891
|
+
const server = createServer3((request, response) => {
|
|
577
892
|
void (async () => {
|
|
578
893
|
try {
|
|
579
894
|
const url = new URL(request.url ?? "/", `http://${HTTP_HOST}`);
|
|
580
895
|
if (request.method === "POST" && url.pathname === "/api/v1/refresh") {
|
|
581
896
|
request.resume();
|
|
582
897
|
input.service.requestReconcile();
|
|
583
|
-
|
|
898
|
+
respondJson2(response, 202, { ok: true });
|
|
584
899
|
return;
|
|
585
900
|
}
|
|
586
901
|
const resolved = await resolveDashboardResponse({
|
|
@@ -588,11 +903,11 @@ async function startHttpServer(input) {
|
|
|
588
903
|
method: request.method ?? "GET",
|
|
589
904
|
reader
|
|
590
905
|
});
|
|
591
|
-
|
|
906
|
+
respondJson2(response, resolved.status, resolved.payload);
|
|
592
907
|
} catch (error) {
|
|
593
908
|
logHttpRequestError(error);
|
|
594
909
|
if (!response.headersSent) {
|
|
595
|
-
|
|
910
|
+
respondJson2(response, 500, {
|
|
596
911
|
error: "Internal server error"
|
|
597
912
|
});
|
|
598
913
|
} else {
|
|
@@ -622,7 +937,7 @@ async function startHttpServer(input) {
|
|
|
622
937
|
return {
|
|
623
938
|
server,
|
|
624
939
|
port,
|
|
625
|
-
url:
|
|
940
|
+
url: formatBoundUrl2(server)
|
|
626
941
|
};
|
|
627
942
|
} catch (error) {
|
|
628
943
|
await closeHttpServer(server).catch(() => {
|
|
@@ -641,6 +956,9 @@ var handler = async (args, options) => {
|
|
|
641
956
|
setNoColor(options.noColor);
|
|
642
957
|
let parsed;
|
|
643
958
|
try {
|
|
959
|
+
if (rejectRemovedProjectId(args)) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
644
962
|
parsed = parseStartArgs(args);
|
|
645
963
|
} catch (error) {
|
|
646
964
|
process.stderr.write(
|
|
@@ -654,24 +972,33 @@ var handler = async (args, options) => {
|
|
|
654
972
|
process.stderr.write(`${parsed.error}
|
|
655
973
|
`);
|
|
656
974
|
process.stderr.write(
|
|
657
|
-
"Usage: gh-symphony start
|
|
975
|
+
"Usage: gh-symphony start [--daemon] [--once] [--http [port]] [--web [port]]\n"
|
|
658
976
|
);
|
|
659
977
|
process.exitCode = 2;
|
|
660
978
|
return;
|
|
661
979
|
}
|
|
662
980
|
if (parsed.daemon && parsed.once) {
|
|
663
|
-
process.stderr.write(
|
|
981
|
+
process.stderr.write(
|
|
982
|
+
"Options '--daemon' and '--once' cannot be used together\n"
|
|
983
|
+
);
|
|
664
984
|
process.exitCode = 2;
|
|
665
985
|
return;
|
|
666
986
|
}
|
|
667
987
|
const projectConfig = await resolveManagedProjectConfig({
|
|
668
988
|
configDir: options.configDir,
|
|
669
|
-
requestedProjectId:
|
|
989
|
+
requestedProjectId: void 0
|
|
670
990
|
});
|
|
671
991
|
if (!projectConfig) {
|
|
672
992
|
handleMissingManagedProjectConfig();
|
|
673
993
|
return;
|
|
674
994
|
}
|
|
995
|
+
if (!hasConfiguredRepository(projectConfig)) {
|
|
996
|
+
process.stderr.write(
|
|
997
|
+
"No repository is configured in this project. Run 'gh-symphony repo add owner/name' first.\n"
|
|
998
|
+
);
|
|
999
|
+
process.exitCode = 1;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
675
1002
|
const runtimeRoot = resolveRuntimeRoot(options.configDir);
|
|
676
1003
|
const projectId = projectConfig.projectId;
|
|
677
1004
|
let logLevel;
|
|
@@ -688,7 +1015,13 @@ var handler = async (args, options) => {
|
|
|
688
1015
|
return;
|
|
689
1016
|
}
|
|
690
1017
|
if (parsed.daemon) {
|
|
691
|
-
await startDaemon(
|
|
1018
|
+
await startDaemon(
|
|
1019
|
+
options,
|
|
1020
|
+
projectId,
|
|
1021
|
+
parsed.logLevel,
|
|
1022
|
+
parsed.httpPort,
|
|
1023
|
+
parsed.webPort
|
|
1024
|
+
);
|
|
692
1025
|
return;
|
|
693
1026
|
}
|
|
694
1027
|
if (!process.env.GITHUB_GRAPHQL_TOKEN) {
|
|
@@ -739,45 +1072,10 @@ var handler = async (args, options) => {
|
|
|
739
1072
|
}
|
|
740
1073
|
}
|
|
741
1074
|
});
|
|
742
|
-
const httpServer = parsed.httpPort !== void 0 ? await startHttpServer({
|
|
743
|
-
runtimeRoot,
|
|
744
|
-
projectId,
|
|
745
|
-
initialPort: parsed.httpPort,
|
|
746
|
-
service
|
|
747
|
-
}) : null;
|
|
748
|
-
if (httpServer) {
|
|
749
|
-
try {
|
|
750
|
-
await writeHttpBindingState(options.configDir, projectId, {
|
|
751
|
-
host: HTTP_HOST,
|
|
752
|
-
port: httpServer.port,
|
|
753
|
-
endpoint: httpServer.url
|
|
754
|
-
});
|
|
755
|
-
} catch (error) {
|
|
756
|
-
logLine(
|
|
757
|
-
yellow("\u26A0"),
|
|
758
|
-
yellow(
|
|
759
|
-
`Failed to persist HTTP binding state (http.json): ${error instanceof Error ? error.message : "Unknown error"}`
|
|
760
|
-
)
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
logLine(
|
|
765
|
-
green("\u25B2"),
|
|
766
|
-
`Starting orchestrator for project: ${bold(projectId)}`
|
|
767
|
-
);
|
|
768
|
-
if (httpServer) {
|
|
769
|
-
logLine(
|
|
770
|
-
cyan("\u25A1"),
|
|
771
|
-
`HTTP dashboard listening on ${httpServer.url}`
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
logLine(
|
|
775
|
-
dim("\xB7"),
|
|
776
|
-
dim(parsed.once ? "Running one orchestration tick" : "Press Ctrl+C to stop")
|
|
777
|
-
);
|
|
778
1075
|
let shuttingDown = false;
|
|
779
1076
|
let shutdownPromise = null;
|
|
780
1077
|
let keepHttpAliveResolve = null;
|
|
1078
|
+
let httpServer = null;
|
|
781
1079
|
const shutdown = async () => {
|
|
782
1080
|
if (shuttingDown) {
|
|
783
1081
|
return shutdownPromise;
|
|
@@ -805,17 +1103,66 @@ var handler = async (args, options) => {
|
|
|
805
1103
|
process.on("SIGINT", handleSigint);
|
|
806
1104
|
process.on("SIGTERM", handleSigterm);
|
|
807
1105
|
try {
|
|
1106
|
+
httpServer = parsed.webPort !== void 0 ? await startControlPlaneServer({
|
|
1107
|
+
host: HTTP_HOST,
|
|
1108
|
+
port: parsed.webPort,
|
|
1109
|
+
runtimeRoot,
|
|
1110
|
+
onRefreshRequest: () => service.requestReconcile()
|
|
1111
|
+
}) : parsed.httpPort !== void 0 ? await startHttpServer({
|
|
1112
|
+
runtimeRoot,
|
|
1113
|
+
projectId,
|
|
1114
|
+
initialPort: parsed.httpPort,
|
|
1115
|
+
service
|
|
1116
|
+
}) : null;
|
|
1117
|
+
if (httpServer) {
|
|
1118
|
+
try {
|
|
1119
|
+
await writeHttpBindingState(options.configDir, projectId, {
|
|
1120
|
+
host: HTTP_HOST,
|
|
1121
|
+
port: httpServer.port,
|
|
1122
|
+
endpoint: httpServer.url
|
|
1123
|
+
});
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
logLine(
|
|
1126
|
+
yellow("\u26A0"),
|
|
1127
|
+
yellow(
|
|
1128
|
+
`Failed to persist HTTP binding state (http.json): ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1129
|
+
)
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
logLine(
|
|
1134
|
+
green("\u25B2"),
|
|
1135
|
+
`Starting orchestrator for project: ${bold(projectId)}`
|
|
1136
|
+
);
|
|
1137
|
+
if (httpServer) {
|
|
1138
|
+
logLine(
|
|
1139
|
+
cyan("\u25A1"),
|
|
1140
|
+
parsed.webPort !== void 0 ? `Web dashboard listening on ${httpServer.url}` : `HTTP dashboard listening on ${httpServer.url}`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
logLine(
|
|
1144
|
+
dim("\xB7"),
|
|
1145
|
+
dim(
|
|
1146
|
+
parsed.once ? "Running one orchestration tick" : "Press Ctrl+C to stop"
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
808
1149
|
while (!shuttingDown) {
|
|
809
1150
|
try {
|
|
810
1151
|
await service.run({ once: parsed.once });
|
|
1152
|
+
if (shuttingDown) {
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
811
1155
|
if (parsed.once) {
|
|
812
1156
|
if (httpServer) {
|
|
813
1157
|
logLine(
|
|
814
1158
|
cyan("\u25A1"),
|
|
815
|
-
"One-shot tick completed; HTTP dashboard remains available until Ctrl+C"
|
|
1159
|
+
parsed.webPort !== void 0 ? "One-shot tick completed; web dashboard remains available until Ctrl+C" : "One-shot tick completed; HTTP dashboard remains available until Ctrl+C"
|
|
816
1160
|
);
|
|
817
|
-
|
|
818
|
-
|
|
1161
|
+
if (shuttingDown) {
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
await new Promise((resolve3) => {
|
|
1165
|
+
keepHttpAliveResolve = resolve3;
|
|
819
1166
|
});
|
|
820
1167
|
} else {
|
|
821
1168
|
await shutdown();
|
|
@@ -903,25 +1250,33 @@ async function shutdownForegroundOrchestrator(input) {
|
|
|
903
1250
|
}
|
|
904
1251
|
return (input.exit ?? process.exit)(0);
|
|
905
1252
|
}
|
|
1253
|
+
function hasConfiguredRepository(config) {
|
|
1254
|
+
return Boolean(config.repository?.owner && config.repository.name);
|
|
1255
|
+
}
|
|
906
1256
|
async function tailWorkerLog(runtimeRoot, projectId, runId, issueIdentifier) {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1257
|
+
for (const logPath of [
|
|
1258
|
+
join3(runtimeRoot, "runs", runId, "worker.log"),
|
|
1259
|
+
join3(runtimeRoot, "projects", projectId, "runs", runId, "worker.log")
|
|
1260
|
+
]) {
|
|
1261
|
+
try {
|
|
1262
|
+
const content = await readFile2(logPath, "utf8");
|
|
1263
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1264
|
+
if (lines.length === 0) return;
|
|
1265
|
+
const tail = lines.slice(-30);
|
|
1266
|
+
logLine(red("\u2717"), red(`Worker stderr (${issueIdentifier}):`));
|
|
1267
|
+
for (const line of tail) {
|
|
1268
|
+
process.stdout.write(` ${dim(line)}
|
|
916
1269
|
`);
|
|
1270
|
+
}
|
|
1271
|
+
return;
|
|
1272
|
+
} catch {
|
|
917
1273
|
}
|
|
918
|
-
} catch {
|
|
919
1274
|
}
|
|
920
1275
|
}
|
|
921
1276
|
var start_default = handler;
|
|
922
|
-
async function startDaemon(options, projectId, logLevel, httpPort) {
|
|
1277
|
+
async function startDaemon(options, projectId, logLevel, httpPort, webPort) {
|
|
923
1278
|
const logPath = orchestratorLogPath(options.configDir, projectId);
|
|
924
|
-
await mkdir(
|
|
1279
|
+
await mkdir(dirname2(logPath), { recursive: true });
|
|
925
1280
|
const { openSync } = await import("fs");
|
|
926
1281
|
const logFd = openSync(logPath, "a");
|
|
927
1282
|
const child = spawn(
|
|
@@ -929,9 +1284,8 @@ async function startDaemon(options, projectId, logLevel, httpPort) {
|
|
|
929
1284
|
[
|
|
930
1285
|
process.argv[1],
|
|
931
1286
|
"start",
|
|
932
|
-
"--project",
|
|
933
|
-
projectId,
|
|
934
1287
|
...httpPort !== void 0 ? ["--http", String(httpPort)] : [],
|
|
1288
|
+
...webPort !== void 0 ? ["--web", String(webPort)] : [],
|
|
935
1289
|
...logLevel ? ["--log-level", logLevel] : []
|
|
936
1290
|
],
|
|
937
1291
|
{
|
|
@@ -945,7 +1299,7 @@ async function startDaemon(options, projectId, logLevel, httpPort) {
|
|
|
945
1299
|
}
|
|
946
1300
|
);
|
|
947
1301
|
const pidPath = daemonPidPath(options.configDir, projectId);
|
|
948
|
-
await mkdir(
|
|
1302
|
+
await mkdir(dirname2(pidPath), { recursive: true });
|
|
949
1303
|
await writeFile(pidPath, String(child.pid), "utf8");
|
|
950
1304
|
child.unref();
|
|
951
1305
|
const { closeSync } = await import("fs");
|
|
@@ -953,7 +1307,7 @@ async function startDaemon(options, projectId, logLevel, httpPort) {
|
|
|
953
1307
|
process.stdout.write(
|
|
954
1308
|
`Orchestrator started in background (PID: ${child.pid}).
|
|
955
1309
|
Logs: ${logPath}
|
|
956
|
-
Stop with: gh-symphony
|
|
1310
|
+
Stop with: gh-symphony repo stop
|
|
957
1311
|
`
|
|
958
1312
|
);
|
|
959
1313
|
}
|