@virtengine/openfleet 0.26.1 → 0.26.3

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/.env.example CHANGED
@@ -1,4 +1,4 @@
1
- # ─── Codex Monitor — Environment Configuration ───────────────────────────────
1
+ # ─── OpenFleet — Environment Configuration ───────────────────────────────
2
2
  # Copy this file to .env and fill in your values.
3
3
  # Or run: openfleet --setup
4
4
  # All variables are optional unless marked [REQUIRED].
@@ -109,7 +109,7 @@ TELEGRAM_MINIAPP_ENABLED=false
109
109
  # Keep Telegram command availability even when openfleet is down.
110
110
  # Sentinel can auto-restart monitor, detect crash loops, and run repair-agent.
111
111
  # Auto-start sentinel whenever openfleet starts (default: disabled)
112
- # CODEX_MONITOR_SENTINEL_AUTO_START=true
112
+ # OPENFLEET_SENTINEL_AUTO_START=true
113
113
  # Auto-restart monitor when sentinel detects monitor down/crash (default: 1)
114
114
  # SENTINEL_AUTO_RESTART_MONITOR=true
115
115
  # Crash-loop threshold within rolling window (default: 3)
@@ -322,9 +322,9 @@ TELEGRAM_MINIAPP_ENABLED=false
322
322
  # Auto-assign task creator/login when creating issues (default: true)
323
323
  # GITHUB_AUTO_ASSIGN_CREATOR=true
324
324
  # Codex task scoping label policy (only matching labels are picked by openfleet)
325
- # CODEX_MONITOR_TASK_LABEL=openfleet
326
- # CODEX_MONITOR_TASK_LABELS=openfleet,codex-mointor
327
- # CODEX_MONITOR_ENFORCE_TASK_LABEL=true
325
+ # OPENFLEET_TASK_LABEL=openfleet
326
+ # OPENFLEET_TASK_LABELS=openfleet,codex-mointor
327
+ # OPENFLEET_ENFORCE_TASK_LABEL=true
328
328
  # Optional issue fetch cap per sync/poll cycle (default: 1000)
329
329
  # GITHUB_ISSUES_LIST_LIMIT=1000
330
330
 
@@ -427,12 +427,12 @@ TELEGRAM_MINIAPP_ENABLED=false
427
427
  # PID file: .cache/openfleet.pid
428
428
  # Logs: logs/daemon.log
429
429
  # Daemon crash supervision (monitor child):
430
- # CODEX_MONITOR_DAEMON_RESTART_DELAY_MS=5000
431
- # CODEX_MONITOR_DAEMON_MAX_RESTARTS=0 # 0 = unlimited
430
+ # OPENFLEET_DAEMON_RESTART_DELAY_MS=5000
431
+ # OPENFLEET_DAEMON_MAX_RESTARTS=0 # 0 = unlimited
432
432
  # Consider any crash within this window as an instant startup failure (default: 15000)
433
- # CODEX_MONITOR_DAEMON_INSTANT_CRASH_WINDOW_MS=15000
433
+ # OPENFLEET_DAEMON_INSTANT_CRASH_WINDOW_MS=15000
434
434
  # Stop auto-restarts after this many instant failures in a row (default: 3)
435
- # CODEX_MONITOR_DAEMON_MAX_INSTANT_RESTARTS=3
435
+ # OPENFLEET_DAEMON_MAX_INSTANT_RESTARTS=3
436
436
 
437
437
  # ─── Vibe-Kanban ──────────────────────────────────────────────────────────────
438
438
  # Base URL for the Vibe-Kanban API (default: http://127.0.0.1:54089)
@@ -492,7 +492,7 @@ VK_RECOVERY_PORT=54089
492
492
  # Target branch for PR checks/merge (default: origin/main)
493
493
  # VK_TARGET_BRANCH=origin/main
494
494
  # Default upstream/base branch for openfleet tasks (overrides VK_TARGET_BRANCH)
495
- # CODEX_MONITOR_TASK_UPSTREAM=origin/ve/openfleet-generic
495
+ # OPENFLEET_TASK_UPSTREAM=origin/ve/openfleet-generic
496
496
 
