@vincemakes/kiso-runtime 1.0.0 → 1.0.2

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.
@@ -61,6 +61,7 @@
61
61
  * pauses plus SIGSTOP/SIGCONT, and locate the freeze points via
62
62
  * ready-marker files (ADR-0050 §test affordances).
63
63
  */
64
+ import { execFileSync } from "node:child_process";
64
65
  import { closeSync, fsyncSync, linkSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
65
66
  import { randomUUID } from "node:crypto";
66
67
  import { join } from "node:path";
@@ -78,14 +79,58 @@ export class LockUnavailableError extends Error {
78
79
  this.name = "LockUnavailableError";
79
80
  }
80
81
  }
82
+ /**
83
+ * ADR-0050 Amendment 1 (Finding R-I-1): the liveness probe is
84
+ * state-aware. A dead holder can linger in the process table after a
85
+ * kill — the exiting state (STAT E on macOS: the kill landing while a
86
+ * pty syscall is blocked, the dead session's terminal left open) or an
87
+ * un-reaped zombie (STAT Z). POSIX reports BOTH alive to kill(pid, 0)
88
+ * (they exist until reaped), so the takeover refused a holder that can
89
+ * never execute another session write. Both states are probed and
90
+ * judged DEAD. A probe failure (unreadable state) maintains the
91
+ * pre-amendment behavior — alive, the fail-safe refusal (prefer a false
92
+ * refusal over a double-write). Live-process semantics and the PID-reuse
93
+ * rules do not move: a live foreign writer is still refused.
94
+ *
95
+ * ADR-0050 Amendment 2 (Finding R-I-p-3): the state letters are matched
96
+ * ANYWHERE in the state string, not as the first character. The
97
+ * exit-path linger's ps output is "?E" — the first character is the
98
+ * no-controlling-terminal marker "?", with the E sitting AFTER it — so
99
+ * first-character matching judged the finding's own documented shape
100
+ * alive. The ps state alphabet (macOS + Linux) has no flag letters
101
+ * "E"/"Z": an occurrence anywhere is the process-state code, and the
102
+ * holder is dead.
103
+ */
81
104
  function isAlive(pid) {
82
105
  try {
83
106
  process.kill(pid, 0);
84
- return true;
85
107
  }
86
108
  catch (err) {
87
109
  return err.code === "EPERM";
88
110
  }
111
+ const state = processState(pid);
112
+ // A null state (probe failure) keeps the pre-amendment behavior —
113
+ // judged alive, the fail-safe refusal.
114
+ return state === null || (!state.includes("E") && !state.includes("Z"));
115
+ }
116
+ /**
117
+ * The process state via `ps -o state= -p <pid>` (macOS/Linux) — the
118
+ * whole state string (a multi-char string like "Ss+" or "?E" is a
119
+ * combined flag set; the E/Z process-state codes may sit after the "?"
120
+ * no-tty marker or other flag letters, so the matching in isAlive scans
121
+ * the whole string). An empty or unreadable state — the process
122
+ * vanished between the kill and the probe, or ps itself failed —
123
+ * returns null; isAlive's fail-safe branch then judges the holder alive
124
+ * (the pre-amendment behavior).
125
+ */
126
+ function processState(pid) {
127
+ try {
128
+ const state = execFileSync("ps", ["-o", "state=", "-p", String(pid)], { encoding: "utf8" }).trim();
129
+ return state.length > 0 ? state : null;
130
+ }
131
+ catch {
132
+ return null;
133
+ }
89
134
  }
90
135
  /**
91
136
  * Read a lock file's holder identity (round 4 formats, unchanged — the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-runtime",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "kiso runtime — durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
5
5
  "type": "module",
6
6
  "license": "MIT",