@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,48 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
//#region src/services/lifeline/client.ts
|
|
3
|
+
/** Environment variable carrying the lifeline socket path to every runner supervisor. */
|
|
4
|
+
const LIFELINE_ENV = "DOOM_RUNNER_LIFELINE";
|
|
5
|
+
/**
|
|
6
|
+
* Reports the owning pi process dying to a runner's supervisor.
|
|
7
|
+
*
|
|
8
|
+
* A connected Unix socket drops the moment the owner's descriptors close, which
|
|
9
|
+
* happens on every exit path including SIGKILL, so it is the one death signal a
|
|
10
|
+
* graceful shutdown handler cannot provide. Connecting never blocks, the close
|
|
11
|
+
* edge arrives through the event loop rather than a poll, and an unref'd socket
|
|
12
|
+
* can never keep the supervisor alive: the watcher is therefore incapable of
|
|
13
|
+
* outliving the process that installed it.
|
|
14
|
+
*
|
|
15
|
+
* A refused or missing socket means the owner died before this runner started,
|
|
16
|
+
* which is reported the same way as a later death rather than left to hang.
|
|
17
|
+
*
|
|
18
|
+
* Runners run unwatched when no lifeline is armed, so a missing environment
|
|
19
|
+
* variable can never be mistaken for a dead owner.
|
|
20
|
+
*/
|
|
21
|
+
function watchOwner(onOwnerLost, target = process.env[LIFELINE_ENV]) {
|
|
22
|
+
if (!target) return () => void 0;
|
|
23
|
+
let settled = false;
|
|
24
|
+
const settle = () => {
|
|
25
|
+
if (settled) return;
|
|
26
|
+
settled = true;
|
|
27
|
+
onOwnerLost();
|
|
28
|
+
};
|
|
29
|
+
let connection;
|
|
30
|
+
try {
|
|
31
|
+
connection = net.connect(target);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
process.emitWarning(`Could not watch the doom-runner lifeline: ${String(error)}`);
|
|
34
|
+
return () => void 0;
|
|
35
|
+
}
|
|
36
|
+
connection.unref();
|
|
37
|
+
connection.on("connect", () => connection.resume());
|
|
38
|
+
connection.on("error", settle);
|
|
39
|
+
connection.on("close", settle);
|
|
40
|
+
return () => {
|
|
41
|
+
settled = true;
|
|
42
|
+
connection.destroy();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { LIFELINE_ENV, watchOwner };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"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,aAAa,IAAI,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,207 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../../rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
7
|
+
let node_url = require("node:url");
|
|
8
|
+
//#region src/services/runnerSupervisor/index.ts
|
|
9
|
+
/**
|
|
10
|
+
* Settings that make a terminal safe for a caller with no keyboard.
|
|
11
|
+
*
|
|
12
|
+
* Commands run inside an rmux pane, so their stdout is a real tty. Git and
|
|
13
|
+
* friends take that as permission to open a pager, and `less` then waits at
|
|
14
|
+
* `(END)` for a keypress nobody is there to send, so the run hangs until its
|
|
15
|
+
* timeout. The same tty invites credential prompts, which hang the same way.
|
|
16
|
+
* A conventional piped stdout disables both automatically; a pane has to be
|
|
17
|
+
* told. A command that genuinely wants a pager can still ask for one, as in
|
|
18
|
+
* `git -c core.pager=less log`.
|
|
19
|
+
*/
|
|
20
|
+
const NO_TERMINAL_INPUT_ENV = {
|
|
21
|
+
GIT_PAGER: "cat",
|
|
22
|
+
PAGER: "cat",
|
|
23
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
24
|
+
};
|
|
25
|
+
const SPEC_MODE = 384;
|
|
26
|
+
const NOT_FOUND_ERROR_CODE = "ENOENT";
|
|
27
|
+
function supervisorPaths(directory, id) {
|
|
28
|
+
return {
|
|
29
|
+
spec: node_path.default.join(directory, `${id}.command.json`),
|
|
30
|
+
gate: node_path.default.join(directory, `${id}.gate`),
|
|
31
|
+
exit: node_path.default.join(directory, `${id}.exit.json`),
|
|
32
|
+
logDone: node_path.default.join(directory, `${id}.log.done`)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function writeCommandSpec(target, spec) {
|
|
36
|
+
node_fs.default.writeFileSync(target, `${JSON.stringify(spec)}\n`, { mode: SPEC_MODE });
|
|
37
|
+
}
|
|
38
|
+
function readExitMetadata(target) {
|
|
39
|
+
try {
|
|
40
|
+
const value = JSON.parse(node_fs.default.readFileSync(target, "utf8"));
|
|
41
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
42
|
+
const metadata = value;
|
|
43
|
+
return {
|
|
44
|
+
code: typeof metadata.code === "number" ? metadata.code : null,
|
|
45
|
+
signal: typeof metadata.signal === "string" ? metadata.signal : null,
|
|
46
|
+
...metadata.reason === "stopped" ? { reason: metadata.reason } : {},
|
|
47
|
+
...metadata.terminationReason === "owner_lost" ? { terminationReason: metadata.terminationReason } : {},
|
|
48
|
+
...typeof metadata.stopReason === "string" ? { stopReason: metadata.stopReason } : {}
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.code !== NOT_FOUND_ERROR_CODE) process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function cleanupSupervisorFiles(paths) {
|
|
56
|
+
for (const target of Object.values(paths)) try {
|
|
57
|
+
node_fs.default.unlinkSync(target);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolves an executable entry point by walking up to the `bin` directory that
|
|
64
|
+
* ships beside this module. Source and published layouts nest services at
|
|
65
|
+
* different depths. Source modules select built subprocess entries so Node can resolve their imports. A hardcoded depth stays silent
|
|
66
|
+
* until the shell tries to exec it.
|
|
67
|
+
*/
|
|
68
|
+
function runtimeEntry(name, moduleUrl = require("url").pathToFileURL(__filename).href) {
|
|
69
|
+
const modulePath = (0, node_url.fileURLToPath)(moduleUrl);
|
|
70
|
+
const source = node_path.default.extname(modulePath) === ".ts";
|
|
71
|
+
const extension = "mjs";
|
|
72
|
+
let directory = node_path.default.dirname(modulePath);
|
|
73
|
+
while (true) {
|
|
74
|
+
const entry = source ? node_path.default.join(directory, "dist", "bin", `${name}.${extension}`) : node_path.default.join(directory, "bin", `${name}.${extension}`);
|
|
75
|
+
if (node_fs.default.existsSync(entry)) return node_fs.default.realpathSync(entry);
|
|
76
|
+
const parent = node_path.default.dirname(directory);
|
|
77
|
+
if (parent === directory) break;
|
|
78
|
+
directory = parent;
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`Cannot find bin/${name}.${extension} by walking up from ${modulePath}.`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Replaces the shell rather than nesting under it, so the pid handed back is the
|
|
84
|
+
* supervisor's and a group signal reaches it directly.
|
|
85
|
+
*/
|
|
86
|
+
function supervisorCommand(paths) {
|
|
87
|
+
return `exec ${shellJoin([
|
|
88
|
+
process.execPath,
|
|
89
|
+
runtimeEntry("runnerHost"),
|
|
90
|
+
paths.spec,
|
|
91
|
+
paths.gate,
|
|
92
|
+
paths.exit
|
|
93
|
+
])}`;
|
|
94
|
+
}
|
|
95
|
+
function shellJoin(values) {
|
|
96
|
+
return values.map((value) => `'${value.replaceAll("'", `'"'"'`)}'`).join(" ");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* One watcher per directory, shared by every waiter inside it.
|
|
100
|
+
*
|
|
101
|
+
* A session's runners share a runs directory, and `PO_LOG_DIR` makes every
|
|
102
|
+
* session using it share one. A watcher per waited file would then put a watcher
|
|
103
|
+
* per in-flight runner on the same directory, each woken by every other runner's
|
|
104
|
+
* sidecar writes, so the watchers are pooled and reference counted instead.
|
|
105
|
+
*/
|
|
106
|
+
const directoryWatches = /* @__PURE__ */ new Map();
|
|
107
|
+
function acquireDirectoryWatch(directory) {
|
|
108
|
+
const existing = directoryWatches.get(directory);
|
|
109
|
+
if (existing) {
|
|
110
|
+
existing.references += 1;
|
|
111
|
+
return existing;
|
|
112
|
+
}
|
|
113
|
+
const entry = {
|
|
114
|
+
watcher: void 0,
|
|
115
|
+
waiters: /* @__PURE__ */ new Map(),
|
|
116
|
+
references: 1
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
entry.watcher = node_fs.default.watch(directory, (_event, changed) => {
|
|
120
|
+
const named = changed === null ? void 0 : String(changed);
|
|
121
|
+
const targets = named === void 0 ? [...entry.waiters.values()] : [entry.waiters.get(named)];
|
|
122
|
+
for (const listeners of targets) {
|
|
123
|
+
if (!listeners) continue;
|
|
124
|
+
for (const listener of listeners) listener();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
entry.watcher.on("error", () => void 0);
|
|
128
|
+
} catch {
|
|
129
|
+
entry.watcher = void 0;
|
|
130
|
+
}
|
|
131
|
+
directoryWatches.set(directory, entry);
|
|
132
|
+
return entry;
|
|
133
|
+
}
|
|
134
|
+
function releaseDirectoryWatch(directory, entry) {
|
|
135
|
+
entry.references -= 1;
|
|
136
|
+
if (entry.references > 0) return;
|
|
137
|
+
entry.watcher?.close();
|
|
138
|
+
entry.watcher = void 0;
|
|
139
|
+
entry.waiters.clear();
|
|
140
|
+
directoryWatches.delete(directory);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reports a supervisor sidecar landing on disk without polling for it.
|
|
144
|
+
*
|
|
145
|
+
* The sidecars are written once and never rewritten, so the directory's own
|
|
146
|
+
* create event answers the only question a waiter has. Polling for them costs a
|
|
147
|
+
* full poll interval on every run that finishes inside one, and the runs that
|
|
148
|
+
* finish fastest are the ones that pay that tax most often.
|
|
149
|
+
*
|
|
150
|
+
* The watch is best effort: a platform or a directory that cannot deliver events
|
|
151
|
+
* leaves every slice to time out, which is exactly the poll the caller already
|
|
152
|
+
* runs as its fallback.
|
|
153
|
+
*/
|
|
154
|
+
function watchForFile(filePath) {
|
|
155
|
+
const directory = node_path.default.dirname(filePath);
|
|
156
|
+
const name = node_path.default.basename(filePath);
|
|
157
|
+
const entry = acquireDirectoryWatch(directory);
|
|
158
|
+
let released = false;
|
|
159
|
+
let found = false;
|
|
160
|
+
let notify;
|
|
161
|
+
const settle = () => {
|
|
162
|
+
if (found || !node_fs.default.existsSync(filePath)) return;
|
|
163
|
+
found = true;
|
|
164
|
+
notify?.();
|
|
165
|
+
};
|
|
166
|
+
const listeners = entry.waiters.get(name) ?? /* @__PURE__ */ new Set();
|
|
167
|
+
listeners.add(settle);
|
|
168
|
+
entry.waiters.set(name, listeners);
|
|
169
|
+
settle();
|
|
170
|
+
return {
|
|
171
|
+
async appeared(withinMs) {
|
|
172
|
+
if (found) return true;
|
|
173
|
+
return await new Promise((resolve) => {
|
|
174
|
+
const timer = setTimeout(() => {
|
|
175
|
+
notify = void 0;
|
|
176
|
+
settle();
|
|
177
|
+
resolve(found);
|
|
178
|
+
}, withinMs);
|
|
179
|
+
notify = () => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
notify = void 0;
|
|
182
|
+
resolve(true);
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
close() {
|
|
187
|
+
if (released) return;
|
|
188
|
+
released = true;
|
|
189
|
+
notify = void 0;
|
|
190
|
+
listeners.delete(settle);
|
|
191
|
+
if (listeners.size === 0) entry.waiters.delete(name);
|
|
192
|
+
releaseDirectoryWatch(directory, entry);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
exports.NO_TERMINAL_INPUT_ENV = NO_TERMINAL_INPUT_ENV;
|
|
198
|
+
exports.cleanupSupervisorFiles = cleanupSupervisorFiles;
|
|
199
|
+
exports.readExitMetadata = readExitMetadata;
|
|
200
|
+
exports.runtimeEntry = runtimeEntry;
|
|
201
|
+
exports.shellJoin = shellJoin;
|
|
202
|
+
exports.supervisorCommand = supervisorCommand;
|
|
203
|
+
exports.supervisorPaths = supervisorPaths;
|
|
204
|
+
exports.watchForFile = watchForFile;
|
|
205
|
+
exports.writeCommandSpec = writeCommandSpec;
|
|
206
|
+
|
|
207
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","fs","fileURLToPath"],"sources":["../../../src/services/runnerSupervisor/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { ExitResult } from '../../types/spawner';\n/** What a supervisor needs to run one command. */\nexport interface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\n/** Exit status the supervisor records once its command is gone. */\nexport type ExitMetadata = ExitResult;\n\n/** Sidecars a supervised run reads and writes beside its log. */\nexport interface SupervisorPaths {\n spec: string;\n gate: string;\n exit: string;\n logDone: string;\n}\n\n/**\n * Settings that make a terminal safe for a caller with no keyboard.\n *\n * Commands run inside an rmux pane, so their stdout is a real tty. Git and\n * friends take that as permission to open a pager, and `less` then waits at\n * `(END)` for a keypress nobody is there to send, so the run hangs until its\n * timeout. The same tty invites credential prompts, which hang the same way.\n * A conventional piped stdout disables both automatically; a pane has to be\n * told. A command that genuinely wants a pager can still ask for one, as in\n * `git -c core.pager=less log`.\n */\nexport const NO_TERMINAL_INPUT_ENV = {\n GIT_PAGER: 'cat',\n PAGER: 'cat',\n GIT_TERMINAL_PROMPT: '0',\n};\n\nconst SPEC_MODE = 0o600;\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nexport function supervisorPaths(directory: string, id: string): SupervisorPaths {\n return {\n spec: path.join(directory, `${id}.command.json`),\n gate: path.join(directory, `${id}.gate`),\n exit: path.join(directory, `${id}.exit.json`),\n logDone: path.join(directory, `${id}.log.done`),\n };\n}\n\nexport function writeCommandSpec(target: string, spec: CommandSpec): void {\n fs.writeFileSync(target, `${JSON.stringify(spec)}\\n`, { mode: SPEC_MODE });\n}\n\nexport function readExitMetadata(target: string): ExitMetadata | undefined {\n try {\n const value: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (typeof value !== 'object' || value === null) return undefined;\n const metadata = value as Partial<ExitMetadata>;\n const code = typeof metadata.code === 'number' ? metadata.code : null;\n const signal = typeof metadata.signal === 'string' ? (metadata.signal as NodeJS.Signals) : null;\n return {\n code,\n signal,\n ...(metadata.reason === 'stopped' ? { reason: metadata.reason } : {}),\n ...(metadata.terminationReason === 'owner_lost' ? { terminationReason: metadata.terminationReason } : {}),\n ...(typeof metadata.stopReason === 'string' ? { stopReason: metadata.stopReason } : {}),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);\n }\n return undefined;\n }\n}\n\nexport function cleanupSupervisorFiles(paths: SupervisorPaths): void {\n for (const target of Object.values(paths)) {\n try {\n fs.unlinkSync(target);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));\n }\n }\n}\n\n/**\n * Resolves an executable entry point by walking up to the `bin` directory that\n * ships beside this module. Source and published layouts nest services at\n * different depths. Source modules select built subprocess entries so Node can resolve their imports. A hardcoded depth stays silent\n * until the shell tries to exec it.\n */\nexport function runtimeEntry(name: 'runnerHost' | 'logSink', moduleUrl = import.meta.url): string {\n const modulePath = fileURLToPath(moduleUrl);\n const source = path.extname(modulePath) === '.ts';\n const extension = 'mjs';\n let directory = path.dirname(modulePath);\n\n while (true) {\n const entry = source\n ? path.join(directory, 'dist', 'bin', `${name}.${extension}`)\n : path.join(directory, 'bin', `${name}.${extension}`);\n if (fs.existsSync(entry)) return fs.realpathSync(entry);\n\n const parent = path.dirname(directory);\n if (parent === directory) break;\n directory = parent;\n }\n\n throw new Error(`Cannot find bin/${name}.${extension} by walking up from ${modulePath}.`);\n}\n\n/**\n * Replaces the shell rather than nesting under it, so the pid handed back is the\n * supervisor's and a group signal reaches it directly.\n */\nexport function supervisorCommand(paths: SupervisorPaths): string {\n return `exec ${shellJoin([process.execPath, runtimeEntry('runnerHost'), paths.spec, paths.gate, paths.exit])}`;\n}\n\nexport function shellJoin(values: readonly string[]): string {\n return values.map((value) => `'${value.replaceAll(\"'\", `'\"'\"'`)}'`).join(' ');\n}\n\n/** A pending appearance of one supervisor sidecar, watched rather than polled for. */\nexport interface FileWatch {\n /** Resolves true once the file exists, or false when the slice elapses. */\n appeared(withinMs: number): Promise<boolean>;\n close(): void;\n}\n\ninterface DirectoryWatch {\n watcher: fs.FSWatcher | undefined;\n readonly waiters: Map<string, Set<() => void>>;\n references: number;\n}\n\n/**\n * One watcher per directory, shared by every waiter inside it.\n *\n * A session's runners share a runs directory, and `PO_LOG_DIR` makes every\n * session using it share one. A watcher per waited file would then put a watcher\n * per in-flight runner on the same directory, each woken by every other runner's\n * sidecar writes, so the watchers are pooled and reference counted instead.\n */\nconst directoryWatches = new Map<string, DirectoryWatch>();\n\nfunction acquireDirectoryWatch(directory: string): DirectoryWatch {\n const existing = directoryWatches.get(directory);\n if (existing) {\n existing.references += 1;\n return existing;\n }\n const entry: DirectoryWatch = { watcher: undefined, waiters: new Map(), references: 1 };\n try {\n entry.watcher = fs.watch(directory, (_event, changed) => {\n // A rename can arrive without a name, and then the only safe reading is\n // that any waiter in this directory might be the one it belongs to.\n const named = changed === null ? undefined : String(changed);\n const targets = named === undefined ? [...entry.waiters.values()] : [entry.waiters.get(named)];\n for (const listeners of targets) {\n if (!listeners) continue;\n for (const listener of listeners) listener();\n }\n });\n // A watcher that cannot report is a watcher whose slices time out, which the\n // caller's own probe already covers.\n entry.watcher.on('error', () => undefined);\n } catch {\n entry.watcher = undefined;\n }\n directoryWatches.set(directory, entry);\n return entry;\n}\n\nfunction releaseDirectoryWatch(directory: string, entry: DirectoryWatch): void {\n entry.references -= 1;\n if (entry.references > 0) return;\n entry.watcher?.close();\n entry.watcher = undefined;\n entry.waiters.clear();\n directoryWatches.delete(directory);\n}\n\n/**\n * Reports a supervisor sidecar landing on disk without polling for it.\n *\n * The sidecars are written once and never rewritten, so the directory's own\n * create event answers the only question a waiter has. Polling for them costs a\n * full poll interval on every run that finishes inside one, and the runs that\n * finish fastest are the ones that pay that tax most often.\n *\n * The watch is best effort: a platform or a directory that cannot deliver events\n * leaves every slice to time out, which is exactly the poll the caller already\n * runs as its fallback.\n */\nexport function watchForFile(filePath: string): FileWatch {\n const directory = path.dirname(filePath);\n const name = path.basename(filePath);\n const entry = acquireDirectoryWatch(directory);\n let released = false;\n let found = false;\n let notify: (() => void) | undefined;\n\n const settle = (): void => {\n if (found || !fs.existsSync(filePath)) return;\n found = true;\n notify?.();\n };\n const listeners = entry.waiters.get(name) ?? new Set<() => void>();\n listeners.add(settle);\n entry.waiters.set(name, listeners);\n\n // The file can land between opening the watcher and the first wait, and that\n // create event has nobody listening for it yet.\n settle();\n\n return {\n async appeared(withinMs: number): Promise<boolean> {\n if (found) return true;\n return await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n notify = undefined;\n // The watcher is an optimisation, not the answer. A create event that\n // was coalesced, reported under the temporary name of a rename, or\n // never delivered at all must not outlive the slice it belongs to, so\n // every slice ends by asking the filesystem itself.\n settle();\n resolve(found);\n }, withinMs);\n // Deliberately left holding the event loop. A run still being waited on\n // is work, and a process that exits during the gap between two probes\n // drops the result its caller is awaiting.\n notify = () => {\n clearTimeout(timer);\n notify = undefined;\n resolve(true);\n };\n });\n },\n close(): void {\n if (released) return;\n released = true;\n notify = undefined;\n listeners.delete(settle);\n if (listeners.size === 0) entry.waiters.delete(name);\n releaseDirectoryWatch(directory, entry);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { t as ExitResult } from "../../spawner-CdrN_HNk.mjs";
|
|
2
|
+
//#region src/services/runnerSupervisor/index.d.ts
|
|
3
|
+
/** What a supervisor needs to run one command. */
|
|
4
|
+
export interface CommandSpec {
|
|
5
|
+
command: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
env: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/** Exit status the supervisor records once its command is gone. */
|
|
10
|
+
export type ExitMetadata = ExitResult;
|
|
11
|
+
/** Sidecars a supervised run reads and writes beside its log. */
|
|
12
|
+
export interface SupervisorPaths {
|
|
13
|
+
spec: string;
|
|
14
|
+
gate: string;
|
|
15
|
+
exit: string;
|
|
16
|
+
logDone: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Settings that make a terminal safe for a caller with no keyboard.
|
|
20
|
+
*
|
|
21
|
+
* Commands run inside an rmux pane, so their stdout is a real tty. Git and
|
|
22
|
+
* friends take that as permission to open a pager, and `less` then waits at
|
|
23
|
+
* `(END)` for a keypress nobody is there to send, so the run hangs until its
|
|
24
|
+
* timeout. The same tty invites credential prompts, which hang the same way.
|
|
25
|
+
* A conventional piped stdout disables both automatically; a pane has to be
|
|
26
|
+
* told. A command that genuinely wants a pager can still ask for one, as in
|
|
27
|
+
* `git -c core.pager=less log`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const NO_TERMINAL_INPUT_ENV: {
|
|
30
|
+
GIT_PAGER: string;
|
|
31
|
+
PAGER: string;
|
|
32
|
+
GIT_TERMINAL_PROMPT: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function supervisorPaths(directory: string, id: string): SupervisorPaths;
|
|
35
|
+
export declare function writeCommandSpec(target: string, spec: CommandSpec): void;
|
|
36
|
+
export declare function readExitMetadata(target: string): ExitMetadata | undefined;
|
|
37
|
+
export declare function cleanupSupervisorFiles(paths: SupervisorPaths): void;
|
|
38
|
+
/**
|
|
39
|
+
* Resolves an executable entry point by walking up to the `bin` directory that
|
|
40
|
+
* ships beside this module. Source and published layouts nest services at
|
|
41
|
+
* different depths. Source modules select built subprocess entries so Node can resolve their imports. A hardcoded depth stays silent
|
|
42
|
+
* until the shell tries to exec it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function runtimeEntry(name: 'runnerHost' | 'logSink', moduleUrl?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Replaces the shell rather than nesting under it, so the pid handed back is the
|
|
47
|
+
* supervisor's and a group signal reaches it directly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function supervisorCommand(paths: SupervisorPaths): string;
|
|
50
|
+
export declare function shellJoin(values: readonly string[]): string;
|
|
51
|
+
/** A pending appearance of one supervisor sidecar, watched rather than polled for. */
|
|
52
|
+
export interface FileWatch {
|
|
53
|
+
/** Resolves true once the file exists, or false when the slice elapses. */
|
|
54
|
+
appeared(withinMs: number): Promise<boolean>;
|
|
55
|
+
close(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reports a supervisor sidecar landing on disk without polling for it.
|
|
59
|
+
*
|
|
60
|
+
* The sidecars are written once and never rewritten, so the directory's own
|
|
61
|
+
* create event answers the only question a waiter has. Polling for them costs a
|
|
62
|
+
* full poll interval on every run that finishes inside one, and the runs that
|
|
63
|
+
* finish fastest are the ones that pay that tax most often.
|
|
64
|
+
*
|
|
65
|
+
* The watch is best effort: a platform or a directory that cannot deliver events
|
|
66
|
+
* leaves every slice to time out, which is exactly the poll the caller already
|
|
67
|
+
* runs as its fallback.
|
|
68
|
+
*/
|
|
69
|
+
export declare function watchForFile(filePath: string): FileWatch;
|
|
70
|
+
//#endregion
|
|
71
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/runnerSupervisor/index.ts"],"mappings":";;;iBAMiB;EACf;EACA;EACA,KAAK;;;YAIK,eAAe;;iBAGV;EACf;EACA;EACA;EACA;;;;;;;;;;;;;qBAcW;EACX;EACA;EACA;;wBAMc,gBAAgB,mBAAmB,aAAa;wBAShD,iBAAiB,gBAAgB,MAAM;wBAIvC,iBAAiB,iBAAiB;wBAsBlC,uBAAuB,OAAO;;;;;;;wBAgB9B,aAAa,gCAAgC;;;;;wBAwB7C,kBAAkB,OAAO;wBAIzB,UAAU;;iBAKT;;EAEf,SAAS,mBAAmB;EAC5B;;;;;;;;;;;;;;wBAoEc,aAAa,mBAAmB"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/services/runnerSupervisor/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Settings that make a terminal safe for a caller with no keyboard.
|
|
7
|
+
*
|
|
8
|
+
* Commands run inside an rmux pane, so their stdout is a real tty. Git and
|
|
9
|
+
* friends take that as permission to open a pager, and `less` then waits at
|
|
10
|
+
* `(END)` for a keypress nobody is there to send, so the run hangs until its
|
|
11
|
+
* timeout. The same tty invites credential prompts, which hang the same way.
|
|
12
|
+
* A conventional piped stdout disables both automatically; a pane has to be
|
|
13
|
+
* told. A command that genuinely wants a pager can still ask for one, as in
|
|
14
|
+
* `git -c core.pager=less log`.
|
|
15
|
+
*/
|
|
16
|
+
const NO_TERMINAL_INPUT_ENV = {
|
|
17
|
+
GIT_PAGER: "cat",
|
|
18
|
+
PAGER: "cat",
|
|
19
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
20
|
+
};
|
|
21
|
+
const SPEC_MODE = 384;
|
|
22
|
+
const NOT_FOUND_ERROR_CODE = "ENOENT";
|
|
23
|
+
function supervisorPaths(directory, id) {
|
|
24
|
+
return {
|
|
25
|
+
spec: path.join(directory, `${id}.command.json`),
|
|
26
|
+
gate: path.join(directory, `${id}.gate`),
|
|
27
|
+
exit: path.join(directory, `${id}.exit.json`),
|
|
28
|
+
logDone: path.join(directory, `${id}.log.done`)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function writeCommandSpec(target, spec) {
|
|
32
|
+
fs.writeFileSync(target, `${JSON.stringify(spec)}\n`, { mode: SPEC_MODE });
|
|
33
|
+
}
|
|
34
|
+
function readExitMetadata(target) {
|
|
35
|
+
try {
|
|
36
|
+
const value = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
37
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
38
|
+
const metadata = value;
|
|
39
|
+
return {
|
|
40
|
+
code: typeof metadata.code === "number" ? metadata.code : null,
|
|
41
|
+
signal: typeof metadata.signal === "string" ? metadata.signal : null,
|
|
42
|
+
...metadata.reason === "stopped" ? { reason: metadata.reason } : {},
|
|
43
|
+
...metadata.terminationReason === "owner_lost" ? { terminationReason: metadata.terminationReason } : {},
|
|
44
|
+
...typeof metadata.stopReason === "string" ? { stopReason: metadata.stopReason } : {}
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code !== NOT_FOUND_ERROR_CODE) process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function cleanupSupervisorFiles(paths) {
|
|
52
|
+
for (const target of Object.values(paths)) try {
|
|
53
|
+
fs.unlinkSync(target);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolves an executable entry point by walking up to the `bin` directory that
|
|
60
|
+
* ships beside this module. Source and published layouts nest services at
|
|
61
|
+
* different depths. Source modules select built subprocess entries so Node can resolve their imports. A hardcoded depth stays silent
|
|
62
|
+
* until the shell tries to exec it.
|
|
63
|
+
*/
|
|
64
|
+
function runtimeEntry(name, moduleUrl = import.meta.url) {
|
|
65
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
66
|
+
const source = path.extname(modulePath) === ".ts";
|
|
67
|
+
const extension = "mjs";
|
|
68
|
+
let directory = path.dirname(modulePath);
|
|
69
|
+
while (true) {
|
|
70
|
+
const entry = source ? path.join(directory, "dist", "bin", `${name}.${extension}`) : path.join(directory, "bin", `${name}.${extension}`);
|
|
71
|
+
if (fs.existsSync(entry)) return fs.realpathSync(entry);
|
|
72
|
+
const parent = path.dirname(directory);
|
|
73
|
+
if (parent === directory) break;
|
|
74
|
+
directory = parent;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Cannot find bin/${name}.${extension} by walking up from ${modulePath}.`);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Replaces the shell rather than nesting under it, so the pid handed back is the
|
|
80
|
+
* supervisor's and a group signal reaches it directly.
|
|
81
|
+
*/
|
|
82
|
+
function supervisorCommand(paths) {
|
|
83
|
+
return `exec ${shellJoin([
|
|
84
|
+
process.execPath,
|
|
85
|
+
runtimeEntry("runnerHost"),
|
|
86
|
+
paths.spec,
|
|
87
|
+
paths.gate,
|
|
88
|
+
paths.exit
|
|
89
|
+
])}`;
|
|
90
|
+
}
|
|
91
|
+
function shellJoin(values) {
|
|
92
|
+
return values.map((value) => `'${value.replaceAll("'", `'"'"'`)}'`).join(" ");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* One watcher per directory, shared by every waiter inside it.
|
|
96
|
+
*
|
|
97
|
+
* A session's runners share a runs directory, and `PO_LOG_DIR` makes every
|
|
98
|
+
* session using it share one. A watcher per waited file would then put a watcher
|
|
99
|
+
* per in-flight runner on the same directory, each woken by every other runner's
|
|
100
|
+
* sidecar writes, so the watchers are pooled and reference counted instead.
|
|
101
|
+
*/
|
|
102
|
+
const directoryWatches = /* @__PURE__ */ new Map();
|
|
103
|
+
function acquireDirectoryWatch(directory) {
|
|
104
|
+
const existing = directoryWatches.get(directory);
|
|
105
|
+
if (existing) {
|
|
106
|
+
existing.references += 1;
|
|
107
|
+
return existing;
|
|
108
|
+
}
|
|
109
|
+
const entry = {
|
|
110
|
+
watcher: void 0,
|
|
111
|
+
waiters: /* @__PURE__ */ new Map(),
|
|
112
|
+
references: 1
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
entry.watcher = fs.watch(directory, (_event, changed) => {
|
|
116
|
+
const named = changed === null ? void 0 : String(changed);
|
|
117
|
+
const targets = named === void 0 ? [...entry.waiters.values()] : [entry.waiters.get(named)];
|
|
118
|
+
for (const listeners of targets) {
|
|
119
|
+
if (!listeners) continue;
|
|
120
|
+
for (const listener of listeners) listener();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
entry.watcher.on("error", () => void 0);
|
|
124
|
+
} catch {
|
|
125
|
+
entry.watcher = void 0;
|
|
126
|
+
}
|
|
127
|
+
directoryWatches.set(directory, entry);
|
|
128
|
+
return entry;
|
|
129
|
+
}
|
|
130
|
+
function releaseDirectoryWatch(directory, entry) {
|
|
131
|
+
entry.references -= 1;
|
|
132
|
+
if (entry.references > 0) return;
|
|
133
|
+
entry.watcher?.close();
|
|
134
|
+
entry.watcher = void 0;
|
|
135
|
+
entry.waiters.clear();
|
|
136
|
+
directoryWatches.delete(directory);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Reports a supervisor sidecar landing on disk without polling for it.
|
|
140
|
+
*
|
|
141
|
+
* The sidecars are written once and never rewritten, so the directory's own
|
|
142
|
+
* create event answers the only question a waiter has. Polling for them costs a
|
|
143
|
+
* full poll interval on every run that finishes inside one, and the runs that
|
|
144
|
+
* finish fastest are the ones that pay that tax most often.
|
|
145
|
+
*
|
|
146
|
+
* The watch is best effort: a platform or a directory that cannot deliver events
|
|
147
|
+
* leaves every slice to time out, which is exactly the poll the caller already
|
|
148
|
+
* runs as its fallback.
|
|
149
|
+
*/
|
|
150
|
+
function watchForFile(filePath) {
|
|
151
|
+
const directory = path.dirname(filePath);
|
|
152
|
+
const name = path.basename(filePath);
|
|
153
|
+
const entry = acquireDirectoryWatch(directory);
|
|
154
|
+
let released = false;
|
|
155
|
+
let found = false;
|
|
156
|
+
let notify;
|
|
157
|
+
const settle = () => {
|
|
158
|
+
if (found || !fs.existsSync(filePath)) return;
|
|
159
|
+
found = true;
|
|
160
|
+
notify?.();
|
|
161
|
+
};
|
|
162
|
+
const listeners = entry.waiters.get(name) ?? /* @__PURE__ */ new Set();
|
|
163
|
+
listeners.add(settle);
|
|
164
|
+
entry.waiters.set(name, listeners);
|
|
165
|
+
settle();
|
|
166
|
+
return {
|
|
167
|
+
async appeared(withinMs) {
|
|
168
|
+
if (found) return true;
|
|
169
|
+
return await new Promise((resolve) => {
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
notify = void 0;
|
|
172
|
+
settle();
|
|
173
|
+
resolve(found);
|
|
174
|
+
}, withinMs);
|
|
175
|
+
notify = () => {
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
notify = void 0;
|
|
178
|
+
resolve(true);
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
close() {
|
|
183
|
+
if (released) return;
|
|
184
|
+
released = true;
|
|
185
|
+
notify = void 0;
|
|
186
|
+
listeners.delete(settle);
|
|
187
|
+
if (listeners.size === 0) entry.waiters.delete(name);
|
|
188
|
+
releaseDirectoryWatch(directory, entry);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { NO_TERMINAL_INPUT_ENV, cleanupSupervisorFiles, readExitMetadata, runtimeEntry, shellJoin, supervisorCommand, supervisorPaths, watchForFile, writeCommandSpec };
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/runnerSupervisor/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { ExitResult } from '../../types/spawner';\n/** What a supervisor needs to run one command. */\nexport interface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\n/** Exit status the supervisor records once its command is gone. */\nexport type ExitMetadata = ExitResult;\n\n/** Sidecars a supervised run reads and writes beside its log. */\nexport interface SupervisorPaths {\n spec: string;\n gate: string;\n exit: string;\n logDone: string;\n}\n\n/**\n * Settings that make a terminal safe for a caller with no keyboard.\n *\n * Commands run inside an rmux pane, so their stdout is a real tty. Git and\n * friends take that as permission to open a pager, and `less` then waits at\n * `(END)` for a keypress nobody is there to send, so the run hangs until its\n * timeout. The same tty invites credential prompts, which hang the same way.\n * A conventional piped stdout disables both automatically; a pane has to be\n * told. A command that genuinely wants a pager can still ask for one, as in\n * `git -c core.pager=less log`.\n */\nexport const NO_TERMINAL_INPUT_ENV = {\n GIT_PAGER: 'cat',\n PAGER: 'cat',\n GIT_TERMINAL_PROMPT: '0',\n};\n\nconst SPEC_MODE = 0o600;\nconst NOT_FOUND_ERROR_CODE = 'ENOENT';\n\nexport function supervisorPaths(directory: string, id: string): SupervisorPaths {\n return {\n spec: path.join(directory, `${id}.command.json`),\n gate: path.join(directory, `${id}.gate`),\n exit: path.join(directory, `${id}.exit.json`),\n logDone: path.join(directory, `${id}.log.done`),\n };\n}\n\nexport function writeCommandSpec(target: string, spec: CommandSpec): void {\n fs.writeFileSync(target, `${JSON.stringify(spec)}\\n`, { mode: SPEC_MODE });\n}\n\nexport function readExitMetadata(target: string): ExitMetadata | undefined {\n try {\n const value: unknown = JSON.parse(fs.readFileSync(target, 'utf8'));\n if (typeof value !== 'object' || value === null) return undefined;\n const metadata = value as Partial<ExitMetadata>;\n const code = typeof metadata.code === 'number' ? metadata.code : null;\n const signal = typeof metadata.signal === 'string' ? (metadata.signal as NodeJS.Signals) : null;\n return {\n code,\n signal,\n ...(metadata.reason === 'stopped' ? { reason: metadata.reason } : {}),\n ...(metadata.terminationReason === 'owner_lost' ? { terminationReason: metadata.terminationReason } : {}),\n ...(typeof metadata.stopReason === 'string' ? { stopReason: metadata.stopReason } : {}),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) {\n process.emitWarning(`Could not read runner exit metadata ${target}: ${String(error)}`);\n }\n return undefined;\n }\n}\n\nexport function cleanupSupervisorFiles(paths: SupervisorPaths): void {\n for (const target of Object.values(paths)) {\n try {\n fs.unlinkSync(target);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== NOT_FOUND_ERROR_CODE) process.emitWarning(String(error));\n }\n }\n}\n\n/**\n * Resolves an executable entry point by walking up to the `bin` directory that\n * ships beside this module. Source and published layouts nest services at\n * different depths. Source modules select built subprocess entries so Node can resolve their imports. A hardcoded depth stays silent\n * until the shell tries to exec it.\n */\nexport function runtimeEntry(name: 'runnerHost' | 'logSink', moduleUrl = import.meta.url): string {\n const modulePath = fileURLToPath(moduleUrl);\n const source = path.extname(modulePath) === '.ts';\n const extension = 'mjs';\n let directory = path.dirname(modulePath);\n\n while (true) {\n const entry = source\n ? path.join(directory, 'dist', 'bin', `${name}.${extension}`)\n : path.join(directory, 'bin', `${name}.${extension}`);\n if (fs.existsSync(entry)) return fs.realpathSync(entry);\n\n const parent = path.dirname(directory);\n if (parent === directory) break;\n directory = parent;\n }\n\n throw new Error(`Cannot find bin/${name}.${extension} by walking up from ${modulePath}.`);\n}\n\n/**\n * Replaces the shell rather than nesting under it, so the pid handed back is the\n * supervisor's and a group signal reaches it directly.\n */\nexport function supervisorCommand(paths: SupervisorPaths): string {\n return `exec ${shellJoin([process.execPath, runtimeEntry('runnerHost'), paths.spec, paths.gate, paths.exit])}`;\n}\n\nexport function shellJoin(values: readonly string[]): string {\n return values.map((value) => `'${value.replaceAll(\"'\", `'\"'\"'`)}'`).join(' ');\n}\n\n/** A pending appearance of one supervisor sidecar, watched rather than polled for. */\nexport interface FileWatch {\n /** Resolves true once the file exists, or false when the slice elapses. */\n appeared(withinMs: number): Promise<boolean>;\n close(): void;\n}\n\ninterface DirectoryWatch {\n watcher: fs.FSWatcher | undefined;\n readonly waiters: Map<string, Set<() => void>>;\n references: number;\n}\n\n/**\n * One watcher per directory, shared by every waiter inside it.\n *\n * A session's runners share a runs directory, and `PO_LOG_DIR` makes every\n * session using it share one. A watcher per waited file would then put a watcher\n * per in-flight runner on the same directory, each woken by every other runner's\n * sidecar writes, so the watchers are pooled and reference counted instead.\n */\nconst directoryWatches = new Map<string, DirectoryWatch>();\n\nfunction acquireDirectoryWatch(directory: string): DirectoryWatch {\n const existing = directoryWatches.get(directory);\n if (existing) {\n existing.references += 1;\n return existing;\n }\n const entry: DirectoryWatch = { watcher: undefined, waiters: new Map(), references: 1 };\n try {\n entry.watcher = fs.watch(directory, (_event, changed) => {\n // A rename can arrive without a name, and then the only safe reading is\n // that any waiter in this directory might be the one it belongs to.\n const named = changed === null ? undefined : String(changed);\n const targets = named === undefined ? [...entry.waiters.values()] : [entry.waiters.get(named)];\n for (const listeners of targets) {\n if (!listeners) continue;\n for (const listener of listeners) listener();\n }\n });\n // A watcher that cannot report is a watcher whose slices time out, which the\n // caller's own probe already covers.\n entry.watcher.on('error', () => undefined);\n } catch {\n entry.watcher = undefined;\n }\n directoryWatches.set(directory, entry);\n return entry;\n}\n\nfunction releaseDirectoryWatch(directory: string, entry: DirectoryWatch): void {\n entry.references -= 1;\n if (entry.references > 0) return;\n entry.watcher?.close();\n entry.watcher = undefined;\n entry.waiters.clear();\n directoryWatches.delete(directory);\n}\n\n/**\n * Reports a supervisor sidecar landing on disk without polling for it.\n *\n * The sidecars are written once and never rewritten, so the directory's own\n * create event answers the only question a waiter has. Polling for them costs a\n * full poll interval on every run that finishes inside one, and the runs that\n * finish fastest are the ones that pay that tax most often.\n *\n * The watch is best effort: a platform or a directory that cannot deliver events\n * leaves every slice to time out, which is exactly the poll the caller already\n * runs as its fallback.\n */\nexport function watchForFile(filePath: string): FileWatch {\n const directory = path.dirname(filePath);\n const name = path.basename(filePath);\n const entry = acquireDirectoryWatch(directory);\n let released = false;\n let found = false;\n let notify: (() => void) | undefined;\n\n const settle = (): void => {\n if (found || !fs.existsSync(filePath)) return;\n found = true;\n notify?.();\n };\n const listeners = entry.waiters.get(name) ?? new Set<() => void>();\n listeners.add(settle);\n entry.waiters.set(name, listeners);\n\n // The file can land between opening the watcher and the first wait, and that\n // create event has nobody listening for it yet.\n settle();\n\n return {\n async appeared(withinMs: number): Promise<boolean> {\n if (found) return true;\n return await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n notify = undefined;\n // The watcher is an optimisation, not the answer. A create event that\n // was coalesced, reported under the temporary name of a rename, or\n // never delivered at all must not outlive the slice it belongs to, so\n // every slice ends by asking the filesystem itself.\n settle();\n resolve(found);\n }, withinMs);\n // Deliberately left holding the event loop. A run still being waited on\n // is work, and a process that exits during the gap between two probes\n // drops the result its caller is awaiting.\n notify = () => {\n clearTimeout(timer);\n notify = undefined;\n resolve(true);\n };\n });\n },\n close(): void {\n if (released) return;\n released = true;\n notify = undefined;\n listeners.delete(settle);\n if (listeners.size === 0) entry.waiters.delete(name);\n releaseDirectoryWatch(directory, entry);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const require_session = require("./session-SfIPtdZO.cjs");
|
|
2
|
+
//#region src/services/session/index.ts
|
|
3
|
+
/** Session identity inherited by commands launched from a Pi session. */
|
|
4
|
+
/** Returns a usable inherited session id, failing closed on blank values. */
|
|
5
|
+
function inheritedSessionId(env = process.env) {
|
|
6
|
+
const value = env[require_session.PI_SESSION_ID_ENV]?.trim();
|
|
7
|
+
return value ? value : void 0;
|
|
8
|
+
}
|
|
9
|
+
/** Returns the inherited Pi session id or fails closed outside a session. */
|
|
10
|
+
function requiredSessionId(env = process.env) {
|
|
11
|
+
const sessionId = inheritedSessionId(env);
|
|
12
|
+
if (!sessionId) throw new Error(`${require_session.PI_SESSION_ID_ENV} is required for doom-runner`);
|
|
13
|
+
return sessionId;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
Object.defineProperty(exports, "inheritedSessionId", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function() {
|
|
19
|
+
return inheritedSessionId;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(exports, "requiredSessionId", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: function() {
|
|
25
|
+
return requiredSessionId;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=session-BC43uEyG.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-BC43uEyG.cjs","names":["PI_SESSION_ID_ENV"],"sources":["../src/services/session/index.ts"],"sourcesContent":["import { PI_SESSION_ID_ENV } from '../../constants/session';\n/** Session identity inherited by commands launched from a Pi session. */\n\n/** Returns a usable inherited session id, failing closed on blank values. */\nexport function inheritedSessionId(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const value = env[PI_SESSION_ID_ENV]?.trim();\n return value ? value : undefined;\n}\n\n/** Returns the inherited Pi session id or fails closed outside a session. */\nexport function requiredSessionId(env: NodeJS.ProcessEnv = process.env): string {\n const sessionId = inheritedSessionId(env);\n if (!sessionId) throw new Error(`${PI_SESSION_ID_ENV} is required for doom-runner`);\n return sessionId;\n}\n"],"mappings":";;;;AAIA,SAAgB,mBAAmB,MAAyB,QAAQ,KAAyB;CAC3F,MAAM,QAAQ,IAAIA,gBAAAA,kBAAkB,EAAE,KAAK;CAC3C,OAAO,QAAQ,QAAQ,KAAA;AACzB;;AAGA,SAAgB,kBAAkB,MAAyB,QAAQ,KAAa;CAC9E,MAAM,YAAY,mBAAmB,GAAG;CACxC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,GAAGA,gBAAAA,kBAAkB,6BAA6B;CAClF,OAAO;AACT"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { t as PI_SESSION_ID_ENV } from "./session-DE2Acesp.mjs";
|
|
2
|
+
//#region src/services/session/index.ts
|
|
3
|
+
/** Session identity inherited by commands launched from a Pi session. */
|
|
4
|
+
/** Returns a usable inherited session id, failing closed on blank values. */
|
|
5
|
+
function inheritedSessionId(env = process.env) {
|
|
6
|
+
const value = env[PI_SESSION_ID_ENV]?.trim();
|
|
7
|
+
return value ? value : void 0;
|
|
8
|
+
}
|
|
9
|
+
/** Returns the inherited Pi session id or fails closed outside a session. */
|
|
10
|
+
function requiredSessionId(env = process.env) {
|
|
11
|
+
const sessionId = inheritedSessionId(env);
|
|
12
|
+
if (!sessionId) throw new Error(`${PI_SESSION_ID_ENV} is required for doom-runner`);
|
|
13
|
+
return sessionId;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { requiredSessionId as n, inheritedSessionId as t };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=session-Chsb_Nh2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-Chsb_Nh2.mjs","names":[],"sources":["../src/services/session/index.ts"],"sourcesContent":["import { PI_SESSION_ID_ENV } from '../../constants/session';\n/** Session identity inherited by commands launched from a Pi session. */\n\n/** Returns a usable inherited session id, failing closed on blank values. */\nexport function inheritedSessionId(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const value = env[PI_SESSION_ID_ENV]?.trim();\n return value ? value : undefined;\n}\n\n/** Returns the inherited Pi session id or fails closed outside a session. */\nexport function requiredSessionId(env: NodeJS.ProcessEnv = process.env): string {\n const sessionId = inheritedSessionId(env);\n if (!sessionId) throw new Error(`${PI_SESSION_ID_ENV} is required for doom-runner`);\n return sessionId;\n}\n"],"mappings":";;;;AAIA,SAAgB,mBAAmB,MAAyB,QAAQ,KAAyB;CAC3F,MAAM,QAAQ,IAAI,kBAAkB,EAAE,KAAK;CAC3C,OAAO,QAAQ,QAAQ,KAAA;AACzB;;AAGA,SAAgB,kBAAkB,MAAyB,QAAQ,KAAa;CAC9E,MAAM,YAAY,mBAAmB,GAAG;CACxC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,GAAG,kBAAkB,6BAA6B;CAClF,OAAO;AACT"}
|