497
497
  # ─── Codex / AI Provider ─────────────────────────────────────────────────────
498
498
  # The Codex SDK uses OpenAI-compatible configuration that has been setup in ~/.codex/config.toml -
@@ -544,39 +544,39 @@ VK_RECOVERY_PORT=54089
544
544
  #
545
545
  # Hook profile for setup/non-interactive runs:
546
546
  # strict | balanced | lightweight | none
547
- # CODEX_MONITOR_HOOK_PROFILE=strict
547
+ # OPENFLEET_HOOK_PROFILE=strict
548
548
  #
549
549
  # Which agents should receive generated hook files (comma-separated):
550
550
  # codex,claude,copilot
551
- # CODEX_MONITOR_HOOK_TARGETS=codex,claude,copilot
551
+ # OPENFLEET_HOOK_TARGETS=codex,claude,copilot
552
552
  #
553
553
  # Set to false to skip hook scaffolding during setup.
554
- # CODEX_MONITOR_HOOKS_ENABLED=true
554
+ # OPENFLEET_HOOKS_ENABLED=true
555
555
  # Set to true to overwrite existing generated hook files.
556
- # CODEX_MONITOR_HOOKS_OVERWRITE=false
556
+ # OPENFLEET_HOOKS_OVERWRITE=false
557
557
  # Optional overrides for generated bridge command tokens.
558
558
  # Defaults are portable across workstations:
559
559
  # node scripts/openfleet/agent-hook-bridge.mjs
560
- # CODEX_MONITOR_HOOK_NODE_BIN=node
561
- # CODEX_MONITOR_HOOK_BRIDGE_PATH=scripts/openfleet/agent-hook-bridge.mjs
560
+ # OPENFLEET_HOOK_NODE_BIN=node
561
+ # OPENFLEET_HOOK_BRIDGE_PATH=scripts/openfleet/agent-hook-bridge.mjs
562
562
  #
563
563
  # Optional per-event command overrides (separate multiple commands with ';;').
564
564
  # Use value 'none' to disable that event in generated .codex/hooks.json.
565
- # CODEX_MONITOR_HOOK_PREPUSH=go vet ./...;;go build ./...
566
- # CODEX_MONITOR_HOOK_PRECOMMIT=gofmt -l .
567
- # CODEX_MONITOR_HOOK_TASK_COMPLETE=echo \"task completed\"
568
- # CODEX_MONITOR_HOOK_SESSION_START=echo \"session start\"
569
- # CODEX_MONITOR_HOOK_SESSION_STOP=echo \"session stop\"
565
+ # OPENFLEET_HOOK_PREPUSH=go vet ./...;;go build ./...
566
+ # OPENFLEET_HOOK_PRECOMMIT=gofmt -l .
567
+ # OPENFLEET_HOOK_TASK_COMPLETE=echo \"task completed\"
568
+ # OPENFLEET_HOOK_SESSION_START=echo \"session start\"
569
+ # OPENFLEET_HOOK_SESSION_STOP=echo \"session stop\"
570
570
  #
571
571
  # Built-in hook behavior inside openfleet runtime:
572
572
  # force (default), auto, off
573
- # CODEX_MONITOR_HOOKS_BUILTINS_MODE=force
574
- # CODEX_MONITOR_HOOKS_DISABLE_PREPUSH=false
575
- # CODEX_MONITOR_HOOKS_DISABLE_TASK_COMPLETE=false
576
- # CODEX_MONITOR_HOOKS_DISABLE_HEALTH_CHECK=false
573
+ # OPENFLEET_HOOKS_BUILTINS_MODE=force
574
+ # OPENFLEET_HOOKS_DISABLE_PREPUSH=false
575
+ # OPENFLEET_HOOKS_DISABLE_TASK_COMPLETE=false
576
+ # OPENFLEET_HOOKS_DISABLE_HEALTH_CHECK=false
577
577
 
578
578
  # Force hooks to fire even for non-managed sessions (debug only):
