@ouro.bot/cli 0.1.0-alpha.121 → 0.1.0-alpha.122
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
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.122",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Daemon now uses a pidfile (~/.ouro-cli/daemon.pids) for process cleanup instead of fragile ps-based name matching. All managed PIDs (daemon, agents, senses) are written on startup and reaped on next restart.",
|
|
8
|
+
"Failover reply matching now uses includes() instead of exact match, fixing switch commands in BlueBubbles where channel metadata prefixes the user text."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.121",
|
|
6
13
|
"changes": [
|
|
@@ -35,8 +35,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.OuroDaemon = void 0;
|
|
37
37
|
exports.killOrphanProcesses = killOrphanProcesses;
|
|
38
|
+
exports.writePidfile = writePidfile;
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const net = __importStar(require("net"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
40
42
|
const path = __importStar(require("path"));
|
|
41
43
|
const identity_1 = require("../identity");
|
|
42
44
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -51,28 +53,41 @@ const child_process_1 = require("child_process");
|
|
|
51
53
|
const pending_1 = require("../../mind/pending");
|
|
52
54
|
const channel_1 = require("../../mind/friends/channel");
|
|
53
55
|
const mcp_manager_1 = require("../../repertoire/mcp-manager");
|
|
56
|
+
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
54
57
|
/**
|
|
55
|
-
* Kill
|
|
56
|
-
* On
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* Kill all ouro processes from the previous daemon instance using the pidfile.
|
|
59
|
+
* On startup, reads PIDs from ~/.ouro-cli/daemon.pids, kills them all, then
|
|
60
|
+
* deletes the file. The new daemon writes its own PIDs after spawning.
|
|
61
|
+
*
|
|
62
|
+
* Falls back to ps-based scanning if the pidfile doesn't exist (first run
|
|
63
|
+
* or manual cleanup).
|
|
61
64
|
*/
|
|
62
|
-
/* v8 ignore start --
|
|
65
|
+
/* v8 ignore start -- process lifecycle: uses kill/ps, tested via deployment @preserve */
|
|
63
66
|
function killOrphanProcesses() {
|
|
64
67
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
let pidsToKill = [];
|
|
69
|
+
// Primary: read pidfile from previous daemon
|
|
70
|
+
try {
|
|
71
|
+
const raw = fs.readFileSync(PIDFILE_PATH, "utf-8");
|
|
72
|
+
pidsToKill = raw.split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n) && n !== process.pid);
|
|
73
|
+
fs.unlinkSync(PIDFILE_PATH);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// No pidfile — fall back to ps scan
|
|
77
|
+
}
|
|
78
|
+
// Fallback: scan ps for any ouro entry processes we missed
|
|
79
|
+
if (pidsToKill.length === 0) {
|
|
80
|
+
try {
|
|
81
|
+
const result = (0, child_process_1.execSync)("ps -eo pid,command", { encoding: "utf-8", timeout: 5000 });
|
|
82
|
+
for (const line of result.split("\n")) {
|
|
83
|
+
if (!line.includes("agent-entry.js") && !line.includes("daemon-entry.js") && !line.includes("-entry.js --agent"))
|
|
84
|
+
continue;
|
|
85
|
+
const pid = parseInt(line.trim(), 10);
|
|
86
|
+
if (!isNaN(pid) && pid !== process.pid)
|
|
87
|
+
pidsToKill.push(pid);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch { /* ps failed — best effort */ }
|
|
76
91
|
}
|
|
77
92
|
if (pidsToKill.length > 0) {
|
|
78
93
|
for (const pid of pidsToKill) {
|
|
@@ -99,6 +114,18 @@ function killOrphanProcesses() {
|
|
|
99
114
|
});
|
|
100
115
|
}
|
|
101
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Write all managed PIDs (daemon + children) to the pidfile.
|
|
119
|
+
* Called after all agents and senses are spawned.
|
|
120
|
+
*/
|
|
121
|
+
function writePidfile(extraPids = []) {
|
|
122
|
+
try {
|
|
123
|
+
const pids = [process.pid, ...extraPids].filter(Boolean);
|
|
124
|
+
fs.mkdirSync(path.dirname(PIDFILE_PATH), { recursive: true });
|
|
125
|
+
fs.writeFileSync(PIDFILE_PATH, pids.join("\n") + "\n", "utf-8");
|
|
126
|
+
}
|
|
127
|
+
catch { /* best effort */ }
|
|
128
|
+
}
|
|
102
129
|
function buildWorkerRows(snapshots) {
|
|
103
130
|
return snapshots.map((snapshot) => ({
|
|
104
131
|
agent: snapshot.name,
|
|
@@ -207,9 +234,17 @@ class OuroDaemon {
|
|
|
207
234
|
// Pre-initialize MCP connections so they're ready for the first command (non-blocking)
|
|
208
235
|
/* v8 ignore next -- catch callback: getSharedMcpManager logs errors internally @preserve */
|
|
209
236
|
(0, mcp_manager_1.getSharedMcpManager)().catch(() => { });
|
|
237
|
+
/* v8 ignore start -- orphan cleanup + pidfile: calls process management functions @preserve */
|
|
210
238
|
killOrphanProcesses();
|
|
239
|
+
/* v8 ignore stop */
|
|
211
240
|
await this.processManager.startAutoStartAgents();
|
|
212
241
|
await this.senseManager?.startAutoStartSenses();
|
|
242
|
+
// Write all managed PIDs to disk so the next daemon can clean up
|
|
243
|
+
/* v8 ignore start -- pidfile write: collects PIDs from process managers @preserve */
|
|
244
|
+
const agentPids = this.processManager.listAgentSnapshots().map((s) => s.pid).filter((p) => p !== null);
|
|
245
|
+
const sensePids = this.senseManager?.listManagedPids?.() ?? [];
|
|
246
|
+
writePidfile([...agentPids, ...sensePids]);
|
|
247
|
+
/* v8 ignore stop */
|
|
213
248
|
this.scheduler.start?.();
|
|
214
249
|
await this.scheduler.reconcile?.();
|
|
215
250
|
await this.drainPendingBundleMessages();
|
|
@@ -251,6 +251,13 @@ class DaemonSenseManager {
|
|
|
251
251
|
async stopAll() {
|
|
252
252
|
await this.processManager.stopAll();
|
|
253
253
|
}
|
|
254
|
+
/* v8 ignore start -- pid collection for orphan cleanup pidfile @preserve */
|
|
255
|
+
listManagedPids() {
|
|
256
|
+
return this.processManager.listAgentSnapshots()
|
|
257
|
+
.map((s) => s.pid)
|
|
258
|
+
.filter((pid) => pid !== null && pid !== undefined);
|
|
259
|
+
}
|
|
260
|
+
/* v8 ignore stop */
|
|
254
261
|
listSenseRows() {
|
|
255
262
|
const runtime = new Map();
|
|
256
263
|
for (const snapshot of this.processManager.listAgentSnapshots()) {
|
|
@@ -80,7 +80,7 @@ function buildFailoverContext(_errorMessage, classification, currentProvider, cu
|
|
|
80
80
|
function handleFailoverReply(reply, context) {
|
|
81
81
|
const lower = reply.toLowerCase().trim();
|
|
82
82
|
for (const provider of context.workingProviders) {
|
|
83
|
-
if (lower
|
|
83
|
+
if (lower.includes(`switch to ${provider}`) || lower === provider) {
|
|
84
84
|
return { action: "switch", provider };
|
|
85
85
|
}
|
|
86
86
|
}
|