@agimon-ai/po-runner 0.0.0-alpha.2 → 0.0.0-alpha.21
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 +149 -21
- package/README.md +17 -17
- package/dist/ansiScrub.mjs +1 -2
- package/dist/apiContracts.cjs +1 -1
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.mjs +1 -1
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/{bashResult-bIVSzoj-.mjs → bashResult-BqDa7sbC.mjs} +10 -9
- package/dist/bashResult-BqDa7sbC.mjs.map +1 -0
- package/dist/{bashResult-DNgtahGR.cjs → bashResult-C2iKhHpD.cjs} +10 -9
- package/dist/bashResult-C2iKhHpD.cjs.map +1 -0
- package/dist/{bashRunService-CJ02XaAq.mjs → bashRunService-Cl3pYccr.mjs} +2 -2
- package/dist/bashRunService-Cl3pYccr.mjs.map +1 -0
- package/dist/{bashRunService-DyqxOSlx.cjs → bashRunService-DjOH5kye.cjs} +2 -2
- package/dist/bashRunService-DjOH5kye.cjs.map +1 -0
- package/dist/bashRunService.cjs +1 -1
- package/dist/bashRunService.d.mts +3 -3
- package/dist/bashRunService.mjs +1 -2
- package/dist/bashSchema.cjs +1 -1
- package/dist/bashSchema.mjs +1 -2
- package/dist/{bashTool-C-k7fmyh.mjs → bashTool-BvC3YOgG.mjs} +2 -2
- package/dist/{bashTool-C-k7fmyh.mjs.map → bashTool-BvC3YOgG.mjs.map} +1 -1
- package/dist/{bashTool-CdgtqVFD.cjs → bashTool-euL6zMDl.cjs} +2 -2
- package/dist/{bashTool-CdgtqVFD.cjs.map → bashTool-euL6zMDl.cjs.map} +1 -1
- package/dist/bashTool.cjs +3 -3
- package/dist/bashTool.d.mts +1 -1
- package/dist/bashTool.mjs +3 -3
- package/dist/bin/cli.cjs +6 -6
- package/dist/bin/cli.cjs.map +1 -1
- package/dist/bin/cli.mjs +7 -7
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/bin/logSink.cjs +6 -6
- package/dist/bin/logSink.cjs.map +1 -1
- package/dist/bin/logSink.mjs +7 -7
- package/dist/bin/logSink.mjs.map +1 -1
- package/dist/bin/runnerHost.cjs +4 -4
- package/dist/bin/runnerHost.cjs.map +1 -1
- package/dist/bin/runnerHost.mjs +5 -5
- package/dist/bin/runnerHost.mjs.map +1 -1
- package/dist/{cliApp-Dj9nwaOv.cjs → cliApp-T8Fx_-1j.cjs} +6 -6
- package/dist/cliApp-T8Fx_-1j.cjs.map +1 -0
- package/dist/{cliApp-v8Z3rMCi.mjs → cliApp-q97eVh4E.mjs} +6 -6
- package/dist/cliApp-q97eVh4E.mjs.map +1 -0
- package/dist/cliApp.cjs +1 -1
- package/dist/cliApp.d.mts +1 -1
- package/dist/cliApp.mjs +1 -2
- package/dist/clock.mjs +1 -2
- package/dist/{compaction-4QSUsBxq.cjs → compaction-Cduebib_.cjs} +3 -3
- package/dist/compaction-Cduebib_.cjs.map +1 -0
- package/dist/{compaction-DCw80VZj.mjs → compaction-nNlE6e5I.mjs} +3 -3
- package/dist/compaction-nNlE6e5I.mjs.map +1 -0
- package/dist/compaction.cjs +1 -1
- package/dist/compaction.d.mts +1 -1
- package/dist/compaction.mjs +1 -2
- package/dist/config.cjs +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -2
- package/dist/extensions/mcp-ui.mjs.map +1 -1
- package/dist/extensions/mcp.cjs +8 -7
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.d.mts +1 -1
- package/dist/extensions/mcp.mjs +7 -6
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/native.mjs +1 -1
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +233 -119
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +2 -2
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +233 -119
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +88 -62
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/{headless-BGYfIlNk.mjs → headless-DOlCixyf.mjs} +5 -5
- package/dist/headless-DOlCixyf.mjs.map +1 -0
- package/dist/{headless-CHUsBGhg.cjs → headless-Dko_yBiy.cjs} +5 -5
- package/dist/headless-Dko_yBiy.cjs.map +1 -0
- package/dist/{index-Dhi8asLH.d.mts → index--xF7DvsE.d.mts} +4 -4
- package/dist/{index-Dhi8asLH.d.mts.map → index--xF7DvsE.d.mts.map} +1 -1
- package/dist/{index-BZL7keVW.d.mts → index-BCCF1sGD.d.mts} +2 -2
- package/dist/{index-BZL7keVW.d.mts.map → index-BCCF1sGD.d.mts.map} +1 -1
- package/dist/{index-BwB_jTI9.d.mts → index-BzeXJk6V.d.mts} +5 -5
- package/dist/index-BzeXJk6V.d.mts.map +1 -0
- package/dist/{index-D9-IXax4.d.mts → index-C-IC35d9.d.mts} +2 -2
- package/dist/{index-D9-IXax4.d.mts.map → index-C-IC35d9.d.mts.map} +1 -1
- package/dist/{index-DBz0DxnL.d.mts → index-CzCF_OP9.d.mts} +3 -4
- package/dist/index-CzCF_OP9.d.mts.map +1 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/{launcher-BDm6-DiQ.mjs → launcher-CCYsFBM9.mjs} +3 -3
- package/dist/{launcher-BDm6-DiQ.mjs.map → launcher-CCYsFBM9.mjs.map} +1 -1
- package/dist/{launcher-SlyE8R3b.cjs → launcher-ZgDdyXpq.cjs} +8 -2
- package/dist/{launcher-SlyE8R3b.cjs.map → launcher-ZgDdyXpq.cjs.map} +1 -1
- package/dist/launcher.cjs +1 -1
- package/dist/launcher.d.mts +1 -1
- package/dist/launcher.mjs +1 -2
- package/dist/{lifeline-BZaQA0rm.mjs → lifeline-Bm-bHIjS.mjs} +6 -6
- package/dist/lifeline-Bm-bHIjS.mjs.map +1 -0
- package/dist/{lifeline-BuV_erib.cjs → lifeline-m7laIzIP.cjs} +6 -6
- package/dist/lifeline-m7laIzIP.cjs.map +1 -0
- package/dist/lifeline.cjs +1 -1
- package/dist/lifeline.d.mts +1 -1
- package/dist/lifeline.mjs +1 -1
- package/dist/{logFile-CNvepZh0.cjs → logFile-CnPKtxPy.cjs} +2 -2
- package/dist/{logFile-CNvepZh0.cjs.map → logFile-CnPKtxPy.cjs.map} +1 -1
- package/dist/{logFile-KKPbkC-a.mjs → logFile-XcDHBwu1.mjs} +2 -2
- package/dist/{logFile-KKPbkC-a.mjs.map → logFile-XcDHBwu1.mjs.map} +1 -1
- package/dist/logFile.cjs +1 -1
- package/dist/logFile.d.mts +1 -1
- package/dist/logFile.mjs +1 -2
- package/dist/{logReader-B6J7L7Ua.cjs → logReader-DHCK2BEr.cjs} +4 -4
- package/dist/{logReader-B6J7L7Ua.cjs.map → logReader-DHCK2BEr.cjs.map} +1 -1
- package/dist/{logReader-UfbDbwQl.mjs → logReader-mq9uVurh.mjs} +4 -4
- package/dist/{logReader-UfbDbwQl.mjs.map → logReader-mq9uVurh.mjs.map} +1 -1
- package/dist/logReader.cjs +1 -1
- package/dist/logReader.mjs +1 -2
- package/dist/processControl.mjs +1 -2
- package/dist/{ptyHost-DlQtTB8F.mjs → ptyHost-CwbLLr90.mjs} +2 -2
- package/dist/{ptyHost-DlQtTB8F.mjs.map → ptyHost-CwbLLr90.mjs.map} +1 -1
- package/dist/{ptyHost-MI2Z6WTG.cjs → ptyHost-D35hNmtd.cjs} +2 -2
- package/dist/{ptyHost-MI2Z6WTG.cjs.map → ptyHost-D35hNmtd.cjs.map} +1 -1
- package/dist/ptyHost.cjs +1 -1
- package/dist/ptyHost.mjs +1 -2
- package/dist/{ptySpawner-DEi1KOnd.mjs → ptySpawner-C375NJnj.mjs} +2 -2
- package/dist/ptySpawner-C375NJnj.mjs.map +1 -0
- package/dist/{ptySpawner-ijw0g5_T.cjs → ptySpawner-C39IzbRo.cjs} +2 -2
- package/dist/ptySpawner-C39IzbRo.cjs.map +1 -0
- package/dist/ptySpawner.cjs +1 -1
- package/dist/ptySpawner.d.mts +1 -1
- package/dist/ptySpawner.mjs +1 -2
- package/dist/{reconcile-DMeNSxwn.mjs → reconcile-5cZ11jSD.cjs} +12 -42
- package/dist/reconcile-5cZ11jSD.cjs.map +1 -0
- package/dist/{reconcile-BugwYFbV.cjs → reconcile-wNYQJZF-.mjs} +2 -62
- package/dist/reconcile-wNYQJZF-.mjs.map +1 -0
- package/dist/reconcile.cjs +1 -2
- package/dist/reconcile.d.mts +2 -9
- package/dist/reconcile.d.mts.map +1 -1
- package/dist/reconcile.mjs +1 -2
- package/dist/responseEnvelope.cjs +2 -2
- package/dist/responseEnvelope.d.mts +3 -3
- package/dist/responseEnvelope.mjs +2 -2
- package/dist/{rmuxBackend-DlxgqRZ1.cjs → rmuxBackend-Dh0CYVAL.cjs} +5 -5
- package/dist/rmuxBackend-Dh0CYVAL.cjs.map +1 -0
- package/dist/{rmuxBackend-DfyQwJrc.mjs → rmuxBackend-rjrVTG6K.mjs} +4 -4
- package/dist/rmuxBackend-rjrVTG6K.mjs.map +1 -0
- package/dist/rmuxBackend.cjs +1 -1
- package/dist/rmuxBackend.d.mts +1 -1
- package/dist/rmuxBackend.mjs +1 -2
- package/dist/{rtkProcessor-Bc4L4o_8.cjs → rtkProcessor-DSGbM-Cm.cjs} +4 -4
- package/dist/rtkProcessor-DSGbM-Cm.cjs.map +1 -0
- package/dist/{rtkProcessor-B9KPdyzr.mjs → rtkProcessor-Iw9gP5Xp.mjs} +3 -3
- package/dist/rtkProcessor-Iw9gP5Xp.mjs.map +1 -0
- package/dist/rtkProcessor.cjs +1 -1
- package/dist/rtkProcessor.d.mts +1 -1
- package/dist/rtkProcessor.mjs +1 -1
- package/dist/{runnerConfig-D2tGq5ZM.cjs → runnerConfig-BoLMinlx.cjs} +11 -11
- package/dist/runnerConfig-BoLMinlx.cjs.map +1 -0
- package/dist/{runnerConfig-BjtulXBI.d.mts → runnerConfig-DuWL6TJx.d.mts} +10 -10
- package/dist/{runnerConfig-BjtulXBI.d.mts.map → runnerConfig-DuWL6TJx.d.mts.map} +1 -1
- package/dist/{runnerConfig-BQBlVAXn.mjs → runnerConfig-XXMq4VQA.mjs} +11 -11
- package/dist/runnerConfig-XXMq4VQA.mjs.map +1 -0
- package/dist/{runnerDependencies-DruByBGS.mjs → runnerDependencies-7h_cxOva.mjs} +17 -17
- package/dist/runnerDependencies-7h_cxOva.mjs.map +1 -0
- package/dist/{runnerDependencies-KMZigIxB.cjs → runnerDependencies-DSun3xO-.cjs} +17 -17
- package/dist/runnerDependencies-DSun3xO-.cjs.map +1 -0
- package/dist/runnerDependencies.cjs +1 -1
- package/dist/runnerDependencies.d.mts +1 -1
- package/dist/runnerDependencies.mjs +1 -2
- package/dist/runnerNamer.d.mts +1 -1
- package/dist/runnerNamer.mjs +1 -2
- package/dist/{runnerPaths-BTEuzxw0.cjs → runnerPaths-B41rMOY6.cjs} +20 -38
- package/dist/runnerPaths-B41rMOY6.cjs.map +1 -0
- package/dist/{runnerPaths-B_dAAeco.mjs → runnerPaths-Dibbvyx_.mjs} +20 -38
- package/dist/runnerPaths-Dibbvyx_.mjs.map +1 -0
- package/dist/runnerPaths.cjs +1 -1
- package/dist/runnerPaths.d.mts +5 -7
- package/dist/runnerPaths.d.mts.map +1 -1
- package/dist/runnerPaths.mjs +1 -2
- package/dist/{runnerRegistry-CeOHEjBv.mjs → runnerRegistry-BgaR5O86.mjs} +12 -11
- package/dist/runnerRegistry-BgaR5O86.mjs.map +1 -0
- package/dist/{runnerRegistry-CtJiD02R.d.mts → runnerRegistry-V-Ocaf4N.d.mts} +1 -3
- package/dist/{runnerRegistry-CtJiD02R.d.mts.map → runnerRegistry-V-Ocaf4N.d.mts.map} +1 -1
- package/dist/{runnerRegistry-BmkZTJHi.cjs → runnerRegistry-dQYlMSpQ.cjs} +23 -10
- package/dist/runnerRegistry-dQYlMSpQ.cjs.map +1 -0
- package/dist/runnerRegistry.cjs +1 -1
- package/dist/runnerRegistry.d.mts +2 -2
- package/dist/runnerRegistry.mjs +1 -2
- package/dist/services/lifeline/client.cjs +2 -2
- package/dist/services/lifeline/client.cjs.map +1 -1
- package/dist/services/lifeline/client.d.mts +1 -1
- package/dist/services/lifeline/client.mjs +2 -2
- package/dist/services/lifeline/client.mjs.map +1 -1
- package/dist/{session-Chsb_Nh2.mjs → session-BKYnH4R6.mjs} +2 -2
- package/dist/{session-Chsb_Nh2.mjs.map → session-BKYnH4R6.mjs.map} +1 -1
- package/dist/{session-BC43uEyG.cjs → session-BvqzhXZY.cjs} +2 -2
- package/dist/{session-BC43uEyG.cjs.map → session-BvqzhXZY.cjs.map} +1 -1
- package/dist/session.cjs +1 -1
- package/dist/session.mjs +1 -1
- package/dist/spawner.mjs +1 -2
- package/dist/{type-C31e1gla.d.mts → type-Do0YxwVQ.d.mts} +4 -4
- package/dist/{type-C31e1gla.d.mts.map → type-Do0YxwVQ.d.mts.map} +1 -1
- package/dist/{type-BXM03qSw.d.mts → type-DwMZoINK.d.mts} +8 -8
- package/dist/{type-BXM03qSw.d.mts.map → type-DwMZoINK.d.mts.map} +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +31 -34
- package/skills/{doom-runner → po-runner}/SKILL.md +15 -15
- package/src/prompts/po-use-runner/SKILL.md +11 -11
- package/dist/bashResult-DNgtahGR.cjs.map +0 -1
- package/dist/bashResult-bIVSzoj-.mjs.map +0 -1
- package/dist/bashRunService-CJ02XaAq.mjs.map +0 -1
- package/dist/bashRunService-DyqxOSlx.cjs.map +0 -1
- package/dist/cliApp-Dj9nwaOv.cjs.map +0 -1
- package/dist/cliApp-v8Z3rMCi.mjs.map +0 -1
- package/dist/compaction-4QSUsBxq.cjs.map +0 -1
- package/dist/compaction-DCw80VZj.mjs.map +0 -1
- package/dist/headless-BGYfIlNk.mjs.map +0 -1
- package/dist/headless-CHUsBGhg.cjs.map +0 -1
- package/dist/index-BwB_jTI9.d.mts.map +0 -1
- package/dist/index-DBz0DxnL.d.mts.map +0 -1
- package/dist/lifeline-BZaQA0rm.mjs.map +0 -1
- package/dist/lifeline-BuV_erib.cjs.map +0 -1
- package/dist/ptySpawner-DEi1KOnd.mjs.map +0 -1
- package/dist/ptySpawner-ijw0g5_T.cjs.map +0 -1
- package/dist/reconcile-BugwYFbV.cjs.map +0 -1
- package/dist/reconcile-DMeNSxwn.mjs.map +0 -1
- package/dist/rmuxBackend-DfyQwJrc.mjs.map +0 -1
- package/dist/rmuxBackend-DlxgqRZ1.cjs.map +0 -1
- package/dist/rtkProcessor-B9KPdyzr.mjs.map +0 -1
- package/dist/rtkProcessor-Bc4L4o_8.cjs.map +0 -1
- package/dist/runnerConfig-BQBlVAXn.mjs.map +0 -1
- package/dist/runnerConfig-D2tGq5ZM.cjs.map +0 -1
- package/dist/runnerDependencies-DruByBGS.mjs.map +0 -1
- package/dist/runnerDependencies-KMZigIxB.cjs.map +0 -1
- package/dist/runnerPaths-BTEuzxw0.cjs.map +0 -1
- package/dist/runnerPaths-B_dAAeco.mjs.map +0 -1
- package/dist/runnerRegistry-BmkZTJHi.cjs.map +0 -1
- package/dist/runnerRegistry-CeOHEjBv.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bashResult-DNgtahGR.cjs","names":["getErrorMaxEntries","getErrorPatterns","getErrorMaxVariantsJoined","getHeadRatio","getResultMaxLines","getResultMaxBytes","getResultMaxTokens","getErrorBudgetRatio","getSuccessResultMaxBytes","getSuccessResultMaxTokens","stripAnsi"],"sources":["../src/constants/bashResult.ts","../src/constants/tokenEstimate.ts","../src/services/tokenEstimate/index.ts","../src/services/bashResult/index.ts"],"sourcesContent":["export const BYTES_PER_KB = 1024;\nexport const BYTES_PER_MB = 1_048_576;\nexport const MAX_VARIANTS_TRACKED = 12;\nexport const MIN_SHARED_AFFIX = 8;\nexport const ERROR_WINDOW_CHARS = 60;\nexport const PRAGMA_PATTERN = /^\\s*(?:#|\\/\\/)\\s*@doom:\\s*(\\{.*\\})\\s*$/u;\nexport const PRAGMA_BYTES_CEILING = 262_144;\nexport const PRAGMA_LINES_CEILING = 5_000;\nexport const PRAGMA_TOKENS_CEILING = 100_000;\n","export const WORD_CHARS_PER_TOKEN = 5;\nexport const MIXED_CHARS_PER_TOKEN = 2.1;\nexport const PUNCTUATION_CHARS_PER_TOKEN = 2;\nexport const DENSE_SCRIPT_START = 0x2fff;\nexport const ASTRAL_TOKEN_COST = 2;\nexport const ALPHANUMERIC_RUN = /([^\\p{L}\\p{N}]+)/u;\nexport const HAS_ALPHANUMERIC = /[\\p{L}\\p{N}]/u;\nexport const HAS_DIGIT = /\\d/u;\nexport const HAS_LETTER = /[A-Za-z]/u;\n","import {\n ALPHANUMERIC_RUN,\n ASTRAL_TOKEN_COST,\n DENSE_SCRIPT_START,\n HAS_ALPHANUMERIC,\n HAS_DIGIT,\n HAS_LETTER,\n MIXED_CHARS_PER_TOKEN,\n PUNCTUATION_CHARS_PER_TOKEN,\n WORD_CHARS_PER_TOKEN,\n} from '../../constants/tokenEstimate';\n/**\n * Estimates BPE tokens without carrying a vocabulary.\n *\n * Bytes are a poor proxy for what a result costs: measured against\n * `gpt-tokenizer`, real text ranges from 1.4 characters per token for base64 to\n * 4.7 for English prose, a spread of more than three. Truncation runs on every\n * command and is synchronous, so loading a real tokenizer here would charge\n * every session for a table most of them never need.\n *\n * The shape below follows how BPE actually behaves: punctuation forces token\n * boundaries, natural words are cheap, and runs mixing letters with digits are\n * expensive. Calibrated against `gpt-tokenizer` over build logs, source, JSON,\n * stack traces, CJK, base64, hex, and UUIDs, the worst error is a factor of\n * 1.46 either way. It stays an estimate, so the byte ceiling remains the hard\n * bound behind it.\n */\n\n/** Characters per token for a natural word. */\n/** Characters per token for a run mixing letters and digits, which tokenizes poorly. */\n/** Characters per token for a run of punctuation or whitespace. */\n/** Dense scripts such as CJK cost about one token per character. */\n\nexport function estimateTokens(text: string): number {\n let tokens = 0;\n for (const run of text.split(ALPHANUMERIC_RUN)) {\n if (run.length === 0) continue;\n if (!HAS_ALPHANUMERIC.test(run)) {\n tokens += run.length / PUNCTUATION_CHARS_PER_TOKEN;\n continue;\n }\n\n let ascii = 0;\n let dense = 0;\n let astral = 0;\n for (const character of run) {\n const code = character.codePointAt(0) ?? 0;\n if (code > 0xffff) astral += 1;\n else if (code > DENSE_SCRIPT_START) dense += 1;\n else ascii += 1;\n }\n tokens += astral * ASTRAL_TOKEN_COST + dense;\n if (ascii > 0) {\n const mixed = HAS_DIGIT.test(run) && HAS_LETTER.test(run);\n tokens += Math.max(1, ascii / (mixed ? MIXED_CHARS_PER_TOKEN : WORD_CHARS_PER_TOKEN));\n }\n }\n return Math.ceil(tokens);\n}\n","import {\n BYTES_PER_KB,\n BYTES_PER_MB,\n ERROR_WINDOW_CHARS,\n MAX_VARIANTS_TRACKED,\n MIN_SHARED_AFFIX,\n PRAGMA_BYTES_CEILING,\n PRAGMA_LINES_CEILING,\n PRAGMA_PATTERN,\n PRAGMA_TOKENS_CEILING,\n} from '../../constants/bashResult';\nimport type {\n ErrorLineScanner,\n Excerpt,\n LogSummarizer,\n LogSummary,\n ResultBudget,\n SalvagedError,\n ToolResult,\n} from '../../types/bashResult';\nimport type { BashRunResult, CompletedRun } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport type { RunnerSettings } from '../../types/runnerSettings';\nimport { stripAnsi } from '../ansiScrub';\nimport {\n getErrorBudgetRatio,\n getErrorMaxEntries,\n getErrorMaxVariantsJoined,\n getErrorPatterns,\n getHeadRatio,\n getResultMaxBytes,\n getResultMaxLines,\n getResultMaxTokens,\n getSuccessResultMaxBytes,\n getSuccessResultMaxTokens,\n} from '../runnerConfig';\nimport { estimateTokens } from '../tokenEstimate';\n\n/** Variants retained per group while scanning; the rest only add to the count. */\n/** Below this much shared text, joining reads worse than separate lines. */\n/**\n * Severity anywhere in the opening of the line, not only at its start. Real\n * tooling buries it behind a tag or a timestamp: `[widget] build: error TS2322`.\n */\nconst SEVERITY_TOKEN =\n /(?<![\\w\\-/=.])(?:errors?|fatal|panic|exception|traceback|assertion|assert|fail(?:ed|ure|ures)?)\\b/iu;\n/** `0 errors` and `no failures` are the success case wearing the same words. */\nconst NEGATED_SEVERITY = /\\b(?:no|not|without|zero|0)\\s+$/iu;\n\nexport function textResult(text: string, details: Record<string, unknown> = {}): ToolResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function errorResult(message: string): ToolResult {\n return textResult(`Error: ${message}`, { error: message });\n}\n\n/** Human-readable byte count, matching how pi reports output sizes. */\nexport function formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\nfunction formatCount(value: number): string {\n return value.toLocaleString('en-US');\n}\n\nexport function countLines(text: string): number {\n if (text.length === 0) return 0;\n return text.endsWith('\\n') ? text.split('\\n').length - 1 : text.split('\\n').length;\n}\n\n/** Accumulates grouped error lines while a log streams past, without holding it. */\nclass ErrorScan {\n private readonly byKey = new Map<string, { variants: string[]; count: number }>();\n\n constructor(\n private readonly maxEntries = getErrorMaxEntries(),\n private readonly settings?: RunnerSettings,\n ) {}\n\n push(line: string): void {\n if (!isErrorLine(line, this.settings)) return;\n const trimmed = line.trim();\n const key = normalizeErrorLine(line);\n const existing = this.byKey.get(key);\n if (existing) {\n existing.count += 1;\n // Only a line we have not seen verbatim earns a slot; identical repeats\n // are carried by the count alone.\n if (!existing.variants.includes(trimmed) && existing.variants.length < MAX_VARIANTS_TRACKED) {\n existing.variants.push(trimmed);\n }\n return;\n }\n if (this.byKey.size >= this.maxEntries) return;\n this.byKey.set(key, { variants: [trimmed], count: 1 });\n }\n\n entries(): SalvagedError[] {\n return [...this.byKey.values()];\n }\n}\n\nexport function createErrorScanner(maxEntries = getErrorMaxEntries(), settings?: RunnerSettings): ErrorLineScanner {\n return new ErrorScan(maxEntries, settings);\n}\n\n/** Codex-style first-line pragma, for the one command that needs a wider result. */\n\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/**\n * Reads `# @doom: {\"maxResultBytes\": 32768}` from the command's first line.\n *\n * The pragma is a shell comment, so it stays harmless if this parse is ever\n * skipped, and it survives verbatim into the log where a wider result can be\n * explained after the fact.\n */\nexport function parseResultPragma(command: string): ResultBudget {\n const match = PRAGMA_PATTERN.exec(command.split('\\n', 1)[0] ?? '');\n if (!match?.[1]) return {};\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(match[1]);\n } catch {\n return {};\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};\n\n const source = parsed as Record<string, unknown>;\n const maxBytes = positiveInteger(source.maxResultBytes);\n const maxLines = positiveInteger(source.maxResultLines);\n const maxTokens = positiveInteger(source.maxResultTokens);\n return {\n ...(maxBytes === undefined ? {} : { maxBytes: Math.min(maxBytes, PRAGMA_BYTES_CEILING) }),\n ...(maxLines === undefined ? {} : { maxLines: Math.min(maxLines, PRAGMA_LINES_CEILING) }),\n ...(maxTokens === undefined ? {} : { maxTokens: Math.min(maxTokens, PRAGMA_TOKENS_CEILING) }),\n };\n}\n\nfunction isErrorLine(line: string, settings?: RunnerSettings): boolean {\n const opening = line.slice(0, ERROR_WINDOW_CHARS);\n const match = SEVERITY_TOKEN.exec(opening);\n if (match && !NEGATED_SEVERITY.test(opening.slice(0, match.index))) return true;\n // Project patterns extend the built-in matcher; tooling this does not\n // recognise is exactly why they exist, so they are not negation-checked.\n return getErrorPatterns(settings).some((pattern) => matchesCustomPattern(pattern, line));\n}\n\nconst customPatterns = new Map<string, RegExp | undefined>();\n\nfunction matchesCustomPattern(pattern: string, line: string): boolean {\n if (!customPatterns.has(pattern)) {\n try {\n customPatterns.set(pattern, new RegExp(pattern, 'iu'));\n } catch {\n customPatterns.set(pattern, undefined);\n }\n }\n return customPatterns.get(pattern)?.test(line) ?? false;\n}\n\n/** Masks the parts that differ between repeats of the same underlying failure. */\nfunction normalizeErrorLine(line: string): string {\n return line\n .replace(/0x[0-9a-f]+/giu, '0x#')\n .replace(/\\/[^\\s:,)]+/gu, '/#')\n .replace(/\\d+/gu, '#')\n .trim()\n .toLowerCase();\n}\n\n/**\n * Collapses repeats so one failure repeated two hundred times costs one slot.\n * Counting continues past the entry cap so the tallies stay honest.\n */\nexport function collectErrorLines(\n lines: Iterable<string>,\n maxEntries = getErrorMaxEntries(),\n settings?: RunnerSettings,\n): SalvagedError[] {\n const scan = new ErrorScan(maxEntries, settings);\n for (const line of lines) scan.push(line);\n return scan.entries();\n}\n\nfunction commonAffixLengths(values: readonly string[]): { prefix: number; suffix: number } {\n const shortest = values.reduce((min, value) => Math.min(min, value.length), Infinity);\n let prefix = 0;\n while (prefix < shortest && values.every((value) => value[prefix] === values[0]![prefix])) prefix += 1;\n let suffix = 0;\n while (\n suffix < shortest - prefix &&\n values.every((value) => value[value.length - 1 - suffix] === values[0]![values[0]!.length - 1 - suffix])\n ) {\n suffix += 1;\n }\n return { prefix, suffix };\n}\n\n/** Back off to a whitespace boundary so the brackets hold whole tokens. */\nfunction retreatToBoundary(value: string, length: number): number {\n const boundary = value.lastIndexOf(' ', Math.max(0, length - 1));\n return boundary >= 0 ? boundary + 1 : 0;\n}\n\nfunction advanceToBoundary(value: string, length: number): number {\n const cut = value.length - length;\n const boundary = value.indexOf(' ', cut);\n return boundary >= 0 ? value.length - boundary : 0;\n}\n\n/**\n * One line per distinct failure would be ideal, but repeats of one failure\n * across many files are the common case. Sharing the fixed text and joining\n * only the parts that differ keeps every file name without paying for the\n * message N times.\n */\nfunction renderGroup(group: SalvagedError, settings?: RunnerSettings): string {\n const [first] = group.variants;\n if (first === undefined) return '';\n if (group.variants.length === 1) {\n return group.count > 1 ? `${first} (\\u00d7${formatCount(group.count)})` : first;\n }\n\n const shown = group.variants.slice(0, getErrorMaxVariantsJoined(settings));\n const { prefix, suffix } = commonAffixLengths(shown);\n const head = first.slice(0, retreatToBoundary(first, prefix));\n const tailLength = advanceToBoundary(first, suffix);\n const tail = tailLength > 0 ? first.slice(first.length - tailLength) : '';\n if (head.length + tail.length < MIN_SHARED_AFFIX) return shown.join('\\n');\n\n const middles = shown.map((value) => value.slice(head.length, value.length - tail.length));\n const overflow = group.variants.length - shown.length;\n const joined = overflow > 0 ? `${middles.join('|')}|+${formatCount(overflow)}` : middles.join('|');\n return `${head}[${joined}]${tail}`;\n}\n\nfunction renderSalvaged(errors: readonly SalvagedError[], maxBytes: number, settings?: RunnerSettings): string[] {\n const rendered: string[] = [];\n let used = 0;\n for (const group of errors) {\n const text = renderGroup(group, settings);\n if (text.length === 0) continue;\n const cost = Buffer.byteLength(text, 'utf8') + 1;\n if (used + cost > maxBytes) break;\n rendered.push(text);\n used += cost;\n }\n return rendered;\n}\n\ninterface Side {\n readonly text: string;\n readonly count: number;\n}\n\nfunction elisionMarker(elidedLines: number, salvagedCount = 0): string {\n const unit = elidedLines === 1 ? 'line' : 'lines';\n const errors = salvagedCount > 0 ? `, ${formatCount(salvagedCount)} shown below` : '';\n return `\\u2026 [${formatCount(elidedLines)} ${unit} elided${errors}] \\u2026`;\n}\n\nfunction splitTextLines(text: string): { lines: string[]; trailingNewline: boolean } {\n const trailingNewline = text.endsWith('\\n');\n const lines = text.split('\\n');\n if (trailingNewline) lines.pop();\n return { lines, trailingNewline };\n}\n\n/** Whole lines only: a fragment of a line reads as data that was never there. */\nfunction takeSide(\n lines: readonly string[],\n maxCount: number,\n maxBytes: number,\n fromEnd: boolean,\n maxTokens = Number.POSITIVE_INFINITY,\n): Side {\n if (maxBytes <= 0 || maxCount <= 0) return { text: '', count: 0 };\n const ordered = fromEnd ? [...lines].reverse() : [...lines];\n const taken: string[] = [];\n let used = 0;\n let tokens = 0;\n for (const line of ordered) {\n if (taken.length >= maxCount) break;\n const cost = Buffer.byteLength(line, 'utf8') + (taken.length > 0 ? 1 : 0);\n // Dense output costs far more context than its byte count implies, so the\n // token ceiling stops the side even while bytes remain. The separator is\n // charged too: summing per-line estimates otherwise drifts past the ceiling\n // once the newlines between them are counted.\n const lineTokens = estimateTokens(line) + (taken.length > 0 ? 1 : 0);\n if (used + cost > maxBytes || tokens + lineTokens > maxTokens) break;\n taken.push(line);\n used += cost;\n tokens += lineTokens;\n }\n if (taken.length === 0) return { text: '', count: 0 };\n const restored = fromEnd ? taken.reverse() : taken;\n return { text: restored.join('\\n'), count: restored.length };\n}\n\n/**\n * Used only when no whole line fits either side, which means the source has no\n * usable line boundary. A clamped fragment beats returning nothing at all.\n */\nfunction clampedFragment(text: string, limitBytes: number): Side {\n const clamped = utf8Tail(Buffer.from(text, 'utf8'), limitBytes);\n const newline = clamped.indexOf('\\n');\n const whole = newline >= 0 ? clamped.slice(newline + 1) : clamped;\n return { text: whole, count: whole.length > 0 ? 1 : 0 };\n}\n\n/** Charged against the budget so the composed excerpt still honours the ceiling. */\nfunction markerReservation(totalLines: number): number {\n return Buffer.byteLength(elisionMarker(totalLines), 'utf8') + 2;\n}\n\nfunction assembleSides(\n head: Side,\n tail: Side,\n elidedLines: number,\n trailingNewline: boolean,\n limitBytes: number,\n salvaged: readonly string[] = [],\n): Excerpt {\n const opening = elidedLines > 0 ? [elisionMarker(elidedLines, salvaged.length)] : [];\n const closing = elidedLines > 0 && salvaged.length > 0 ? ['\\u2026'] : [];\n const parts = [head.text, ...opening, ...salvaged, ...closing, tail.text].filter((part) => part.length > 0);\n const body = parts.join('\\n');\n const withNewline = trailingNewline && body.length > 0 ? `${body}\\n` : body;\n const encoded = Buffer.from(withNewline, 'utf8');\n // Last resort: the ceiling is a hard promise to the caller.\n const text = encoded.byteLength > limitBytes ? utf8Tail(encoded, limitBytes) : withNewline;\n return { text, lines: head.count + tail.count, truncated: true, elidedLines: Math.max(0, elidedLines) };\n}\n\nfunction budgetFor(\n maxLines: number,\n maxBytes: number,\n totalLines: number,\n maxTokens: number,\n settings?: RunnerSettings,\n) {\n const limitLines = Math.max(1, Math.floor(maxLines));\n const usable = Math.max(0, maxBytes - markerReservation(totalLines));\n // The marker is charged against both ceilings; counting it in bytes alone let\n // the composed excerpt drift past the token ceiling.\n const tokens = Math.max(1, Math.floor(maxTokens) - estimateTokens(elisionMarker(totalLines)));\n return {\n limitLines,\n headLines: Math.max(1, Math.floor(limitLines * getHeadRatio(settings))),\n headBytes: Math.max(0, Math.floor(usable * getHeadRatio(settings))),\n headTokens: Math.max(1, Math.floor(tokens * getHeadRatio(settings))),\n usable,\n tokens,\n };\n}\n\n/**\n * Bounds text to the budget, keeping a leading excerpt as well as the tail.\n *\n * Failures announce themselves at both ends: a command that could not start\n * says so on line one, and a command that died says so on the last. Keeping\n * only the tail loses the first kind entirely.\n */\nexport function boundExcerpt(\n text: string,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n maxTokens = getResultMaxTokens(),\n settings?: RunnerSettings,\n): Excerpt {\n if (text.length === 0) return { text: '', lines: 0, truncated: false, elidedLines: 0 };\n const { lines, trailingNewline } = splitTextLines(text);\n const totalBytes = Buffer.byteLength(text, 'utf8');\n const limitBytes = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : totalBytes;\n const limitLines = Math.max(1, Math.floor(maxLines));\n if (lines.length <= limitLines && totalBytes <= limitBytes && estimateTokens(text) <= maxTokens) {\n return { text, lines: lines.length, truncated: false, elidedLines: 0 };\n }\n\n const budget = budgetFor(maxLines, limitBytes, lines.length, maxTokens, settings);\n const head = takeSide(lines, budget.headLines, budget.headBytes, false, budget.headTokens);\n const rest = lines.slice(head.count);\n // Reserved only when the region beyond the head actually holds errors, so a\n // clean run still spends the whole remaining budget on its tail.\n const errorBytes = rest.some((line) => isErrorLine(line, settings))\n ? Math.floor(budget.usable * getErrorBudgetRatio(settings))\n : 0;\n const tailBytes = budget.usable - Buffer.byteLength(head.text, 'utf8') - errorBytes;\n const tailTokens = budget.tokens - estimateTokens(head.text);\n const tail = takeSide(rest, Math.max(1, budget.limitLines - head.count), tailBytes, true, tailTokens);\n if (head.count === 0 && tail.count === 0) {\n const fragment = clampedFragment(text, limitBytes);\n return { text: fragment.text, lines: fragment.count, truncated: true, elidedLines: lines.length - fragment.count };\n }\n const middle = lines.slice(head.count, lines.length - tail.count);\n const salvaged =\n errorBytes > 0\n ? renderSalvaged(collectErrorLines(middle, getErrorMaxEntries(settings), settings), errorBytes, settings)\n : [];\n return assembleSides(head, tail, lines.length - head.count - tail.count, trailingNewline, limitBytes, salvaged);\n}\n\n/**\n * Same budget as `boundExcerpt`, for callers that streamed the two ends\n * separately and never held the middle. `totalLines` counts the whole source,\n * so the marker states what was dropped rather than what was captured.\n */\nexport function composeExcerpt(\n headSource: string,\n tailSource: string,\n totalLines: number,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n errors: readonly SalvagedError[] = [],\n maxTokens = getResultMaxTokens(),\n settings?: RunnerSettings,\n): Excerpt {\n const limitBytes = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : Number.MAX_SAFE_INTEGER;\n const budget = budgetFor(maxLines, limitBytes, totalLines, maxTokens, settings);\n const headLines = splitTextLines(headSource).lines;\n const { lines: tailLines, trailingNewline } = splitTextLines(tailSource);\n // The line at each cut is a fragment of a line neither capture saw whole.\n const head = takeSide(\n headLines.slice(0, Math.max(0, headLines.length - 1)),\n budget.headLines,\n budget.headBytes,\n false,\n budget.headTokens,\n );\n const errorBytes = errors.length > 0 ? Math.floor(budget.usable * getErrorBudgetRatio(settings)) : 0;\n const tailBytes = budget.usable - Buffer.byteLength(head.text, 'utf8') - errorBytes;\n const tailTokens = budget.tokens - estimateTokens(head.text);\n const tail = takeSide(tailLines.slice(1), Math.max(1, budget.limitLines - head.count), tailBytes, true, tailTokens);\n if (head.count === 0 && tail.count === 0) {\n const fragment = clampedFragment(tailSource, limitBytes);\n return {\n text: fragment.text,\n lines: fragment.count,\n truncated: true,\n elidedLines: Math.max(0, totalLines - fragment.count),\n };\n }\n const salvaged = errorBytes > 0 ? renderSalvaged(errors, errorBytes, settings) : [];\n return assembleSides(\n head,\n tail,\n Math.max(0, totalLines - head.count - tail.count),\n trailingNewline,\n limitBytes,\n salvaged,\n );\n}\n\n/** Applies Doom's line and byte ceilings to text already held in memory. */\nexport function summarizeText(\n text: string,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n): Pick<LogSummary, 'tail' | 'tailLines'> {\n const excerpt = boundExcerpt(text, maxLines, maxBytes);\n return { tail: excerpt.text, tailLines: excerpt.lines };\n}\n\n/** Bounds the complete model-facing result after status and recovery text are attached. */\nexport function boundResultText(\n text: string,\n maxBytes = getResultMaxBytes(),\n settings?: RunnerSettings,\n maxTokens = getResultMaxTokens(),\n): string {\n return boundExcerpt(text, getResultMaxLines(settings), maxBytes, maxTokens, settings).text;\n}\n\n/**\n * Keeps both ends of an oversized result and points at the file holding the rest.\n *\n * The notice states the file's real size and line count so the model does not\n * guess at offsets when it goes on to read the file, which is the failure this\n * whole policy exists to prevent.\n */\n\nfunction utf8Tail(encoded: Buffer<ArrayBufferLike>, maxBytes: number): string {\n let start = Math.max(0, encoded.byteLength - maxBytes);\n while (start < encoded.byteLength && (encoded[start]! & 0xc0) === 0x80) start += 1;\n return encoded.subarray(start).toString('utf8');\n}\n\n/** `name pid 1234 up 3m sleep 60` — one runner per line. */\nexport function formatRunnerLine(record: RunnerRecord, now = Date.now()): string {\n const mode = record.interactive ? ' interactive' : '';\n return `${record.name} pid ${record.pid} up ${formatUptime(record.startedAt, now)}${mode} ${record.command}`;\n}\n\nexport function formatUptime(startedAt: string, now: number): string {\n const started = Date.parse(startedAt);\n if (Number.isNaN(started)) return 'unknown';\n const seconds = Math.max(0, Math.floor((now - started) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;\n}\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold',\n interactive: 'Started interactively',\n};\n\nfunction completionFailed(result: CompletedRun): boolean {\n return result.timedOut === true || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0);\n}\n\nfunction completionStatus(result: CompletedRun): string | undefined {\n if (result.timedOut === true) return 'Timed out: exceeded the requested timeout.';\n if (result.signal !== null) return `Signal: ${result.signal}`;\n if (result.exitCode === null) return 'Exit status unavailable.';\n if (result.exitCode !== 0) return `Exit: ${result.exitCode}`;\n return undefined;\n}\n\nconst emptyLogSummary: LogSummarizer = () => ({ tail: '', bytes: 0, lines: 0, tailLines: 0 });\n\nexport function formatRunResult(\n result: BashRunResult,\n budget: ResultBudget = {},\n summarizeLog: LogSummarizer = emptyLogSummary,\n settings?: RunnerSettings,\n environment: NodeJS.ProcessEnv = process.env,\n): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n 'Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason}: runner \"${result.name}\" (${result.id}).`,\n `Streaming log: ${result.logPath}`,\n `Inspect: doom-runner logs ${result.id}`,\n ].join('\\n');\n return textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n });\n }\n\n const succeeded = !completionFailed(result);\n const maxLines = budget.maxLines ?? getResultMaxLines(settings);\n const maxBytes =\n budget.maxBytes ??\n (succeeded ? getSuccessResultMaxBytes(environment, settings) : getResultMaxBytes(environment, settings));\n const maxTokens =\n budget.maxTokens ??\n (succeeded ? getSuccessResultMaxTokens(environment, settings) : getResultMaxTokens(environment, settings));\n const log = summarizeLog(result.logPath, maxLines, maxBytes, maxTokens);\n const useCapturedOutput = !result.rtkOutput && log.tail.length === 0 && result.output.length > 0;\n let tail: string;\n let tailLines: number;\n let outputLines: number;\n let outputBytes: number;\n let truncated: boolean;\n if (result.rtkOutput) {\n const clipped = Buffer.byteLength(result.rtkOutput.output, 'utf8') < result.rtkOutput.bytes;\n const bounded = clipped\n ? composeExcerpt(\n result.rtkOutput.head,\n result.rtkOutput.output,\n result.rtkOutput.lines,\n maxLines,\n maxBytes,\n [],\n maxTokens,\n settings,\n )\n : boundExcerpt(result.rtkOutput.output, maxLines, maxBytes, maxTokens, settings);\n tail = bounded.text;\n tailLines = bounded.lines;\n outputLines = result.rtkOutput.lines;\n outputBytes = result.rtkOutput.bytes;\n truncated = outputLines > tailLines || outputBytes > Buffer.byteLength(tail, 'utf8');\n } else {\n tail = useCapturedOutput ? result.output : log.tail;\n tailLines = useCapturedOutput ? countLines(tail) : log.tailLines;\n outputLines = Math.max(log.lines, tailLines);\n outputBytes = useCapturedOutput ? Buffer.byteLength(tail, 'utf8') : log.bytes;\n truncated = !useCapturedOutput && (outputLines > tailLines || outputBytes > Buffer.byteLength(tail, 'utf8'));\n }\n const plainTail = stripAnsi(tail).replace(/\\r?\\n$/u, '');\n const failed = completionFailed(result);\n const status = completionStatus(result);\n const textLines: string[] = [];\n if (plainTail.length === 0) {\n textLines.push(status === undefined ? 'Completed with no output.' : 'No output.');\n } else if (truncated) {\n const label = result.rtkOutput ? `RTK ${result.rtkOutput.filter} excerpt` : 'Log excerpt';\n textLines.push(\n `${label} (${tailLines.toLocaleString('en-US')} of ${outputLines.toLocaleString('en-US')} lines):\\n${plainTail}`,\n );\n } else {\n textLines.push(plainTail);\n }\n if (status !== undefined) textLines.push(status);\n if (result.rtkWarning) textLines.push(result.rtkWarning);\n if (truncated) {\n const label = result.rtkOutput ? 'Complete raw log' : 'Full log';\n textLines.push(\n `${label}: ${result.logPath} (${formatSize(log.bytes)}, ${log.lines.toLocaleString('en-US')} lines); inspect with doom-runner logs ${result.id}`,\n );\n } else if (failed && plainTail.length === 0) {\n textLines.push(\n `Log: ${result.logPath}`,\n 'Next: run one read-only diagnostic; retry only after correcting the cause.',\n );\n }\n const text = boundResultText(\n textLines.join('\\n'),\n maxBytes,\n settings,\n settings === undefined ? undefined : maxTokens,\n );\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: useCapturedOutput ? tailLines : log.lines,\n tail,\n tailLines,\n ...(result.rtkOutput\n ? {\n rtkFilter: result.rtkOutput.filter,\n rtkOutputBytes: result.rtkOutput.bytes,\n rtkOutputLines: result.rtkOutput.lines,\n }\n : {}),\n ...(result.rtkWarning ? { rtkWarning: result.rtkWarning } : {}),\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n if (failed) throw new Error(text);\n return textResult(text, details);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bashResult-bIVSzoj-.mjs","names":[],"sources":["../src/constants/bashResult.ts","../src/constants/tokenEstimate.ts","../src/services/tokenEstimate/index.ts","../src/services/bashResult/index.ts"],"sourcesContent":["export const BYTES_PER_KB = 1024;\nexport const BYTES_PER_MB = 1_048_576;\nexport const MAX_VARIANTS_TRACKED = 12;\nexport const MIN_SHARED_AFFIX = 8;\nexport const ERROR_WINDOW_CHARS = 60;\nexport const PRAGMA_PATTERN = /^\\s*(?:#|\\/\\/)\\s*@doom:\\s*(\\{.*\\})\\s*$/u;\nexport const PRAGMA_BYTES_CEILING = 262_144;\nexport const PRAGMA_LINES_CEILING = 5_000;\nexport const PRAGMA_TOKENS_CEILING = 100_000;\n","export const WORD_CHARS_PER_TOKEN = 5;\nexport const MIXED_CHARS_PER_TOKEN = 2.1;\nexport const PUNCTUATION_CHARS_PER_TOKEN = 2;\nexport const DENSE_SCRIPT_START = 0x2fff;\nexport const ASTRAL_TOKEN_COST = 2;\nexport const ALPHANUMERIC_RUN = /([^\\p{L}\\p{N}]+)/u;\nexport const HAS_ALPHANUMERIC = /[\\p{L}\\p{N}]/u;\nexport const HAS_DIGIT = /\\d/u;\nexport const HAS_LETTER = /[A-Za-z]/u;\n","import {\n ALPHANUMERIC_RUN,\n ASTRAL_TOKEN_COST,\n DENSE_SCRIPT_START,\n HAS_ALPHANUMERIC,\n HAS_DIGIT,\n HAS_LETTER,\n MIXED_CHARS_PER_TOKEN,\n PUNCTUATION_CHARS_PER_TOKEN,\n WORD_CHARS_PER_TOKEN,\n} from '../../constants/tokenEstimate';\n/**\n * Estimates BPE tokens without carrying a vocabulary.\n *\n * Bytes are a poor proxy for what a result costs: measured against\n * `gpt-tokenizer`, real text ranges from 1.4 characters per token for base64 to\n * 4.7 for English prose, a spread of more than three. Truncation runs on every\n * command and is synchronous, so loading a real tokenizer here would charge\n * every session for a table most of them never need.\n *\n * The shape below follows how BPE actually behaves: punctuation forces token\n * boundaries, natural words are cheap, and runs mixing letters with digits are\n * expensive. Calibrated against `gpt-tokenizer` over build logs, source, JSON,\n * stack traces, CJK, base64, hex, and UUIDs, the worst error is a factor of\n * 1.46 either way. It stays an estimate, so the byte ceiling remains the hard\n * bound behind it.\n */\n\n/** Characters per token for a natural word. */\n/** Characters per token for a run mixing letters and digits, which tokenizes poorly. */\n/** Characters per token for a run of punctuation or whitespace. */\n/** Dense scripts such as CJK cost about one token per character. */\n\nexport function estimateTokens(text: string): number {\n let tokens = 0;\n for (const run of text.split(ALPHANUMERIC_RUN)) {\n if (run.length === 0) continue;\n if (!HAS_ALPHANUMERIC.test(run)) {\n tokens += run.length / PUNCTUATION_CHARS_PER_TOKEN;\n continue;\n }\n\n let ascii = 0;\n let dense = 0;\n let astral = 0;\n for (const character of run) {\n const code = character.codePointAt(0) ?? 0;\n if (code > 0xffff) astral += 1;\n else if (code > DENSE_SCRIPT_START) dense += 1;\n else ascii += 1;\n }\n tokens += astral * ASTRAL_TOKEN_COST + dense;\n if (ascii > 0) {\n const mixed = HAS_DIGIT.test(run) && HAS_LETTER.test(run);\n tokens += Math.max(1, ascii / (mixed ? MIXED_CHARS_PER_TOKEN : WORD_CHARS_PER_TOKEN));\n }\n }\n return Math.ceil(tokens);\n}\n","import {\n BYTES_PER_KB,\n BYTES_PER_MB,\n ERROR_WINDOW_CHARS,\n MAX_VARIANTS_TRACKED,\n MIN_SHARED_AFFIX,\n PRAGMA_BYTES_CEILING,\n PRAGMA_LINES_CEILING,\n PRAGMA_PATTERN,\n PRAGMA_TOKENS_CEILING,\n} from '../../constants/bashResult';\nimport type {\n ErrorLineScanner,\n Excerpt,\n LogSummarizer,\n LogSummary,\n ResultBudget,\n SalvagedError,\n ToolResult,\n} from '../../types/bashResult';\nimport type { BashRunResult, CompletedRun } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport type { RunnerSettings } from '../../types/runnerSettings';\nimport { stripAnsi } from '../ansiScrub';\nimport {\n getErrorBudgetRatio,\n getErrorMaxEntries,\n getErrorMaxVariantsJoined,\n getErrorPatterns,\n getHeadRatio,\n getResultMaxBytes,\n getResultMaxLines,\n getResultMaxTokens,\n getSuccessResultMaxBytes,\n getSuccessResultMaxTokens,\n} from '../runnerConfig';\nimport { estimateTokens } from '../tokenEstimate';\n\n/** Variants retained per group while scanning; the rest only add to the count. */\n/** Below this much shared text, joining reads worse than separate lines. */\n/**\n * Severity anywhere in the opening of the line, not only at its start. Real\n * tooling buries it behind a tag or a timestamp: `[widget] build: error TS2322`.\n */\nconst SEVERITY_TOKEN =\n /(?<![\\w\\-/=.])(?:errors?|fatal|panic|exception|traceback|assertion|assert|fail(?:ed|ure|ures)?)\\b/iu;\n/** `0 errors` and `no failures` are the success case wearing the same words. */\nconst NEGATED_SEVERITY = /\\b(?:no|not|without|zero|0)\\s+$/iu;\n\nexport function textResult(text: string, details: Record<string, unknown> = {}): ToolResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function errorResult(message: string): ToolResult {\n return textResult(`Error: ${message}`, { error: message });\n}\n\n/** Human-readable byte count, matching how pi reports output sizes. */\nexport function formatSize(bytes: number): string {\n if (bytes < BYTES_PER_KB) return `${bytes} B`;\n if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;\n return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;\n}\n\nfunction formatCount(value: number): string {\n return value.toLocaleString('en-US');\n}\n\nexport function countLines(text: string): number {\n if (text.length === 0) return 0;\n return text.endsWith('\\n') ? text.split('\\n').length - 1 : text.split('\\n').length;\n}\n\n/** Accumulates grouped error lines while a log streams past, without holding it. */\nclass ErrorScan {\n private readonly byKey = new Map<string, { variants: string[]; count: number }>();\n\n constructor(\n private readonly maxEntries = getErrorMaxEntries(),\n private readonly settings?: RunnerSettings,\n ) {}\n\n push(line: string): void {\n if (!isErrorLine(line, this.settings)) return;\n const trimmed = line.trim();\n const key = normalizeErrorLine(line);\n const existing = this.byKey.get(key);\n if (existing) {\n existing.count += 1;\n // Only a line we have not seen verbatim earns a slot; identical repeats\n // are carried by the count alone.\n if (!existing.variants.includes(trimmed) && existing.variants.length < MAX_VARIANTS_TRACKED) {\n existing.variants.push(trimmed);\n }\n return;\n }\n if (this.byKey.size >= this.maxEntries) return;\n this.byKey.set(key, { variants: [trimmed], count: 1 });\n }\n\n entries(): SalvagedError[] {\n return [...this.byKey.values()];\n }\n}\n\nexport function createErrorScanner(maxEntries = getErrorMaxEntries(), settings?: RunnerSettings): ErrorLineScanner {\n return new ErrorScan(maxEntries, settings);\n}\n\n/** Codex-style first-line pragma, for the one command that needs a wider result. */\n\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/**\n * Reads `# @doom: {\"maxResultBytes\": 32768}` from the command's first line.\n *\n * The pragma is a shell comment, so it stays harmless if this parse is ever\n * skipped, and it survives verbatim into the log where a wider result can be\n * explained after the fact.\n */\nexport function parseResultPragma(command: string): ResultBudget {\n const match = PRAGMA_PATTERN.exec(command.split('\\n', 1)[0] ?? '');\n if (!match?.[1]) return {};\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(match[1]);\n } catch {\n return {};\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};\n\n const source = parsed as Record<string, unknown>;\n const maxBytes = positiveInteger(source.maxResultBytes);\n const maxLines = positiveInteger(source.maxResultLines);\n const maxTokens = positiveInteger(source.maxResultTokens);\n return {\n ...(maxBytes === undefined ? {} : { maxBytes: Math.min(maxBytes, PRAGMA_BYTES_CEILING) }),\n ...(maxLines === undefined ? {} : { maxLines: Math.min(maxLines, PRAGMA_LINES_CEILING) }),\n ...(maxTokens === undefined ? {} : { maxTokens: Math.min(maxTokens, PRAGMA_TOKENS_CEILING) }),\n };\n}\n\nfunction isErrorLine(line: string, settings?: RunnerSettings): boolean {\n const opening = line.slice(0, ERROR_WINDOW_CHARS);\n const match = SEVERITY_TOKEN.exec(opening);\n if (match && !NEGATED_SEVERITY.test(opening.slice(0, match.index))) return true;\n // Project patterns extend the built-in matcher; tooling this does not\n // recognise is exactly why they exist, so they are not negation-checked.\n return getErrorPatterns(settings).some((pattern) => matchesCustomPattern(pattern, line));\n}\n\nconst customPatterns = new Map<string, RegExp | undefined>();\n\nfunction matchesCustomPattern(pattern: string, line: string): boolean {\n if (!customPatterns.has(pattern)) {\n try {\n customPatterns.set(pattern, new RegExp(pattern, 'iu'));\n } catch {\n customPatterns.set(pattern, undefined);\n }\n }\n return customPatterns.get(pattern)?.test(line) ?? false;\n}\n\n/** Masks the parts that differ between repeats of the same underlying failure. */\nfunction normalizeErrorLine(line: string): string {\n return line\n .replace(/0x[0-9a-f]+/giu, '0x#')\n .replace(/\\/[^\\s:,)]+/gu, '/#')\n .replace(/\\d+/gu, '#')\n .trim()\n .toLowerCase();\n}\n\n/**\n * Collapses repeats so one failure repeated two hundred times costs one slot.\n * Counting continues past the entry cap so the tallies stay honest.\n */\nexport function collectErrorLines(\n lines: Iterable<string>,\n maxEntries = getErrorMaxEntries(),\n settings?: RunnerSettings,\n): SalvagedError[] {\n const scan = new ErrorScan(maxEntries, settings);\n for (const line of lines) scan.push(line);\n return scan.entries();\n}\n\nfunction commonAffixLengths(values: readonly string[]): { prefix: number; suffix: number } {\n const shortest = values.reduce((min, value) => Math.min(min, value.length), Infinity);\n let prefix = 0;\n while (prefix < shortest && values.every((value) => value[prefix] === values[0]![prefix])) prefix += 1;\n let suffix = 0;\n while (\n suffix < shortest - prefix &&\n values.every((value) => value[value.length - 1 - suffix] === values[0]![values[0]!.length - 1 - suffix])\n ) {\n suffix += 1;\n }\n return { prefix, suffix };\n}\n\n/** Back off to a whitespace boundary so the brackets hold whole tokens. */\nfunction retreatToBoundary(value: string, length: number): number {\n const boundary = value.lastIndexOf(' ', Math.max(0, length - 1));\n return boundary >= 0 ? boundary + 1 : 0;\n}\n\nfunction advanceToBoundary(value: string, length: number): number {\n const cut = value.length - length;\n const boundary = value.indexOf(' ', cut);\n return boundary >= 0 ? value.length - boundary : 0;\n}\n\n/**\n * One line per distinct failure would be ideal, but repeats of one failure\n * across many files are the common case. Sharing the fixed text and joining\n * only the parts that differ keeps every file name without paying for the\n * message N times.\n */\nfunction renderGroup(group: SalvagedError, settings?: RunnerSettings): string {\n const [first] = group.variants;\n if (first === undefined) return '';\n if (group.variants.length === 1) {\n return group.count > 1 ? `${first} (\\u00d7${formatCount(group.count)})` : first;\n }\n\n const shown = group.variants.slice(0, getErrorMaxVariantsJoined(settings));\n const { prefix, suffix } = commonAffixLengths(shown);\n const head = first.slice(0, retreatToBoundary(first, prefix));\n const tailLength = advanceToBoundary(first, suffix);\n const tail = tailLength > 0 ? first.slice(first.length - tailLength) : '';\n if (head.length + tail.length < MIN_SHARED_AFFIX) return shown.join('\\n');\n\n const middles = shown.map((value) => value.slice(head.length, value.length - tail.length));\n const overflow = group.variants.length - shown.length;\n const joined = overflow > 0 ? `${middles.join('|')}|+${formatCount(overflow)}` : middles.join('|');\n return `${head}[${joined}]${tail}`;\n}\n\nfunction renderSalvaged(errors: readonly SalvagedError[], maxBytes: number, settings?: RunnerSettings): string[] {\n const rendered: string[] = [];\n let used = 0;\n for (const group of errors) {\n const text = renderGroup(group, settings);\n if (text.length === 0) continue;\n const cost = Buffer.byteLength(text, 'utf8') + 1;\n if (used + cost > maxBytes) break;\n rendered.push(text);\n used += cost;\n }\n return rendered;\n}\n\ninterface Side {\n readonly text: string;\n readonly count: number;\n}\n\nfunction elisionMarker(elidedLines: number, salvagedCount = 0): string {\n const unit = elidedLines === 1 ? 'line' : 'lines';\n const errors = salvagedCount > 0 ? `, ${formatCount(salvagedCount)} shown below` : '';\n return `\\u2026 [${formatCount(elidedLines)} ${unit} elided${errors}] \\u2026`;\n}\n\nfunction splitTextLines(text: string): { lines: string[]; trailingNewline: boolean } {\n const trailingNewline = text.endsWith('\\n');\n const lines = text.split('\\n');\n if (trailingNewline) lines.pop();\n return { lines, trailingNewline };\n}\n\n/** Whole lines only: a fragment of a line reads as data that was never there. */\nfunction takeSide(\n lines: readonly string[],\n maxCount: number,\n maxBytes: number,\n fromEnd: boolean,\n maxTokens = Number.POSITIVE_INFINITY,\n): Side {\n if (maxBytes <= 0 || maxCount <= 0) return { text: '', count: 0 };\n const ordered = fromEnd ? [...lines].reverse() : [...lines];\n const taken: string[] = [];\n let used = 0;\n let tokens = 0;\n for (const line of ordered) {\n if (taken.length >= maxCount) break;\n const cost = Buffer.byteLength(line, 'utf8') + (taken.length > 0 ? 1 : 0);\n // Dense output costs far more context than its byte count implies, so the\n // token ceiling stops the side even while bytes remain. The separator is\n // charged too: summing per-line estimates otherwise drifts past the ceiling\n // once the newlines between them are counted.\n const lineTokens = estimateTokens(line) + (taken.length > 0 ? 1 : 0);\n if (used + cost > maxBytes || tokens + lineTokens > maxTokens) break;\n taken.push(line);\n used += cost;\n tokens += lineTokens;\n }\n if (taken.length === 0) return { text: '', count: 0 };\n const restored = fromEnd ? taken.reverse() : taken;\n return { text: restored.join('\\n'), count: restored.length };\n}\n\n/**\n * Used only when no whole line fits either side, which means the source has no\n * usable line boundary. A clamped fragment beats returning nothing at all.\n */\nfunction clampedFragment(text: string, limitBytes: number): Side {\n const clamped = utf8Tail(Buffer.from(text, 'utf8'), limitBytes);\n const newline = clamped.indexOf('\\n');\n const whole = newline >= 0 ? clamped.slice(newline + 1) : clamped;\n return { text: whole, count: whole.length > 0 ? 1 : 0 };\n}\n\n/** Charged against the budget so the composed excerpt still honours the ceiling. */\nfunction markerReservation(totalLines: number): number {\n return Buffer.byteLength(elisionMarker(totalLines), 'utf8') + 2;\n}\n\nfunction assembleSides(\n head: Side,\n tail: Side,\n elidedLines: number,\n trailingNewline: boolean,\n limitBytes: number,\n salvaged: readonly string[] = [],\n): Excerpt {\n const opening = elidedLines > 0 ? [elisionMarker(elidedLines, salvaged.length)] : [];\n const closing = elidedLines > 0 && salvaged.length > 0 ? ['\\u2026'] : [];\n const parts = [head.text, ...opening, ...salvaged, ...closing, tail.text].filter((part) => part.length > 0);\n const body = parts.join('\\n');\n const withNewline = trailingNewline && body.length > 0 ? `${body}\\n` : body;\n const encoded = Buffer.from(withNewline, 'utf8');\n // Last resort: the ceiling is a hard promise to the caller.\n const text = encoded.byteLength > limitBytes ? utf8Tail(encoded, limitBytes) : withNewline;\n return { text, lines: head.count + tail.count, truncated: true, elidedLines: Math.max(0, elidedLines) };\n}\n\nfunction budgetFor(\n maxLines: number,\n maxBytes: number,\n totalLines: number,\n maxTokens: number,\n settings?: RunnerSettings,\n) {\n const limitLines = Math.max(1, Math.floor(maxLines));\n const usable = Math.max(0, maxBytes - markerReservation(totalLines));\n // The marker is charged against both ceilings; counting it in bytes alone let\n // the composed excerpt drift past the token ceiling.\n const tokens = Math.max(1, Math.floor(maxTokens) - estimateTokens(elisionMarker(totalLines)));\n return {\n limitLines,\n headLines: Math.max(1, Math.floor(limitLines * getHeadRatio(settings))),\n headBytes: Math.max(0, Math.floor(usable * getHeadRatio(settings))),\n headTokens: Math.max(1, Math.floor(tokens * getHeadRatio(settings))),\n usable,\n tokens,\n };\n}\n\n/**\n * Bounds text to the budget, keeping a leading excerpt as well as the tail.\n *\n * Failures announce themselves at both ends: a command that could not start\n * says so on line one, and a command that died says so on the last. Keeping\n * only the tail loses the first kind entirely.\n */\nexport function boundExcerpt(\n text: string,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n maxTokens = getResultMaxTokens(),\n settings?: RunnerSettings,\n): Excerpt {\n if (text.length === 0) return { text: '', lines: 0, truncated: false, elidedLines: 0 };\n const { lines, trailingNewline } = splitTextLines(text);\n const totalBytes = Buffer.byteLength(text, 'utf8');\n const limitBytes = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : totalBytes;\n const limitLines = Math.max(1, Math.floor(maxLines));\n if (lines.length <= limitLines && totalBytes <= limitBytes && estimateTokens(text) <= maxTokens) {\n return { text, lines: lines.length, truncated: false, elidedLines: 0 };\n }\n\n const budget = budgetFor(maxLines, limitBytes, lines.length, maxTokens, settings);\n const head = takeSide(lines, budget.headLines, budget.headBytes, false, budget.headTokens);\n const rest = lines.slice(head.count);\n // Reserved only when the region beyond the head actually holds errors, so a\n // clean run still spends the whole remaining budget on its tail.\n const errorBytes = rest.some((line) => isErrorLine(line, settings))\n ? Math.floor(budget.usable * getErrorBudgetRatio(settings))\n : 0;\n const tailBytes = budget.usable - Buffer.byteLength(head.text, 'utf8') - errorBytes;\n const tailTokens = budget.tokens - estimateTokens(head.text);\n const tail = takeSide(rest, Math.max(1, budget.limitLines - head.count), tailBytes, true, tailTokens);\n if (head.count === 0 && tail.count === 0) {\n const fragment = clampedFragment(text, limitBytes);\n return { text: fragment.text, lines: fragment.count, truncated: true, elidedLines: lines.length - fragment.count };\n }\n const middle = lines.slice(head.count, lines.length - tail.count);\n const salvaged =\n errorBytes > 0\n ? renderSalvaged(collectErrorLines(middle, getErrorMaxEntries(settings), settings), errorBytes, settings)\n : [];\n return assembleSides(head, tail, lines.length - head.count - tail.count, trailingNewline, limitBytes, salvaged);\n}\n\n/**\n * Same budget as `boundExcerpt`, for callers that streamed the two ends\n * separately and never held the middle. `totalLines` counts the whole source,\n * so the marker states what was dropped rather than what was captured.\n */\nexport function composeExcerpt(\n headSource: string,\n tailSource: string,\n totalLines: number,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n errors: readonly SalvagedError[] = [],\n maxTokens = getResultMaxTokens(),\n settings?: RunnerSettings,\n): Excerpt {\n const limitBytes = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : Number.MAX_SAFE_INTEGER;\n const budget = budgetFor(maxLines, limitBytes, totalLines, maxTokens, settings);\n const headLines = splitTextLines(headSource).lines;\n const { lines: tailLines, trailingNewline } = splitTextLines(tailSource);\n // The line at each cut is a fragment of a line neither capture saw whole.\n const head = takeSide(\n headLines.slice(0, Math.max(0, headLines.length - 1)),\n budget.headLines,\n budget.headBytes,\n false,\n budget.headTokens,\n );\n const errorBytes = errors.length > 0 ? Math.floor(budget.usable * getErrorBudgetRatio(settings)) : 0;\n const tailBytes = budget.usable - Buffer.byteLength(head.text, 'utf8') - errorBytes;\n const tailTokens = budget.tokens - estimateTokens(head.text);\n const tail = takeSide(tailLines.slice(1), Math.max(1, budget.limitLines - head.count), tailBytes, true, tailTokens);\n if (head.count === 0 && tail.count === 0) {\n const fragment = clampedFragment(tailSource, limitBytes);\n return {\n text: fragment.text,\n lines: fragment.count,\n truncated: true,\n elidedLines: Math.max(0, totalLines - fragment.count),\n };\n }\n const salvaged = errorBytes > 0 ? renderSalvaged(errors, errorBytes, settings) : [];\n return assembleSides(\n head,\n tail,\n Math.max(0, totalLines - head.count - tail.count),\n trailingNewline,\n limitBytes,\n salvaged,\n );\n}\n\n/** Applies Doom's line and byte ceilings to text already held in memory. */\nexport function summarizeText(\n text: string,\n maxLines = getResultMaxLines(),\n maxBytes = getResultMaxBytes(),\n): Pick<LogSummary, 'tail' | 'tailLines'> {\n const excerpt = boundExcerpt(text, maxLines, maxBytes);\n return { tail: excerpt.text, tailLines: excerpt.lines };\n}\n\n/** Bounds the complete model-facing result after status and recovery text are attached. */\nexport function boundResultText(\n text: string,\n maxBytes = getResultMaxBytes(),\n settings?: RunnerSettings,\n maxTokens = getResultMaxTokens(),\n): string {\n return boundExcerpt(text, getResultMaxLines(settings), maxBytes, maxTokens, settings).text;\n}\n\n/**\n * Keeps both ends of an oversized result and points at the file holding the rest.\n *\n * The notice states the file's real size and line count so the model does not\n * guess at offsets when it goes on to read the file, which is the failure this\n * whole policy exists to prevent.\n */\n\nfunction utf8Tail(encoded: Buffer<ArrayBufferLike>, maxBytes: number): string {\n let start = Math.max(0, encoded.byteLength - maxBytes);\n while (start < encoded.byteLength && (encoded[start]! & 0xc0) === 0x80) start += 1;\n return encoded.subarray(start).toString('utf8');\n}\n\n/** `name pid 1234 up 3m sleep 60` — one runner per line. */\nexport function formatRunnerLine(record: RunnerRecord, now = Date.now()): string {\n const mode = record.interactive ? ' interactive' : '';\n return `${record.name} pid ${record.pid} up ${formatUptime(record.startedAt, now)}${mode} ${record.command}`;\n}\n\nexport function formatUptime(startedAt: string, now: number): string {\n const started = Date.parse(startedAt);\n if (Number.isNaN(started)) return 'unknown';\n const seconds = Math.max(0, Math.floor((now - started) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.floor(minutes / 60);\n return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;\n}\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold',\n interactive: 'Started interactively',\n};\n\nfunction completionFailed(result: CompletedRun): boolean {\n return result.timedOut === true || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0);\n}\n\nfunction completionStatus(result: CompletedRun): string | undefined {\n if (result.timedOut === true) return 'Timed out: exceeded the requested timeout.';\n if (result.signal !== null) return `Signal: ${result.signal}`;\n if (result.exitCode === null) return 'Exit status unavailable.';\n if (result.exitCode !== 0) return `Exit: ${result.exitCode}`;\n return undefined;\n}\n\nconst emptyLogSummary: LogSummarizer = () => ({ tail: '', bytes: 0, lines: 0, tailLines: 0 });\n\nexport function formatRunResult(\n result: BashRunResult,\n budget: ResultBudget = {},\n summarizeLog: LogSummarizer = emptyLogSummary,\n settings?: RunnerSettings,\n environment: NodeJS.ProcessEnv = process.env,\n): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n 'Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason}: runner \"${result.name}\" (${result.id}).`,\n `Streaming log: ${result.logPath}`,\n `Inspect: doom-runner logs ${result.id}`,\n ].join('\\n');\n return textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n });\n }\n\n const succeeded = !completionFailed(result);\n const maxLines = budget.maxLines ?? getResultMaxLines(settings);\n const maxBytes =\n budget.maxBytes ??\n (succeeded ? getSuccessResultMaxBytes(environment, settings) : getResultMaxBytes(environment, settings));\n const maxTokens =\n budget.maxTokens ??\n (succeeded ? getSuccessResultMaxTokens(environment, settings) : getResultMaxTokens(environment, settings));\n const log = summarizeLog(result.logPath, maxLines, maxBytes, maxTokens);\n const useCapturedOutput = !result.rtkOutput && log.tail.length === 0 && result.output.length > 0;\n let tail: string;\n let tailLines: number;\n let outputLines: number;\n let outputBytes: number;\n let truncated: boolean;\n if (result.rtkOutput) {\n const clipped = Buffer.byteLength(result.rtkOutput.output, 'utf8') < result.rtkOutput.bytes;\n const bounded = clipped\n ? composeExcerpt(\n result.rtkOutput.head,\n result.rtkOutput.output,\n result.rtkOutput.lines,\n maxLines,\n maxBytes,\n [],\n maxTokens,\n settings,\n )\n : boundExcerpt(result.rtkOutput.output, maxLines, maxBytes, maxTokens, settings);\n tail = bounded.text;\n tailLines = bounded.lines;\n outputLines = result.rtkOutput.lines;\n outputBytes = result.rtkOutput.bytes;\n truncated = outputLines > tailLines || outputBytes > Buffer.byteLength(tail, 'utf8');\n } else {\n tail = useCapturedOutput ? result.output : log.tail;\n tailLines = useCapturedOutput ? countLines(tail) : log.tailLines;\n outputLines = Math.max(log.lines, tailLines);\n outputBytes = useCapturedOutput ? Buffer.byteLength(tail, 'utf8') : log.bytes;\n truncated = !useCapturedOutput && (outputLines > tailLines || outputBytes > Buffer.byteLength(tail, 'utf8'));\n }\n const plainTail = stripAnsi(tail).replace(/\\r?\\n$/u, '');\n const failed = completionFailed(result);\n const status = completionStatus(result);\n const textLines: string[] = [];\n if (plainTail.length === 0) {\n textLines.push(status === undefined ? 'Completed with no output.' : 'No output.');\n } else if (truncated) {\n const label = result.rtkOutput ? `RTK ${result.rtkOutput.filter} excerpt` : 'Log excerpt';\n textLines.push(\n `${label} (${tailLines.toLocaleString('en-US')} of ${outputLines.toLocaleString('en-US')} lines):\\n${plainTail}`,\n );\n } else {\n textLines.push(plainTail);\n }\n if (status !== undefined) textLines.push(status);\n if (result.rtkWarning) textLines.push(result.rtkWarning);\n if (truncated) {\n const label = result.rtkOutput ? 'Complete raw log' : 'Full log';\n textLines.push(\n `${label}: ${result.logPath} (${formatSize(log.bytes)}, ${log.lines.toLocaleString('en-US')} lines); inspect with doom-runner logs ${result.id}`,\n );\n } else if (failed && plainTail.length === 0) {\n textLines.push(\n `Log: ${result.logPath}`,\n 'Next: run one read-only diagnostic; retry only after correcting the cause.',\n );\n }\n const text = boundResultText(\n textLines.join('\\n'),\n maxBytes,\n settings,\n settings === undefined ? undefined : maxTokens,\n );\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: useCapturedOutput ? tailLines : log.lines,\n tail,\n tailLines,\n ...(result.rtkOutput\n ? {\n rtkFilter: result.rtkOutput.filter,\n rtkOutputBytes: result.rtkOutput.bytes,\n rtkOutputLines: result.rtkOutput.lines,\n }\n : {}),\n ...(result.rtkWarning ? { rtkWarning: result.rtkWarning } : {}),\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n if (failed) throw new Error(text);\n return textResult(text, details);\n}\n"],"mappings":";;;AAAA,MAAa,eAAe;AAC5B,MAAa,eAAe;AAI5B,MAAa,iBAAiB;AAC9B,MAAa,uBAAuB;AACpC,MAAa,uBAAuB;AACpC,MAAa,wBAAwB;;;ACPrC,MAAa,wBAAwB;AAIrC,MAAa,mBAAmB;AAChC,MAAa,mBAAmB;AAChC,MAAa,YAAY;AACzB,MAAa,aAAa;;;;;;;;;;;;;;;;;;;;;;;ACyB1B,SAAgB,eAAe,MAAsB;CACnD,IAAI,SAAS;CACb,KAAK,MAAM,OAAO,KAAK,MAAM,gBAAgB,GAAG;EAC9C,IAAI,IAAI,WAAW,GAAG;EACtB,IAAI,CAAC,iBAAiB,KAAK,GAAG,GAAG;GAC/B,UAAU,IAAI,SAAA;GACd;EACF;EAEA,IAAI,QAAQ;EACZ,IAAI,QAAQ;EACZ,IAAI,SAAS;EACb,KAAK,MAAM,aAAa,KAAK;GAC3B,MAAM,OAAO,UAAU,YAAY,CAAC,KAAK;GACzC,IAAI,OAAO,OAAQ,UAAU;QACxB,IAAI,OAAA,OAA2B,SAAS;QACxC,SAAS;EAChB;EACA,UAAU,SAAA,IAA6B;EACvC,IAAI,QAAQ,GAAG;GACb,MAAM,QAAQ,UAAU,KAAK,GAAG,KAAK,WAAW,KAAK,GAAG;GACxD,UAAU,KAAK,IAAI,GAAG,SAAS,QAAQ,wBAAA,EAA6C;EACtF;CACF;CACA,OAAO,KAAK,KAAK,MAAM;AACzB;;;;;;;;;ACdA,MAAM,iBACJ;;AAEF,MAAM,mBAAmB;AAEzB,SAAgB,WAAW,MAAc,UAAmC,CAAC,GAAe;CAC1F,OAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;EAAK,CAAC;EAAG;CAAQ;AACtD;AAEA,SAAgB,YAAY,SAA6B;CACvD,OAAO,WAAW,UAAU,WAAW,EAAE,OAAO,QAAQ,CAAC;AAC3D;;AAGA,SAAgB,WAAW,OAAuB;CAChD,IAAI,QAAA,MAAsB,OAAO,GAAG,MAAM;CAC1C,IAAI,QAAA,SAAsB,OAAO,IAAI,QAAQ,aAAA,CAAc,QAAQ,CAAC,EAAE;CACtE,OAAO,IAAI,QAAQ,aAAA,CAAc,QAAQ,CAAC,EAAE;AAC9C;AAEA,SAAS,YAAY,OAAuB;CAC1C,OAAO,MAAM,eAAe,OAAO;AACrC;AAEA,SAAgB,WAAW,MAAsB;CAC/C,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC;AAC9E;;AAGA,IAAM,YAAN,MAAgB;CAIK;CACA;CAJnB,wBAAyB,IAAI,IAAmD;CAEhF,YACE,aAA8B,mBAAmB,GACjD,UACA;EAFiB,KAAA,aAAA;EACA,KAAA,WAAA;CAChB;CAEH,KAAK,MAAoB;EACvB,IAAI,CAAC,YAAY,MAAM,KAAK,QAAQ,GAAG;EACvC,MAAM,UAAU,KAAK,KAAK;EAC1B,MAAM,MAAM,mBAAmB,IAAI;EACnC,MAAM,WAAW,KAAK,MAAM,IAAI,GAAG;EACnC,IAAI,UAAU;GACZ,SAAS,SAAS;GAGlB,IAAI,CAAC,SAAS,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,SAAA,IAC5D,SAAS,SAAS,KAAK,OAAO;GAEhC;EACF;EACA,IAAI,KAAK,MAAM,QAAQ,KAAK,YAAY;EACxC,KAAK,MAAM,IAAI,KAAK;GAAE,UAAU,CAAC,OAAO;GAAG,OAAO;EAAE,CAAC;CACvD;CAEA,UAA2B;EACzB,OAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;CAChC;AACF;AAEA,SAAgB,mBAAmB,aAAa,mBAAmB,GAAG,UAA6C;CACjH,OAAO,IAAI,UAAU,YAAY,QAAQ;AAC3C;;AAIA,SAAS,gBAAgB,OAAoC;CAC3D,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,IAAI,QAAQ,KAAA;AACrF;;;;;;;;AASA,SAAgB,kBAAkB,SAA+B;CAC/D,MAAM,QAAQ,eAAe,KAAK,QAAQ,MAAM,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE;CACjE,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC;CAEzB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM,EAAE;CAC9B,QAAQ;EACN,OAAO,CAAC;CACV;CACA,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;CAEpF,MAAM,SAAS;CACf,MAAM,WAAW,gBAAgB,OAAO,cAAc;CACtD,MAAM,WAAW,gBAAgB,OAAO,cAAc;CACtD,MAAM,YAAY,gBAAgB,OAAO,eAAe;CACxD,OAAO;EACL,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,UAAU,oBAAoB,EAAE;EACvF,GAAI,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,UAAU,oBAAoB,EAAE;EACvF,GAAI,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,WAAW,qBAAqB,EAAE;CAC7F;AACF;AAEA,SAAS,YAAY,MAAc,UAAoC;CACrE,MAAM,UAAU,KAAK,MAAM,GAAA,EAAqB;CAChD,MAAM,QAAQ,eAAe,KAAK,OAAO;CACzC,IAAI,SAAS,CAAC,iBAAiB,KAAK,QAAQ,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO;CAG3E,OAAO,iBAAiB,QAAQ,CAAC,CAAC,MAAM,YAAY,qBAAqB,SAAS,IAAI,CAAC;AACzF;AAEA,MAAM,iCAAiB,IAAI,IAAgC;AAE3D,SAAS,qBAAqB,SAAiB,MAAuB;CACpE,IAAI,CAAC,eAAe,IAAI,OAAO,GAC7B,IAAI;EACF,eAAe,IAAI,SAAS,IAAI,OAAO,SAAS,IAAI,CAAC;CACvD,QAAQ;EACN,eAAe,IAAI,SAAS,KAAA,CAAS;CACvC;CAEF,OAAO,eAAe,IAAI,OAAO,CAAC,EAAE,KAAK,IAAI,KAAK;AACpD;;AAGA,SAAS,mBAAmB,MAAsB;CAChD,OAAO,KACJ,QAAQ,kBAAkB,KAAK,CAAC,CAChC,QAAQ,iBAAiB,IAAI,CAAC,CAC9B,QAAQ,SAAS,GAAG,CAAC,CACrB,KAAK,CAAC,CACN,YAAY;AACjB;;;;;AAMA,SAAgB,kBACd,OACA,aAAa,mBAAmB,GAChC,UACiB;CACjB,MAAM,OAAO,IAAI,UAAU,YAAY,QAAQ;CAC/C,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,IAAI;CACxC,OAAO,KAAK,QAAQ;AACtB;AAEA,SAAS,mBAAmB,QAA+D;CACzF,MAAM,WAAW,OAAO,QAAQ,KAAK,UAAU,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,QAAQ;CACpF,IAAI,SAAS;CACb,OAAO,SAAS,YAAY,OAAO,OAAO,UAAU,MAAM,YAAY,OAAO,EAAE,CAAE,OAAO,GAAG,UAAU;CACrG,IAAI,SAAS;CACb,OACE,SAAS,WAAW,UACpB,OAAO,OAAO,UAAU,MAAM,MAAM,SAAS,IAAI,YAAY,OAAO,EAAE,CAAE,OAAO,EAAE,CAAE,SAAS,IAAI,OAAO,GAEvG,UAAU;CAEZ,OAAO;EAAE;EAAQ;CAAO;AAC1B;;AAGA,SAAS,kBAAkB,OAAe,QAAwB;CAChE,MAAM,WAAW,MAAM,YAAY,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC,CAAC;CAC/D,OAAO,YAAY,IAAI,WAAW,IAAI;AACxC;AAEA,SAAS,kBAAkB,OAAe,QAAwB;CAChE,MAAM,MAAM,MAAM,SAAS;CAC3B,MAAM,WAAW,MAAM,QAAQ,KAAK,GAAG;CACvC,OAAO,YAAY,IAAI,MAAM,SAAS,WAAW;AACnD;;;;;;;AAQA,SAAS,YAAY,OAAsB,UAAmC;CAC5E,MAAM,CAAC,SAAS,MAAM;CACtB,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,MAAM,SAAS,WAAW,GAC5B,OAAO,MAAM,QAAQ,IAAI,GAAG,MAAM,UAAU,YAAY,MAAM,KAAK,EAAE,KAAK;CAG5E,MAAM,QAAQ,MAAM,SAAS,MAAM,GAAG,0BAA0B,QAAQ,CAAC;CACzE,MAAM,EAAE,QAAQ,WAAW,mBAAmB,KAAK;CACnD,MAAM,OAAO,MAAM,MAAM,GAAG,kBAAkB,OAAO,MAAM,CAAC;CAC5D,MAAM,aAAa,kBAAkB,OAAO,MAAM;CAClD,MAAM,OAAO,aAAa,IAAI,MAAM,MAAM,MAAM,SAAS,UAAU,IAAI;CACvE,IAAI,KAAK,SAAS,KAAK,SAAA,GAA2B,OAAO,MAAM,KAAK,IAAI;CAExE,MAAM,UAAU,MAAM,KAAK,UAAU,MAAM,MAAM,KAAK,QAAQ,MAAM,SAAS,KAAK,MAAM,CAAC;CACzF,MAAM,WAAW,MAAM,SAAS,SAAS,MAAM;CAE/C,OAAO,GAAG,KAAK,GADA,WAAW,IAAI,GAAG,QAAQ,KAAK,GAAG,EAAE,IAAI,YAAY,QAAQ,MAAM,QAAQ,KAAK,GAAG,EACxE,GAAG;AAC9B;AAEA,SAAS,eAAe,QAAkC,UAAkB,UAAqC;CAC/G,MAAM,WAAqB,CAAC;CAC5B,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,YAAY,OAAO,QAAQ;EACxC,IAAI,KAAK,WAAW,GAAG;EACvB,MAAM,OAAO,OAAO,WAAW,MAAM,MAAM,IAAI;EAC/C,IAAI,OAAO,OAAO,UAAU;EAC5B,SAAS,KAAK,IAAI;EAClB,QAAQ;CACV;CACA,OAAO;AACT;AAOA,SAAS,cAAc,aAAqB,gBAAgB,GAAW;CACrE,MAAM,OAAO,gBAAgB,IAAI,SAAS;CAC1C,MAAM,SAAS,gBAAgB,IAAI,KAAK,YAAY,aAAa,EAAE,gBAAgB;CACnF,OAAO,WAAW,YAAY,WAAW,EAAE,GAAG,KAAK,SAAS,OAAO;AACrE;AAEA,SAAS,eAAe,MAA6D;CACnF,MAAM,kBAAkB,KAAK,SAAS,IAAI;CAC1C,MAAM,QAAQ,KAAK,MAAM,IAAI;CAC7B,IAAI,iBAAiB,MAAM,IAAI;CAC/B,OAAO;EAAE;EAAO;CAAgB;AAClC;;AAGA,SAAS,SACP,OACA,UACA,UACA,SACA,YAAY,OAAO,mBACb;CACN,IAAI,YAAY,KAAK,YAAY,GAAG,OAAO;EAAE,MAAM;EAAI,OAAO;CAAE;CAChE,MAAM,UAAU,UAAU,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,GAAG,KAAK;CAC1D,MAAM,QAAkB,CAAC;CACzB,IAAI,OAAO;CACX,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,SAAS;EAC1B,IAAI,MAAM,UAAU,UAAU;EAC9B,MAAM,OAAO,OAAO,WAAW,MAAM,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI;EAKvE,MAAM,aAAa,eAAe,IAAI,KAAK,MAAM,SAAS,IAAI,IAAI;EAClE,IAAI,OAAO,OAAO,YAAY,SAAS,aAAa,WAAW;EAC/D,MAAM,KAAK,IAAI;EACf,QAAQ;EACR,UAAU;CACZ;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;EAAE,MAAM;EAAI,OAAO;CAAE;CACpD,MAAM,WAAW,UAAU,MAAM,QAAQ,IAAI;CAC7C,OAAO;EAAE,MAAM,SAAS,KAAK,IAAI;EAAG,OAAO,SAAS;CAAO;AAC7D;;;;;AAMA,SAAS,gBAAgB,MAAc,YAA0B;CAC/D,MAAM,UAAU,SAAS,OAAO,KAAK,MAAM,MAAM,GAAG,UAAU;CAC9D,MAAM,UAAU,QAAQ,QAAQ,IAAI;CACpC,MAAM,QAAQ,WAAW,IAAI,QAAQ,MAAM,UAAU,CAAC,IAAI;CAC1D,OAAO;EAAE,MAAM;EAAO,OAAO,MAAM,SAAS,IAAI,IAAI;CAAE;AACxD;;AAGA,SAAS,kBAAkB,YAA4B;CACrD,OAAO,OAAO,WAAW,cAAc,UAAU,GAAG,MAAM,IAAI;AAChE;AAEA,SAAS,cACP,MACA,MACA,aACA,iBACA,YACA,WAA8B,CAAC,GACtB;CACT,MAAM,UAAU,cAAc,IAAI,CAAC,cAAc,aAAa,SAAS,MAAM,CAAC,IAAI,CAAC;CACnF,MAAM,UAAU,cAAc,KAAK,SAAS,SAAS,IAAI,CAAC,GAAQ,IAAI,CAAC;CAEvE,MAAM,OADQ;EAAC,KAAK;EAAM,GAAG;EAAS,GAAG;EAAU,GAAG;EAAS,KAAK;CAAI,CAAC,CAAC,QAAQ,SAAS,KAAK,SAAS,CACxF,CAAC,CAAC,KAAK,IAAI;CAC5B,MAAM,cAAc,mBAAmB,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM;CACvE,MAAM,UAAU,OAAO,KAAK,aAAa,MAAM;CAG/C,OAAO;EAAE,MADI,QAAQ,aAAa,aAAa,SAAS,SAAS,UAAU,IAAI;EAChE,OAAO,KAAK,QAAQ,KAAK;EAAO,WAAW;EAAM,aAAa,KAAK,IAAI,GAAG,WAAW;CAAE;AACxG;AAEA,SAAS,UACP,UACA,UACA,YACA,WACA,UACA;CACA,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC;CACnD,MAAM,SAAS,KAAK,IAAI,GAAG,WAAW,kBAAkB,UAAU,CAAC;CAGnE,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,IAAI,eAAe,cAAc,UAAU,CAAC,CAAC;CAC5F,OAAO;EACL;EACA,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,aAAa,QAAQ,CAAC,CAAC;EACtE,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,aAAa,QAAQ,CAAC,CAAC;EAClE,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,aAAa,QAAQ,CAAC,CAAC;EACnE;EACA;CACF;AACF;;;;;;;;AASA,SAAgB,aACd,MACA,WAAW,kBAAkB,GAC7B,WAAW,kBAAkB,GAC7B,YAAY,mBAAmB,GAC/B,UACS;CACT,IAAI,KAAK,WAAW,GAAG,OAAO;EAAE,MAAM;EAAI,OAAO;EAAG,WAAW;EAAO,aAAa;CAAE;CACrF,MAAM,EAAE,OAAO,oBAAoB,eAAe,IAAI;CACtD,MAAM,aAAa,OAAO,WAAW,MAAM,MAAM;CACjD,MAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI;CACnF,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC;CACnD,IAAI,MAAM,UAAU,cAAc,cAAc,cAAc,eAAe,IAAI,KAAK,WACpF,OAAO;EAAE;EAAM,OAAO,MAAM;EAAQ,WAAW;EAAO,aAAa;CAAE;CAGvE,MAAM,SAAS,UAAU,UAAU,YAAY,MAAM,QAAQ,WAAW,QAAQ;CAChF,MAAM,OAAO,SAAS,OAAO,OAAO,WAAW,OAAO,WAAW,OAAO,OAAO,UAAU;CACzF,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK;CAGnC,MAAM,aAAa,KAAK,MAAM,SAAS,YAAY,MAAM,QAAQ,CAAC,IAC9D,KAAK,MAAM,OAAO,SAAS,oBAAoB,QAAQ,CAAC,IACxD;CACJ,MAAM,YAAY,OAAO,SAAS,OAAO,WAAW,KAAK,MAAM,MAAM,IAAI;CACzE,MAAM,aAAa,OAAO,SAAS,eAAe,KAAK,IAAI;CAC3D,MAAM,OAAO,SAAS,MAAM,KAAK,IAAI,GAAG,OAAO,aAAa,KAAK,KAAK,GAAG,WAAW,MAAM,UAAU;CACpG,IAAI,KAAK,UAAU,KAAK,KAAK,UAAU,GAAG;EACxC,MAAM,WAAW,gBAAgB,MAAM,UAAU;EACjD,OAAO;GAAE,MAAM,SAAS;GAAM,OAAO,SAAS;GAAO,WAAW;GAAM,aAAa,MAAM,SAAS,SAAS;EAAM;CACnH;CACA,MAAM,SAAS,MAAM,MAAM,KAAK,OAAO,MAAM,SAAS,KAAK,KAAK;CAChE,MAAM,WACJ,aAAa,IACT,eAAe,kBAAkB,QAAQ,mBAAmB,QAAQ,GAAG,QAAQ,GAAG,YAAY,QAAQ,IACtG,CAAC;CACP,OAAO,cAAc,MAAM,MAAM,MAAM,SAAS,KAAK,QAAQ,KAAK,OAAO,iBAAiB,YAAY,QAAQ;AAChH;;;;;;AAOA,SAAgB,eACd,YACA,YACA,YACA,WAAW,kBAAkB,GAC7B,WAAW,kBAAkB,GAC7B,SAAmC,CAAC,GACpC,YAAY,mBAAmB,GAC/B,UACS;CACT,MAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI,OAAO;CAC1F,MAAM,SAAS,UAAU,UAAU,YAAY,YAAY,WAAW,QAAQ;CAC9E,MAAM,YAAY,eAAe,UAAU,CAAC,CAAC;CAC7C,MAAM,EAAE,OAAO,WAAW,oBAAoB,eAAe,UAAU;CAEvE,MAAM,OAAO,SACX,UAAU,MAAM,GAAG,KAAK,IAAI,GAAG,UAAU,SAAS,CAAC,CAAC,GACpD,OAAO,WACP,OAAO,WACP,OACA,OAAO,UACT;CACA,MAAM,aAAa,OAAO,SAAS,IAAI,KAAK,MAAM,OAAO,SAAS,oBAAoB,QAAQ,CAAC,IAAI;CACnG,MAAM,YAAY,OAAO,SAAS,OAAO,WAAW,KAAK,MAAM,MAAM,IAAI;CACzE,MAAM,aAAa,OAAO,SAAS,eAAe,KAAK,IAAI;CAC3D,MAAM,OAAO,SAAS,UAAU,MAAM,CAAC,GAAG,KAAK,IAAI,GAAG,OAAO,aAAa,KAAK,KAAK,GAAG,WAAW,MAAM,UAAU;CAClH,IAAI,KAAK,UAAU,KAAK,KAAK,UAAU,GAAG;EACxC,MAAM,WAAW,gBAAgB,YAAY,UAAU;EACvD,OAAO;GACL,MAAM,SAAS;GACf,OAAO,SAAS;GAChB,WAAW;GACX,aAAa,KAAK,IAAI,GAAG,aAAa,SAAS,KAAK;EACtD;CACF;CACA,MAAM,WAAW,aAAa,IAAI,eAAe,QAAQ,YAAY,QAAQ,IAAI,CAAC;CAClF,OAAO,cACL,MACA,MACA,KAAK,IAAI,GAAG,aAAa,KAAK,QAAQ,KAAK,KAAK,GAChD,iBACA,YACA,QACF;AACF;;AAGA,SAAgB,cACd,MACA,WAAW,kBAAkB,GAC7B,WAAW,kBAAkB,GACW;CACxC,MAAM,UAAU,aAAa,MAAM,UAAU,QAAQ;CACrD,OAAO;EAAE,MAAM,QAAQ;EAAM,WAAW,QAAQ;CAAM;AACxD;;AAGA,SAAgB,gBACd,MACA,WAAW,kBAAkB,GAC7B,UACA,YAAY,mBAAmB,GACvB;CACR,OAAO,aAAa,MAAM,kBAAkB,QAAQ,GAAG,UAAU,WAAW,QAAQ,CAAC,CAAC;AACxF;;;;;;;;AAUA,SAAS,SAAS,SAAkC,UAA0B;CAC5E,IAAI,QAAQ,KAAK,IAAI,GAAG,QAAQ,aAAa,QAAQ;CACrD,OAAO,QAAQ,QAAQ,eAAe,QAAQ,SAAU,SAAU,KAAM,SAAS;CACjF,OAAO,QAAQ,SAAS,KAAK,CAAC,CAAC,SAAS,MAAM;AAChD;;AAGA,SAAgB,iBAAiB,QAAsB,MAAM,KAAK,IAAI,GAAW;CAC/E,MAAM,OAAO,OAAO,cAAc,iBAAiB;CACnD,OAAO,GAAG,OAAO,KAAK,QAAQ,OAAO,IAAI,OAAO,aAAa,OAAO,WAAW,GAAG,IAAI,KAAK,IAAI,OAAO;AACxG;AAEA,SAAgB,aAAa,WAAmB,KAAqB;CACnE,MAAM,UAAU,KAAK,MAAM,SAAS;CACpC,IAAI,OAAO,MAAM,OAAO,GAAG,OAAO;CAClC,MAAM,UAAU,KAAK,IAAI,GAAG,KAAK,OAAO,MAAM,WAAW,GAAI,CAAC;CAC9D,IAAI,UAAU,IAAI,OAAO,GAAG,QAAQ;CACpC,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;CACvC,IAAI,UAAU,IAAI,OAAO,GAAG,QAAQ;CACpC,MAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;CACrC,OAAO,QAAQ,KAAK,GAAG,MAAM,KAAK,GAAG,KAAK,MAAM,QAAQ,EAAE,EAAE;AAC9D;AAEA,MAAM,oBAA+E;CACnF,WAAW;CACX,WAAW;CACX,aAAa;AACf;AAEA,SAAS,iBAAiB,QAA+B;CACvD,OAAO,OAAO,aAAa,QAAQ,OAAO,WAAW,QAAS,OAAO,aAAa,QAAQ,OAAO,aAAa;AAChH;AAEA,SAAS,iBAAiB,QAA0C;CAClE,IAAI,OAAO,aAAa,MAAM,OAAO;CACrC,IAAI,OAAO,WAAW,MAAM,OAAO,WAAW,OAAO;CACrD,IAAI,OAAO,aAAa,MAAM,OAAO;CACrC,IAAI,OAAO,aAAa,GAAG,OAAO,SAAS,OAAO;AAEpD;AAEA,MAAM,yBAAwC;CAAE,MAAM;CAAI,OAAO;CAAG,OAAO;CAAG,WAAW;AAAE;AAE3F,SAAgB,gBACd,QACA,SAAuB,CAAC,GACxB,eAA8B,iBAC9B,UACA,cAAiC,QAAQ,KAC7B;CACZ,IAAI,OAAO,SAAS,UAClB,MAAM,IAAI,MACR,CACE,2BAA2B,OAAO,KAAK,KAAK,OAAO,SACnD,iHACF,CAAC,CAAC,KAAK,IAAI,CACb;CAGF,IAAI,OAAO,SAAS,YAOlB,OAAO,WALM;EACX,GAFa,kBAAkB,OAAO,QAE5B,YAAY,OAAO,KAAK,KAAK,OAAO,GAAG;EACjD,kBAAkB,OAAO;EACzB,6BAA6B,OAAO;CACtC,CAAC,CAAC,KAAK,IACc,GAAG;EACtB,IAAI,OAAO;EACX,QAAQ,OAAO;EACf,KAAK,OAAO;EACZ,SAAS,OAAO;EAChB,UAAU;EACV,QAAQ,OAAO;CACjB,CAAC;CAGH,MAAM,YAAY,CAAC,iBAAiB,MAAM;CAC1C,MAAM,WAAW,OAAO,YAAY,kBAAkB,QAAQ;CAC9D,MAAM,WACJ,OAAO,aACN,YAAY,yBAAyB,aAAa,QAAQ,IAAI,kBAAkB,aAAa,QAAQ;CACxG,MAAM,YACJ,OAAO,cACN,YAAY,0BAA0B,aAAa,QAAQ,IAAI,mBAAmB,aAAa,QAAQ;CAC1G,MAAM,MAAM,aAAa,OAAO,SAAS,UAAU,UAAU,SAAS;CACtE,MAAM,oBAAoB,CAAC,OAAO,aAAa,IAAI,KAAK,WAAW,KAAK,OAAO,OAAO,SAAS;CAC/F,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI,OAAO,WAAW;EAEpB,MAAM,UADU,OAAO,WAAW,OAAO,UAAU,QAAQ,MAAM,IAAI,OAAO,UAAU,QAElF,eACE,OAAO,UAAU,MACjB,OAAO,UAAU,QACjB,OAAO,UAAU,OACjB,UACA,UACA,CAAC,GACD,WACA,QACF,IACA,aAAa,OAAO,UAAU,QAAQ,UAAU,UAAU,WAAW,QAAQ;EACjF,OAAO,QAAQ;EACf,YAAY,QAAQ;EACpB,cAAc,OAAO,UAAU;EAC/B,cAAc,OAAO,UAAU;EAC/B,YAAY,cAAc,aAAa,cAAc,OAAO,WAAW,MAAM,MAAM;CACrF,OAAO;EACL,OAAO,oBAAoB,OAAO,SAAS,IAAI;EAC/C,YAAY,oBAAoB,WAAW,IAAI,IAAI,IAAI;EACvD,cAAc,KAAK,IAAI,IAAI,OAAO,SAAS;EAC3C,cAAc,oBAAoB,OAAO,WAAW,MAAM,MAAM,IAAI,IAAI;EACxE,YAAY,CAAC,sBAAsB,cAAc,aAAa,cAAc,OAAO,WAAW,MAAM,MAAM;CAC5G;CACA,MAAM,YAAY,UAAU,IAAI,CAAC,CAAC,QAAQ,WAAW,EAAE;CACvD,MAAM,SAAS,iBAAiB,MAAM;CACtC,MAAM,SAAS,iBAAiB,MAAM;CACtC,MAAM,YAAsB,CAAC;CAC7B,IAAI,UAAU,WAAW,GACvB,UAAU,KAAK,WAAW,KAAA,IAAY,8BAA8B,YAAY;MAC3E,IAAI,WAAW;EACpB,MAAM,QAAQ,OAAO,YAAY,OAAO,OAAO,UAAU,OAAO,YAAY;EAC5E,UAAU,KACR,GAAG,MAAM,IAAI,UAAU,eAAe,OAAO,EAAE,MAAM,YAAY,eAAe,OAAO,EAAE,YAAY,WACvG;CACF,OACE,UAAU,KAAK,SAAS;CAE1B,IAAI,WAAW,KAAA,GAAW,UAAU,KAAK,MAAM;CAC/C,IAAI,OAAO,YAAY,UAAU,KAAK,OAAO,UAAU;CACvD,IAAI,WAAW;EACb,MAAM,QAAQ,OAAO,YAAY,qBAAqB;EACtD,UAAU,KACR,GAAG,MAAM,IAAI,OAAO,QAAQ,IAAI,WAAW,IAAI,KAAK,EAAE,IAAI,IAAI,MAAM,eAAe,OAAO,EAAE,yCAAyC,OAAO,IAC9I;CACF,OAAO,IAAI,UAAU,UAAU,WAAW,GACxC,UAAU,KACR,QAAQ,OAAO,WACf,4EACF;CAEF,MAAM,OAAO,gBACX,UAAU,KAAK,IAAI,GACnB,UACA,UACA,aAAa,KAAA,IAAY,KAAA,IAAY,SACvC;CACA,MAAM,UAAU;EACd,IAAI,OAAO;EACX,QAAQ,OAAO;EACf,UAAU,OAAO;EACjB,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,UAAU,IAAI;EACd,OAAO,oBAAoB,YAAY,IAAI;EAC3C;EACA;EACA,GAAI,OAAO,YACP;GACE,WAAW,OAAO,UAAU;GAC5B,gBAAgB,OAAO,UAAU;GACjC,gBAAgB,OAAO,UAAU;EACnC,IACA,CAAC;EACL,GAAI,OAAO,aAAa,EAAE,YAAY,OAAO,WAAW,IAAI,CAAC;EAC7D,GAAI,OAAO,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;CAC9C;CACA,IAAI,QAAQ,MAAM,IAAI,MAAM,IAAI;CAChC,OAAO,WAAW,MAAM,OAAO;AACjC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bashRunService-CJ02XaAq.mjs","names":[],"sources":["../src/constants/bashRunService.ts","../src/services/bashRunService/resources.ts","../src/services/bashRunService/index.ts"],"sourcesContent":["export const RUNNER_ID_RANDOM_BYTES = 3;\nexport const OUTPUT_UPDATE_POLL_MS = 100;\nexport const COMPLETED = 'completed';\nexport const FAILED = 'failed';\nexport const LAUNCHER_ERROR = 'launcher_error';\nexport const SIGNALED = 'signaled';\n","/** Split shell words without mistaking quoted examples for executed commands. */\nfunction commandInvocations(command: string): string[][] {\n const invocations: string[][] = [];\n let words: string[] = [];\n let word = '';\n let quote = '';\n let escaped = false;\n const flushWord = (): void => {\n if (word) words.push(word);\n word = '';\n };\n for (const character of command) {\n if (escaped) {\n word += character;\n escaped = false;\n } else if (character === '\\\\' && quote !== \"'\") {\n escaped = true;\n } else if (quote) {\n if (character === quote) quote = '';\n else word += character;\n } else if (character === '\"' || character === \"'\") {\n quote = character;\n } else if (';&|\\n'.includes(character)) {\n flushWord();\n if (words.length) invocations.push(words);\n words = [];\n } else if (/\\s/.test(character)) {\n flushWord();\n } else {\n word += character;\n }\n }\n flushWord();\n if (words.length) invocations.push(words);\n return invocations;\n}\n\nfunction executableName(word: string): string {\n return word.replace(/\\\\/g, '/').split('/').pop() ?? word;\n}\n\n/** Admission policy, not a shell security boundary. Opaque scripts still need explicit ownership. */\nexport function isHeavyCommand(command: string): boolean {\n const heavyTarget =\n /(?:^|[\\s:,=])(?:build(?:-[\\w-]+)?|test(?:-[\\w-]+)?|lint|fixcode|typecheck|check|bundle)(?:[\\s:,=]|$)/;\n const expensiveBinary =\n /^(?:vitest|jest|tsc|tsgo|tsdown|webpack|rollup|esbuild|oxlint|oxfmt|pytest|maestro|xcodebuild|gradle|gradlew)$/;\n return commandInvocations(command).some((words) => {\n while (words.length) {\n const first = words[0]!;\n if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(first) || /^(?:env|command|exec|time)$/.test(executableName(first))) {\n words.shift();\n } else break;\n }\n let executable = executableName(words.shift() ?? '');\n if (words.includes('--help') || words.includes('--version')) return false;\n if (/^(?:pnpm|npm|npx|yarn|bun)$/.test(executable)) {\n while (words[0]?.startsWith('-')) {\n const option = words.shift()!;\n if (/^(?:--filter|--dir|-C|--cwd|--prefix)$/.test(option)) words.shift();\n }\n if (words[0] === 'exec' || words[0] === 'run') words.shift();\n const invocation = words.shift() ?? '';\n if (/^(?:build|test|lint|fixcode|typecheck|check)(?::[\\w-]+)?$/.test(invocation)) return true;\n executable = executableName(invocation);\n }\n if (expensiveBinary.test(executable)) return true;\n if (executable === 'nx') {\n if (/^(?:show|graph|daemon|reset|report|list)$/.test(words[0] ?? '')) return false;\n return heavyTarget.test(words.join(' '));\n }\n if (/^(?:playwright|vite|cargo|go)$/.test(executable)) return heavyTarget.test(words.join(' '));\n if (/^(?:python|python3)$/.test(executable)) return words[0] === '-m' && words[1] === 'pytest';\n if (/^(?:node|bun|tsx)$/.test(executable)) {\n executable = executableName(words.find((word) => !word.startsWith('-')) ?? '');\n }\n const script = executable.replace(/\\.(?:[cm]?[jt]s)$/, '');\n return (\n script !== executable &&\n (expensiveBinary.test(script) || /^(?:build|test|lint|fixcode|typecheck|dev)(?:[-:]|$)/.test(script))\n );\n });\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { DoomHostExecutionBudget, DoomHostExecutionPermit } from '@agimon-ai/po-core/packageApi';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\nimport { isHeavyCommand } from './resources';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n/** The caller cancelled the command and its owned runner should be stopped. */\nconst ABORTED = Symbol('aborted');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n private readonly cwd: string = process.cwd(),\n private readonly env: Readonly<Record<string, string | undefined>> = process.env,\n private readonly executionBudget?: DoomHostExecutionBudget,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? this.cwd;\n const interactive = request.interactive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n let releaseSlot: DoomHostExecutionPermit | undefined;\n try {\n if (this.executionBudget && isHeavyCommand(request.command)) {\n releaseSlot = await this.executionBudget.acquire(request.signal);\n }\n request.signal?.throwIfAborted();\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n // A background promotion is not process completion. Keep the slot until actual exit.\n if (releaseSlot) void handle.completion().then(releaseSlot, releaseSlot);\n // Register first so a process that survives PID attachment failure remains discoverable.\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (releaseSlot) {\n try {\n await releaseSlot.attachProcess?.(handle.pid);\n } catch (error) {\n if (!(await handle.stop())) {\n return this.promote(handle, 'requested', undefined);\n }\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n }\n if (request.signal?.aborted) {\n if (!(await handle.stop())) return this.promote(handle, 'requested', undefined);\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs, request.signal);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await this.registry.requestTermination(id, { reason: 'stopped', terminationReason: 'request_cancelled' });\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n aborted: true,\n };\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await this.registry.requestTermination(id, { reason: 'timed_out', terminationReason: 'timeout' });\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n signal: AbortSignal | undefined,\n ): Promise<\n Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE | typeof ABORTED\n > {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(this.env), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n if (signal) {\n racers.push(\n new Promise((resolve) => {\n if (signal.aborted) {\n resolve(ABORTED);\n return;\n }\n const abort = (): void => resolve(ABORTED);\n signal.addEventListener('abort', abort, { once: true });\n cancels.push(() => signal.removeEventListener('abort', abort));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as\n | Awaited<ReturnType<RunHandle['completion']>>\n | Error\n | typeof TIMED_OUT\n | typeof DEADLINE\n | typeof ABORTED;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(\n handle: RunHandle,\n reason: PromotedRun['reason'],\n signal: AbortSignal | undefined,\n ): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n let abortStop: Promise<boolean> | undefined;\n const abort = (): void => {\n abortStop = this.registry\n .requestTermination(handle.id, { reason: 'stopped', terminationReason: 'request_cancelled' })\n .then(() => handle.stop())\n .then(async () => {\n await this.registry.complete(handle.id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return true;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);\n return false;\n });\n };\n // The signal belongs to the tool call, which ends when this returns, and\n // Pi aborts it at that point. A promoted runner outlives the call, so only\n // an abort that landed before the handoff stops it.\n if (signal?.aborted) abort();\n void handle\n .completion()\n .then(\n async (outcome) => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, {\n reason: outcome.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n },\n async () => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n },\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bashRunService-DyqxOSlx.cjs","names":["randomBytes","RTK_FAILED_WARNING","getBackgroundThresholdMs"],"sources":["../src/constants/bashRunService.ts","../src/services/bashRunService/resources.ts","../src/services/bashRunService/index.ts"],"sourcesContent":["export const RUNNER_ID_RANDOM_BYTES = 3;\nexport const OUTPUT_UPDATE_POLL_MS = 100;\nexport const COMPLETED = 'completed';\nexport const FAILED = 'failed';\nexport const LAUNCHER_ERROR = 'launcher_error';\nexport const SIGNALED = 'signaled';\n","/** Split shell words without mistaking quoted examples for executed commands. */\nfunction commandInvocations(command: string): string[][] {\n const invocations: string[][] = [];\n let words: string[] = [];\n let word = '';\n let quote = '';\n let escaped = false;\n const flushWord = (): void => {\n if (word) words.push(word);\n word = '';\n };\n for (const character of command) {\n if (escaped) {\n word += character;\n escaped = false;\n } else if (character === '\\\\' && quote !== \"'\") {\n escaped = true;\n } else if (quote) {\n if (character === quote) quote = '';\n else word += character;\n } else if (character === '\"' || character === \"'\") {\n quote = character;\n } else if (';&|\\n'.includes(character)) {\n flushWord();\n if (words.length) invocations.push(words);\n words = [];\n } else if (/\\s/.test(character)) {\n flushWord();\n } else {\n word += character;\n }\n }\n flushWord();\n if (words.length) invocations.push(words);\n return invocations;\n}\n\nfunction executableName(word: string): string {\n return word.replace(/\\\\/g, '/').split('/').pop() ?? word;\n}\n\n/** Admission policy, not a shell security boundary. Opaque scripts still need explicit ownership. */\nexport function isHeavyCommand(command: string): boolean {\n const heavyTarget =\n /(?:^|[\\s:,=])(?:build(?:-[\\w-]+)?|test(?:-[\\w-]+)?|lint|fixcode|typecheck|check|bundle)(?:[\\s:,=]|$)/;\n const expensiveBinary =\n /^(?:vitest|jest|tsc|tsgo|tsdown|webpack|rollup|esbuild|oxlint|oxfmt|pytest|maestro|xcodebuild|gradle|gradlew)$/;\n return commandInvocations(command).some((words) => {\n while (words.length) {\n const first = words[0]!;\n if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(first) || /^(?:env|command|exec|time)$/.test(executableName(first))) {\n words.shift();\n } else break;\n }\n let executable = executableName(words.shift() ?? '');\n if (words.includes('--help') || words.includes('--version')) return false;\n if (/^(?:pnpm|npm|npx|yarn|bun)$/.test(executable)) {\n while (words[0]?.startsWith('-')) {\n const option = words.shift()!;\n if (/^(?:--filter|--dir|-C|--cwd|--prefix)$/.test(option)) words.shift();\n }\n if (words[0] === 'exec' || words[0] === 'run') words.shift();\n const invocation = words.shift() ?? '';\n if (/^(?:build|test|lint|fixcode|typecheck|check)(?::[\\w-]+)?$/.test(invocation)) return true;\n executable = executableName(invocation);\n }\n if (expensiveBinary.test(executable)) return true;\n if (executable === 'nx') {\n if (/^(?:show|graph|daemon|reset|report|list)$/.test(words[0] ?? '')) return false;\n return heavyTarget.test(words.join(' '));\n }\n if (/^(?:playwright|vite|cargo|go)$/.test(executable)) return heavyTarget.test(words.join(' '));\n if (/^(?:python|python3)$/.test(executable)) return words[0] === '-m' && words[1] === 'pytest';\n if (/^(?:node|bun|tsx)$/.test(executable)) {\n executable = executableName(words.find((word) => !word.startsWith('-')) ?? '');\n }\n const script = executable.replace(/\\.(?:[cm]?[jt]s)$/, '');\n return (\n script !== executable &&\n (expensiveBinary.test(script) || /^(?:build|test|lint|fixcode|typecheck|dev)(?:[-:]|$)/.test(script))\n );\n });\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { DoomHostExecutionBudget, DoomHostExecutionPermit } from '@agimon-ai/po-core/packageApi';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\nimport { isHeavyCommand } from './resources';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n/** The caller cancelled the command and its owned runner should be stopped. */\nconst ABORTED = Symbol('aborted');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n private readonly cwd: string = process.cwd(),\n private readonly env: Readonly<Record<string, string | undefined>> = process.env,\n private readonly executionBudget?: DoomHostExecutionBudget,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? this.cwd;\n const interactive = request.interactive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n let releaseSlot: DoomHostExecutionPermit | undefined;\n try {\n if (this.executionBudget && isHeavyCommand(request.command)) {\n releaseSlot = await this.executionBudget.acquire(request.signal);\n }\n request.signal?.throwIfAborted();\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n // A background promotion is not process completion. Keep the slot until actual exit.\n if (releaseSlot) void handle.completion().then(releaseSlot, releaseSlot);\n // Register first so a process that survives PID attachment failure remains discoverable.\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (releaseSlot) {\n try {\n await releaseSlot.attachProcess?.(handle.pid);\n } catch (error) {\n if (!(await handle.stop())) {\n return this.promote(handle, 'requested', undefined);\n }\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n }\n if (request.signal?.aborted) {\n if (!(await handle.stop())) return this.promote(handle, 'requested', undefined);\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs, request.signal);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await this.registry.requestTermination(id, { reason: 'stopped', terminationReason: 'request_cancelled' });\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n aborted: true,\n };\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await this.registry.requestTermination(id, { reason: 'timed_out', terminationReason: 'timeout' });\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n signal: AbortSignal | undefined,\n ): Promise<\n Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE | typeof ABORTED\n > {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(this.env), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n if (signal) {\n racers.push(\n new Promise((resolve) => {\n if (signal.aborted) {\n resolve(ABORTED);\n return;\n }\n const abort = (): void => resolve(ABORTED);\n signal.addEventListener('abort', abort, { once: true });\n cancels.push(() => signal.removeEventListener('abort', abort));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as\n | Awaited<ReturnType<RunHandle['completion']>>\n | Error\n | typeof TIMED_OUT\n | typeof DEADLINE\n | typeof ABORTED;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(\n handle: RunHandle,\n reason: PromotedRun['reason'],\n signal: AbortSignal | undefined,\n ): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n let abortStop: Promise<boolean> | undefined;\n const abort = (): void => {\n abortStop = this.registry\n .requestTermination(handle.id, { reason: 'stopped', terminationReason: 'request_cancelled' })\n .then(() => handle.stop())\n .then(async () => {\n await this.registry.complete(handle.id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return true;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);\n return false;\n });\n };\n // The signal belongs to the tool call, which ends when this returns, and\n // Pi aborts it at that point. A promoted runner outlives the call, so only\n // an abort that landed before the handoff stops it.\n if (signal?.aborted) abort();\n void handle\n .completion()\n .then(\n async (outcome) => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, {\n reason: outcome.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n },\n async () => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n },\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cliApp-Dj9nwaOv.cjs","names":["inheritedSessionId","requiredSessionId","formatRunnerLine","fs"],"sources":["../src/constants/cliApp.ts","../src/services/cliApp/index.ts"],"sourcesContent":["export const DEFAULT_LOG_LINES = 200;\nexport const FOLLOW_INTERVAL_MS = 250;\nexport const COMPLETED_STATE = 'completed';\nexport const STOPPED_REASON = 'stopped';\nexport const TERMINATION_SIGNAL = 'SIGTERM';\n","import fs from 'node:fs';\n\nimport {\n COMPLETED_STATE,\n DEFAULT_LOG_LINES,\n FOLLOW_INTERVAL_MS,\n STOPPED_REASON,\n TERMINATION_SIGNAL,\n} from '../../constants/cliApp';\nimport { formatRunnerLine } from '../../services/bashResult';\nimport { inheritedSessionId, requiredSessionId } from '../../services/session';\nimport type { ILauncher } from '../../types/launcher';\nimport type { ILogReader } from '../../types/logReader';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport type { IRunnerRegistry, RunnerRecord } from '../../types/runnerRegistry';\n\nconst MULTIPLEXER_BACKENDS = new Set(['rmux', 'tmux']);\n\nexport interface CliDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n logReader: ILogReader;\n env?: NodeJS.ProcessEnv;\n stdout(text: string): void;\n stderr(text: string): void;\n readStdin(): Promise<string>;\n}\n\nexport async function runCli(argv: readonly string[], dependencies: CliDependencies): Promise<number> {\n const [command, ...args] = argv;\n if (command !== undefined && command !== '--help' && command !== '-h' && !inheritedSessionId(dependencies.env)) {\n dependencies.stderr('PI_SESSION_ID is required for doom-runner commands');\n return 1;\n }\n switch (command) {\n case 'list':\n return list(args, dependencies);\n case 'status':\n return status(args, dependencies);\n case 'logs':\n return logs(args, dependencies);\n case 'stop':\n return stop(args, dependencies);\n case 'stop-all':\n return stopAll(args, dependencies);\n case 'input':\n return input(args, dependencies);\n case '--help':\n case '-h':\n case undefined:\n dependencies.stdout(help());\n return 0;\n default:\n dependencies.stderr(`Unknown command: ${command}\\n\\n${help()}`);\n return 1;\n }\n}\n\nasync function list(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = args.includes('--all')\n ? await dependencies.registry.listAll(sessionId)\n : await dependencies.registry.listBySession(sessionId);\n dependencies.stdout(records.length > 0 ? records.map(formatRecord).join('\\n') : 'No runners');\n return 0;\n}\n\nasync function status(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n dependencies.stdout(\n [\n `ID: ${record.id}`,\n `Name: ${record.name}`,\n `State: ${record.state}`,\n `Backend: ${record.backend}`,\n ...(record.backendTarget ? [`Backend target: ${record.backendTarget}`] : []),\n `PID: ${record.pid}`,\n `Command: ${record.command}`,\n `CWD: ${record.cwd}`,\n `Session: ${record.sessionId}`,\n `Log: ${record.logPath}`,\n ...(record.exit ? [`Exit: ${formatExit(record)}`] : []),\n ].join('\\n'),\n );\n return 0;\n}\n\nasync function logs(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n const lines = positiveInteger(option(args, '--lines')) ?? DEFAULT_LOG_LINES;\n const result = dependencies.logReader.read(record.logPath, { lines });\n if (!result.exists) {\n dependencies.stderr(`Runner ${record.id} has not written a log yet`);\n return 1;\n }\n if (result.text) dependencies.stdout(result.text);\n if (!args.includes('--follow')) return 0;\n return follow(record, result.fileSize, dependencies);\n}\n\nasync function stop(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state !== 'running') {\n dependencies.stderr(`Runner ${record.id} is already ${record.state}`);\n return 1;\n }\n const reason = option(args, '--reason');\n if (!(await stopRecord(record, dependencies, reason))) {\n dependencies.stderr(`Could not stop runner ${record.id}`);\n return 1;\n }\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n dependencies.stdout(`Stopped ${record.id}\\nLog: ${record.logPath}`);\n return 0;\n}\n\nasync function stopAll(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = await dependencies.registry.listBySession(sessionId);\n const reason = option(args, '--reason');\n const stopped: RunnerRecord[] = [];\n for (const record of records) {\n if (!(await stopRecord(record, dependencies, reason))) continue;\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n stopped.push(record);\n }\n dependencies.stdout(\n records.length === 0\n ? `No active runners for session ${sessionId}`\n : `Stopped ${stopped.length} runner(s) for session ${sessionId}: ${stopped.map((record) => record.id).join(', ')}`,\n );\n if (stopped.length !== records.length) {\n dependencies.stderr(`Could not stop ${records.length - stopped.length} runner(s) for session ${sessionId}`);\n return 1;\n }\n return 0;\n}\n\nasync function input(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state === COMPLETED_STATE) {\n dependencies.stderr(`Runner ${record.id} is already completed; use doom-runner logs ${record.id}`);\n return 1;\n }\n if (!record.interactive) {\n dependencies.stderr(`Runner ${record.id} was not started with interactive: true`);\n return 1;\n }\n const supplied = option(args, '--text');\n const text = supplied ?? (await dependencies.readStdin());\n const payload = args.includes('--enter') && !text.endsWith('\\n') ? `${text}\\n` : text;\n if (!MULTIPLEXER_BACKENDS.has(record.backend) || !record.backendTarget) {\n dependencies.stderr(`Runner ${record.id} uses the ${record.backend} backend, which does not expose CLI input`);\n return 1;\n }\n if (!(await dependencies.rmuxBackend.input(record.backendTarget, payload))) {\n dependencies.stderr(`Runner ${record.id} is no longer accepting RMUX input`);\n return 1;\n }\n dependencies.stdout(`Sent input to ${record.id}`);\n return 0;\n}\n\nasync function requiredRecord(\n id: string | undefined,\n dependencies: CliDependencies,\n): Promise<RunnerRecord | undefined> {\n if (!id) {\n dependencies.stderr('A runner ID is required');\n return undefined;\n }\n const sessionId = requiredSessionId(dependencies.env);\n const scopedRecord = await dependencies.registry.get(id, sessionId);\n if (!scopedRecord) {\n dependencies.stderr(`No runner with id ${id}`);\n return undefined;\n }\n return scopedRecord;\n}\n\nfunction formatRecord(record: RunnerRecord): string {\n const active = formatRunnerLine(record, Date.now());\n return `${record.id} ${record.state} ${record.backend} ${active}`;\n}\n\nfunction formatExit(record: RunnerRecord): string {\n const exit = record.exit;\n if (!exit) return 'unknown';\n const detail = exit.code !== null ? ` code ${exit.code}` : exit.signal ? ` ${exit.signal}` : '';\n const attribution = exit.terminationReason ? ` [${exit.terminationReason}]` : '';\n const note = exit.stopReason ? ` (${exit.stopReason})` : '';\n return `${exit.reason}${detail}${attribution}${note}`;\n}\n\nfunction option(args: readonly string[], name: string): string | undefined {\n const index = args.indexOf(name);\n return index >= 0 ? args[index + 1] : undefined;\n}\n\nfunction positiveInteger(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nasync function follow(record: RunnerRecord, initialSize: number, dependencies: CliDependencies): Promise<number> {\n let offset = initialSize;\n return new Promise<number>((resolve) => {\n let stopped = false;\n let stateTimer: NodeJS.Timeout;\n const listener = (current: fs.Stats): void => {\n if (current.size < offset) offset = 0;\n if (current.size === offset) return;\n const length = current.size - offset;\n const buffer = Buffer.alloc(length);\n const descriptor = fs.openSync(record.logPath, 'r');\n try {\n const bytesRead = fs.readSync(descriptor, buffer, 0, length, offset);\n if (bytesRead > 0) dependencies.stdout(buffer.subarray(0, bytesRead).toString('utf8'));\n } finally {\n fs.closeSync(descriptor);\n }\n offset = current.size;\n };\n const stopFollowing = (): void => {\n if (stopped) return;\n stopped = true;\n try {\n listener(fs.statSync(record.logPath));\n } catch (error) {\n // A missing log file was already reported by the initial read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n dependencies.stderr(`Runner ${record.id} failed to flush its final log output: ${(error as Error).message}`);\n }\n }\n fs.unwatchFile(record.logPath, listener);\n clearInterval(stateTimer);\n process.off('SIGINT', stopFollowing);\n resolve(0);\n };\n process.once('SIGINT', stopFollowing);\n fs.watchFile(record.logPath, { interval: FOLLOW_INTERVAL_MS }, listener);\n stateTimer = setInterval(() => {\n void dependencies.registry.get(record.id).then((current) => {\n if (current?.state === COMPLETED_STATE) stopFollowing();\n });\n }, FOLLOW_INTERVAL_MS);\n });\n}\n\nasync function stopRecord(record: RunnerRecord, dependencies: CliDependencies, reason?: string): Promise<boolean> {\n await dependencies.registry.requestTermination(\n record.id,\n {\n reason: STOPPED_REASON,\n terminationReason: 'user_stop',\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n const stopped =\n MULTIPLEXER_BACKENDS.has(record.backend) && record.backendTarget\n ? await dependencies.rmuxBackend.stop(record.backendTarget, record.pid)\n : await dependencies.launcher.stop(record.pid);\n if (!stopped) await dependencies.registry.requestTermination(record.id, undefined, record.sessionId);\n return stopped;\n}\n\nfunction help(): string {\n return [\n 'Usage: doom-runner <command>',\n '',\n 'Commands:',\n ' list [--all]',\n ' status <id>',\n ' logs <id> [--lines N] [--follow]',\n ' stop <id> [--reason <text>]',\n ' stop-all [--reason <text>]',\n ' input <id> [--text <text>] [--enter]',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cliApp-v8Z3rMCi.mjs","names":[],"sources":["../src/constants/cliApp.ts","../src/services/cliApp/index.ts"],"sourcesContent":["export const DEFAULT_LOG_LINES = 200;\nexport const FOLLOW_INTERVAL_MS = 250;\nexport const COMPLETED_STATE = 'completed';\nexport const STOPPED_REASON = 'stopped';\nexport const TERMINATION_SIGNAL = 'SIGTERM';\n","import fs from 'node:fs';\n\nimport {\n COMPLETED_STATE,\n DEFAULT_LOG_LINES,\n FOLLOW_INTERVAL_MS,\n STOPPED_REASON,\n TERMINATION_SIGNAL,\n} from '../../constants/cliApp';\nimport { formatRunnerLine } from '../../services/bashResult';\nimport { inheritedSessionId, requiredSessionId } from '../../services/session';\nimport type { ILauncher } from '../../types/launcher';\nimport type { ILogReader } from '../../types/logReader';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport type { IRunnerRegistry, RunnerRecord } from '../../types/runnerRegistry';\n\nconst MULTIPLEXER_BACKENDS = new Set(['rmux', 'tmux']);\n\nexport interface CliDependencies {\n registry: IRunnerRegistry;\n launcher: ILauncher;\n rmuxBackend: IRmuxBackend;\n logReader: ILogReader;\n env?: NodeJS.ProcessEnv;\n stdout(text: string): void;\n stderr(text: string): void;\n readStdin(): Promise<string>;\n}\n\nexport async function runCli(argv: readonly string[], dependencies: CliDependencies): Promise<number> {\n const [command, ...args] = argv;\n if (command !== undefined && command !== '--help' && command !== '-h' && !inheritedSessionId(dependencies.env)) {\n dependencies.stderr('PI_SESSION_ID is required for doom-runner commands');\n return 1;\n }\n switch (command) {\n case 'list':\n return list(args, dependencies);\n case 'status':\n return status(args, dependencies);\n case 'logs':\n return logs(args, dependencies);\n case 'stop':\n return stop(args, dependencies);\n case 'stop-all':\n return stopAll(args, dependencies);\n case 'input':\n return input(args, dependencies);\n case '--help':\n case '-h':\n case undefined:\n dependencies.stdout(help());\n return 0;\n default:\n dependencies.stderr(`Unknown command: ${command}\\n\\n${help()}`);\n return 1;\n }\n}\n\nasync function list(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = args.includes('--all')\n ? await dependencies.registry.listAll(sessionId)\n : await dependencies.registry.listBySession(sessionId);\n dependencies.stdout(records.length > 0 ? records.map(formatRecord).join('\\n') : 'No runners');\n return 0;\n}\n\nasync function status(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n dependencies.stdout(\n [\n `ID: ${record.id}`,\n `Name: ${record.name}`,\n `State: ${record.state}`,\n `Backend: ${record.backend}`,\n ...(record.backendTarget ? [`Backend target: ${record.backendTarget}`] : []),\n `PID: ${record.pid}`,\n `Command: ${record.command}`,\n `CWD: ${record.cwd}`,\n `Session: ${record.sessionId}`,\n `Log: ${record.logPath}`,\n ...(record.exit ? [`Exit: ${formatExit(record)}`] : []),\n ].join('\\n'),\n );\n return 0;\n}\n\nasync function logs(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n const lines = positiveInteger(option(args, '--lines')) ?? DEFAULT_LOG_LINES;\n const result = dependencies.logReader.read(record.logPath, { lines });\n if (!result.exists) {\n dependencies.stderr(`Runner ${record.id} has not written a log yet`);\n return 1;\n }\n if (result.text) dependencies.stdout(result.text);\n if (!args.includes('--follow')) return 0;\n return follow(record, result.fileSize, dependencies);\n}\n\nasync function stop(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state !== 'running') {\n dependencies.stderr(`Runner ${record.id} is already ${record.state}`);\n return 1;\n }\n const reason = option(args, '--reason');\n if (!(await stopRecord(record, dependencies, reason))) {\n dependencies.stderr(`Could not stop runner ${record.id}`);\n return 1;\n }\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n dependencies.stdout(`Stopped ${record.id}\\nLog: ${record.logPath}`);\n return 0;\n}\n\nasync function stopAll(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const sessionId = requiredSessionId(dependencies.env);\n const records = await dependencies.registry.listBySession(sessionId);\n const reason = option(args, '--reason');\n const stopped: RunnerRecord[] = [];\n for (const record of records) {\n if (!(await stopRecord(record, dependencies, reason))) continue;\n await dependencies.registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: TERMINATION_SIGNAL,\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n stopped.push(record);\n }\n dependencies.stdout(\n records.length === 0\n ? `No active runners for session ${sessionId}`\n : `Stopped ${stopped.length} runner(s) for session ${sessionId}: ${stopped.map((record) => record.id).join(', ')}`,\n );\n if (stopped.length !== records.length) {\n dependencies.stderr(`Could not stop ${records.length - stopped.length} runner(s) for session ${sessionId}`);\n return 1;\n }\n return 0;\n}\n\nasync function input(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n const record = await requiredRecord(args[0], dependencies);\n if (!record) return 1;\n if (record.state === COMPLETED_STATE) {\n dependencies.stderr(`Runner ${record.id} is already completed; use doom-runner logs ${record.id}`);\n return 1;\n }\n if (!record.interactive) {\n dependencies.stderr(`Runner ${record.id} was not started with interactive: true`);\n return 1;\n }\n const supplied = option(args, '--text');\n const text = supplied ?? (await dependencies.readStdin());\n const payload = args.includes('--enter') && !text.endsWith('\\n') ? `${text}\\n` : text;\n if (!MULTIPLEXER_BACKENDS.has(record.backend) || !record.backendTarget) {\n dependencies.stderr(`Runner ${record.id} uses the ${record.backend} backend, which does not expose CLI input`);\n return 1;\n }\n if (!(await dependencies.rmuxBackend.input(record.backendTarget, payload))) {\n dependencies.stderr(`Runner ${record.id} is no longer accepting RMUX input`);\n return 1;\n }\n dependencies.stdout(`Sent input to ${record.id}`);\n return 0;\n}\n\nasync function requiredRecord(\n id: string | undefined,\n dependencies: CliDependencies,\n): Promise<RunnerRecord | undefined> {\n if (!id) {\n dependencies.stderr('A runner ID is required');\n return undefined;\n }\n const sessionId = requiredSessionId(dependencies.env);\n const scopedRecord = await dependencies.registry.get(id, sessionId);\n if (!scopedRecord) {\n dependencies.stderr(`No runner with id ${id}`);\n return undefined;\n }\n return scopedRecord;\n}\n\nfunction formatRecord(record: RunnerRecord): string {\n const active = formatRunnerLine(record, Date.now());\n return `${record.id} ${record.state} ${record.backend} ${active}`;\n}\n\nfunction formatExit(record: RunnerRecord): string {\n const exit = record.exit;\n if (!exit) return 'unknown';\n const detail = exit.code !== null ? ` code ${exit.code}` : exit.signal ? ` ${exit.signal}` : '';\n const attribution = exit.terminationReason ? ` [${exit.terminationReason}]` : '';\n const note = exit.stopReason ? ` (${exit.stopReason})` : '';\n return `${exit.reason}${detail}${attribution}${note}`;\n}\n\nfunction option(args: readonly string[], name: string): string | undefined {\n const index = args.indexOf(name);\n return index >= 0 ? args[index + 1] : undefined;\n}\n\nfunction positiveInteger(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nasync function follow(record: RunnerRecord, initialSize: number, dependencies: CliDependencies): Promise<number> {\n let offset = initialSize;\n return new Promise<number>((resolve) => {\n let stopped = false;\n let stateTimer: NodeJS.Timeout;\n const listener = (current: fs.Stats): void => {\n if (current.size < offset) offset = 0;\n if (current.size === offset) return;\n const length = current.size - offset;\n const buffer = Buffer.alloc(length);\n const descriptor = fs.openSync(record.logPath, 'r');\n try {\n const bytesRead = fs.readSync(descriptor, buffer, 0, length, offset);\n if (bytesRead > 0) dependencies.stdout(buffer.subarray(0, bytesRead).toString('utf8'));\n } finally {\n fs.closeSync(descriptor);\n }\n offset = current.size;\n };\n const stopFollowing = (): void => {\n if (stopped) return;\n stopped = true;\n try {\n listener(fs.statSync(record.logPath));\n } catch (error) {\n // A missing log file was already reported by the initial read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n dependencies.stderr(`Runner ${record.id} failed to flush its final log output: ${(error as Error).message}`);\n }\n }\n fs.unwatchFile(record.logPath, listener);\n clearInterval(stateTimer);\n process.off('SIGINT', stopFollowing);\n resolve(0);\n };\n process.once('SIGINT', stopFollowing);\n fs.watchFile(record.logPath, { interval: FOLLOW_INTERVAL_MS }, listener);\n stateTimer = setInterval(() => {\n void dependencies.registry.get(record.id).then((current) => {\n if (current?.state === COMPLETED_STATE) stopFollowing();\n });\n }, FOLLOW_INTERVAL_MS);\n });\n}\n\nasync function stopRecord(record: RunnerRecord, dependencies: CliDependencies, reason?: string): Promise<boolean> {\n await dependencies.registry.requestTermination(\n record.id,\n {\n reason: STOPPED_REASON,\n terminationReason: 'user_stop',\n ...(reason ? { stopReason: reason } : {}),\n },\n record.sessionId,\n );\n const stopped =\n MULTIPLEXER_BACKENDS.has(record.backend) && record.backendTarget\n ? await dependencies.rmuxBackend.stop(record.backendTarget, record.pid)\n : await dependencies.launcher.stop(record.pid);\n if (!stopped) await dependencies.registry.requestTermination(record.id, undefined, record.sessionId);\n return stopped;\n}\n\nfunction help(): string {\n return [\n 'Usage: doom-runner <command>',\n '',\n 'Commands:',\n ' list [--all]',\n ' status <id>',\n ' logs <id> [--lines N] [--follow]',\n ' stop <id> [--reason <text>]',\n ' stop-all [--reason <text>]',\n ' input <id> [--text <text>] [--enter]',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-4QSUsBxq.cjs","names":[],"sources":["../src/constants/compaction.ts","../src/services/compaction/index.ts"],"sourcesContent":["export const COMPACTION_RUNNERS_MARKER = 'Active background runners survived compaction:';\nexport const RUNNER_COMPACTION_MESSAGE = 'doom-runner-compaction';\n","import { COMPACTION_RUNNERS_MARKER } from '../../constants/compaction';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\n\nexport interface RunnerCompactionDependencies {\n getSessionId(): string | undefined | Promise<string | undefined>;\n listBySession(sessionId: string): Promise<RunnerRecord[]>;\n}\n\nexport function activeRunnerRecovery(active: readonly RunnerRecord[]): string {\n if (active.length === 0) return '';\n const runners = active.flatMap((record) => [\n `- ${record.name} (${record.id}), log: ${record.logPath}`,\n ` Check: doom-runner status ${record.id}`,\n ` Stream: doom-runner logs ${record.id} --follow`,\n ]);\n return [\n COMPACTION_RUNNERS_MARKER,\n ...runners,\n 'Use these CLI commands to inspect existing runners before launching another command.',\n ].join('\\n');\n}\n\nexport function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string {\n if (active.length === 0 || summary.includes(COMPACTION_RUNNERS_MARKER)) return summary;\n return [summary, '', activeRunnerRecovery(active)].join('\\n');\n}\n"],"mappings":";AAAA,MAAa,4BAA4B;;;ACQzC,SAAgB,qBAAqB,QAAyC;CAC5E,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,UAAU,OAAO,SAAS,WAAW;EACzC,KAAK,OAAO,KAAK,IAAI,OAAO,GAAG,UAAU,OAAO;EAChD,+BAA+B,OAAO;EACtC,8BAA8B,OAAO,GAAG;CAC1C,CAAC;CACD,OAAO;EACL;EACA,GAAG;EACH;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,6BAA6B,SAAiB,QAAyC;CACrG,IAAI,OAAO,WAAW,KAAK,QAAQ,SAAA,gDAAkC,GAAG,OAAO;CAC/E,OAAO;EAAC;EAAS;EAAI,qBAAqB,MAAM;CAAC,CAAC,CAAC,KAAK,IAAI;AAC9D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-DCw80VZj.mjs","names":[],"sources":["../src/constants/compaction.ts","../src/services/compaction/index.ts"],"sourcesContent":["export const COMPACTION_RUNNERS_MARKER = 'Active background runners survived compaction:';\nexport const RUNNER_COMPACTION_MESSAGE = 'doom-runner-compaction';\n","import { COMPACTION_RUNNERS_MARKER } from '../../constants/compaction';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\n\nexport interface RunnerCompactionDependencies {\n getSessionId(): string | undefined | Promise<string | undefined>;\n listBySession(sessionId: string): Promise<RunnerRecord[]>;\n}\n\nexport function activeRunnerRecovery(active: readonly RunnerRecord[]): string {\n if (active.length === 0) return '';\n const runners = active.flatMap((record) => [\n `- ${record.name} (${record.id}), log: ${record.logPath}`,\n ` Check: doom-runner status ${record.id}`,\n ` Stream: doom-runner logs ${record.id} --follow`,\n ]);\n return [\n COMPACTION_RUNNERS_MARKER,\n ...runners,\n 'Use these CLI commands to inspect existing runners before launching another command.',\n ].join('\\n');\n}\n\nexport function appendActiveRunnersToSummary(summary: string, active: readonly RunnerRecord[]): string {\n if (active.length === 0 || summary.includes(COMPACTION_RUNNERS_MARKER)) return summary;\n return [summary, '', activeRunnerRecovery(active)].join('\\n');\n}\n"],"mappings":";AAAA,MAAa,4BAA4B;;;ACQzC,SAAgB,qBAAqB,QAAyC;CAC5E,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,UAAU,OAAO,SAAS,WAAW;EACzC,KAAK,OAAO,KAAK,IAAI,OAAO,GAAG,UAAU,OAAO;EAChD,+BAA+B,OAAO;EACtC,8BAA8B,OAAO,GAAG;CAC1C,CAAC;CACD,OAAO;EACL;EACA,GAAG;EACH;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,6BAA6B,SAAiB,QAAyC;CACrG,IAAI,OAAO,WAAW,KAAK,QAAQ,SAAA,gDAAkC,GAAG,OAAO;CAC/E,OAAO;EAAC;EAAS;EAAI,qBAAqB,MAAM;CAAC,CAAC,CAAC,KAAK,IAAI;AAC9D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headless-BGYfIlNk.mjs","names":[],"sources":["../src/constants/headless.ts","../src/constants/runners.ts","../src/constants/runnersCommand.ts","../src/services/runnersCommand/index.ts","../src/services/headless/index.ts"],"sourcesContent":["export const MS_PER_SECOND = 1000;\n","export const COMMAND_NAME = 'runners';\nexport const COMMAND_DESCRIPTION =\n 'Open Runner Space: background processes started by bash. `/runners stop <id> [reason]` stops one, `/runners start [name=x] [cwd=y] [interactive=true] -- <command>` starts one.';\nexport const SERVER_COMMAND_DESCRIPTION = 'Start and stop supervised background runners without a terminal UI.';\n","export const RUNNERS_STOP_VERB = 'stop';\nexport const RUNNERS_START_VERB = 'start';\nexport const RUNNERS_COMMAND_SEPARATOR = '--';\nexport const NAME_KEY = 'name';\nexport const CWD_KEY = 'cwd';\nexport const INTERACTIVE_KEY = 'interactive';\n","import {\n CWD_KEY,\n INTERACTIVE_KEY,\n NAME_KEY,\n RUNNERS_COMMAND_SEPARATOR,\n RUNNERS_START_VERB,\n RUNNERS_STOP_VERB,\n} from '../../constants/runnersCommand';\n/** The `/runners` verb that stops one runner without opening Runner Space. */\n/** The `/runners` verb that starts one, for a client that cannot call the bash tool. */\n/**\n * Ends the options and begins the command.\n *\n * A command is arbitrary shell: it may hold `=`, and it may hold flags of its\n * own (`pnpm test -- --watch`). Only the first of these is the separator, so\n * everything the reader typed survives intact after it.\n */\n\nexport interface RunnersStartRequest {\n kind: 'start';\n /** The shell command to run; empty when the reader gave none. */\n command: string;\n cwd?: string;\n name?: string;\n interactive: boolean;\n}\n\nexport type RunnersCommandRequest =\n /** No arguments: open Runner Space. */\n | { kind: 'space' }\n /** `stop <id> [reason]`: stop one runner headlessly; id is empty when it was left out. */\n | { kind: 'stop'; id: string; reason?: string }\n /** `start [key=value ...] -- <command>`: start one headlessly. */\n | RunnersStartRequest;\n\n/** Splits on whitespace but keeps a double-quoted run together, so a path with spaces survives. */\nfunction tokenize(text: string): string[] {\n return text.match(/(?:\"[^\"]*\"|\\S)+/gu) ?? [];\n}\n\nfunction unquote(value: string): string {\n return value.length >= 2 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Parses `start`'s tail: `key=value` pairs up to the separator, then the\n * command verbatim.\n *\n * Without a separator the whole tail is the command, so typing\n * `/runners start pnpm build` does what it looks like. Options are read only\n * when a separator says where they end, which is what keeps a command such as\n * `FOO=bar pnpm test` from being mistaken for one.\n */\nfunction parseStart(tail: string): RunnersStartRequest {\n const tokens = tokenize(tail);\n const separator = tokens.indexOf(RUNNERS_COMMAND_SEPARATOR);\n if (separator === -1) return { kind: 'start', command: tail.trim(), interactive: false };\n\n const request: RunnersStartRequest = {\n kind: 'start',\n // Sliced from the raw text rather than rejoined from tokens: rejoining\n // would collapse the spacing the reader wrote.\n command: tail.slice(tail.indexOf(RUNNERS_COMMAND_SEPARATOR) + RUNNERS_COMMAND_SEPARATOR.length).trim(),\n interactive: false,\n };\n for (const token of tokens.slice(0, separator)) {\n const split = token.indexOf('=');\n if (split <= 0) continue;\n const value = unquote(token.slice(split + 1));\n const key = token.slice(0, split);\n if (key === NAME_KEY && value !== '') request.name = value;\n if (key === CWD_KEY && value !== '') request.cwd = value;\n if (key === INTERACTIVE_KEY) request.interactive = value === 'true';\n }\n return request;\n}\n\n/**\n * Parses `/runners` arguments. Anything other than a known verb opens Runner\n * Space, which is what the bare command has always done.\n */\nexport function parseRunnersCommand(args: string): RunnersCommandRequest {\n const trimmed = args.trim();\n const [verb = '', id = '', ...rest] = trimmed.split(/\\s+/u).filter(Boolean);\n if (verb === RUNNERS_START_VERB) return parseStart(trimmed.slice(verb.length).trim());\n if (verb !== RUNNERS_STOP_VERB) return { kind: 'space' };\n const reason = rest.join(' ').trim();\n return { kind: 'stop', id, ...(reason ? { reason } : {}) };\n}\n","import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/po-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerSettings } from '../../types/runnerSettings';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n settings?: RunnerSettings,\n environment: NodeJS.ProcessEnv = process.env,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n return formatRunResult(result, parseResultPragma(command), summarizeLog, settings, environment);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body:\n formatRunResult(result, {}, undefined, dependencies.settings, dependencies.environment).content[0]?.text ??\n 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend, {\n registry: dependencies.runnerRegistry,\n intent: {\n reason: 'stopped',\n terminationReason: 'user_stop',\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n });\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headless-CHUsBGhg.cjs","names":["BASH_TOOL_NAME","BASH_TOOL_LABEL","getBackgroundThresholdMs","BashParamsSchema","textResult","formatRunResult","parseResultPragma","stopRunnerProcess"],"sources":["../src/constants/headless.ts","../src/constants/runners.ts","../src/constants/runnersCommand.ts","../src/services/runnersCommand/index.ts","../src/services/headless/index.ts"],"sourcesContent":["export const MS_PER_SECOND = 1000;\n","export const COMMAND_NAME = 'runners';\nexport const COMMAND_DESCRIPTION =\n 'Open Runner Space: background processes started by bash. `/runners stop <id> [reason]` stops one, `/runners start [name=x] [cwd=y] [interactive=true] -- <command>` starts one.';\nexport const SERVER_COMMAND_DESCRIPTION = 'Start and stop supervised background runners without a terminal UI.';\n","export const RUNNERS_STOP_VERB = 'stop';\nexport const RUNNERS_START_VERB = 'start';\nexport const RUNNERS_COMMAND_SEPARATOR = '--';\nexport const NAME_KEY = 'name';\nexport const CWD_KEY = 'cwd';\nexport const INTERACTIVE_KEY = 'interactive';\n","import {\n CWD_KEY,\n INTERACTIVE_KEY,\n NAME_KEY,\n RUNNERS_COMMAND_SEPARATOR,\n RUNNERS_START_VERB,\n RUNNERS_STOP_VERB,\n} from '../../constants/runnersCommand';\n/** The `/runners` verb that stops one runner without opening Runner Space. */\n/** The `/runners` verb that starts one, for a client that cannot call the bash tool. */\n/**\n * Ends the options and begins the command.\n *\n * A command is arbitrary shell: it may hold `=`, and it may hold flags of its\n * own (`pnpm test -- --watch`). Only the first of these is the separator, so\n * everything the reader typed survives intact after it.\n */\n\nexport interface RunnersStartRequest {\n kind: 'start';\n /** The shell command to run; empty when the reader gave none. */\n command: string;\n cwd?: string;\n name?: string;\n interactive: boolean;\n}\n\nexport type RunnersCommandRequest =\n /** No arguments: open Runner Space. */\n | { kind: 'space' }\n /** `stop <id> [reason]`: stop one runner headlessly; id is empty when it was left out. */\n | { kind: 'stop'; id: string; reason?: string }\n /** `start [key=value ...] -- <command>`: start one headlessly. */\n | RunnersStartRequest;\n\n/** Splits on whitespace but keeps a double-quoted run together, so a path with spaces survives. */\nfunction tokenize(text: string): string[] {\n return text.match(/(?:\"[^\"]*\"|\\S)+/gu) ?? [];\n}\n\nfunction unquote(value: string): string {\n return value.length >= 2 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Parses `start`'s tail: `key=value` pairs up to the separator, then the\n * command verbatim.\n *\n * Without a separator the whole tail is the command, so typing\n * `/runners start pnpm build` does what it looks like. Options are read only\n * when a separator says where they end, which is what keeps a command such as\n * `FOO=bar pnpm test` from being mistaken for one.\n */\nfunction parseStart(tail: string): RunnersStartRequest {\n const tokens = tokenize(tail);\n const separator = tokens.indexOf(RUNNERS_COMMAND_SEPARATOR);\n if (separator === -1) return { kind: 'start', command: tail.trim(), interactive: false };\n\n const request: RunnersStartRequest = {\n kind: 'start',\n // Sliced from the raw text rather than rejoined from tokens: rejoining\n // would collapse the spacing the reader wrote.\n command: tail.slice(tail.indexOf(RUNNERS_COMMAND_SEPARATOR) + RUNNERS_COMMAND_SEPARATOR.length).trim(),\n interactive: false,\n };\n for (const token of tokens.slice(0, separator)) {\n const split = token.indexOf('=');\n if (split <= 0) continue;\n const value = unquote(token.slice(split + 1));\n const key = token.slice(0, split);\n if (key === NAME_KEY && value !== '') request.name = value;\n if (key === CWD_KEY && value !== '') request.cwd = value;\n if (key === INTERACTIVE_KEY) request.interactive = value === 'true';\n }\n return request;\n}\n\n/**\n * Parses `/runners` arguments. Anything other than a known verb opens Runner\n * Space, which is what the bare command has always done.\n */\nexport function parseRunnersCommand(args: string): RunnersCommandRequest {\n const trimmed = args.trim();\n const [verb = '', id = '', ...rest] = trimmed.split(/\\s+/u).filter(Boolean);\n if (verb === RUNNERS_START_VERB) return parseStart(trimmed.slice(verb.length).trim());\n if (verb !== RUNNERS_STOP_VERB) return { kind: 'space' };\n const reason = rest.join(' ').trim();\n return { kind: 'stop', id, ...(reason ? { reason } : {}) };\n}\n","import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/po-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerSettings } from '../../types/runnerSettings';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n settings?: RunnerSettings,\n environment: NodeJS.ProcessEnv = process.env,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n return formatRunResult(result, parseResultPragma(command), summarizeLog, settings, environment);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body:\n formatRunResult(result, {}, undefined, dependencies.settings, dependencies.environment).content[0]?.text ??\n 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend, {\n registry: dependencies.runnerRegistry,\n intent: {\n reason: 'stopped',\n terminationReason: 'user_stop',\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n });\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-BwB_jTI9.d.mts","names":[],"sources":["../src/services/headless/index.ts"],"mappings":";;;;;;;iBAoBgB,uBACd,gBAAgB,iBAChB,eAAe,eACf,WAAW,gBACX,cAAa,OAAO,aACnB,wBAAwB;iBAgDX,6BAA6B,cAAc,qBAAqB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-DBz0DxnL.d.mts","names":[],"sources":["../src/services/runnerRegistry/index.ts"],"mappings":";;;;;;KAoCY,sBAAsB,KAChC;;iBAKc,6BACd,MAAK,SAAS,sCACb;cA+CU,0BAA0B;mBAIlB;mBACA;mBACA;mBACA;mBANF;EAEjB,YACmB,OAAO,cACP,gBAAgB,iBAChB,UAAU,qBACV,aAAa,SAAS;EAGnC,SAAS,OAAO,sBAAsB,QAAQ;EAqD9C,QAAQ,QAAQ;EAShB,0BAA0B,QAAQ;EAQlC,cAAc,oBAAoB,QAAQ;EAK1C,kBAAkB,wBAAwB,QAAQ;EAKlD,QAAQ,qBAAqB,QAAQ;EAgBrC,IAAI,YAAY,qBAAqB,QAAQ;EAgB7C,aAAa,aAAa,QAAQ;EASlC,mBACJ,YACA,QAAQ,qCACR,qBACC,QAAQ;EAWL,SAAS,YAAY,SAAS,qBAAqB,qBAAqB,QAAQ;EAiChF,QAAQ,YAAY,qBAAqB;EAiBzC,aAAa;EAQnB,UAAU,sBAAsB;EAUhC;UAKQ;UAUA;UAOA;UAIA;UAIA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lifeline-BZaQA0rm.mjs","names":[],"sources":["../src/constants/lifeline.ts","../src/services/lifeline/index.ts"],"sourcesContent":["export const SOCKET_NAME = 'lifeline.sock';\nexport const SOCKET_PATH_LIMIT = 100;\nexport const SOCKET_HASH_LENGTH = 12;\nexport const SOCKET_MODE = 0o600;\n","import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { SOCKET_HASH_LENGTH, SOCKET_MODE, SOCKET_NAME, SOCKET_PATH_LIMIT } from '../../constants/lifeline';\nimport type { ILifeline } from '../../types/lifeline';\nimport type { IRunnerPaths } from '../runnerPaths/type';\n\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\n\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":";;;;;;AAAA,MAAa,cAAc;;;;ACY3B,IAAa,eAAb,MAA+C;CAMhB;CAL7B;CACA;;CAEA,8BAA+B,IAAI,IAAgB;CAEnD,YAAY,OAAsC;EAArB,KAAA,QAAA;CAAsB;CAEnD,MAAM,IAAI,WAAgD;EACxD,IAAI,KAAK,QAAQ,OAAO,KAAK;EAC7B,IAAI;GACF,KAAK,MAAM,kBAAkB,SAAS;GACtC,MAAM,SAAS,KAAK,WAAW,SAAS;GAExC,GAAG,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;GACjC,MAAM,SAAS,IAAI,cAAc,eAAe;IAE9C,WAAW,MAAM;IACjB,WAAW,OAAO;IAClB,KAAK,YAAY,IAAI,UAAU;IAC/B,WAAW,GAAG,eAAe,KAAK,YAAY,OAAO,UAAU,CAAC;GAClE,CAAC;GACD,MAAM,OAAO,QAAQ,MAAM;GAC3B,GAAG,UAAU,QAAA,GAAmB;GAEhC,OAAO,MAAM;GACb,KAAK,SAAS;GACd,KAAK,SAAS;GACd,OAAO;EACT,SAAS,OAAO;GAGd,QAAQ,YAAY,2CAA2C,OAAO,KAAK,GAAG;GAC9E;EACF;CACF;CAEA,OAA2B;EACzB,OAAO,KAAK;CACd;CAEA,UAAgB;EAGd,KAAK,MAAM,cAAc,KAAK,aAAa,WAAW,QAAQ;EAC9D,KAAK,YAAY,MAAM;EACvB,IAAI,KAAK,QAAQ;GACf,IAAI;IACF,KAAK,OAAO,MAAM;GACpB,SAAS,OAAO;IACd,QAAQ,YAAY,6CAA6C,OAAO,KAAK,GAAG;GAClF;GACA,KAAK,SAAS,KAAA;EAChB;EACA,IAAI,KAAK,QAAQ;GACf,IAAI;IACF,GAAG,OAAO,KAAK,QAAQ,EAAE,OAAO,KAAK,CAAC;GACxC,SAAS,OAAO;IACd,QAAQ,YAAY,8CAA8C,OAAO,KAAK,GAAG;GACnF;GACA,KAAK,SAAS,KAAA;EAChB;CACF;;CAGA,WAAmB,WAA2B;EAC5C,MAAM,YAAY,KAAK,KAAK,KAAK,MAAM,eAAe,SAAS,GAAG,WAAW;EAC7E,IAAI,OAAO,WAAW,SAAS,KAAA,KAAwB,OAAO;EAC9D,MAAM,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAA,EAAqB;EAC7F,OAAO,KAAK,KAAK,GAAG,OAAO,GAAG,eAAe,KAAK,MAAM;CAC1D;AACF;;AAGA,SAAS,OAAO,QAAoB,QAA+B;CACjE,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,cAAc;GAC1B,OAAO,eAAe,SAAS,MAAM;GAErC,OAAO,GAAG,UAAU,UAAiB,QAAQ,YAAY,gCAAgC,MAAM,SAAS,CAAC;GACzG,QAAQ;EACV,CAAC;CACH,CAAC;AACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lifeline-BuV_erib.cjs","names":["net","path","createHash","os"],"sources":["../src/constants/lifeline.ts","../src/services/lifeline/index.ts"],"sourcesContent":["export const SOCKET_NAME = 'lifeline.sock';\nexport const SOCKET_PATH_LIMIT = 100;\nexport const SOCKET_HASH_LENGTH = 12;\nexport const SOCKET_MODE = 0o600;\n","import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { SOCKET_HASH_LENGTH, SOCKET_MODE, SOCKET_NAME, SOCKET_PATH_LIMIT } from '../../constants/lifeline';\nimport type { ILifeline } from '../../types/lifeline';\nimport type { IRunnerPaths } from '../runnerPaths/type';\n\n/** Unix socket paths are capped at 104 bytes, so a long agent directory falls back to the temp dir. */\n\nexport class NodeLifeline implements ILifeline {\n private server: net.Server | undefined;\n private target: string | undefined;\n /** Closing a server stops it accepting; only the connections carry the death signal. */\n private readonly connections = new Set<net.Socket>();\n\n constructor(private readonly paths: IRunnerPaths) {}\n\n async arm(sessionId: string): Promise<string | undefined> {\n if (this.target) return this.target;\n try {\n this.paths.ensureDirectories(sessionId);\n const target = this.socketPath(sessionId);\n // A socket left behind by a SIGKILLed predecessor refuses connections, never accepts them.\n fs.rmSync(target, { force: true });\n const server = net.createServer((connection) => {\n // Runners need only the close edge, so their liveness and their data are equally irrelevant.\n connection.unref();\n connection.resume();\n this.connections.add(connection);\n connection.on('close', () => this.connections.delete(connection));\n });\n await listen(server, target);\n fs.chmodSync(target, SOCKET_MODE);\n // The lifeline must never be the reason pi stays alive.\n server.unref();\n this.server = server;\n this.target = target;\n return target;\n } catch (error) {\n // Runners stay unwatched rather than unlaunchable, so a session without a\n // lifeline degrades to running them unsupervised instead of failing.\n process.emitWarning(`Could not arm the doom-runner lifeline: ${String(error)}`);\n return undefined;\n }\n }\n\n path(): string | undefined {\n return this.target;\n }\n\n dispose(): void {\n // Dropping the connections is what a runner sees, and it is the whole point\n // of disposing: closing the server alone would leave every runner waiting.\n for (const connection of this.connections) connection.destroy();\n this.connections.clear();\n if (this.server) {\n try {\n this.server.close();\n } catch (error) {\n process.emitWarning(`Could not close the doom-runner lifeline: ${String(error)}`);\n }\n this.server = undefined;\n }\n if (this.target) {\n try {\n fs.rmSync(this.target, { force: true });\n } catch (error) {\n process.emitWarning(`Could not remove the doom-runner lifeline: ${String(error)}`);\n }\n this.target = undefined;\n }\n }\n\n /** Keeps the socket beside the session it belongs to unless that path is too long to bind. */\n private socketPath(sessionId: string): string {\n const preferred = path.join(this.paths.stateDirectory(sessionId), SOCKET_NAME);\n if (Buffer.byteLength(preferred) <= SOCKET_PATH_LIMIT) return preferred;\n const hash = createHash('sha256').update(preferred).digest('hex').slice(0, SOCKET_HASH_LENGTH);\n return path.join(os.tmpdir(), `doom-runner-${hash}.sock`);\n }\n}\n\n/** Binding must complete before the first runner launches, so arming waits for it. */\nfunction listen(server: net.Server, target: string): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(target, () => {\n server.removeListener('error', reject);\n // Later errors would otherwise reach the process as unhandled.\n server.on('error', (error: Error) => process.emitWarning(`doom-runner lifeline failed: ${error.message}`));\n resolve();\n });\n });\n}\n"],"mappings":";;;;;;;;;;;AAAA,MAAa,cAAc;;;;ACY3B,IAAa,eAAb,MAA+C;CAMhB;CAL7B;CACA;;CAEA,8BAA+B,IAAI,IAAgB;CAEnD,YAAY,OAAsC;EAArB,KAAA,QAAA;CAAsB;CAEnD,MAAM,IAAI,WAAgD;EACxD,IAAI,KAAK,QAAQ,OAAO,KAAK;EAC7B,IAAI;GACF,KAAK,MAAM,kBAAkB,SAAS;GACtC,MAAM,SAAS,KAAK,WAAW,SAAS;GAExC,QAAA,QAAG,OAAO,QAAQ,EAAE,OAAO,KAAK,CAAC;GACjC,MAAM,SAASA,SAAAA,QAAI,cAAc,eAAe;IAE9C,WAAW,MAAM;IACjB,WAAW,OAAO;IAClB,KAAK,YAAY,IAAI,UAAU;IAC/B,WAAW,GAAG,eAAe,KAAK,YAAY,OAAO,UAAU,CAAC;GAClE,CAAC;GACD,MAAM,OAAO,QAAQ,MAAM;GAC3B,QAAA,QAAG,UAAU,QAAA,GAAmB;GAEhC,OAAO,MAAM;GACb,KAAK,SAAS;GACd,KAAK,SAAS;GACd,OAAO;EACT,SAAS,OAAO;GAGd,QAAQ,YAAY,2CAA2C,OAAO,KAAK,GAAG;GAC9E;EACF;CACF;CAEA,OAA2B;EACzB,OAAO,KAAK;CACd;CAEA,UAAgB;EAGd,KAAK,MAAM,cAAc,KAAK,aAAa,WAAW,QAAQ;EAC9D,KAAK,YAAY,MAAM;EACvB,IAAI,KAAK,QAAQ;GACf,IAAI;IACF,KAAK,OAAO,MAAM;GACpB,SAAS,OAAO;IACd,QAAQ,YAAY,6CAA6C,OAAO,KAAK,GAAG;GAClF;GACA,KAAK,SAAS,KAAA;EAChB;EACA,IAAI,KAAK,QAAQ;GACf,IAAI;IACF,QAAA,QAAG,OAAO,KAAK,QAAQ,EAAE,OAAO,KAAK,CAAC;GACxC,SAAS,OAAO;IACd,QAAQ,YAAY,8CAA8C,OAAO,KAAK,GAAG;GACnF;GACA,KAAK,SAAS,KAAA;EAChB;CACF;;CAGA,WAAmB,WAA2B;EAC5C,MAAM,YAAYC,UAAAA,QAAK,KAAK,KAAK,MAAM,eAAe,SAAS,GAAG,WAAW;EAC7E,IAAI,OAAO,WAAW,SAAS,KAAA,KAAwB,OAAO;EAC9D,MAAM,QAAA,GAAOC,YAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAA,EAAqB;EAC7F,OAAOD,UAAAA,QAAK,KAAKE,QAAAA,QAAG,OAAO,GAAG,eAAe,KAAK,MAAM;CAC1D;AACF;;AAGA,SAAS,OAAO,QAAoB,QAA+B;CACjE,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,cAAc;GAC1B,OAAO,eAAe,SAAS,MAAM;GAErC,OAAO,GAAG,UAAU,UAAiB,QAAQ,YAAY,gCAAgC,MAAM,SAAS,CAAC;GACzG,QAAQ;EACV,CAAC;CACH,CAAC;AACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ptySpawner-DEi1KOnd.mjs","names":[],"sources":["../src/services/ptySpawner/index.ts"],"sourcesContent":["import type { IPtySpawner, PtyProcess, PtySpawnRequest } from '../../types/ptySpawner';\n\nconst RMUX_REQUIRED_ERROR =\n 'The node-pty fallback has been removed. Interactive commands require a supported RMUX runtime.';\n\n/**\n * Compatibility stub for the published NodePtySpawner subpath.\n *\n * Doom Runner no longer starts interactive commands through node-pty. The class\n * remains exportable so existing imports fail at launch with an actionable error.\n */\nexport class NodePtySpawner implements IPtySpawner {\n async spawn(_request: PtySpawnRequest): Promise<PtyProcess> {\n throw new Error(RMUX_REQUIRED_ERROR);\n }\n}\n"],"mappings":";AAEA,MAAM,sBACJ;;;;;;;AAQF,IAAa,iBAAb,MAAmD;CACjD,MAAM,MAAM,UAAgD;EAC1D,MAAM,IAAI,MAAM,mBAAmB;CACrC;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ptySpawner-ijw0g5_T.cjs","names":[],"sources":["../src/services/ptySpawner/index.ts"],"sourcesContent":["import type { IPtySpawner, PtyProcess, PtySpawnRequest } from '../../types/ptySpawner';\n\nconst RMUX_REQUIRED_ERROR =\n 'The node-pty fallback has been removed. Interactive commands require a supported RMUX runtime.';\n\n/**\n * Compatibility stub for the published NodePtySpawner subpath.\n *\n * Doom Runner no longer starts interactive commands through node-pty. The class\n * remains exportable so existing imports fail at launch with an actionable error.\n */\nexport class NodePtySpawner implements IPtySpawner {\n async spawn(_request: PtySpawnRequest): Promise<PtyProcess> {\n throw new Error(RMUX_REQUIRED_ERROR);\n }\n}\n"],"mappings":";AAEA,MAAM,sBACJ;;;;;;;AAQF,IAAa,iBAAb,MAAmD;CACjD,MAAM,MAAM,UAAgD;EAC1D,MAAM,IAAI,MAAM,mBAAmB;CACrC;AACF"}
|