579
- # CODEX_MONITOR_HOOKS_FORCE=false
579
+ # OPENFLEET_HOOKS_FORCE=false
580
580
 
581
581
  # VE_MANAGED is auto-set by openfleet at startup. Agent hook bridge
582
582
  # scripts check for this and exit silently if not present, preventing
@@ -788,7 +788,7 @@ COPILOT_CLOUD_DISABLED=true
788
788
 
789
789
  # ─── Git Identity (optional) ─────────────────────────────────────────────────
790
790
  # Override git author for automated commits
791
- # VE_GIT_AUTHOR_NAME=Codex Monitor
791
+ # VE_GIT_AUTHOR_NAME=OpenFleet
792
792
  # VE_GIT_AUTHOR_EMAIL=bot@yoursite.com
793
793
 
794
794
  # ─── Task Planner ─────────────────────────────────────────────────────────────
@@ -816,11 +816,11 @@ COPILOT_CLOUD_DISABLED=true
816
816
  # .openfleet/agents/*.md
817
817
  # Files in that folder are loaded automatically and are intended for per-project customization.
818
818
  # You can also override any prompt path explicitly with env vars:
819
- # CODEX_MONITOR_PROMPT_PLANNER=.openfleet/agents/task-planner.md
820
- # CODEX_MONITOR_PROMPT_MONITOR_MONITOR=.openfleet/agents/monitor-monitor.md
821
- # CODEX_MONITOR_PROMPT_TASK_EXECUTOR=.openfleet/agents/task-executor.md
822
- # CODEX_MONITOR_PROMPT_REVIEWER=.openfleet/agents/reviewer.md
823
- # CODEX_MONITOR_PROMPT_SDK_CONFLICT_RESOLVER=.openfleet/agents/sdk-conflict-resolver.md
819
+ # OPENFLEET_PROMPT_PLANNER=.openfleet/agents/task-planner.md
820
+ # OPENFLEET_PROMPT_MONITOR_MONITOR=.openfleet/agents/monitor-monitor.md
821
+ # OPENFLEET_PROMPT_TASK_EXECUTOR=.openfleet/agents/task-executor.md
822
+ # OPENFLEET_PROMPT_REVIEWER=.openfleet/agents/reviewer.md
823
+ # OPENFLEET_PROMPT_SDK_CONFLICT_RESOLVER=.openfleet/agents/sdk-conflict-resolver.md
824
824
 
825
825
  # ─── Dependabot / Bot PR Auto-Merge ───────────────────────────────────────────
826
826
  # Auto-merge Dependabot (and other bot) PRs after all CI checks pass.
@@ -864,7 +864,7 @@ COPILOT_CLOUD_DISABLED=true
864
864
 
865
865
  # ─── Advanced ─────────────────────────────────────────────────────────────────
866
866
  # Override openfleet config directory (where .env and config live)
867
- # CODEX_MONITOR_DIR=/path/to/scripts/openfleet
867
+ # OPENFLEET_DIR=/path/to/scripts/openfleet
868
868
  # Max orchestrator restarts (0 = unlimited)
869
869
  # MAX_RESTARTS=0
870
870
  # Restart delay in milliseconds
