@ouro.bot/cli 0.1.0-alpha.253 → 0.1.0-alpha.254
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/changelog.json +6 -0
- package/dist/heart/daemon/daemon.js +48 -15
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.254",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(daemon): orphan-cleanup fallback no longer kills processes from sibling harness instances. On startup, `killOrphanProcesses` scans `ps` for harness entry points (`agent-entry.js`, `daemon-entry.js`, `bluebubbles/entry.js`, `teams-entry.js`) and SIGTERMs them when the pidfile is missing. Previously any matching process was fair game, so a vitest-driven harness run from a sibling worktree (or a parallel Claude Code session running the coverage gate) would terminate the production daemon's children, triggering cascading graceful shutdowns and making production slugger unavailable for seconds-to-minutes at a time. Now the fallback only flags true orphans \u2014 processes whose PPID has been reparented to init (PID 1). Sibling daemons' live-parented children are left alone. New exported helper `parseOrphanPidsFromPs` isolates the filter for direct unit coverage. Complements the test-isolation guard shipped in alpha.253 (#333): that PR stopped tests from SENDING `inner.wake testagent` commands into the production socket; this PR stops the production daemon from SIGTERMing test-spawned child processes during its own startup."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.253",
|
|
6
12
|
"changes": [
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.OuroDaemon = void 0;
|
|
37
|
+
exports.parseOrphanPidsFromPs = parseOrphanPidsFromPs;
|
|
37
38
|
exports.killOrphanProcesses = killOrphanProcesses;
|
|
38
39
|
exports.writePidfile = writePidfile;
|
|
39
40
|
exports.handleAgentSenseTurn = handleAgentSenseTurn;
|
|
@@ -62,13 +63,55 @@ const outlook_types_1 = require("../outlook/outlook-types");
|
|
|
62
63
|
const outlook_read_1 = require("../outlook/outlook-read");
|
|
63
64
|
const outlook_view_1 = require("../outlook/outlook-view");
|
|
64
65
|
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
66
|
+
/**
|
|
67
|
+
* Scan `ps -eo pid,ppid,command` output for daemon-owned entry points whose
|
|
68
|
+
* parent has died (PPID reparented to init/PID 1). Returns the list of PIDs
|
|
69
|
+
* that are safe to SIGTERM — true orphans, not children of live sibling
|
|
70
|
+
* daemons running from worktrees, test suites, or other users of the harness.
|
|
71
|
+
*
|
|
72
|
+
* Exported so unit tests can exercise the filter without shelling out.
|
|
73
|
+
*/
|
|
74
|
+
function parseOrphanPidsFromPs(psOutput, selfPid) {
|
|
75
|
+
const orphans = [];
|
|
76
|
+
for (const line of psOutput.split("\n")) {
|
|
77
|
+
// Explicitly exclude MCP server processes — they share a harness entry
|
|
78
|
+
// point but are not daemon children and must never be killed.
|
|
79
|
+
if (line.includes("mcp-serve") || line.includes("mcp serve"))
|
|
80
|
+
continue;
|
|
81
|
+
// Match only daemon-owned JS entry points.
|
|
82
|
+
if (!line.includes("agent-entry.js")
|
|
83
|
+
&& !line.includes("daemon-entry.js")
|
|
84
|
+
&& !line.includes("bluebubbles/entry.js")
|
|
85
|
+
&& !line.includes("teams-entry.js"))
|
|
86
|
+
continue;
|
|
87
|
+
// Parse `<pid> <ppid> <command...>`. ps pads these with leading spaces.
|
|
88
|
+
// Regex guarantees both groups are \d+ so parseInt can't produce NaN.
|
|
89
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)\s/);
|
|
90
|
+
if (!match)
|
|
91
|
+
continue;
|
|
92
|
+
const pid = parseInt(match[1], 10);
|
|
93
|
+
const ppid = parseInt(match[2], 10);
|
|
94
|
+
if (pid === selfPid)
|
|
95
|
+
continue;
|
|
96
|
+
// CRITICAL: only kill processes whose parent is init (PID 1). A live
|
|
97
|
+
// PPID means the process belongs to another daemon instance (parallel
|
|
98
|
+
// test run, sibling worktree, another user of /tmp/ouroboros-daemon.sock).
|
|
99
|
+
// Killing those will crash unrelated harnesses — we saw this in B6
|
|
100
|
+
// when a vitest worker's daemon killed slugger's production children.
|
|
101
|
+
if (ppid !== 1)
|
|
102
|
+
continue;
|
|
103
|
+
orphans.push(pid);
|
|
104
|
+
}
|
|
105
|
+
return orphans;
|
|
106
|
+
}
|
|
65
107
|
/**
|
|
66
108
|
* Kill all ouro processes from the previous daemon instance using the pidfile.
|
|
67
109
|
* On startup, reads PIDs from ~/.ouro-cli/daemon.pids, kills them all, then
|
|
68
110
|
* deletes the file. The new daemon writes its own PIDs after spawning.
|
|
69
111
|
*
|
|
70
|
-
* Falls back to ps-based scanning
|
|
71
|
-
*
|
|
112
|
+
* Falls back to ps-based scanning scoped to true orphans (PPID=1) if the
|
|
113
|
+
* pidfile doesn't exist (first run, previous daemon crashed before writing,
|
|
114
|
+
* manual cleanup). The scope is narrow on purpose — see parseOrphanPidsFromPs.
|
|
72
115
|
*/
|
|
73
116
|
/* v8 ignore start -- process lifecycle: uses kill/ps, tested via deployment @preserve */
|
|
74
117
|
function killOrphanProcesses() {
|
|
@@ -81,22 +124,12 @@ function killOrphanProcesses() {
|
|
|
81
124
|
fs.unlinkSync(PIDFILE_PATH);
|
|
82
125
|
}
|
|
83
126
|
catch {
|
|
84
|
-
// No pidfile — fall back to ps scan
|
|
127
|
+
// No pidfile — fall back to ps scan (scoped to orphans with PPID=1).
|
|
85
128
|
}
|
|
86
|
-
// Fallback: scan ps for daemon-owned processes only (not MCP servers or external tools)
|
|
87
129
|
if (pidsToKill.length === 0) {
|
|
88
130
|
try {
|
|
89
|
-
const result = (0, child_process_1.execSync)("ps -eo pid,command", { encoding: "utf-8", timeout: 5000 });
|
|
90
|
-
|
|
91
|
-
// Only match daemon-owned entry points, NOT mcp-serve or other external processes
|
|
92
|
-
if (line.includes("mcp-serve") || line.includes("mcp serve"))
|
|
93
|
-
continue;
|
|
94
|
-
if (!line.includes("agent-entry.js") && !line.includes("daemon-entry.js") && !line.includes("bluebubbles/entry.js") && !line.includes("teams-entry.js"))
|
|
95
|
-
continue;
|
|
96
|
-
const pid = parseInt(line.trim(), 10);
|
|
97
|
-
if (!isNaN(pid) && pid !== process.pid)
|
|
98
|
-
pidsToKill.push(pid);
|
|
99
|
-
}
|
|
131
|
+
const result = (0, child_process_1.execSync)("ps -eo pid,ppid,command", { encoding: "utf-8", timeout: 5000 });
|
|
132
|
+
pidsToKill = parseOrphanPidsFromPs(result, process.pid);
|
|
100
133
|
}
|
|
101
134
|
catch { /* ps failed — best effort */ }
|
|
102
135
|
}
|