copilot-hub 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-hub",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Copilot Hub CLI and runtime bundle",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,7 +11,10 @@ const runtimeDir = path.join(repoRoot, ".copilot-hub");
11
11
  const pidsDir = path.join(runtimeDir, "pids");
12
12
  const logsDir = path.join(repoRoot, "logs");
13
13
  const daemonStatePath = path.join(pidsDir, "daemon.json");
14
+ const lastStartupErrorPath = path.join(runtimeDir, "last-startup-error.json");
14
15
  const daemonLogPath = path.join(logsDir, "service-daemon.log");
16
+ const controlPlaneLogPath = path.join(logsDir, "control-plane.log");
17
+ const agentEngineLogPath = path.join(logsDir, "agent-engine.log");
15
18
  const daemonScriptPath = path.join(repoRoot, "scripts", "dist", "daemon.mjs");
16
19
  const supervisorScriptPath = path.join(repoRoot, "scripts", "dist", "supervisor.mjs");
17
20
  const nodeBin = process.execPath;
@@ -104,6 +107,7 @@ async function runDaemonLoop() {
104
107
  while (!state.stopping) {
105
108
  const ensureResult = runSupervisor("ensure", { allowFailure: true });
106
109
  if (ensureResult.ok) {
110
+ clearLastStartupError();
107
111
  if (failureCount > 0) {
108
112
  console.log("[daemon] workers recovered.");
109
113
  }
@@ -111,6 +115,15 @@ async function runDaemonLoop() {
111
115
  await sleepInterruptible(BASE_CHECK_MS, () => state.stopping);
112
116
  continue;
113
117
  }
118
+ const fatal = detectFatalStartupError(ensureResult);
119
+ if (fatal) {
120
+ writeLastStartupError(fatal);
121
+ console.error(`[daemon] fatal startup error: ${fatal.reason}`);
122
+ console.error(`[daemon] action required: ${fatal.action}`);
123
+ state.stopping = true;
124
+ await shutdownDaemon(state, { reason: "fatal-configuration", exitCode: 1 });
125
+ return;
126
+ }
114
127
  failureCount += 1;
115
128
  const delay = computeBackoffDelay(failureCount);
116
129
  const reason = firstLine(ensureResult.combinedOutput) ||
@@ -148,6 +161,7 @@ function showDaemonStatus() {
148
161
  console.log(`running: ${running ? "yes" : "no"}`);
149
162
  console.log(`pid: ${running ? String(pid) : "-"}`);
150
163
  console.log(`logFile: ${daemonLogPath}`);
164
+ printLastStartupError();
151
165
  if (!fs.existsSync(supervisorScriptPath)) {
152
166
  console.log("\n(worker status unavailable: supervisor script missing)");
153
167
  return;
@@ -384,6 +398,98 @@ function getErrorMessage(error) {
384
398
  }
385
399
  return String(error ?? "Unknown error.");
386
400
  }
401
+ function detectFatalStartupError(ensureResult) {
402
+ const evidenceChunks = [
403
+ String(ensureResult?.combinedOutput ?? ""),
404
+ readLogTail(controlPlaneLogPath, 120),
405
+ readLogTail(agentEngineLogPath, 120),
406
+ ].map((chunk) => String(chunk ?? "").trim());
407
+ const missingHubTokenLine = findLineContaining(evidenceChunks, (line) => line.includes("hub telegram token is missing") && line.includes("hub_telegram_token"));
408
+ const missingHubToken = Boolean(missingHubTokenLine);
409
+ if (missingHubToken) {
410
+ return {
411
+ reason: missingHubTokenLine || "Hub Telegram token is missing (HUB_TELEGRAM_TOKEN).",
412
+ action: "Run 'copilot-hub start' in a terminal (it will guide setup), then retry service.",
413
+ detectedAt: new Date().toISOString(),
414
+ };
415
+ }
416
+ return null;
417
+ }
418
+ function readLogTail(filePath, maxLines = 120) {
419
+ try {
420
+ if (!fs.existsSync(filePath)) {
421
+ return "";
422
+ }
423
+ const content = fs.readFileSync(filePath, "utf8");
424
+ const lines = content.split(/\r?\n/);
425
+ return lines.slice(-maxLines).join("\n").trim();
426
+ }
427
+ catch {
428
+ return "";
429
+ }
430
+ }
431
+ function findLineContaining(chunks, predicate) {
432
+ const lines = chunks
433
+ .flatMap((chunk) => String(chunk ?? "").split(/\r?\n/))
434
+ .map((line) => String(line ?? "").trim())
435
+ .filter(Boolean);
436
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
437
+ const line = lines[index];
438
+ if (predicate(line.toLowerCase())) {
439
+ return line;
440
+ }
441
+ }
442
+ return "";
443
+ }
444
+ function writeLastStartupError(value) {
445
+ try {
446
+ fs.mkdirSync(runtimeDir, { recursive: true });
447
+ fs.writeFileSync(lastStartupErrorPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
448
+ }
449
+ catch {
450
+ // Best effort only.
451
+ }
452
+ }
453
+ function readLastStartupError() {
454
+ if (!fs.existsSync(lastStartupErrorPath)) {
455
+ return null;
456
+ }
457
+ try {
458
+ const raw = fs.readFileSync(lastStartupErrorPath, "utf8");
459
+ const parsed = JSON.parse(raw);
460
+ return parsed && typeof parsed === "object" ? parsed : null;
461
+ }
462
+ catch {
463
+ return null;
464
+ }
465
+ }
466
+ function clearLastStartupError() {
467
+ if (!fs.existsSync(lastStartupErrorPath)) {
468
+ return;
469
+ }
470
+ try {
471
+ fs.rmSync(lastStartupErrorPath, { force: true });
472
+ }
473
+ catch {
474
+ // Best effort only.
475
+ }
476
+ }
477
+ function printLastStartupError() {
478
+ const issue = readLastStartupError();
479
+ if (!issue) {
480
+ return;
481
+ }
482
+ console.log("\n=== last startup error ===");
483
+ if (issue.detectedAt) {
484
+ console.log(`detectedAt: ${String(issue.detectedAt)}`);
485
+ }
486
+ if (issue.reason) {
487
+ console.log(`reason: ${String(issue.reason)}`);
488
+ }
489
+ if (issue.action) {
490
+ console.log(`action: ${String(issue.action)}`);
491
+ }
492
+ }
387
493
  function printUsage() {
388
494
  console.log("Usage: node scripts/dist/daemon.mjs <start|run|stop|status|help>");
389
495
  }
@@ -14,7 +14,10 @@ const pidsDir = path.join(runtimeDir, "pids");
14
14
  const logsDir = path.join(repoRoot, "logs");
15
15
 
16
16
  const daemonStatePath = path.join(pidsDir, "daemon.json");
17
+ const lastStartupErrorPath = path.join(runtimeDir, "last-startup-error.json");
17
18
  const daemonLogPath = path.join(logsDir, "service-daemon.log");
19
+ const controlPlaneLogPath = path.join(logsDir, "control-plane.log");
20
+ const agentEngineLogPath = path.join(logsDir, "agent-engine.log");
18
21
  const daemonScriptPath = path.join(repoRoot, "scripts", "dist", "daemon.mjs");
19
22
  const supervisorScriptPath = path.join(repoRoot, "scripts", "dist", "supervisor.mjs");
20
23
  const nodeBin = process.execPath;
@@ -123,6 +126,7 @@ async function runDaemonLoop() {
123
126
  while (!state.stopping) {
124
127
  const ensureResult = runSupervisor("ensure", { allowFailure: true });
125
128
  if (ensureResult.ok) {
129
+ clearLastStartupError();
126
130
  if (failureCount > 0) {
127
131
  console.log("[daemon] workers recovered.");
128
132
  }
@@ -131,6 +135,16 @@ async function runDaemonLoop() {
131
135
  continue;
132
136
  }
133
137
 
138
+ const fatal = detectFatalStartupError(ensureResult);
139
+ if (fatal) {
140
+ writeLastStartupError(fatal);
141
+ console.error(`[daemon] fatal startup error: ${fatal.reason}`);
142
+ console.error(`[daemon] action required: ${fatal.action}`);
143
+ state.stopping = true;
144
+ await shutdownDaemon(state, { reason: "fatal-configuration", exitCode: 1 });
145
+ return;
146
+ }
147
+
134
148
  failureCount += 1;
135
149
  const delay = computeBackoffDelay(failureCount);
136
150
  const reason =
@@ -180,6 +194,7 @@ function showDaemonStatus() {
180
194
  console.log(`running: ${running ? "yes" : "no"}`);
181
195
  console.log(`pid: ${running ? String(pid) : "-"}`);
182
196
  console.log(`logFile: ${daemonLogPath}`);
197
+ printLastStartupError();
183
198
 
184
199
  if (!fs.existsSync(supervisorScriptPath)) {
185
200
  console.log("\n(worker status unavailable: supervisor script missing)");
@@ -454,6 +469,108 @@ function getErrorMessage(error) {
454
469
  return String(error ?? "Unknown error.");
455
470
  }
456
471
 
472
+ function detectFatalStartupError(ensureResult) {
473
+ const evidenceChunks = [
474
+ String(ensureResult?.combinedOutput ?? ""),
475
+ readLogTail(controlPlaneLogPath, 120),
476
+ readLogTail(agentEngineLogPath, 120),
477
+ ].map((chunk) => String(chunk ?? "").trim());
478
+
479
+ const missingHubTokenLine = findLineContaining(
480
+ evidenceChunks,
481
+ (line) => line.includes("hub telegram token is missing") && line.includes("hub_telegram_token"),
482
+ );
483
+ const missingHubToken = Boolean(missingHubTokenLine);
484
+ if (missingHubToken) {
485
+ return {
486
+ reason: missingHubTokenLine || "Hub Telegram token is missing (HUB_TELEGRAM_TOKEN).",
487
+ action: "Run 'copilot-hub start' in a terminal (it will guide setup), then retry service.",
488
+ detectedAt: new Date().toISOString(),
489
+ };
490
+ }
491
+
492
+ return null;
493
+ }
494
+
495
+ function readLogTail(filePath, maxLines = 120) {
496
+ try {
497
+ if (!fs.existsSync(filePath)) {
498
+ return "";
499
+ }
500
+
501
+ const content = fs.readFileSync(filePath, "utf8");
502
+ const lines = content.split(/\r?\n/);
503
+ return lines.slice(-maxLines).join("\n").trim();
504
+ } catch {
505
+ return "";
506
+ }
507
+ }
508
+
509
+ function findLineContaining(chunks, predicate) {
510
+ const lines = chunks
511
+ .flatMap((chunk) => String(chunk ?? "").split(/\r?\n/))
512
+ .map((line) => String(line ?? "").trim())
513
+ .filter(Boolean);
514
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
515
+ const line = lines[index];
516
+ if (predicate(line.toLowerCase())) {
517
+ return line;
518
+ }
519
+ }
520
+ return "";
521
+ }
522
+
523
+ function writeLastStartupError(value) {
524
+ try {
525
+ fs.mkdirSync(runtimeDir, { recursive: true });
526
+ fs.writeFileSync(lastStartupErrorPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
527
+ } catch {
528
+ // Best effort only.
529
+ }
530
+ }
531
+
532
+ function readLastStartupError() {
533
+ if (!fs.existsSync(lastStartupErrorPath)) {
534
+ return null;
535
+ }
536
+ try {
537
+ const raw = fs.readFileSync(lastStartupErrorPath, "utf8");
538
+ const parsed = JSON.parse(raw);
539
+ return parsed && typeof parsed === "object" ? parsed : null;
540
+ } catch {
541
+ return null;
542
+ }
543
+ }
544
+
545
+ function clearLastStartupError() {
546
+ if (!fs.existsSync(lastStartupErrorPath)) {
547
+ return;
548
+ }
549
+ try {
550
+ fs.rmSync(lastStartupErrorPath, { force: true });
551
+ } catch {
552
+ // Best effort only.
553
+ }
554
+ }
555
+
556
+ function printLastStartupError() {
557
+ const issue = readLastStartupError();
558
+ if (!issue) {
559
+ return;
560
+ }
561
+
562
+ console.log("\n=== last startup error ===");
563
+ if (issue.detectedAt) {
564
+ console.log(`detectedAt: ${String(issue.detectedAt)}`);
565
+ }
566
+ if (issue.reason) {
567
+ console.log(`reason: ${String(issue.reason)}`);
568
+ }
569
+ if (issue.action) {
570
+ console.log(`action: ${String(issue.action)}`);
571
+ }
572
+ }
573
+
457
574
  function printUsage() {
458
575
  console.log("Usage: node scripts/dist/daemon.mjs <start|run|stop|status|help>");
459
576
  }