niahere 0.2.74 → 0.2.75

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/utils/pid.ts +44 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.74",
3
+ "version": "0.2.75",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  "license": "MIT",
45
45
  "private": false,
46
46
  "dependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "^0.2.97",
47
+ "@anthropic-ai/claude-agent-sdk": "^0.2.119",
48
48
  "@anthropic-ai/sdk": "^0.88.0",
49
49
  "@modelcontextprotocol/sdk": "^1.27.1",
50
50
  "@slack/bolt": "^4.6.0",
package/src/utils/pid.ts CHANGED
@@ -3,23 +3,50 @@ import { dirname } from "path";
3
3
  import { getPaths } from "./paths";
4
4
  import { log } from "./log";
5
5
 
6
+ type PidEntry = { pid: number; lstart: string };
7
+
8
+ function getLstart(pid: number): string {
9
+ try {
10
+ const result = Bun.spawnSync(["ps", "-o", "lstart=", "-p", String(pid)]);
11
+ return new TextDecoder().decode(result.stdout).trim();
12
+ } catch {
13
+ return "";
14
+ }
15
+ }
16
+
6
17
  export function writePid(pid: number): void {
7
18
  const { pid: pidPath } = getPaths();
19
+ const lstart = getLstart(pid);
20
+ if (!lstart) {
21
+ log.warn({ pid }, "could not capture pid identity (ps returned nothing)");
22
+ }
8
23
  mkdirSync(dirname(pidPath), { recursive: true });
9
- writeFileSync(pidPath, String(pid));
24
+ writeFileSync(pidPath, JSON.stringify({ pid, lstart }));
10
25
  }
11
26
 
12
- export function readPid(): number | null {
27
+ function readEntry(): PidEntry | null {
13
28
  const { pid: pidPath } = getPaths();
14
29
  if (!existsSync(pidPath)) return null;
15
30
 
16
31
  try {
17
- return parseInt(readFileSync(pidPath, "utf8").trim(), 10);
32
+ const raw = readFileSync(pidPath, "utf8").trim();
33
+ if (/^\d+$/.test(raw)) {
34
+ return { pid: parseInt(raw, 10), lstart: "" };
35
+ }
36
+ const parsed = JSON.parse(raw);
37
+ if (typeof parsed?.pid === "number" && typeof parsed?.lstart === "string") {
38
+ return parsed as PidEntry;
39
+ }
40
+ return null;
18
41
  } catch {
19
42
  return null;
20
43
  }
21
44
  }
22
45
 
46
+ export function readPid(): number | null {
47
+ return readEntry()?.pid ?? null;
48
+ }
49
+
23
50
  export function removePid(): void {
24
51
  const { pid: pidPath } = getPaths();
25
52
  try {
@@ -30,15 +57,22 @@ export function removePid(): void {
30
57
  }
31
58
 
32
59
  export function isRunning(): boolean {
33
- const pid = readPid();
34
- if (pid === null) return false;
60
+ const entry = readEntry();
61
+ if (entry === null) return false;
35
62
 
36
- try {
37
- process.kill(pid, 0);
38
- return true;
39
- } catch {
40
- log.warn({ stalePid: pid }, "removing stale pid file (process not running)");
63
+ const currentLstart = getLstart(entry.pid);
64
+ if (!currentLstart) {
65
+ log.warn({ stalePid: entry.pid }, "removing stale pid file (process not running)");
66
+ removePid();
67
+ return false;
68
+ }
69
+ if (entry.lstart && currentLstart !== entry.lstart) {
70
+ log.warn(
71
+ { stalePid: entry.pid, recorded: entry.lstart, current: currentLstart },
72
+ "removing stale pid file (process identity mismatch)",
73
+ );
41
74
  removePid();
42
75
  return false;
43
76
  }
77
+ return true;
44
78
  }