omnius 1.0.598 → 1.0.599

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/dist/index.js CHANGED
@@ -678083,6 +678083,7 @@ __export(daemon_exports, {
678083
678083
  getDaemonStatus: () => getDaemonStatus,
678084
678084
  getLocalCliVersion: () => getLocalCliVersion,
678085
678085
  isDaemonRunning: () => isDaemonRunning,
678086
+ reclaimOwnedDaemonListener: () => reclaimOwnedDaemonListener,
678086
678087
  recoverDaemonVersionBoundedly: () => recoverDaemonVersionBoundedly,
678087
678088
  releaseDaemonEndpointClaim: () => releaseDaemonEndpointClaim,
678088
678089
  releaseDaemonEndpointForCurrentProcess: () => releaseDaemonEndpointForCurrentProcess,
@@ -678094,6 +678095,7 @@ import { spawn as spawn28 } from "node:child_process";
678094
678095
  import { existsSync as existsSync116, readFileSync as readFileSync94, writeFileSync as writeFileSync60, mkdirSync as mkdirSync69, unlinkSync as unlinkSync22, openSync as openSync4, closeSync as closeSync4, writeSync as writeSync3, statSync as statSync46, renameSync as renameSync17 } from "node:fs";
678095
678096
  import { join as join128 } from "node:path";
678096
678097
  import { homedir as homedir39 } from "node:os";
678098
+ import { createServer as createNetServer } from "node:net";
678097
678099
  import { fileURLToPath as fileURLToPath18 } from "node:url";
678098
678100
  import { dirname as dirname42 } from "node:path";
678099
678101
  function getDaemonPort() {
@@ -678353,55 +678355,105 @@ ${fuser.stderr ?? ""}`;
678353
678355
  return null;
678354
678356
  }
678355
678357
  }
678356
- function processCommandLine(pid) {
678357
- try {
678358
- return readFileSync94(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
678359
- } catch {
678360
- return "";
678361
- }
678362
- }
678363
- function isOwnedOmniusDaemon(pid, daemonPid) {
678364
- if (pid === daemonPid) return true;
678365
- if (listProcessLeases({ includeInactive: false }).some((lease) => lease.pid === pid && lease.ownerKind === "daemon")) {
678366
- return true;
678367
- }
678368
- const command = processCommandLine(pid);
678369
- return /(?:^|[\\/\s])omnius(?:[\\/\s]|$)|omnius-daemon|[\\/]dist[\\/]index\.js\b|launcher\.cjs\b/i.test(command);
678370
- }
678371
- async function ownedDaemonPids(port) {
678372
- const daemonPid = getDaemonPid();
678373
- const candidates = /* @__PURE__ */ new Set();
678374
- if (daemonPid) candidates.add(daemonPid);
678375
- const holders = await portHolderPids(port);
678376
- for (const pid of holders ?? []) candidates.add(pid);
678377
- return [...candidates].filter((pid) => processIsAlive(pid) && isOwnedOmniusDaemon(pid, daemonPid));
678358
+ async function daemonPortIsFree(port) {
678359
+ return new Promise((resolve87) => {
678360
+ const probe = createNetServer();
678361
+ let settled = false;
678362
+ const finish = (free) => {
678363
+ if (settled) return;
678364
+ settled = true;
678365
+ resolve87(free);
678366
+ };
678367
+ probe.once("error", () => finish(false));
678368
+ probe.once("listening", () => {
678369
+ probe.close(() => finish(true));
678370
+ });
678371
+ probe.listen(port, "127.0.0.1");
678372
+ probe.unref();
678373
+ });
678378
678374
  }
678379
678375
  async function waitForDaemonStopped(port, attempts = DAEMON_GRACEFUL_STOP_ATTEMPTS) {
678380
678376
  for (let attempt = 0; attempt < attempts; attempt++) {
678381
678377
  const healthy = await isDaemonRunning(port);
678382
678378
  const holders = await portHolderPids(port);
678383
- if (!healthy && (holders === null || holders.length === 0)) return true;
678379
+ const portFree = holders === null ? await daemonPortIsFree(port) : holders.length === 0;
678380
+ if (!healthy && portFree) return true;
678384
678381
  await delay3(500);
678385
678382
  }
678386
678383
  return false;
678387
678384
  }
678388
- async function gracefullyStopOwnedDaemon(port) {
678389
- const pids = await ownedDaemonPids(port);
678390
- for (const pid of pids) {
678391
- try {
678392
- process.kill(pid, "SIGTERM");
678393
- } catch {
678385
+ async function reclaimOwnedDaemonListener(port = getDaemonPort(), dependencies = DEFAULT_RECLAIM_DEPENDENCIES) {
678386
+ const holders = await dependencies.holderPids(port);
678387
+ if (holders === null) {
678388
+ if (await dependencies.portIsFree(port)) {
678389
+ return { ok: true, action: "free", port, clearedPids: [], blockedPids: [] };
678394
678390
  }
678391
+ return {
678392
+ ok: false,
678393
+ action: "blocked",
678394
+ port,
678395
+ clearedPids: [],
678396
+ blockedPids: [],
678397
+ reason: "Could not identify the process holding the daemon port"
678398
+ };
678395
678399
  }
678396
- if (await waitForDaemonStopped(port)) return true;
678397
- const survivors = await ownedDaemonPids(port);
678398
- for (const pid of survivors) {
678399
- try {
678400
- process.kill(pid, "SIGKILL");
678401
- } catch {
678400
+ const uniqueHolders = [...new Set(holders.filter((pid) => pid > 0 && pid !== process.pid))];
678401
+ if (uniqueHolders.length === 0) {
678402
+ if (await dependencies.portIsFree(port)) {
678403
+ return { ok: true, action: "free", port, clearedPids: [], blockedPids: [] };
678404
+ }
678405
+ return {
678406
+ ok: false,
678407
+ action: "blocked",
678408
+ port,
678409
+ clearedPids: [],
678410
+ blockedPids: [],
678411
+ reason: "Daemon port is occupied but its holder could not be verified"
678412
+ };
678413
+ }
678414
+ const ownerId = `daemon:${port}`;
678415
+ const activeLeases = dependencies.leases().filter(
678416
+ (lease) => lease.status === "active" && lease.ownerKind === "daemon" && lease.ownerId === ownerId && uniqueHolders.includes(lease.pid)
678417
+ );
678418
+ const leasesByPid = new Map(activeLeases.map((lease) => [lease.pid, lease]));
678419
+ const blockedPids = uniqueHolders.filter((pid) => !leasesByPid.has(pid));
678420
+ if (blockedPids.length > 0) {
678421
+ return {
678422
+ ok: false,
678423
+ action: "blocked",
678424
+ port,
678425
+ clearedPids: [],
678426
+ blockedPids,
678427
+ reason: "Port holder is not an identity-verified Omnius daemon lease"
678428
+ };
678429
+ }
678430
+ const clearedPids = [];
678431
+ for (const pid of uniqueHolders) {
678432
+ const lease = leasesByPid.get(pid);
678433
+ const stopped = await dependencies.stopLease(lease.leaseId);
678434
+ if (stopped.action !== "killed" && stopped.action !== "dead") {
678435
+ return {
678436
+ ok: false,
678437
+ action: "failed",
678438
+ port,
678439
+ clearedPids,
678440
+ blockedPids: [pid],
678441
+ reason: stopped.reason || "Daemon lease identity could not be verified"
678442
+ };
678402
678443
  }
678444
+ clearedPids.push(pid);
678403
678445
  }
678404
- return waitForDaemonStopped(port, 10);
678446
+ if (!await dependencies.waitForFree(port)) {
678447
+ return {
678448
+ ok: false,
678449
+ action: "failed",
678450
+ port,
678451
+ clearedPids,
678452
+ blockedPids: [],
678453
+ reason: "Verified daemon stopped but the port did not become free"
678454
+ };
678455
+ }
678456
+ return { ok: true, action: "cleared", port, clearedPids, blockedPids: [] };
678405
678457
  }
678406
678458
  async function restartDaemon(port, expectedVersion) {
678407
678459
  const p2 = port ?? getDaemonPort();
@@ -678417,9 +678469,15 @@ async function restartDaemon(port, expectedVersion) {
678417
678469
  if (started.ok && (await waitForDaemonReady(p2, expectedVersion)).ok) return true;
678418
678470
  }
678419
678471
  await runUserSystemctl(["stop", "omnius-daemon.service"]);
678420
- if (!await waitForDaemonStopped(p2)) return false;
678472
+ if (!await waitForDaemonStopped(p2)) {
678473
+ const reclaimed = await reclaimOwnedDaemonListener(p2);
678474
+ if (!reclaimed.ok) return false;
678475
+ }
678476
+ }
678477
+ if (await isDaemonRunning(p2)) {
678478
+ const reclaimed = await reclaimOwnedDaemonListener(p2);
678479
+ if (!reclaimed.ok) return false;
678421
678480
  }
678422
- if (await isDaemonRunning(p2) && !await gracefullyStopOwnedDaemon(p2)) return false;
678423
678481
  const pid = await startDaemon(p2);
678424
678482
  if (!pid) return false;
678425
678483
  return (await waitForDaemonReady(p2, expectedVersion)).ok;
@@ -678496,7 +678554,7 @@ async function startDaemon(port = getDaemonPort()) {
678496
678554
  outFd = openSync4(join128(OMNIUS_DIR, "daemon.log"), "a");
678497
678555
  errFd = openSync4(join128(OMNIUS_DIR, "daemon.err.log"), "a");
678498
678556
  const leaseId = newProcessLeaseId("daemon");
678499
- const ownerId = `daemon:${getDaemonPort()}`;
678557
+ const ownerId = `daemon:${daemonPort}`;
678500
678558
  const child = spawn28(daemonCommand.command, daemonCommand.args, {
678501
678559
  detached: true,
678502
678560
  stdio: ["ignore", outFd, errFd],
@@ -678670,17 +678728,17 @@ async function ensureDaemonVersion(expectedVersion = getLocalCliVersion(), port
678670
678728
  }
678671
678729
  return finish(true, "unchanged", await getDaemonReportedVersion(port));
678672
678730
  }
678673
- const stalePid = getDaemonPid();
678674
- if (stalePid) {
678675
- try {
678676
- process.kill(stalePid, "SIGTERM");
678677
- } catch {
678678
- }
678679
- try {
678680
- unlinkSync22(PID_FILE);
678681
- } catch {
678682
- }
678731
+ const reclaimed = await reclaimOwnedDaemonListener(port);
678732
+ if (!reclaimed.ok) {
678733
+ return finish(
678734
+ false,
678735
+ "failed",
678736
+ null,
678737
+ 0,
678738
+ process.platform === "linux" && reclaimed.action === "blocked"
678739
+ );
678683
678740
  }
678741
+ getDaemonPid();
678684
678742
  const pid = await startDaemon(port);
678685
678743
  if (!pid) {
678686
678744
  for (let i2 = 0; i2 < 20; i2++) {
@@ -678728,7 +678786,7 @@ async function getDaemonStatus() {
678728
678786
  }
678729
678787
  return { running, pid, port, uptime: uptime2, pidFile: PID_FILE };
678730
678788
  }
678731
- var OMNIUS_DIR, PID_FILE, DEFAULT_PORT2, LOCK_INITIALIZATION_GRACE_MS, DAEMON_VERSION_RECOVERY_ATTEMPTS, DAEMON_GRACEFUL_STOP_ATTEMPTS;
678789
+ var OMNIUS_DIR, PID_FILE, DEFAULT_PORT2, LOCK_INITIALIZATION_GRACE_MS, DAEMON_VERSION_RECOVERY_ATTEMPTS, DAEMON_GRACEFUL_STOP_ATTEMPTS, DEFAULT_RECLAIM_DEPENDENCIES;
678732
678790
  var init_daemon = __esm({
678733
678791
  "packages/cli/src/daemon.ts"() {
678734
678792
  init_dist5();
@@ -678738,6 +678796,16 @@ var init_daemon = __esm({
678738
678796
  LOCK_INITIALIZATION_GRACE_MS = 5e3;
678739
678797
  DAEMON_VERSION_RECOVERY_ATTEMPTS = 2;
678740
678798
  DAEMON_GRACEFUL_STOP_ATTEMPTS = 20;
678799
+ DEFAULT_RECLAIM_DEPENDENCIES = {
678800
+ holderPids: (port) => portHolderPids(port),
678801
+ portIsFree: (port) => daemonPortIsFree(port),
678802
+ leases: () => listProcessLeases({ includeInactive: false }),
678803
+ stopLease: (leaseId) => stopProcessLease(leaseId, {
678804
+ reason: "stale daemon listener reclaim",
678805
+ termGraceMs: 1e3
678806
+ }),
678807
+ waitForFree: (port) => waitForDaemonStopped(port)
678808
+ };
678741
678809
  }
678742
678810
  });
678743
678811
 
@@ -678757,6 +678825,7 @@ __export(tray_exports, {
678757
678825
  runTrayForeground: () => runTrayForeground,
678758
678826
  startTray: () => startTray,
678759
678827
  stopTray: () => stopTray,
678828
+ trayRequiresRestart: () => trayRequiresRestart,
678760
678829
  traySupport: () => traySupport,
678761
678830
  trayUpdatePresentation: () => trayUpdatePresentation,
678762
678831
  uninstallTrayAutostart: () => uninstallTrayAutostart,
@@ -679149,7 +679218,7 @@ start "" /b ${argv.map(windowsCmdQuote).join(" ")}\r
679149
679218
  }
679150
679219
  throw new Error(`Autostart is not supported on ${platform8}`);
679151
679220
  }
679152
- function installTrayAutostart(endpoint) {
679221
+ function writeTrayAutostart(endpoint) {
679153
679222
  const launch = resolveTrayLaunchCommand();
679154
679223
  if (!launch) throw new Error("Could not resolve the installed Omnius CLI entrypoint");
679155
679224
  const paths = resolveTrayPaths();
@@ -679159,11 +679228,15 @@ function installTrayAutostart(endpoint) {
679159
679228
  buildAutostartContent(process.platform, launch, endpoint, assetPath("online")),
679160
679229
  { encoding: "utf8", mode: process.platform === "win32" ? 448 : 384 }
679161
679230
  );
679231
+ return paths.autostartFile;
679232
+ }
679233
+ function installTrayAutostart(endpoint) {
679234
+ const autostartFile = writeTrayAutostart(endpoint);
679162
679235
  if (process.platform === "darwin" && typeof process.getuid === "function") {
679163
679236
  spawnSync9("launchctl", ["bootout", `gui/${process.getuid()}/ai.omnius.tray`], { stdio: "ignore" });
679164
- spawnSync9("launchctl", ["bootstrap", `gui/${process.getuid()}`, paths.autostartFile], { stdio: "ignore" });
679237
+ spawnSync9("launchctl", ["bootstrap", `gui/${process.getuid()}`, autostartFile], { stdio: "ignore" });
679165
679238
  }
679166
- return paths.autostartFile;
679239
+ return autostartFile;
679167
679240
  }
679168
679241
  function uninstallTrayAutostart() {
679169
679242
  const paths = resolveTrayPaths();
@@ -679295,11 +679368,15 @@ async function getTrayStatus(explicitEndpoint) {
679295
679368
  }
679296
679369
  const state = readProcessState(paths.stateFile);
679297
679370
  const support = traySupport();
679371
+ const requestedEndpoint = resolveTrayEndpoint(explicitEndpoint);
679298
679372
  return {
679299
679373
  running,
679300
679374
  ready: running && Boolean(state?.ready),
679301
679375
  pid: running ? pid : null,
679302
- endpoint: state?.endpoint || resolveTrayEndpoint(explicitEndpoint),
679376
+ // A stopped indicator's cached state must not override an explicitly
679377
+ // requested endpoint. This previously kept resurrecting an obsolete
679378
+ // custom port (for example 11535) after the daemon had returned to 11435.
679379
+ endpoint: running || !explicitEndpoint ? state?.endpoint || requestedEndpoint : requestedEndpoint,
679303
679380
  registered: existsSync117(paths.autostartFile),
679304
679381
  registrationFile: paths.autostartFile,
679305
679382
  ...state?.health ? { health: state.health } : {},
@@ -679308,16 +679385,26 @@ async function getTrayStatus(explicitEndpoint) {
679308
679385
  ...state?.error ? { error: state.error } : {}
679309
679386
  };
679310
679387
  }
679388
+ function trayRequiresRestart(existing, endpoint, liveHealth) {
679389
+ return existing.endpoint !== endpoint || !existing.ready || Boolean(existing.error) || existing.health?.kind !== liveHealth.kind || existing.health?.version !== liveHealth.version;
679390
+ }
679311
679391
  async function startTray(explicitEndpoint) {
679312
- const existing = await getTrayStatus(explicitEndpoint);
679313
- if (existing.running) return existing;
679392
+ const endpoint = resolveTrayEndpoint(explicitEndpoint);
679393
+ const existing = await getTrayStatus(endpoint);
679394
+ if (existing.running) {
679395
+ const liveHealth = await pollTrayHealth(endpoint);
679396
+ if (!trayRequiresRestart(existing, endpoint, liveHealth)) {
679397
+ return { ...existing, health: liveHealth };
679398
+ }
679399
+ await stopTray();
679400
+ }
679314
679401
  const support = traySupport();
679315
679402
  if (!support.supported) throw new Error(support.reason || "Tray is not supported on this host");
679316
679403
  verifyTrayHelper();
679317
679404
  const launch = resolveTrayLaunchCommand();
679318
679405
  if (!launch) throw new Error("Could not resolve the installed Omnius CLI entrypoint");
679319
- const endpoint = resolveTrayEndpoint(explicitEndpoint);
679320
679406
  const paths = resolveTrayPaths();
679407
+ if (existing.registered) writeTrayAutostart(endpoint);
679321
679408
  mkdirSync70(paths.stateDir, { recursive: true, mode: 448 });
679322
679409
  mkdirSync70(paths.runtimeDir, { recursive: true, mode: 448 });
679323
679410
  try {
@@ -724408,7 +724495,15 @@ var indicator_command_exports = {};
724408
724495
  __export(indicator_command_exports, {
724409
724496
  runIndicatorCommand: () => runIndicatorCommand
724410
724497
  });
724411
- function formatIndicatorStatus(status) {
724498
+ async function waitForOnlineHealth(endpoint, operations, attempts = 5) {
724499
+ let health = await operations.health(endpoint);
724500
+ for (let attempt = 1; health.kind !== "online" && attempt < attempts; attempt++) {
724501
+ await operations.wait(200);
724502
+ health = await operations.health(endpoint);
724503
+ }
724504
+ return health;
724505
+ }
724506
+ function formatIndicatorStatus(status, daemon) {
724412
724507
  const state = status.running ? status.ready ? "ready" : "starting" : "stopped";
724413
724508
  const pid = status.pid ? ` (PID ${status.pid})` : "";
724414
724509
  const health = status.health?.label ?? "not checked";
@@ -724419,6 +724514,12 @@ function formatIndicatorStatus(status) {
724419
724514
  `Endpoint: ${status.endpoint}.`,
724420
724515
  `Autostart: ${autostart}.`
724421
724516
  ];
724517
+ if (daemon) {
724518
+ const version5 = daemon.observedVersion ?? daemon.expectedVersion;
724519
+ parts.push(
724520
+ `Daemon: ${daemon.action}${version5 ? ` (v${version5})` : ""} on port ${daemon.port}.`
724521
+ );
724522
+ }
724422
724523
  if (status.error) parts.push(`Error: ${status.error}`);
724423
724524
  return parts.join(" ");
724424
724525
  }
@@ -724431,9 +724532,42 @@ async function runIndicatorCommand(rawAction, operations = DEFAULT_OPERATIONS) {
724431
724532
  };
724432
724533
  }
724433
724534
  try {
724434
- const status = action === "status" ? await operations.status() : await operations.start();
724435
- const level = status.error ? "error" : status.running && status.ready ? "info" : status.running ? "warning" : action === "start" ? "error" : "warning";
724436
- return { level, message: formatIndicatorStatus(status), status };
724535
+ if (action === "status") {
724536
+ const cached2 = await operations.status();
724537
+ const health2 = await operations.health(cached2.endpoint);
724538
+ const status2 = { ...cached2, health: health2 };
724539
+ const level2 = status2.error ? "error" : status2.running && status2.ready && health2.kind === "online" ? "info" : "warning";
724540
+ return { level: level2, message: formatIndicatorStatus(status2), status: status2 };
724541
+ }
724542
+ const daemon = await operations.ensureDaemon();
724543
+ if (!daemon.ok) {
724544
+ const observed = daemon.observedVersion ? `; observed v${daemon.observedVersion}` : "";
724545
+ const migration = daemon.requiresPrivilegedMigration ? " Privileged service migration is required; run /daemon takeover." : "";
724546
+ return {
724547
+ level: "error",
724548
+ message: `Indicator not started: daemon reconciliation failed on port ${daemon.port}${observed}.${migration}`,
724549
+ daemon
724550
+ };
724551
+ }
724552
+ const endpoint = `http://127.0.0.1:${daemon.port}`;
724553
+ const daemonHealth = await waitForOnlineHealth(endpoint, operations);
724554
+ if (daemonHealth.kind !== "online") {
724555
+ return {
724556
+ level: "error",
724557
+ message: `Indicator not started: daemon did not become online at ${endpoint}. ` + daemonHealth.tooltip,
724558
+ daemon
724559
+ };
724560
+ }
724561
+ const started = await operations.start(endpoint);
724562
+ const health = await waitForOnlineHealth(endpoint, operations);
724563
+ const status = { ...started, endpoint, health };
724564
+ const level = status.error ? "error" : status.running && status.ready && health.kind === "online" ? "info" : "error";
724565
+ return {
724566
+ level,
724567
+ message: formatIndicatorStatus(status, daemon),
724568
+ status,
724569
+ daemon
724570
+ };
724437
724571
  } catch (error) {
724438
724572
  return {
724439
724573
  level: "error",
@@ -724445,9 +724579,13 @@ var DEFAULT_OPERATIONS;
724445
724579
  var init_indicator_command = __esm({
724446
724580
  "packages/cli/src/tui/indicator-command.ts"() {
724447
724581
  init_tray();
724582
+ init_daemon();
724448
724583
  DEFAULT_OPERATIONS = {
724449
- start: () => startTray(),
724450
- status: () => getTrayStatus()
724584
+ ensureDaemon: () => ensureDaemonVersion(),
724585
+ start: (endpoint) => startTray(endpoint),
724586
+ status: () => getTrayStatus(),
724587
+ health: (endpoint) => pollTrayHealth(endpoint),
724588
+ wait: (ms) => new Promise((resolve87) => setTimeout(resolve87, ms))
724451
724589
  };
724452
724590
  }
724453
724591
  });
@@ -25,6 +25,12 @@ From an active Omnius TUI, use the local slash command instead:
25
25
  start/stop/restart lifecycle remain available through the top-level
26
26
  `omnius tray` command.
27
27
 
28
+ `/indicator` first reconciles the current Omnius daemon on its configured
29
+ loopback port (11435 by default), waits for verified online health, repairs a
30
+ stale tray endpoint/autostart registration, and only then starts the native
31
+ indicator. Untracked processes occupying the port are reported as blockers and
32
+ are never terminated automatically.
33
+
28
34
  The indicator polls the daemon's loopback-only `GET /health` route. It does not
29
35
  load a model or contact the configured inference backend. If the daemon uses a
30
36
  non-default port, Omnius discovers `OMNIUS_HOST`/`OMNIUS_PORT` from the current
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.598",
3
+ "version": "1.0.599",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.598",
9
+ "version": "1.0.599",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.598",
3
+ "version": "1.0.599",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/library.js",