codeharbor 0.1.13 → 0.1.14
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 +10 -1
- package/dist/cli.js +689 -116
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -24,30 +24,78 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
27
|
+
var import_node_child_process7 = require("child_process");
|
|
28
28
|
var import_node_fs11 = __toESM(require("fs"));
|
|
29
|
-
var
|
|
29
|
+
var import_node_path14 = __toESM(require("path"));
|
|
30
30
|
var import_commander = require("commander");
|
|
31
31
|
|
|
32
32
|
// src/app.ts
|
|
33
|
-
var
|
|
34
|
-
var
|
|
33
|
+
var import_node_child_process5 = require("child_process");
|
|
34
|
+
var import_node_util3 = require("util");
|
|
35
35
|
|
|
36
36
|
// src/admin-server.ts
|
|
37
|
-
var
|
|
37
|
+
var import_node_child_process3 = require("child_process");
|
|
38
38
|
var import_node_fs4 = __toESM(require("fs"));
|
|
39
39
|
var import_node_http = __toESM(require("http"));
|
|
40
|
-
var
|
|
41
|
-
var
|
|
40
|
+
var import_node_path5 = __toESM(require("path"));
|
|
41
|
+
var import_node_util2 = require("util");
|
|
42
42
|
|
|
43
43
|
// src/init.ts
|
|
44
44
|
var import_node_fs = __toESM(require("fs"));
|
|
45
|
-
var
|
|
45
|
+
var import_node_path2 = __toESM(require("path"));
|
|
46
46
|
var import_promises = require("readline/promises");
|
|
47
47
|
var import_dotenv = __toESM(require("dotenv"));
|
|
48
|
+
|
|
49
|
+
// src/codex-bin.ts
|
|
50
|
+
var import_node_child_process = require("child_process");
|
|
51
|
+
var import_node_os = __toESM(require("os"));
|
|
52
|
+
var import_node_path = __toESM(require("path"));
|
|
53
|
+
var import_node_util = require("util");
|
|
54
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
55
|
+
function buildCodexBinCandidates(configuredBin, env = process.env) {
|
|
56
|
+
const normalized = configuredBin.trim() || "codex";
|
|
57
|
+
const home = env.HOME?.trim() || import_node_os.default.homedir();
|
|
58
|
+
const npmGlobalBin = home ? import_node_path.default.resolve(home, ".npm-global/bin/codex") : "";
|
|
59
|
+
const candidates = [
|
|
60
|
+
normalized,
|
|
61
|
+
"codex",
|
|
62
|
+
npmGlobalBin,
|
|
63
|
+
"/usr/bin/codex",
|
|
64
|
+
"/usr/local/bin/codex",
|
|
65
|
+
"/opt/homebrew/bin/codex"
|
|
66
|
+
];
|
|
67
|
+
const seen = /* @__PURE__ */ new Set();
|
|
68
|
+
const output = [];
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
const trimmed = candidate.trim();
|
|
71
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(trimmed);
|
|
75
|
+
output.push(trimmed);
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
async function findWorkingCodexBin(configuredBin, options = {}) {
|
|
80
|
+
const checkBinary = options.checkBinary ?? defaultCheckBinary;
|
|
81
|
+
const candidates = buildCodexBinCandidates(configuredBin, options.env);
|
|
82
|
+
for (const candidate of candidates) {
|
|
83
|
+
try {
|
|
84
|
+
await checkBinary(candidate);
|
|
85
|
+
return candidate;
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
async function defaultCheckBinary(bin) {
|
|
92
|
+
await execFileAsync(bin, ["--version"]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/init.ts
|
|
48
96
|
async function runInitCommand(options = {}) {
|
|
49
97
|
const cwd = options.cwd ?? process.cwd();
|
|
50
|
-
const envPath =
|
|
98
|
+
const envPath = import_node_path2.default.resolve(cwd, ".env");
|
|
51
99
|
const templatePath = resolveInitTemplatePath(cwd);
|
|
52
100
|
const input = options.input ?? process.stdin;
|
|
53
101
|
const output = options.output ?? process.stdout;
|
|
@@ -70,6 +118,7 @@ async function runInitCommand(options = {}) {
|
|
|
70
118
|
output.write("CodeHarbor setup wizard\n");
|
|
71
119
|
output.write(`Target file: ${envPath}
|
|
72
120
|
`);
|
|
121
|
+
const detectedCodexBin = await findWorkingCodexBin(existingValues.CODEX_BIN ?? "codex");
|
|
73
122
|
const questions = [
|
|
74
123
|
{
|
|
75
124
|
key: "MATRIX_HOMESERVER",
|
|
@@ -109,14 +158,14 @@ async function runInitCommand(options = {}) {
|
|
|
109
158
|
{
|
|
110
159
|
key: "CODEX_BIN",
|
|
111
160
|
label: "Codex binary",
|
|
112
|
-
fallbackValue: "codex"
|
|
161
|
+
fallbackValue: detectedCodexBin ?? "codex"
|
|
113
162
|
},
|
|
114
163
|
{
|
|
115
164
|
key: "CODEX_WORKDIR",
|
|
116
165
|
label: "Codex working directory",
|
|
117
166
|
fallbackValue: cwd,
|
|
118
167
|
validate: (value) => {
|
|
119
|
-
const resolved =
|
|
168
|
+
const resolved = import_node_path2.default.resolve(cwd, value);
|
|
120
169
|
if (!import_node_fs.default.existsSync(resolved) || !import_node_fs.default.statSync(resolved).isDirectory()) {
|
|
121
170
|
return `Directory does not exist: ${resolved}`;
|
|
122
171
|
}
|
|
@@ -144,8 +193,8 @@ async function runInitCommand(options = {}) {
|
|
|
144
193
|
}
|
|
145
194
|
function resolveInitTemplatePath(cwd) {
|
|
146
195
|
const candidates = [
|
|
147
|
-
|
|
148
|
-
|
|
196
|
+
import_node_path2.default.resolve(cwd, ".env.example"),
|
|
197
|
+
import_node_path2.default.resolve(__dirname, "..", ".env.example")
|
|
149
198
|
];
|
|
150
199
|
for (const candidate of candidates) {
|
|
151
200
|
if (import_node_fs.default.existsSync(candidate)) {
|
|
@@ -221,33 +270,33 @@ async function askYesNo(rl, question, defaultValue) {
|
|
|
221
270
|
}
|
|
222
271
|
|
|
223
272
|
// src/service-manager.ts
|
|
224
|
-
var
|
|
273
|
+
var import_node_child_process2 = require("child_process");
|
|
225
274
|
var import_node_fs3 = __toESM(require("fs"));
|
|
226
|
-
var
|
|
227
|
-
var
|
|
275
|
+
var import_node_os3 = __toESM(require("os"));
|
|
276
|
+
var import_node_path4 = __toESM(require("path"));
|
|
228
277
|
|
|
229
278
|
// src/runtime-home.ts
|
|
230
279
|
var import_node_fs2 = __toESM(require("fs"));
|
|
231
|
-
var
|
|
232
|
-
var
|
|
280
|
+
var import_node_os2 = __toESM(require("os"));
|
|
281
|
+
var import_node_path3 = __toESM(require("path"));
|
|
233
282
|
var LEGACY_RUNTIME_HOME = "/opt/codeharbor";
|
|
234
283
|
var USER_RUNTIME_HOME_DIR = ".codeharbor";
|
|
235
|
-
var DEFAULT_RUNTIME_HOME =
|
|
284
|
+
var DEFAULT_RUNTIME_HOME = import_node_path3.default.resolve(import_node_os2.default.homedir(), USER_RUNTIME_HOME_DIR);
|
|
236
285
|
var RUNTIME_HOME_ENV_KEY = "CODEHARBOR_HOME";
|
|
237
286
|
function resolveRuntimeHome(env = process.env) {
|
|
238
287
|
const configured = env[RUNTIME_HOME_ENV_KEY]?.trim();
|
|
239
288
|
if (configured) {
|
|
240
|
-
return
|
|
289
|
+
return import_node_path3.default.resolve(configured);
|
|
241
290
|
}
|
|
242
|
-
const legacyEnvPath =
|
|
291
|
+
const legacyEnvPath = import_node_path3.default.resolve(LEGACY_RUNTIME_HOME, ".env");
|
|
243
292
|
if (import_node_fs2.default.existsSync(legacyEnvPath)) {
|
|
244
293
|
return LEGACY_RUNTIME_HOME;
|
|
245
294
|
}
|
|
246
295
|
return resolveUserRuntimeHome(env);
|
|
247
296
|
}
|
|
248
297
|
function resolveUserRuntimeHome(env = process.env) {
|
|
249
|
-
const home = env.HOME?.trim() ||
|
|
250
|
-
return
|
|
298
|
+
const home = env.HOME?.trim() || import_node_os2.default.homedir();
|
|
299
|
+
return import_node_path3.default.resolve(home, USER_RUNTIME_HOME_DIR);
|
|
251
300
|
}
|
|
252
301
|
|
|
253
302
|
// src/service-manager.ts
|
|
@@ -266,7 +315,7 @@ function resolveDefaultRunUser(env = process.env) {
|
|
|
266
315
|
return user;
|
|
267
316
|
}
|
|
268
317
|
try {
|
|
269
|
-
return
|
|
318
|
+
return import_node_os3.default.userInfo().username;
|
|
270
319
|
} catch {
|
|
271
320
|
return "root";
|
|
272
321
|
}
|
|
@@ -274,17 +323,17 @@ function resolveDefaultRunUser(env = process.env) {
|
|
|
274
323
|
function resolveRuntimeHomeForUser(runUser, env = process.env, explicitRuntimeHome) {
|
|
275
324
|
const configuredRuntimeHome = explicitRuntimeHome?.trim() || env[RUNTIME_HOME_ENV_KEY]?.trim();
|
|
276
325
|
if (configuredRuntimeHome) {
|
|
277
|
-
return
|
|
326
|
+
return import_node_path4.default.resolve(configuredRuntimeHome);
|
|
278
327
|
}
|
|
279
328
|
const userHome = resolveUserHome(runUser);
|
|
280
329
|
if (userHome) {
|
|
281
|
-
return
|
|
330
|
+
return import_node_path4.default.resolve(userHome, USER_RUNTIME_HOME_DIR);
|
|
282
331
|
}
|
|
283
|
-
return
|
|
332
|
+
return import_node_path4.default.resolve(import_node_os3.default.homedir(), USER_RUNTIME_HOME_DIR);
|
|
284
333
|
}
|
|
285
334
|
function buildMainServiceUnit(options) {
|
|
286
335
|
validateUnitOptions(options);
|
|
287
|
-
const runtimeHome2 =
|
|
336
|
+
const runtimeHome2 = import_node_path4.default.resolve(options.runtimeHome);
|
|
288
337
|
return [
|
|
289
338
|
"[Unit]",
|
|
290
339
|
"Description=CodeHarbor main service",
|
|
@@ -296,7 +345,7 @@ function buildMainServiceUnit(options) {
|
|
|
296
345
|
`User=${options.runUser}`,
|
|
297
346
|
`WorkingDirectory=${runtimeHome2}`,
|
|
298
347
|
`Environment=CODEHARBOR_HOME=${runtimeHome2}`,
|
|
299
|
-
`ExecStart=${
|
|
348
|
+
`ExecStart=${import_node_path4.default.resolve(options.nodeBinPath)} ${import_node_path4.default.resolve(options.cliScriptPath)} start`,
|
|
300
349
|
"Restart=always",
|
|
301
350
|
"RestartSec=3",
|
|
302
351
|
"NoNewPrivileges=true",
|
|
@@ -312,7 +361,7 @@ function buildMainServiceUnit(options) {
|
|
|
312
361
|
}
|
|
313
362
|
function buildAdminServiceUnit(options) {
|
|
314
363
|
validateUnitOptions(options);
|
|
315
|
-
const runtimeHome2 =
|
|
364
|
+
const runtimeHome2 = import_node_path4.default.resolve(options.runtimeHome);
|
|
316
365
|
return [
|
|
317
366
|
"[Unit]",
|
|
318
367
|
"Description=CodeHarbor admin service",
|
|
@@ -324,7 +373,7 @@ function buildAdminServiceUnit(options) {
|
|
|
324
373
|
`User=${options.runUser}`,
|
|
325
374
|
`WorkingDirectory=${runtimeHome2}`,
|
|
326
375
|
`Environment=CODEHARBOR_HOME=${runtimeHome2}`,
|
|
327
|
-
`ExecStart=${
|
|
376
|
+
`ExecStart=${import_node_path4.default.resolve(options.nodeBinPath)} ${import_node_path4.default.resolve(options.cliScriptPath)} admin serve`,
|
|
328
377
|
"Restart=always",
|
|
329
378
|
"RestartSec=3",
|
|
330
379
|
"NoNewPrivileges=true",
|
|
@@ -343,7 +392,7 @@ function buildRestartSudoersPolicy(options) {
|
|
|
343
392
|
const systemctlPath = options.systemctlPath.trim();
|
|
344
393
|
validateSimpleValue(runUser, "runUser");
|
|
345
394
|
validateSimpleValue(systemctlPath, "systemctlPath");
|
|
346
|
-
if (!
|
|
395
|
+
if (!import_node_path4.default.isAbsolute(systemctlPath)) {
|
|
347
396
|
throw new Error("systemctlPath must be an absolute path.");
|
|
348
397
|
}
|
|
349
398
|
return [
|
|
@@ -358,7 +407,7 @@ function installSystemdServices(options) {
|
|
|
358
407
|
assertRootPrivileges();
|
|
359
408
|
const output = options.output ?? process.stdout;
|
|
360
409
|
const runUser = options.runUser.trim();
|
|
361
|
-
const runtimeHome2 =
|
|
410
|
+
const runtimeHome2 = import_node_path4.default.resolve(options.runtimeHome);
|
|
362
411
|
validateSimpleValue(runUser, "runUser");
|
|
363
412
|
validateSimpleValue(runtimeHome2, "runtimeHome");
|
|
364
413
|
validateSimpleValue(options.nodeBinPath, "nodeBinPath");
|
|
@@ -367,9 +416,9 @@ function installSystemdServices(options) {
|
|
|
367
416
|
const runGroup = resolveUserGroup(runUser);
|
|
368
417
|
import_node_fs3.default.mkdirSync(runtimeHome2, { recursive: true });
|
|
369
418
|
runCommand("chown", ["-R", `${runUser}:${runGroup}`, runtimeHome2]);
|
|
370
|
-
const mainPath =
|
|
371
|
-
const adminPath =
|
|
372
|
-
const restartSudoersPath =
|
|
419
|
+
const mainPath = import_node_path4.default.join(SYSTEMD_DIR, MAIN_SERVICE_NAME);
|
|
420
|
+
const adminPath = import_node_path4.default.join(SYSTEMD_DIR, ADMIN_SERVICE_NAME);
|
|
421
|
+
const restartSudoersPath = import_node_path4.default.join(SUDOERS_DIR, RESTART_SUDOERS_FILE);
|
|
373
422
|
const unitOptions = {
|
|
374
423
|
runUser,
|
|
375
424
|
runtimeHome: runtimeHome2,
|
|
@@ -417,9 +466,9 @@ function uninstallSystemdServices(options) {
|
|
|
417
466
|
assertLinuxWithSystemd();
|
|
418
467
|
assertRootPrivileges();
|
|
419
468
|
const output = options.output ?? process.stdout;
|
|
420
|
-
const mainPath =
|
|
421
|
-
const adminPath =
|
|
422
|
-
const restartSudoersPath =
|
|
469
|
+
const mainPath = import_node_path4.default.join(SYSTEMD_DIR, MAIN_SERVICE_NAME);
|
|
470
|
+
const adminPath = import_node_path4.default.join(SYSTEMD_DIR, ADMIN_SERVICE_NAME);
|
|
471
|
+
const restartSudoersPath = import_node_path4.default.join(SUDOERS_DIR, RESTART_SUDOERS_FILE);
|
|
423
472
|
stopAndDisableIfPresent(MAIN_SERVICE_NAME);
|
|
424
473
|
if (import_node_fs3.default.existsSync(mainPath)) {
|
|
425
474
|
import_node_fs3.default.unlinkSync(mainPath);
|
|
@@ -492,7 +541,7 @@ function assertLinuxWithSystemd() {
|
|
|
492
541
|
throw new Error("Systemd service install only supports Linux.");
|
|
493
542
|
}
|
|
494
543
|
try {
|
|
495
|
-
(0,
|
|
544
|
+
(0, import_node_child_process2.execFileSync)("systemctl", ["--version"], { stdio: "ignore" });
|
|
496
545
|
} catch {
|
|
497
546
|
throw new Error("systemctl is required but not found.");
|
|
498
547
|
}
|
|
@@ -539,13 +588,13 @@ function runSystemctlIgnoreFailure(args) {
|
|
|
539
588
|
}
|
|
540
589
|
function resolveSystemctlPath() {
|
|
541
590
|
const candidates = [];
|
|
542
|
-
const pathEntries = (process.env.PATH ?? "").split(
|
|
591
|
+
const pathEntries = (process.env.PATH ?? "").split(import_node_path4.default.delimiter).filter(Boolean);
|
|
543
592
|
for (const entry of pathEntries) {
|
|
544
|
-
candidates.push(
|
|
593
|
+
candidates.push(import_node_path4.default.join(entry, "systemctl"));
|
|
545
594
|
}
|
|
546
595
|
candidates.push("/usr/bin/systemctl", "/bin/systemctl", "/usr/local/bin/systemctl");
|
|
547
596
|
for (const candidate of candidates) {
|
|
548
|
-
if (
|
|
597
|
+
if (import_node_path4.default.isAbsolute(candidate) && import_node_fs3.default.existsSync(candidate)) {
|
|
549
598
|
return candidate;
|
|
550
599
|
}
|
|
551
600
|
}
|
|
@@ -553,7 +602,7 @@ function resolveSystemctlPath() {
|
|
|
553
602
|
}
|
|
554
603
|
function runCommand(file, args) {
|
|
555
604
|
try {
|
|
556
|
-
return (0,
|
|
605
|
+
return (0, import_node_child_process2.execFileSync)(file, args, {
|
|
557
606
|
encoding: "utf8",
|
|
558
607
|
stdio: ["ignore", "pipe", "pipe"]
|
|
559
608
|
});
|
|
@@ -581,7 +630,7 @@ function bufferToTrimmedString(value) {
|
|
|
581
630
|
}
|
|
582
631
|
|
|
583
632
|
// src/admin-server.ts
|
|
584
|
-
var
|
|
633
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
|
|
585
634
|
var HttpError = class extends Error {
|
|
586
635
|
statusCode;
|
|
587
636
|
constructor(statusCode, message) {
|
|
@@ -839,7 +888,7 @@ var AdminServer = class {
|
|
|
839
888
|
updatedKeys.push("matrixCommandPrefix");
|
|
840
889
|
}
|
|
841
890
|
if ("codexWorkdir" in body) {
|
|
842
|
-
const workdir =
|
|
891
|
+
const workdir = import_node_path5.default.resolve(String(body.codexWorkdir ?? "").trim());
|
|
843
892
|
ensureDirectory(workdir, "codexWorkdir");
|
|
844
893
|
this.config.codexWorkdir = workdir;
|
|
845
894
|
envUpdates.CODEX_WORKDIR = workdir;
|
|
@@ -1063,8 +1112,8 @@ var AdminServer = class {
|
|
|
1063
1112
|
return this.adminIpAllowlist.includes(normalizedRemote);
|
|
1064
1113
|
}
|
|
1065
1114
|
persistEnvUpdates(updates) {
|
|
1066
|
-
const envPath =
|
|
1067
|
-
const examplePath =
|
|
1115
|
+
const envPath = import_node_path5.default.resolve(this.cwd, ".env");
|
|
1116
|
+
const examplePath = import_node_path5.default.resolve(this.cwd, ".env.example");
|
|
1068
1117
|
const template = import_node_fs4.default.existsSync(envPath) ? import_node_fs4.default.readFileSync(envPath, "utf8") : import_node_fs4.default.existsSync(examplePath) ? import_node_fs4.default.readFileSync(examplePath, "utf8") : "";
|
|
1069
1118
|
const next = applyEnvOverrides(template, updates);
|
|
1070
1119
|
import_node_fs4.default.writeFileSync(envPath, next, "utf8");
|
|
@@ -2219,7 +2268,7 @@ var ADMIN_CONSOLE_HTML = `<!doctype html>
|
|
|
2219
2268
|
`;
|
|
2220
2269
|
async function defaultCheckCodex(bin) {
|
|
2221
2270
|
try {
|
|
2222
|
-
const { stdout } = await
|
|
2271
|
+
const { stdout } = await execFileAsync2(bin, ["--version"]);
|
|
2223
2272
|
return {
|
|
2224
2273
|
ok: true,
|
|
2225
2274
|
version: stdout.trim() || null,
|
|
@@ -2262,8 +2311,8 @@ async function defaultCheckMatrix(homeserver, timeoutMs) {
|
|
|
2262
2311
|
|
|
2263
2312
|
// src/channels/matrix-channel.ts
|
|
2264
2313
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
2265
|
-
var
|
|
2266
|
-
var
|
|
2314
|
+
var import_node_os4 = __toESM(require("os"));
|
|
2315
|
+
var import_node_path6 = __toESM(require("path"));
|
|
2267
2316
|
var import_matrix_js_sdk = require("matrix-js-sdk");
|
|
2268
2317
|
|
|
2269
2318
|
// src/utils/message.ts
|
|
@@ -2686,10 +2735,10 @@ var MatrixChannel = class {
|
|
|
2686
2735
|
}
|
|
2687
2736
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
2688
2737
|
const extension = resolveFileExtension(fileName, mimeType);
|
|
2689
|
-
const directory =
|
|
2738
|
+
const directory = import_node_path6.default.join(import_node_os4.default.tmpdir(), "codeharbor-media");
|
|
2690
2739
|
await import_promises2.default.mkdir(directory, { recursive: true });
|
|
2691
2740
|
const safeEventId = sanitizeFilename(eventId);
|
|
2692
|
-
const targetPath =
|
|
2741
|
+
const targetPath = import_node_path6.default.join(directory, `${safeEventId}-${index}${extension}`);
|
|
2693
2742
|
await import_promises2.default.writeFile(targetPath, bytes);
|
|
2694
2743
|
return targetPath;
|
|
2695
2744
|
}
|
|
@@ -2774,7 +2823,7 @@ function sanitizeFilename(value) {
|
|
|
2774
2823
|
return value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
|
|
2775
2824
|
}
|
|
2776
2825
|
function resolveFileExtension(fileName, mimeType) {
|
|
2777
|
-
const ext =
|
|
2826
|
+
const ext = import_node_path6.default.extname(fileName).trim();
|
|
2778
2827
|
if (ext) {
|
|
2779
2828
|
return ext;
|
|
2780
2829
|
}
|
|
@@ -2792,13 +2841,13 @@ function resolveFileExtension(fileName, mimeType) {
|
|
|
2792
2841
|
|
|
2793
2842
|
// src/config-service.ts
|
|
2794
2843
|
var import_node_fs5 = __toESM(require("fs"));
|
|
2795
|
-
var
|
|
2844
|
+
var import_node_path7 = __toESM(require("path"));
|
|
2796
2845
|
var ConfigService = class {
|
|
2797
2846
|
stateStore;
|
|
2798
2847
|
defaultWorkdir;
|
|
2799
2848
|
constructor(stateStore, defaultWorkdir) {
|
|
2800
2849
|
this.stateStore = stateStore;
|
|
2801
|
-
this.defaultWorkdir =
|
|
2850
|
+
this.defaultWorkdir = import_node_path7.default.resolve(defaultWorkdir);
|
|
2802
2851
|
}
|
|
2803
2852
|
resolveRoomConfig(roomId, fallbackPolicy) {
|
|
2804
2853
|
const room = this.stateStore.getRoomSettings(roomId);
|
|
@@ -2869,7 +2918,7 @@ function normalizeRoomSettingsInput(input) {
|
|
|
2869
2918
|
if (!roomId) {
|
|
2870
2919
|
throw new Error("roomId is required.");
|
|
2871
2920
|
}
|
|
2872
|
-
const workdir =
|
|
2921
|
+
const workdir = import_node_path7.default.resolve(input.workdir);
|
|
2873
2922
|
if (!import_node_fs5.default.existsSync(workdir) || !import_node_fs5.default.statSync(workdir).isDirectory()) {
|
|
2874
2923
|
throw new Error(`workdir does not exist or is not a directory: ${workdir}`);
|
|
2875
2924
|
}
|
|
@@ -2885,7 +2934,7 @@ function normalizeRoomSettingsInput(input) {
|
|
|
2885
2934
|
}
|
|
2886
2935
|
|
|
2887
2936
|
// src/executor/codex-executor.ts
|
|
2888
|
-
var
|
|
2937
|
+
var import_node_child_process4 = require("child_process");
|
|
2889
2938
|
var import_node_readline = __toESM(require("readline"));
|
|
2890
2939
|
var CodexExecutionCancelledError = class extends Error {
|
|
2891
2940
|
constructor(message = "codex execution cancelled") {
|
|
@@ -2903,7 +2952,7 @@ var CodexExecutor = class {
|
|
|
2903
2952
|
}
|
|
2904
2953
|
startExecution(prompt, sessionId, onProgress, startOptions) {
|
|
2905
2954
|
const args = buildCodexArgs(prompt, sessionId, this.options, startOptions);
|
|
2906
|
-
const child = (0,
|
|
2955
|
+
const child = (0, import_node_child_process4.spawn)(this.options.bin, args, {
|
|
2907
2956
|
cwd: startOptions?.workdir ?? this.options.workdir,
|
|
2908
2957
|
env: {
|
|
2909
2958
|
...process.env,
|
|
@@ -3136,17 +3185,17 @@ function stringify(value) {
|
|
|
3136
3185
|
|
|
3137
3186
|
// src/orchestrator.ts
|
|
3138
3187
|
var import_async_mutex = require("async-mutex");
|
|
3139
|
-
var
|
|
3188
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
3140
3189
|
|
|
3141
3190
|
// src/compat/cli-compat-recorder.ts
|
|
3142
3191
|
var import_node_fs6 = __toESM(require("fs"));
|
|
3143
|
-
var
|
|
3192
|
+
var import_node_path8 = __toESM(require("path"));
|
|
3144
3193
|
var CliCompatRecorder = class {
|
|
3145
3194
|
filePath;
|
|
3146
3195
|
chain = Promise.resolve();
|
|
3147
3196
|
constructor(filePath) {
|
|
3148
|
-
this.filePath =
|
|
3149
|
-
import_node_fs6.default.mkdirSync(
|
|
3197
|
+
this.filePath = import_node_path8.default.resolve(filePath);
|
|
3198
|
+
import_node_fs6.default.mkdirSync(import_node_path8.default.dirname(this.filePath), { recursive: true });
|
|
3150
3199
|
}
|
|
3151
3200
|
append(entry) {
|
|
3152
3201
|
const payload = `${JSON.stringify(entry)}
|
|
@@ -3623,6 +3672,296 @@ function createIdleWorkflowSnapshot() {
|
|
|
3623
3672
|
};
|
|
3624
3673
|
}
|
|
3625
3674
|
|
|
3675
|
+
// src/workflow/autodev.ts
|
|
3676
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
3677
|
+
var import_node_path9 = __toESM(require("path"));
|
|
3678
|
+
function parseAutoDevCommand(text) {
|
|
3679
|
+
const normalized = text.trim();
|
|
3680
|
+
if (!/^\/autodev(?:\s|$)/i.test(normalized)) {
|
|
3681
|
+
return null;
|
|
3682
|
+
}
|
|
3683
|
+
const parts = normalized.split(/\s+/);
|
|
3684
|
+
if (parts.length === 1 || parts[1]?.toLowerCase() === "status") {
|
|
3685
|
+
return { kind: "status" };
|
|
3686
|
+
}
|
|
3687
|
+
if (parts[1]?.toLowerCase() !== "run") {
|
|
3688
|
+
return null;
|
|
3689
|
+
}
|
|
3690
|
+
const taskId = normalized.replace(/^\/autodev\s+run\s*/i, "").trim();
|
|
3691
|
+
return {
|
|
3692
|
+
kind: "run",
|
|
3693
|
+
taskId: taskId || null
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
async function loadAutoDevContext(workdir) {
|
|
3697
|
+
const requirementsPath = import_node_path9.default.join(workdir, "REQUIREMENTS.md");
|
|
3698
|
+
const taskListPath = import_node_path9.default.join(workdir, "TASK_LIST.md");
|
|
3699
|
+
const requirementsContent = await readOptionalFile(requirementsPath);
|
|
3700
|
+
const taskListContent = await readOptionalFile(taskListPath);
|
|
3701
|
+
return {
|
|
3702
|
+
workdir,
|
|
3703
|
+
requirementsPath,
|
|
3704
|
+
taskListPath,
|
|
3705
|
+
requirementsContent,
|
|
3706
|
+
taskListContent,
|
|
3707
|
+
tasks: taskListContent ? parseTasks(taskListContent) : []
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
function summarizeAutoDevTasks(tasks) {
|
|
3711
|
+
const summary = {
|
|
3712
|
+
total: tasks.length,
|
|
3713
|
+
pending: 0,
|
|
3714
|
+
inProgress: 0,
|
|
3715
|
+
completed: 0,
|
|
3716
|
+
cancelled: 0,
|
|
3717
|
+
blocked: 0
|
|
3718
|
+
};
|
|
3719
|
+
for (const task of tasks) {
|
|
3720
|
+
if (task.status === "pending") {
|
|
3721
|
+
summary.pending += 1;
|
|
3722
|
+
continue;
|
|
3723
|
+
}
|
|
3724
|
+
if (task.status === "in_progress") {
|
|
3725
|
+
summary.inProgress += 1;
|
|
3726
|
+
continue;
|
|
3727
|
+
}
|
|
3728
|
+
if (task.status === "completed") {
|
|
3729
|
+
summary.completed += 1;
|
|
3730
|
+
continue;
|
|
3731
|
+
}
|
|
3732
|
+
if (task.status === "cancelled") {
|
|
3733
|
+
summary.cancelled += 1;
|
|
3734
|
+
continue;
|
|
3735
|
+
}
|
|
3736
|
+
summary.blocked += 1;
|
|
3737
|
+
}
|
|
3738
|
+
return summary;
|
|
3739
|
+
}
|
|
3740
|
+
function selectAutoDevTask(tasks, taskId) {
|
|
3741
|
+
if (taskId) {
|
|
3742
|
+
const normalizedTarget = taskId.trim().toLowerCase();
|
|
3743
|
+
return tasks.find((task) => task.id.toLowerCase() === normalizedTarget) ?? null;
|
|
3744
|
+
}
|
|
3745
|
+
const inProgressTask = tasks.find((task) => task.status === "in_progress");
|
|
3746
|
+
if (inProgressTask) {
|
|
3747
|
+
return inProgressTask;
|
|
3748
|
+
}
|
|
3749
|
+
return tasks.find((task) => task.status === "pending") ?? null;
|
|
3750
|
+
}
|
|
3751
|
+
function buildAutoDevObjective(task) {
|
|
3752
|
+
return [
|
|
3753
|
+
"\u4F60\u6B63\u5728\u6267\u884C CodeHarbor AutoDev \u4EFB\u52A1\uFF0C\u8BF7\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u5B8C\u6210\u6307\u5B9A\u5F00\u53D1\u76EE\u6807\u3002",
|
|
3754
|
+
"",
|
|
3755
|
+
`\u4EFB\u52A1ID: ${task.id}`,
|
|
3756
|
+
`\u4EFB\u52A1\u63CF\u8FF0: ${task.description}`,
|
|
3757
|
+
"",
|
|
3758
|
+
"\u4E0A\u4E0B\u6587\u6587\u4EF6\uFF1A",
|
|
3759
|
+
"- REQUIREMENTS.md\uFF08\u9700\u6C42\u57FA\u7EBF\uFF09",
|
|
3760
|
+
"- TASK_LIST.md\uFF08\u4EFB\u52A1\u72B6\u6001\uFF09",
|
|
3761
|
+
"",
|
|
3762
|
+
"\u6267\u884C\u8981\u6C42\uFF1A",
|
|
3763
|
+
"1. \u5148\u8BFB\u53D6 REQUIREMENTS.md \u548C TASK_LIST.md\uFF0C\u786E\u8BA4\u8FB9\u754C\u4E0E\u7EA6\u675F\u3002",
|
|
3764
|
+
"2. \u5728\u5F53\u524D\u4ED3\u5E93\u76F4\u63A5\u5B8C\u6210\u4EE3\u7801\u4E0E\u6D4B\u8BD5\u6539\u52A8\u3002",
|
|
3765
|
+
"3. \u8FD0\u884C\u53D7\u5F71\u54CD\u9A8C\u8BC1\u547D\u4EE4\u5E76\u6C47\u603B\u7ED3\u679C\u3002",
|
|
3766
|
+
"4. \u8F93\u51FA\u6539\u52A8\u6587\u4EF6\u548C\u98CE\u9669\u8BF4\u660E\u3002"
|
|
3767
|
+
].join("\n");
|
|
3768
|
+
}
|
|
3769
|
+
function formatTaskForDisplay(task) {
|
|
3770
|
+
return `${task.id} ${task.description} (${statusToSymbol(task.status)})`;
|
|
3771
|
+
}
|
|
3772
|
+
function statusToSymbol(status) {
|
|
3773
|
+
if (status === "pending") {
|
|
3774
|
+
return "\u2B1C";
|
|
3775
|
+
}
|
|
3776
|
+
if (status === "in_progress") {
|
|
3777
|
+
return "\u{1F504}";
|
|
3778
|
+
}
|
|
3779
|
+
if (status === "completed") {
|
|
3780
|
+
return "\u2705";
|
|
3781
|
+
}
|
|
3782
|
+
if (status === "cancelled") {
|
|
3783
|
+
return "\u274C";
|
|
3784
|
+
}
|
|
3785
|
+
return "\u{1F6AB}";
|
|
3786
|
+
}
|
|
3787
|
+
async function updateAutoDevTaskStatus(taskListPath, task, nextStatus) {
|
|
3788
|
+
const content = await import_promises3.default.readFile(taskListPath, "utf8");
|
|
3789
|
+
const lines = splitLines(content);
|
|
3790
|
+
if (task.lineIndex < 0 || task.lineIndex >= lines.length) {
|
|
3791
|
+
throw new Error(`task ${task.id} line index out of range`);
|
|
3792
|
+
}
|
|
3793
|
+
const updatedLine = replaceLineStatus(lines[task.lineIndex] ?? "", task.id, nextStatus);
|
|
3794
|
+
if (!updatedLine) {
|
|
3795
|
+
throw new Error(`failed to update task status for ${task.id}`);
|
|
3796
|
+
}
|
|
3797
|
+
lines[task.lineIndex] = updatedLine;
|
|
3798
|
+
await import_promises3.default.writeFile(taskListPath, lines.join("\n"), "utf8");
|
|
3799
|
+
return {
|
|
3800
|
+
...task,
|
|
3801
|
+
status: nextStatus
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
async function readOptionalFile(filePath) {
|
|
3805
|
+
try {
|
|
3806
|
+
return await import_promises3.default.readFile(filePath, "utf8");
|
|
3807
|
+
} catch (error) {
|
|
3808
|
+
if (error.code === "ENOENT") {
|
|
3809
|
+
return null;
|
|
3810
|
+
}
|
|
3811
|
+
throw error;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
function parseTasks(content) {
|
|
3815
|
+
const lines = splitLines(content);
|
|
3816
|
+
const tasks = [];
|
|
3817
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3818
|
+
const line = lines[index] ?? "";
|
|
3819
|
+
const tableTask = parseTableTaskLine(line, index);
|
|
3820
|
+
if (tableTask) {
|
|
3821
|
+
tasks.push(tableTask);
|
|
3822
|
+
continue;
|
|
3823
|
+
}
|
|
3824
|
+
const listTask = parseListTaskLine(line, index);
|
|
3825
|
+
if (listTask) {
|
|
3826
|
+
tasks.push(listTask);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
return tasks;
|
|
3830
|
+
}
|
|
3831
|
+
function parseTableTaskLine(line, lineIndex) {
|
|
3832
|
+
const trimmed = line.trim();
|
|
3833
|
+
if (!trimmed.startsWith("|")) {
|
|
3834
|
+
return null;
|
|
3835
|
+
}
|
|
3836
|
+
const cells = trimmed.split("|").slice(1, -1).map((cell) => cell.trim());
|
|
3837
|
+
if (cells.length < 3) {
|
|
3838
|
+
return null;
|
|
3839
|
+
}
|
|
3840
|
+
const taskId = cells[0] ?? "";
|
|
3841
|
+
if (!isLikelyTaskId(taskId)) {
|
|
3842
|
+
return null;
|
|
3843
|
+
}
|
|
3844
|
+
const statusCell = cells[cells.length - 1] ?? "";
|
|
3845
|
+
const status = parseStatusToken(statusCell);
|
|
3846
|
+
if (!status) {
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
return {
|
|
3850
|
+
id: taskId,
|
|
3851
|
+
description: cells[1] ?? taskId,
|
|
3852
|
+
status,
|
|
3853
|
+
lineIndex
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3856
|
+
function parseListTaskLine(line, lineIndex) {
|
|
3857
|
+
const checkboxMatch = line.match(/^\s*[-*]\s+\[( |x|X)\]\s+(.+)$/);
|
|
3858
|
+
if (checkboxMatch) {
|
|
3859
|
+
const rawText2 = checkboxMatch[2]?.trim() ?? "";
|
|
3860
|
+
const taskId2 = extractTaskId(rawText2);
|
|
3861
|
+
if (!taskId2) {
|
|
3862
|
+
return null;
|
|
3863
|
+
}
|
|
3864
|
+
return {
|
|
3865
|
+
id: taskId2,
|
|
3866
|
+
description: stripTaskIdPrefix(rawText2, taskId2),
|
|
3867
|
+
status: checkboxMatch[1]?.toLowerCase() === "x" ? "completed" : "pending",
|
|
3868
|
+
lineIndex
|
|
3869
|
+
};
|
|
3870
|
+
}
|
|
3871
|
+
const symbolMatch = line.match(/^\s*[-*]\s*(⬜|🔄|✅|❌|🚫)\s+(.+)$/);
|
|
3872
|
+
if (!symbolMatch) {
|
|
3873
|
+
return null;
|
|
3874
|
+
}
|
|
3875
|
+
const rawText = symbolMatch[2]?.trim() ?? "";
|
|
3876
|
+
const taskId = extractTaskId(rawText);
|
|
3877
|
+
if (!taskId) {
|
|
3878
|
+
return null;
|
|
3879
|
+
}
|
|
3880
|
+
const status = parseStatusToken(symbolMatch[1] ?? "");
|
|
3881
|
+
if (!status) {
|
|
3882
|
+
return null;
|
|
3883
|
+
}
|
|
3884
|
+
return {
|
|
3885
|
+
id: taskId,
|
|
3886
|
+
description: stripTaskIdPrefix(rawText, taskId),
|
|
3887
|
+
status,
|
|
3888
|
+
lineIndex
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
function stripTaskIdPrefix(text, taskId) {
|
|
3892
|
+
const normalized = text.trim();
|
|
3893
|
+
const escapedId = escapeRegex(taskId);
|
|
3894
|
+
return normalized.replace(new RegExp(`^${escapedId}[\\s:\uFF1A\\-]+`, "i"), "").trim() || normalized;
|
|
3895
|
+
}
|
|
3896
|
+
function extractTaskId(text) {
|
|
3897
|
+
const normalized = text.trim();
|
|
3898
|
+
const bracketMatch = normalized.match(/\(([A-Za-z][A-Za-z0-9._-]*)\)/);
|
|
3899
|
+
if (bracketMatch?.[1] && isLikelyTaskId(bracketMatch[1])) {
|
|
3900
|
+
return bracketMatch[1];
|
|
3901
|
+
}
|
|
3902
|
+
const token = normalized.split(/\s+/)[0]?.replace(/[,::|]+$/, "") ?? "";
|
|
3903
|
+
if (!isLikelyTaskId(token)) {
|
|
3904
|
+
return null;
|
|
3905
|
+
}
|
|
3906
|
+
return token;
|
|
3907
|
+
}
|
|
3908
|
+
function isLikelyTaskId(taskId) {
|
|
3909
|
+
if (!/^[A-Za-z][A-Za-z0-9._-]*$/.test(taskId)) {
|
|
3910
|
+
return false;
|
|
3911
|
+
}
|
|
3912
|
+
return /\d/.test(taskId);
|
|
3913
|
+
}
|
|
3914
|
+
function parseStatusToken(text) {
|
|
3915
|
+
const normalized = text.trim().toLowerCase();
|
|
3916
|
+
if (!normalized) {
|
|
3917
|
+
return null;
|
|
3918
|
+
}
|
|
3919
|
+
if (normalized.includes("\u2705") || normalized.includes("[x]") || normalized.includes("done")) {
|
|
3920
|
+
return "completed";
|
|
3921
|
+
}
|
|
3922
|
+
if (normalized.includes("\u2B1C") || normalized.includes("\u2610") || normalized.includes("[ ]") || normalized === "todo") {
|
|
3923
|
+
return "pending";
|
|
3924
|
+
}
|
|
3925
|
+
if (normalized.includes("\u{1F504}") || normalized.includes("\u8FDB\u884C\u4E2D") || normalized.includes("in progress")) {
|
|
3926
|
+
return "in_progress";
|
|
3927
|
+
}
|
|
3928
|
+
if (normalized.includes("\u274C") || normalized.includes("\u53D6\u6D88") || normalized.includes("cancel")) {
|
|
3929
|
+
return "cancelled";
|
|
3930
|
+
}
|
|
3931
|
+
if (normalized.includes("\u{1F6AB}") || normalized.includes("\u963B\u585E") || normalized.includes("block")) {
|
|
3932
|
+
return "blocked";
|
|
3933
|
+
}
|
|
3934
|
+
return null;
|
|
3935
|
+
}
|
|
3936
|
+
function replaceLineStatus(line, taskId, status) {
|
|
3937
|
+
const trimmed = line.trim();
|
|
3938
|
+
const symbol = statusToSymbol(status);
|
|
3939
|
+
if (trimmed.startsWith("|")) {
|
|
3940
|
+
const cells = trimmed.split("|").slice(1, -1).map((cell) => cell.trim());
|
|
3941
|
+
if (cells.length >= 3 && cells[0]?.toLowerCase() === taskId.toLowerCase()) {
|
|
3942
|
+
const rawParts = line.split("|");
|
|
3943
|
+
if (rawParts.length >= 3) {
|
|
3944
|
+
rawParts[rawParts.length - 2] = ` ${symbol} `;
|
|
3945
|
+
return rawParts.join("|");
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
if (/\[( |x|X)\]/.test(line)) {
|
|
3950
|
+
const checkbox = status === "completed" ? "[x]" : "[ ]";
|
|
3951
|
+
return line.replace(/\[( |x|X)\]/, checkbox);
|
|
3952
|
+
}
|
|
3953
|
+
if (/(⬜|🔄|✅|❌|🚫)/.test(line)) {
|
|
3954
|
+
return line.replace(/(⬜|🔄|✅|❌|🚫)/, symbol);
|
|
3955
|
+
}
|
|
3956
|
+
return null;
|
|
3957
|
+
}
|
|
3958
|
+
function splitLines(content) {
|
|
3959
|
+
return content.replace(/\r\n/g, "\n").split("\n");
|
|
3960
|
+
}
|
|
3961
|
+
function escapeRegex(value) {
|
|
3962
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3626
3965
|
// src/orchestrator.ts
|
|
3627
3966
|
var RequestMetrics = class {
|
|
3628
3967
|
total = 0;
|
|
@@ -3710,6 +4049,7 @@ var Orchestrator = class {
|
|
|
3710
4049
|
cliCompatRecorder;
|
|
3711
4050
|
workflowRunner;
|
|
3712
4051
|
workflowSnapshots = /* @__PURE__ */ new Map();
|
|
4052
|
+
autoDevSnapshots = /* @__PURE__ */ new Map();
|
|
3713
4053
|
metrics = new RequestMetrics();
|
|
3714
4054
|
lastLockPruneAt = 0;
|
|
3715
4055
|
constructor(channel, executor, stateStore, logger, options) {
|
|
@@ -3804,11 +4144,17 @@ var Orchestrator = class {
|
|
|
3804
4144
|
return;
|
|
3805
4145
|
}
|
|
3806
4146
|
const workflowCommand = this.workflowRunner.isEnabled() ? parseWorkflowCommand(route.prompt) : null;
|
|
4147
|
+
const autoDevCommand = this.workflowRunner.isEnabled() ? parseAutoDevCommand(route.prompt) : null;
|
|
3807
4148
|
if (workflowCommand?.kind === "status") {
|
|
3808
4149
|
await this.handleWorkflowStatusCommand(sessionKey, message);
|
|
3809
4150
|
this.stateStore.markEventProcessed(sessionKey, message.eventId);
|
|
3810
4151
|
return;
|
|
3811
4152
|
}
|
|
4153
|
+
if (autoDevCommand?.kind === "status") {
|
|
4154
|
+
await this.handleAutoDevStatusCommand(sessionKey, message, roomConfig.workdir);
|
|
4155
|
+
this.stateStore.markEventProcessed(sessionKey, message.eventId);
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
3812
4158
|
const rateDecision = this.rateLimiter.tryAcquire({
|
|
3813
4159
|
userId: message.senderId,
|
|
3814
4160
|
roomId: message.conversationId
|
|
@@ -3857,6 +4203,37 @@ var Orchestrator = class {
|
|
|
3857
4203
|
}
|
|
3858
4204
|
return;
|
|
3859
4205
|
}
|
|
4206
|
+
if (autoDevCommand?.kind === "run") {
|
|
4207
|
+
const executionStartedAt = Date.now();
|
|
4208
|
+
let sendDurationMs2 = 0;
|
|
4209
|
+
this.stateStore.activateSession(sessionKey, this.sessionActiveWindowMs);
|
|
4210
|
+
try {
|
|
4211
|
+
const sendStartedAt = Date.now();
|
|
4212
|
+
await this.handleAutoDevRunCommand(
|
|
4213
|
+
autoDevCommand.taskId,
|
|
4214
|
+
sessionKey,
|
|
4215
|
+
message,
|
|
4216
|
+
requestId,
|
|
4217
|
+
roomConfig.workdir
|
|
4218
|
+
);
|
|
4219
|
+
sendDurationMs2 += Date.now() - sendStartedAt;
|
|
4220
|
+
this.stateStore.markEventProcessed(sessionKey, message.eventId);
|
|
4221
|
+
this.metrics.record("success", queueWaitMs, Date.now() - executionStartedAt, sendDurationMs2);
|
|
4222
|
+
} catch (error) {
|
|
4223
|
+
sendDurationMs2 += await this.sendAutoDevFailure(message.conversationId, error);
|
|
4224
|
+
this.stateStore.commitExecutionHandled(sessionKey, message.eventId);
|
|
4225
|
+
const status = classifyExecutionOutcome(error);
|
|
4226
|
+
this.metrics.record(status, queueWaitMs, Date.now() - executionStartedAt, sendDurationMs2);
|
|
4227
|
+
this.logger.error("AutoDev request failed", {
|
|
4228
|
+
requestId,
|
|
4229
|
+
sessionKey,
|
|
4230
|
+
error: formatError2(error)
|
|
4231
|
+
});
|
|
4232
|
+
} finally {
|
|
4233
|
+
rateDecision.release?.();
|
|
4234
|
+
}
|
|
4235
|
+
return;
|
|
4236
|
+
}
|
|
3860
4237
|
this.stateStore.activateSession(sessionKey, this.sessionActiveWindowMs);
|
|
3861
4238
|
const previousCodexSessionId = this.stateStore.getCodexSessionId(sessionKey);
|
|
3862
4239
|
const executionPrompt = this.buildExecutionPrompt(route.prompt, message);
|
|
@@ -4078,6 +4455,7 @@ var Orchestrator = class {
|
|
|
4078
4455
|
const limiter = this.rateLimiter.snapshot();
|
|
4079
4456
|
const runtime = this.sessionRuntime.getRuntimeStats();
|
|
4080
4457
|
const workflow = this.workflowSnapshots.get(sessionKey) ?? createIdleWorkflowSnapshot();
|
|
4458
|
+
const autoDev = this.autoDevSnapshots.get(sessionKey) ?? createIdleAutoDevSnapshot();
|
|
4081
4459
|
await this.channel.sendNotice(
|
|
4082
4460
|
message.conversationId,
|
|
4083
4461
|
`[CodeHarbor] \u5F53\u524D\u72B6\u6001
|
|
@@ -4091,7 +4469,8 @@ var Orchestrator = class {
|
|
|
4091
4469
|
- \u5E73\u5747\u8017\u65F6: queue=${metrics.avgQueueMs}ms, exec=${metrics.avgExecMs}ms, send=${metrics.avgSendMs}ms
|
|
4092
4470
|
- \u9650\u6D41\u5E76\u53D1: global=${limiter.activeGlobal}, users=${limiter.activeUsers}, rooms=${limiter.activeRooms}
|
|
4093
4471
|
- CLI runtime: workers=${runtime.workerCount}, running=${runtime.runningCount}, compat_mode=${this.cliCompat.enabled ? "on" : "off"}
|
|
4094
|
-
- Multi-Agent workflow: enabled=${this.workflowRunner.isEnabled() ? "on" : "off"}, state=${workflow.state}
|
|
4472
|
+
- Multi-Agent workflow: enabled=${this.workflowRunner.isEnabled() ? "on" : "off"}, state=${workflow.state}
|
|
4473
|
+
- AutoDev: enabled=${this.workflowRunner.isEnabled() ? "on" : "off"}, state=${autoDev.state}, task=${autoDev.taskId ?? "N/A"}`
|
|
4095
4474
|
);
|
|
4096
4475
|
}
|
|
4097
4476
|
async handleWorkflowStatusCommand(sessionKey, message) {
|
|
@@ -4108,11 +4487,158 @@ var Orchestrator = class {
|
|
|
4108
4487
|
- error: ${snapshot.error ?? "N/A"}`
|
|
4109
4488
|
);
|
|
4110
4489
|
}
|
|
4490
|
+
async handleAutoDevStatusCommand(sessionKey, message, workdir) {
|
|
4491
|
+
const snapshot = this.autoDevSnapshots.get(sessionKey) ?? createIdleAutoDevSnapshot();
|
|
4492
|
+
try {
|
|
4493
|
+
const context = await loadAutoDevContext(workdir);
|
|
4494
|
+
const summary = summarizeAutoDevTasks(context.tasks);
|
|
4495
|
+
const nextTask = selectAutoDevTask(context.tasks);
|
|
4496
|
+
await this.channel.sendNotice(
|
|
4497
|
+
message.conversationId,
|
|
4498
|
+
`[CodeHarbor] AutoDev \u72B6\u6001
|
|
4499
|
+
- workdir: ${workdir}
|
|
4500
|
+
- REQUIREMENTS.md: ${context.requirementsContent ? "found" : "missing"}
|
|
4501
|
+
- TASK_LIST.md: ${context.taskListContent ? "found" : "missing"}
|
|
4502
|
+
- tasks: total=${summary.total}, pending=${summary.pending}, in_progress=${summary.inProgress}, completed=${summary.completed}, blocked=${summary.blocked}, cancelled=${summary.cancelled}
|
|
4503
|
+
- nextTask: ${nextTask ? formatTaskForDisplay(nextTask) : "N/A"}
|
|
4504
|
+
- runState: ${snapshot.state}
|
|
4505
|
+
- runTask: ${snapshot.taskId ? `${snapshot.taskId} ${snapshot.taskDescription ?? ""}`.trim() : "N/A"}
|
|
4506
|
+
- runApproved: ${snapshot.approved === null ? "N/A" : snapshot.approved ? "yes" : "no"}
|
|
4507
|
+
- runError: ${snapshot.error ?? "N/A"}`
|
|
4508
|
+
);
|
|
4509
|
+
} catch (error) {
|
|
4510
|
+
await this.channel.sendNotice(message.conversationId, `[CodeHarbor] AutoDev \u72B6\u6001\u8BFB\u53D6\u5931\u8D25: ${formatError2(error)}`);
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
async handleAutoDevRunCommand(taskId, sessionKey, message, requestId, workdir) {
|
|
4514
|
+
const requestedTaskId = taskId?.trim() || null;
|
|
4515
|
+
const context = await loadAutoDevContext(workdir);
|
|
4516
|
+
if (!context.requirementsContent) {
|
|
4517
|
+
await this.channel.sendNotice(
|
|
4518
|
+
message.conversationId,
|
|
4519
|
+
`[CodeHarbor] AutoDev \u9700\u8981 ${context.requirementsPath}\uFF0C\u8BF7\u5148\u51C6\u5907\u9700\u6C42\u6587\u6863\u3002`
|
|
4520
|
+
);
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
if (!context.taskListContent) {
|
|
4524
|
+
await this.channel.sendNotice(
|
|
4525
|
+
message.conversationId,
|
|
4526
|
+
`[CodeHarbor] AutoDev \u9700\u8981 ${context.taskListPath}\uFF0C\u8BF7\u5148\u51C6\u5907\u4EFB\u52A1\u6E05\u5355\u3002`
|
|
4527
|
+
);
|
|
4528
|
+
return;
|
|
4529
|
+
}
|
|
4530
|
+
if (context.tasks.length === 0) {
|
|
4531
|
+
await this.channel.sendNotice(
|
|
4532
|
+
message.conversationId,
|
|
4533
|
+
"[CodeHarbor] \u672A\u5728 TASK_LIST.md \u8BC6\u522B\u5230\u4EFB\u52A1\uFF08\u9700\u5305\u542B\u4EFB\u52A1 ID \u4E0E\u72B6\u6001\u5217\uFF09\u3002"
|
|
4534
|
+
);
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
const selectedTask = selectAutoDevTask(context.tasks, requestedTaskId);
|
|
4538
|
+
if (!selectedTask) {
|
|
4539
|
+
if (requestedTaskId) {
|
|
4540
|
+
await this.channel.sendNotice(message.conversationId, `[CodeHarbor] \u672A\u627E\u5230\u4EFB\u52A1 ${requestedTaskId}\u3002`);
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
await this.channel.sendNotice(message.conversationId, "[CodeHarbor] \u5F53\u524D\u6CA1\u6709\u53EF\u6267\u884C\u4EFB\u52A1\uFF08pending/in_progress\uFF09\u3002");
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4546
|
+
if (selectedTask.status === "completed") {
|
|
4547
|
+
await this.channel.sendNotice(message.conversationId, `[CodeHarbor] \u4EFB\u52A1 ${selectedTask.id} \u5DF2\u5B8C\u6210\uFF08\u2705\uFF09\u3002`);
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
if (selectedTask.status === "cancelled") {
|
|
4551
|
+
await this.channel.sendNotice(message.conversationId, `[CodeHarbor] \u4EFB\u52A1 ${selectedTask.id} \u5DF2\u53D6\u6D88\uFF08\u274C\uFF09\u3002`);
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
let activeTask = selectedTask;
|
|
4555
|
+
let promotedToInProgress = false;
|
|
4556
|
+
if (selectedTask.status === "pending") {
|
|
4557
|
+
activeTask = await updateAutoDevTaskStatus(context.taskListPath, selectedTask, "in_progress");
|
|
4558
|
+
promotedToInProgress = true;
|
|
4559
|
+
}
|
|
4560
|
+
const startedAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4561
|
+
this.autoDevSnapshots.set(sessionKey, {
|
|
4562
|
+
state: "running",
|
|
4563
|
+
startedAt: startedAtIso,
|
|
4564
|
+
endedAt: null,
|
|
4565
|
+
taskId: activeTask.id,
|
|
4566
|
+
taskDescription: activeTask.description,
|
|
4567
|
+
approved: null,
|
|
4568
|
+
repairRounds: 0,
|
|
4569
|
+
error: null
|
|
4570
|
+
});
|
|
4571
|
+
await this.channel.sendNotice(
|
|
4572
|
+
message.conversationId,
|
|
4573
|
+
`[CodeHarbor] AutoDev \u542F\u52A8\u4EFB\u52A1 ${activeTask.id}: ${activeTask.description}`
|
|
4574
|
+
);
|
|
4575
|
+
try {
|
|
4576
|
+
const result = await this.handleWorkflowRunCommand(
|
|
4577
|
+
buildAutoDevObjective(activeTask),
|
|
4578
|
+
sessionKey,
|
|
4579
|
+
message,
|
|
4580
|
+
requestId,
|
|
4581
|
+
workdir
|
|
4582
|
+
);
|
|
4583
|
+
if (!result) {
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
let finalTask = activeTask;
|
|
4587
|
+
if (result.approved) {
|
|
4588
|
+
finalTask = await updateAutoDevTaskStatus(context.taskListPath, activeTask, "completed");
|
|
4589
|
+
}
|
|
4590
|
+
const endedAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4591
|
+
this.autoDevSnapshots.set(sessionKey, {
|
|
4592
|
+
state: "succeeded",
|
|
4593
|
+
startedAt: startedAtIso,
|
|
4594
|
+
endedAt: endedAtIso,
|
|
4595
|
+
taskId: finalTask.id,
|
|
4596
|
+
taskDescription: finalTask.description,
|
|
4597
|
+
approved: result.approved,
|
|
4598
|
+
repairRounds: result.repairRounds,
|
|
4599
|
+
error: null
|
|
4600
|
+
});
|
|
4601
|
+
const refreshed = await loadAutoDevContext(workdir);
|
|
4602
|
+
const nextTask = selectAutoDevTask(refreshed.tasks);
|
|
4603
|
+
await this.channel.sendNotice(
|
|
4604
|
+
message.conversationId,
|
|
4605
|
+
`[CodeHarbor] AutoDev \u4EFB\u52A1\u7ED3\u679C
|
|
4606
|
+
- task: ${finalTask.id}
|
|
4607
|
+
- reviewer approved: ${result.approved ? "yes" : "no"}
|
|
4608
|
+
- task status: ${statusToSymbol(finalTask.status)}
|
|
4609
|
+
- nextTask: ${nextTask ? formatTaskForDisplay(nextTask) : "N/A"}`
|
|
4610
|
+
);
|
|
4611
|
+
} catch (error) {
|
|
4612
|
+
if (promotedToInProgress) {
|
|
4613
|
+
try {
|
|
4614
|
+
await updateAutoDevTaskStatus(context.taskListPath, activeTask, "pending");
|
|
4615
|
+
} catch (restoreError) {
|
|
4616
|
+
this.logger.warn("Failed to restore AutoDev task status after failure", {
|
|
4617
|
+
taskId: activeTask.id,
|
|
4618
|
+
error: formatError2(restoreError)
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
const status = classifyExecutionOutcome(error);
|
|
4623
|
+
const endedAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4624
|
+
this.autoDevSnapshots.set(sessionKey, {
|
|
4625
|
+
state: status === "cancelled" ? "idle" : "failed",
|
|
4626
|
+
startedAt: startedAtIso,
|
|
4627
|
+
endedAt: endedAtIso,
|
|
4628
|
+
taskId: activeTask.id,
|
|
4629
|
+
taskDescription: activeTask.description,
|
|
4630
|
+
approved: null,
|
|
4631
|
+
repairRounds: 0,
|
|
4632
|
+
error: formatError2(error)
|
|
4633
|
+
});
|
|
4634
|
+
throw error;
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4111
4637
|
async handleWorkflowRunCommand(objective, sessionKey, message, requestId, workdir) {
|
|
4112
4638
|
const normalizedObjective = objective.trim();
|
|
4113
4639
|
if (!normalizedObjective) {
|
|
4114
4640
|
await this.channel.sendNotice(message.conversationId, "[CodeHarbor] /agents run \u9700\u8981\u63D0\u4F9B\u4EFB\u52A1\u76EE\u6807\u3002");
|
|
4115
|
-
return;
|
|
4641
|
+
return null;
|
|
4116
4642
|
}
|
|
4117
4643
|
const requestStartedAt = Date.now();
|
|
4118
4644
|
let progressNoticeEventId = null;
|
|
@@ -4174,6 +4700,7 @@ var Orchestrator = class {
|
|
|
4174
4700
|
});
|
|
4175
4701
|
await this.channel.sendMessage(message.conversationId, buildWorkflowResultReply(result));
|
|
4176
4702
|
await this.finishProgress(progressCtx, `\u591A\u667A\u80FD\u4F53\u6D41\u7A0B\u5B8C\u6210\uFF08\u8017\u65F6 ${formatDurationMs(Date.now() - requestStartedAt)}\uFF09`);
|
|
4703
|
+
return result;
|
|
4177
4704
|
} catch (error) {
|
|
4178
4705
|
const status = classifyExecutionOutcome(error);
|
|
4179
4706
|
const endedAtIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4206,6 +4733,16 @@ var Orchestrator = class {
|
|
|
4206
4733
|
await this.channel.sendMessage(conversationId, `[CodeHarbor] Multi-Agent workflow \u5931\u8D25: ${formatError2(error)}`);
|
|
4207
4734
|
return Date.now() - startedAt;
|
|
4208
4735
|
}
|
|
4736
|
+
async sendAutoDevFailure(conversationId, error) {
|
|
4737
|
+
const startedAt = Date.now();
|
|
4738
|
+
const status = classifyExecutionOutcome(error);
|
|
4739
|
+
if (status === "cancelled") {
|
|
4740
|
+
await this.channel.sendNotice(conversationId, "[CodeHarbor] AutoDev \u5DF2\u53D6\u6D88\u3002");
|
|
4741
|
+
return Date.now() - startedAt;
|
|
4742
|
+
}
|
|
4743
|
+
await this.channel.sendMessage(conversationId, `[CodeHarbor] AutoDev \u5931\u8D25: ${formatError2(error)}`);
|
|
4744
|
+
return Date.now() - startedAt;
|
|
4745
|
+
}
|
|
4209
4746
|
async handleStopCommand(sessionKey, message, requestId) {
|
|
4210
4747
|
this.stateStore.deactivateSession(sessionKey);
|
|
4211
4748
|
this.stateStore.clearCodexSessionId(sessionKey);
|
|
@@ -4401,6 +4938,18 @@ ${attachmentSummary}
|
|
|
4401
4938
|
}
|
|
4402
4939
|
}
|
|
4403
4940
|
};
|
|
4941
|
+
function createIdleAutoDevSnapshot() {
|
|
4942
|
+
return {
|
|
4943
|
+
state: "idle",
|
|
4944
|
+
startedAt: null,
|
|
4945
|
+
endedAt: null,
|
|
4946
|
+
taskId: null,
|
|
4947
|
+
taskDescription: null,
|
|
4948
|
+
approved: null,
|
|
4949
|
+
repairRounds: 0,
|
|
4950
|
+
error: null
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4404
4953
|
function buildSessionKey(message) {
|
|
4405
4954
|
return `${message.channel}:${message.conversationId}:${message.senderId}`;
|
|
4406
4955
|
}
|
|
@@ -4424,7 +4973,7 @@ async function cleanupAttachmentFiles(imagePaths) {
|
|
|
4424
4973
|
await Promise.all(
|
|
4425
4974
|
imagePaths.map(async (imagePath) => {
|
|
4426
4975
|
try {
|
|
4427
|
-
await
|
|
4976
|
+
await import_promises4.default.unlink(imagePath);
|
|
4428
4977
|
} catch {
|
|
4429
4978
|
}
|
|
4430
4979
|
})
|
|
@@ -4474,11 +5023,11 @@ function stripLeadingBotMention(text, matrixUserId) {
|
|
|
4474
5023
|
if (!matrixUserId) {
|
|
4475
5024
|
return text;
|
|
4476
5025
|
}
|
|
4477
|
-
const escapedUserId =
|
|
5026
|
+
const escapedUserId = escapeRegex2(matrixUserId);
|
|
4478
5027
|
const mentionPattern = new RegExp(`^\\s*(?:<)?${escapedUserId}(?:>)?[\\s,:\uFF0C\uFF1A-]*`, "i");
|
|
4479
5028
|
return text.replace(mentionPattern, "").trim();
|
|
4480
5029
|
}
|
|
4481
|
-
function
|
|
5030
|
+
function escapeRegex2(value) {
|
|
4482
5031
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4483
5032
|
}
|
|
4484
5033
|
function formatDurationMs(durationMs) {
|
|
@@ -4541,7 +5090,7 @@ ${result.review}
|
|
|
4541
5090
|
|
|
4542
5091
|
// src/store/state-store.ts
|
|
4543
5092
|
var import_node_fs7 = __toESM(require("fs"));
|
|
4544
|
-
var
|
|
5093
|
+
var import_node_path10 = __toESM(require("path"));
|
|
4545
5094
|
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
4546
5095
|
var PRUNE_INTERVAL_MS = 5 * 60 * 1e3;
|
|
4547
5096
|
var SQLITE_MODULE_ID = `node:${"sqlite"}`;
|
|
@@ -4567,7 +5116,7 @@ var StateStore = class {
|
|
|
4567
5116
|
this.maxProcessedEventsPerSession = maxProcessedEventsPerSession;
|
|
4568
5117
|
this.maxSessionAgeMs = maxSessionAgeDays * ONE_DAY_MS;
|
|
4569
5118
|
this.maxSessions = maxSessions;
|
|
4570
|
-
import_node_fs7.default.mkdirSync(
|
|
5119
|
+
import_node_fs7.default.mkdirSync(import_node_path10.default.dirname(this.dbPath), { recursive: true });
|
|
4571
5120
|
this.db = new DatabaseSync(this.dbPath);
|
|
4572
5121
|
this.initializeSchema();
|
|
4573
5122
|
this.importLegacyStateIfNeeded();
|
|
@@ -4938,7 +5487,7 @@ function boolToInt(value) {
|
|
|
4938
5487
|
}
|
|
4939
5488
|
|
|
4940
5489
|
// src/app.ts
|
|
4941
|
-
var
|
|
5490
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
|
|
4942
5491
|
var CodeHarborApp = class {
|
|
4943
5492
|
config;
|
|
4944
5493
|
logger;
|
|
@@ -5050,7 +5599,7 @@ async function runDoctor(config) {
|
|
|
5050
5599
|
const logger = new Logger(config.logLevel);
|
|
5051
5600
|
logger.info("Doctor check started");
|
|
5052
5601
|
try {
|
|
5053
|
-
const { stdout } = await
|
|
5602
|
+
const { stdout } = await execFileAsync3(config.codexBin, ["--version"]);
|
|
5054
5603
|
logger.info("codex available", { version: stdout.trim() });
|
|
5055
5604
|
} catch (error) {
|
|
5056
5605
|
logger.error("codex unavailable", error);
|
|
@@ -5088,7 +5637,7 @@ function isNonLoopbackHost(host) {
|
|
|
5088
5637
|
|
|
5089
5638
|
// src/config.ts
|
|
5090
5639
|
var import_node_fs8 = __toESM(require("fs"));
|
|
5091
|
-
var
|
|
5640
|
+
var import_node_path11 = __toESM(require("path"));
|
|
5092
5641
|
var import_dotenv2 = __toESM(require("dotenv"));
|
|
5093
5642
|
var import_zod = require("zod");
|
|
5094
5643
|
var configSchema = import_zod.z.object({
|
|
@@ -5149,7 +5698,7 @@ var configSchema = import_zod.z.object({
|
|
|
5149
5698
|
matrixCommandPrefix: v.MATRIX_COMMAND_PREFIX,
|
|
5150
5699
|
codexBin: v.CODEX_BIN,
|
|
5151
5700
|
codexModel: v.CODEX_MODEL?.trim() || null,
|
|
5152
|
-
codexWorkdir:
|
|
5701
|
+
codexWorkdir: import_node_path11.default.resolve(v.CODEX_WORKDIR),
|
|
5153
5702
|
codexDangerousBypass: v.CODEX_DANGEROUS_BYPASS,
|
|
5154
5703
|
codexExecTimeoutMs: v.CODEX_EXEC_TIMEOUT_MS,
|
|
5155
5704
|
codexSandboxMode: v.CODEX_SANDBOX_MODE?.trim() || null,
|
|
@@ -5160,8 +5709,8 @@ var configSchema = import_zod.z.object({
|
|
|
5160
5709
|
enabled: v.AGENT_WORKFLOW_ENABLED,
|
|
5161
5710
|
autoRepairMaxRounds: v.AGENT_WORKFLOW_AUTO_REPAIR_MAX_ROUNDS
|
|
5162
5711
|
},
|
|
5163
|
-
stateDbPath:
|
|
5164
|
-
legacyStateJsonPath: v.STATE_PATH.trim() ?
|
|
5712
|
+
stateDbPath: import_node_path11.default.resolve(v.STATE_DB_PATH),
|
|
5713
|
+
legacyStateJsonPath: v.STATE_PATH.trim() ? import_node_path11.default.resolve(v.STATE_PATH) : null,
|
|
5165
5714
|
maxProcessedEventsPerSession: v.MAX_PROCESSED_EVENTS_PER_SESSION,
|
|
5166
5715
|
maxSessionAgeDays: v.MAX_SESSION_AGE_DAYS,
|
|
5167
5716
|
maxSessions: v.MAX_SESSIONS,
|
|
@@ -5192,7 +5741,7 @@ var configSchema = import_zod.z.object({
|
|
|
5192
5741
|
disableReplyChunkSplit: v.CLI_COMPAT_DISABLE_REPLY_CHUNK_SPLIT,
|
|
5193
5742
|
progressThrottleMs: v.CLI_COMPAT_PROGRESS_THROTTLE_MS,
|
|
5194
5743
|
fetchMedia: v.CLI_COMPAT_FETCH_MEDIA,
|
|
5195
|
-
recordPath: v.CLI_COMPAT_RECORD_PATH.trim() ?
|
|
5744
|
+
recordPath: v.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path11.default.resolve(v.CLI_COMPAT_RECORD_PATH) : null
|
|
5196
5745
|
},
|
|
5197
5746
|
doctorHttpTimeoutMs: v.DOCTOR_HTTP_TIMEOUT_MS,
|
|
5198
5747
|
adminBindHost: v.ADMIN_BIND_HOST.trim() || "127.0.0.1",
|
|
@@ -5202,7 +5751,7 @@ var configSchema = import_zod.z.object({
|
|
|
5202
5751
|
adminAllowedOrigins: parseCsvList(v.ADMIN_ALLOWED_ORIGINS),
|
|
5203
5752
|
logLevel: v.LOG_LEVEL
|
|
5204
5753
|
}));
|
|
5205
|
-
function loadEnvFromFile(filePath =
|
|
5754
|
+
function loadEnvFromFile(filePath = import_node_path11.default.resolve(process.cwd(), ".env"), env = process.env) {
|
|
5206
5755
|
import_dotenv2.default.config({
|
|
5207
5756
|
path: filePath,
|
|
5208
5757
|
processEnv: env,
|
|
@@ -5215,9 +5764,9 @@ function loadConfig(env = process.env) {
|
|
|
5215
5764
|
const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
|
|
5216
5765
|
throw new Error(`Invalid configuration: ${message}`);
|
|
5217
5766
|
}
|
|
5218
|
-
import_node_fs8.default.mkdirSync(
|
|
5767
|
+
import_node_fs8.default.mkdirSync(import_node_path11.default.dirname(parsed.data.stateDbPath), { recursive: true });
|
|
5219
5768
|
if (parsed.data.legacyStateJsonPath) {
|
|
5220
|
-
import_node_fs8.default.mkdirSync(
|
|
5769
|
+
import_node_fs8.default.mkdirSync(import_node_path11.default.dirname(parsed.data.legacyStateJsonPath), { recursive: true });
|
|
5221
5770
|
}
|
|
5222
5771
|
return parsed.data;
|
|
5223
5772
|
}
|
|
@@ -5291,7 +5840,7 @@ function parseCsvList(raw) {
|
|
|
5291
5840
|
|
|
5292
5841
|
// src/config-snapshot.ts
|
|
5293
5842
|
var import_node_fs9 = __toESM(require("fs"));
|
|
5294
|
-
var
|
|
5843
|
+
var import_node_path12 = __toESM(require("path"));
|
|
5295
5844
|
var import_zod2 = require("zod");
|
|
5296
5845
|
var CONFIG_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
5297
5846
|
var CONFIG_SNAPSHOT_ENV_KEYS = [
|
|
@@ -5478,8 +6027,8 @@ async function runConfigExportCommand(options = {}) {
|
|
|
5478
6027
|
const snapshot = buildConfigSnapshot(config, stateStore.listRoomSettings(), options.now ?? /* @__PURE__ */ new Date());
|
|
5479
6028
|
const serialized = serializeConfigSnapshot(snapshot);
|
|
5480
6029
|
if (options.outputPath) {
|
|
5481
|
-
const targetPath =
|
|
5482
|
-
import_node_fs9.default.mkdirSync(
|
|
6030
|
+
const targetPath = import_node_path12.default.resolve(cwd, options.outputPath);
|
|
6031
|
+
import_node_fs9.default.mkdirSync(import_node_path12.default.dirname(targetPath), { recursive: true });
|
|
5483
6032
|
import_node_fs9.default.writeFileSync(targetPath, serialized, "utf8");
|
|
5484
6033
|
output.write(`Exported config snapshot to ${targetPath}
|
|
5485
6034
|
`);
|
|
@@ -5494,7 +6043,7 @@ async function runConfigImportCommand(options) {
|
|
|
5494
6043
|
const cwd = options.cwd ?? process.cwd();
|
|
5495
6044
|
const output = options.output ?? process.stdout;
|
|
5496
6045
|
const actor = options.actor?.trim() || "cli:config-import";
|
|
5497
|
-
const sourcePath =
|
|
6046
|
+
const sourcePath = import_node_path12.default.resolve(cwd, options.filePath);
|
|
5498
6047
|
if (!import_node_fs9.default.existsSync(sourcePath)) {
|
|
5499
6048
|
throw new Error(`Config snapshot file not found: ${sourcePath}`);
|
|
5500
6049
|
}
|
|
@@ -5525,7 +6074,7 @@ async function runConfigImportCommand(options) {
|
|
|
5525
6074
|
synchronizeRoomSettings(stateStore, normalizedRooms);
|
|
5526
6075
|
stateStore.appendConfigRevision(
|
|
5527
6076
|
actor,
|
|
5528
|
-
`import config snapshot from ${
|
|
6077
|
+
`import config snapshot from ${import_node_path12.default.basename(sourcePath)}`,
|
|
5529
6078
|
JSON.stringify({
|
|
5530
6079
|
type: "config_snapshot_import",
|
|
5531
6080
|
sourcePath,
|
|
@@ -5539,7 +6088,7 @@ async function runConfigImportCommand(options) {
|
|
|
5539
6088
|
output.write(
|
|
5540
6089
|
[
|
|
5541
6090
|
`Imported config snapshot from ${sourcePath}`,
|
|
5542
|
-
`- updated .env in ${
|
|
6091
|
+
`- updated .env in ${import_node_path12.default.resolve(cwd, ".env")}`,
|
|
5543
6092
|
`- synchronized room settings: ${normalizedRooms.length}`,
|
|
5544
6093
|
"- restart required: yes (global env settings are restart-scoped)"
|
|
5545
6094
|
].join("\n") + "\n"
|
|
@@ -5613,10 +6162,10 @@ function parseJsonFile(filePath) {
|
|
|
5613
6162
|
function normalizeSnapshotEnv(env, cwd) {
|
|
5614
6163
|
return {
|
|
5615
6164
|
...env,
|
|
5616
|
-
CODEX_WORKDIR:
|
|
5617
|
-
STATE_DB_PATH:
|
|
5618
|
-
STATE_PATH: env.STATE_PATH.trim() ?
|
|
5619
|
-
CLI_COMPAT_RECORD_PATH: env.CLI_COMPAT_RECORD_PATH.trim() ?
|
|
6165
|
+
CODEX_WORKDIR: import_node_path12.default.resolve(cwd, env.CODEX_WORKDIR),
|
|
6166
|
+
STATE_DB_PATH: import_node_path12.default.resolve(cwd, env.STATE_DB_PATH),
|
|
6167
|
+
STATE_PATH: env.STATE_PATH.trim() ? import_node_path12.default.resolve(cwd, env.STATE_PATH) : "",
|
|
6168
|
+
CLI_COMPAT_RECORD_PATH: env.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path12.default.resolve(cwd, env.CLI_COMPAT_RECORD_PATH) : ""
|
|
5620
6169
|
};
|
|
5621
6170
|
}
|
|
5622
6171
|
function normalizeSnapshotRooms(rooms, cwd) {
|
|
@@ -5631,7 +6180,7 @@ function normalizeSnapshotRooms(rooms, cwd) {
|
|
|
5631
6180
|
throw new Error(`Duplicate roomId in snapshot: ${roomId}`);
|
|
5632
6181
|
}
|
|
5633
6182
|
seen.add(roomId);
|
|
5634
|
-
const workdir =
|
|
6183
|
+
const workdir = import_node_path12.default.resolve(cwd, room.workdir);
|
|
5635
6184
|
ensureDirectory2(workdir, `room workdir (${roomId})`);
|
|
5636
6185
|
normalized.push({
|
|
5637
6186
|
roomId,
|
|
@@ -5658,8 +6207,8 @@ function synchronizeRoomSettings(stateStore, rooms) {
|
|
|
5658
6207
|
}
|
|
5659
6208
|
}
|
|
5660
6209
|
function persistEnvSnapshot(cwd, env) {
|
|
5661
|
-
const envPath =
|
|
5662
|
-
const examplePath =
|
|
6210
|
+
const envPath = import_node_path12.default.resolve(cwd, ".env");
|
|
6211
|
+
const examplePath = import_node_path12.default.resolve(cwd, ".env.example");
|
|
5663
6212
|
const template = import_node_fs9.default.existsSync(envPath) ? import_node_fs9.default.readFileSync(envPath, "utf8") : import_node_fs9.default.existsSync(examplePath) ? import_node_fs9.default.readFileSync(examplePath, "utf8") : "";
|
|
5664
6213
|
const overrides = {};
|
|
5665
6214
|
for (const key of CONFIG_SNAPSHOT_ENV_KEYS) {
|
|
@@ -5722,11 +6271,11 @@ function jsonObjectStringSchema(key, allowEmpty) {
|
|
|
5722
6271
|
}
|
|
5723
6272
|
|
|
5724
6273
|
// src/preflight.ts
|
|
5725
|
-
var
|
|
6274
|
+
var import_node_child_process6 = require("child_process");
|
|
5726
6275
|
var import_node_fs10 = __toESM(require("fs"));
|
|
5727
|
-
var
|
|
5728
|
-
var
|
|
5729
|
-
var
|
|
6276
|
+
var import_node_path13 = __toESM(require("path"));
|
|
6277
|
+
var import_node_util4 = require("util");
|
|
6278
|
+
var execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process6.execFile);
|
|
5730
6279
|
var REQUIRED_ENV_KEYS = ["MATRIX_HOMESERVER", "MATRIX_USER_ID", "MATRIX_ACCESS_TOKEN"];
|
|
5731
6280
|
async function runStartupPreflight(options = {}) {
|
|
5732
6281
|
const env = options.env ?? process.env;
|
|
@@ -5735,7 +6284,9 @@ async function runStartupPreflight(options = {}) {
|
|
|
5735
6284
|
const fileExists = options.fileExists ?? import_node_fs10.default.existsSync;
|
|
5736
6285
|
const isDirectory = options.isDirectory ?? defaultIsDirectory;
|
|
5737
6286
|
const issues = [];
|
|
5738
|
-
const envPath =
|
|
6287
|
+
const envPath = import_node_path13.default.resolve(cwd, ".env");
|
|
6288
|
+
let resolvedCodexBin = null;
|
|
6289
|
+
let usedCodexFallback = false;
|
|
5739
6290
|
if (!fileExists(envPath)) {
|
|
5740
6291
|
issues.push({
|
|
5741
6292
|
level: "warn",
|
|
@@ -5783,18 +6334,34 @@ async function runStartupPreflight(options = {}) {
|
|
|
5783
6334
|
const codexBin = readEnv(env, "CODEX_BIN") || "codex";
|
|
5784
6335
|
try {
|
|
5785
6336
|
await checkCodexBinary(codexBin);
|
|
6337
|
+
resolvedCodexBin = codexBin;
|
|
5786
6338
|
} catch (error) {
|
|
5787
|
-
const
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
6339
|
+
const fallbackBin = await findWorkingCodexBin(codexBin, { env, checkBinary: checkCodexBinary });
|
|
6340
|
+
if (fallbackBin && fallbackBin !== codexBin) {
|
|
6341
|
+
resolvedCodexBin = fallbackBin;
|
|
6342
|
+
usedCodexFallback = true;
|
|
6343
|
+
issues.push({
|
|
6344
|
+
level: "warn",
|
|
6345
|
+
code: "codex_bin_fallback",
|
|
6346
|
+
check: "CODEX_BIN",
|
|
6347
|
+
message: `Configured CODEX_BIN "${codexBin}" is unavailable; fallback to "${fallbackBin}".`,
|
|
6348
|
+
fix: `Update CODEX_BIN=${fallbackBin} in .env to avoid fallback probing on startup.`
|
|
6349
|
+
});
|
|
6350
|
+
} else if (fallbackBin) {
|
|
6351
|
+
resolvedCodexBin = fallbackBin;
|
|
6352
|
+
} else {
|
|
6353
|
+
const reason = error instanceof Error && error.message ? ` (${error.message})` : "";
|
|
6354
|
+
issues.push({
|
|
6355
|
+
level: "error",
|
|
6356
|
+
code: "missing_codex_bin",
|
|
6357
|
+
check: "CODEX_BIN",
|
|
6358
|
+
message: `Unable to execute "${codexBin}"${reason}.`,
|
|
6359
|
+
fix: `Install Codex CLI and ensure "${codexBin}" is in PATH, or set CODEX_BIN=/absolute/path/to/codex.`
|
|
6360
|
+
});
|
|
6361
|
+
}
|
|
5795
6362
|
}
|
|
5796
6363
|
const configuredWorkdir = readEnv(env, "CODEX_WORKDIR");
|
|
5797
|
-
const workdir =
|
|
6364
|
+
const workdir = import_node_path13.default.resolve(cwd, configuredWorkdir || cwd);
|
|
5798
6365
|
if (!fileExists(workdir) || !isDirectory(workdir)) {
|
|
5799
6366
|
issues.push({
|
|
5800
6367
|
level: "error",
|
|
@@ -5806,7 +6373,9 @@ async function runStartupPreflight(options = {}) {
|
|
|
5806
6373
|
}
|
|
5807
6374
|
return {
|
|
5808
6375
|
ok: issues.every((issue) => issue.level !== "error"),
|
|
5809
|
-
issues
|
|
6376
|
+
issues,
|
|
6377
|
+
resolvedCodexBin,
|
|
6378
|
+
usedCodexFallback
|
|
5810
6379
|
};
|
|
5811
6380
|
}
|
|
5812
6381
|
function formatPreflightReport(result, commandName) {
|
|
@@ -5829,7 +6398,7 @@ function formatPreflightReport(result, commandName) {
|
|
|
5829
6398
|
`;
|
|
5830
6399
|
}
|
|
5831
6400
|
async function defaultCheckCodexBinary(bin) {
|
|
5832
|
-
await
|
|
6401
|
+
await execFileAsync4(bin, ["--version"]);
|
|
5833
6402
|
}
|
|
5834
6403
|
function defaultIsDirectory(targetPath) {
|
|
5835
6404
|
try {
|
|
@@ -6015,7 +6584,11 @@ if (process.argv.length <= 2) {
|
|
|
6015
6584
|
}
|
|
6016
6585
|
void program.parseAsync(process.argv);
|
|
6017
6586
|
async function loadConfigWithPreflight(commandName, runtimeHomePath) {
|
|
6018
|
-
const
|
|
6587
|
+
const env = { ...process.env };
|
|
6588
|
+
const preflight = await runStartupPreflight({ cwd: runtimeHomePath, env });
|
|
6589
|
+
if (preflight.resolvedCodexBin) {
|
|
6590
|
+
env.CODEX_BIN = preflight.resolvedCodexBin;
|
|
6591
|
+
}
|
|
6019
6592
|
if (preflight.issues.length > 0) {
|
|
6020
6593
|
const report = formatPreflightReport(preflight, commandName);
|
|
6021
6594
|
if (preflight.ok) {
|
|
@@ -6026,7 +6599,7 @@ async function loadConfigWithPreflight(commandName, runtimeHomePath) {
|
|
|
6026
6599
|
}
|
|
6027
6600
|
}
|
|
6028
6601
|
try {
|
|
6029
|
-
return loadConfig();
|
|
6602
|
+
return loadConfig(env);
|
|
6030
6603
|
} catch (error) {
|
|
6031
6604
|
const message = error instanceof Error ? error.message : String(error);
|
|
6032
6605
|
process.stderr.write(`Configuration error: ${message}
|
|
@@ -6056,7 +6629,7 @@ function ensureRuntimeHomeOrExit() {
|
|
|
6056
6629
|
`);
|
|
6057
6630
|
process.exit(1);
|
|
6058
6631
|
}
|
|
6059
|
-
loadEnvFromFile(
|
|
6632
|
+
loadEnvFromFile(import_node_path14.default.resolve(home, ".env"));
|
|
6060
6633
|
runtimeHome = home;
|
|
6061
6634
|
return runtimeHome;
|
|
6062
6635
|
}
|
|
@@ -6071,7 +6644,7 @@ function parsePortOption(raw, fallback) {
|
|
|
6071
6644
|
}
|
|
6072
6645
|
function resolveCliVersion() {
|
|
6073
6646
|
try {
|
|
6074
|
-
const packagePath =
|
|
6647
|
+
const packagePath = import_node_path14.default.resolve(__dirname, "..", "package.json");
|
|
6075
6648
|
const content = import_node_fs11.default.readFileSync(packagePath, "utf8");
|
|
6076
6649
|
const parsed = JSON.parse(content);
|
|
6077
6650
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
|
|
@@ -6082,9 +6655,9 @@ function resolveCliVersion() {
|
|
|
6082
6655
|
function resolveCliScriptPath() {
|
|
6083
6656
|
const argvPath = process.argv[1];
|
|
6084
6657
|
if (argvPath && argvPath.trim()) {
|
|
6085
|
-
return
|
|
6658
|
+
return import_node_path14.default.resolve(argvPath);
|
|
6086
6659
|
}
|
|
6087
|
-
return
|
|
6660
|
+
return import_node_path14.default.resolve(__dirname, "cli.js");
|
|
6088
6661
|
}
|
|
6089
6662
|
function maybeReexecServiceCommandWithSudo() {
|
|
6090
6663
|
if (typeof process.getuid !== "function" || process.getuid() === 0) {
|
|
@@ -6095,7 +6668,7 @@ function maybeReexecServiceCommandWithSudo() {
|
|
|
6095
6668
|
return;
|
|
6096
6669
|
}
|
|
6097
6670
|
const cliScriptPath = resolveCliScriptPath();
|
|
6098
|
-
const child = (0,
|
|
6671
|
+
const child = (0, import_node_child_process7.spawnSync)("sudo", [process.execPath, cliScriptPath, ...serviceArgs], {
|
|
6099
6672
|
stdio: "inherit"
|
|
6100
6673
|
});
|
|
6101
6674
|
if (child.error) {
|