@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,1156 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
const require_rolldown_runtime = require("../rolldown-runtime-VH7oDXx4.cjs");
|
|
6
|
+
const require_runnerConfig = require("../runnerConfig-D2tGq5ZM.cjs");
|
|
7
|
+
const require_logReader = require("../logReader-B6J7L7Ua.cjs");
|
|
8
|
+
const require_headless = require("../headless-CHUsBGhg.cjs");
|
|
9
|
+
const require_reconcile = require("../reconcile-BugwYFbV.cjs");
|
|
10
|
+
const require_compaction = require("../compaction-4QSUsBxq.cjs");
|
|
11
|
+
const require_rmuxBackend = require("../rmuxBackend-DlxgqRZ1.cjs");
|
|
12
|
+
const require_runnerDependencies = require("../runnerDependencies-KMZigIxB.cjs");
|
|
13
|
+
const require_runnerPaths = require("../runnerPaths-BTEuzxw0.cjs");
|
|
14
|
+
const require_runnerRegistry = require("../runnerRegistry-BmkZTJHi.cjs");
|
|
15
|
+
let node_fs = require("node:fs");
|
|
16
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
17
|
+
let node_path = require("node:path");
|
|
18
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
19
|
+
let _agimon_ai_po_core_childProcess = require("@agimon-ai/po-core/childProcess");
|
|
20
|
+
let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
|
|
21
|
+
let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
|
|
22
|
+
let _agimon_ai_po_core_backgroundWork = require("@agimon-ai/po-core/backgroundWork");
|
|
23
|
+
let _agimon_ai_po_telemetry = require("@agimon-ai/po-telemetry");
|
|
24
|
+
let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
|
|
25
|
+
let hono = require("hono");
|
|
26
|
+
let hono_streaming = require("hono/streaming");
|
|
27
|
+
let _agimon_ai_po_core_web = require("@agimon-ai/po-core/web");
|
|
28
|
+
let _agimon_ai_po_core_help = require("@agimon-ai/po-core/help");
|
|
29
|
+
//#region src/constants/runnerSettings.ts
|
|
30
|
+
const RUNNER_SETTINGS_FILE = "po-runner.json";
|
|
31
|
+
const RATIO_KEYS = ["headRatio", "errorBudgetRatio"];
|
|
32
|
+
const MAX_RESULT_BYTES_CEILING = 262144;
|
|
33
|
+
const MAX_RESULT_LINES_CEILING = 5e3;
|
|
34
|
+
const MAX_RESULT_TOKENS_CEILING = 1e5;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/constants/runnerRuntime.ts
|
|
37
|
+
const RUNNER_STATUS_KEY = "doom-runner-runners";
|
|
38
|
+
const BACKGROUND_WORK_PROVIDER = "doom-runner";
|
|
39
|
+
const HISTORY_SWEEP_INTERVAL_MS = 6e5;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/constants/webRunners.ts
|
|
42
|
+
const RUNNER_RUNS_TYPE = "runner_runs";
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/services/webRunnerRuns/index.ts
|
|
45
|
+
/** How long a finished runner stays in the cockpit's feed after it exited. */
|
|
46
|
+
/** One on-disk metadata record, or undefined when the text is not a runner record. */
|
|
47
|
+
function parseRunnerRecord(raw) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
return require_runnerRegistry.isRunnerRecord(parsed) ? parsed : void 0;
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function toRunnerRunView(record) {
|
|
56
|
+
return {
|
|
57
|
+
id: record.id,
|
|
58
|
+
name: record.name,
|
|
59
|
+
pid: record.pid,
|
|
60
|
+
command: record.command,
|
|
61
|
+
cwd: record.cwd,
|
|
62
|
+
interactive: record.interactive,
|
|
63
|
+
backend: record.backend,
|
|
64
|
+
state: record.state,
|
|
65
|
+
promoted: record.promoted,
|
|
66
|
+
startedAt: record.startedAt,
|
|
67
|
+
logPath: record.logPath,
|
|
68
|
+
...record.exit ? { exit: {
|
|
69
|
+
reason: record.exit.reason,
|
|
70
|
+
code: record.exit.code,
|
|
71
|
+
signal: record.exit.signal,
|
|
72
|
+
...record.exit.terminationReason === void 0 ? {} : { terminationReason: record.exit.terminationReason },
|
|
73
|
+
...record.exit.stopReason === void 0 ? {} : { stopReason: record.exit.stopReason },
|
|
74
|
+
finishedAt: record.exit.finishedAt
|
|
75
|
+
} } : {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function finishedAt(record) {
|
|
79
|
+
const parsed = record.exit === void 0 ? NaN : Date.parse(record.exit.finishedAt);
|
|
80
|
+
return Number.isFinite(parsed) ? parsed : Date.parse(record.startedAt);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The feed the cockpit shows: running runners first, newest start first, then
|
|
84
|
+
* the recently finished ones, newest exit first. Finished runners older than
|
|
85
|
+
* the retention window are left out; the registry keeps them for the CLI.
|
|
86
|
+
*/
|
|
87
|
+
function presentRunnerRuns(records, now) {
|
|
88
|
+
const running = records.filter((record) => record.state === "running").sort((left, right) => right.startedAt.localeCompare(left.startedAt));
|
|
89
|
+
const finished = records.filter((record) => record.state === "completed" && now - finishedAt(record) <= 6e5).sort((left, right) => finishedAt(right) - finishedAt(left));
|
|
90
|
+
return [...running, ...finished].map(toRunnerRunView);
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/services/runnerServerRuntime/index.ts
|
|
94
|
+
const COMPLETION_RETRY_MS = 1e3;
|
|
95
|
+
function createRunnerServerRuntime(container, directEvents, wakeAgent, execution) {
|
|
96
|
+
const telemetry = execution ? (0, _agimon_ai_po_telemetry.createDoomTelemetry)({
|
|
97
|
+
serviceName: "doom-runner",
|
|
98
|
+
packageName: "@agimon-ai/po-runner",
|
|
99
|
+
cwd: execution.cwd,
|
|
100
|
+
env: execution.environment,
|
|
101
|
+
enableLogs: true,
|
|
102
|
+
enableTraces: true
|
|
103
|
+
}) : void 0;
|
|
104
|
+
let sessionId;
|
|
105
|
+
let recoveredCompactionId;
|
|
106
|
+
let supervision;
|
|
107
|
+
let runtimeDisposal;
|
|
108
|
+
let disposal;
|
|
109
|
+
let backgroundWorkItems = [];
|
|
110
|
+
let backgroundWorkProvider;
|
|
111
|
+
const watched = /* @__PURE__ */ new Set();
|
|
112
|
+
let unsubscribeWatched;
|
|
113
|
+
let wakes = Promise.resolve();
|
|
114
|
+
let closing = false;
|
|
115
|
+
let retryTimer;
|
|
116
|
+
let statusTimer;
|
|
117
|
+
let historyTimer;
|
|
118
|
+
let historySweep;
|
|
119
|
+
const sweepHistory = () => {
|
|
120
|
+
if (closing) return Promise.resolve();
|
|
121
|
+
historySweep ??= (async () => {
|
|
122
|
+
const ttl = require_runnerConfig.getLogTtlMs(container.environment);
|
|
123
|
+
const result = container.paths.sweepHistoryAsync ? await container.paths.sweepHistoryAsync(ttl) : container.paths.sweepHistory(ttl);
|
|
124
|
+
for (const error of result.errors) process.emitWarning(error);
|
|
125
|
+
})().catch((error) => process.emitWarning(`Could not sweep doom-runner history: ${String(error)}`)).finally(() => {
|
|
126
|
+
historySweep = void 0;
|
|
127
|
+
});
|
|
128
|
+
return historySweep;
|
|
129
|
+
};
|
|
130
|
+
const ensureSession = (requestedSessionId) => {
|
|
131
|
+
if (closing) return Promise.reject(/* @__PURE__ */ new Error("Runner runtime is closing."));
|
|
132
|
+
if (sessionId !== void 0 && sessionId !== requestedSessionId) throw new Error(`Runner runtime belongs to session ${sessionId}.`);
|
|
133
|
+
sessionId ??= requestedSessionId;
|
|
134
|
+
supervision ??= (async () => {
|
|
135
|
+
await telemetry?.recordEvent("doom_runner.session_started", { outcome: "started" });
|
|
136
|
+
if (closing) return;
|
|
137
|
+
container.paths.setSessionId(requestedSessionId);
|
|
138
|
+
if (await container.lifeline.arm(requestedSessionId) === void 0) throw new Error("Could not arm runner supervision lifeline.");
|
|
139
|
+
if (closing) return;
|
|
140
|
+
const promotedIds = (await container.runnerRegistry.listAll(requestedSessionId)).filter((record) => record.state === "running" && record.promoted).map((record) => record.id);
|
|
141
|
+
const legacy = await require_reconcile.cleanupLegacyRunnerStore({
|
|
142
|
+
registry: container.runnerRegistry,
|
|
143
|
+
launcher: container.launcher,
|
|
144
|
+
rmuxBackend: container.rmuxBackend,
|
|
145
|
+
processControl: container.processControl,
|
|
146
|
+
currentHostPid: process.pid,
|
|
147
|
+
paths: container.paths
|
|
148
|
+
});
|
|
149
|
+
for (const error of legacy.errors) process.emitWarning(error);
|
|
150
|
+
if (closing) return;
|
|
151
|
+
const reconciled = await require_reconcile.reconcileActiveRunners({
|
|
152
|
+
registry: container.runnerRegistry,
|
|
153
|
+
launcher: container.launcher,
|
|
154
|
+
rmuxBackend: container.rmuxBackend,
|
|
155
|
+
processControl: container.processControl,
|
|
156
|
+
currentHostPid: process.pid,
|
|
157
|
+
startup: true,
|
|
158
|
+
startupSessionId: requestedSessionId
|
|
159
|
+
});
|
|
160
|
+
for (const error of reconciled.errors) process.emitWarning(error);
|
|
161
|
+
await telemetry?.recordEvent("doom_runner.reconciled", {
|
|
162
|
+
"runner.reclaimed_count": legacy.reclaimed.length + reconciled.reclaimed.length,
|
|
163
|
+
"runner.error_count": legacy.errors.length + reconciled.errors.length,
|
|
164
|
+
outcome: legacy.errors.length + reconciled.errors.length === 0 ? "completed" : "degraded"
|
|
165
|
+
});
|
|
166
|
+
if (!closing) {
|
|
167
|
+
unsubscribeRunnerUpdates ??= container.runnerRegistry.subscribe(() => requestPublish(requestedSessionId), requestedSessionId);
|
|
168
|
+
for (const id of promotedIds) watchRunner(id);
|
|
169
|
+
requestPublish(requestedSessionId);
|
|
170
|
+
await sweepHistory();
|
|
171
|
+
if (closing) return;
|
|
172
|
+
statusTimer ??= setInterval(() => {
|
|
173
|
+
refreshSession(requestedSessionId);
|
|
174
|
+
}, 500);
|
|
175
|
+
statusTimer.unref?.();
|
|
176
|
+
historyTimer ??= setInterval(() => {
|
|
177
|
+
sweepHistory();
|
|
178
|
+
}, HISTORY_SWEEP_INTERVAL_MS);
|
|
179
|
+
historyTimer.unref?.();
|
|
180
|
+
}
|
|
181
|
+
})().catch((error) => {
|
|
182
|
+
supervision = void 0;
|
|
183
|
+
throw error;
|
|
184
|
+
});
|
|
185
|
+
return supervision;
|
|
186
|
+
};
|
|
187
|
+
const disposeRuntime = () => {
|
|
188
|
+
runtimeDisposal ??= (async () => {
|
|
189
|
+
await supervision?.catch((error) => process.emitWarning(`Could not initialize headless runner supervision: ${String(error)}`));
|
|
190
|
+
const ownedSessionId = sessionId;
|
|
191
|
+
if (ownedSessionId === void 0) return;
|
|
192
|
+
const records = await container.runnerRegistry.listBySession(ownedSessionId).catch(() => []);
|
|
193
|
+
await Promise.all(records.map(async (record) => {
|
|
194
|
+
try {
|
|
195
|
+
if (!await require_reconcile.stopRunnerProcess(record, container.launcher, container.rmuxBackend, {
|
|
196
|
+
registry: container.runnerRegistry,
|
|
197
|
+
intent: {
|
|
198
|
+
reason: "stopped",
|
|
199
|
+
terminationReason: "parent_session_cleanup",
|
|
200
|
+
stopReason: "session ended"
|
|
201
|
+
}
|
|
202
|
+
}) && container.processControl.isAlive(record.pid)) {
|
|
203
|
+
process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
await container.runnerRegistry.complete(record.id, {
|
|
207
|
+
reason: "stopped",
|
|
208
|
+
code: null,
|
|
209
|
+
signal: null,
|
|
210
|
+
stopReason: "session ended"
|
|
211
|
+
}, ownedSessionId);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
try {
|
|
217
|
+
await container.ptyHost.disposeAll();
|
|
218
|
+
} finally {
|
|
219
|
+
container.lifeline.dispose();
|
|
220
|
+
container.runnerRegistry.close();
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
|
+
return runtimeDisposal;
|
|
224
|
+
};
|
|
225
|
+
let refreshInFlight;
|
|
226
|
+
const refreshSession = (ownedSessionId) => {
|
|
227
|
+
if (closing) return Promise.resolve();
|
|
228
|
+
refreshInFlight ??= refreshNow(ownedSessionId).finally(() => {
|
|
229
|
+
refreshInFlight = void 0;
|
|
230
|
+
});
|
|
231
|
+
return refreshInFlight;
|
|
232
|
+
};
|
|
233
|
+
const refreshNow = async (ownedSessionId) => {
|
|
234
|
+
if (closing) return;
|
|
235
|
+
try {
|
|
236
|
+
const reconciled = await require_reconcile.reconcileActiveRunners({
|
|
237
|
+
registry: container.runnerRegistry,
|
|
238
|
+
launcher: container.launcher,
|
|
239
|
+
rmuxBackend: container.rmuxBackend,
|
|
240
|
+
processControl: container.processControl,
|
|
241
|
+
currentHostPid: process.pid,
|
|
242
|
+
startup: false,
|
|
243
|
+
active: await container.runnerRegistry.listBySession(ownedSessionId)
|
|
244
|
+
});
|
|
245
|
+
for (const error of reconciled.errors) process.emitWarning(error);
|
|
246
|
+
if (!closing) {
|
|
247
|
+
requestPublish(ownedSessionId);
|
|
248
|
+
wakeForFinished(ownedSessionId);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const publishRuns = async (ownedSessionId) => {
|
|
255
|
+
try {
|
|
256
|
+
const records = await container.runnerRegistry.listAll(ownedSessionId);
|
|
257
|
+
if (closing) return;
|
|
258
|
+
if (execution) {
|
|
259
|
+
const root = (0, _agimon_ai_po_core_childProcess.resolveRootSessionId)(ownedSessionId, container.environment);
|
|
260
|
+
const count = (await container.runnerRegistry.list()).filter((record) => (record.rootSessionId ?? record.sessionId) === root).length;
|
|
261
|
+
if (closing) return;
|
|
262
|
+
execution.client.setStatus(RUNNER_STATUS_KEY, require_runnerRegistry.formatRunnerStatus(count));
|
|
263
|
+
}
|
|
264
|
+
if (closing) return;
|
|
265
|
+
const nextBackgroundWork = records.filter((record) => record.state === "running" || watched.has(record.id)).map((record) => ({
|
|
266
|
+
id: record.id,
|
|
267
|
+
sessionId: record.sessionId,
|
|
268
|
+
label: record.name,
|
|
269
|
+
...record.state === "running" ? { status: record.state } : {}
|
|
270
|
+
}));
|
|
271
|
+
if (JSON.stringify(nextBackgroundWork) !== JSON.stringify(backgroundWorkItems)) {
|
|
272
|
+
backgroundWorkItems = nextBackgroundWork;
|
|
273
|
+
backgroundWorkProvider?.update();
|
|
274
|
+
}
|
|
275
|
+
directEvents.publish(RUNNER_RUNS_TYPE, ownedSessionId, { runs: presentRunnerRuns(records, Date.now()) });
|
|
276
|
+
} catch (error) {
|
|
277
|
+
process.emitWarning(`Could not publish runner state: ${String(error)}`);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
let publishInFlight;
|
|
281
|
+
let publishAgain = false;
|
|
282
|
+
let unsubscribeRunnerUpdates;
|
|
283
|
+
const requestPublish = (ownedSessionId) => {
|
|
284
|
+
if (closing) return;
|
|
285
|
+
publishAgain = true;
|
|
286
|
+
if (publishInFlight !== void 0) return;
|
|
287
|
+
publishInFlight = (async () => {
|
|
288
|
+
do {
|
|
289
|
+
publishAgain = false;
|
|
290
|
+
await publishRuns(ownedSessionId);
|
|
291
|
+
} while (publishAgain && !closing);
|
|
292
|
+
})().catch((error) => {
|
|
293
|
+
process.emitWarning(`Could not schedule runner state publication: ${String(error)}`);
|
|
294
|
+
}).finally(() => {
|
|
295
|
+
publishInFlight = void 0;
|
|
296
|
+
if (publishAgain) requestPublish(ownedSessionId);
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
const wakeForFinished = (ownedSessionId) => {
|
|
300
|
+
if (closing) return;
|
|
301
|
+
wakes = wakes.then(async () => {
|
|
302
|
+
if (closing) return;
|
|
303
|
+
let retry = false;
|
|
304
|
+
for (const id of watched) {
|
|
305
|
+
if (closing) return;
|
|
306
|
+
try {
|
|
307
|
+
const record = await container.runnerRegistry.get(id, ownedSessionId);
|
|
308
|
+
if (closing) return;
|
|
309
|
+
if (record?.state === "running") continue;
|
|
310
|
+
if (record) await wakeAgent?.(require_runnerRegistry.formatRunnerFinished(record), `runner-finished:${JSON.stringify([ownedSessionId, id])}`);
|
|
311
|
+
if (record) await telemetry?.recordEvent("doom_runner.process_finished", {
|
|
312
|
+
outcome: "completed",
|
|
313
|
+
"runner.exit_code": record.exit?.code ?? 0,
|
|
314
|
+
"runner.backend": record.backend
|
|
315
|
+
});
|
|
316
|
+
watched.delete(id);
|
|
317
|
+
requestPublish(ownedSessionId);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
retry = true;
|
|
320
|
+
process.emitWarning(`Could not report finished runner ${id}: ${String(error)}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (retry && !closing && retryTimer === void 0) {
|
|
324
|
+
retryTimer = setTimeout(() => {
|
|
325
|
+
retryTimer = void 0;
|
|
326
|
+
wakeForFinished(ownedSessionId);
|
|
327
|
+
}, COMPLETION_RETRY_MS);
|
|
328
|
+
retryTimer.unref?.();
|
|
329
|
+
}
|
|
330
|
+
if (watched.size === 0) {
|
|
331
|
+
clearTimeout(retryTimer);
|
|
332
|
+
retryTimer = void 0;
|
|
333
|
+
unsubscribeWatched?.();
|
|
334
|
+
unsubscribeWatched = void 0;
|
|
335
|
+
}
|
|
336
|
+
}).catch((error) => {
|
|
337
|
+
process.emitWarning(`Could not report a finished runner: ${String(error)}`);
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const watchRunner = (id) => {
|
|
341
|
+
const ownedSessionId = sessionId;
|
|
342
|
+
if (!wakeAgent || closing || ownedSessionId === void 0) return;
|
|
343
|
+
watched.add(id);
|
|
344
|
+
requestPublish(ownedSessionId);
|
|
345
|
+
unsubscribeWatched ??= container.runnerRegistry.subscribe(() => wakeForFinished(ownedSessionId), ownedSessionId);
|
|
346
|
+
wakeForFinished(ownedSessionId);
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
container,
|
|
350
|
+
activity: {
|
|
351
|
+
name: "runner",
|
|
352
|
+
start: async (executionContext) => {
|
|
353
|
+
await ensureSession(executionContext.sessionId);
|
|
354
|
+
requestPublish(executionContext.sessionId);
|
|
355
|
+
return () => {};
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
ensureSession,
|
|
359
|
+
watchRunner,
|
|
360
|
+
async recoverCompaction(event) {
|
|
361
|
+
if (event.status !== "completed" || closing || !execution || typeof event.entryId !== "string" || event.entryId === recoveredCompactionId) return;
|
|
362
|
+
const compacted = (await execution.session.entries()).findLast((entry) => entry.type === "compaction");
|
|
363
|
+
if (closing || compacted?.id !== event.entryId) return;
|
|
364
|
+
await ensureSession(execution.sessionId);
|
|
365
|
+
let records;
|
|
366
|
+
try {
|
|
367
|
+
records = await container.runnerRegistry.listBySession(execution.sessionId);
|
|
368
|
+
} catch {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (closing) return;
|
|
372
|
+
const active = records.filter((record) => record.state === "running");
|
|
373
|
+
const text = require_compaction.activeRunnerRecovery(active);
|
|
374
|
+
if (!text) return;
|
|
375
|
+
await execution.session.appendMessage?.({
|
|
376
|
+
type: "doom-runner-compaction",
|
|
377
|
+
text,
|
|
378
|
+
display: false,
|
|
379
|
+
details: {
|
|
380
|
+
sessionId: execution.sessionId,
|
|
381
|
+
runnerIds: active.map((record) => record.id)
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
if (!closing) recoveredCompactionId = event.entryId;
|
|
385
|
+
},
|
|
386
|
+
backgroundWorkPlugin: (context) => {
|
|
387
|
+
context.inject([_agimon_ai_po_core_backgroundWork.DOOM_BACKGROUND_WORK_SERVICE], (serviceContext) => {
|
|
388
|
+
const service = (0, _agimon_ai_po_core_backgroundWork.readDoomBackgroundWorkService)(serviceContext);
|
|
389
|
+
if (!service) return void 0;
|
|
390
|
+
const registration = service.register({
|
|
391
|
+
provider: BACKGROUND_WORK_PROVIDER,
|
|
392
|
+
listActiveWork: () => backgroundWorkItems
|
|
393
|
+
});
|
|
394
|
+
backgroundWorkProvider = registration;
|
|
395
|
+
return () => {
|
|
396
|
+
registration.dispose();
|
|
397
|
+
if (backgroundWorkProvider === registration) backgroundWorkProvider = void 0;
|
|
398
|
+
};
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
dispose() {
|
|
402
|
+
if (disposal) return disposal;
|
|
403
|
+
closing = true;
|
|
404
|
+
disposal = (async () => {
|
|
405
|
+
closing = true;
|
|
406
|
+
clearInterval(statusTimer);
|
|
407
|
+
clearInterval(historyTimer);
|
|
408
|
+
clearTimeout(retryTimer);
|
|
409
|
+
retryTimer = void 0;
|
|
410
|
+
watched.clear();
|
|
411
|
+
unsubscribeWatched?.();
|
|
412
|
+
unsubscribeWatched = void 0;
|
|
413
|
+
unsubscribeRunnerUpdates?.();
|
|
414
|
+
unsubscribeRunnerUpdates = void 0;
|
|
415
|
+
await wakes;
|
|
416
|
+
await publishInFlight;
|
|
417
|
+
await historySweep;
|
|
418
|
+
await refreshInFlight;
|
|
419
|
+
try {
|
|
420
|
+
execution?.client.setStatus(RUNNER_STATUS_KEY, void 0);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
process.emitWarning(`Could not clear runner status: ${String(error)}`);
|
|
423
|
+
}
|
|
424
|
+
await disposeRuntime();
|
|
425
|
+
await telemetry?.recordEvent("doom_runner.session_finished", { outcome: "stopped" });
|
|
426
|
+
await telemetry?.shutdown();
|
|
427
|
+
})();
|
|
428
|
+
return disposal;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/services/runnerSettings/index.ts
|
|
434
|
+
/** Pi's convention is one JSON file per extension under the config directory. */
|
|
435
|
+
const EMPTY = {
|
|
436
|
+
settings: {},
|
|
437
|
+
issues: []
|
|
438
|
+
};
|
|
439
|
+
const COUNT_KEYS = [
|
|
440
|
+
"maxResultBytes",
|
|
441
|
+
"maxResultLines",
|
|
442
|
+
"successMaxResultBytes",
|
|
443
|
+
"maxResultTokens",
|
|
444
|
+
"successMaxResultTokens",
|
|
445
|
+
"errorMaxEntries",
|
|
446
|
+
"errorMaxVariantsJoined"
|
|
447
|
+
];
|
|
448
|
+
const KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
449
|
+
...RATIO_KEYS,
|
|
450
|
+
...COUNT_KEYS,
|
|
451
|
+
"errorPatterns"
|
|
452
|
+
]);
|
|
453
|
+
/** Ceilings so a project file cannot flood the model's context. */
|
|
454
|
+
const CEILINGS = {
|
|
455
|
+
maxResultBytes: MAX_RESULT_BYTES_CEILING,
|
|
456
|
+
successMaxResultBytes: MAX_RESULT_BYTES_CEILING,
|
|
457
|
+
maxResultTokens: MAX_RESULT_TOKENS_CEILING,
|
|
458
|
+
successMaxResultTokens: MAX_RESULT_TOKENS_CEILING,
|
|
459
|
+
maxResultLines: MAX_RESULT_LINES_CEILING,
|
|
460
|
+
errorMaxEntries: 100,
|
|
461
|
+
errorMaxVariantsJoined: 100
|
|
462
|
+
};
|
|
463
|
+
var RunnerSettingsLoader = class {
|
|
464
|
+
load(cwd, trusted) {
|
|
465
|
+
if (!trusted) return EMPTY;
|
|
466
|
+
let raw;
|
|
467
|
+
try {
|
|
468
|
+
raw = node_fs.default.readFileSync(node_path.default.join(cwd, _earendil_works_pi_coding_agent.CONFIG_DIR_NAME, RUNNER_SETTINGS_FILE), "utf8");
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error.code === "ENOENT") return EMPTY;
|
|
471
|
+
return {
|
|
472
|
+
settings: {},
|
|
473
|
+
issues: [`${RUNNER_SETTINGS_FILE} could not be read`]
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
let parsed;
|
|
477
|
+
try {
|
|
478
|
+
parsed = JSON.parse(raw);
|
|
479
|
+
} catch {
|
|
480
|
+
return {
|
|
481
|
+
settings: {},
|
|
482
|
+
issues: [`${RUNNER_SETTINGS_FILE} is not valid JSON`]
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return validate(parsed);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
function validate(parsed) {
|
|
489
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
490
|
+
settings: {},
|
|
491
|
+
issues: [`${RUNNER_SETTINGS_FILE} must contain a JSON object`]
|
|
492
|
+
};
|
|
493
|
+
const source = parsed;
|
|
494
|
+
const settings = {};
|
|
495
|
+
const issues = [];
|
|
496
|
+
for (const key of Object.keys(source)) if (!KNOWN_KEYS.has(key)) issues.push(`unknown key ${key}`);
|
|
497
|
+
for (const key of COUNT_KEYS) {
|
|
498
|
+
const value = source[key];
|
|
499
|
+
if (value === void 0) continue;
|
|
500
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
501
|
+
issues.push(`${key} must be a positive integer`);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
settings[key] = Math.min(value, CEILINGS[key] ?? value);
|
|
505
|
+
}
|
|
506
|
+
for (const key of RATIO_KEYS) {
|
|
507
|
+
const value = source[key];
|
|
508
|
+
if (value === void 0) continue;
|
|
509
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value >= 1) {
|
|
510
|
+
issues.push(`${key} must be a number above 0 and below 1`);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
settings[key] = value;
|
|
514
|
+
}
|
|
515
|
+
const patterns = source.errorPatterns;
|
|
516
|
+
if (patterns !== void 0) {
|
|
517
|
+
if (!Array.isArray(patterns) || patterns.some((entry) => typeof entry !== "string")) issues.push("errorPatterns must be an array of strings");
|
|
518
|
+
else {
|
|
519
|
+
const accepted = [];
|
|
520
|
+
for (const pattern of patterns.slice(0, 32)) {
|
|
521
|
+
if (pattern.length > 512) {
|
|
522
|
+
issues.push("errorPatterns entry is too long");
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
new RegExp(pattern, "iu");
|
|
527
|
+
accepted.push(pattern);
|
|
528
|
+
} catch {
|
|
529
|
+
issues.push(`errorPatterns entry is not a valid regular expression: ${pattern}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (accepted.length > 0) settings.errorPatterns = accepted;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
settings,
|
|
537
|
+
issues
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts
|
|
542
|
+
const RUNNER_SERVER_SCOPE_SERVICE = "doom/runner-server-scope";
|
|
543
|
+
const createRunnerServerRoot = ({ host, agent, signal }) => {
|
|
544
|
+
if (!host.context.directEvents) throw new Error("Runner headless facet requires the session direct event bus.");
|
|
545
|
+
if (!host.context.cwd || !host.context.environment) throw new Error("Runner headless facet requires session cwd and environment.");
|
|
546
|
+
const loaded = new RunnerSettingsLoader().load(host.context.cwd, true);
|
|
547
|
+
const settings = Object.freeze({
|
|
548
|
+
...loaded.settings,
|
|
549
|
+
...loaded.settings.errorPatterns ? { errorPatterns: Object.freeze([...loaded.settings.errorPatterns]) } : {}
|
|
550
|
+
});
|
|
551
|
+
for (const issue of loaded.issues) process.emitWarning(`${RUNNER_SETTINGS_FILE}: ${issue}`);
|
|
552
|
+
const dependencies = require_runnerDependencies.createRunnerDependencies({
|
|
553
|
+
settings,
|
|
554
|
+
cwd: host.context.cwd,
|
|
555
|
+
environment: host.context.environment,
|
|
556
|
+
...host.context.executionBudget === void 0 ? {} : { executionBudget: host.context.executionBudget }
|
|
557
|
+
});
|
|
558
|
+
const execution = agent?.context;
|
|
559
|
+
const wakeAgent = execution && (async (content, requestId) => {
|
|
560
|
+
try {
|
|
561
|
+
if (!execution.session.admitPrompt) throw new Error("The session cannot admit a runner notification.");
|
|
562
|
+
await execution.session.admitPrompt(content, "steer", void 0, requestId);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
await Promise.resolve().then(() => execution.client.notify({
|
|
565
|
+
body: String(error),
|
|
566
|
+
level: "warning"
|
|
567
|
+
})).catch(() => void 0);
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
const runtime = createRunnerServerRuntime(dependencies, host.context.directEvents, wakeAgent, execution);
|
|
572
|
+
const bash = (wakeOnExit) => require_headless.createHeadlessBashTool({ run: async (request) => {
|
|
573
|
+
await runtime.ensureSession(request.sessionId);
|
|
574
|
+
const result = await dependencies.bashRunService.run({
|
|
575
|
+
...request,
|
|
576
|
+
...signal ? { signal: request.signal ? AbortSignal.any([signal, request.signal]) : signal } : {}
|
|
577
|
+
});
|
|
578
|
+
if (wakeOnExit && result.kind === "promoted") runtime.watchRunner(result.id);
|
|
579
|
+
return result;
|
|
580
|
+
} }, (path, lines, bytes, tokens) => require_logReader.summarizeLog(path, lines, bytes, tokens, settings), settings, dependencies.environment);
|
|
581
|
+
const value = {
|
|
582
|
+
tool: bash(true),
|
|
583
|
+
remoteTool: bash(false),
|
|
584
|
+
command: require_headless.createHeadlessRunnersCommand(dependencies),
|
|
585
|
+
recoverCompaction: runtime.recoverCompaction
|
|
586
|
+
};
|
|
587
|
+
return {
|
|
588
|
+
value,
|
|
589
|
+
services: [runtime.backgroundWorkPlugin, (context) => {
|
|
590
|
+
context.plugin((providerContext) => providerContext.provide(RUNNER_SERVER_SCOPE_SERVICE, value));
|
|
591
|
+
}],
|
|
592
|
+
activities: [runtime.activity],
|
|
593
|
+
onStart: execution ? () => runtime.ensureSession(execution.sessionId) : void 0,
|
|
594
|
+
onStop: runtime.dispose
|
|
595
|
+
};
|
|
596
|
+
};
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/extensions/workspaces/sessions/(backend)/root.server.ts
|
|
599
|
+
var root_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoot)(createRunnerServerRoot);
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/services/runnersChannel/index.ts
|
|
602
|
+
function runnerPayload(value) {
|
|
603
|
+
return typeof value === "object" && value !== null && Array.isArray(value.runs);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* The runners data channel consumes lifecycle-owned snapshots from the session
|
|
607
|
+
* server. Durable registry files are read by the owning session only, never by
|
|
608
|
+
* this hub channel.
|
|
609
|
+
*/
|
|
610
|
+
function createRunnersChannel() {
|
|
611
|
+
return {
|
|
612
|
+
frameType: RUNNER_RUNS_TYPE,
|
|
613
|
+
start(host) {
|
|
614
|
+
const latest = /* @__PURE__ */ new Map();
|
|
615
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
616
|
+
return {
|
|
617
|
+
payloadFor(scope) {
|
|
618
|
+
const runs = latest.get(scope.sessionId);
|
|
619
|
+
return runs === void 0 ? void 0 : { runs };
|
|
620
|
+
},
|
|
621
|
+
sessionAdded(scope) {
|
|
622
|
+
subscriptions.get(scope.sessionId)?.();
|
|
623
|
+
latest.delete(scope.sessionId);
|
|
624
|
+
const unsubscribe = host.directEvents.subscribe(RUNNER_RUNS_TYPE, scope.sessionId, (payload) => {
|
|
625
|
+
if (!runnerPayload(payload)) return;
|
|
626
|
+
latest.set(scope.sessionId, payload.runs);
|
|
627
|
+
host.publish(scope.sessionId, payload);
|
|
628
|
+
}, { replayLatest: true });
|
|
629
|
+
subscriptions.set(scope.sessionId, unsubscribe);
|
|
630
|
+
},
|
|
631
|
+
sessionRemoved(sessionId) {
|
|
632
|
+
subscriptions.get(sessionId)?.();
|
|
633
|
+
subscriptions.delete(sessionId);
|
|
634
|
+
latest.delete(sessionId);
|
|
635
|
+
},
|
|
636
|
+
close() {
|
|
637
|
+
for (const unsubscribe of subscriptions.values()) unsubscribe();
|
|
638
|
+
subscriptions.clear();
|
|
639
|
+
latest.clear();
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region src/extensions/(backend)/channel/runnerRuns.server.ts
|
|
647
|
+
var runnerRuns_server_default = (0, _agimon_ai_po_core_extensionFile.defineChannel)(function runnerRunsChannel() {
|
|
648
|
+
return createRunnersChannel();
|
|
649
|
+
});
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region src/constants/runnerLogApi.ts
|
|
652
|
+
const LOG_DIR_NAME = "logs";
|
|
653
|
+
const STATE_EXTENSION = ".json";
|
|
654
|
+
const RUNNING_STATE = "running";
|
|
655
|
+
const STATE_POLL_MS = 1e3;
|
|
656
|
+
const HEARTBEAT_MS = 15e3;
|
|
657
|
+
const MAX_LINES = 5e3;
|
|
658
|
+
const SAFE_SEGMENT = /^[\w.@-]+$/;
|
|
659
|
+
const RAW_LOG_SUFFIX = ".raw";
|
|
660
|
+
const NOT_ATTACHABLE = "No attachable runner by that name in this session.";
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/constants/webRunnerLog.ts
|
|
663
|
+
const RUNNER_API_BASE_PATH = "runner";
|
|
664
|
+
const RUN_ID_PARAM = "runId";
|
|
665
|
+
const RUNNER_SCREEN_EVENT = "screen";
|
|
666
|
+
const RUNNER_LOG_STREAM_EVENT = "append";
|
|
667
|
+
const RUNNER_LOG_PING_EVENT = "ping";
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/services/logTail/index.ts
|
|
670
|
+
/** How often the tail restats the file; the same cadence the CLI's --follow uses. */
|
|
671
|
+
/**
|
|
672
|
+
* Polling tail: read the delta between the last offset and the file's current
|
|
673
|
+
* size, and hand back only complete lines.
|
|
674
|
+
*
|
|
675
|
+
* Same reliability posture as this package's runner watcher: the poll is the
|
|
676
|
+
* mechanism, not an accelerator for a change notification. fs.watchFile only
|
|
677
|
+
* fires when it observes a difference from a baseline it took itself, so a
|
|
678
|
+
* write that lands before its first stat is a change it never reports; a
|
|
679
|
+
* restat every tick has no such window.
|
|
680
|
+
*
|
|
681
|
+
* A write that lands mid-line is common, so the trailing fragment is held
|
|
682
|
+
* until its newline arrives rather than emitted as a line that will change.
|
|
683
|
+
*/
|
|
684
|
+
var LogTail = class {
|
|
685
|
+
follow(logPath, options) {
|
|
686
|
+
let offset = options.from;
|
|
687
|
+
let pending = "";
|
|
688
|
+
let closed = false;
|
|
689
|
+
const drain = (size) => {
|
|
690
|
+
if (size < offset) {
|
|
691
|
+
offset = 0;
|
|
692
|
+
pending = "";
|
|
693
|
+
}
|
|
694
|
+
if (size === offset) return;
|
|
695
|
+
const length = size - offset;
|
|
696
|
+
const buffer = Buffer.alloc(length);
|
|
697
|
+
const descriptor = node_fs.default.openSync(logPath, "r");
|
|
698
|
+
let bytesRead;
|
|
699
|
+
try {
|
|
700
|
+
bytesRead = node_fs.default.readSync(descriptor, buffer, 0, length, offset);
|
|
701
|
+
} finally {
|
|
702
|
+
node_fs.default.closeSync(descriptor);
|
|
703
|
+
}
|
|
704
|
+
offset = size;
|
|
705
|
+
if (bytesRead <= 0) return;
|
|
706
|
+
pending += buffer.subarray(0, bytesRead).toString("utf8");
|
|
707
|
+
const lines = pending.split("\n");
|
|
708
|
+
pending = lines.pop() ?? "";
|
|
709
|
+
if (lines.length > 0) options.onLines(lines, offset - Buffer.byteLength(pending, "utf8"));
|
|
710
|
+
};
|
|
711
|
+
const tick = () => {
|
|
712
|
+
if (closed) return;
|
|
713
|
+
try {
|
|
714
|
+
drain(node_fs.default.statSync(logPath).size);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (error.code === "ENOENT") return;
|
|
717
|
+
options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
const timer = setInterval(tick, 250);
|
|
721
|
+
timer.unref?.();
|
|
722
|
+
return { close() {
|
|
723
|
+
if (closed) return;
|
|
724
|
+
closed = true;
|
|
725
|
+
clearInterval(timer);
|
|
726
|
+
} };
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/types/webRunnerLog.ts
|
|
731
|
+
/** The named SSE event carrying a RunnerLogStreamEvent payload. */
|
|
732
|
+
/**
|
|
733
|
+
* The named SSE event that carries nothing.
|
|
734
|
+
*
|
|
735
|
+
* A runner can be alive and silent for minutes, which on the wire is
|
|
736
|
+
* indistinguishable from a connection that died. This is sent on a timer so
|
|
737
|
+
* the socket keeps proving itself. Readers that only listen for `append`
|
|
738
|
+
* ignore it, which is what makes it safe to add.
|
|
739
|
+
*/
|
|
740
|
+
/** Query parameter names, shared so the page and the route cannot drift apart. */
|
|
741
|
+
const RUNNER_LOG_PARAMS = {
|
|
742
|
+
lines: "lines",
|
|
743
|
+
grep: "grep",
|
|
744
|
+
ignoreCase: "ignoreCase",
|
|
745
|
+
contextLines: "contextLines",
|
|
746
|
+
from: "from"
|
|
747
|
+
};
|
|
748
|
+
//#endregion
|
|
749
|
+
//#region src/types/apiRoutes.ts
|
|
750
|
+
/**
|
|
751
|
+
* This package's routes, as data.
|
|
752
|
+
*
|
|
753
|
+
* No scope and no base path: the build reads both off
|
|
754
|
+
* `src/extensions/workspaces/sessions/(backend)/api/runner/`, so the mount is
|
|
755
|
+
* stated once, by the folder that creates it. This package stated it four times
|
|
756
|
+
* before, once per URL builder, each of them re-spelling `/plugins/` and the
|
|
757
|
+
* session prefix by hand.
|
|
758
|
+
*
|
|
759
|
+
* The Hono app registers these paths verbatim and the browser addresses them,
|
|
760
|
+
* so a path cannot move on one side alone. `:runId` is Hono's own parameter
|
|
761
|
+
* syntax for that reason; a caller supplies the value as `params`, and the
|
|
762
|
+
* client encodes the segment.
|
|
763
|
+
*/
|
|
764
|
+
var apiRoutes_default = (0, _agimon_ai_po_core_web.defineApiRoutes)({
|
|
765
|
+
log: {
|
|
766
|
+
method: "GET",
|
|
767
|
+
path: `/runners/:${RUN_ID_PARAM}/log`,
|
|
768
|
+
query: [
|
|
769
|
+
RUNNER_LOG_PARAMS.lines,
|
|
770
|
+
RUNNER_LOG_PARAMS.grep,
|
|
771
|
+
RUNNER_LOG_PARAMS.ignoreCase,
|
|
772
|
+
RUNNER_LOG_PARAMS.contextLines
|
|
773
|
+
],
|
|
774
|
+
response: (0, _agimon_ai_po_core_web.apiResponse)()
|
|
775
|
+
},
|
|
776
|
+
/**
|
|
777
|
+
* The live tail of that same log.
|
|
778
|
+
*
|
|
779
|
+
* `stream: true`, so the client exposes a URL and no call method: the
|
|
780
|
+
* consumer is the browser's own `EventSource`, and a relayed call would
|
|
781
|
+
* buffer a stream that never ends.
|
|
782
|
+
*/
|
|
783
|
+
logStream: {
|
|
784
|
+
method: "GET",
|
|
785
|
+
path: `/runners/:${RUN_ID_PARAM}/log/stream`,
|
|
786
|
+
query: [RUNNER_LOG_PARAMS.from],
|
|
787
|
+
stream: true
|
|
788
|
+
},
|
|
789
|
+
/** The unscrubbed pane bytes, for a terminal emulator rather than a reader. */
|
|
790
|
+
screenStream: {
|
|
791
|
+
method: "GET",
|
|
792
|
+
path: `/runners/:${RUN_ID_PARAM}/screen/stream`,
|
|
793
|
+
query: [RUNNER_LOG_PARAMS.from],
|
|
794
|
+
stream: true
|
|
795
|
+
},
|
|
796
|
+
input: {
|
|
797
|
+
method: "POST",
|
|
798
|
+
path: `/runners/:${RUN_ID_PARAM}/screen/input`
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
//#endregion
|
|
802
|
+
//#region src/services/runnerLogApi/index.ts
|
|
803
|
+
/** How often a follow re-reads the record to notice the runner exiting. */
|
|
804
|
+
/**
|
|
805
|
+
* How often a silent but living stream proves it is still there. Short enough
|
|
806
|
+
* that an idle proxy does not reap the socket, long enough to cost nothing.
|
|
807
|
+
*/
|
|
808
|
+
/** Kept in step with LogQuery's own default, so an unasked-for tail is the same size everywhere. */
|
|
809
|
+
/** A page that asks for more than this gets this; the whole point of the route is a bounded read. */
|
|
810
|
+
/** Path segments the client supplies are names, never paths. */
|
|
811
|
+
/** Backends that expose a pane another process can read and write. */
|
|
812
|
+
const MULTIPLEXER_BACKENDS = /* @__PURE__ */ new Set(["rmux", "tmux"]);
|
|
813
|
+
/** Rejects anything that could climb out of the session's own directory before a path is built. */
|
|
814
|
+
function isSafeSegment(value) {
|
|
815
|
+
return value !== "" && value !== "." && value !== ".." && SAFE_SEGMENT.test(value);
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* The log file a run wrote, or undefined when there is no such run or its
|
|
819
|
+
* record points somewhere it has no business pointing.
|
|
820
|
+
*
|
|
821
|
+
* The record names its own log path, and that record is written by another
|
|
822
|
+
* process, so the path is checked rather than trusted: it must sit inside this
|
|
823
|
+
* session's own logs directory. Without that check a doctored record would
|
|
824
|
+
* turn this route into an arbitrary file read.
|
|
825
|
+
*/
|
|
826
|
+
function resolveLogFile(stateDir, logsDir, sessionId, runId) {
|
|
827
|
+
const statePath = node_path.default.join(stateDir, `${runId}${STATE_EXTENSION}`);
|
|
828
|
+
let raw;
|
|
829
|
+
try {
|
|
830
|
+
raw = node_fs.default.readFileSync(statePath, "utf8");
|
|
831
|
+
} catch {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const record = parseRunnerRecord(raw);
|
|
835
|
+
if (record === void 0 || record.id !== runId || record.sessionId !== sessionId) return void 0;
|
|
836
|
+
const resolved = node_path.default.resolve(record.logPath);
|
|
837
|
+
if (resolved !== logsDir && !resolved.startsWith(`${logsDir}${node_path.default.sep}`)) return void 0;
|
|
838
|
+
return record;
|
|
839
|
+
}
|
|
840
|
+
function isRecord(value) {
|
|
841
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
842
|
+
}
|
|
843
|
+
function numberParam(value, fallback, max) {
|
|
844
|
+
const parsed = value === void 0 ? NaN : Number.parseInt(value, 10);
|
|
845
|
+
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
846
|
+
return Math.min(parsed, max);
|
|
847
|
+
}
|
|
848
|
+
function logQueryOf(url) {
|
|
849
|
+
const grep = url.searchParams.get(RUNNER_LOG_PARAMS.grep) ?? void 0;
|
|
850
|
+
return {
|
|
851
|
+
lines: numberParam(url.searchParams.get(RUNNER_LOG_PARAMS.lines) ?? void 0, 200, MAX_LINES),
|
|
852
|
+
...grep === void 0 || grep === "" ? {} : { grep },
|
|
853
|
+
ignoreCase: url.searchParams.get(RUNNER_LOG_PARAMS.ignoreCase) === "true",
|
|
854
|
+
contextLines: numberParam(url.searchParams.get(RUNNER_LOG_PARAMS.contextLines) ?? void 0, 0, MAX_LINES)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* This package's HTTP surface: one runner's log, as a bounded slice and as a
|
|
859
|
+
* live tail.
|
|
860
|
+
*
|
|
861
|
+
* The routes are mounted inside one session's own server, so they name a runner
|
|
862
|
+
* and nothing else; the session is the host, not a parameter. The reader only
|
|
863
|
+
* ever returns the last N lines of what matched, so both routes stay bounded no
|
|
864
|
+
* matter how large the file on disk is.
|
|
865
|
+
*
|
|
866
|
+
* Paths come from src/types/apiRoutes, the one table the browser's generated
|
|
867
|
+
* client reads too, so this app and the cockpit cannot disagree about where a
|
|
868
|
+
* route lives. They stay relative to the mount the host strips.
|
|
869
|
+
*/
|
|
870
|
+
function createRunnerLogApi(options) {
|
|
871
|
+
const logReader = options.logReader ?? new require_logReader.LogReader();
|
|
872
|
+
const logTail = options.logTail ?? new LogTail();
|
|
873
|
+
const sessionId = options.sessionId;
|
|
874
|
+
const paths = new require_runnerPaths.RunnerPaths(options.cwd, options.environment);
|
|
875
|
+
const storeDir = options.storeDir ?? require_runnerPaths.resolveRunnerStoreDirectory(options.environment ?? process.env);
|
|
876
|
+
const stateDir = options.storeDir === void 0 ? paths.stateDirectory(sessionId) : require_runnerPaths.runnerStateDirFor(storeDir, sessionId);
|
|
877
|
+
const logsDir = options.storeDir === void 0 ? paths.logDirectory(sessionId) : node_path.default.join(storeDir, sessionId, LOG_DIR_NAME);
|
|
878
|
+
const app = new hono.Hono();
|
|
879
|
+
let paneBackend = options.pane;
|
|
880
|
+
const pane = () => {
|
|
881
|
+
paneBackend ??= (() => {
|
|
882
|
+
const paths = new require_runnerPaths.RunnerPaths(options.cwd, options.environment);
|
|
883
|
+
return new require_runnerDependencies.PtyBackendChain(new require_rmuxBackend.RmuxBackend(paths), new require_runnerDependencies.TmuxBackend(paths));
|
|
884
|
+
})();
|
|
885
|
+
return paneBackend;
|
|
886
|
+
};
|
|
887
|
+
/** The record behind a request, or undefined when this session has no such run. */
|
|
888
|
+
const runnerOf = (runId) => isSafeSegment(runId) ? resolveLogFile(stateDir, logsDir, sessionId, runId) : void 0;
|
|
889
|
+
/**
|
|
890
|
+
* The pane a request may attach to.
|
|
891
|
+
*
|
|
892
|
+
* Same posture as the log routes: the record is written by another process,
|
|
893
|
+
* so nothing on it is trusted until it is checked. The target must be one
|
|
894
|
+
* this package owns, the run must still be up, and it must have been started
|
|
895
|
+
* interactive. A non-interactive run has no terminal to type into, and a
|
|
896
|
+
* subprocess-backed run has no pane at all.
|
|
897
|
+
*/
|
|
898
|
+
const attachableOf = (runId) => {
|
|
899
|
+
const record = runnerOf(runId);
|
|
900
|
+
if (record === void 0 || record.state !== "running" || record.interactive !== true) return void 0;
|
|
901
|
+
const target = record.backendTarget;
|
|
902
|
+
if (target === void 0 || !MULTIPLEXER_BACKENDS.has(record.backend) || !isSafeSegment(target)) return void 0;
|
|
903
|
+
return {
|
|
904
|
+
record,
|
|
905
|
+
target
|
|
906
|
+
};
|
|
907
|
+
};
|
|
908
|
+
app.get(apiRoutes_default.log.path, (context) => {
|
|
909
|
+
const runId = context.req.param(RUN_ID_PARAM);
|
|
910
|
+
const record = runnerOf(runId);
|
|
911
|
+
if (record === void 0) return context.json({ error: `No runner '${runId}' in this session.` }, 404);
|
|
912
|
+
const body = {
|
|
913
|
+
...logReader.read(record.logPath, logQueryOf(new URL(context.req.url))),
|
|
914
|
+
runId,
|
|
915
|
+
running: record.state === RUNNING_STATE
|
|
916
|
+
};
|
|
917
|
+
return context.json(body);
|
|
918
|
+
});
|
|
919
|
+
app.get(apiRoutes_default.logStream.path, (context) => {
|
|
920
|
+
const runId = context.req.param(RUN_ID_PARAM);
|
|
921
|
+
const record = runnerOf(runId);
|
|
922
|
+
if (record === void 0) return context.json({ error: `No runner '${runId}' in this session.` }, 404);
|
|
923
|
+
const from = numberParam(new URL(context.req.url).searchParams.get(RUNNER_LOG_PARAMS.from) ?? void 0, 0, Number.MAX_SAFE_INTEGER);
|
|
924
|
+
return (0, hono_streaming.streamSSE)(context, async (stream) => {
|
|
925
|
+
const send = (event) => stream.writeSSE({
|
|
926
|
+
event: RUNNER_LOG_STREAM_EVENT,
|
|
927
|
+
data: JSON.stringify(event)
|
|
928
|
+
});
|
|
929
|
+
let pump = Promise.resolve();
|
|
930
|
+
const queue = (event) => {
|
|
931
|
+
pump = pump.then(() => send(event)).catch(() => void 0);
|
|
932
|
+
};
|
|
933
|
+
let finish = () => void 0;
|
|
934
|
+
const done = new Promise((resolve) => {
|
|
935
|
+
finish = resolve;
|
|
936
|
+
});
|
|
937
|
+
const handle = logTail.follow(record.logPath, {
|
|
938
|
+
from,
|
|
939
|
+
onLines: (lines, completeThrough) => queue({
|
|
940
|
+
lines,
|
|
941
|
+
offset: completeThrough
|
|
942
|
+
}),
|
|
943
|
+
onError: () => finish()
|
|
944
|
+
});
|
|
945
|
+
const poll = setInterval(() => {
|
|
946
|
+
const current = runnerOf(runId);
|
|
947
|
+
if (current !== void 0 && current.state === "running") return;
|
|
948
|
+
queue({
|
|
949
|
+
lines: [],
|
|
950
|
+
ended: true
|
|
951
|
+
});
|
|
952
|
+
finish();
|
|
953
|
+
}, STATE_POLL_MS);
|
|
954
|
+
const heartbeat = setInterval(() => {
|
|
955
|
+
pump = pump.then(() => stream.writeSSE({
|
|
956
|
+
event: RUNNER_LOG_PING_EVENT,
|
|
957
|
+
data: ""
|
|
958
|
+
})).catch(() => void 0);
|
|
959
|
+
}, HEARTBEAT_MS);
|
|
960
|
+
stream.onAbort(() => finish());
|
|
961
|
+
await done;
|
|
962
|
+
clearInterval(poll);
|
|
963
|
+
clearInterval(heartbeat);
|
|
964
|
+
handle.close();
|
|
965
|
+
await pump;
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
app.get(apiRoutes_default.screenStream.path, (context) => {
|
|
969
|
+
const runId = context.req.param(RUN_ID_PARAM);
|
|
970
|
+
const attachable = attachableOf(runId);
|
|
971
|
+
if (attachable === void 0) return context.json({ error: NOT_ATTACHABLE }, 404);
|
|
972
|
+
const rawPath = `${attachable.record.logPath}${RAW_LOG_SUFFIX}`;
|
|
973
|
+
const from = numberParam(new URL(context.req.url).searchParams.get(RUNNER_LOG_PARAMS.from) ?? void 0, 0, Number.MAX_SAFE_INTEGER);
|
|
974
|
+
return (0, hono_streaming.streamSSE)(context, async (stream) => {
|
|
975
|
+
let pump = Promise.resolve();
|
|
976
|
+
const queue = (event) => {
|
|
977
|
+
pump = pump.then(() => stream.writeSSE({
|
|
978
|
+
event: RUNNER_SCREEN_EVENT,
|
|
979
|
+
data: JSON.stringify(event)
|
|
980
|
+
})).catch(() => void 0);
|
|
981
|
+
};
|
|
982
|
+
let finish = () => void 0;
|
|
983
|
+
const done = new Promise((resolve) => {
|
|
984
|
+
finish = resolve;
|
|
985
|
+
});
|
|
986
|
+
let offset = from;
|
|
987
|
+
const drain = () => {
|
|
988
|
+
let size;
|
|
989
|
+
try {
|
|
990
|
+
size = node_fs.default.statSync(rawPath).size;
|
|
991
|
+
} catch {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (size < offset) offset = 0;
|
|
995
|
+
if (size === offset) return;
|
|
996
|
+
const length = size - offset;
|
|
997
|
+
const buffer = Buffer.alloc(length);
|
|
998
|
+
const descriptor = node_fs.default.openSync(rawPath, "r");
|
|
999
|
+
let read;
|
|
1000
|
+
try {
|
|
1001
|
+
read = node_fs.default.readSync(descriptor, buffer, 0, length, offset);
|
|
1002
|
+
} finally {
|
|
1003
|
+
node_fs.default.closeSync(descriptor);
|
|
1004
|
+
}
|
|
1005
|
+
if (read <= 0) return;
|
|
1006
|
+
offset += read;
|
|
1007
|
+
queue({
|
|
1008
|
+
chunk: buffer.subarray(0, read).toString("base64"),
|
|
1009
|
+
offset
|
|
1010
|
+
});
|
|
1011
|
+
};
|
|
1012
|
+
const poll = setInterval(() => {
|
|
1013
|
+
const current = runnerOf(runId);
|
|
1014
|
+
drain();
|
|
1015
|
+
if (current === void 0 || current.state !== "running") {
|
|
1016
|
+
queue({
|
|
1017
|
+
chunk: "",
|
|
1018
|
+
offset,
|
|
1019
|
+
ended: true
|
|
1020
|
+
});
|
|
1021
|
+
finish();
|
|
1022
|
+
}
|
|
1023
|
+
}, 100);
|
|
1024
|
+
stream.onAbort(() => finish());
|
|
1025
|
+
await done;
|
|
1026
|
+
clearInterval(poll);
|
|
1027
|
+
await pump;
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
/**
|
|
1031
|
+
* Types into a live pane.
|
|
1032
|
+
*
|
|
1033
|
+
* This is the one route in the package that writes rather than reads, so it
|
|
1034
|
+
* is deliberately narrow: an owned target, still running, started
|
|
1035
|
+
* interactive, and a body that is nothing but text.
|
|
1036
|
+
*/
|
|
1037
|
+
app.post(apiRoutes_default.input.path, async (context) => {
|
|
1038
|
+
const runId = context.req.param(RUN_ID_PARAM);
|
|
1039
|
+
const attachable = attachableOf(runId);
|
|
1040
|
+
if (attachable === void 0) return context.json({ error: NOT_ATTACHABLE }, 404);
|
|
1041
|
+
let body;
|
|
1042
|
+
try {
|
|
1043
|
+
body = await context.req.json();
|
|
1044
|
+
} catch {
|
|
1045
|
+
return context.json({ error: "Expected a JSON body carrying text." }, 400);
|
|
1046
|
+
}
|
|
1047
|
+
const text = isRecord(body) ? body.text : void 0;
|
|
1048
|
+
if (typeof text !== "string" || text === "") return context.json({ error: "Expected a non-empty text." }, 400);
|
|
1049
|
+
if (text.length > 4096) return context.json({ error: "That is more input than a pane takes." }, 413);
|
|
1050
|
+
if (!await pane().input(attachable.target, text)) return context.json({ error: "The runner did not take the input." }, 502);
|
|
1051
|
+
return context.json({ delivered: true });
|
|
1052
|
+
});
|
|
1053
|
+
return app;
|
|
1054
|
+
}
|
|
1055
|
+
/** The named export a host imports from this package's built session entry. */
|
|
1056
|
+
const api = {
|
|
1057
|
+
basePath: RUNNER_API_BASE_PATH,
|
|
1058
|
+
start(context) {
|
|
1059
|
+
const app = createRunnerLogApi({
|
|
1060
|
+
sessionId: context.sessionId ?? "",
|
|
1061
|
+
cwd: context.cwd,
|
|
1062
|
+
environment: context.environment
|
|
1063
|
+
});
|
|
1064
|
+
return {
|
|
1065
|
+
fetch: (request) => app.fetch(request),
|
|
1066
|
+
close: () => void 0
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/extensions/workspaces/sessions/(backend)/api/runner/route.server.ts
|
|
1072
|
+
/**
|
|
1073
|
+
* Mounts this package's HTTP API at `runner`, which the folder names.
|
|
1074
|
+
*
|
|
1075
|
+
* The folder is the declaration: the build reads the base path off it and hands
|
|
1076
|
+
* the browser a client bound to the same segment, so the mount is stated once
|
|
1077
|
+
* rather than in a constant and in four URL builders that were each free to
|
|
1078
|
+
* drift.
|
|
1079
|
+
*/
|
|
1080
|
+
var route_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoute)(api);
|
|
1081
|
+
//#endregion
|
|
1082
|
+
//#region src/extensions/workspaces/sessions/(backend)/command/runners.server.ts
|
|
1083
|
+
var runners_server_default = (0, _agimon_ai_po_core_extensionFile.defineServerCommand)((context) => context.root.command);
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/session-compact.server.ts
|
|
1086
|
+
var session_compact_server_default = (0, _agimon_ai_po_core_extensionFile.defineHook)(({ root }) => ({
|
|
1087
|
+
event: "session_compact",
|
|
1088
|
+
handle: root.recoverCompaction
|
|
1089
|
+
}));
|
|
1090
|
+
//#endregion
|
|
1091
|
+
//#region src/extensions/workspaces/sessions/(backend)/resource/po-use-runner.server.ts
|
|
1092
|
+
var po_use_runner_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)({
|
|
1093
|
+
name: "po-use-runner",
|
|
1094
|
+
description: "Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.",
|
|
1095
|
+
when: _agimon_ai_po_core_help.DOOM_HELP_WHEN,
|
|
1096
|
+
kind: "skill",
|
|
1097
|
+
path: (0, _agimon_ai_po_core_serverFacet.packageResourcePath)(require("url").pathToFileURL(__filename).href, "src/prompts/po-use-runner/SKILL.md"),
|
|
1098
|
+
read: () => (0, _agimon_ai_po_core_serverFacet.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/po-use-runner/SKILL.md")
|
|
1099
|
+
});
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool/bash.server.ts
|
|
1102
|
+
var bash_server_default = (0, _agimon_ai_po_core_extensionFile.defineServerTool)((context) => context.root.tool);
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region generated/server.ts
|
|
1105
|
+
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
1106
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
1107
|
+
...identity,
|
|
1108
|
+
...value
|
|
1109
|
+
};
|
|
1110
|
+
const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
|
|
1111
|
+
name: "@agimon-ai/po-runner",
|
|
1112
|
+
providerOverrides: (0, _agimon_ai_po_core_serverFacet.composeServerProviderOverrides)(root_server_default),
|
|
1113
|
+
global: () => ({ get channels() {
|
|
1114
|
+
return [() => via({ frameType: "runner_runs" }, runnerRuns_server_default())];
|
|
1115
|
+
} }),
|
|
1116
|
+
workspace: () => ({ get channels() {
|
|
1117
|
+
return [() => via({ frameType: "runner_runs" }, runnerRuns_server_default())];
|
|
1118
|
+
} }),
|
|
1119
|
+
session: async (context) => {
|
|
1120
|
+
const scopeSession = await root_server_default(context);
|
|
1121
|
+
const contextSession = {
|
|
1122
|
+
...context,
|
|
1123
|
+
root: scopeSession.value
|
|
1124
|
+
};
|
|
1125
|
+
return {
|
|
1126
|
+
...(0, _agimon_ai_po_core_extensionFile.composeRootHooks)(scopeSession),
|
|
1127
|
+
get api() {
|
|
1128
|
+
return [at(route_server_default, contextSession)];
|
|
1129
|
+
},
|
|
1130
|
+
get channels() {
|
|
1131
|
+
return [() => via({ frameType: "runner_runs" }, runnerRuns_server_default())];
|
|
1132
|
+
},
|
|
1133
|
+
get commands() {
|
|
1134
|
+
return [via({ name: "runners" }, at(runners_server_default, contextSession))];
|
|
1135
|
+
},
|
|
1136
|
+
get hooks() {
|
|
1137
|
+
return [via({ event: "session_compact" }, at(session_compact_server_default, contextSession))];
|
|
1138
|
+
},
|
|
1139
|
+
get resources() {
|
|
1140
|
+
return [at(po_use_runner_server_default, contextSession)];
|
|
1141
|
+
},
|
|
1142
|
+
get tools() {
|
|
1143
|
+
return [via({ name: "bash" }, at(bash_server_default, contextSession))];
|
|
1144
|
+
},
|
|
1145
|
+
services: [...scopeSession.services ?? []],
|
|
1146
|
+
get activities() {
|
|
1147
|
+
return [...scopeSession.activities ?? []];
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
//#endregion
|
|
1153
|
+
exports.default = facet;
|
|
1154
|
+
exports.facet = facet;
|
|
1155
|
+
|
|
1156
|
+
//# sourceMappingURL=server.cjs.map
|