@virtengine/openfleet 0.26.2 → 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].
@@ -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 ─────────────────────────────────────────────────────────────
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);
@@ -381,7 +386,8 @@ function startDaemon() {
381
386
  stdio: "ignore",
382
387
  windowsHide: process.platform === "win32",
383
388
  env: { ...process.env, OPENFLEET_DAEMON: "1" },
384
- cwd: process.cwd(),
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();
@@ -689,6 +698,23 @@ async function main() {
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
@@ -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.
package/compat.mjs ADDED
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * compat.mjs — Backward compatibility for users migrating from codex-monitor
4
+ *
5
+ * Handles:
6
+ * 1. Legacy env var aliasing: CODEX_MONITOR_X → OPENFLEET_X
7
+ * 2. Legacy config dir detection: ~/codex-monitor or $CODEX_MONITOR_DIR
8
+ * 3. Migration: copy old config dir to new openfleet dir
9
+ *
10
+ * This module is intentionally dependency-free (only Node built-ins) and
11
+ * should be imported as early as possible in the startup path.
12
+ */
13
+
14
+ import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
15
+ import { resolve, join } from "node:path";
16
+
17
+ // ── Legacy config file names accepted from old codex-monitor installations ───
18
+ const LEGACY_CONFIG_NAMES = [
19
+ "codex-monitor.config.json",
20
+ "openfleet.config.json",
21
+ ".openfleet.json",
22
+ "openfleet.json",
23
+ ".env",
24
+ ];
25
+
26
+ /**
27
+ * Env vars the old codex-monitor package used that have been renamed.
28
+ * Format: [oldName, newName]
29
+ * Rule: any CODEX_MONITOR_X maps to OPENFLEET_X by default, but some had
30
+ * different names entirely — those are listed explicitly below.
31
+ */
32
+ const EXPLICIT_ALIASES = [
33
+ // Dir / location
34
+ ["CODEX_MONITOR_DIR", "OPENFLEET_DIR"],
35
+ ["CODEX_MONITOR_HOME", "OPENFLEET_HOME"],
36
+ // Runtime flags
37
+ ["CODEX_MONITOR_SKIP_AUTO_UPDATE", "OPENFLEET_SKIP_AUTO_UPDATE"],
38
+ ["CODEX_MONITOR_SKIP_UPDATE_CHECK", "OPENFLEET_SKIP_UPDATE_CHECK"],
39
+ ["CODEX_MONITOR_UPDATE_INTERVAL_MS", "OPENFLEET_UPDATE_INTERVAL_MS"],
40
+ ["CODEX_MONITOR_SKIP_POSTINSTALL", "OPENFLEET_SKIP_POSTINSTALL"],
41
+ ["CODEX_MONITOR_INTERACTIVE", "OPENFLEET_INTERACTIVE"],
42
+ ["CODEX_MONITOR_PREFLIGHT_DISABLED", "OPENFLEET_PREFLIGHT_DISABLED"],
43
+ ["CODEX_MONITOR_PREFLIGHT_RETRY_MS", "OPENFLEET_PREFLIGHT_RETRY_MS"],
44
+ ["CODEX_MONITOR_MIN_FREE_GB", "OPENFLEET_MIN_FREE_GB"],
45
+ ["CODEX_MONITOR_INSTANCE_ID", "OPENFLEET_INSTANCE_ID"],
46
+ ["CODEX_MONITOR_MODE", "OPENFLEET_MODE"],
47
+ ["CODEX_MONITOR_SHELL", "OPENFLEET_SHELL"],
48
+ ["CODEX_MONITOR_SHELL_MODE", "OPENFLEET_SHELL_MODE"],
49
+ ["CODEX_MONITOR_PROFILE", "OPENFLEET_PROFILE"],
50
+ ["CODEX_MONITOR_ENV_PROFILE", "OPENFLEET_ENV_PROFILE"],
51
+ // Task
52
+ ["CODEX_MONITOR_TASK_LABEL", "OPENFLEET_TASK_LABEL"],
53
+ ["CODEX_MONITOR_TASK_LABELS", "OPENFLEET_TASK_LABELS"],
54
+ ["CODEX_MONITOR_ENFORCE_TASK_LABEL", "OPENFLEET_ENFORCE_TASK_LABEL"],
55
+ ["CODEX_MONITOR_TASK_UPSTREAM", "OPENFLEET_TASK_UPSTREAM"],
56
+ // Daemon
57
+ ["CODEX_MONITOR_DAEMON", "OPENFLEET_DAEMON"],
58
+ ["CODEX_MONITOR_DAEMON_RESTART_DELAY_MS", "OPENFLEET_DAEMON_RESTART_DELAY_MS"],
59
+ ["CODEX_MONITOR_DAEMON_MAX_RESTARTS", "OPENFLEET_DAEMON_MAX_RESTARTS"],
60
+ ["CODEX_MONITOR_DAEMON_INSTANT_CRASH_WINDOW_MS", "OPENFLEET_DAEMON_INSTANT_CRASH_WINDOW_MS"],
61
+ ["CODEX_MONITOR_DAEMON_MAX_INSTANT_RESTARTS", "OPENFLEET_DAEMON_MAX_INSTANT_RESTARTS"],
62
+ // Hooks
63
+ ["CODEX_MONITOR_HOOK_PROFILE", "OPENFLEET_HOOK_PROFILE"],
64
+ ["CODEX_MONITOR_HOOK_TARGETS", "OPENFLEET_HOOK_TARGETS"],
65
+ ["CODEX_MONITOR_HOOKS_ENABLED", "OPENFLEET_HOOKS_ENABLED"],
66
+ ["CODEX_MONITOR_HOOKS_OVERWRITE", "OPENFLEET_HOOKS_OVERWRITE"],
67
+ ["CODEX_MONITOR_HOOK_NODE_BIN", "OPENFLEET_HOOK_NODE_BIN"],
68
+ ["CODEX_MONITOR_HOOK_BRIDGE_PATH", "OPENFLEET_HOOK_BRIDGE_PATH"],
69
+ ["CODEX_MONITOR_HOOK_PREPUSH", "OPENFLEET_HOOK_PREPUSH"],
70
+ ["CODEX_MONITOR_HOOK_PRECOMMIT", "OPENFLEET_HOOK_PRECOMMIT"],
71
+ ["CODEX_MONITOR_HOOK_TASK_COMPLETE", "OPENFLEET_HOOK_TASK_COMPLETE"],
72
+ ["CODEX_MONITOR_HOOKS_BUILTINS_MODE", "OPENFLEET_HOOKS_BUILTINS_MODE"],
73
+ ["CODEX_MONITOR_HOOKS_FORCE", "OPENFLEET_HOOKS_FORCE"],
74
+ // Sentinel
75
+ ["CODEX_MONITOR_SENTINEL_AUTO_START", "OPENFLEET_SENTINEL_AUTO_START"],
76
+ ["CODEX_MONITOR_SENTINEL_STRICT", "OPENFLEET_SENTINEL_STRICT"],
77
+ // Prompts
78
+ ["CODEX_MONITOR_PROMPT_WORKSPACE", "OPENFLEET_PROMPT_WORKSPACE"],
79
+ // Repo
80
+ ["CODEX_MONITOR_REPO", "OPENFLEET_REPO"],
81
+ ["CODEX_MONITOR_REPO_NAME", "OPENFLEET_REPO_NAME"],
82
+ ["CODEX_MONITOR_REPO_ROOT", "OPENFLEET_REPO_ROOT"],
83
+ // Config path
84
+ ["CODEX_MONITOR_CONFIG_PATH", "OPENFLEET_CONFIG_PATH"],
85
+ ];
86
+
87
+ /**
88
+ * Apply all legacy CODEX_MONITOR_* → OPENFLEET_* env var aliases.
89
+ * Also does a generic pass: any remaining CODEX_MONITOR_X that wasn't in the
90
+ * explicit list is aliased to OPENFLEET_X.
91
+ *
92
+ * Existing OPENFLEET_* values are never overwritten (OPENFLEET_ wins).
93
+ * Safe to call multiple times (idempotent).
94
+ */
95
+ export function applyLegacyEnvAliases() {
96
+ // Explicit known renames
97
+ for (const [oldKey, newKey] of EXPLICIT_ALIASES) {
98
+ if (process.env[oldKey] !== undefined && process.env[newKey] === undefined) {
99
+ process.env[newKey] = process.env[oldKey];
100
+ }
101
+ }
102
+
103
+ // Generic pass: CODEX_MONITOR_X → OPENFLEET_X for anything not already mapped
104
+ for (const key of Object.keys(process.env)) {
105
+ if (!key.startsWith("CODEX_MONITOR_")) continue;
106
+ const suffix = key.slice("CODEX_MONITOR_".length);
107
+ const newKey = `OPENFLEET_${suffix}`;
108
+ if (process.env[newKey] === undefined) {
109
+ process.env[newKey] = process.env[key];
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Returns the legacy codex-monitor config directory if it exists and
116
+ * contains config files, null otherwise.
117
+ *
118
+ * Checks in priority order:
119
+ * 1. $CODEX_MONITOR_DIR env var
120
+ * 2. ~/codex-monitor
121
+ * 3. ~/.codex-monitor
122
+ * 4. ~/.config/codex-monitor
123
+ */
124
+ export function getLegacyConfigDir() {
125
+ const home =
126
+ process.env.HOME ||
127
+ process.env.USERPROFILE ||
128
+ process.env.APPDATA ||
129
+ null;
130
+
131
+ const candidates = [
132
+ process.env.CODEX_MONITOR_DIR || null,
133
+ home ? join(home, "codex-monitor") : null,
134
+ home ? join(home, ".codex-monitor") : null,
135
+ home ? join(home, ".config", "codex-monitor") : null,
136
+ ].filter(Boolean);
137
+
138
+ for (const dir of candidates) {
139
+ if (existsSync(dir) && hasLegacyMarkers(dir)) {
140
+ return dir;
141
+ }
142
+ }
143
+ return null;
144
+ }
145
+
146
+ function hasLegacyMarkers(dir) {
147
+ return LEGACY_CONFIG_NAMES.some((name) => existsSync(join(dir, name)));
148
+ }
149
+
150
+ /**
151
+ * Returns the target openfleet config directory (where we'd migrate to).
152
+ * Does NOT create the directory.
153
+ */
154
+ export function getNewConfigDir() {
155
+ const home =
156
+ process.env.HOME ||
157
+ process.env.USERPROFILE ||
158
+ process.env.APPDATA ||
159
+ process.cwd();
160
+
161
+ return (
162
+ process.env.OPENFLEET_DIR ||
163
+ join(home, "openfleet")
164
+ );
165
+ }
166
+
167
+ /**
168
+ * Detect if the user has a legacy codex-monitor setup but no openfleet setup.
169
+ * Returns { hasLegacy, legacyDir, newDir, alreadyMigrated }
170
+ */
171
+ export function detectLegacySetup() {
172
+ const legacyDir = getLegacyConfigDir();
173
+ const newDir = getNewConfigDir();
174
+ const newHasConfig = hasLegacyMarkers(newDir);
175
+
176
+ return {
177
+ hasLegacy: legacyDir !== null,
178
+ legacyDir,
179
+ newDir,
180
+ alreadyMigrated: newHasConfig,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Migrate config files from old codex-monitor dir to new openfleet dir.
186
+ * Only copies; never deletes the old directory.
187
+ *
188
+ * Files copied:
189
+ * .env → .env (with CODEX_MONITOR_ → OPENFLEET_ substitution)
190
+ * openfleet.config.json → openfleet.config.json
191
+ * codex-monitor.config.json → openfleet.config.json (rename)
192
+ * .openfleet.json / openfleet.json → as-is
193
+ *
194
+ * Returns { migrated: string[], skipped: string[], errors: string[] }
195
+ */
196
+ export function migrateFromLegacy(legacyDir, newDir, { overwrite = false } = {}) {
197
+ const result = { migrated: [], skipped: [], errors: [] };
198
+
199
+ try {
200
+ mkdirSync(newDir, { recursive: true });
201
+ } catch (e) {
202
+ result.errors.push(`Could not create ${newDir}: ${e.message}`);
203
+ return result;
204
+ }
205
+
206
+ // Files to migrate: [source name, dest name]
207
+ const filePairs = [
208
+ [".env", ".env"],
209
+ ["openfleet.config.json", "openfleet.config.json"],
210
+ ["codex-monitor.config.json","openfleet.config.json"],
211
+ [".openfleet.json", ".openfleet.json"],
212
+ ["openfleet.json", "openfleet.json"],
213
+ ];
214
+
215
+ for (const [srcName, destName] of filePairs) {
216
+ const src = join(legacyDir, srcName);
217
+ const dest = join(newDir, destName);
218
+
219
+ if (!existsSync(src)) continue;
220
+ if (existsSync(dest) && !overwrite) {
221
+ result.skipped.push(destName);
222
+ continue;
223
+ }
224
+
225
+ try {
226
+ if (srcName === ".env") {
227
+ // Rewrite CODEX_MONITOR_ prefixes to OPENFLEET_ in .env content
228
+ const content = readFileSync(src, "utf8");
229
+ const updated = rewriteEnvContent(content);
230
+ writeFileSync(dest, updated, "utf8");
231
+ } else {
232
+ copyFileSync(src, dest);
233
+ }
234
+ result.migrated.push(destName);
235
+ } catch (e) {
236
+ result.errors.push(`${destName}: ${e.message}`);
237
+ }
238
+ }
239
+
240
+ return result;
241
+ }
242
+
243
+ /**
244
+ * Rewrite .env file content: replace CODEX_MONITOR_X= with OPENFLEET_X=
245
+ * while preserving comments and blank lines.
246
+ */
247
+ function rewriteEnvContent(content) {
248
+ return content
249
+ .split("\n")
250
+ .map((line) => {
251
+ // Match both active vars and commented-out vars
252
+ return line.replace(/^(#?\s*)CODEX_MONITOR_/, "$1OPENFLEET_");
253
+ })
254
+ .join("\n");
255
+ }
256
+
257
+ /**
258
+ * If OPENFLEET_DIR is not set but CODEX_MONITOR_DIR is (or legacy dir exists),
259
+ * transparently set OPENFLEET_DIR to point at the legacy dir so openfleet reads
260
+ * from it without requiring a migration step.
261
+ *
262
+ * This is the "zero-friction" path: existing users just upgrade the package and
263
+ * it works. Migration is optional (improves going forward).
264
+ */
265
+ export function autoApplyLegacyDir() {
266
+ // Already set — nothing to do
267
+ if (process.env.OPENFLEET_DIR) return false;
268
+
269
+ const legacyDir = getLegacyConfigDir();
270
+ if (!legacyDir) return false;
271
+
272
+ process.env.OPENFLEET_DIR = legacyDir;
273
+ console.log(
274
+ `[compat] Legacy codex-monitor config detected at ${legacyDir} — using it as OPENFLEET_DIR.`,
275
+ );
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * Run full compatibility setup: alias env vars + auto-apply legacy dir.
281
+ * Call this before loadConfig().
282
+ */
283
+ export function applyAllCompatibility() {
284
+ applyLegacyEnvAliases();
285
+ autoApplyLegacyDir();
286
+ }
package/config.mjs CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  getAgentPromptDefinitions,
24
24
  resolveAgentPrompts,
25
25
  } from "./agent-prompts.mjs";
26
+ import { applyAllCompatibility } from "./compat.mjs";
26
27
 
27
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
29
 
@@ -719,6 +720,9 @@ function loadAgentPrompts(configDir, repoRoot, configData) {
719
720
  * Returns a frozen config object used by all modules.
720
721
  */
721
722
  export function loadConfig(argv = process.argv, options = {}) {
723
+ // Apply legacy CODEX_MONITOR_* → OPENFLEET_* compatibility before anything else
724
+ applyAllCompatibility();
725
+
722
726
  const { reloadEnv = false } = options;
723
727
  const cli = parseArgs(argv);
724
728
 
@@ -2581,7 +2581,7 @@ class GitHubIssuesAdapter {
2581
2581
  const commentBody = `<!-- openfleet-state
2582
2582
  ${stateJson}
2583
2583
  -->
2584
- **Codex Monitor Status**: Agent \`${agentId}\` on \`${workstationId}\` is ${normalizedState.status === "working" ? "working on" : normalizedState.status === "claimed" ? "claiming" : "stale for"} this task.
2584
+ **OpenFleet Status**: Agent \`${agentId}\` on \`${workstationId}\` is ${normalizedState.status === "working" ? "working on" : normalizedState.status === "claimed" ? "claiming" : "stale for"} this task.
2585
2585
  *Last heartbeat: ${normalizedState.heartbeat || normalizedState.ownerHeartbeat}*`;
2586
2586
 
2587
2587
  if (stateCommentIndex >= 0) {
@@ -2709,7 +2709,7 @@ ${stateJson}
2709
2709
  );
2710
2710
 
2711
2711
  // Add comment explaining why
2712
- const commentBody = `**Codex Monitor**: This task has been marked as ignored.
2712
+ const commentBody = `**OpenFleet**: This task has been marked as ignored.
2713
2713
 
2714
2714
  **Reason**: ${reason}
2715
2715
 
@@ -3581,7 +3581,7 @@ class JiraAdapter {
3581
3581
  const json = JSON.stringify(normalized, null, 2);
3582
3582
  return (
3583
3583
  `<!-- openfleet-state\n${json}\n-->\n` +
3584
- `Codex Monitor Status: Agent ${agentId} on ${workstationId} is ${normalized?.status} this task.\n` +
3584
+ `OpenFleet Status: Agent ${agentId} on ${workstationId} is ${normalized?.status} this task.\n` +
3585
3585
  `Last heartbeat: ${normalized?.heartbeat || normalized?.ownerHeartbeat || ""}`
3586
3586
  );
3587
3587
  }
@@ -4245,7 +4245,7 @@ class JiraAdapter {
4245
4245
  * {
4246
4246
  * "type": "paragraph",
4247
4247
  * "content": [
4248
- * {"type": "text", "text": "Codex Monitor: Task marked as ignored."}
4248
+ * {"type": "text", "text": "OpenFleet: Task marked as ignored."}
4249
4249
  * ]
4250
4250
  * }
4251
4251
  * ]
@@ -4294,7 +4294,7 @@ class JiraAdapter {
4294
4294
  });
4295
4295
  }
4296
4296
  const commentBody =
4297
- `Codex Monitor: This task has been marked as ignored.\n\n` +
4297
+ `OpenFleet: This task has been marked as ignored.\n\n` +
4298
4298
  `Reason: ${ignoreReason}\n\n` +
4299
4299
  `To re-enable openfleet for this task, remove the ${this._codexLabels.ignore} label.`;
4300
4300
  await this.addComment(key, commentBody);
package/monitor.mjs CHANGED
@@ -46,6 +46,8 @@ import {
46
46
  getDigestSnapshot,
47
47
  startStatusFileWriter,
48
48
  stopStatusFileWriter,
49
+ initStatusBoard,
50
+ pushStatusBoardUpdate,
49
51
  } from "./telegram-bot.mjs";
50
52
  import { PRCleanupDaemon } from "./pr-cleanup-daemon.mjs";
51
53
  import {
@@ -1739,6 +1741,11 @@ function notifyVkError(line) {
1739
1741
  if (!isVkBackendActive()) {
1740
1742
  return;
1741
1743
  }
1744
+ // If the user explicitly disabled VK spawning they know VK isn't running —
1745
+ // spamming "unreachable" every 10 minutes is pure noise.
1746
+ if (!vkSpawnEnabled) {
1747
+ return;
1748
+ }
1742
1749
  const key = "vibe-kanban-unavailable";
1743
1750
  const now = Date.now();
1744
1751
  const last = vkErrorNotified.get(key) || 0;
@@ -3401,6 +3408,14 @@ async function fetchTasksByStatus(status) {
3401
3408
  const backend = getActiveKanbanBackend();
3402
3409
  if (backend !== "vk") {
3403
3410
  try {
3411
+ // Internal backend uses file-based storage — no project ID required.
3412
+ if (backend === "internal") {
3413
+ const tasks = status
3414
+ ? getInternalTasksByStatus(status)
3415
+ : getAllInternalTasks();
3416
+ return Array.isArray(tasks) ? tasks : [];
3417
+ }
3418
+
3404
3419
  const projectId = getConfiguredKanbanProjectId(backend);
3405
3420
  if (!projectId) {
3406
3421
  console.warn(
@@ -7796,6 +7811,10 @@ async function sendTelegramMessage(text, options = {}) {
7796
7811
  }
7797
7812
  }
7798
7813
 
7814
+ // Allow caller to explicitly override the computed priority (e.g. auto-update
7815
+ // restart notifications should always arrive as direct messages, not digest).
7816
+ if (options.priority !== undefined) priority = Number(options.priority);
7817
+
7799
7818
  // Route through batching system — apply verbosity filter first.
7800
7819
  // minimal: only priority 1-2 (critical + error)
7801
7820
  // summary: priority 1-4 (everything except debug) — DEFAULT
@@ -8278,10 +8297,17 @@ async function startTelegramNotifier() {
8278
8297
  const sendUpdate = async () => {
8279
8298
  const summary = await readStatusSummary();
8280
8299
  if (summary && summary.text) {
8281
- await sendTelegramMessage(summary.text, {
8300
+ // Push to the pinned status board (edits in-place) when available.
8301
+ // Fall back to a regular new message only if the board hasn't been set up.
8302
+ const routed = pushStatusBoardUpdate(summary.text, {
8282
8303
  parseMode: summary.parseMode,
8283
- disablePreview: true,
8284
8304
  });
8305
+ if (!routed) {
8306
+ await sendTelegramMessage(summary.text, {
8307
+ parseMode: summary.parseMode,
8308
+ disablePreview: true,
8309
+ });
8310
+ }
8285
8311
  }
8286
8312
  await flushMergeNotifications();
8287
8313
  await checkStatusMilestones();
@@ -11466,7 +11492,9 @@ startAutoUpdateLoop({
11466
11492
  onRestart: (reason) => restartSelf(reason),
11467
11493
  onNotify: (msg) => {
11468
11494
  try {
11469
- void sendTelegramMessage(msg);
11495
+ // Priority 1 (critical) bypasses the live digest so the user gets a
11496
+ // direct push notification for update-detected and restarting events.
11497
+ void sendTelegramMessage(msg, { priority: 1, skipDedup: true });
11470
11498
  } catch {
11471
11499
  /* best-effort */
11472
11500
  }
@@ -12019,9 +12047,11 @@ if (telegramCommandEnabled) {
12019
12047
  // Restore live digest state BEFORE any messages flow — so restarts continue the
12020
12048
  // existing digest message instead of creating a new one.
12021
12049
  // Chain notifier start after restore to prevent race conditions.
12050
+ // Also initialise the pinned status board (creates/restores the persistent message).
12022
12051
  void restoreLiveDigest()
12023
12052
  .catch(() => {})
12024
- .then(() => startTelegramNotifier());
12053
+ .then(() => startTelegramNotifier())
12054
+ .then(() => initStatusBoard().catch(() => {}));
12025
12055
 
12026
12056
  // ── Start long-running devmode monitor-monitor supervisor ───────────────────
12027
12057
  startMonitorMonitorSupervisor();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virtengine/openfleet",
3
- "version": "0.26.2",
3
+ "version": "0.26.3",
4
4
  "description": "AI-powered orchestrator supervisor — manages AI agent executors with failover, auto-restarts on failure, analyzes crashes with Codex SDK, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
5
5
  "type": "module",
6
6
  "license": "Apache 2.0",
@@ -62,7 +62,8 @@
62
62
  "./startup-service": "./startup-service.mjs",
63
63
  "./telegram-sentinel": "./telegram-sentinel.mjs",
64
64
  "./whatsapp-channel": "./whatsapp-channel.mjs",
65
- "./container-runner": "./container-runner.mjs"
65
+ "./container-runner": "./container-runner.mjs",
66
+ "./compat": "./compat.mjs"
66
67
  },
67
68
  "bin": {
68
69
  "openfleet": "cli.mjs",
@@ -110,6 +111,7 @@
110
111
  "autofix.mjs",
111
112
  "claude-shell.mjs",
112
113
  "cli.mjs",
114
+ "compat.mjs",
113
115
  "codex-config.mjs",
114
116
  "openfleet.config.example.json",
115
117
  "openfleet.schema.json",
package/presence.mjs CHANGED
@@ -286,7 +286,7 @@ export function formatPresenceSummary({ nowMs, ttlMs } = {}) {
286
286
  return "No active instances reported.";
287
287
  }
288
288
  const coordinator = selectCoordinator({ nowMs, ttlMs });
289
- const lines = ["🛰️ Codex Monitor Presence"];
289
+ const lines = ["🛰️ OpenFleet Presence"];
290
290
  for (const entry of active) {
291
291
  const name = entry.instance_label || entry.instance_id;
292
292
  const role = entry.workspace_role || "workspace";
package/publish.mjs CHANGED
@@ -225,9 +225,9 @@ function main() {
225
225
  if (status === 0 && !dryRun) {
226
226
  console.log(
227
227
  "\n[publish] REMINDER: deprecate the legacy npm package to redirect users:\n" +
228
- " npm deprecate codex-monitor@'*' \"Renamed to @virtengine/openfleet. Install: npm install -g @virtengine/openfleet\"\n" +
228
+ " npm deprecate openfleet@'*' \"Renamed to @virtengine/openfleet. Install: npm install -g @virtengine/openfleet\"\n" +
229
229
  " # If a scoped legacy package exists:\n" +
230
- " npm deprecate @virtengine/codex-monitor@'*' \"Renamed to @virtengine/openfleet. Install: npm install -g @virtengine/openfleet\"\n",
230
+ " npm deprecate @virtengine/openfleet@'*' \"Renamed to @virtengine/openfleet. Install: npm install -g @virtengine/openfleet\"\n",
231
231
  );
232
232
  }
233
233
  process.exit(status);
package/setup.mjs CHANGED
@@ -44,6 +44,7 @@ import {
44
44
  normalizeHookTargets,
45
45
  scaffoldAgentHookFiles,
46
46
  } from "./hook-profiles.mjs";
47
+ import { detectLegacySetup, applyAllCompatibility } from "./compat.mjs";
47
48
 
48
49
  const __dirname = dirname(fileURLToPath(import.meta.url));
49
50
 
@@ -3904,6 +3905,14 @@ async function writeConfigFiles({ env, configJson, repoRoot, configDir }) {
3904
3905
  * Called from monitor.mjs before starting the main loop.
3905
3906
  */
3906
3907
  export function shouldRunSetup() {
3908
+ // Apply legacy compat so OPENFLEET_DIR is set before resolveConfigDir is called
3909
+ applyAllCompatibility();
3910
+
3911
+ // If a legacy codex-monitor setup exists and the user hasn't migrated yet,
3912
+ // skip the setup wizard — they are already configured.
3913
+ const legacyInfo = detectLegacySetup();
3914
+ if (legacyInfo.hasLegacy) return false;
3915
+
3907
3916
  const repoRoot = detectRepoRoot();
3908
3917
  const configDir = resolveConfigDir(repoRoot);
3909
3918
  return !hasSetupMarkers(configDir);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Shared State Manager for Codex Monitor Task Coordination
2
+ * @fileoverview Shared State Manager for OpenFleet Task Coordination
3
3
  *
4
4
  * Manages distributed task execution state across multiple agents and workstations.
5
5
  * Provides atomic operations for claiming, updating, and releasing task ownership
@@ -32,7 +32,7 @@ import { fileURLToPath } from "node:url";
32
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
33
33
 
34
34
  const SERVICE_LABEL = "com.openfleet.service";
35
- const TASK_NAME = "CodexMonitor";
35
+ const TASK_NAME = "OpenFleet";
36
36
  const SYSTEMD_UNIT = "openfleet.service";
37
37
 
38
38
  // ── Platform Detection ───────────────────────────────────────────────────────
@@ -121,7 +121,7 @@ function generateTaskSchedulerXml({ daemon = true } = {}) {
121
121
  <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
122
122
  <RegistrationInfo>
123
123
  <Description>Auto-start openfleet AI orchestrator on login</Description>
124
- <Author>Codex Monitor</Author>
124
+ <Author>OpenFleet</Author>
125
125
  <URI>\\${TASK_NAME}</URI>
126
126
  </RegistrationInfo>
127
127
  <Triggers>