total-recall-brain 3.23.0 → 3.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.23.0",
3
+ "version": "3.23.1",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -13,6 +13,8 @@
13
13
 
14
14
  import path from 'path';
15
15
  import fs from 'fs';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { readProcessCommand, entryPathHint, shouldHonorPidLock } from './pid-lock.mjs';
16
18
  import { createScheduler, updateTaskStatus, persistTaskToDisk } from './scheduler.mjs';
17
19
  import { scanAndIngest } from './session-watcher.mjs';
18
20
  import { syncAllRepos } from './repo-sync.mjs';
@@ -111,20 +113,36 @@ export function acquirePidLock() {
111
113
  if (fs.existsSync(PID_FILE)) {
112
114
  const existingPid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
113
115
  if (existingPid && !isNaN(existingPid)) {
114
- try {
115
- process.kill(existingPid, 0); // Check if process is alive (signal 0 = no-op check)
116
+ // Liveness alone cannot tell our daemon from whatever else inherited
117
+ // the number after a reboot — observed here holding a PID that had
118
+ // since been handed to an unrelated desktop app, which would refuse
119
+ // every start from then on.
120
+ const verdict = shouldHonorPidLock(existingPid, {
121
+ isAlive: (pid) => {
122
+ try {
123
+ process.kill(pid, 0); // signal 0 = no-op liveness check
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ },
129
+ readCommand: readProcessCommand,
130
+ entryHint: entryPathHint(fileURLToPath(import.meta.url)),
131
+ });
132
+ if (verdict.honor) {
116
133
  logger.error({
117
134
  subsystem: 'daemon-loop',
118
135
  message: `Another daemon is already running (PID: ${existingPid}). Exiting.`,
119
136
  });
120
137
  process.exit(1);
121
- } catch {
122
- // Process is dead — stale PID file
123
- logger.info({
124
- subsystem: 'daemon-loop',
125
- message: `Stale PID file found (PID: ${existingPid} is dead). Overwriting.`,
126
- });
127
138
  }
139
+ logger.info({
140
+ subsystem: 'daemon-loop',
141
+ message:
142
+ verdict.reason === 'pid-reused'
143
+ ? `Stale PID file found (PID ${existingPid} now belongs to another program). Overwriting.`
144
+ : `Stale PID file found (PID: ${existingPid} is dead). Overwriting.`,
145
+ });
128
146
  }
129
147
  }
130
148
  } catch {
@@ -11,19 +11,20 @@
11
11
  * already running", and it did so on every boot from then on — an outage with
12
12
  * an error message that sent you looking for a process that was never there.
13
13
  *
14
- * So liveness is necessary but not sufficient: the process has to look like
15
- * this server before its lock is honored.
14
+ * So liveness is necessary but not sufficient: the process has to look like the
15
+ * program that wrote the lock before the lock is honored. Shared by the server
16
+ * and the daemon, which had the same check and therefore the same bug.
16
17
  */
17
18
 
18
19
  import { spawnSync } from 'node:child_process';
19
20
  import path from 'node:path';
20
21
 
21
22
  /**
22
- * The tail of our own entry path (`server/index.mjs`), derived rather than
23
- * written down so it stays correct wherever the package is installed and never
24
- * hardcodes a checkout location.
23
+ * The tail of an entry path (`server/index.mjs`, `core/daemon-loop.mjs`),
24
+ * derived from the caller's own module URL rather than written down, so it stays
25
+ * correct wherever the package is installed and hardcodes no checkout location.
25
26
  */
26
- export function serverEntryHint(entryPath) {
27
+ export function entryPathHint(entryPath) {
27
28
  return path.join(path.basename(path.dirname(entryPath)), path.basename(entryPath));
28
29
  }
29
30
 
@@ -28,7 +28,7 @@ import {
28
28
  requireScope
29
29
  } from './auth.mjs';
30
30
  import { logger, logEvents } from "../core/logger.mjs";
31
- import { readProcessCommand, serverEntryHint, shouldHonorPidLock } from './pid-lock.mjs';
31
+ import { readProcessCommand, entryPathHint, shouldHonorPidLock } from '../core/pid-lock.mjs';
32
32
  import { drainActiveEmbeddings } from './routes/sessions.mjs';
33
33
  import { agentDir as configAgentDir, brainDir as configBrainDir, port as configPort, host as configHost, nodeEnv } from '../core/config.mjs';
34
34
  import { getDaemonStatus, ensureDaemonRunning } from '../core/daemon-control.mjs';
@@ -69,7 +69,7 @@ function acquireServerPidLock() {
69
69
  }
70
70
  },
71
71
  readCommand: readProcessCommand,
72
- entryHint: serverEntryHint(fileURLToPath(import.meta.url)),
72
+ entryHint: entryPathHint(fileURLToPath(import.meta.url)),
73
73
  });
74
74
  if (verdict.honor) {
75
75
  logger.error('server', `Another server instance is already running (PID: ${existingPid}). Exiting.`);