@agimon-ai/po-runner 0.0.0-alpha.0
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/LICENSE +21 -0
- package/README.md +257 -0
- package/dist/ansiScrub-CsqTSaVT.mjs +71 -0
- package/dist/ansiScrub-CsqTSaVT.mjs.map +1 -0
- package/dist/ansiScrub-D5gpHDch.cjs +100 -0
- package/dist/ansiScrub-D5gpHDch.cjs.map +1 -0
- package/dist/ansiScrub.cjs +7 -0
- package/dist/ansiScrub.d.mts +26 -0
- package/dist/ansiScrub.d.mts.map +1 -0
- package/dist/ansiScrub.mjs +2 -0
- package/dist/apiContracts.cjs +162 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +157 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/bashResult-DNgtahGR.cjs +629 -0
- package/dist/bashResult-DNgtahGR.cjs.map +1 -0
- package/dist/bashResult-DcrRaBLv.d.mts +49 -0
- package/dist/bashResult-DcrRaBLv.d.mts.map +1 -0
- package/dist/bashResult-bIVSzoj-.mjs +546 -0
- package/dist/bashResult-bIVSzoj-.mjs.map +1 -0
- package/dist/bashRunService-CJ02XaAq.mjs +440 -0
- package/dist/bashRunService-CJ02XaAq.mjs.map +1 -0
- package/dist/bashRunService-Ci0cGdYO.d.mts +71 -0
- package/dist/bashRunService-Ci0cGdYO.d.mts.map +1 -0
- package/dist/bashRunService-DyqxOSlx.cjs +445 -0
- package/dist/bashRunService-DyqxOSlx.cjs.map +1 -0
- package/dist/bashRunService.cjs +3 -0
- package/dist/bashRunService.d.mts +36 -0
- package/dist/bashRunService.d.mts.map +1 -0
- package/dist/bashRunService.mjs +2 -0
- package/dist/bashSchema.cjs +5 -0
- package/dist/bashSchema.d.mts +7 -0
- package/dist/bashSchema.d.mts.map +1 -0
- package/dist/bashSchema.mjs +2 -0
- package/dist/bashTool-B1EwZLo_.d.mts +20 -0
- package/dist/bashTool-B1EwZLo_.d.mts.map +1 -0
- package/dist/bashTool-C-k7fmyh.mjs +24 -0
- package/dist/bashTool-C-k7fmyh.mjs.map +1 -0
- package/dist/bashTool-CdgtqVFD.cjs +41 -0
- package/dist/bashTool-CdgtqVFD.cjs.map +1 -0
- package/dist/bashTool.cjs +13 -0
- package/dist/bashTool.cjs.map +1 -0
- package/dist/bashTool.d.mts +8 -0
- package/dist/bashTool.d.mts.map +1 -0
- package/dist/bashTool.mjs +11 -0
- package/dist/bashTool.mjs.map +1 -0
- package/dist/bin/cli.cjs +78 -0
- package/dist/bin/cli.cjs.map +1 -0
- package/dist/bin/cli.d.mts +4 -0
- package/dist/bin/cli.d.mts.map +1 -0
- package/dist/bin/cli.mjs +77 -0
- package/dist/bin/cli.mjs.map +1 -0
- package/dist/bin/logSink.cjs +102 -0
- package/dist/bin/logSink.cjs.map +1 -0
- package/dist/bin/logSink.d.mts +4 -0
- package/dist/bin/logSink.d.mts.map +1 -0
- package/dist/bin/logSink.mjs +99 -0
- package/dist/bin/logSink.mjs.map +1 -0
- package/dist/bin/runnerHost.cjs +131 -0
- package/dist/bin/runnerHost.cjs.map +1 -0
- package/dist/bin/runnerHost.d.mts +4 -0
- package/dist/bin/runnerHost.d.mts.map +1 -0
- package/dist/bin/runnerHost.mjs +127 -0
- package/dist/bin/runnerHost.mjs.map +1 -0
- package/dist/cliApp-Dj9nwaOv.cjs +244 -0
- package/dist/cliApp-Dj9nwaOv.cjs.map +1 -0
- package/dist/cliApp-v8Z3rMCi.mjs +237 -0
- package/dist/cliApp-v8Z3rMCi.mjs.map +1 -0
- package/dist/cliApp.cjs +3 -0
- package/dist/cliApp.d.mts +18 -0
- package/dist/cliApp.d.mts.map +1 -0
- package/dist/cliApp.mjs +2 -0
- package/dist/clock-Cv2pddgo.mjs +16 -0
- package/dist/clock-Cv2pddgo.mjs.map +1 -0
- package/dist/clock-euJkweLq.d.mts +11 -0
- package/dist/clock-euJkweLq.d.mts.map +1 -0
- package/dist/clock-jqnLWziT.cjs +21 -0
- package/dist/clock-jqnLWziT.cjs.map +1 -0
- package/dist/clock.cjs +3 -0
- package/dist/clock.d.mts +10 -0
- package/dist/clock.d.mts.map +1 -0
- package/dist/clock.mjs +2 -0
- package/dist/compaction-4QSUsBxq.cjs +40 -0
- package/dist/compaction-4QSUsBxq.cjs.map +1 -0
- package/dist/compaction-DCw80VZj.mjs +29 -0
- package/dist/compaction-DCw80VZj.mjs.map +1 -0
- package/dist/compaction.cjs +4 -0
- package/dist/compaction.d.mts +10 -0
- package/dist/compaction.d.mts.map +1 -0
- package/dist/compaction.mjs +2 -0
- package/dist/config.cjs +41 -0
- package/dist/config.d.mts +2 -0
- package/dist/config.mjs +2 -0
- package/dist/extensions/mcp-ui.mjs +23 -0
- package/dist/extensions/mcp-ui.mjs.map +1 -0
- package/dist/extensions/mcp.cjs +142 -0
- package/dist/extensions/mcp.cjs.map +1 -0
- package/dist/extensions/mcp.d.mts +5 -0
- package/dist/extensions/mcp.d.mts.map +1 -0
- package/dist/extensions/mcp.mjs +114 -0
- package/dist/extensions/mcp.mjs.map +1 -0
- package/dist/extensions/server.cjs +1156 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +1146 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +1785 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/headless-BGYfIlNk.mjs +169 -0
- package/dist/headless-BGYfIlNk.mjs.map +1 -0
- package/dist/headless-CHUsBGhg.cjs +180 -0
- package/dist/headless-CHUsBGhg.cjs.map +1 -0
- package/dist/index-BZL7keVW.d.mts +31 -0
- package/dist/index-BZL7keVW.d.mts.map +1 -0
- package/dist/index-BwB_jTI9.d.mts +12 -0
- package/dist/index-BwB_jTI9.d.mts.map +1 -0
- package/dist/index-D9-IXax4.d.mts +43 -0
- package/dist/index-D9-IXax4.d.mts.map +1 -0
- package/dist/index-DBz0DxnL.d.mts +39 -0
- package/dist/index-DBz0DxnL.d.mts.map +1 -0
- package/dist/index-DM7yJpMs.d.mts +25 -0
- package/dist/index-DM7yJpMs.d.mts.map +1 -0
- package/dist/index-Dhi8asLH.d.mts +22 -0
- package/dist/index-Dhi8asLH.d.mts.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +6 -0
- package/dist/launcher-BDm6-DiQ.mjs +150 -0
- package/dist/launcher-BDm6-DiQ.mjs.map +1 -0
- package/dist/launcher-BG5t9nvG.d.mts +39 -0
- package/dist/launcher-BG5t9nvG.d.mts.map +1 -0
- package/dist/launcher-SlyE8R3b.cjs +157 -0
- package/dist/launcher-SlyE8R3b.cjs.map +1 -0
- package/dist/launcher.cjs +3 -0
- package/dist/launcher.d.mts +33 -0
- package/dist/launcher.d.mts.map +1 -0
- package/dist/launcher.mjs +2 -0
- package/dist/lifeline-BZaQA0rm.mjs +88 -0
- package/dist/lifeline-BZaQA0rm.mjs.map +1 -0
- package/dist/lifeline-BuV_erib.cjs +98 -0
- package/dist/lifeline-BuV_erib.cjs.map +1 -0
- package/dist/lifeline-eJdZV0nN.d.mts +19 -0
- package/dist/lifeline-eJdZV0nN.d.mts.map +1 -0
- package/dist/lifeline.cjs +6 -0
- package/dist/lifeline.d.mts +21 -0
- package/dist/lifeline.d.mts.map +1 -0
- package/dist/lifeline.mjs +3 -0
- package/dist/logFile-C7oQ_v-g.d.mts +16 -0
- package/dist/logFile-C7oQ_v-g.d.mts.map +1 -0
- package/dist/logFile-CNvepZh0.cjs +67 -0
- package/dist/logFile-CNvepZh0.cjs.map +1 -0
- package/dist/logFile-KKPbkC-a.mjs +60 -0
- package/dist/logFile-KKPbkC-a.mjs.map +1 -0
- package/dist/logFile.cjs +3 -0
- package/dist/logFile.d.mts +11 -0
- package/dist/logFile.d.mts.map +1 -0
- package/dist/logFile.mjs +2 -0
- package/dist/logReader-B6J7L7Ua.cjs +282 -0
- package/dist/logReader-B6J7L7Ua.cjs.map +1 -0
- package/dist/logReader-MdW9ffvV.d.mts +43 -0
- package/dist/logReader-MdW9ffvV.d.mts.map +1 -0
- package/dist/logReader-UfbDbwQl.mjs +257 -0
- package/dist/logReader-UfbDbwQl.mjs.map +1 -0
- package/dist/logReader.cjs +4 -0
- package/dist/logReader.d.mts +3 -0
- package/dist/logReader.mjs +2 -0
- package/dist/processControl-BAOa3oo_.d.mts +14 -0
- package/dist/processControl-BAOa3oo_.d.mts.map +1 -0
- package/dist/processControl-DApTpV16.mjs +26 -0
- package/dist/processControl-DApTpV16.mjs.map +1 -0
- package/dist/processControl-DNJ1WZ9N.cjs +31 -0
- package/dist/processControl-DNJ1WZ9N.cjs.map +1 -0
- package/dist/processControl.cjs +3 -0
- package/dist/processControl.d.mts +10 -0
- package/dist/processControl.d.mts.map +1 -0
- package/dist/processControl.mjs +2 -0
- package/dist/ptyHost-DWWtRrGn.d.mts +38 -0
- package/dist/ptyHost-DWWtRrGn.d.mts.map +1 -0
- package/dist/ptyHost-DlQtTB8F.mjs +150 -0
- package/dist/ptyHost-DlQtTB8F.mjs.map +1 -0
- package/dist/ptyHost-MI2Z6WTG.cjs +157 -0
- package/dist/ptyHost-MI2Z6WTG.cjs.map +1 -0
- package/dist/ptyHost.cjs +3 -0
- package/dist/ptyHost.d.mts +26 -0
- package/dist/ptyHost.d.mts.map +1 -0
- package/dist/ptyHost.mjs +2 -0
- package/dist/ptySpawner-B6GsueZd.d.mts +28 -0
- package/dist/ptySpawner-B6GsueZd.d.mts.map +1 -0
- package/dist/ptySpawner-DEi1KOnd.mjs +17 -0
- package/dist/ptySpawner-DEi1KOnd.mjs.map +1 -0
- package/dist/ptySpawner-ijw0g5_T.cjs +22 -0
- package/dist/ptySpawner-ijw0g5_T.cjs.map +1 -0
- package/dist/ptySpawner.cjs +3 -0
- package/dist/ptySpawner.d.mts +14 -0
- package/dist/ptySpawner.d.mts.map +1 -0
- package/dist/ptySpawner.mjs +2 -0
- package/dist/reconcile-BugwYFbV.cjs +133 -0
- package/dist/reconcile-BugwYFbV.cjs.map +1 -0
- package/dist/reconcile-DMeNSxwn.mjs +114 -0
- package/dist/reconcile-DMeNSxwn.mjs.map +1 -0
- package/dist/reconcile.cjs +5 -0
- package/dist/reconcile.d.mts +36 -0
- package/dist/reconcile.d.mts.map +1 -0
- package/dist/reconcile.mjs +2 -0
- package/dist/responseEnvelope.cjs +19 -0
- package/dist/responseEnvelope.d.mts +50 -0
- package/dist/responseEnvelope.d.mts.map +1 -0
- package/dist/responseEnvelope.mjs +3 -0
- package/dist/rmuxBackend-CaEWooWn.d.mts +36 -0
- package/dist/rmuxBackend-CaEWooWn.d.mts.map +1 -0
- package/dist/rmuxBackend-DfyQwJrc.mjs +509 -0
- package/dist/rmuxBackend-DfyQwJrc.mjs.map +1 -0
- package/dist/rmuxBackend-DlxgqRZ1.cjs +523 -0
- package/dist/rmuxBackend-DlxgqRZ1.cjs.map +1 -0
- package/dist/rmuxBackend.cjs +4 -0
- package/dist/rmuxBackend.d.mts +3 -0
- package/dist/rmuxBackend.mjs +2 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/rtkProcessor-B4_urXQr.mjs +10 -0
- package/dist/rtkProcessor-B4_urXQr.mjs.map +1 -0
- package/dist/rtkProcessor-B9KPdyzr.mjs +309 -0
- package/dist/rtkProcessor-B9KPdyzr.mjs.map +1 -0
- package/dist/rtkProcessor-Bc4L4o_8.cjs +335 -0
- package/dist/rtkProcessor-Bc4L4o_8.cjs.map +1 -0
- package/dist/rtkProcessor-CVZvNhkJ.d.mts +35 -0
- package/dist/rtkProcessor-CVZvNhkJ.d.mts.map +1 -0
- package/dist/rtkProcessor-CsrCPyTQ.cjs +33 -0
- package/dist/rtkProcessor-CsrCPyTQ.cjs.map +1 -0
- package/dist/rtkProcessor.cjs +11 -0
- package/dist/rtkProcessor.d.mts +7 -0
- package/dist/rtkProcessor.d.mts.map +1 -0
- package/dist/rtkProcessor.mjs +3 -0
- package/dist/runnerConfig-BQBlVAXn.mjs +134 -0
- package/dist/runnerConfig-BQBlVAXn.mjs.map +1 -0
- package/dist/runnerConfig-BjtulXBI.d.mts +57 -0
- package/dist/runnerConfig-BjtulXBI.d.mts.map +1 -0
- package/dist/runnerConfig-D2tGq5ZM.cjs +367 -0
- package/dist/runnerConfig-D2tGq5ZM.cjs.map +1 -0
- package/dist/runnerDependencies-DruByBGS.mjs +647 -0
- package/dist/runnerDependencies-DruByBGS.mjs.map +1 -0
- package/dist/runnerDependencies-KMZigIxB.cjs +667 -0
- package/dist/runnerDependencies-KMZigIxB.cjs.map +1 -0
- package/dist/runnerDependencies.cjs +3 -0
- package/dist/runnerDependencies.d.mts +2 -0
- package/dist/runnerDependencies.mjs +2 -0
- package/dist/runnerNamer-B8FH7n2v.mjs +44 -0
- package/dist/runnerNamer-B8FH7n2v.mjs.map +1 -0
- package/dist/runnerNamer-we1XVYfM.cjs +49 -0
- package/dist/runnerNamer-we1XVYfM.cjs.map +1 -0
- package/dist/runnerNamer.cjs +3 -0
- package/dist/runnerNamer.d.mts +11 -0
- package/dist/runnerNamer.d.mts.map +1 -0
- package/dist/runnerNamer.mjs +2 -0
- package/dist/runnerPaths-BTEuzxw0.cjs +471 -0
- package/dist/runnerPaths-BTEuzxw0.cjs.map +1 -0
- package/dist/runnerPaths-B_dAAeco.mjs +451 -0
- package/dist/runnerPaths-B_dAAeco.mjs.map +1 -0
- package/dist/runnerPaths.cjs +3 -0
- package/dist/runnerPaths.d.mts +50 -0
- package/dist/runnerPaths.d.mts.map +1 -0
- package/dist/runnerPaths.mjs +2 -0
- package/dist/runnerRegistry-BmkZTJHi.cjs +371 -0
- package/dist/runnerRegistry-BmkZTJHi.cjs.map +1 -0
- package/dist/runnerRegistry-CeOHEjBv.mjs +339 -0
- package/dist/runnerRegistry-CeOHEjBv.mjs.map +1 -0
- package/dist/runnerRegistry-CtJiD02R.d.mts +88 -0
- package/dist/runnerRegistry-CtJiD02R.d.mts.map +1 -0
- package/dist/runnerRegistry.cjs +4 -0
- package/dist/runnerRegistry.d.mts +3 -0
- package/dist/runnerRegistry.mjs +2 -0
- package/dist/runnerSettings-DiV1lvzx.d.mts +34 -0
- package/dist/runnerSettings-DiV1lvzx.d.mts.map +1 -0
- package/dist/services/lifeline/client.cjs +52 -0
- package/dist/services/lifeline/client.cjs.map +1 -0
- package/dist/services/lifeline/client.d.mts +22 -0
- package/dist/services/lifeline/client.d.mts.map +1 -0
- package/dist/services/lifeline/client.mjs +48 -0
- package/dist/services/lifeline/client.mjs.map +1 -0
- package/dist/services/runnerSupervisor/index.cjs +207 -0
- package/dist/services/runnerSupervisor/index.cjs.map +1 -0
- package/dist/services/runnerSupervisor/index.d.mts +71 -0
- package/dist/services/runnerSupervisor/index.d.mts.map +1 -0
- package/dist/services/runnerSupervisor/index.mjs +195 -0
- package/dist/services/runnerSupervisor/index.mjs.map +1 -0
- package/dist/session-BC43uEyG.cjs +29 -0
- package/dist/session-BC43uEyG.cjs.map +1 -0
- package/dist/session-Chsb_Nh2.mjs +18 -0
- package/dist/session-Chsb_Nh2.mjs.map +1 -0
- package/dist/session-DE2Acesp.mjs +6 -0
- package/dist/session-DE2Acesp.mjs.map +1 -0
- package/dist/session-SfIPtdZO.cjs +11 -0
- package/dist/session-SfIPtdZO.cjs.map +1 -0
- package/dist/session.cjs +6 -0
- package/dist/session.d.mts +11 -0
- package/dist/session.d.mts.map +1 -0
- package/dist/session.mjs +3 -0
- package/dist/spawner-BxFsINXP.cjs +68 -0
- package/dist/spawner-BxFsINXP.cjs.map +1 -0
- package/dist/spawner-CdrN_HNk.d.mts +35 -0
- package/dist/spawner-CdrN_HNk.d.mts.map +1 -0
- package/dist/spawner-LoVsn55-.mjs +63 -0
- package/dist/spawner-LoVsn55-.mjs.map +1 -0
- package/dist/spawner.cjs +3 -0
- package/dist/spawner.d.mts +9 -0
- package/dist/spawner.d.mts.map +1 -0
- package/dist/spawner.mjs +2 -0
- package/dist/type-BXM03qSw.d.mts +44 -0
- package/dist/type-BXM03qSw.d.mts.map +1 -0
- package/dist/type-C31e1gla.d.mts +43 -0
- package/dist/type-C31e1gla.d.mts.map +1 -0
- package/dist/type-vWQNTahw.d.mts +12 -0
- package/dist/type-vWQNTahw.d.mts.map +1 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.mts +3 -0
- package/dist/types.mjs +1 -0
- package/llms.txt +9 -0
- package/package.json +303 -0
- package/skills/doom-runner/SKILL.md +78 -0
- package/src/prompts/po-use-runner/SKILL.md +47 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { createProcessRegistryService, normalizeRepositoryPath, resolveProcessTags } from "@agimon-ai/foundation-process-registry";
|
|
6
|
+
import { resolveRootSessionId } from "@agimon-ai/po-core/childProcess";
|
|
7
|
+
//#region src/constants/runnerRegistry.ts
|
|
8
|
+
const SERVICE_TYPE = "tool";
|
|
9
|
+
const SERVICE_PREFIX = "doom-runner:";
|
|
10
|
+
const RUNNER_TAG = "doom-runner";
|
|
11
|
+
const REGISTRY_PATH_ENV = "PROCESS_REGISTRY_PATH";
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/services/runnerRecord/index.ts
|
|
14
|
+
/** The disk record's shape guard, shared by the registry and every reader of its files. */
|
|
15
|
+
function isRunnerRecord(value) {
|
|
16
|
+
if (typeof value !== "object" || value === null) return false;
|
|
17
|
+
const record = value;
|
|
18
|
+
return typeof record.id === "string" && typeof record.name === "string" && typeof record.pid === "number" && typeof record.command === "string" && typeof record.cwd === "string" && typeof record.logPath === "string" && typeof record.sessionId === "string" && typeof record.startedAt === "string" && (record.state === "running" || record.state === "completed") && typeof record.promoted === "boolean" && (record.backend === "rmux" || record.backend === "tmux" || record.backend === "native");
|
|
19
|
+
}
|
|
20
|
+
/** What the agent reads when a runner it sent to the background exits. Shared by the Pi and server hosts. */
|
|
21
|
+
function formatRunnerFinished(record) {
|
|
22
|
+
const outcome = record.exit?.reason ?? record.state;
|
|
23
|
+
const code = record.exit?.code === null || record.exit?.code === void 0 ? "" : `, exit code ${record.exit.code}`;
|
|
24
|
+
return [
|
|
25
|
+
`Background runner ${record.name} exited: ${outcome}${code}.`,
|
|
26
|
+
`Runner ID: ${record.id}`,
|
|
27
|
+
`Log: ${record.logPath}`,
|
|
28
|
+
`Inspect: doom-runner logs ${record.id}`
|
|
29
|
+
].join("\n");
|
|
30
|
+
}
|
|
31
|
+
/** Compact footer text. An absent value removes the group entirely. */
|
|
32
|
+
function formatRunnerStatus(count) {
|
|
33
|
+
return count > 0 ? `Runners ${count} ●` : void 0;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/services/runnerRegistry/index.ts
|
|
37
|
+
/** Opens the SQLite-backed registry the rest of the monorepo already shares. */
|
|
38
|
+
function createDefaultProcessRegistry(env = process.env) {
|
|
39
|
+
return createProcessRegistryService(env[REGISTRY_PATH_ENV]);
|
|
40
|
+
}
|
|
41
|
+
function sessionTag(sessionId) {
|
|
42
|
+
return `session:${sessionId}`;
|
|
43
|
+
}
|
|
44
|
+
function readString(record, key) {
|
|
45
|
+
const value = record.metadata?.[key];
|
|
46
|
+
return typeof value === "string" ? value : "";
|
|
47
|
+
}
|
|
48
|
+
function readNumber(record, key) {
|
|
49
|
+
const value = record.metadata?.[key];
|
|
50
|
+
return typeof value === "number" ? value : 0;
|
|
51
|
+
}
|
|
52
|
+
const runCommand = promisify(execFile);
|
|
53
|
+
/**
|
|
54
|
+
* The process start time the registry stores to prove a pid is still the
|
|
55
|
+
* process it registered.
|
|
56
|
+
*
|
|
57
|
+
* The registry resolves it itself when the caller omits it, and it does so with
|
|
58
|
+
* `execFileSync`, which stops this process for the length of a `ps` on every
|
|
59
|
+
* runner that starts. Reading it here costs the same subprocess and blocks
|
|
60
|
+
* nothing: the command is already running by the time a runner is registered.
|
|
61
|
+
*
|
|
62
|
+
* The value has to match what the registry would have read, so the command and
|
|
63
|
+
* the trimming are deliberately identical to its own.
|
|
64
|
+
*/
|
|
65
|
+
async function processStartedAt(pid) {
|
|
66
|
+
if (process.platform === "win32") return void 0;
|
|
67
|
+
try {
|
|
68
|
+
const { stdout } = await runCommand("ps", [
|
|
69
|
+
"-o",
|
|
70
|
+
"lstart=",
|
|
71
|
+
"-p",
|
|
72
|
+
String(pid)
|
|
73
|
+
], { encoding: "utf8" });
|
|
74
|
+
return stdout.trim() || void 0;
|
|
75
|
+
} catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const listenersBySession = /* @__PURE__ */ new Map();
|
|
80
|
+
var RunnerRegistry = class {
|
|
81
|
+
paths;
|
|
82
|
+
processControl;
|
|
83
|
+
registry;
|
|
84
|
+
environment;
|
|
85
|
+
subscriptions = /* @__PURE__ */ new Set();
|
|
86
|
+
constructor(paths, processControl, registry, environment) {
|
|
87
|
+
this.paths = paths;
|
|
88
|
+
this.processControl = processControl;
|
|
89
|
+
this.registry = registry;
|
|
90
|
+
this.environment = environment;
|
|
91
|
+
}
|
|
92
|
+
async register(input) {
|
|
93
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
const rootSessionId = resolveRootSessionId(input.sessionId, this.environment);
|
|
95
|
+
const identity = await processStartedAt(input.pid);
|
|
96
|
+
const response = await this.registry.registerProcess({
|
|
97
|
+
repositoryPath: this.repositoryPath(),
|
|
98
|
+
serviceName: `${SERVICE_PREFIX}${input.id}`,
|
|
99
|
+
serviceType: SERVICE_TYPE,
|
|
100
|
+
pid: input.pid,
|
|
101
|
+
command: input.command,
|
|
102
|
+
...identity === void 0 ? {} : { processStartedAt: identity },
|
|
103
|
+
tags: resolveProcessTags([
|
|
104
|
+
RUNNER_TAG,
|
|
105
|
+
`runner:${input.id}`,
|
|
106
|
+
sessionTag(input.sessionId)
|
|
107
|
+
]),
|
|
108
|
+
metadata: {
|
|
109
|
+
id: input.id,
|
|
110
|
+
name: input.name,
|
|
111
|
+
command: input.command,
|
|
112
|
+
cwd: input.cwd,
|
|
113
|
+
logPath: input.logPath,
|
|
114
|
+
interactive: input.interactive,
|
|
115
|
+
sessionId: input.sessionId,
|
|
116
|
+
rootSessionId,
|
|
117
|
+
backend: input.backend,
|
|
118
|
+
...input.backendTarget ? { backendTarget: input.backendTarget } : {},
|
|
119
|
+
startedAt,
|
|
120
|
+
hostPid: process.pid
|
|
121
|
+
},
|
|
122
|
+
force: true
|
|
123
|
+
});
|
|
124
|
+
if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);
|
|
125
|
+
const record = {
|
|
126
|
+
id: input.id,
|
|
127
|
+
name: input.name,
|
|
128
|
+
pid: input.pid,
|
|
129
|
+
command: input.command,
|
|
130
|
+
cwd: input.cwd,
|
|
131
|
+
logPath: input.logPath,
|
|
132
|
+
interactive: input.interactive,
|
|
133
|
+
sessionId: input.sessionId,
|
|
134
|
+
rootSessionId,
|
|
135
|
+
startedAt,
|
|
136
|
+
state: "running",
|
|
137
|
+
promoted: false,
|
|
138
|
+
backend: input.backend,
|
|
139
|
+
...input.backendTarget ? { backendTarget: input.backendTarget } : {},
|
|
140
|
+
hostPid: process.pid
|
|
141
|
+
};
|
|
142
|
+
this.writeRecord(record);
|
|
143
|
+
this.notify(record.sessionId);
|
|
144
|
+
return record;
|
|
145
|
+
}
|
|
146
|
+
async list() {
|
|
147
|
+
return (await this.registry.listProcesses({
|
|
148
|
+
repositoryPath: this.repositoryPath(),
|
|
149
|
+
serviceType: SERVICE_TYPE,
|
|
150
|
+
tags: resolveProcessTags([RUNNER_TAG])
|
|
151
|
+
})).map(toRunnerRecord);
|
|
152
|
+
}
|
|
153
|
+
async listAcrossRepositories() {
|
|
154
|
+
return (await this.registry.listProcesses({
|
|
155
|
+
serviceType: SERVICE_TYPE,
|
|
156
|
+
tags: resolveProcessTags([RUNNER_TAG])
|
|
157
|
+
})).map(toRunnerRecord);
|
|
158
|
+
}
|
|
159
|
+
async listBySession(sessionId) {
|
|
160
|
+
return (await this.list()).filter((record) => record.sessionId === sessionId);
|
|
161
|
+
}
|
|
162
|
+
async listByRootSession(rootSessionId) {
|
|
163
|
+
return (await this.list()).filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);
|
|
164
|
+
}
|
|
165
|
+
async listAll(sessionId) {
|
|
166
|
+
this.paths.ensureDirectories(sessionId);
|
|
167
|
+
const records = [];
|
|
168
|
+
const directory = this.paths.stateDirectory(sessionId);
|
|
169
|
+
for (const entry of fs.readdirSync(directory)) {
|
|
170
|
+
if (!isPrimaryMetadataEntry(entry)) continue;
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(fs.readFileSync(`${directory}/${entry}`, "utf8"));
|
|
173
|
+
if (isRunnerRecord(parsed)) records.push(parsed);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));
|
|
179
|
+
}
|
|
180
|
+
async get(id, sessionId) {
|
|
181
|
+
const target = this.paths.statePathFor(id, sessionId);
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
184
|
+
if (!isRunnerRecord(parsed) || parsed.id !== id) {
|
|
185
|
+
process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
return parsed;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error.code === "ENOENT") return void 0;
|
|
191
|
+
process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async markPromoted(id) {
|
|
196
|
+
const record = await this.get(id);
|
|
197
|
+
if (!record) return void 0;
|
|
198
|
+
const promoted = {
|
|
199
|
+
...record,
|
|
200
|
+
promoted: true
|
|
201
|
+
};
|
|
202
|
+
this.writeRecord(promoted);
|
|
203
|
+
this.notify(promoted.sessionId);
|
|
204
|
+
return promoted;
|
|
205
|
+
}
|
|
206
|
+
async requestTermination(id, intent, sessionId) {
|
|
207
|
+
const record = await this.get(id, sessionId);
|
|
208
|
+
if (!record) return void 0;
|
|
209
|
+
if (record.state === "completed") return record;
|
|
210
|
+
if (intent !== void 0 && record.terminationIntent !== void 0) return record;
|
|
211
|
+
const { terminationIntent: _previous, ...rest } = record;
|
|
212
|
+
const updated = {
|
|
213
|
+
...rest,
|
|
214
|
+
...intent ? { terminationIntent: intent } : {}
|
|
215
|
+
};
|
|
216
|
+
this.writeRecord(updated);
|
|
217
|
+
return updated;
|
|
218
|
+
}
|
|
219
|
+
async complete(id, outcome, sessionId) {
|
|
220
|
+
const persisted = await this.get(id, sessionId);
|
|
221
|
+
if (persisted?.state === "completed") {
|
|
222
|
+
await this.release(id, persisted.sessionId);
|
|
223
|
+
return persisted;
|
|
224
|
+
}
|
|
225
|
+
const active = await this.list();
|
|
226
|
+
const record = persisted ?? active.find((candidate) => candidate.id === id && (sessionId === void 0 || candidate.sessionId === sessionId));
|
|
227
|
+
if (!record) {
|
|
228
|
+
if (!(sessionId !== void 0 && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId))) await this.release(id, sessionId);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const completed = {
|
|
232
|
+
...record,
|
|
233
|
+
state: "completed",
|
|
234
|
+
exit: {
|
|
235
|
+
...outcome,
|
|
236
|
+
...outcome.signal !== null && outcome.terminationReason === void 0 && record.terminationIntent === void 0 ? { terminationReason: "external_signal" } : {},
|
|
237
|
+
...record.terminationIntent,
|
|
238
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
this.writeRecord(completed);
|
|
242
|
+
await this.release(id, completed.sessionId);
|
|
243
|
+
return completed;
|
|
244
|
+
}
|
|
245
|
+
async release(id, sessionId) {
|
|
246
|
+
const ownedSessionId = sessionId ?? (await this.list()).find((record) => record.id === id)?.sessionId ?? (await this.get(id))?.sessionId;
|
|
247
|
+
const response = await this.registry.releaseProcess({
|
|
248
|
+
repositoryPath: this.repositoryPath(),
|
|
249
|
+
serviceName: `${SERVICE_PREFIX}${id}`,
|
|
250
|
+
serviceType: SERVICE_TYPE,
|
|
251
|
+
kill: false,
|
|
252
|
+
releasePort: false,
|
|
253
|
+
force: true
|
|
254
|
+
});
|
|
255
|
+
if (!response.success && !response.error?.includes("No matching process entry")) throw new Error(response.error ?? `Failed to release runner ${id}`);
|
|
256
|
+
if (ownedSessionId !== void 0) this.notify(ownedSessionId);
|
|
257
|
+
}
|
|
258
|
+
async pruneDead() {
|
|
259
|
+
const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));
|
|
260
|
+
for (const record of dead) await this.complete(record.id, {
|
|
261
|
+
reason: "backend_lost",
|
|
262
|
+
code: null,
|
|
263
|
+
signal: null
|
|
264
|
+
}, record.sessionId);
|
|
265
|
+
return dead.map((record) => record.id);
|
|
266
|
+
}
|
|
267
|
+
subscribe(listener, sessionId) {
|
|
268
|
+
const subscribed = () => listener();
|
|
269
|
+
const registration = {
|
|
270
|
+
key: this.subscriptionKey(sessionId),
|
|
271
|
+
listener: subscribed
|
|
272
|
+
};
|
|
273
|
+
const listeners = listenersBySession.get(registration.key) ?? /* @__PURE__ */ new Set();
|
|
274
|
+
listeners.add(subscribed);
|
|
275
|
+
listenersBySession.set(registration.key, listeners);
|
|
276
|
+
this.subscriptions.add(registration);
|
|
277
|
+
return () => this.removeSubscription(registration);
|
|
278
|
+
}
|
|
279
|
+
close() {
|
|
280
|
+
for (const registration of this.subscriptions) this.removeSubscription(registration);
|
|
281
|
+
this.registry.close();
|
|
282
|
+
}
|
|
283
|
+
notify(sessionId) {
|
|
284
|
+
for (const listener of listenersBySession.get(this.subscriptionKey(sessionId)) ?? []) try {
|
|
285
|
+
listener();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
process.emitWarning(`Runner registry listener failed: ${String(error)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
removeSubscription(registration) {
|
|
291
|
+
const listeners = listenersBySession.get(registration.key);
|
|
292
|
+
listeners?.delete(registration.listener);
|
|
293
|
+
if (listeners?.size === 0) listenersBySession.delete(registration.key);
|
|
294
|
+
this.subscriptions.delete(registration);
|
|
295
|
+
}
|
|
296
|
+
subscriptionKey(sessionId) {
|
|
297
|
+
return JSON.stringify([path.resolve(this.paths.stateDirectory(sessionId)), sessionId]);
|
|
298
|
+
}
|
|
299
|
+
repositoryPath() {
|
|
300
|
+
return normalizeRepositoryPath(this.paths.repositoryPath());
|
|
301
|
+
}
|
|
302
|
+
writeRecord(record) {
|
|
303
|
+
this.paths.ensureDirectories(record.sessionId);
|
|
304
|
+
const target = this.paths.statePathFor(record.id, record.sessionId);
|
|
305
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
306
|
+
fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
307
|
+
fs.renameSync(temporary, target);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
/** Every field is read back from metadata this class wrote on registration. */
|
|
311
|
+
function toRunnerRecord(record) {
|
|
312
|
+
return {
|
|
313
|
+
id: readString(record, "id"),
|
|
314
|
+
name: readString(record, "name"),
|
|
315
|
+
pid: record.pid,
|
|
316
|
+
command: readString(record, "command"),
|
|
317
|
+
cwd: readString(record, "cwd"),
|
|
318
|
+
logPath: readString(record, "logPath"),
|
|
319
|
+
interactive: record.metadata?.interactive === true,
|
|
320
|
+
sessionId: readString(record, "sessionId"),
|
|
321
|
+
...readString(record, "rootSessionId") ? { rootSessionId: readString(record, "rootSessionId") } : {},
|
|
322
|
+
startedAt: readString(record, "startedAt"),
|
|
323
|
+
state: "running",
|
|
324
|
+
promoted: false,
|
|
325
|
+
backend: readBackend(record.metadata?.backend),
|
|
326
|
+
...readString(record, "backendTarget") ? { backendTarget: readString(record, "backendTarget") } : {},
|
|
327
|
+
hostPid: readNumber(record, "hostPid")
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function readBackend(value) {
|
|
331
|
+
return value === "rmux" || value === "tmux" ? value : "native";
|
|
332
|
+
}
|
|
333
|
+
function isPrimaryMetadataEntry(entry) {
|
|
334
|
+
return entry.endsWith(".json") && !entry.endsWith(".command.json") && !entry.endsWith(".exit.json");
|
|
335
|
+
}
|
|
336
|
+
//#endregion
|
|
337
|
+
export { isRunnerRecord as a, formatRunnerStatus as i, createDefaultProcessRegistry as n, formatRunnerFinished as r, RunnerRegistry as t };
|
|
338
|
+
|
|
339
|
+
//# sourceMappingURL=runnerRegistry-CeOHEjBv.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerRegistry-CeOHEjBv.mjs","names":[],"sources":["../src/constants/runnerRegistry.ts","../src/services/runnerRecord/index.ts","../src/services/runnerRegistry/index.ts"],"sourcesContent":["export const SERVICE_TYPE = 'tool';\nexport const SERVICE_PREFIX = 'doom-runner:';\nexport const RUNNER_TAG = 'doom-runner';\nexport const REGISTRY_PATH_ENV = 'PROCESS_REGISTRY_PATH';\nexport const JSON_EXTENSION = '.json';\nexport const COMMAND_SIDECAR_SUFFIX = '.command.json';\nexport const EXIT_SIDECAR_SUFFIX = '.exit.json';\nexport const NOT_FOUND_ERROR_CODE = 'ENOENT';\n","import type { RunnerRecord } from '../../types/runnerRegistry';\n\n/** The disk record's shape guard, shared by the registry and every reader of its files. */\nexport function isRunnerRecord(value: unknown): value is RunnerRecord {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Partial<RunnerRecord>;\n return (\n typeof record.id === 'string' &&\n typeof record.name === 'string' &&\n typeof record.pid === 'number' &&\n typeof record.command === 'string' &&\n typeof record.cwd === 'string' &&\n typeof record.logPath === 'string' &&\n typeof record.sessionId === 'string' &&\n typeof record.startedAt === 'string' &&\n (record.state === 'running' || record.state === 'completed') &&\n typeof record.promoted === 'boolean' &&\n (record.backend === 'rmux' || record.backend === 'tmux' || record.backend === 'native')\n );\n}\n\n/** What the agent reads when a runner it sent to the background exits. Shared by the Pi and server hosts. */\nexport function formatRunnerFinished(record: RunnerRecord): string {\n const outcome = record.exit?.reason ?? record.state;\n const code = record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n return [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n}\n\n/** Compact footer text. An absent value removes the group entirely. */\nexport function formatRunnerStatus(count: number): string | undefined {\n return count > 0 ? `Runners ${count} ●` : undefined;\n}\n\nexport function formatRunnerFooterContribution(count: number): { fullText: string; compactText: string } | undefined {\n return count > 0 ? { fullText: `Runner ${count}`, compactText: `R${count}` } : undefined;\n}\n","import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport {\n createProcessRegistryService,\n normalizeRepositoryPath,\n type ProcessRegistryRecord,\n type ProcessRegistryService,\n resolveProcessTags,\n} from '@agimon-ai/foundation-process-registry';\nimport { resolveRootSessionId } from '@agimon-ai/po-core/childProcess';\n\nimport {\n COMMAND_SIDECAR_SUFFIX,\n EXIT_SIDECAR_SUFFIX,\n JSON_EXTENSION,\n NOT_FOUND_ERROR_CODE,\n REGISTRY_PATH_ENV,\n RUNNER_TAG,\n SERVICE_PREFIX,\n SERVICE_TYPE,\n} from '../../constants/runnerRegistry';\nimport type { IProcessControl } from '../../types/processControl';\nimport type {\n CompleteRunnerInput,\n IRunnerRegistry,\n RegisterRunnerInput,\n RunnerRecord,\n RunnerTerminationIntent,\n} from '../../types/runnerRegistry';\nimport type { IRunnerPaths } from '../runnerPaths/type';\nimport { isRunnerRecord } from '../runnerRecord';\n\n/** The slice of the shared registry service this wrapper depends on. */\nexport type ProcessRegistryPort = Pick<\n ProcessRegistryService,\n 'registerProcess' | 'releaseProcess' | 'listProcesses' | 'close'\n>;\n\n/** Opens the SQLite-backed registry the rest of the monorepo already shares. */\nexport function createDefaultProcessRegistry(\n env: Readonly<Record<string, string | undefined>> = process.env,\n): ProcessRegistryPort {\n return createProcessRegistryService(env[REGISTRY_PATH_ENV]);\n}\n\nfunction sessionTag(sessionId: string): string {\n return `session:${sessionId}`;\n}\n\nfunction readString(record: ProcessRegistryRecord, key: string): string {\n const value = record.metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumber(record: ProcessRegistryRecord, key: string): number {\n const value = record.metadata?.[key];\n return typeof value === 'number' ? value : 0;\n}\n\nconst runCommand = promisify(execFile);\n\n/**\n * The process start time the registry stores to prove a pid is still the\n * process it registered.\n *\n * The registry resolves it itself when the caller omits it, and it does so with\n * `execFileSync`, which stops this process for the length of a `ps` on every\n * runner that starts. Reading it here costs the same subprocess and blocks\n * nothing: the command is already running by the time a runner is registered.\n *\n * The value has to match what the registry would have read, so the command and\n * the trimming are deliberately identical to its own.\n */\nasync function processStartedAt(pid: number): Promise<string | undefined> {\n if (process.platform === 'win32') return undefined;\n try {\n const { stdout } = await runCommand('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8' });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\ntype ListenerRegistration = Readonly<{ key: string; listener: () => void }>;\n\n// Facet replacement can leave an in-flight tool and its publisher on different registry instances.\nconst listenersBySession = new Map<string, Set<() => void>>();\n\nexport class RunnerRegistry implements IRunnerRegistry {\n private readonly subscriptions = new Set<ListenerRegistration>();\n\n constructor(\n private readonly paths: IRunnerPaths,\n private readonly processControl: IProcessControl,\n private readonly registry: ProcessRegistryPort,\n private readonly environment: Readonly<Record<string, string | undefined>>,\n ) {}\n\n async register(input: RegisterRunnerInput): Promise<RunnerRecord> {\n const startedAt = new Date().toISOString();\n const rootSessionId = resolveRootSessionId(input.sessionId, this.environment);\n const identity = await processStartedAt(input.pid);\n const response = await this.registry.registerProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${input.id}`,\n serviceType: SERVICE_TYPE,\n pid: input.pid,\n command: input.command,\n ...(identity === undefined ? {} : { processStartedAt: identity }),\n tags: resolveProcessTags([RUNNER_TAG, `runner:${input.id}`, sessionTag(input.sessionId)]),\n metadata: {\n id: input.id,\n name: input.name,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n startedAt,\n hostPid: process.pid,\n },\n // The caller has already resolved name collisions against live records,\n // so anything still holding this name is stale.\n force: true,\n });\n if (!response.success) throw new Error(response.error ?? `Failed to register runner ${input.name}`);\n const record: RunnerRecord = {\n id: input.id,\n name: input.name,\n pid: input.pid,\n command: input.command,\n cwd: input.cwd,\n logPath: input.logPath,\n interactive: input.interactive,\n sessionId: input.sessionId,\n rootSessionId,\n startedAt,\n state: 'running',\n promoted: false,\n backend: input.backend,\n ...(input.backendTarget ? { backendTarget: input.backendTarget } : {}),\n hostPid: process.pid,\n };\n this.writeRecord(record);\n this.notify(record.sessionId);\n return record;\n }\n\n async list(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n repositoryPath: this.repositoryPath(),\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listAcrossRepositories(): Promise<RunnerRecord[]> {\n const records = await this.registry.listProcesses({\n serviceType: SERVICE_TYPE,\n tags: resolveProcessTags([RUNNER_TAG]),\n });\n return records.map(toRunnerRecord);\n }\n\n async listBySession(sessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => record.sessionId === sessionId);\n }\n\n async listByRootSession(rootSessionId: string): Promise<RunnerRecord[]> {\n const records = await this.list();\n return records.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId);\n }\n\n async listAll(sessionId?: string): Promise<RunnerRecord[]> {\n this.paths.ensureDirectories(sessionId);\n const records: RunnerRecord[] = [];\n const directory = this.paths.stateDirectory(sessionId);\n for (const entry of fs.readdirSync(directory)) {\n if (!isPrimaryMetadataEntry(entry)) continue;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(`${directory}/${entry}`, 'utf8'));\n if (isRunnerRecord(parsed)) records.push(parsed);\n } catch (error) {\n process.emitWarning(`Could not read runner metadata ${entry}: ${String(error)}`);\n }\n }\n return records.sort((left, right) => right.startedAt.localeCompare(left.startedAt));\n }\n\n async get(id: string, sessionId?: string): Promise<RunnerRecord | undefined> {\n const target = this.paths.statePathFor(id, sessionId);\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (!isRunnerRecord(parsed) || parsed.id !== id) {\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: invalid runner record`);\n return undefined;\n }\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === NOT_FOUND_ERROR_CODE) return undefined;\n process.emitWarning(`Could not read runner metadata ${path.basename(target)}: ${String(error)}`);\n return undefined;\n }\n }\n\n async markPromoted(id: string): Promise<RunnerRecord | undefined> {\n const record = await this.get(id);\n if (!record) return undefined;\n const promoted = { ...record, promoted: true };\n this.writeRecord(promoted);\n this.notify(promoted.sessionId);\n return promoted;\n }\n\n async requestTermination(\n id: string,\n intent: RunnerTerminationIntent | undefined,\n sessionId?: string,\n ): Promise<RunnerRecord | undefined> {\n const record = await this.get(id, sessionId);\n if (!record) return undefined;\n if (record.state === 'completed') return record;\n if (intent !== undefined && record.terminationIntent !== undefined) return record;\n const { terminationIntent: _previous, ...rest } = record;\n const updated: RunnerRecord = { ...rest, ...(intent ? { terminationIntent: intent } : {}) };\n this.writeRecord(updated);\n return updated;\n }\n\n async complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined> {\n const persisted = await this.get(id, sessionId);\n if (persisted?.state === 'completed') {\n await this.release(id, persisted.sessionId);\n return persisted;\n }\n const active = await this.list();\n const record =\n persisted ??\n active.find((candidate) => candidate.id === id && (sessionId === undefined || candidate.sessionId === sessionId));\n if (!record) {\n const belongsToAnotherSession =\n sessionId !== undefined && active.some((candidate) => candidate.id === id && candidate.sessionId !== sessionId);\n if (!belongsToAnotherSession) await this.release(id, sessionId);\n return undefined;\n }\n const completed: RunnerRecord = {\n ...record,\n state: 'completed',\n exit: {\n ...outcome,\n ...(outcome.signal !== null && outcome.terminationReason === undefined && record.terminationIntent === undefined\n ? { terminationReason: 'external_signal' as const }\n : {}),\n ...record.terminationIntent,\n finishedAt: new Date().toISOString(),\n },\n };\n this.writeRecord(completed);\n await this.release(id, completed.sessionId);\n return completed;\n }\n\n async release(id: string, sessionId?: string): Promise<void> {\n const activeSessionId = sessionId ?? (await this.list()).find((record) => record.id === id)?.sessionId;\n const ownedSessionId = activeSessionId ?? (await this.get(id))?.sessionId;\n const response = await this.registry.releaseProcess({\n repositoryPath: this.repositoryPath(),\n serviceName: `${SERVICE_PREFIX}${id}`,\n serviceType: SERVICE_TYPE,\n kill: false,\n releasePort: false,\n force: true,\n });\n if (!response.success && !response.error?.includes('No matching process entry')) {\n throw new Error(response.error ?? `Failed to release runner ${id}`);\n }\n if (ownedSessionId !== undefined) this.notify(ownedSessionId);\n }\n\n async pruneDead(): Promise<string[]> {\n const dead = (await this.list()).filter((record) => !this.processControl.isAlive(record.pid));\n for (const record of dead) {\n await this.complete(record.id, { reason: 'backend_lost', code: null, signal: null }, record.sessionId);\n }\n return dead.map((record) => record.id);\n }\n\n subscribe(listener: () => void, sessionId: string): () => void {\n const subscribed = (): void => listener();\n const registration = { key: this.subscriptionKey(sessionId), listener: subscribed };\n const listeners = listenersBySession.get(registration.key) ?? new Set<() => void>();\n listeners.add(subscribed);\n listenersBySession.set(registration.key, listeners);\n this.subscriptions.add(registration);\n return () => this.removeSubscription(registration);\n }\n\n close(): void {\n for (const registration of this.subscriptions) this.removeSubscription(registration);\n this.registry.close();\n }\n\n private notify(sessionId: string): void {\n for (const listener of listenersBySession.get(this.subscriptionKey(sessionId)) ?? []) {\n try {\n listener();\n } catch (error) {\n process.emitWarning(`Runner registry listener failed: ${String(error)}`);\n }\n }\n }\n\n private removeSubscription(registration: ListenerRegistration): void {\n const listeners = listenersBySession.get(registration.key);\n listeners?.delete(registration.listener);\n if (listeners?.size === 0) listenersBySession.delete(registration.key);\n this.subscriptions.delete(registration);\n }\n\n private subscriptionKey(sessionId: string): string {\n return JSON.stringify([path.resolve(this.paths.stateDirectory(sessionId)), sessionId]);\n }\n\n private repositoryPath(): string {\n return normalizeRepositoryPath(this.paths.repositoryPath());\n }\n\n private writeRecord(record: RunnerRecord): void {\n this.paths.ensureDirectories(record.sessionId);\n const target = this.paths.statePathFor(record.id, record.sessionId);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\\n`, 'utf8');\n fs.renameSync(temporary, target);\n }\n}\n\n/** Every field is read back from metadata this class wrote on registration. */\nfunction toRunnerRecord(record: ProcessRegistryRecord): RunnerRecord {\n return {\n id: readString(record, 'id'),\n name: readString(record, 'name'),\n pid: record.pid,\n command: readString(record, 'command'),\n cwd: readString(record, 'cwd'),\n logPath: readString(record, 'logPath'),\n interactive: record.metadata?.interactive === true,\n sessionId: readString(record, 'sessionId'),\n ...(readString(record, 'rootSessionId') ? { rootSessionId: readString(record, 'rootSessionId') } : {}),\n startedAt: readString(record, 'startedAt'),\n state: 'running',\n promoted: false,\n backend: readBackend(record.metadata?.backend),\n ...(readString(record, 'backendTarget') ? { backendTarget: readString(record, 'backendTarget') } : {}),\n hostPid: readNumber(record, 'hostPid'),\n };\n}\n\nfunction readBackend(value: unknown): RunnerRecord['backend'] {\n return value === 'rmux' || value === 'tmux' ? value : 'native';\n}\n\nfunction isPrimaryMetadataEntry(entry: string): boolean {\n return (\n entry.endsWith(JSON_EXTENSION) && !entry.endsWith(COMMAND_SIDECAR_SUFFIX) && !entry.endsWith(EXIT_SIDECAR_SUFFIX)\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/types/runnerRegistry.d.ts
|
|
2
|
+
type RunnerBackend = 'rmux' | 'tmux' | 'native';
|
|
3
|
+
type RunnerState = 'running' | 'completed';
|
|
4
|
+
type RunnerExitReason = 'completed' | 'failed' | 'signaled' | 'stopped' | 'timed_out' | 'launcher_error' | 'backend_lost';
|
|
5
|
+
type RunnerTerminationReason = 'timeout' | 'user_stop' | 'request_cancelled' | 'parent_session_cleanup' | 'owner_lost' | 'external_signal';
|
|
6
|
+
type RunnerTerminationIntent = {
|
|
7
|
+
reason: 'stopped' | 'timed_out';
|
|
8
|
+
terminationReason: Exclude<RunnerTerminationReason, 'external_signal'>;
|
|
9
|
+
stopReason?: string;
|
|
10
|
+
};
|
|
11
|
+
interface RunnerExit {
|
|
12
|
+
reason: RunnerExitReason;
|
|
13
|
+
code: number | null;
|
|
14
|
+
signal: NodeJS.Signals | null;
|
|
15
|
+
terminationReason?: RunnerTerminationReason;
|
|
16
|
+
stopReason?: string;
|
|
17
|
+
finishedAt: string;
|
|
18
|
+
}
|
|
19
|
+
/** A command this extension supervises, retained after completion for CLI access. */
|
|
20
|
+
interface RunnerRecord {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** Process group leader, the pid signals are sent to. */
|
|
24
|
+
pid: number;
|
|
25
|
+
command: string;
|
|
26
|
+
cwd: string;
|
|
27
|
+
logPath: string;
|
|
28
|
+
interactive: boolean;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
/** Root Pi session shared by the process tree. Ownership remains with sessionId. */
|
|
31
|
+
rootSessionId?: string;
|
|
32
|
+
startedAt: string;
|
|
33
|
+
state: RunnerState;
|
|
34
|
+
promoted: boolean;
|
|
35
|
+
backend: RunnerBackend;
|
|
36
|
+
backendTarget?: string;
|
|
37
|
+
exit?: RunnerExit;
|
|
38
|
+
/** Persisted before sending a stop signal so other observers retain the caller's intent. */
|
|
39
|
+
terminationIntent?: RunnerTerminationIntent;
|
|
40
|
+
/** pid of the pi process that launched it, for orphan detection. */
|
|
41
|
+
hostPid: number;
|
|
42
|
+
}
|
|
43
|
+
interface RegisterRunnerInput {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
pid: number;
|
|
47
|
+
command: string;
|
|
48
|
+
cwd: string;
|
|
49
|
+
logPath: string;
|
|
50
|
+
interactive: boolean;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
backend: RunnerBackend;
|
|
53
|
+
backendTarget?: string;
|
|
54
|
+
}
|
|
55
|
+
interface CompleteRunnerInput {
|
|
56
|
+
reason: RunnerExitReason;
|
|
57
|
+
code: number | null;
|
|
58
|
+
signal: NodeJS.Signals | null;
|
|
59
|
+
terminationReason?: RunnerTerminationReason;
|
|
60
|
+
stopReason?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Worktree-scoped active registry with session-scoped retained history. */
|
|
63
|
+
interface IRunnerRegistry {
|
|
64
|
+
register(input: RegisterRunnerInput): Promise<RunnerRecord>;
|
|
65
|
+
list(): Promise<RunnerRecord[]>;
|
|
66
|
+
/** Active doom-runner records across repository scopes, used only for safe legacy-store cleanup. */
|
|
67
|
+
listAcrossRepositories(): Promise<RunnerRecord[]>;
|
|
68
|
+
/** Active records started by one session, used for scoped bulk stop and shutdown. */
|
|
69
|
+
listBySession(sessionId: string): Promise<RunnerRecord[]>;
|
|
70
|
+
/** Active records visible to every descendant of one root Pi session. */
|
|
71
|
+
listByRootSession(rootSessionId: string): Promise<RunnerRecord[]>;
|
|
72
|
+
/** Active and recently completed records retained for one session. */
|
|
73
|
+
listAll(sessionId?: string): Promise<RunnerRecord[]>;
|
|
74
|
+
get(id: string, sessionId?: string): Promise<RunnerRecord | undefined>;
|
|
75
|
+
markPromoted(id: string): Promise<RunnerRecord | undefined>;
|
|
76
|
+
requestTermination(id: string, intent: RunnerTerminationIntent | undefined, sessionId?: string): Promise<RunnerRecord | undefined>;
|
|
77
|
+
complete(id: string, outcome: CompleteRunnerInput, sessionId?: string): Promise<RunnerRecord | undefined>;
|
|
78
|
+
/** Removes the active process entry while retaining run metadata. */
|
|
79
|
+
release(id: string, sessionId?: string): Promise<void>;
|
|
80
|
+
/** Marks entries whose process is gone as lost. Returns their ids. */
|
|
81
|
+
pruneDead(): Promise<string[]>;
|
|
82
|
+
/** Notifies in-process consumers after this session's registry changes. */
|
|
83
|
+
subscribe(listener: () => void, sessionId: string): () => void;
|
|
84
|
+
close(): void;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { RunnerTerminationIntent as a, RunnerRecord as i, IRunnerRegistry as n, RegisterRunnerInput as r, CompleteRunnerInput as t };
|
|
88
|
+
//# sourceMappingURL=runnerRegistry-CtJiD02R.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerRegistry-CtJiD02R.d.mts","names":[],"sources":["../src/types/runnerRegistry.ts"],"mappings":";KAAY;KACA;KACA;KASA;KAOA;EACV;EACA,mBAAmB,QAAQ;EAC3B;;UAGe;EACf,QAAQ;EACR;EACA,QAAQ,OAAO;EACf,oBAAoB;EACpB;EACA;;;UAIe;EACf;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA,OAAO;EACP;EACA,SAAS;EACT;EACA,OAAO;;EAEP,oBAAoB;;EAEpB;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;EACT;;UAGe;EACf,QAAQ;EACR;EACA,QAAQ,OAAO;EACf,oBAAoB;EACpB;;;UAIe;EACf,SAAS,OAAO,sBAAsB,QAAQ;EAC9C,QAAQ,QAAQ;;EAEhB,0BAA0B,QAAQ;;EAElC,cAAc,oBAAoB,QAAQ;;EAE1C,kBAAkB,wBAAwB,QAAQ;;EAElD,QAAQ,qBAAqB,QAAQ;EACrC,IAAI,YAAY,qBAAqB,QAAQ;EAC7C,aAAa,aAAa,QAAQ;EAClC,mBACE,YACA,QAAQ,qCACR,qBACC,QAAQ;EACX,SAAS,YAAY,SAAS,qBAAqB,qBAAqB,QAAQ;;EAEhF,QAAQ,YAAY,qBAAqB;;EAEzC,aAAa;;EAEb,UAAU,sBAAsB;EAChC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_runnerRegistry = require("./runnerRegistry-BmkZTJHi.cjs");
|
|
3
|
+
exports.RunnerRegistry = require_runnerRegistry.RunnerRegistry;
|
|
4
|
+
exports.createDefaultProcessRegistry = require_runnerRegistry.createDefaultProcessRegistry;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { i as RunnerRecord, n as IRunnerRegistry, r as RegisterRunnerInput } from "./runnerRegistry-CtJiD02R.mjs";
|
|
2
|
+
import { n as RunnerRegistry, r as createDefaultProcessRegistry, t as ProcessRegistryPort } from "./index-DBz0DxnL.mjs";
|
|
3
|
+
export { type IRunnerRegistry, type ProcessRegistryPort, type RegisterRunnerInput, type RunnerRecord, RunnerRegistry, createDefaultProcessRegistry };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/types/runnerSettings.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Project-local extension configuration, read from `<CONFIG_DIR_NAME>/po-runner.json`.
|
|
4
|
+
*
|
|
5
|
+
* Pi's convention for extension configuration is one JSON file per extension
|
|
6
|
+
* under the config directory, honoured only for a trusted project. Values here
|
|
7
|
+
* are the project's baseline: an environment variable still wins for a single
|
|
8
|
+
* invocation, and a per-command pragma wins over both.
|
|
9
|
+
*/
|
|
10
|
+
interface RunnerSettings {
|
|
11
|
+
/** Byte ceiling for one tool result. */
|
|
12
|
+
readonly maxResultBytes?: number;
|
|
13
|
+
/** Line ceiling for one tool result. */
|
|
14
|
+
readonly maxResultLines?: number;
|
|
15
|
+
/** Byte ceiling for the result of a command that succeeded. */
|
|
16
|
+
readonly successMaxResultBytes?: number;
|
|
17
|
+
/** Token ceiling for one tool result. */
|
|
18
|
+
readonly maxResultTokens?: number;
|
|
19
|
+
/** Token ceiling for the result of a command that succeeded. */
|
|
20
|
+
readonly successMaxResultTokens?: number;
|
|
21
|
+
/** Share of the budget spent on the leading excerpt, between 0 and 1. */
|
|
22
|
+
readonly headRatio?: number;
|
|
23
|
+
/** Extra severity patterns appended to the built-in matcher. */
|
|
24
|
+
readonly errorPatterns?: readonly string[];
|
|
25
|
+
/** Distinct failures rescued from the elided middle. */
|
|
26
|
+
readonly errorMaxEntries?: number;
|
|
27
|
+
/** Variants joined inside one bracket before the rest become a count. */
|
|
28
|
+
readonly errorMaxVariantsJoined?: number;
|
|
29
|
+
/** Share of the budget reserved for rescued errors, between 0 and 1. */
|
|
30
|
+
readonly errorBudgetRatio?: number;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { RunnerSettings as t };
|
|
34
|
+
//# sourceMappingURL=runnerSettings-DiV1lvzx.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runnerSettings-DiV1lvzx.d.mts","names":[],"sources":["../src/types/runnerSettings.ts"],"mappings":";;;;;;;;;UAQiB;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../../rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let node_net = require("node:net");
|
|
4
|
+
node_net = require_rolldown_runtime.__toESM(node_net, 1);
|
|
5
|
+
//#region src/services/lifeline/client.ts
|
|
6
|
+
/** Environment variable carrying the lifeline socket path to every runner supervisor. */
|
|
7
|
+
const LIFELINE_ENV = "DOOM_RUNNER_LIFELINE";
|
|
8
|
+
/**
|
|
9
|
+
* Reports the owning pi process dying to a runner's supervisor.
|
|
10
|
+
*
|
|
11
|
+
* A connected Unix socket drops the moment the owner's descriptors close, which
|
|
12
|
+
* happens on every exit path including SIGKILL, so it is the one death signal a
|
|
13
|
+
* graceful shutdown handler cannot provide. Connecting never blocks, the close
|
|
14
|
+
* edge arrives through the event loop rather than a poll, and an unref'd socket
|
|
15
|
+
* can never keep the supervisor alive: the watcher is therefore incapable of
|
|
16
|
+
* outliving the process that installed it.
|
|
17
|
+
*
|
|
18
|
+
* A refused or missing socket means the owner died before this runner started,
|
|
19
|
+
* which is reported the same way as a later death rather than left to hang.
|
|
20
|
+
*
|
|
21
|
+
* Runners run unwatched when no lifeline is armed, so a missing environment
|
|
22
|
+
* variable can never be mistaken for a dead owner.
|
|
23
|
+
*/
|
|
24
|
+
function watchOwner(onOwnerLost, target = process.env[LIFELINE_ENV]) {
|
|
25
|
+
if (!target) return () => void 0;
|
|
26
|
+
let settled = false;
|
|
27
|
+
const settle = () => {
|
|
28
|
+
if (settled) return;
|
|
29
|
+
settled = true;
|
|
30
|
+
onOwnerLost();
|
|
31
|
+
};
|
|
32
|
+
let connection;
|
|
33
|
+
try {
|
|
34
|
+
connection = node_net.default.connect(target);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
process.emitWarning(`Could not watch the doom-runner lifeline: ${String(error)}`);
|
|
37
|
+
return () => void 0;
|
|
38
|
+
}
|
|
39
|
+
connection.unref();
|
|
40
|
+
connection.on("connect", () => connection.resume());
|
|
41
|
+
connection.on("error", settle);
|
|
42
|
+
connection.on("close", settle);
|
|
43
|
+
return () => {
|
|
44
|
+
settled = true;
|
|
45
|
+
connection.destroy();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
exports.LIFELINE_ENV = LIFELINE_ENV;
|
|
50
|
+
exports.watchOwner = watchOwner;
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["net"],"sources":["../../../src/services/lifeline/client.ts"],"sourcesContent":["import net from 'node:net';\n\n/** Environment variable carrying the lifeline socket path to every runner supervisor. */\nexport const LIFELINE_ENV = 'DOOM_RUNNER_LIFELINE';\n\n/**\n * Reports the owning pi process dying to a runner's supervisor.\n *\n * A connected Unix socket drops the moment the owner's descriptors close, which\n * happens on every exit path including SIGKILL, so it is the one death signal a\n * graceful shutdown handler cannot provide. Connecting never blocks, the close\n * edge arrives through the event loop rather than a poll, and an unref'd socket\n * can never keep the supervisor alive: the watcher is therefore incapable of\n * outliving the process that installed it.\n *\n * A refused or missing socket means the owner died before this runner started,\n * which is reported the same way as a later death rather than left to hang.\n *\n * Runners run unwatched when no lifeline is armed, so a missing environment\n * variable can never be mistaken for a dead owner.\n */\nexport function watchOwner(\n onOwnerLost: () => void,\n target: string | undefined = process.env[LIFELINE_ENV],\n): () => void {\n if (!target) return () => undefined;\n\n let settled = false;\n const settle = (): void => {\n if (settled) return;\n settled = true;\n onOwnerLost();\n };\n\n let connection: net.Socket;\n try {\n connection = net.connect(target);\n } catch (error) {\n process.emitWarning(`Could not watch the doom-runner lifeline: ${String(error)}`);\n return () => undefined;\n }\n\n // The supervisor's own exit must never wait on this socket.\n connection.unref();\n // The owner never sends anything, so drain rather than accumulate.\n connection.on('connect', () => connection.resume());\n connection.on('error', settle);\n connection.on('close', settle);\n\n return () => {\n settled = true;\n connection.destroy();\n };\n}\n"],"mappings":";;;;;;AAGA,MAAa,eAAe;;;;;;;;;;;;;;;;;AAkB5B,SAAgB,WACd,aACA,SAA6B,QAAQ,IAAI,eAC7B;CACZ,IAAI,CAAC,QAAQ,aAAa,KAAA;CAE1B,IAAI,UAAU;CACd,MAAM,eAAqB;EACzB,IAAI,SAAS;EACb,UAAU;EACV,YAAY;CACd;CAEA,IAAI;CACJ,IAAI;EACF,aAAaA,SAAAA,QAAI,QAAQ,MAAM;CACjC,SAAS,OAAO;EACd,QAAQ,YAAY,6CAA6C,OAAO,KAAK,GAAG;EAChF,aAAa,KAAA;CACf;CAGA,WAAW,MAAM;CAEjB,WAAW,GAAG,iBAAiB,WAAW,OAAO,CAAC;CAClD,WAAW,GAAG,SAAS,MAAM;CAC7B,WAAW,GAAG,SAAS,MAAM;CAE7B,aAAa;EACX,UAAU;EACV,WAAW,QAAQ;CACrB;AACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/services/lifeline/client.d.ts
|
|
2
|
+
/** Environment variable carrying the lifeline socket path to every runner supervisor. */
|
|
3
|
+
export declare const LIFELINE_ENV = "DOOM_RUNNER_LIFELINE";
|
|
4
|
+
/**
|
|
5
|
+
* Reports the owning pi process dying to a runner's supervisor.
|
|
6
|
+
*
|
|
7
|
+
* A connected Unix socket drops the moment the owner's descriptors close, which
|
|
8
|
+
* happens on every exit path including SIGKILL, so it is the one death signal a
|
|
9
|
+
* graceful shutdown handler cannot provide. Connecting never blocks, the close
|
|
10
|
+
* edge arrives through the event loop rather than a poll, and an unref'd socket
|
|
11
|
+
* can never keep the supervisor alive: the watcher is therefore incapable of
|
|
12
|
+
* outliving the process that installed it.
|
|
13
|
+
*
|
|
14
|
+
* A refused or missing socket means the owner died before this runner started,
|
|
15
|
+
* which is reported the same way as a later death rather than left to hang.
|
|
16
|
+
*
|
|
17
|
+
* Runners run unwatched when no lifeline is armed, so a missing environment
|
|
18
|
+
* variable can never be mistaken for a dead owner.
|
|
19
|
+
*/
|
|
20
|
+
export declare function watchOwner(onOwnerLost: () => void, target?: string | undefined): () => void;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../../../src/services/lifeline/client.ts"],"mappings":";;qBAGa;;;;;;;;;;;;;;;;;wBAkBG,WACd,yBACA"}
|