@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 +33 -33
- package/agent-hook-bridge.mjs +1 -1
- package/agent-hooks.mjs +5 -5
- package/agent-prompts.mjs +4 -4
- package/cli.mjs +67 -25
- package/compat.mjs +286 -0
- package/config-doctor.mjs +1 -1
- package/config.mjs +15 -11
- package/hook-profiles.mjs +17 -17
- package/kanban-adapter.mjs +11 -11
- package/monitor.mjs +37 -7
- package/package.json +4 -2
- package/postinstall.mjs +1 -1
- package/preflight.mjs +2 -2
- package/presence.mjs +1 -1
- package/publish.mjs +13 -7
- package/setup.mjs +26 -17
- package/shared-state-manager.mjs +1 -1
- package/startup-service.mjs +2 -2
- package/task-executor.mjs +3 -3
- package/telegram-bot.mjs +136 -2
- package/telegram-sentinel.mjs +1 -1
- package/ui/demo.html +640 -0
- package/ui/modules/settings-schema.js +10 -10
- package/ui/styles/variables.css +1 -1
- package/ui-server.mjs +20 -19
- package/update-check.mjs +16 -10
- package/ve-orchestrator.ps1 +6 -6
- package/whatsapp-channel.mjs +3 -3
- package/worktree-manager.mjs +2 -2
package/.env.example
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ───
|
|
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
|
-
#
|
|
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
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
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
|
-
#
|
|
431
|
-
#
|
|
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
|
-
#
|
|
433
|
+
# OPENFLEET_DAEMON_INSTANT_CRASH_WINDOW_MS=15000
|
|
434
434
|
# Stop auto-restarts after this many instant failures in a row (default: 3)
|
|
435
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
547
|
+
# OPENFLEET_HOOK_PROFILE=strict
|
|
548
548
|
#
|
|
549
549
|
# Which agents should receive generated hook files (comma-separated):
|
|
550
550
|
# codex,claude,copilot
|
|
551
|
-
#
|
|
551
|
+
# OPENFLEET_HOOK_TARGETS=codex,claude,copilot
|
|
552
552
|
#
|
|
553
553
|
# Set to false to skip hook scaffolding during setup.
|
|
554
|
-
#
|
|
554
|
+
# OPENFLEET_HOOKS_ENABLED=true
|
|
555
555
|
# Set to true to overwrite existing generated hook files.
|
|
556
|
-
#
|
|
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
|
-
#
|
|
561
|
-
#
|
|
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
|
-
#
|
|
566
|
-
#
|
|
567
|
-
#
|
|
568
|
-
#
|
|
569
|
-
#
|
|
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
|
-
#
|
|
574
|
-
#
|
|
575
|
-
#
|
|
576
|
-
#
|
|
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
|
-
#
|
|
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=
|
|
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
|
-
#
|
|
820
|
-
#
|
|
821
|
-
#
|
|
822
|
-
#
|
|
823
|
-
#
|
|
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
|
-
#
|
|
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
|
package/agent-hook-bridge.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
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("
|
|
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("
|
|
641
|
-
envFlag("
|
|
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("
|
|
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: `
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
384
|
-
|
|
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.
|
|
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.
|
|
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
|
|
574
|
+
: "requested by OPENFLEET_SENTINEL_AUTO_START";
|
|
566
575
|
const strictSentinel = parseBoolEnv(
|
|
567
|
-
process.env.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
697
|
-
? resolve(process.env.
|
|
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
|
-
|
|
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
|
-
//
|
|
792
|
+
// Should not reach here — all 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.
|