@yancyyu/openhermit 1.6.16 → 1.6.18
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/bin/hermit.mjs +100 -75
- package/package.json +1 -1
package/bin/hermit.mjs
CHANGED
|
@@ -90,13 +90,13 @@ const daemonRequested = args.includes('--daemon');
|
|
|
90
90
|
const daemonChild = process.env.HERMIT_DAEMON_CHILD === '1';
|
|
91
91
|
const daemonPidPath = path.join(hermitHome, 'openhermit.pid');
|
|
92
92
|
const daemonLogPath = path.join(hermitHome, 'logs', 'openhermit.log');
|
|
93
|
+
const runtimeLogPath = path.join(hermitHome, 'logs', 'openhermit-runtime.log');
|
|
93
94
|
const serverLogPath = path.join(hermitHome, 'logs', 'openhermit-server.log');
|
|
94
95
|
const ccConnectConfigPath =
|
|
95
96
|
process.env.HERMIT_CC_CONNECT_CONFIG ||
|
|
96
97
|
process.env.CC_CONNECT_CONFIG ||
|
|
97
98
|
path.join(hermitHome, 'cc-connect', 'config.toml');
|
|
98
|
-
const
|
|
99
|
-
const legacyBootstrapProjectName = '__openhermit_bootstrap__';
|
|
99
|
+
const bootstrapProjectNames = new Set(['default', '__openhermit_bootstrap__']);
|
|
100
100
|
|
|
101
101
|
// ---------------------------------------------------------------------------
|
|
102
102
|
// Update command
|
|
@@ -363,36 +363,6 @@ function parseTomlToken(raw, section) {
|
|
|
363
363
|
return match?.[1] || '';
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
function hasProjectEntries(raw) {
|
|
367
|
-
return /^\s*\[\[projects\]\]/m.test(raw);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function buildBootstrapProjectToml() {
|
|
371
|
-
return `
|
|
372
|
-
# Internal bootstrap project used only so cc-connect can start with an otherwise empty config.
|
|
373
|
-
# It is safe to keep this project; users can replace or delete it after creating real teams.
|
|
374
|
-
[[projects]]
|
|
375
|
-
name = "${bootstrapProjectName}"
|
|
376
|
-
disabled_commands = ["*"]
|
|
377
|
-
|
|
378
|
-
[projects.agent]
|
|
379
|
-
type = "claudecode"
|
|
380
|
-
|
|
381
|
-
[projects.agent.options]
|
|
382
|
-
work_dir = "${escapeTomlPath(hermitHome)}"
|
|
383
|
-
mode = "default"
|
|
384
|
-
|
|
385
|
-
[[projects.platforms]]
|
|
386
|
-
type = "line"
|
|
387
|
-
|
|
388
|
-
[projects.platforms.options]
|
|
389
|
-
channel_secret = "openhermit-bootstrap"
|
|
390
|
-
channel_token = "openhermit-bootstrap"
|
|
391
|
-
port = "0"
|
|
392
|
-
callback_path = "/openhermit-bootstrap"
|
|
393
|
-
`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
366
|
function escapeRegExp(value) {
|
|
397
367
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
398
368
|
}
|
|
@@ -414,16 +384,26 @@ function isManagedBootstrapBlock(block) {
|
|
|
414
384
|
);
|
|
415
385
|
}
|
|
416
386
|
|
|
417
|
-
function
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
387
|
+
function stripManagedBootstrapProjects(raw) {
|
|
388
|
+
let next = raw;
|
|
389
|
+
for (const name of bootstrapProjectNames) {
|
|
390
|
+
const block = findProjectBlock(next, name);
|
|
391
|
+
if (block && isManagedBootstrapBlock(block.match[0])) {
|
|
392
|
+
next = next.replace(block.pattern, '').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
393
|
+
}
|
|
424
394
|
}
|
|
395
|
+
return next;
|
|
396
|
+
}
|
|
425
397
|
|
|
426
|
-
|
|
398
|
+
function hasRealProjectEntries(raw) {
|
|
399
|
+
const projectPattern = /\[\[projects\]\]\nname\s*=\s*"([^"]+)"[\s\S]*?(?=\n\[\[projects\]\]|\s*$)/g;
|
|
400
|
+
for (const match of raw.matchAll(projectPattern)) {
|
|
401
|
+
const name = match[1];
|
|
402
|
+
if (!bootstrapProjectNames.has(name) || !isManagedBootstrapBlock(match[0])) {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
427
407
|
}
|
|
428
408
|
|
|
429
409
|
function ensureCcConnectConfig() {
|
|
@@ -449,20 +429,15 @@ path = "/bridge/ws"
|
|
|
449
429
|
|
|
450
430
|
[log]
|
|
451
431
|
level = "info"
|
|
452
|
-
|
|
432
|
+
`;
|
|
453
433
|
writeFileSync(ccConnectConfigPath, config, 'utf-8');
|
|
454
434
|
}
|
|
455
435
|
|
|
456
436
|
let raw = readFileSync(ccConnectConfigPath, 'utf-8');
|
|
457
|
-
|
|
458
|
-
|
|
437
|
+
const migrated = stripManagedBootstrapProjects(raw);
|
|
438
|
+
if (migrated !== raw) {
|
|
439
|
+
raw = migrated;
|
|
459
440
|
writeFileSync(ccConnectConfigPath, raw, 'utf-8');
|
|
460
|
-
} else {
|
|
461
|
-
const migrated = migrateManagedBootstrapProject(raw);
|
|
462
|
-
if (migrated !== raw) {
|
|
463
|
-
raw = migrated;
|
|
464
|
-
writeFileSync(ccConnectConfigPath, raw, 'utf-8');
|
|
465
|
-
}
|
|
466
441
|
}
|
|
467
442
|
|
|
468
443
|
return {
|
|
@@ -474,6 +449,7 @@ ${buildBootstrapProjectToml()}`;
|
|
|
474
449
|
process.env.CC_CONNECT_BRIDGE_TOKEN ||
|
|
475
450
|
process.env.CC_CONNECT_TOKEN ||
|
|
476
451
|
parseTomlToken(raw, 'bridge'),
|
|
452
|
+
hasRealProjects: hasRealProjectEntries(raw),
|
|
477
453
|
};
|
|
478
454
|
}
|
|
479
455
|
|
|
@@ -493,6 +469,54 @@ async function waitForCcConnect(baseUrl, token, timeoutMs = 15_000) {
|
|
|
493
469
|
return false;
|
|
494
470
|
}
|
|
495
471
|
|
|
472
|
+
function appendLog(filePath, chunk) {
|
|
473
|
+
try {
|
|
474
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
475
|
+
appendFileSync(filePath, chunk);
|
|
476
|
+
} catch {
|
|
477
|
+
// Logging must never block startup.
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function printLogTail(label, filePath) {
|
|
482
|
+
try {
|
|
483
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
484
|
+
const lines = content.trimEnd().split(/\r?\n/).slice(-80);
|
|
485
|
+
console.error(`[openHermit] ${label} log: ${filePath}`);
|
|
486
|
+
if (lines.length > 0) {
|
|
487
|
+
console.error(`[openHermit] Last ${label} log lines:`);
|
|
488
|
+
console.error(lines.join('\n'));
|
|
489
|
+
}
|
|
490
|
+
} catch {
|
|
491
|
+
console.error(`[openHermit] ${label} log: ${filePath}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function waitForRuntimeReady(baseUrl, token, child, timeoutMs = 30_000) {
|
|
496
|
+
let exitCode = null;
|
|
497
|
+
child.once('exit', (code) => {
|
|
498
|
+
exitCode = code ?? 1;
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const startedAt = Date.now();
|
|
502
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
503
|
+
if (exitCode !== null) {
|
|
504
|
+
throw new Error(`Runtime service exited before becoming ready (code ${exitCode})`);
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const res = await fetch(`${baseUrl}/api/v1/status`, {
|
|
508
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
509
|
+
});
|
|
510
|
+
if (res.ok) return;
|
|
511
|
+
} catch {
|
|
512
|
+
// keep polling
|
|
513
|
+
}
|
|
514
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
throw new Error(`Runtime service did not become ready within ${Math.round(timeoutMs / 1000)}s`);
|
|
518
|
+
}
|
|
519
|
+
|
|
496
520
|
function resolveCcConnectRunner() {
|
|
497
521
|
const pkgPath = require.resolve('cc-connect/package.json');
|
|
498
522
|
return path.join(path.dirname(pkgPath), 'run.js');
|
|
@@ -519,6 +543,9 @@ if (!skipCcConnect) {
|
|
|
519
543
|
const alreadyRunning = await waitForCcConnect(ccBaseUrl, ccTokens.managementToken, 1_000);
|
|
520
544
|
if (alreadyRunning) {
|
|
521
545
|
console.log(`[openHermit] Runtime service already running: ${ccBaseUrl}`);
|
|
546
|
+
} else if (!ccTokens.hasRealProjects) {
|
|
547
|
+
console.log('[openHermit] No runtime projects configured; starting control panel only.');
|
|
548
|
+
console.log('[openHermit] Create a team in the UI to install/configure a runtime.');
|
|
522
549
|
} else {
|
|
523
550
|
console.log('[openHermit] Starting bundled runtime service...');
|
|
524
551
|
console.log(`[openHermit] Runtime config: ${ccConnectConfigPath}`);
|
|
@@ -531,11 +558,28 @@ if (!skipCcConnect) {
|
|
|
531
558
|
CC_CONNECT_MANAGEMENT_TOKEN: ccTokens.managementToken,
|
|
532
559
|
CC_CONNECT_BRIDGE_TOKEN: ccTokens.bridgeToken,
|
|
533
560
|
},
|
|
534
|
-
stdio: '
|
|
561
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
ccConnectProcess.stdout?.on('data', (chunk) => {
|
|
565
|
+
process.stdout.write(chunk);
|
|
566
|
+
appendLog(runtimeLogPath, chunk);
|
|
535
567
|
});
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
568
|
+
ccConnectProcess.stderr?.on('data', (chunk) => {
|
|
569
|
+
process.stderr.write(chunk);
|
|
570
|
+
appendLog(runtimeLogPath, chunk);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
try {
|
|
574
|
+
await waitForRuntimeReady(ccBaseUrl, ccTokens.managementToken, ccConnectProcess, 30_000);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
console.error(
|
|
577
|
+
`[openHermit] Runtime service failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
578
|
+
);
|
|
579
|
+
printLogTail('Runtime', runtimeLogPath);
|
|
580
|
+
signalDaemon(ccConnectProcess.pid, 'SIGTERM');
|
|
581
|
+
setTimeout(() => signalDaemon(ccConnectProcess?.pid, 'SIGKILL'), 2_000).unref();
|
|
582
|
+
process.exit(1);
|
|
539
583
|
}
|
|
540
584
|
}
|
|
541
585
|
}
|
|
@@ -577,27 +621,8 @@ if (!existsSync(distRenderererDir) || !existsSync(path.join(distRenderererDir, '
|
|
|
577
621
|
// Start the server
|
|
578
622
|
console.log('[openHermit] Launching server...\n');
|
|
579
623
|
|
|
580
|
-
function appendServerLog(chunk) {
|
|
581
|
-
try {
|
|
582
|
-
mkdirSync(path.dirname(serverLogPath), { recursive: true });
|
|
583
|
-
appendFileSync(serverLogPath, chunk);
|
|
584
|
-
} catch {
|
|
585
|
-
// Logging must never block startup.
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
624
|
function printServerLogTail() {
|
|
590
|
-
|
|
591
|
-
const content = readFileSync(serverLogPath, 'utf-8');
|
|
592
|
-
const lines = content.trimEnd().split(/\r?\n/).slice(-60);
|
|
593
|
-
if (lines.length > 0) {
|
|
594
|
-
console.error(`[openHermit] Server log: ${serverLogPath}`);
|
|
595
|
-
console.error('[openHermit] Last server log lines:');
|
|
596
|
-
console.error(lines.join('\n'));
|
|
597
|
-
}
|
|
598
|
-
} catch {
|
|
599
|
-
console.error(`[openHermit] Server log: ${serverLogPath}`);
|
|
600
|
-
}
|
|
625
|
+
printLogTail('Server', serverLogPath);
|
|
601
626
|
}
|
|
602
627
|
|
|
603
628
|
const serverProcess = spawn(process.execPath, ['--import', resolveAliasLoaderRegister(), '--import', resolveTsxLoader(), 'src/main/server.ts'], {
|
|
@@ -619,12 +644,12 @@ const serverProcess = spawn(process.execPath, ['--import', resolveAliasLoaderReg
|
|
|
619
644
|
|
|
620
645
|
serverProcess.stdout?.on('data', (chunk) => {
|
|
621
646
|
process.stdout.write(chunk);
|
|
622
|
-
|
|
647
|
+
appendLog(serverLogPath, chunk);
|
|
623
648
|
});
|
|
624
649
|
|
|
625
650
|
serverProcess.stderr?.on('data', (chunk) => {
|
|
626
651
|
process.stderr.write(chunk);
|
|
627
|
-
|
|
652
|
+
appendLog(serverLogPath, chunk);
|
|
628
653
|
});
|
|
629
654
|
|
|
630
655
|
serverProcess.on('exit', (code) => {
|