@agimon-ai/doompi-runner 0.0.1-alpha.32 → 0.0.1-alpha.34
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/README.md +1 -1
- package/dist/src/adapters/pi/extension.cjs +1 -1
- package/dist/src/adapters/pi/extension.cjs.map +1 -1
- package/dist/src/adapters/pi/extension.mjs +1 -1
- package/dist/src/adapters/pi/extension.mjs.map +1 -1
- package/dist/src/tui/runnerSpace.cjs.map +1 -1
- package/dist/src/tui/runnerSpace.mjs.map +1 -1
- package/package.json +12 -12
- package/skills/doom-runner/SKILL.md +1 -1
- package/src/prompts/doompi-use-runner/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ also returns bounded raw output with a warning and does not change the command e
|
|
|
59
59
|
|
|
60
60
|
## Inspect and control runs
|
|
61
61
|
|
|
62
|
-
Use `/runners` or `SPC r
|
|
62
|
+
Use `/runners` or `SPC r l` in the TUI. The `doom-runner` CLI supports:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
doom-runner list
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
const e=require("../../services/runs/compaction.cjs"),t=require("../../types/config.cjs"),n=require("../../container/index.cjs"),r=require("../../services/runs/reconcile.cjs"),i=require("../../commands/bash/bashTool.cjs"),a=require("../../tui/format.cjs"),o=require("../../tui/runnerSpace.cjs"),s=require("../RunnerSettings/RunnerSettingsLoader.cjs");let c=require("@agimon-ai/doompi-extension-contracts/child-process"),l=require("@agimon-ai/doompi-extension-contracts/cordis-host"),u=require("@agimon-ai/doompi-extension-contracts/help"),d=require("@agimon-ai/doompi-extension-contracts/readiness"),f=require("@agimon-ai/doompi-extension-contracts/ui-hub"),p=require("@agimon-ai/doompi-telemetry");const m=`@agimon-ai/doompi-runner`,ee=`runners`,h=`completed`,te=`stopped`,g=`doom-runner-runners`;function ne(e){try{let n=e.isProjectTrusted?.()??!1,{settings:r,issues:i}=new s.RunnerSettingsLoader().load(e.cwd,n);t.setRunnerSettings(r);for(let e of i)process.emitWarning(`${s.RUNNER_SETTINGS_FILE}: ${e}`)}catch(e){process.emitWarning(`Could not read ${s.RUNNER_SETTINGS_FILE}: ${String(e)}`),t.setRunnerSettings({})}}function _(s,l){let u=n.createRunnerContainer(),_=u.runnerRegistry,v=async()=>{try{_.close()}catch(e){process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(e)}`)}};s.effect(()=>()=>v(),`${m}/runtime`);let y=u.launcher,b=u.rmuxBackend,re=u.logReader,x=u.ptyHost,ie=u.bashRunService,S=u.paths,C=u.processControl,w=u.lifeline,T=!0,E=0,D,O,k=[],A,j,M,N=!1,P=!1,F,I,L,R,z,B=Promise.resolve(),V,H=!1,U=!1,W,G=new Set,K=new Set,q=new Set,J=e=>(q.add(e),e.then(()=>q.delete(e),()=>q.delete(e)),e),Y=async()=>{if(await B,!T||!N||!D)throw Error(`doom-runner requires an active Pi session`)},ae={run:async e=>(await Y(),J(ie.run(e)))},X=(e,t=D)=>T&&!P&&e===E&&t===D,Z=e=>(M??=(0,p.createDoomTelemetry)({serviceName:`doom-runner`,packageName:`@agimon-ai/doompi-runner`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),M),oe=async e=>{let t=D,n=E;if(!t||!X(n,t))return;let i=await _.list();if(!X(n,t))return;let o=i;if(e){let e=await r.reconcileActiveRunners({registry:_,launcher:y,rmuxBackend:b,processControl:C,currentHostPid:process.pid,startup:!1,active:i});for(let t of e.errors)process.emitWarning(t);if(e.reclaimed.length>0){let t=new Set(e.reclaimed);o=i.filter(e=>!t.has(e.id))}}if(!X(n,t))return;k=o.filter(e=>e.sessionId===t);let s=(O?o.filter(e=>(e.rootSessionId??e.sessionId)===O):[]).length;W!==s&&(W=s,j?.update(a.formatRunnerFooterContribution(s)),A?.hasUI&&A.ui.setStatus(g,a.formatRunnerStatus(s)));let c=[...K],u=await Promise.all(c.map(e=>_.get(e,t)));if(X(n,t))for(let[e,n]of u.entries()){let r=c[e];if(!r)continue;if(!n||n.sessionId!==t){K.delete(r);continue}if(n.state!==h||(K.delete(n.id),G.has(n.id)))continue;G.add(n.id);let i=n.exit?.reason??h,a=n.exit?.code===null||n.exit?.code===void 0?``:`, exit code ${n.exit.code}`,o=[`Background runner ${n.name} exited: ${i}${a}.`,`Runner ID: ${n.id}`,`Log: ${n.logPath}`,`Inspect: doom-runner logs ${n.id}`].join(`
|
|
2
|
-
`);l.sendMessage({customType:`doom-runner-finished`,content:o,display:!0},{triggerTurn:!0,deliverAs:`steer`}),A&&J(Z(A).recordEvent(`doom_runner.process_finished`,{outcome:`completed`,"runner.exit_code":n.exit?.code??0,"runner.backend":n.backend,...Number.isFinite(Date.parse(n.startedAt))?{duration_ms:Math.max(0,Date.now()-Date.parse(n.startedAt))}:{}}))}},Q=(e=!0)=>{if(!D||!N||P)return Promise.resolve();if(F)return H=!0,U||=e,F;let t=(async()=>{let t=e;do H=!1,U=!1,await oe(t),t=U;while(H&&!P)})().finally(()=>{F===t&&(F=void 0)});return F=t,t},se=e=>{P||(A&&J(Z(A).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},ce=e=>{Q(e).catch(se)},le=()=>{L||I||P||(L=setImmediate(()=>{if(L=void 0,P)return;let e=(async()=>{let e=S.sweepHistoryAsync?await S.sweepHistoryAsync(t.getLogTtlMs()):S.sweepHistory(t.getLogTtlMs());for(let t of e.errors)process.emitWarning(t)})().catch(e=>process.emitWarning(`Could not sweep doom-runner history: ${String(e)}`)).finally(()=>{I===e&&(I=void 0)});I=e}),L.unref?.())},$=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-runner cleanup could not ${e}: ${String(t)}`)}};v=async()=>{if(P)return;T=!1,P=!0,E+=1,N=!1,R&&clearInterval(R),R=void 0,L&&clearImmediate(L),L=void 0;let e=z;z=void 0,e&&await $(`unsubscribe from the runner registry`,e);let t=V;V=void 0,t&&await $(`cancel standalone readiness work`,()=>t.dispose()),await Promise.allSettled(q);let n=F;n&&await $(`settle the runner refresh`,()=>n);let i=I;i&&await $(`settle runner history cleanup`,()=>i),await $(`dispose runner PTYs`,()=>x.disposeAll());let a=[];if(D)try{a=await _.listBySession(D)}catch(e){process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(e)}`)}await Promise.all(a.map(async e=>{try{let t=await r.stopRunnerProcess(e,y,b);if(!t&&C.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await _.complete(e.id,{reason:te,code:null,signal:t?`SIGTERM`:null,stopReason:`session ended`},e.sessionId)}catch(t){process.emitWarning(`Could not clean up runner ${e.id}: ${String(t)}`)}}));let o=A,s=M;M=void 0,A=void 0,D=void 0,O=void 0,k=[],G.clear(),K.clear(),await Promise.all([$(`dispose the runner lifeline`,()=>w.dispose()),...o?.hasUI?[$(`clear the runner status`,()=>o.ui.setStatus(g,void 0))]:[]]),await $(`close the runner registry`,()=>_.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},s.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=(0,f.requireDoomUiHub)(e).registerFooter({source:m,id:`runner-count`,order:10});return j=t,()=>{t.dispose(),j===t&&(j=void 0)}}),z=_.subscribe(()=>ce(!1)),R=setInterval(()=>ce(!0),500),R.unref?.(),i.registerBashTool(l,{bashRunService:ae,getSessionId:async()=>{if(await Y(),!D)throw Error(`doom-runner requires an active Pi session`);return D},onRunnerStarted:e=>{!T||!N||(K.add(e),ce(!1))}}),e.registerRunnerCompactionRecovery(l,{getSessionId:async()=>{try{return await Y(),D}catch{return}},listBySession:async e=>{try{await Y()}catch{return[]}return T?_.listBySession(e):[]}}),l.registerCommand(ee,{description:`Open Runner Space: background processes started by bash`,handler:async(e,t)=>{if(!T)return;let n=E,r=D;if(r){if(!t.hasUI){t.ui.notify(`/runners requires interactive mode`,`error`);return}await Y(),await Q(),!(!r||!X(n,r))&&await o.openRunnerSpace(t,{getRunners:()=>X(n,r)?k:[],getPtyRun:e=>X(n,r)?b.get(e)??x.get(e):void 0,readLog:e=>X(n,r)?re.read(e,{lines:1e3}).text:``,stopRunner:async(e,t)=>{if(!X(n,r))return;let i=k.find(t=>t.id===e);i&&(i.backend===`rmux`&&i.backendTarget?await b.stop(i.backendTarget,i.pid):await y.stop(i.pid),X(n,r)&&(await _.complete(i.id,{reason:te,code:null,signal:null,stopReason:t},i.sessionId),X(n,r)&&await Q()))}})}}}),s.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=(0,f.requireDoomUiHub)(e).registerLeader({source:m,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,order:67},{key:`
|
|
2
|
+
`);l.sendMessage({customType:`doom-runner-finished`,content:o,display:!0},{triggerTurn:!0,deliverAs:`steer`}),A&&J(Z(A).recordEvent(`doom_runner.process_finished`,{outcome:`completed`,"runner.exit_code":n.exit?.code??0,"runner.backend":n.backend,...Number.isFinite(Date.parse(n.startedAt))?{duration_ms:Math.max(0,Date.now()-Date.parse(n.startedAt))}:{}}))}},Q=(e=!0)=>{if(!D||!N||P)return Promise.resolve();if(F)return H=!0,U||=e,F;let t=(async()=>{let t=e;do H=!1,U=!1,await oe(t),t=U;while(H&&!P)})().finally(()=>{F===t&&(F=void 0)});return F=t,t},se=e=>{P||(A&&J(Z(A).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},ce=e=>{Q(e).catch(se)},le=()=>{L||I||P||(L=setImmediate(()=>{if(L=void 0,P)return;let e=(async()=>{let e=S.sweepHistoryAsync?await S.sweepHistoryAsync(t.getLogTtlMs()):S.sweepHistory(t.getLogTtlMs());for(let t of e.errors)process.emitWarning(t)})().catch(e=>process.emitWarning(`Could not sweep doom-runner history: ${String(e)}`)).finally(()=>{I===e&&(I=void 0)});I=e}),L.unref?.())},$=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-runner cleanup could not ${e}: ${String(t)}`)}};v=async()=>{if(P)return;T=!1,P=!0,E+=1,N=!1,R&&clearInterval(R),R=void 0,L&&clearImmediate(L),L=void 0;let e=z;z=void 0,e&&await $(`unsubscribe from the runner registry`,e);let t=V;V=void 0,t&&await $(`cancel standalone readiness work`,()=>t.dispose()),await Promise.allSettled(q);let n=F;n&&await $(`settle the runner refresh`,()=>n);let i=I;i&&await $(`settle runner history cleanup`,()=>i),await $(`dispose runner PTYs`,()=>x.disposeAll());let a=[];if(D)try{a=await _.listBySession(D)}catch(e){process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(e)}`)}await Promise.all(a.map(async e=>{try{let t=await r.stopRunnerProcess(e,y,b);if(!t&&C.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await _.complete(e.id,{reason:te,code:null,signal:t?`SIGTERM`:null,stopReason:`session ended`},e.sessionId)}catch(t){process.emitWarning(`Could not clean up runner ${e.id}: ${String(t)}`)}}));let o=A,s=M;M=void 0,A=void 0,D=void 0,O=void 0,k=[],G.clear(),K.clear(),await Promise.all([$(`dispose the runner lifeline`,()=>w.dispose()),...o?.hasUI?[$(`clear the runner status`,()=>o.ui.setStatus(g,void 0))]:[]]),await $(`close the runner registry`,()=>_.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},s.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=(0,f.requireDoomUiHub)(e).registerFooter({source:m,id:`runner-count`,order:10});return j=t,()=>{t.dispose(),j===t&&(j=void 0)}}),z=_.subscribe(()=>ce(!1)),R=setInterval(()=>ce(!0),500),R.unref?.(),i.registerBashTool(l,{bashRunService:ae,getSessionId:async()=>{if(await Y(),!D)throw Error(`doom-runner requires an active Pi session`);return D},onRunnerStarted:e=>{!T||!N||(K.add(e),ce(!1))}}),e.registerRunnerCompactionRecovery(l,{getSessionId:async()=>{try{return await Y(),D}catch{return}},listBySession:async e=>{try{await Y()}catch{return[]}return T?_.listBySession(e):[]}}),l.registerCommand(ee,{description:`Open Runner Space: background processes started by bash`,handler:async(e,t)=>{if(!T)return;let n=E,r=D;if(r){if(!t.hasUI){t.ui.notify(`/runners requires interactive mode`,`error`);return}await Y(),await Q(),!(!r||!X(n,r))&&await o.openRunnerSpace(t,{getRunners:()=>X(n,r)?k:[],getPtyRun:e=>X(n,r)?b.get(e)??x.get(e):void 0,readLog:e=>X(n,r)?re.read(e,{lines:1e3}).text:``,stopRunner:async(e,t)=>{if(!X(n,r))return;let i=k.find(t=>t.id===e);i&&(i.backend===`rmux`&&i.backendTarget?await b.stop(i.backendTarget,i.pid):await y.stop(i.pid),X(n,r)&&(await _.complete(i.id,{reason:te,code:null,signal:null,stopReason:t},i.sessionId),X(n,r)&&await Q()))}})}}}),s.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=(0,f.requireDoomUiHub)(e).registerLeader({source:m,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,detail:`background processes`,order:67},{key:`l`,label:`list`,detail:`runners in this session`}],command:{name:ee}}]});return()=>t.dispose()}),l.on(`session_start`,(e,t)=>{if(!T)return;let n=t;ne(n);let i=n.sessionManager.getSessionId(),a=D,o=A,l=++E;D=i,O=(0,c.resolveRootSessionId)(i),A=n,N=!1,W=void 0,G.clear(),K.clear();let u=async e=>{let t=()=>!e.aborted&&X(l,i);if(!t())return[];if(a&&a!==i){await $(`dispose the previous session PTYs`,()=>x.disposeAll()),await $(`dispose the previous session lifeline`,()=>w.dispose()),o?.hasUI&&await $(`clear the previous session status`,()=>o.ui.setStatus(g,void 0));let e=M;if(M=void 0,e&&await $(`stop the previous session telemetry`,()=>e.shutdown()),!t())return[]}let s=Z(n);if(await s.recordEvent(`doom_runner.session_started`,{outcome:`started`}),!t()||(S.setSessionId(i),await w.arm(i),!t()))return[];let c=await _.listAll(i);if(!t())return[];for(let e of c)e.state===h?G.add(e.id):e.promoted&&K.add(e.id);let u=await r.cleanupLegacyRunnerStore({registry:_,launcher:y,rmuxBackend:b,processControl:C,currentHostPid:process.pid,paths:S});if(!t())return[];let d=await r.reconcileActiveRunners({registry:_,launcher:y,rmuxBackend:b,processControl:C,currentHostPid:process.pid,startup:!0});if(!t()||(N=!0,await Q(!1),!t()))return[];le();let f=[...u.errors,...d.errors],p=u.reclaimed.length+d.reclaimed.length;return p>0&&n.hasUI&&n.ui.notify(`Reclaimed ${p} stale runner record(s)`,`warning`),await s.recordEvent(`doom_runner.reconciled`,{"runner.reclaimed_count":p,"runner.error_count":f.length,outcome:f.length===0?`completed`:`degraded`}),f},f=B.catch(()=>void 0),p=(async()=>{await f,X(l,i)&&await((0,d.readDoomReadinessCoordinator)(s)??(V??=(0,d.createDoomReadinessCoordinator)({notify:e=>{process.emitWarning(`${e.packageId} initialization ${e.state}: ${e.error?.message??e.diagnostics.join(`; `)}`)}}))).start(m,`${i}:${l}`,async e=>({value:void 0,diagnostics:await u(e)})).wait()})();B=p,J(p).catch(()=>void 0)})}async function v(e){let t=await(0,l.connectDoomCordisHost)(e,m),n=t.root.plugin(y,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function y(e,t){e.inject([u.DOOM_HELP_SERVICE],e=>{let t=(0,u.requireDoomHelpService)(e).register({source:m,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-use-runner`,description:`Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.`}]});return()=>t.dispose()}),_(e,t.pi)}exports.default=v,exports.runnerExtension=v,exports.installRunnerRuntime=_;
|
|
3
3
|
//# sourceMappingURL=extension.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.cjs","names":["RunnerSettingsLoader","RUNNER_SETTINGS_FILE","createRunnerContainer","createDoomTelemetry","reconcileActiveRunners","formatRunnerFooterContribution","formatRunnerStatus","getLogTtlMs","stopRunnerProcess","DOOM_UI_HUB_SERVICE","requireDoomUiHub","openRunnerSpace","resolveRootSessionId","cleanupLegacyRunnerStore","readDoomReadinessCoordinator","createDoomReadinessCoordinator","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService"],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport type { DoomFooterContributionHandle } from '@agimon-ai/doompi-extension-contracts/footer';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerBashTool } from '../../commands/bash/bashTool.ts';\nimport { createRunnerContainer } from '../../container/index.ts';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport { registerRunnerCompactionRecovery } from '../../services/runs/compaction.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from '../../services/runs/reconcile.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from '../../tui/format.ts';\nimport { openRunnerSpace } from '../../tui/runnerSpace.ts';\nimport { getLogTtlMs } from '../../types/config.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nimport { RUNNER_SETTINGS_FILE, RunnerSettingsLoader } from '../RunnerSettings/RunnerSettingsLoader';\nimport { setRunnerSettings } from '../../types/config.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * Project-local extension config, read the way Pi documents it: one JSON file\n * per extension under the config directory, and only for a trusted project.\n */\nfunction loadRunnerSettings(context: ExtensionContext): void {\n try {\n const trusted = context.isProjectTrusted?.() ?? false;\n const { settings, issues } = new RunnerSettingsLoader().load(context.cwd, trusted);\n setRunnerSettings(settings);\n // A rejected key is surfaced rather than dropped: silence reads as a\n // setting that simply does not work.\n for (const issue of issues) process.emitWarning(`${RUNNER_SETTINGS_FILE}: ${issue}`);\n } catch (error) {\n process.emitWarning(`Could not read ${RUNNER_SETTINGS_FILE}: ${String(error)}`);\n setRunnerSettings({});\n }\n}\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function installRunnerRuntime(cordis: Context, pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.runnerRegistry;\n let disposeRuntime = async (): Promise<void> => {\n try {\n registry.close();\n } catch (error) {\n process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(error)}`);\n }\n };\n cordis.effect(() => () => disposeRuntime(), `${LEADER_SOURCE}/runtime`);\n\n const launcher = container.launcher;\n const rmuxBackend = container.rmuxBackend;\n const logReader = container.logReader;\n const ptyHost = container.ptyHost;\n const bashRunService = container.bashRunService;\n const paths = container.paths;\n const processControl = container.processControl;\n const lifeline = container.lifeline;\n\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let footerContribution: DoomFooterContributionHandle | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let refreshInFlight: Promise<void> | undefined;\n let historySweepInFlight: Promise<void> | undefined;\n let historySweepTimer: ReturnType<typeof setImmediate> | undefined;\n let statusPoll: ReturnType<typeof setInterval> | undefined;\n let unsubscribeRegistry: (() => void) | undefined;\n let sessionInitialization: Promise<void> = Promise.resolve();\n let fallbackReadiness: DoomReadinessCoordinator | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const pendingOperations = new Set<Promise<unknown>>();\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const waitForSessionReadiness = async (): Promise<void> => {\n await sessionInitialization;\n if (!active || !sessionReady || !sessionId) {\n throw new Error('doom-runner requires an active Pi session');\n }\n };\n const trackedBashRunService: IBashRunService = {\n run: async (request) => {\n await waitForSessionReadiness();\n return trackOperation(bashRunService.run(request));\n },\n };\n const isCurrent = (generation: number, expectedSessionId = sessionId): boolean =>\n active && !disposed && generation === sessionGeneration && expectedSessionId === sessionId;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n const generation = sessionGeneration;\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n\n const activeRecords = await registry.list();\n if (!isCurrent(generation, activeSessionId)) return;\n let visibleActive = activeRecords;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active: activeRecords,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = activeRecords.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (!isCurrent(generation, activeSessionId)) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution?.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (!isCurrent(generation, activeSessionId)) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void trackOperation(\n getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n }),\n );\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) {\n void trackOperation(getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error));\n }\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const scheduleHistorySweep = (): void => {\n if (historySweepTimer || historySweepInFlight || disposed) return;\n historySweepTimer = setImmediate(() => {\n historySweepTimer = undefined;\n if (disposed) return;\n const execution = (async () => {\n const sweep = paths.sweepHistoryAsync\n ? await paths.sweepHistoryAsync(getLogTtlMs())\n : paths.sweepHistory(getLogTtlMs());\n for (const error of sweep.errors) process.emitWarning(error);\n })()\n .catch((error) => process.emitWarning(`Could not sweep doom-runner history: ${String(error)}`))\n .finally(() => {\n if (historySweepInFlight === execution) historySweepInFlight = undefined;\n });\n historySweepInFlight = execution;\n });\n historySweepTimer.unref?.();\n };\n\n const runCleanup = async (label: string, cleanup: () => void | Promise<void>): Promise<void> => {\n try {\n await cleanup();\n } catch (error) {\n process.emitWarning(`Doom-runner cleanup could not ${label}: ${String(error)}`);\n }\n };\n\n const shutdownRuntime = async (): Promise<void> => {\n if (disposed) return;\n active = false;\n disposed = true;\n sessionGeneration += 1;\n sessionReady = false;\n if (statusPoll) clearInterval(statusPoll);\n statusPoll = undefined;\n if (historySweepTimer) clearImmediate(historySweepTimer);\n historySweepTimer = undefined;\n const unsubscribe = unsubscribeRegistry;\n unsubscribeRegistry = undefined;\n if (unsubscribe) await runCleanup('unsubscribe from the runner registry', unsubscribe);\n\n const ownedReadiness = fallbackReadiness;\n fallbackReadiness = undefined;\n if (ownedReadiness) await runCleanup('cancel standalone readiness work', () => ownedReadiness.dispose());\n await Promise.allSettled(pendingOperations);\n const activeRefresh = refreshInFlight;\n if (activeRefresh) await runCleanup('settle the runner refresh', () => activeRefresh);\n const activeHistorySweep = historySweepInFlight;\n if (activeHistorySweep) await runCleanup('settle runner history cleanup', () => activeHistorySweep);\n\n await runCleanup('dispose runner PTYs', () => ptyHost.disposeAll());\n\n let owned: RunnerRecord[] = [];\n if (sessionId) {\n try {\n owned = await registry.listBySession(sessionId);\n } catch (error) {\n process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(error)}`);\n }\n }\n await Promise.all(\n owned.map(async (record) => {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n return;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }),\n );\n\n const context = sessionContext;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n sessionContext = undefined;\n sessionId = undefined;\n rootSessionId = undefined;\n runners = [];\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n await Promise.all([\n runCleanup('dispose the runner lifeline', () => lifeline.dispose()),\n ...(context?.hasUI\n ? [runCleanup('clear the runner status', () => context.ui.setStatus(RUNNER_STATUS_KEY, undefined))]\n : []),\n ]);\n await runCleanup('close the runner registry', () => registry.close());\n if (ownedTelemetry) {\n await runCleanup('record the runner session finish', () =>\n ownedTelemetry.recordEvent('doom_runner.session_finished', { outcome: 'stopped' }),\n );\n await runCleanup('stop runner telemetry', () => ownedTelemetry.shutdown());\n }\n };\n\n disposeRuntime = shutdownRuntime;\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerFooter({\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n footerContribution = contribution;\n return () => {\n contribution.dispose();\n if (footerContribution === contribution) footerContribution = undefined;\n };\n });\n unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService: trackedBashRunService,\n getSessionId: async () => {\n await waitForSessionReadiness();\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n if (!active || !sessionReady) return;\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: async () => {\n try {\n await waitForSessionReadiness();\n return sessionId;\n } catch {\n return undefined;\n }\n },\n listBySession: async (activeSessionId) => {\n try {\n await waitForSessionReadiness();\n } catch {\n return [];\n }\n return active ? registry.listBySession(activeSessionId) : [];\n },\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n const activeSessionId = sessionId;\n if (!activeSessionId) return;\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await waitForSessionReadiness();\n await refresh();\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n await openRunnerSpace(ctx, {\n getRunners: () => (isCurrent(generation, activeSessionId) ? runners : []),\n getPtyRun: (name) =>\n isCurrent(generation, activeSessionId) ? (rmuxBackend.get(name) ?? ptyHost.get(name)) : undefined,\n readLog: (logPath) =>\n isCurrent(generation, activeSessionId) ? logReader.read(logPath, { lines: 1_000 }).text : '',\n stopRunner: async (id, reason) => {\n if (!isCurrent(generation, activeSessionId)) return;\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n if (!isCurrent(generation, activeSessionId)) return;\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n if (isCurrent(generation, activeSessionId)) await refresh();\n },\n });\n },\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', order: LEADER_GROUP_ORDER },\n { key: 'r', label: 'open', detail: 'background processes' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n if (!active) return;\n const context = ctx as ExtensionContext;\n loadRunnerSettings(context);\n const activeSessionId = context.sessionManager.getSessionId();\n const previousSessionId = sessionId;\n const previousContext = sessionContext;\n const generation = ++sessionGeneration;\n sessionId = activeSessionId;\n rootSessionId = resolveRootSessionId(activeSessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n const initializeSession = async (signal: AbortSignal): Promise<readonly string[]> => {\n const stillCurrent = (): boolean => !signal.aborted && isCurrent(generation, activeSessionId);\n if (!stillCurrent()) return [];\n if (previousSessionId && previousSessionId !== activeSessionId) {\n await runCleanup('dispose the previous session PTYs', () => ptyHost.disposeAll());\n await runCleanup('dispose the previous session lifeline', () => lifeline.dispose());\n if (previousContext?.hasUI) {\n await runCleanup('clear the previous session status', () =>\n previousContext.ui.setStatus(RUNNER_STATUS_KEY, undefined),\n );\n }\n const previousTelemetry = telemetry;\n telemetry = undefined;\n if (previousTelemetry) {\n await runCleanup('stop the previous session telemetry', () => previousTelemetry.shutdown());\n }\n if (!stillCurrent()) return [];\n }\n\n const activeTelemetry = getTelemetry(context);\n await activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n if (!stillCurrent()) return [];\n paths.setSessionId(activeSessionId);\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(activeSessionId);\n if (!stillCurrent()) return [];\n\n const retained = await registry.listAll(activeSessionId);\n if (!stillCurrent()) return [];\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n if (!stillCurrent()) return [];\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n if (!stillCurrent()) return [];\n sessionReady = true;\n await refresh(false);\n if (!stillCurrent()) return [];\n scheduleHistorySweep();\n const errors = [...legacy.errors, ...reconciled.errors];\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n await activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n return errors;\n };\n\n const previousInitialization = sessionInitialization.catch(() => undefined);\n const startReadiness = async (): Promise<void> => {\n await previousInitialization;\n if (!isCurrent(generation, activeSessionId)) return;\n const coordinator =\n readDoomReadinessCoordinator(cordis) ??\n (fallbackReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n process.emitWarning(\n `${notification.packageId} initialization ${notification.state}: ${notification.error?.message ?? notification.diagnostics.join('; ')}`,\n );\n },\n }));\n const handle = coordinator.start(LEADER_SOURCE, `${activeSessionId}:${generation}`, async (signal) => ({\n value: undefined,\n diagnostics: await initializeSession(signal),\n }));\n await handle.wait();\n };\n const operation = startReadiness();\n sessionInitialization = operation;\n // The coordinator reports a failed generation once; this branch only marks\n // the detached waiter handled so Pi is not held open by the notification path.\n void trackOperation(operation).catch(() => undefined);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function runnerExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(runnerPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface RunnerPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction runnerPlugin(cordis: Context, config: RunnerPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-runner',\n description:\n 'Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installRunnerRuntime(cordis, config.pi);\n}\n\nexport default runnerExtension;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extension.cjs","names":["RunnerSettingsLoader","RUNNER_SETTINGS_FILE","createRunnerContainer","createDoomTelemetry","reconcileActiveRunners","formatRunnerFooterContribution","formatRunnerStatus","getLogTtlMs","stopRunnerProcess","DOOM_UI_HUB_SERVICE","requireDoomUiHub","openRunnerSpace","resolveRootSessionId","cleanupLegacyRunnerStore","readDoomReadinessCoordinator","createDoomReadinessCoordinator","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService"],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport type { DoomFooterContributionHandle } from '@agimon-ai/doompi-extension-contracts/footer';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerBashTool } from '../../commands/bash/bashTool.ts';\nimport { createRunnerContainer } from '../../container/index.ts';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport { registerRunnerCompactionRecovery } from '../../services/runs/compaction.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from '../../services/runs/reconcile.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from '../../tui/format.ts';\nimport { openRunnerSpace } from '../../tui/runnerSpace.ts';\nimport { getLogTtlMs } from '../../types/config.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nimport { RUNNER_SETTINGS_FILE, RunnerSettingsLoader } from '../RunnerSettings/RunnerSettingsLoader';\nimport { setRunnerSettings } from '../../types/config.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * Project-local extension config, read the way Pi documents it: one JSON file\n * per extension under the config directory, and only for a trusted project.\n */\nfunction loadRunnerSettings(context: ExtensionContext): void {\n try {\n const trusted = context.isProjectTrusted?.() ?? false;\n const { settings, issues } = new RunnerSettingsLoader().load(context.cwd, trusted);\n setRunnerSettings(settings);\n // A rejected key is surfaced rather than dropped: silence reads as a\n // setting that simply does not work.\n for (const issue of issues) process.emitWarning(`${RUNNER_SETTINGS_FILE}: ${issue}`);\n } catch (error) {\n process.emitWarning(`Could not read ${RUNNER_SETTINGS_FILE}: ${String(error)}`);\n setRunnerSettings({});\n }\n}\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function installRunnerRuntime(cordis: Context, pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.runnerRegistry;\n let disposeRuntime = async (): Promise<void> => {\n try {\n registry.close();\n } catch (error) {\n process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(error)}`);\n }\n };\n cordis.effect(() => () => disposeRuntime(), `${LEADER_SOURCE}/runtime`);\n\n const launcher = container.launcher;\n const rmuxBackend = container.rmuxBackend;\n const logReader = container.logReader;\n const ptyHost = container.ptyHost;\n const bashRunService = container.bashRunService;\n const paths = container.paths;\n const processControl = container.processControl;\n const lifeline = container.lifeline;\n\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let footerContribution: DoomFooterContributionHandle | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let refreshInFlight: Promise<void> | undefined;\n let historySweepInFlight: Promise<void> | undefined;\n let historySweepTimer: ReturnType<typeof setImmediate> | undefined;\n let statusPoll: ReturnType<typeof setInterval> | undefined;\n let unsubscribeRegistry: (() => void) | undefined;\n let sessionInitialization: Promise<void> = Promise.resolve();\n let fallbackReadiness: DoomReadinessCoordinator | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const pendingOperations = new Set<Promise<unknown>>();\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const waitForSessionReadiness = async (): Promise<void> => {\n await sessionInitialization;\n if (!active || !sessionReady || !sessionId) {\n throw new Error('doom-runner requires an active Pi session');\n }\n };\n const trackedBashRunService: IBashRunService = {\n run: async (request) => {\n await waitForSessionReadiness();\n return trackOperation(bashRunService.run(request));\n },\n };\n const isCurrent = (generation: number, expectedSessionId = sessionId): boolean =>\n active && !disposed && generation === sessionGeneration && expectedSessionId === sessionId;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n const generation = sessionGeneration;\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n\n const activeRecords = await registry.list();\n if (!isCurrent(generation, activeSessionId)) return;\n let visibleActive = activeRecords;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active: activeRecords,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = activeRecords.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (!isCurrent(generation, activeSessionId)) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution?.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (!isCurrent(generation, activeSessionId)) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void trackOperation(\n getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n }),\n );\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) {\n void trackOperation(getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error));\n }\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const scheduleHistorySweep = (): void => {\n if (historySweepTimer || historySweepInFlight || disposed) return;\n historySweepTimer = setImmediate(() => {\n historySweepTimer = undefined;\n if (disposed) return;\n const execution = (async () => {\n const sweep = paths.sweepHistoryAsync\n ? await paths.sweepHistoryAsync(getLogTtlMs())\n : paths.sweepHistory(getLogTtlMs());\n for (const error of sweep.errors) process.emitWarning(error);\n })()\n .catch((error) => process.emitWarning(`Could not sweep doom-runner history: ${String(error)}`))\n .finally(() => {\n if (historySweepInFlight === execution) historySweepInFlight = undefined;\n });\n historySweepInFlight = execution;\n });\n historySweepTimer.unref?.();\n };\n\n const runCleanup = async (label: string, cleanup: () => void | Promise<void>): Promise<void> => {\n try {\n await cleanup();\n } catch (error) {\n process.emitWarning(`Doom-runner cleanup could not ${label}: ${String(error)}`);\n }\n };\n\n const shutdownRuntime = async (): Promise<void> => {\n if (disposed) return;\n active = false;\n disposed = true;\n sessionGeneration += 1;\n sessionReady = false;\n if (statusPoll) clearInterval(statusPoll);\n statusPoll = undefined;\n if (historySweepTimer) clearImmediate(historySweepTimer);\n historySweepTimer = undefined;\n const unsubscribe = unsubscribeRegistry;\n unsubscribeRegistry = undefined;\n if (unsubscribe) await runCleanup('unsubscribe from the runner registry', unsubscribe);\n\n const ownedReadiness = fallbackReadiness;\n fallbackReadiness = undefined;\n if (ownedReadiness) await runCleanup('cancel standalone readiness work', () => ownedReadiness.dispose());\n await Promise.allSettled(pendingOperations);\n const activeRefresh = refreshInFlight;\n if (activeRefresh) await runCleanup('settle the runner refresh', () => activeRefresh);\n const activeHistorySweep = historySweepInFlight;\n if (activeHistorySweep) await runCleanup('settle runner history cleanup', () => activeHistorySweep);\n\n await runCleanup('dispose runner PTYs', () => ptyHost.disposeAll());\n\n let owned: RunnerRecord[] = [];\n if (sessionId) {\n try {\n owned = await registry.listBySession(sessionId);\n } catch (error) {\n process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(error)}`);\n }\n }\n await Promise.all(\n owned.map(async (record) => {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n return;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }),\n );\n\n const context = sessionContext;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n sessionContext = undefined;\n sessionId = undefined;\n rootSessionId = undefined;\n runners = [];\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n await Promise.all([\n runCleanup('dispose the runner lifeline', () => lifeline.dispose()),\n ...(context?.hasUI\n ? [runCleanup('clear the runner status', () => context.ui.setStatus(RUNNER_STATUS_KEY, undefined))]\n : []),\n ]);\n await runCleanup('close the runner registry', () => registry.close());\n if (ownedTelemetry) {\n await runCleanup('record the runner session finish', () =>\n ownedTelemetry.recordEvent('doom_runner.session_finished', { outcome: 'stopped' }),\n );\n await runCleanup('stop runner telemetry', () => ownedTelemetry.shutdown());\n }\n };\n\n disposeRuntime = shutdownRuntime;\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerFooter({\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n footerContribution = contribution;\n return () => {\n contribution.dispose();\n if (footerContribution === contribution) footerContribution = undefined;\n };\n });\n unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService: trackedBashRunService,\n getSessionId: async () => {\n await waitForSessionReadiness();\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n if (!active || !sessionReady) return;\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: async () => {\n try {\n await waitForSessionReadiness();\n return sessionId;\n } catch {\n return undefined;\n }\n },\n listBySession: async (activeSessionId) => {\n try {\n await waitForSessionReadiness();\n } catch {\n return [];\n }\n return active ? registry.listBySession(activeSessionId) : [];\n },\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n const activeSessionId = sessionId;\n if (!activeSessionId) return;\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await waitForSessionReadiness();\n await refresh();\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n await openRunnerSpace(ctx, {\n getRunners: () => (isCurrent(generation, activeSessionId) ? runners : []),\n getPtyRun: (name) =>\n isCurrent(generation, activeSessionId) ? (rmuxBackend.get(name) ?? ptyHost.get(name)) : undefined,\n readLog: (logPath) =>\n isCurrent(generation, activeSessionId) ? logReader.read(logPath, { lines: 1_000 }).text : '',\n stopRunner: async (id, reason) => {\n if (!isCurrent(generation, activeSessionId)) return;\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n if (!isCurrent(generation, activeSessionId)) return;\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n if (isCurrent(generation, activeSessionId)) await refresh();\n },\n });\n },\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', detail: 'background processes', order: LEADER_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'runners in this session' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n if (!active) return;\n const context = ctx as ExtensionContext;\n loadRunnerSettings(context);\n const activeSessionId = context.sessionManager.getSessionId();\n const previousSessionId = sessionId;\n const previousContext = sessionContext;\n const generation = ++sessionGeneration;\n sessionId = activeSessionId;\n rootSessionId = resolveRootSessionId(activeSessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n const initializeSession = async (signal: AbortSignal): Promise<readonly string[]> => {\n const stillCurrent = (): boolean => !signal.aborted && isCurrent(generation, activeSessionId);\n if (!stillCurrent()) return [];\n if (previousSessionId && previousSessionId !== activeSessionId) {\n await runCleanup('dispose the previous session PTYs', () => ptyHost.disposeAll());\n await runCleanup('dispose the previous session lifeline', () => lifeline.dispose());\n if (previousContext?.hasUI) {\n await runCleanup('clear the previous session status', () =>\n previousContext.ui.setStatus(RUNNER_STATUS_KEY, undefined),\n );\n }\n const previousTelemetry = telemetry;\n telemetry = undefined;\n if (previousTelemetry) {\n await runCleanup('stop the previous session telemetry', () => previousTelemetry.shutdown());\n }\n if (!stillCurrent()) return [];\n }\n\n const activeTelemetry = getTelemetry(context);\n await activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n if (!stillCurrent()) return [];\n paths.setSessionId(activeSessionId);\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(activeSessionId);\n if (!stillCurrent()) return [];\n\n const retained = await registry.listAll(activeSessionId);\n if (!stillCurrent()) return [];\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n if (!stillCurrent()) return [];\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n if (!stillCurrent()) return [];\n sessionReady = true;\n await refresh(false);\n if (!stillCurrent()) return [];\n scheduleHistorySweep();\n const errors = [...legacy.errors, ...reconciled.errors];\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n await activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n return errors;\n };\n\n const previousInitialization = sessionInitialization.catch(() => undefined);\n const startReadiness = async (): Promise<void> => {\n await previousInitialization;\n if (!isCurrent(generation, activeSessionId)) return;\n const coordinator =\n readDoomReadinessCoordinator(cordis) ??\n (fallbackReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n process.emitWarning(\n `${notification.packageId} initialization ${notification.state}: ${notification.error?.message ?? notification.diagnostics.join('; ')}`,\n );\n },\n }));\n const handle = coordinator.start(LEADER_SOURCE, `${activeSessionId}:${generation}`, async (signal) => ({\n value: undefined,\n diagnostics: await initializeSession(signal),\n }));\n await handle.wait();\n };\n const operation = startReadiness();\n sessionInitialization = operation;\n // The coordinator reports a failed generation once; this branch only marks\n // the detached waiter handled so Pi is not held open by the notification path.\n void trackOperation(operation).catch(() => undefined);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function runnerExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(runnerPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface RunnerPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction runnerPlugin(cordis: Context, config: RunnerPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-runner',\n description:\n 'Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installRunnerRuntime(cordis, config.pi);\n}\n\nexport default runnerExtension;\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{registerRunnerCompactionRecovery as e}from"../../services/runs/compaction.mjs";import{getLogTtlMs as t,setRunnerSettings as n}from"../../types/config.mjs";import{createRunnerContainer as r}from"../../container/index.mjs";import{cleanupLegacyRunnerStore as i,reconcileActiveRunners as a,stopRunnerProcess as o}from"../../services/runs/reconcile.mjs";import{registerBashTool as s}from"../../commands/bash/bashTool.mjs";import{formatRunnerFooterContribution as c,formatRunnerStatus as l}from"../../tui/format.mjs";import{openRunnerSpace as u}from"../../tui/runnerSpace.mjs";import{RUNNER_SETTINGS_FILE as d,RunnerSettingsLoader as f}from"../RunnerSettings/RunnerSettingsLoader.mjs";import{resolveRootSessionId as p}from"@agimon-ai/doompi-extension-contracts/child-process";import{connectDoomCordisHost as m}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as h,requireDoomHelpService as g}from"@agimon-ai/doompi-extension-contracts/help";import{createDoomReadinessCoordinator as ee,readDoomReadinessCoordinator as te}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as ne,requireDoomUiHub as re}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as ie}from"@agimon-ai/doompi-telemetry";const _=`@agimon-ai/doompi-runner`,ae=`runners`,v=`completed`,oe=`stopped`,y=`doom-runner-runners`;function se(e){try{let t=e.isProjectTrusted?.()??!1,{settings:r,issues:i}=new f().load(e.cwd,t);n(r);for(let e of i)process.emitWarning(`${d}: ${e}`)}catch(e){process.emitWarning(`Could not read ${d}: ${String(e)}`),n({})}}function b(n,d){let f=r(),m=f.runnerRegistry,h=async()=>{try{m.close()}catch(e){process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(e)}`)}};n.effect(()=>()=>h(),`${_}/runtime`);let g=f.launcher,b=f.rmuxBackend,x=f.logReader,S=f.ptyHost,ce=f.bashRunService,C=f.paths,w=f.processControl,T=f.lifeline,E=!0,D=0,O,k,A=[],j,M,N,P=!1,F=!1,I,L,R,z,B,V=Promise.resolve(),H,U=!1,W=!1,G,K=new Set,q=new Set,J=new Set,Y=e=>(J.add(e),e.then(()=>J.delete(e),()=>J.delete(e)),e),X=async()=>{if(await V,!E||!P||!O)throw Error(`doom-runner requires an active Pi session`)},le={run:async e=>(await X(),Y(ce.run(e)))},Z=(e,t=O)=>E&&!F&&e===D&&t===O,ue=e=>(N??=ie({serviceName:`doom-runner`,packageName:`@agimon-ai/doompi-runner`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),N),de=async e=>{let t=O,n=D;if(!t||!Z(n,t))return;let r=await m.list();if(!Z(n,t))return;let i=r;if(e){let e=await a({registry:m,launcher:g,rmuxBackend:b,processControl:w,currentHostPid:process.pid,startup:!1,active:r});for(let t of e.errors)process.emitWarning(t);if(e.reclaimed.length>0){let t=new Set(e.reclaimed);i=r.filter(e=>!t.has(e.id))}}if(!Z(n,t))return;A=i.filter(e=>e.sessionId===t);let o=(k?i.filter(e=>(e.rootSessionId??e.sessionId)===k):[]).length;G!==o&&(G=o,M?.update(c(o)),j?.hasUI&&j.ui.setStatus(y,l(o)));let s=[...q],u=await Promise.all(s.map(e=>m.get(e,t)));if(Z(n,t))for(let[e,n]of u.entries()){let r=s[e];if(!r)continue;if(!n||n.sessionId!==t){q.delete(r);continue}if(n.state!==v||(q.delete(n.id),K.has(n.id)))continue;K.add(n.id);let i=n.exit?.reason??v,a=n.exit?.code===null||n.exit?.code===void 0?``:`, exit code ${n.exit.code}`,o=[`Background runner ${n.name} exited: ${i}${a}.`,`Runner ID: ${n.id}`,`Log: ${n.logPath}`,`Inspect: doom-runner logs ${n.id}`].join(`
|
|
2
|
-
`);d.sendMessage({customType:`doom-runner-finished`,content:o,display:!0},{triggerTurn:!0,deliverAs:`steer`}),j&&Y(ue(j).recordEvent(`doom_runner.process_finished`,{outcome:`completed`,"runner.exit_code":n.exit?.code??0,"runner.backend":n.backend,...Number.isFinite(Date.parse(n.startedAt))?{duration_ms:Math.max(0,Date.now()-Date.parse(n.startedAt))}:{}}))}},Q=(e=!0)=>{if(!O||!P||F)return Promise.resolve();if(I)return U=!0,W||=e,I;let t=(async()=>{let t=e;do U=!1,W=!1,await de(t),t=W;while(U&&!F)})().finally(()=>{I===t&&(I=void 0)});return I=t,t},fe=e=>{F||(j&&Y(ue(j).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},pe=e=>{Q(e).catch(fe)},me=()=>{R||L||F||(R=setImmediate(()=>{if(R=void 0,F)return;let e=(async()=>{let e=C.sweepHistoryAsync?await C.sweepHistoryAsync(t()):C.sweepHistory(t());for(let t of e.errors)process.emitWarning(t)})().catch(e=>process.emitWarning(`Could not sweep doom-runner history: ${String(e)}`)).finally(()=>{L===e&&(L=void 0)});L=e}),R.unref?.())},$=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-runner cleanup could not ${e}: ${String(t)}`)}};h=async()=>{if(F)return;E=!1,F=!0,D+=1,P=!1,z&&clearInterval(z),z=void 0,R&&clearImmediate(R),R=void 0;let e=B;B=void 0,e&&await $(`unsubscribe from the runner registry`,e);let t=H;H=void 0,t&&await $(`cancel standalone readiness work`,()=>t.dispose()),await Promise.allSettled(J);let n=I;n&&await $(`settle the runner refresh`,()=>n);let r=L;r&&await $(`settle runner history cleanup`,()=>r),await $(`dispose runner PTYs`,()=>S.disposeAll());let i=[];if(O)try{i=await m.listBySession(O)}catch(e){process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(e)}`)}await Promise.all(i.map(async e=>{try{let t=await o(e,g,b);if(!t&&w.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await m.complete(e.id,{reason:oe,code:null,signal:t?`SIGTERM`:null,stopReason:`session ended`},e.sessionId)}catch(t){process.emitWarning(`Could not clean up runner ${e.id}: ${String(t)}`)}}));let a=j,s=N;N=void 0,j=void 0,O=void 0,k=void 0,A=[],K.clear(),q.clear(),await Promise.all([$(`dispose the runner lifeline`,()=>T.dispose()),...a?.hasUI?[$(`clear the runner status`,()=>a.ui.setStatus(y,void 0))]:[]]),await $(`close the runner registry`,()=>m.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},n.inject([ne],e=>{let t=re(e).registerFooter({source:_,id:`runner-count`,order:10});return M=t,()=>{t.dispose(),M===t&&(M=void 0)}}),B=m.subscribe(()=>pe(!1)),z=setInterval(()=>pe(!0),500),z.unref?.(),s(d,{bashRunService:le,getSessionId:async()=>{if(await X(),!O)throw Error(`doom-runner requires an active Pi session`);return O},onRunnerStarted:e=>{!E||!P||(q.add(e),pe(!1))}}),e(d,{getSessionId:async()=>{try{return await X(),O}catch{return}},listBySession:async e=>{try{await X()}catch{return[]}return E?m.listBySession(e):[]}}),d.registerCommand(ae,{description:`Open Runner Space: background processes started by bash`,handler:async(e,t)=>{if(!E)return;let n=D,r=O;if(r){if(!t.hasUI){t.ui.notify(`/runners requires interactive mode`,`error`);return}await X(),await Q(),!(!r||!Z(n,r))&&await u(t,{getRunners:()=>Z(n,r)?A:[],getPtyRun:e=>Z(n,r)?b.get(e)??S.get(e):void 0,readLog:e=>Z(n,r)?x.read(e,{lines:1e3}).text:``,stopRunner:async(e,t)=>{if(!Z(n,r))return;let i=A.find(t=>t.id===e);i&&(i.backend===`rmux`&&i.backendTarget?await b.stop(i.backendTarget,i.pid):await g.stop(i.pid),Z(n,r)&&(await m.complete(i.id,{reason:oe,code:null,signal:null,stopReason:t},i.sessionId),Z(n,r)&&await Q()))}})}}}),n.inject([ne],e=>{let t=re(e).registerLeader({source:_,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,order:67},{key:`
|
|
2
|
+
`);d.sendMessage({customType:`doom-runner-finished`,content:o,display:!0},{triggerTurn:!0,deliverAs:`steer`}),j&&Y(ue(j).recordEvent(`doom_runner.process_finished`,{outcome:`completed`,"runner.exit_code":n.exit?.code??0,"runner.backend":n.backend,...Number.isFinite(Date.parse(n.startedAt))?{duration_ms:Math.max(0,Date.now()-Date.parse(n.startedAt))}:{}}))}},Q=(e=!0)=>{if(!O||!P||F)return Promise.resolve();if(I)return U=!0,W||=e,I;let t=(async()=>{let t=e;do U=!1,W=!1,await de(t),t=W;while(U&&!F)})().finally(()=>{I===t&&(I=void 0)});return I=t,t},fe=e=>{F||(j&&Y(ue(j).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},pe=e=>{Q(e).catch(fe)},me=()=>{R||L||F||(R=setImmediate(()=>{if(R=void 0,F)return;let e=(async()=>{let e=C.sweepHistoryAsync?await C.sweepHistoryAsync(t()):C.sweepHistory(t());for(let t of e.errors)process.emitWarning(t)})().catch(e=>process.emitWarning(`Could not sweep doom-runner history: ${String(e)}`)).finally(()=>{L===e&&(L=void 0)});L=e}),R.unref?.())},$=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-runner cleanup could not ${e}: ${String(t)}`)}};h=async()=>{if(F)return;E=!1,F=!0,D+=1,P=!1,z&&clearInterval(z),z=void 0,R&&clearImmediate(R),R=void 0;let e=B;B=void 0,e&&await $(`unsubscribe from the runner registry`,e);let t=H;H=void 0,t&&await $(`cancel standalone readiness work`,()=>t.dispose()),await Promise.allSettled(J);let n=I;n&&await $(`settle the runner refresh`,()=>n);let r=L;r&&await $(`settle runner history cleanup`,()=>r),await $(`dispose runner PTYs`,()=>S.disposeAll());let i=[];if(O)try{i=await m.listBySession(O)}catch(e){process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(e)}`)}await Promise.all(i.map(async e=>{try{let t=await o(e,g,b);if(!t&&w.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await m.complete(e.id,{reason:oe,code:null,signal:t?`SIGTERM`:null,stopReason:`session ended`},e.sessionId)}catch(t){process.emitWarning(`Could not clean up runner ${e.id}: ${String(t)}`)}}));let a=j,s=N;N=void 0,j=void 0,O=void 0,k=void 0,A=[],K.clear(),q.clear(),await Promise.all([$(`dispose the runner lifeline`,()=>T.dispose()),...a?.hasUI?[$(`clear the runner status`,()=>a.ui.setStatus(y,void 0))]:[]]),await $(`close the runner registry`,()=>m.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},n.inject([ne],e=>{let t=re(e).registerFooter({source:_,id:`runner-count`,order:10});return M=t,()=>{t.dispose(),M===t&&(M=void 0)}}),B=m.subscribe(()=>pe(!1)),z=setInterval(()=>pe(!0),500),z.unref?.(),s(d,{bashRunService:le,getSessionId:async()=>{if(await X(),!O)throw Error(`doom-runner requires an active Pi session`);return O},onRunnerStarted:e=>{!E||!P||(q.add(e),pe(!1))}}),e(d,{getSessionId:async()=>{try{return await X(),O}catch{return}},listBySession:async e=>{try{await X()}catch{return[]}return E?m.listBySession(e):[]}}),d.registerCommand(ae,{description:`Open Runner Space: background processes started by bash`,handler:async(e,t)=>{if(!E)return;let n=D,r=O;if(r){if(!t.hasUI){t.ui.notify(`/runners requires interactive mode`,`error`);return}await X(),await Q(),!(!r||!Z(n,r))&&await u(t,{getRunners:()=>Z(n,r)?A:[],getPtyRun:e=>Z(n,r)?b.get(e)??S.get(e):void 0,readLog:e=>Z(n,r)?x.read(e,{lines:1e3}).text:``,stopRunner:async(e,t)=>{if(!Z(n,r))return;let i=A.find(t=>t.id===e);i&&(i.backend===`rmux`&&i.backendTarget?await b.stop(i.backendTarget,i.pid):await g.stop(i.pid),Z(n,r)&&(await m.complete(i.id,{reason:oe,code:null,signal:null,stopReason:t},i.sessionId),Z(n,r)&&await Q()))}})}}}),n.inject([ne],e=>{let t=re(e).registerLeader({source:_,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,detail:`background processes`,order:67},{key:`l`,label:`list`,detail:`runners in this session`}],command:{name:ae}}]});return()=>t.dispose()}),d.on(`session_start`,(e,t)=>{if(!E)return;let r=t;se(r);let o=r.sessionManager.getSessionId(),s=O,c=j,l=++D;O=o,k=p(o),j=r,P=!1,G=void 0,K.clear(),q.clear();let u=async e=>{let t=()=>!e.aborted&&Z(l,o);if(!t())return[];if(s&&s!==o){await $(`dispose the previous session PTYs`,()=>S.disposeAll()),await $(`dispose the previous session lifeline`,()=>T.dispose()),c?.hasUI&&await $(`clear the previous session status`,()=>c.ui.setStatus(y,void 0));let e=N;if(N=void 0,e&&await $(`stop the previous session telemetry`,()=>e.shutdown()),!t())return[]}let n=ue(r);if(await n.recordEvent(`doom_runner.session_started`,{outcome:`started`}),!t()||(C.setSessionId(o),await T.arm(o),!t()))return[];let u=await m.listAll(o);if(!t())return[];for(let e of u)e.state===v?K.add(e.id):e.promoted&&q.add(e.id);let d=await i({registry:m,launcher:g,rmuxBackend:b,processControl:w,currentHostPid:process.pid,paths:C});if(!t())return[];let f=await a({registry:m,launcher:g,rmuxBackend:b,processControl:w,currentHostPid:process.pid,startup:!0});if(!t()||(P=!0,await Q(!1),!t()))return[];me();let p=[...d.errors,...f.errors],h=d.reclaimed.length+f.reclaimed.length;return h>0&&r.hasUI&&r.ui.notify(`Reclaimed ${h} stale runner record(s)`,`warning`),await n.recordEvent(`doom_runner.reconciled`,{"runner.reclaimed_count":h,"runner.error_count":p.length,outcome:p.length===0?`completed`:`degraded`}),p},d=V.catch(()=>void 0),f=(async()=>{await d,Z(l,o)&&await(te(n)??(H??=ee({notify:e=>{process.emitWarning(`${e.packageId} initialization ${e.state}: ${e.error?.message??e.diagnostics.join(`; `)}`)}}))).start(_,`${o}:${l}`,async e=>({value:void 0,diagnostics:await u(e)})).wait()})();V=f,Y(f).catch(()=>void 0)})}async function x(e){let t=await m(e,_),n=t.root.plugin(S,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function S(e,t){e.inject([h],e=>{let t=g(e).register({source:_,moduleUrl:import.meta.url,skills:[{name:`doompi-use-runner`,description:`Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.`}]});return()=>t.dispose()}),b(e,t.pi)}export{x as default,x as runnerExtension,b as installRunnerRuntime};
|
|
3
3
|
//# sourceMappingURL=extension.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.mjs","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport type { DoomFooterContributionHandle } from '@agimon-ai/doompi-extension-contracts/footer';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerBashTool } from '../../commands/bash/bashTool.ts';\nimport { createRunnerContainer } from '../../container/index.ts';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport { registerRunnerCompactionRecovery } from '../../services/runs/compaction.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from '../../services/runs/reconcile.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from '../../tui/format.ts';\nimport { openRunnerSpace } from '../../tui/runnerSpace.ts';\nimport { getLogTtlMs } from '../../types/config.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nimport { RUNNER_SETTINGS_FILE, RunnerSettingsLoader } from '../RunnerSettings/RunnerSettingsLoader';\nimport { setRunnerSettings } from '../../types/config.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * Project-local extension config, read the way Pi documents it: one JSON file\n * per extension under the config directory, and only for a trusted project.\n */\nfunction loadRunnerSettings(context: ExtensionContext): void {\n try {\n const trusted = context.isProjectTrusted?.() ?? false;\n const { settings, issues } = new RunnerSettingsLoader().load(context.cwd, trusted);\n setRunnerSettings(settings);\n // A rejected key is surfaced rather than dropped: silence reads as a\n // setting that simply does not work.\n for (const issue of issues) process.emitWarning(`${RUNNER_SETTINGS_FILE}: ${issue}`);\n } catch (error) {\n process.emitWarning(`Could not read ${RUNNER_SETTINGS_FILE}: ${String(error)}`);\n setRunnerSettings({});\n }\n}\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function installRunnerRuntime(cordis: Context, pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.runnerRegistry;\n let disposeRuntime = async (): Promise<void> => {\n try {\n registry.close();\n } catch (error) {\n process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(error)}`);\n }\n };\n cordis.effect(() => () => disposeRuntime(), `${LEADER_SOURCE}/runtime`);\n\n const launcher = container.launcher;\n const rmuxBackend = container.rmuxBackend;\n const logReader = container.logReader;\n const ptyHost = container.ptyHost;\n const bashRunService = container.bashRunService;\n const paths = container.paths;\n const processControl = container.processControl;\n const lifeline = container.lifeline;\n\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let footerContribution: DoomFooterContributionHandle | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let refreshInFlight: Promise<void> | undefined;\n let historySweepInFlight: Promise<void> | undefined;\n let historySweepTimer: ReturnType<typeof setImmediate> | undefined;\n let statusPoll: ReturnType<typeof setInterval> | undefined;\n let unsubscribeRegistry: (() => void) | undefined;\n let sessionInitialization: Promise<void> = Promise.resolve();\n let fallbackReadiness: DoomReadinessCoordinator | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const pendingOperations = new Set<Promise<unknown>>();\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const waitForSessionReadiness = async (): Promise<void> => {\n await sessionInitialization;\n if (!active || !sessionReady || !sessionId) {\n throw new Error('doom-runner requires an active Pi session');\n }\n };\n const trackedBashRunService: IBashRunService = {\n run: async (request) => {\n await waitForSessionReadiness();\n return trackOperation(bashRunService.run(request));\n },\n };\n const isCurrent = (generation: number, expectedSessionId = sessionId): boolean =>\n active && !disposed && generation === sessionGeneration && expectedSessionId === sessionId;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n const generation = sessionGeneration;\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n\n const activeRecords = await registry.list();\n if (!isCurrent(generation, activeSessionId)) return;\n let visibleActive = activeRecords;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active: activeRecords,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = activeRecords.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (!isCurrent(generation, activeSessionId)) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution?.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (!isCurrent(generation, activeSessionId)) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void trackOperation(\n getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n }),\n );\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) {\n void trackOperation(getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error));\n }\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const scheduleHistorySweep = (): void => {\n if (historySweepTimer || historySweepInFlight || disposed) return;\n historySweepTimer = setImmediate(() => {\n historySweepTimer = undefined;\n if (disposed) return;\n const execution = (async () => {\n const sweep = paths.sweepHistoryAsync\n ? await paths.sweepHistoryAsync(getLogTtlMs())\n : paths.sweepHistory(getLogTtlMs());\n for (const error of sweep.errors) process.emitWarning(error);\n })()\n .catch((error) => process.emitWarning(`Could not sweep doom-runner history: ${String(error)}`))\n .finally(() => {\n if (historySweepInFlight === execution) historySweepInFlight = undefined;\n });\n historySweepInFlight = execution;\n });\n historySweepTimer.unref?.();\n };\n\n const runCleanup = async (label: string, cleanup: () => void | Promise<void>): Promise<void> => {\n try {\n await cleanup();\n } catch (error) {\n process.emitWarning(`Doom-runner cleanup could not ${label}: ${String(error)}`);\n }\n };\n\n const shutdownRuntime = async (): Promise<void> => {\n if (disposed) return;\n active = false;\n disposed = true;\n sessionGeneration += 1;\n sessionReady = false;\n if (statusPoll) clearInterval(statusPoll);\n statusPoll = undefined;\n if (historySweepTimer) clearImmediate(historySweepTimer);\n historySweepTimer = undefined;\n const unsubscribe = unsubscribeRegistry;\n unsubscribeRegistry = undefined;\n if (unsubscribe) await runCleanup('unsubscribe from the runner registry', unsubscribe);\n\n const ownedReadiness = fallbackReadiness;\n fallbackReadiness = undefined;\n if (ownedReadiness) await runCleanup('cancel standalone readiness work', () => ownedReadiness.dispose());\n await Promise.allSettled(pendingOperations);\n const activeRefresh = refreshInFlight;\n if (activeRefresh) await runCleanup('settle the runner refresh', () => activeRefresh);\n const activeHistorySweep = historySweepInFlight;\n if (activeHistorySweep) await runCleanup('settle runner history cleanup', () => activeHistorySweep);\n\n await runCleanup('dispose runner PTYs', () => ptyHost.disposeAll());\n\n let owned: RunnerRecord[] = [];\n if (sessionId) {\n try {\n owned = await registry.listBySession(sessionId);\n } catch (error) {\n process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(error)}`);\n }\n }\n await Promise.all(\n owned.map(async (record) => {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n return;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }),\n );\n\n const context = sessionContext;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n sessionContext = undefined;\n sessionId = undefined;\n rootSessionId = undefined;\n runners = [];\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n await Promise.all([\n runCleanup('dispose the runner lifeline', () => lifeline.dispose()),\n ...(context?.hasUI\n ? [runCleanup('clear the runner status', () => context.ui.setStatus(RUNNER_STATUS_KEY, undefined))]\n : []),\n ]);\n await runCleanup('close the runner registry', () => registry.close());\n if (ownedTelemetry) {\n await runCleanup('record the runner session finish', () =>\n ownedTelemetry.recordEvent('doom_runner.session_finished', { outcome: 'stopped' }),\n );\n await runCleanup('stop runner telemetry', () => ownedTelemetry.shutdown());\n }\n };\n\n disposeRuntime = shutdownRuntime;\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerFooter({\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n footerContribution = contribution;\n return () => {\n contribution.dispose();\n if (footerContribution === contribution) footerContribution = undefined;\n };\n });\n unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService: trackedBashRunService,\n getSessionId: async () => {\n await waitForSessionReadiness();\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n if (!active || !sessionReady) return;\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: async () => {\n try {\n await waitForSessionReadiness();\n return sessionId;\n } catch {\n return undefined;\n }\n },\n listBySession: async (activeSessionId) => {\n try {\n await waitForSessionReadiness();\n } catch {\n return [];\n }\n return active ? registry.listBySession(activeSessionId) : [];\n },\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n const activeSessionId = sessionId;\n if (!activeSessionId) return;\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await waitForSessionReadiness();\n await refresh();\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n await openRunnerSpace(ctx, {\n getRunners: () => (isCurrent(generation, activeSessionId) ? runners : []),\n getPtyRun: (name) =>\n isCurrent(generation, activeSessionId) ? (rmuxBackend.get(name) ?? ptyHost.get(name)) : undefined,\n readLog: (logPath) =>\n isCurrent(generation, activeSessionId) ? logReader.read(logPath, { lines: 1_000 }).text : '',\n stopRunner: async (id, reason) => {\n if (!isCurrent(generation, activeSessionId)) return;\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n if (!isCurrent(generation, activeSessionId)) return;\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n if (isCurrent(generation, activeSessionId)) await refresh();\n },\n });\n },\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', order: LEADER_GROUP_ORDER },\n { key: 'r', label: 'open', detail: 'background processes' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n if (!active) return;\n const context = ctx as ExtensionContext;\n loadRunnerSettings(context);\n const activeSessionId = context.sessionManager.getSessionId();\n const previousSessionId = sessionId;\n const previousContext = sessionContext;\n const generation = ++sessionGeneration;\n sessionId = activeSessionId;\n rootSessionId = resolveRootSessionId(activeSessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n const initializeSession = async (signal: AbortSignal): Promise<readonly string[]> => {\n const stillCurrent = (): boolean => !signal.aborted && isCurrent(generation, activeSessionId);\n if (!stillCurrent()) return [];\n if (previousSessionId && previousSessionId !== activeSessionId) {\n await runCleanup('dispose the previous session PTYs', () => ptyHost.disposeAll());\n await runCleanup('dispose the previous session lifeline', () => lifeline.dispose());\n if (previousContext?.hasUI) {\n await runCleanup('clear the previous session status', () =>\n previousContext.ui.setStatus(RUNNER_STATUS_KEY, undefined),\n );\n }\n const previousTelemetry = telemetry;\n telemetry = undefined;\n if (previousTelemetry) {\n await runCleanup('stop the previous session telemetry', () => previousTelemetry.shutdown());\n }\n if (!stillCurrent()) return [];\n }\n\n const activeTelemetry = getTelemetry(context);\n await activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n if (!stillCurrent()) return [];\n paths.setSessionId(activeSessionId);\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(activeSessionId);\n if (!stillCurrent()) return [];\n\n const retained = await registry.listAll(activeSessionId);\n if (!stillCurrent()) return [];\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n if (!stillCurrent()) return [];\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n if (!stillCurrent()) return [];\n sessionReady = true;\n await refresh(false);\n if (!stillCurrent()) return [];\n scheduleHistorySweep();\n const errors = [...legacy.errors, ...reconciled.errors];\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n await activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n return errors;\n };\n\n const previousInitialization = sessionInitialization.catch(() => undefined);\n const startReadiness = async (): Promise<void> => {\n await previousInitialization;\n if (!isCurrent(generation, activeSessionId)) return;\n const coordinator =\n readDoomReadinessCoordinator(cordis) ??\n (fallbackReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n process.emitWarning(\n `${notification.packageId} initialization ${notification.state}: ${notification.error?.message ?? notification.diagnostics.join('; ')}`,\n );\n },\n }));\n const handle = coordinator.start(LEADER_SOURCE, `${activeSessionId}:${generation}`, async (signal) => ({\n value: undefined,\n diagnostics: await initializeSession(signal),\n }));\n await handle.wait();\n };\n const operation = startReadiness();\n sessionInitialization = operation;\n // The coordinator reports a failed generation once; this branch only marks\n // the detached waiter handled so Pi is not held open by the notification path.\n void trackOperation(operation).catch(() => undefined);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function runnerExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(runnerPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface RunnerPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction runnerPlugin(cordis: Context, config: RunnerPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-runner',\n description:\n 'Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installRunnerRuntime(cordis, config.pi);\n}\n\nexport default runnerExtension;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extension.mjs","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport type { DoomFooterContributionHandle } from '@agimon-ai/doompi-extension-contracts/footer';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerBashTool } from '../../commands/bash/bashTool.ts';\nimport { createRunnerContainer } from '../../container/index.ts';\nimport type { IBashRunService } from '../../types/bashRunService';\nimport type { RunnerRecord } from '../../types/runnerRegistry';\nimport { registerRunnerCompactionRecovery } from '../../services/runs/compaction.ts';\nimport { cleanupLegacyRunnerStore, reconcileActiveRunners, stopRunnerProcess } from '../../services/runs/reconcile.ts';\nimport { formatRunnerFooterContribution, formatRunnerStatus } from '../../tui/format.ts';\nimport { openRunnerSpace } from '../../tui/runnerSpace.ts';\nimport { getLogTtlMs } from '../../types/config.ts';\n\nconst LEADER_SOURCE = '@agimon-ai/doompi-runner';\n/** After doom-task's `t` (65) and before the core help group (70). */\nconst LEADER_GROUP_ORDER = 67;\nconst COMMAND_NAME = 'runners';\nconst ERR_REQUIRES_INTERACTIVE = '/runners requires interactive mode';\n\nimport { RUNNER_SETTINGS_FILE, RunnerSettingsLoader } from '../RunnerSettings/RunnerSettingsLoader';\nimport { setRunnerSettings } from '../../types/config.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst RUNNER_FINISHED_MESSAGE = 'doom-runner-finished';\nconst COMPLETED_STATE = 'completed';\nconst RMUX_BACKEND = 'rmux';\nconst STOPPED_REASON = 'stopped';\nconst RUNNER_STATUS_KEY = 'doom-runner-runners';\nconst RUNNER_FOOTER_ORDER = 10;\nconst RUNNER_STATUS_POLL_MS = 500;\n\n/**\n * Project-local extension config, read the way Pi documents it: one JSON file\n * per extension under the config directory, and only for a trusted project.\n */\nfunction loadRunnerSettings(context: ExtensionContext): void {\n try {\n const trusted = context.isProjectTrusted?.() ?? false;\n const { settings, issues } = new RunnerSettingsLoader().load(context.cwd, trusted);\n setRunnerSettings(settings);\n // A rejected key is surfaced rather than dropped: silence reads as a\n // setting that simply does not work.\n for (const issue of issues) process.emitWarning(`${RUNNER_SETTINGS_FILE}: ${issue}`);\n } catch (error) {\n process.emitWarning(`Could not read ${RUNNER_SETTINGS_FILE}: ${String(error)}`);\n setRunnerSettings({});\n }\n}\n\n/**\n * doom-runner: replaces pi's `bash` tool with a supervised one and provides a\n * CLI for anything it leaves running.\n */\nexport function installRunnerRuntime(cordis: Context, pi: ExtensionAPI): void {\n const container = createRunnerContainer();\n const registry = container.runnerRegistry;\n let disposeRuntime = async (): Promise<void> => {\n try {\n registry.close();\n } catch (error) {\n process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(error)}`);\n }\n };\n cordis.effect(() => () => disposeRuntime(), `${LEADER_SOURCE}/runtime`);\n\n const launcher = container.launcher;\n const rmuxBackend = container.rmuxBackend;\n const logReader = container.logReader;\n const ptyHost = container.ptyHost;\n const bashRunService = container.bashRunService;\n const paths = container.paths;\n const processControl = container.processControl;\n const lifeline = container.lifeline;\n\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let rootSessionId: string | undefined;\n let runners: RunnerRecord[] = [];\n let sessionContext: ExtensionContext | undefined;\n let footerContribution: DoomFooterContributionHandle | undefined;\n let telemetry: DoomTelemetry | undefined;\n let sessionReady = false;\n let disposed = false;\n let refreshInFlight: Promise<void> | undefined;\n let historySweepInFlight: Promise<void> | undefined;\n let historySweepTimer: ReturnType<typeof setImmediate> | undefined;\n let statusPoll: ReturnType<typeof setInterval> | undefined;\n let unsubscribeRegistry: (() => void) | undefined;\n let sessionInitialization: Promise<void> = Promise.resolve();\n let fallbackReadiness: DoomReadinessCoordinator | undefined;\n let refreshQueued = false;\n let queuedReconciliation = false;\n let lastRunnerCount: number | undefined;\n const notifiedRunnerIds = new Set<string>();\n const pendingPromotedRunnerIds = new Set<string>();\n const pendingOperations = new Set<Promise<unknown>>();\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const waitForSessionReadiness = async (): Promise<void> => {\n await sessionInitialization;\n if (!active || !sessionReady || !sessionId) {\n throw new Error('doom-runner requires an active Pi session');\n }\n };\n const trackedBashRunService: IBashRunService = {\n run: async (request) => {\n await waitForSessionReadiness();\n return trackOperation(bashRunService.run(request));\n },\n };\n const isCurrent = (generation: number, expectedSessionId = sessionId): boolean =>\n active && !disposed && generation === sessionGeneration && expectedSessionId === sessionId;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-runner',\n packageName: '@agimon-ai/doompi-runner',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n /** Performs one bounded pass over active state and explicitly monitored runners. */\n const refreshNow = async (shouldReconcile: boolean): Promise<void> => {\n const activeSessionId = sessionId;\n const generation = sessionGeneration;\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n\n const activeRecords = await registry.list();\n if (!isCurrent(generation, activeSessionId)) return;\n let visibleActive = activeRecords;\n if (shouldReconcile) {\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: false,\n active: activeRecords,\n });\n for (const error of reconciled.errors) process.emitWarning(error);\n if (reconciled.reclaimed.length > 0) {\n const reclaimedIds = new Set(reconciled.reclaimed);\n visibleActive = activeRecords.filter((record) => !reclaimedIds.has(record.id));\n }\n }\n if (!isCurrent(generation, activeSessionId)) return;\n\n runners = visibleActive.filter((record) => record.sessionId === activeSessionId);\n const rootRunners = rootSessionId\n ? visibleActive.filter((record) => (record.rootSessionId ?? record.sessionId) === rootSessionId)\n : [];\n const runnerCount = rootRunners.length;\n if (lastRunnerCount !== runnerCount) {\n lastRunnerCount = runnerCount;\n footerContribution?.update(formatRunnerFooterContribution(runnerCount));\n if (sessionContext?.hasUI) sessionContext.ui.setStatus(RUNNER_STATUS_KEY, formatRunnerStatus(runnerCount));\n }\n\n const pendingIds = [...pendingPromotedRunnerIds];\n const monitored = await Promise.all(pendingIds.map((id) => registry.get(id, activeSessionId)));\n if (!isCurrent(generation, activeSessionId)) return;\n for (const [index, record] of monitored.entries()) {\n const id = pendingIds[index];\n if (!id) continue;\n if (!record || record.sessionId !== activeSessionId) {\n pendingPromotedRunnerIds.delete(id);\n continue;\n }\n if (record.state !== COMPLETED_STATE) continue;\n pendingPromotedRunnerIds.delete(record.id);\n if (notifiedRunnerIds.has(record.id)) continue;\n notifiedRunnerIds.add(record.id);\n const outcome = record.exit?.reason ?? COMPLETED_STATE;\n const code =\n record.exit?.code === null || record.exit?.code === undefined ? '' : `, exit code ${record.exit.code}`;\n const content = [\n `Background runner ${record.name} exited: ${outcome}${code}.`,\n `Runner ID: ${record.id}`,\n `Log: ${record.logPath}`,\n `Inspect: doom-runner logs ${record.id}`,\n ].join('\\n');\n pi.sendMessage(\n { customType: RUNNER_FINISHED_MESSAGE, content, display: true },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n if (sessionContext) {\n void trackOperation(\n getTelemetry(sessionContext).recordEvent('doom_runner.process_finished', {\n outcome: 'completed',\n 'runner.exit_code': record.exit?.code ?? 0,\n 'runner.backend': record.backend,\n ...(Number.isFinite(Date.parse(record.startedAt))\n ? { duration_ms: Math.max(0, Date.now() - Date.parse(record.startedAt)) }\n : {}),\n }),\n );\n }\n }\n };\n\n /** Coalesces timer and event triggers without losing a requested reconciliation pass. */\n const refresh = (shouldReconcile = true): Promise<void> => {\n if (!sessionId || !sessionReady || disposed) return Promise.resolve();\n if (refreshInFlight) {\n refreshQueued = true;\n queuedReconciliation ||= shouldReconcile;\n return refreshInFlight;\n }\n\n const execution = (async () => {\n let reconcileNext = shouldReconcile;\n do {\n refreshQueued = false;\n queuedReconciliation = false;\n await refreshNow(reconcileNext);\n reconcileNext = queuedReconciliation;\n } while (refreshQueued && !disposed);\n })().finally(() => {\n if (refreshInFlight === execution) refreshInFlight = undefined;\n });\n refreshInFlight = execution;\n return execution;\n };\n\n const reportRefreshError = (error: unknown): void => {\n if (disposed) return;\n if (sessionContext) {\n void trackOperation(getTelemetry(sessionContext).recordError('doom_runner.refresh_failed', error));\n }\n process.emitWarning(`Could not refresh doom-runner: ${String(error)}`);\n };\n const scheduleRefresh = (shouldReconcile: boolean): void => {\n void refresh(shouldReconcile).catch(reportRefreshError);\n };\n const scheduleHistorySweep = (): void => {\n if (historySweepTimer || historySweepInFlight || disposed) return;\n historySweepTimer = setImmediate(() => {\n historySweepTimer = undefined;\n if (disposed) return;\n const execution = (async () => {\n const sweep = paths.sweepHistoryAsync\n ? await paths.sweepHistoryAsync(getLogTtlMs())\n : paths.sweepHistory(getLogTtlMs());\n for (const error of sweep.errors) process.emitWarning(error);\n })()\n .catch((error) => process.emitWarning(`Could not sweep doom-runner history: ${String(error)}`))\n .finally(() => {\n if (historySweepInFlight === execution) historySweepInFlight = undefined;\n });\n historySweepInFlight = execution;\n });\n historySweepTimer.unref?.();\n };\n\n const runCleanup = async (label: string, cleanup: () => void | Promise<void>): Promise<void> => {\n try {\n await cleanup();\n } catch (error) {\n process.emitWarning(`Doom-runner cleanup could not ${label}: ${String(error)}`);\n }\n };\n\n const shutdownRuntime = async (): Promise<void> => {\n if (disposed) return;\n active = false;\n disposed = true;\n sessionGeneration += 1;\n sessionReady = false;\n if (statusPoll) clearInterval(statusPoll);\n statusPoll = undefined;\n if (historySweepTimer) clearImmediate(historySweepTimer);\n historySweepTimer = undefined;\n const unsubscribe = unsubscribeRegistry;\n unsubscribeRegistry = undefined;\n if (unsubscribe) await runCleanup('unsubscribe from the runner registry', unsubscribe);\n\n const ownedReadiness = fallbackReadiness;\n fallbackReadiness = undefined;\n if (ownedReadiness) await runCleanup('cancel standalone readiness work', () => ownedReadiness.dispose());\n await Promise.allSettled(pendingOperations);\n const activeRefresh = refreshInFlight;\n if (activeRefresh) await runCleanup('settle the runner refresh', () => activeRefresh);\n const activeHistorySweep = historySweepInFlight;\n if (activeHistorySweep) await runCleanup('settle runner history cleanup', () => activeHistorySweep);\n\n await runCleanup('dispose runner PTYs', () => ptyHost.disposeAll());\n\n let owned: RunnerRecord[] = [];\n if (sessionId) {\n try {\n owned = await registry.listBySession(sessionId);\n } catch (error) {\n process.emitWarning(`Could not list runners during doom-runner shutdown: ${String(error)}`);\n }\n }\n await Promise.all(\n owned.map(async (record) => {\n try {\n const stopped = await stopRunnerProcess(record, launcher, rmuxBackend);\n if (!stopped && processControl.isAlive(record.pid)) {\n process.emitWarning(`Could not stop runner ${record.id} during session shutdown`);\n return;\n }\n await registry.complete(\n record.id,\n {\n reason: STOPPED_REASON,\n code: null,\n signal: stopped ? 'SIGTERM' : null,\n stopReason: 'session ended',\n },\n record.sessionId,\n );\n } catch (error) {\n process.emitWarning(`Could not clean up runner ${record.id}: ${String(error)}`);\n }\n }),\n );\n\n const context = sessionContext;\n const ownedTelemetry = telemetry;\n telemetry = undefined;\n sessionContext = undefined;\n sessionId = undefined;\n rootSessionId = undefined;\n runners = [];\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n await Promise.all([\n runCleanup('dispose the runner lifeline', () => lifeline.dispose()),\n ...(context?.hasUI\n ? [runCleanup('clear the runner status', () => context.ui.setStatus(RUNNER_STATUS_KEY, undefined))]\n : []),\n ]);\n await runCleanup('close the runner registry', () => registry.close());\n if (ownedTelemetry) {\n await runCleanup('record the runner session finish', () =>\n ownedTelemetry.recordEvent('doom_runner.session_finished', { outcome: 'stopped' }),\n );\n await runCleanup('stop runner telemetry', () => ownedTelemetry.shutdown());\n }\n };\n\n disposeRuntime = shutdownRuntime;\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerFooter({\n source: LEADER_SOURCE,\n id: 'runner-count',\n order: RUNNER_FOOTER_ORDER,\n });\n footerContribution = contribution;\n return () => {\n contribution.dispose();\n if (footerContribution === contribution) footerContribution = undefined;\n };\n });\n unsubscribeRegistry = registry.subscribe(() => scheduleRefresh(false));\n statusPoll = setInterval(() => scheduleRefresh(true), RUNNER_STATUS_POLL_MS);\n statusPoll.unref?.();\n\n registerBashTool(pi, {\n bashRunService: trackedBashRunService,\n getSessionId: async () => {\n await waitForSessionReadiness();\n if (!sessionId) throw new Error('doom-runner requires an active Pi session');\n return sessionId;\n },\n onRunnerStarted: (id) => {\n if (!active || !sessionReady) return;\n pendingPromotedRunnerIds.add(id);\n scheduleRefresh(false);\n },\n });\n\n registerRunnerCompactionRecovery(pi, {\n getSessionId: async () => {\n try {\n await waitForSessionReadiness();\n return sessionId;\n } catch {\n return undefined;\n }\n },\n listBySession: async (activeSessionId) => {\n try {\n await waitForSessionReadiness();\n } catch {\n return [];\n }\n return active ? registry.listBySession(activeSessionId) : [];\n },\n });\n\n pi.registerCommand(COMMAND_NAME, {\n description: 'Open Runner Space: background processes started by bash',\n handler: async (_args, ctx) => {\n if (!active) return;\n const generation = sessionGeneration;\n const activeSessionId = sessionId;\n if (!activeSessionId) return;\n if (!ctx.hasUI) {\n ctx.ui.notify(ERR_REQUIRES_INTERACTIVE, 'error');\n return;\n }\n await waitForSessionReadiness();\n await refresh();\n if (!activeSessionId || !isCurrent(generation, activeSessionId)) return;\n await openRunnerSpace(ctx, {\n getRunners: () => (isCurrent(generation, activeSessionId) ? runners : []),\n getPtyRun: (name) =>\n isCurrent(generation, activeSessionId) ? (rmuxBackend.get(name) ?? ptyHost.get(name)) : undefined,\n readLog: (logPath) =>\n isCurrent(generation, activeSessionId) ? logReader.read(logPath, { lines: 1_000 }).text : '',\n stopRunner: async (id, reason) => {\n if (!isCurrent(generation, activeSessionId)) return;\n const record = runners.find((candidate) => candidate.id === id);\n if (!record) return;\n if (record.backend === RMUX_BACKEND && record.backendTarget) {\n await rmuxBackend.stop(record.backendTarget, record.pid);\n } else await launcher.stop(record.pid);\n if (!isCurrent(generation, activeSessionId)) return;\n await registry.complete(\n record.id,\n { reason: STOPPED_REASON, code: null, signal: null, stopReason: reason },\n record.sessionId,\n );\n if (isCurrent(generation, activeSessionId)) await refresh();\n },\n });\n },\n });\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const contribution = requireDoomUiHub(uiContext).registerLeader({\n source: LEADER_SOURCE,\n bindings: [\n {\n id: 'runners.open',\n path: [\n { key: 'r', label: 'runners', detail: 'background processes', order: LEADER_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'runners in this session' },\n ],\n command: { name: COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n if (!active) return;\n const context = ctx as ExtensionContext;\n loadRunnerSettings(context);\n const activeSessionId = context.sessionManager.getSessionId();\n const previousSessionId = sessionId;\n const previousContext = sessionContext;\n const generation = ++sessionGeneration;\n sessionId = activeSessionId;\n rootSessionId = resolveRootSessionId(activeSessionId);\n sessionContext = context;\n sessionReady = false;\n lastRunnerCount = undefined;\n notifiedRunnerIds.clear();\n pendingPromotedRunnerIds.clear();\n\n const initializeSession = async (signal: AbortSignal): Promise<readonly string[]> => {\n const stillCurrent = (): boolean => !signal.aborted && isCurrent(generation, activeSessionId);\n if (!stillCurrent()) return [];\n if (previousSessionId && previousSessionId !== activeSessionId) {\n await runCleanup('dispose the previous session PTYs', () => ptyHost.disposeAll());\n await runCleanup('dispose the previous session lifeline', () => lifeline.dispose());\n if (previousContext?.hasUI) {\n await runCleanup('clear the previous session status', () =>\n previousContext.ui.setStatus(RUNNER_STATUS_KEY, undefined),\n );\n }\n const previousTelemetry = telemetry;\n telemetry = undefined;\n if (previousTelemetry) {\n await runCleanup('stop the previous session telemetry', () => previousTelemetry.shutdown());\n }\n if (!stillCurrent()) return [];\n }\n\n const activeTelemetry = getTelemetry(context);\n await activeTelemetry.recordEvent('doom_runner.session_started', { outcome: 'started' });\n if (!stillCurrent()) return [];\n paths.setSessionId(activeSessionId);\n // Awaited before anything can launch, so every runner this session starts\n // finds a lifeline it can connect to rather than a socket that is not\n // listening yet, which would read as an owner that is already gone.\n await lifeline.arm(activeSessionId);\n if (!stillCurrent()) return [];\n\n const retained = await registry.listAll(activeSessionId);\n if (!stillCurrent()) return [];\n for (const record of retained) {\n if (record.state === COMPLETED_STATE) notifiedRunnerIds.add(record.id);\n else if (record.promoted) pendingPromotedRunnerIds.add(record.id);\n }\n const legacy = await cleanupLegacyRunnerStore({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n paths,\n });\n if (!stillCurrent()) return [];\n const reconciled = await reconcileActiveRunners({\n registry,\n launcher,\n rmuxBackend,\n processControl,\n currentHostPid: process.pid,\n startup: true,\n });\n if (!stillCurrent()) return [];\n sessionReady = true;\n await refresh(false);\n if (!stillCurrent()) return [];\n scheduleHistorySweep();\n const errors = [...legacy.errors, ...reconciled.errors];\n const reclaimedCount = legacy.reclaimed.length + reconciled.reclaimed.length;\n if (reclaimedCount > 0 && context.hasUI) {\n context.ui.notify(`Reclaimed ${reclaimedCount} stale runner record(s)`, 'warning');\n }\n await activeTelemetry.recordEvent('doom_runner.reconciled', {\n 'runner.reclaimed_count': reclaimedCount,\n 'runner.error_count': errors.length,\n outcome: errors.length === 0 ? 'completed' : 'degraded',\n });\n return errors;\n };\n\n const previousInitialization = sessionInitialization.catch(() => undefined);\n const startReadiness = async (): Promise<void> => {\n await previousInitialization;\n if (!isCurrent(generation, activeSessionId)) return;\n const coordinator =\n readDoomReadinessCoordinator(cordis) ??\n (fallbackReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n process.emitWarning(\n `${notification.packageId} initialization ${notification.state}: ${notification.error?.message ?? notification.diagnostics.join('; ')}`,\n );\n },\n }));\n const handle = coordinator.start(LEADER_SOURCE, `${activeSessionId}:${generation}`, async (signal) => ({\n value: undefined,\n diagnostics: await initializeSession(signal),\n }));\n await handle.wait();\n };\n const operation = startReadiness();\n sessionInitialization = operation;\n // The coordinator reports a failed generation once; this branch only marks\n // the detached waiter handled so Pi is not held open by the notification path.\n void trackOperation(operation).catch(() => undefined);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function runnerExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, LEADER_SOURCE);\n const fiber = connection.root.plugin(runnerPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface RunnerPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction runnerPlugin(cordis: Context, config: RunnerPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: LEADER_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-runner',\n description:\n 'Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installRunnerRuntime(cordis, config.pi);\n}\n\nexport default runnerExtension;\n"],"mappings":"iwCAuBA,MAAM,EAAgB,2BAGhB,GAAe,UAQf,EAAkB,YAElB,GAAiB,UACjB,EAAoB,sBAQ1B,SAAS,GAAmB,EAAiC,CAC3D,GAAI,CACF,IAAM,EAAU,EAAQ,mBAAmB,GAAK,GAC1C,CAAE,WAAU,UAAW,IAAI,EAAqB,CAAC,CAAC,KAAK,EAAQ,IAAK,CAAO,EACjF,EAAkB,CAAQ,EAG1B,IAAK,IAAM,KAAS,EAAQ,QAAQ,YAAY,GAAG,EAAqB,IAAI,GAAO,CACrF,OAAS,EAAO,CACd,QAAQ,YAAY,kBAAkB,EAAqB,IAAI,OAAO,CAAK,GAAG,EAC9E,EAAkB,CAAC,CAAC,CACtB,CACF,CAMA,SAAgB,EAAqB,EAAiB,EAAwB,CAC5E,IAAM,EAAY,EAAsB,EAClC,EAAW,EAAU,eACvB,EAAiB,SAA2B,CAC9C,GAAI,CACF,EAAS,MAAM,CACjB,OAAS,EAAO,CACd,QAAQ,YAAY,+DAA+D,OAAO,CAAK,GAAG,CACpG,CACF,EACA,EAAO,eAAmB,EAAe,EAAG,GAAG,EAAc,SAAS,EAEtE,IAAM,EAAW,EAAU,SACrB,EAAc,EAAU,YACxB,EAAY,EAAU,UACtB,EAAU,EAAU,QACpB,GAAiB,EAAU,eAC3B,EAAQ,EAAU,MAClB,EAAiB,EAAU,eAC3B,EAAW,EAAU,SAEvB,EAAS,GACT,EAAoB,EACpB,EACA,EACA,EAA0B,CAAC,EAC3B,EACA,EACA,EACA,EAAe,GACf,EAAW,GACX,EACA,EACA,EACA,EACA,EACA,EAAuC,QAAQ,QAAQ,EACvD,EACA,EAAgB,GAChB,EAAuB,GACvB,EACE,EAAoB,IAAI,IACxB,EAA2B,IAAI,IAC/B,EAAoB,IAAI,IACxB,EAAqB,IACzB,EAAkB,IAAI,CAAS,EAC/B,EAAe,SACP,EAAkB,OAAO,CAAS,MAClC,EAAkB,OAAO,CAAS,CAC1C,EACO,GAEH,EAA0B,SAA2B,CAEzD,GADA,MAAM,EACF,CAAC,GAAU,CAAC,GAAgB,CAAC,EAC/B,MAAU,MAAM,2CAA2C,CAE/D,EACM,GAAyC,CAC7C,IAAK,KAAO,KACV,MAAM,EAAwB,EACvB,EAAe,GAAe,IAAI,CAAO,CAAC,EAErD,EACM,GAAa,EAAoB,EAAoB,IACzD,GAAU,CAAC,GAAY,IAAe,GAAqB,IAAsB,EAC7E,GAAgB,IACpB,IAAc,GAAoB,CAChC,YAAa,cACb,YAAa,2BACb,IAAK,EAAI,IACT,IAAK,QAAQ,IACb,WAAY,GACZ,aAAc,EAChB,CAAC,EACM,GAGH,GAAa,KAAO,IAA4C,CACpE,IAAM,EAAkB,EAClB,EAAa,EACnB,GAAI,CAAC,GAAmB,CAAC,EAAU,EAAY,CAAe,EAAG,OAEjE,IAAM,EAAgB,MAAM,EAAS,KAAK,EAC1C,GAAI,CAAC,EAAU,EAAY,CAAe,EAAG,OAC7C,IAAI,EAAgB,EACpB,GAAI,EAAiB,CACnB,IAAM,EAAa,MAAM,EAAuB,CAC9C,WACA,WACA,cACA,iBACA,eAAgB,QAAQ,IACxB,QAAS,GACT,OAAQ,CACV,CAAC,EACD,IAAK,IAAM,KAAS,EAAW,OAAQ,QAAQ,YAAY,CAAK,EAChE,GAAI,EAAW,UAAU,OAAS,EAAG,CACnC,IAAM,EAAe,IAAI,IAAI,EAAW,SAAS,EACjD,EAAgB,EAAc,OAAQ,GAAW,CAAC,EAAa,IAAI,EAAO,EAAE,CAAC,CAC/E,CACF,CACA,GAAI,CAAC,EAAU,EAAY,CAAe,EAAG,OAE7C,EAAU,EAAc,OAAQ,GAAW,EAAO,YAAc,CAAe,EAI/E,IAAM,GAHc,EAChB,EAAc,OAAQ,IAAY,EAAO,eAAiB,EAAO,aAAe,CAAa,EAC7F,CAAC,EAAA,CAC2B,OAC5B,IAAoB,IACtB,EAAkB,EAClB,GAAoB,OAAO,EAA+B,CAAW,CAAC,EAClE,GAAgB,OAAO,EAAe,GAAG,UAAU,EAAmB,EAAmB,CAAW,CAAC,GAG3G,IAAM,EAAa,CAAC,GAAG,CAAwB,EACzC,EAAY,MAAM,QAAQ,IAAI,EAAW,IAAK,GAAO,EAAS,IAAI,EAAI,CAAe,CAAC,CAAC,EACxF,KAAU,EAAY,CAAe,EAC1C,IAAK,GAAM,CAAC,EAAO,KAAW,EAAU,QAAQ,EAAG,CACjD,IAAM,EAAK,EAAW,GACtB,GAAI,CAAC,EAAI,SACT,GAAI,CAAC,GAAU,EAAO,YAAc,EAAiB,CACnD,EAAyB,OAAO,CAAE,EAClC,QACF,CAGA,GAFI,EAAO,QAAU,IACrB,EAAyB,OAAO,EAAO,EAAE,EACrC,EAAkB,IAAI,EAAO,EAAE,GAAG,SACtC,EAAkB,IAAI,EAAO,EAAE,EAC/B,IAAM,EAAU,EAAO,MAAM,QAAU,EACjC,EACJ,EAAO,MAAM,OAAS,MAAQ,EAAO,MAAM,OAAS,IAAA,GAAY,GAAK,eAAe,EAAO,KAAK,OAC5F,EAAU,CACd,qBAAqB,EAAO,KAAK,WAAW,IAAU,EAAK,GAC3D,cAAc,EAAO,KACrB,QAAQ,EAAO,UACf,6BAA6B,EAAO,IACtC,CAAC,CAAC,KAAK;CAAI,EACX,EAAG,YACD,CAAE,WAAY,uBAAyB,UAAS,QAAS,EAAK,EAC9D,CAAE,YAAa,GAAM,UAAW,OAAQ,CAC1C,EACI,GACF,EACE,GAAa,CAAc,CAAC,CAAC,YAAY,+BAAgC,CACvE,QAAS,YACT,mBAAoB,EAAO,MAAM,MAAQ,EACzC,iBAAkB,EAAO,QACzB,GAAI,OAAO,SAAS,KAAK,MAAM,EAAO,SAAS,CAAC,EAC5C,CAAE,YAAa,KAAK,IAAI,EAAG,KAAK,IAAI,EAAI,KAAK,MAAM,EAAO,SAAS,CAAC,CAAE,EACtE,CAAC,CACP,CAAC,CACH,CAEJ,CACF,EAGM,GAAW,EAAkB,KAAwB,CACzD,GAAI,CAAC,GAAa,CAAC,GAAgB,EAAU,OAAO,QAAQ,QAAQ,EACpE,GAAI,EAGF,MAFA,GAAgB,GAChB,IAAyB,EAClB,EAGT,IAAM,GAAa,SAAY,CAC7B,IAAI,EAAgB,EACpB,EACE,GAAgB,GAChB,EAAuB,GACvB,MAAM,GAAW,CAAa,EAC9B,EAAgB,QACT,GAAiB,CAAC,EAC7B,EAAA,CAAG,CAAC,CAAC,YAAc,CACb,IAAoB,IAAW,EAAkB,IAAA,GACvD,CAAC,EAED,MADA,GAAkB,EACX,CACT,EAEM,GAAsB,GAAyB,CAC/C,IACA,GACF,EAAoB,GAAa,CAAc,CAAC,CAAC,YAAY,6BAA8B,CAAK,CAAC,EAEnG,QAAQ,YAAY,kCAAkC,OAAO,CAAK,GAAG,EACvE,EACM,GAAmB,GAAmC,CAC1D,EAAa,CAAe,CAAC,CAAC,MAAM,EAAkB,CACxD,EACM,OAAmC,CACnC,GAAqB,GAAwB,IACjD,EAAoB,iBAAmB,CAErC,GADA,EAAoB,IAAA,GAChB,EAAU,OACd,IAAM,GAAa,SAAY,CAC7B,IAAM,EAAQ,EAAM,kBAChB,MAAM,EAAM,kBAAkB,EAAY,CAAC,EAC3C,EAAM,aAAa,EAAY,CAAC,EACpC,IAAK,IAAM,KAAS,EAAM,OAAQ,QAAQ,YAAY,CAAK,CAC7D,EAAA,CAAG,CAAC,CACD,MAAO,GAAU,QAAQ,YAAY,wCAAwC,OAAO,CAAK,GAAG,CAAC,CAAC,CAC9F,YAAc,CACT,IAAyB,IAAW,EAAuB,IAAA,GACjE,CAAC,EACH,EAAuB,CACzB,CAAC,EACD,EAAkB,QAAQ,EAC5B,EAEM,EAAa,MAAO,EAAe,IAAuD,CAC9F,GAAI,CACF,MAAM,EAAQ,CAChB,OAAS,EAAO,CACd,QAAQ,YAAY,iCAAiC,EAAM,IAAI,OAAO,CAAK,GAAG,CAChF,CACF,EAoFA,EAAiB,SAlFkC,CACjD,GAAI,EAAU,OACd,EAAS,GACT,EAAW,GACX,GAAqB,EACrB,EAAe,GACX,GAAY,cAAc,CAAU,EACxC,EAAa,IAAA,GACT,GAAmB,eAAe,CAAiB,EACvD,EAAoB,IAAA,GACpB,IAAM,EAAc,EACpB,EAAsB,IAAA,GAClB,GAAa,MAAM,EAAW,uCAAwC,CAAW,EAErF,IAAM,EAAiB,EACvB,EAAoB,IAAA,GAChB,GAAgB,MAAM,EAAW,uCAA0C,EAAe,QAAQ,CAAC,EACvG,MAAM,QAAQ,WAAW,CAAiB,EAC1C,IAAM,EAAgB,EAClB,GAAe,MAAM,EAAW,gCAAmC,CAAa,EACpF,IAAM,EAAqB,EACvB,GAAoB,MAAM,EAAW,oCAAuC,CAAkB,EAElG,MAAM,EAAW,0BAA6B,EAAQ,WAAW,CAAC,EAElE,IAAI,EAAwB,CAAC,EAC7B,GAAI,EACF,GAAI,CACF,EAAQ,MAAM,EAAS,cAAc,CAAS,CAChD,OAAS,EAAO,CACd,QAAQ,YAAY,uDAAuD,OAAO,CAAK,GAAG,CAC5F,CAEF,MAAM,QAAQ,IACZ,EAAM,IAAI,KAAO,IAAW,CAC1B,GAAI,CACF,IAAM,EAAU,MAAM,EAAkB,EAAQ,EAAU,CAAW,EACrE,GAAI,CAAC,GAAW,EAAe,QAAQ,EAAO,GAAG,EAAG,CAClD,QAAQ,YAAY,yBAAyB,EAAO,GAAG,yBAAyB,EAChF,MACF,CACA,MAAM,EAAS,SACb,EAAO,GACP,CACE,OAAQ,GACR,KAAM,KACN,OAAQ,EAAU,UAAY,KAC9B,WAAY,eACd,EACA,EAAO,SACT,CACF,OAAS,EAAO,CACd,QAAQ,YAAY,6BAA6B,EAAO,GAAG,IAAI,OAAO,CAAK,GAAG,CAChF,CACF,CAAC,CACH,EAEA,IAAM,EAAU,EACV,EAAiB,EACvB,EAAY,IAAA,GACZ,EAAiB,IAAA,GACjB,EAAY,IAAA,GACZ,EAAgB,IAAA,GAChB,EAAU,CAAC,EACX,EAAkB,MAAM,EACxB,EAAyB,MAAM,EAE/B,MAAM,QAAQ,IAAI,CAChB,EAAW,kCAAqC,EAAS,QAAQ,CAAC,EAClE,GAAI,GAAS,MACT,CAAC,EAAW,8BAAiC,EAAQ,GAAG,UAAU,EAAmB,IAAA,EAAS,CAAC,CAAC,EAChG,CAAC,CACP,CAAC,EACD,MAAM,EAAW,gCAAmC,EAAS,MAAM,CAAC,EAChE,IACF,MAAM,EAAW,uCACf,EAAe,YAAY,+BAAgC,CAAE,QAAS,SAAU,CAAC,CACnF,EACA,MAAM,EAAW,4BAA+B,EAAe,SAAS,CAAC,EAE7E,EAIA,EAAO,OAAO,CAAC,EAAmB,EAAI,GAAc,CAClD,IAAM,EAAe,GAAiB,CAAS,CAAC,CAAC,eAAe,CAC9D,OAAQ,EACR,GAAI,eACJ,MAAO,EACT,CAAC,EAED,MADA,GAAqB,MACR,CACX,EAAa,QAAQ,EACjB,IAAuB,IAAc,EAAqB,IAAA,GAChE,CACF,CAAC,EACD,EAAsB,EAAS,cAAgB,GAAgB,EAAK,CAAC,EACrE,EAAa,gBAAkB,GAAgB,EAAI,EAAG,GAAqB,EAC3E,EAAW,QAAQ,EAEnB,EAAiB,EAAI,CACnB,eAAgB,GAChB,aAAc,SAAY,CAExB,GADA,MAAM,EAAwB,EAC1B,CAAC,EAAW,MAAU,MAAM,2CAA2C,EAC3E,OAAO,CACT,EACA,gBAAkB,GAAO,CACnB,CAAC,GAAU,CAAC,IAChB,EAAyB,IAAI,CAAE,EAC/B,GAAgB,EAAK,EACvB,CACF,CAAC,EAED,EAAiC,EAAI,CACnC,aAAc,SAAY,CACxB,GAAI,CAEF,OADA,MAAM,EAAwB,EACvB,CACT,MAAQ,CACN,MACF,CACF,EACA,cAAe,KAAO,IAAoB,CACxC,GAAI,CACF,MAAM,EAAwB,CAChC,MAAQ,CACN,MAAO,CAAC,CACV,CACA,OAAO,EAAS,EAAS,cAAc,CAAe,EAAI,CAAC,CAC7D,CACF,CAAC,EAED,EAAG,gBAAgB,GAAc,CAC/B,YAAa,0DACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,GAAI,CAAC,EAAQ,OACb,IAAM,EAAa,EACb,EAAkB,EACnB,KACL,IAAI,CAAC,EAAI,MAAO,CACd,EAAI,GAAG,OAAO,qCAA0B,OAAO,EAC/C,MACF,CACA,MAAM,EAAwB,EAC9B,MAAM,EAAQ,EACV,GAAC,GAAmB,CAAC,EAAU,EAAY,CAAe,IAC9D,MAAM,EAAgB,EAAK,CACzB,eAAmB,EAAU,EAAY,CAAe,EAAI,EAAU,CAAC,EACvE,UAAY,GACV,EAAU,EAAY,CAAe,EAAK,EAAY,IAAI,CAAI,GAAK,EAAQ,IAAI,CAAI,EAAK,IAAA,GAC1F,QAAU,GACR,EAAU,EAAY,CAAe,EAAI,EAAU,KAAK,EAAS,CAAE,MAAO,GAAM,CAAC,CAAC,CAAC,KAAO,GAC5F,WAAY,MAAO,EAAI,IAAW,CAChC,GAAI,CAAC,EAAU,EAAY,CAAe,EAAG,OAC7C,IAAM,EAAS,EAAQ,KAAM,GAAc,EAAU,KAAO,CAAE,EACzD,IACD,EAAO,UAAY,QAAgB,EAAO,cAC5C,MAAM,EAAY,KAAK,EAAO,cAAe,EAAO,GAAG,EAClD,MAAM,EAAS,KAAK,EAAO,GAAG,EAChC,EAAU,EAAY,CAAe,IAC1C,MAAM,EAAS,SACb,EAAO,GACP,CAAE,OAAQ,GAAgB,KAAM,KAAM,OAAQ,KAAM,WAAY,CAAO,EACvE,EAAO,SACT,EACI,EAAU,EAAY,CAAe,GAAG,MAAM,EAAQ,GAC5D,CACF,CAAC,CAzBD,CA0BF,CACF,CAAC,EAED,EAAO,OAAO,CAAC,EAAmB,EAAI,GAAc,CAClD,IAAM,EAAe,GAAiB,CAAS,CAAC,CAAC,eAAe,CAC9D,OAAQ,EACR,SAAU,CACR,CACE,GAAI,eACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,uBAAwB,MAAO,EAAmB,EACxF,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,yBAA0B,CAC/D,EACA,QAAS,CAAE,KAAM,EAAa,CAChC,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAG,GAAG,iBAAsB,EAAQ,IAAQ,CAC1C,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,EAChB,GAAmB,CAAO,EAC1B,IAAM,EAAkB,EAAQ,eAAe,aAAa,EACtD,EAAoB,EACpB,EAAkB,EAClB,EAAa,EAAE,EACrB,EAAY,EACZ,EAAgB,EAAqB,CAAe,EACpD,EAAiB,EACjB,EAAe,GACf,EAAkB,IAAA,GAClB,EAAkB,MAAM,EACxB,EAAyB,MAAM,EAE/B,IAAM,EAAoB,KAAO,IAAoD,CACnF,IAAM,MAA8B,CAAC,EAAO,SAAW,EAAU,EAAY,CAAe,EAC5F,GAAI,CAAC,EAAa,EAAG,MAAO,CAAC,EAC7B,GAAI,GAAqB,IAAsB,EAAiB,CAC9D,MAAM,EAAW,wCAA2C,EAAQ,WAAW,CAAC,EAChF,MAAM,EAAW,4CAA+C,EAAS,QAAQ,CAAC,EAC9E,GAAiB,OACnB,MAAM,EAAW,wCACf,EAAgB,GAAG,UAAU,EAAmB,IAAA,EAAS,CAC3D,EAEF,IAAM,EAAoB,EAK1B,GAJA,EAAY,IAAA,GACR,GACF,MAAM,EAAW,0CAA6C,EAAkB,SAAS,CAAC,EAExF,CAAC,EAAa,EAAG,MAAO,CAAC,CAC/B,CAEA,IAAM,EAAkB,GAAa,CAAO,EAQ5C,GAPA,MAAM,EAAgB,YAAY,8BAA+B,CAAE,QAAS,SAAU,CAAC,EACnF,CAAC,EAAa,IAClB,EAAM,aAAa,CAAe,EAIlC,MAAM,EAAS,IAAI,CAAe,EAC9B,CAAC,EAAa,GAAG,MAAO,CAAC,EAE7B,IAAM,EAAW,MAAM,EAAS,QAAQ,CAAe,EACvD,GAAI,CAAC,EAAa,EAAG,MAAO,CAAC,EAC7B,IAAK,IAAM,KAAU,EACf,EAAO,QAAU,EAAiB,EAAkB,IAAI,EAAO,EAAE,EAC5D,EAAO,UAAU,EAAyB,IAAI,EAAO,EAAE,EAElE,IAAM,EAAS,MAAM,EAAyB,CAC5C,WACA,WACA,cACA,iBACA,eAAgB,QAAQ,IACxB,OACF,CAAC,EACD,GAAI,CAAC,EAAa,EAAG,MAAO,CAAC,EAC7B,IAAM,EAAa,MAAM,EAAuB,CAC9C,WACA,WACA,cACA,iBACA,eAAgB,QAAQ,IACxB,QAAS,EACX,CAAC,EAID,GAHI,CAAC,EAAa,IAClB,EAAe,GACf,MAAM,EAAQ,EAAK,EACf,CAAC,EAAa,GAAG,MAAO,CAAC,EAC7B,GAAqB,EACrB,IAAM,EAAS,CAAC,GAAG,EAAO,OAAQ,GAAG,EAAW,MAAM,EAChD,EAAiB,EAAO,UAAU,OAAS,EAAW,UAAU,OAStE,OARI,EAAiB,GAAK,EAAQ,OAChC,EAAQ,GAAG,OAAO,aAAa,EAAe,yBAA0B,SAAS,EAEnF,MAAM,EAAgB,YAAY,yBAA0B,CAC1D,yBAA0B,EAC1B,qBAAsB,EAAO,OAC7B,QAAS,EAAO,SAAW,EAAI,YAAc,UAC/C,CAAC,EACM,CACT,EAEM,EAAyB,EAAsB,UAAY,IAAA,EAAS,EAmBpE,GAAY,SAlBgC,CAChD,MAAM,EACD,EAAU,EAAY,CAAe,GAc1C,MAZE,GAA6B,CAAM,IAClC,IAAsB,GAA+B,CACpD,OAAS,GAAiB,CACxB,QAAQ,YACN,GAAG,EAAa,UAAU,kBAAkB,EAAa,MAAM,IAAI,EAAa,OAAO,SAAW,EAAa,YAAY,KAAK,IAAI,GACtI,CACF,CACF,CAAC,GAAA,CACwB,MAAM,EAAe,GAAG,EAAgB,GAAG,IAAc,KAAO,KAAY,CACrG,MAAO,IAAA,GACP,YAAa,MAAM,EAAkB,CAAM,CAC7C,EACW,CAAC,CAAC,KAAK,CACpB,EACkB,CAAe,EACjC,EAAwB,EAGxB,EAAoB,CAAS,CAAC,CAAC,UAAY,IAAA,EAAS,CACtD,CAAC,CACH,CAGA,eAAsB,EAAgB,EAAiC,CACrE,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAa,EAC1D,EAAQ,EAAW,KAAK,OAAO,EAAc,CAAE,IAAG,CAAC,EACzD,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAMA,SAAS,EAAa,EAAiB,EAAkC,CACvE,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,oBACN,YACE,4HACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EACD,EAAqB,EAAQ,EAAO,EAAE,CACxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","DOOM_NAVIGATION_KEYS","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","toRunnerRows"],"sources":["../../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../types/ptyHost';\nimport type { RunnerRecord } from '../types/runnerRegistry';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runnerSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","DOOM_NAVIGATION_KEYS","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","toRunnerRows"],"sources":["../../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r l` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../types/ptyHost';\nimport type { RunnerRecord } from '../types/runnerRegistry';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerSpace.mjs","names":[],"sources":["../../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../types/ptyHost';\nimport type { RunnerRecord } from '../types/runnerRegistry';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runnerSpace.mjs","names":[],"sources":["../../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r l` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../types/ptyHost';\nimport type { RunnerRecord } from '../types/runnerRegistry';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-runner",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.34",
|
|
4
4
|
"description": "Supervised shell execution, background process control, and run logs for Pi coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -301,9 +301,9 @@
|
|
|
301
301
|
"@rmux/sdk": "0.6.5",
|
|
302
302
|
"@xterm/headless": "6.0.0",
|
|
303
303
|
"typebox": "1.3.16",
|
|
304
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
305
|
-
"@agimon-ai/doompi-
|
|
306
|
-
"@agimon-ai/doompi-
|
|
304
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.34",
|
|
305
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.34",
|
|
306
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.34"
|
|
307
307
|
},
|
|
308
308
|
"devDependencies": {
|
|
309
309
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -327,14 +327,14 @@
|
|
|
327
327
|
}
|
|
328
328
|
},
|
|
329
329
|
"optionalDependencies": {
|
|
330
|
-
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.
|
|
331
|
-
"@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.
|
|
332
|
-
"@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.
|
|
333
|
-
"@agimon-ai/doompi-runner-
|
|
334
|
-
"@agimon-ai/doompi-runner-rtk-linux-
|
|
335
|
-
"@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.
|
|
336
|
-
"@agimon-ai/doompi-runner-rtk-
|
|
337
|
-
"@agimon-ai/doompi-runner-
|
|
330
|
+
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.34",
|
|
331
|
+
"@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.34",
|
|
332
|
+
"@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.34",
|
|
333
|
+
"@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.34",
|
|
334
|
+
"@agimon-ai/doompi-runner-rtk-linux-arm64": "0.0.1-alpha.34",
|
|
335
|
+
"@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.34",
|
|
336
|
+
"@agimon-ai/doompi-runner-rtk-linux-x64": "0.0.1-alpha.34",
|
|
337
|
+
"@agimon-ai/doompi-runner-rtk-darwin-arm64": "0.0.1-alpha.34"
|
|
338
338
|
},
|
|
339
339
|
"engines": {
|
|
340
340
|
"node": ">=22.19.0"
|
|
@@ -69,4 +69,4 @@ require RMUX. Eligible simple commands use a command-specific RTK stdin filter
|
|
|
69
69
|
after completion. Compound shell commands and unsupported formats keep bounded
|
|
70
70
|
raw output.
|
|
71
71
|
|
|
72
|
-
`SPC r
|
|
72
|
+
`SPC r l` opens Runner Space. Escape returns from logs or an attached terminal.
|
|
@@ -17,7 +17,7 @@ Use Runner for shell work that may outlive one tool call, needs durable logs, or
|
|
|
17
17
|
|
|
18
18
|
## Inspect and control work
|
|
19
19
|
|
|
20
|
-
Use `/runners` or `SPC r
|
|
20
|
+
Use `/runners` or `SPC r l` in the TUI. For direct control, use:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
doom-runner list
|