oc-inspector 1.5.0 → 1.5.1

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/bin/cli.mjs CHANGED
@@ -350,7 +350,31 @@ async function runDaemonStop(silent = false, cmdOpts = {}) {
350
350
 
351
351
  if (st.alive) {
352
352
  try {
353
+ // 2a. Send SIGTERM for graceful shutdown
353
354
  process.kill(st.pid, "SIGTERM");
355
+
356
+ // 2b. Wait up to 5 seconds for the process to exit
357
+ let died = false;
358
+ for (let i = 0; i < 10; i++) {
359
+ await new Promise((r) => setTimeout(r, 500));
360
+ try {
361
+ process.kill(st.pid, 0); // probe — throws ESRCH if dead
362
+ } catch {
363
+ died = true;
364
+ break;
365
+ }
366
+ }
367
+
368
+ // 2c. Escalate to SIGKILL if still alive
369
+ if (!died) {
370
+ try {
371
+ process.kill(st.pid, "SIGKILL");
372
+ if (!silent) {
373
+ console.log(` \x1b[33m⚠\x1b[0m Process didn't exit after SIGTERM — sent SIGKILL (PID ${st.pid})`);
374
+ }
375
+ } catch { /* already dead */ }
376
+ }
377
+
354
378
  if (!silent) {
355
379
  console.log(` \x1b[32m✓\x1b[0m Stopped inspector (PID ${st.pid})`);
356
380
  console.log("");
@@ -463,8 +487,17 @@ async function runServe(opts) {
463
487
  }
464
488
 
465
489
  const gracefulShutdown = async (signal) => {
490
+ // Force-exit after 5s so we never leave a zombie process
491
+ const forceTimer = setTimeout(() => {
492
+ console.log(" \x1b[33m⚠\x1b[0m Shutdown timed out — forcing exit");
493
+ cleanupPidFile();
494
+ process.exit(1);
495
+ }, 5000);
496
+ forceTimer.unref();
497
+
466
498
  console.log(`\n Shutting down (${signal})...`);
467
499
  await restoreConfigOnExit(opts);
500
+ cleanupPidFile();
468
501
  process.exit(0);
469
502
  };
470
503
 
@@ -503,8 +536,17 @@ async function runForeground(opts) {
503
536
  }
504
537
 
505
538
  const gracefulShutdown = async (signal) => {
539
+ // Force-exit after 5s so we never leave a zombie process
540
+ const forceTimer = setTimeout(() => {
541
+ console.log(" \x1b[33m⚠\x1b[0m Shutdown timed out — forcing exit");
542
+ cleanupPidFile();
543
+ process.exit(1);
544
+ }, 5000);
545
+ forceTimer.unref();
546
+
506
547
  console.log(`\n Shutting down (${signal})...`);
507
548
  await restoreConfigOnExit(opts);
549
+ cleanupPidFile();
508
550
  process.exit(0);
509
551
  };
510
552
 
@@ -512,6 +554,14 @@ async function runForeground(opts) {
512
554
  process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
513
555
  }
514
556
 
557
+ /**
558
+ * Remove the daemon PID file on exit.
559
+ * Called during graceful shutdown so stop/status don't see stale PIDs.
560
+ */
561
+ function cleanupPidFile() {
562
+ try { unlinkSync(PID_FILE); } catch { /* ignore */ }
563
+ }
564
+
515
565
  /**
516
566
  * Restore original OpenClaw config on process exit.
517
567
  * Used by graceful shutdown handlers in _serve and run modes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-inspector",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Real-time API traffic inspector for OpenClaw — intercepts LLM provider requests, shows token usage, costs, and message flow in a live web dashboard.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.mjs CHANGED
@@ -14,6 +14,16 @@ import { homedir } from "node:os";
14
14
  import { execSync } from "node:child_process";
15
15
  import { BUILTIN_URLS, detectActiveProviders } from "./providers.mjs";
16
16
 
17
+ /**
18
+ * Regex matching inspector proxy URLs on localhost (any port).
19
+ *
20
+ * Used to detect whether a baseUrl has been patched by the inspector,
21
+ * regardless of which port was configured at the time.
22
+ *
23
+ * @type {RegExp}
24
+ */
25
+ const PROXY_URL_RE = /127\.0\.0\.1:\d+/;
26
+
17
27
  /** Default OpenClaw state directory. */
18
28
  const DEFAULT_OPENCLAW_DIR = join(homedir(), ".openclaw");
19
29
 
@@ -260,7 +270,7 @@ export function disable({ configPath, openclawDir }) {
260
270
  // Verify backup is clean (doesn't contain proxy URLs)
261
271
  try {
262
272
  const backupContent = readFileSync(backupPath, "utf-8");
263
- if (!backupContent.includes("127.0.0.1:3000")) {
273
+ if (!PROXY_URL_RE.test(backupContent)) {
264
274
  copyFileSync(backupPath, configPath);
265
275
  removeState(openclawDir);
266
276
  const restart = restartGateway();
@@ -331,7 +341,7 @@ function cleanProxyUrls(configPath) {
331
341
  let cleaned = false;
332
342
 
333
343
  for (const [name, cfg] of Object.entries(providers)) {
334
- if (cfg.baseUrl && cfg.baseUrl.includes("127.0.0.1:3000")) {
344
+ if (cfg.baseUrl && PROXY_URL_RE.test(cfg.baseUrl)) {
335
345
  if (BUILTIN_URLS[name]) {
336
346
  // Known provider — restore builtin URL
337
347
  cfg.baseUrl = BUILTIN_URLS[name];