@@ -166,7 +166,7 @@ async function run() {
166
166
  // openfleet sets VE_MANAGED=1 in all spawned agent environments.
167
167
  // If this env var is missing, we're running inside a standalone agent session
168
168
  // that just happens to have the hook files in its config — exit silently.
169
- if (!process.env.VE_MANAGED && !process.env.CODEX_MONITOR_HOOKS_FORCE) {
169
+ if (!process.env.VE_MANAGED && !process.env.OPENFLEET_HOOKS_FORCE) {
170
170
  process.exit(0);
171
171
  }
172
172
 
package/agent-hooks.mjs CHANGED
@@ -617,7 +617,7 @@ export async function executeBlockingHooks(event, context = {}) {
617
617
  export function registerBuiltinHooks(options = {}) {
618
618
  const modeRaw =
619
619
  options.mode ??
620
- process.env.CODEX_MONITOR_HOOKS_BUILTINS_MODE ??
620
+ process.env.OPENFLEET_HOOKS_BUILTINS_MODE ??
621
621
  process.env.VE_HOOK_BUILTINS_MODE ??
622
622
  "force";
623
623
  const mode = String(modeRaw).trim().toLowerCase();
@@ -633,12 +633,12 @@ export function registerBuiltinHooks(options = {}) {
633
633
  (hook) => !hook.builtin,
634
634
  );
635
635
  const skipPrePush =
636
- envFlag("CODEX_MONITOR_HOOKS_DISABLE_PREPUSH", false) ||
636
+ envFlag("OPENFLEET_HOOKS_DISABLE_PREPUSH", false) ||
637
637
  envFlag("VE_HOOK_DISABLE_PREPUSH", false) ||
638
638
  (mode === "auto" && hasCustomPrePush);
639
639
  const skipTaskComplete =
640
- envFlag("CODEX_MONITOR_HOOKS_DISABLE_TASK_COMPLETE", false) ||
641
- envFlag("CODEX_MONITOR_HOOKS_DISABLE_VALIDATION", false) ||
640
+ envFlag("OPENFLEET_HOOKS_DISABLE_TASK_COMPLETE", false) ||
641
+ envFlag("OPENFLEET_HOOKS_DISABLE_VALIDATION", false) ||
642
642
  envFlag("VE_HOOK_DISABLE_TASK_COMPLETE", false) ||
643
643
  (mode === "auto" && hasCustomTaskComplete);
644
644
 
@@ -680,7 +680,7 @@ export function registerBuiltinHooks(options = {}) {
680
680
  // ── SessionStart: worktree health check ──
681
681
  // Verifies the worktree directory exists, is a valid git repo,
682
682
  // and the expected branch is checked out. Retryable for transient git issues.
683
- const skipHealthCheck = envFlag("CODEX_MONITOR_HOOKS_DISABLE_HEALTH_CHECK", false);
683
+ const skipHealthCheck = envFlag("OPENFLEET_HOOKS_DISABLE_HEALTH_CHECK", false);
684
684
  if (!skipHealthCheck) {
685
685
  const healthCmd = IS_WINDOWS
686
686
  ? 'powershell -NoProfile -Command "if (-not (Test-Path .git)) { if (-not (git rev-parse --git-dir 2>$null)) { Write-Error \'Not a git repository\'; exit 1 } }; git status --porcelain 2>&1 | Out-Null; if ($LASTEXITCODE -ne 0) { Write-Error \'git status failed\'; exit 1 }; Write-Host \'OK: worktree healthy\'"'
package/agent-prompts.mjs CHANGED
@@ -119,7 +119,7 @@ export const AGENT_PROMPT_DEFINITIONS = Object.freeze(
119
119
  PROMPT_DEFS.map((item) =>
120
120
  Object.freeze({
121
121
  ...item,
122
- envVar: `CODEX_MONITOR_PROMPT_${toEnvSuffix(item.key)}`,
122
+ envVar: `OPENFLEET_PROMPT_${toEnvSuffix(item.key)}`,
123
123
  defaultRelativePath: `${PROMPT_WORKSPACE_DIR}/${item.filename}`,
124
124
  }),
125
125
  ),
@@ -562,7 +562,7 @@ export function getAgentPromptDefinitions() {
562
562
 
563
563
  export function getDefaultPromptWorkspace(repoRoot) {
564
564
  const override = String(
565
- process.env.CODEX_MONITOR_PROMPT_WORKSPACE || "",
565
+ process.env.OPENFLEET_PROMPT_WORKSPACE || "",
566
566
  ).trim();
567
567
  if (override) {
568
568
  return isAbsolute(override)
@@ -607,13 +607,13 @@ export function ensureAgentPromptWorkspace(repoRoot) {
607
607
  mkdirSync(workspaceDir, { recursive: true });
608
608
  } catch (err) {
609
609
  const fallbackRoot = resolve(
610
- process.env.CODEX_MONITOR_HOME ||
610
+ process.env.OPENFLEET_HOME ||
611
611
  process.env.HOME ||
612
612
  process.env.USERPROFILE ||
613
613
  homedir(),
614
614
  );
615
615
  const fallbackDir = resolve(fallbackRoot, PROMPT_WORKSPACE_DIR);
616
- process.env.CODEX_MONITOR_PROMPT_WORKSPACE = fallbackDir;
616
+ process.env.OPENFLEET_PROMPT_WORKSPACE = fallbackDir;
617
617
  workspaceDir = fallbackDir;
618
618
  mkdirSync(workspaceDir, { recursive: true });
619
619
  console.warn(
package/cli.mjs CHANGED
@@ -27,6 +27,11 @@ import { fileURLToPath } from "node:url";
27
27
  import { fork, spawn } from "node:child_process";
28
28
  import os from "node:os";
29
29
  import { createDaemonCrashTracker } from "./daemon-restart-policy.mjs";
30
+ import {
31
+ applyAllCompatibility,
32
+ detectLegacySetup,
33
+ migrateFromLegacy,
34
+ } from "./compat.mjs";
30
35
 
31
36
  const __dirname = dirname(fileURLToPath(import.meta.url));
32
37
  const args = process.argv.slice(2);
@@ -140,9 +145,9 @@ function showHelp() {
140
145
  5. Built-in defaults
141
146
 
142
147
  Auto-update environment variables:
143
- CODEX_MONITOR_SKIP_UPDATE_CHECK=1 Disable startup version check
144
- CODEX_MONITOR_SKIP_AUTO_UPDATE=1 Disable background polling
145
- CODEX_MONITOR_UPDATE_INTERVAL_MS=N Override poll interval (default: 600000)
148
+ OPENFLEET_SKIP_UPDATE_CHECK=1 Disable startup version check
149
+ OPENFLEET_SKIP_AUTO_UPDATE=1 Disable background polling
150
+ OPENFLEET_UPDATE_INTERVAL_MS=N Override poll interval (default: 600000)
146
151
 
147
152
  See .env.example for all environment variables.
148
153
 
@@ -199,23 +204,23 @@ const SENTINEL_SCRIPT_PATH = fileURLToPath(
199
204
  new URL("./telegram-sentinel.mjs", import.meta.url),
200
205
  );
201
206
  const IS_DAEMON_CHILD =
202
- args.includes("--daemon-child") || process.env.CODEX_MONITOR_DAEMON === "1";
207
+ args.includes("--daemon-child") || process.env.OPENFLEET_DAEMON === "1";
203
208
  const DAEMON_RESTART_DELAY_MS = Math.max(
204
209
  1000,
205
- Number(process.env.CODEX_MONITOR_DAEMON_RESTART_DELAY_MS || 5000) || 5000,
210
+ Number(process.env.OPENFLEET_DAEMON_RESTART_DELAY_MS || 5000) || 5000,
206
211
  );
207
212
  const DAEMON_MAX_RESTARTS = Math.max(
208
213
  0,
209
- Number(process.env.CODEX_MONITOR_DAEMON_MAX_RESTARTS || 0) || 0,
214
+ Number(process.env.OPENFLEET_DAEMON_MAX_RESTARTS || 0) || 0,
210
215
  );
211
216
  const DAEMON_INSTANT_CRASH_WINDOW_MS = Math.max(
212
217
  1000,
213
- Number(process.env.CODEX_MONITOR_DAEMON_INSTANT_CRASH_WINDOW_MS || 15000) ||
218
+ Number(process.env.OPENFLEET_DAEMON_INSTANT_CRASH_WINDOW_MS || 15000) ||
214
219
  15000,
215
220
  );
216
221
  const DAEMON_MAX_INSTANT_RESTARTS = Math.max(
217
222
  1,
218
- Number(process.env.CODEX_MONITOR_DAEMON_MAX_INSTANT_RESTARTS || 3) || 3,
223
+ Number(process.env.OPENFLEET_DAEMON_MAX_INSTANT_RESTARTS || 3) || 3,
219
224
  );
220
225
  let daemonRestartCount = 0;
221
226
  const daemonCrashTracker = createDaemonCrashTracker({
@@ -283,7 +288,7 @@ async function ensureSentinelRunning(options = {}) {
283
288
  windowsHide: process.platform === "win32",
284
289
  env: {
285
290
  ...process.env,
286
- CODEX_MONITOR_SENTINEL_COMPANION: "1",
291
+ OPENFLEET_SENTINEL_COMPANION: "1",
287
292
  },
288
293
  cwd: process.cwd(),
289
294
  });
@@ -380,8 +385,9 @@ function startDaemon() {
380
385
  detached: true,
381
386
  stdio: "ignore",
382
387
  windowsHide: process.platform === "win32",
383
- env: { ...process.env, CODEX_MONITOR_DAEMON: "1" },
384
- cwd: process.cwd(),
388
+ env: { ...process.env, OPENFLEET_DAEMON: "1" },
389
+ // Use home dir so spawn never inherits a deleted CWD (e.g. old git worktree)
390
+ cwd: os.homedir(),
385
391
  },
386
392
  );
387
393
 
@@ -456,6 +462,9 @@ function daemonStatus() {
456
462
  }
457
463
 
458
464
  async function main() {
465
+ // Apply legacy CODEX_MONITOR_* → OPENFLEET_* env aliases before any config ops
466
+ applyAllCompatibility();
467
+
459
468
  // Handle --help
460
469
  if (args.includes("--help") || args.includes("-h")) {
461
470
  showHelp();
@@ -510,7 +519,7 @@ async function main() {
510
519
  // Write PID file if running as daemon child
511
520
  if (
512
521
  args.includes("--daemon-child") ||
513
- process.env.CODEX_MONITOR_DAEMON === "1"
522
+ process.env.OPENFLEET_DAEMON === "1"
514
523
  ) {
515
524
  writePidFile(process.pid);
516
525
  // Redirect console to log file on daemon child
@@ -556,15 +565,15 @@ async function main() {
556
565
 
557
566
  const sentinelRequested =
558
567
  args.includes("--sentinel") ||
559
- parseBoolEnv(process.env.CODEX_MONITOR_SENTINEL_AUTO_START, false);
568
+ parseBoolEnv(process.env.OPENFLEET_SENTINEL_AUTO_START, false);
560
569
  if (sentinelRequested) {
561
570
  const sentinel = await ensureSentinelRunning({ quiet: false });
562
571
  if (!sentinel.ok) {
563
572
  const mode = args.includes("--sentinel")
564
573
  ? "requested by --sentinel"
565
- : "requested by CODEX_MONITOR_SENTINEL_AUTO_START";
574
+ : "requested by OPENFLEET_SENTINEL_AUTO_START";
566
575
  const strictSentinel = parseBoolEnv(
567
- process.env.CODEX_MONITOR_SENTINEL_STRICT,
576
+ process.env.OPENFLEET_SENTINEL_STRICT,
568
577
  false,
569
578
  );
570
579
  const prefix = strictSentinel ? "✖" : "⚠";
@@ -649,7 +658,7 @@ async function main() {
649
658
 
650
659
  // Propagate --no-auto-update to env for monitor.mjs to pick up
651
660
  if (args.includes("--no-auto-update")) {
652
- process.env.CODEX_MONITOR_SKIP_AUTO_UPDATE = "1";
661
+ process.env.OPENFLEET_SKIP_AUTO_UPDATE = "1";
653
662
  }
654
663
 
655
664
  // Mark all child processes as openfleet managed.
@@ -661,7 +670,7 @@ async function main() {
661
670
  if (args.includes("--setup") || args.includes("setup")) {
662
671
  const configDirArg = getArgValue("--config-dir");
663
672
  if (configDirArg) {
664
- process.env.CODEX_MONITOR_DIR = configDirArg;
673
+ process.env.OPENFLEET_DIR = configDirArg;
665
674
  }
666
675
  const { runSetup } = await import("./setup.mjs");
667
676
  await runSetup();
@@ -682,19 +691,36 @@ async function main() {
682
691
  console.log("\n 🚀 First run detected — launching setup wizard...\n");
683
692
  const configDirArg = getArgValue("--config-dir");
684
693
  if (configDirArg) {
685
- process.env.CODEX_MONITOR_DIR = configDirArg;
694
+ process.env.OPENFLEET_DIR = configDirArg;
686
695
  }
687
696
  const { runSetup } = await import("./setup.mjs");
688
697
  await runSetup();
689
698
  console.log("\n Setup complete! Starting openfleet...\n");
690
699
  }
691
700
 
701
+ // Legacy migration: if ~/codex-monitor exists with config, auto-migrate to ~/openfleet
702
+ const legacyInfo = detectLegacySetup();
703
+ if (legacyInfo.hasLegacy && !legacyInfo.alreadyMigrated) {
704
+ console.log(
705
+ `\n 📦 Detected legacy codex-monitor config at ${legacyInfo.legacyDir}`,
706
+ );
707
+ console.log(` Auto-migrating to ${legacyInfo.newDir}...\n`);
708
+ const result = migrateFromLegacy(legacyInfo.legacyDir, legacyInfo.newDir);
709
+ if (result.migrated.length > 0) {
710
+ console.log(` ✅ Migrated: ${result.migrated.join(", ")}`);
711
+ console.log(`\n Config is now at ${legacyInfo.newDir}\n`);
712
+ }
713
+ for (const err of result.errors) {
714
+ console.log(` ⚠️ Migration warning: ${err}`);
715
+ }
716
+ }
717
+
692
718
  // ── Handle --echo-logs: tail the active monitor's log instead of spawning a new instance ──
693
719
  if (args.includes("--echo-logs")) {
694
720
  // Search for the monitor PID file in common cache locations
695
721
  const candidatePidFiles = [
696
- process.env.CODEX_MONITOR_DIR
697
- ? resolve(process.env.CODEX_MONITOR_DIR, ".cache", "openfleet.pid")
722
+ process.env.OPENFLEET_DIR
723
+ ? resolve(process.env.OPENFLEET_DIR, ".cache", "openfleet.pid")
698
724
  : null,
699
725
  resolve(__dirname, "..", ".cache", "openfleet.pid"),
700
726
  resolve(process.cwd(), ".cache", "openfleet.pid"),
@@ -731,24 +757,40 @@ async function main() {
731
757
  `\n Tailing logs for active openfleet (PID ${monitorPid}):\n ${logFile}\n`,
732
758
  );
733
759
  await new Promise((res) => {
760
+ // Spawn tail in its own process group (detached) so that
761
+ // Ctrl+C in this terminal only kills the tailing session,
762
+ // never the running daemon.
734
763
  const tail = spawn("tail", ["-f", "-n", "200", logFile], {
735
- stdio: "inherit",
764
+ stdio: ["ignore", "inherit", "inherit"],
765
+ detached: true,
736
766
  });
737
767
  tail.on("exit", res);
738
768
  process.on("SIGINT", () => {
739
- tail.kill();
769
+ try { process.kill(-tail.pid, "SIGTERM"); } catch { tail.kill(); }
740
770
  res();
741
771
  });
742
772
  });
743
773
  process.exit(0);
774
+ } else {
775
+ console.error(
776
+ `\n No log file found for active openfleet (PID ${monitorPid}).\n Expected: ${logFile}\n`,
777
+ );
778
+ process.exit(1);
744
779
  }
745
780
  }
746
- } catch {
747
- // best-effort fall through to normal start
781
+ } catch (e) {
782
+ console.error(`\n --echo-logs: failed to read PID file — ${e.message}\n`);
783
+ process.exit(1);
748
784
  }
785
+ } else {
786
+ console.error(
787
+ "\n --echo-logs: no active openfleet found (PID file missing).\n Start openfleet first with: openfleet --daemon\n",
788
+ );
789
+ process.exit(1);
749
790
  }
750
791
 
751
- // No active monitor foundstart normally; echoLogs will be picked up by config.mjs
792
+ // Should not reach hereall paths above exit
793
+ process.exit(0);
752
794
  }
753
795
 
754
796
  // Fork monitor as a child process — enables self-restart on source changes.