ework-daemon 0.4.30 → 0.4.32
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 +1 -1
- package/src/op.ts +10 -0
- package/src/opencode.ts +36 -1
package/package.json
CHANGED
package/src/op.ts
CHANGED
|
@@ -260,6 +260,16 @@ export class Store {
|
|
|
260
260
|
return rows.map(rowToSession);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
async listSessionsWithPid(daemonId: number): Promise<Array<{ id: string; opencodePid: number }>> {
|
|
264
|
+
const rows = await getDB().all<{ uid: string; opencode_pid: number }>(
|
|
265
|
+
`SELECT s.uid, s.opencode_pid FROM {{op_sessions}} s
|
|
266
|
+
INNER JOIN {{issues}} i ON i.uid = s.issue_id
|
|
267
|
+
WHERE s.opencode_pid IS NOT NULL AND i.owner_daemon_id = ?`,
|
|
268
|
+
[daemonId]
|
|
269
|
+
);
|
|
270
|
+
return rows.map((r) => ({ id: r.uid, opencodePid: r.opencode_pid }));
|
|
271
|
+
}
|
|
272
|
+
|
|
263
273
|
async listNonIdleSessions(): Promise<OpSession[]> {
|
|
264
274
|
const rows = await getDB().all<SessionRow>("SELECT * FROM {{op_sessions}} WHERE state != 'idle'");
|
|
265
275
|
return rows.map(rowToSession);
|
package/src/opencode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import { mkdirSync, writeFileSync, readdirSync, existsSync } from "fs";
|
|
2
|
+
import { mkdirSync, writeFileSync, readdirSync, existsSync, readFileSync } from "fs";
|
|
3
3
|
import { join, resolve, isAbsolute } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { log } from "./logger";
|
|
@@ -1617,6 +1617,8 @@ export class Engine {
|
|
|
1617
1617
|
log.error("engine: releaseDeadOwners at boot failed:", (err as Error).message);
|
|
1618
1618
|
}
|
|
1619
1619
|
|
|
1620
|
+
await this.cleanupGlobalOrphans();
|
|
1621
|
+
|
|
1620
1622
|
// Multi-machine: recover ONLY this daemon's sessions. Other daemons own
|
|
1621
1623
|
// the rest; touching their state would race them.
|
|
1622
1624
|
const ownedSessions = await this.store.listOwnedSessions(this.daemonId);
|
|
@@ -1711,6 +1713,39 @@ export class Engine {
|
|
|
1711
1713
|
}
|
|
1712
1714
|
}
|
|
1713
1715
|
|
|
1716
|
+
private async cleanupGlobalOrphans(): Promise<void> {
|
|
1717
|
+
let sessions: Array<{ id: string; opencodePid: number }>;
|
|
1718
|
+
try {
|
|
1719
|
+
sessions = await this.store.listSessionsWithPid(this.daemonId);
|
|
1720
|
+
} catch { return; }
|
|
1721
|
+
const binaryName = this.cfg.opencode.binary.split("/").pop() ?? "opencode";
|
|
1722
|
+
let killed = 0;
|
|
1723
|
+
for (const s of sessions) {
|
|
1724
|
+
let alive = false;
|
|
1725
|
+
try { process.kill(s.opencodePid, 0); alive = true; } catch { /* dead */ }
|
|
1726
|
+
if (!alive) continue;
|
|
1727
|
+
|
|
1728
|
+
try {
|
|
1729
|
+
const cmdline = readFileSync(`/proc/${s.opencodePid}/cmdline`, "utf8");
|
|
1730
|
+
if (!cmdline.includes(binaryName) && !cmdline.includes("opencode") && !cmdline.includes("pi")) continue;
|
|
1731
|
+
} catch { continue; }
|
|
1732
|
+
|
|
1733
|
+
let ppid = -1;
|
|
1734
|
+
try {
|
|
1735
|
+
const stat = readFileSync(`/proc/${s.opencodePid}/stat`, "utf8");
|
|
1736
|
+
const m = stat.match(/\)\s+\S+\s+(\d+)/);
|
|
1737
|
+
ppid = m ? Number(m[1]) : -1;
|
|
1738
|
+
} catch { continue; }
|
|
1739
|
+
|
|
1740
|
+
if (ppid === 1) {
|
|
1741
|
+
log.info(`engine: killing orphaned pid=${s.opencodePid} (PPID=1, session ${s.id})`);
|
|
1742
|
+
try { this.killProcessTree(s.opencodePid); } catch { /* dead */ }
|
|
1743
|
+
killed++;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (killed > 0) log.info(`engine: cleaned up ${killed} orphaned processes (PPID=1)`);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1714
1749
|
// ─── API Methods ───
|
|
1715
1750
|
|
|
1716
1751
|
async retryMessage(messageId: string): Promise<boolean> {
|