@agimon-ai/doompi-runner 0.0.1-alpha.28 → 0.0.1-alpha.29
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 +32 -17
- package/dist/src/adapters/pi/extension.cjs +2 -2
- package/dist/src/adapters/pi/extension.cjs.map +1 -1
- package/dist/src/adapters/pi/extension.d.cts.map +1 -1
- package/dist/src/adapters/pi/extension.d.mts.map +1 -1
- package/dist/src/adapters/pi/extension.mjs +2 -2
- package/dist/src/adapters/pi/extension.mjs.map +1 -1
- package/dist/src/commands/bash/bashTool.cjs +5 -6
- package/dist/src/commands/bash/bashTool.cjs.map +1 -1
- package/dist/src/commands/bash/bashTool.d.cts +1 -1
- package/dist/src/commands/bash/bashTool.d.cts.map +1 -1
- package/dist/src/commands/bash/bashTool.d.mts +1 -1
- package/dist/src/commands/bash/bashTool.d.mts.map +1 -1
- package/dist/src/commands/bash/bashTool.mjs +5 -6
- package/dist/src/commands/bash/bashTool.mjs.map +1 -1
- package/llms.txt +9 -0
- package/package.json +14 -9
- package/src/prompts/doompi-use-runner/SKILL.md +38 -0
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Supervised shell execution, background process control, durable logs, and Runner
|
|
|
4
4
|
|
|
5
5
|
Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
|
|
6
6
|
|
|
7
|
-
Runner replaces Pi's built-in `bash` tool only in sessions that load it. Short commands return
|
|
7
|
+
Runner replaces Pi's built-in `bash` tool only in sessions that load it. Short commands return
|
|
8
|
+
inline; long commands can be promoted to a background runner instead of blocking the agent.
|
|
8
9
|
|
|
9
10
|
> **Alpha:** tool and process contracts may change between releases.
|
|
10
11
|
|
|
@@ -15,7 +16,8 @@ Runner replaces Pi's built-in `bash` tool only in sessions that load it. Short c
|
|
|
15
16
|
- `/bin/bash`
|
|
16
17
|
- macOS or Linux on arm64 or x64 for bundled RMUX support
|
|
17
18
|
|
|
18
|
-
Linux native loading also depends on a compatible system loader
|
|
19
|
+
Linux native loading also depends on a compatible system loader and libc. Runner uses a `node-pty`
|
|
20
|
+
fallback when RMUX is unavailable.
|
|
19
21
|
|
|
20
22
|
## Install
|
|
21
23
|
|
|
@@ -26,7 +28,9 @@ installation:
|
|
|
26
28
|
pi install npm:@agimon-ai/doompi-runner
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
Pi discovers Runner's sole extension factory from `package.json.pi.extensions`. The
|
|
31
|
+
Pi discovers Runner's sole extension factory from `package.json.pi.extensions`. The extension joins
|
|
32
|
+
the runner-scoped Cordis host and releases its plugin resources and host lease when the Pi session
|
|
33
|
+
shuts down.
|
|
30
34
|
|
|
31
35
|
## Use the `bash` tool
|
|
32
36
|
|
|
@@ -37,9 +41,12 @@ Pi discovers Runner's sole extension factory from `package.json.pi.extensions`.
|
|
|
37
41
|
}
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
Commands crossing the promotion threshold move to a supervised runner. The default threshold is 60
|
|
44
|
+
Commands crossing the promotion threshold move to a supervised runner. The default threshold is 60
|
|
45
|
+
seconds. Set `background: true` to detach immediately, or `interactive: true` only when a command
|
|
46
|
+
needs terminal input.
|
|
41
47
|
|
|
42
|
-
Runner requires `PI_SESSION_ID` for session ownership. Processes, controls, and logs remain
|
|
48
|
+
Runner requires `PI_SESSION_ID` for session ownership. Processes, controls, and logs remain
|
|
49
|
+
addressable after transcript compaction because their identity is outside model context.
|
|
43
50
|
|
|
44
51
|
## Inspect and control runs
|
|
45
52
|
|
|
@@ -49,37 +56,44 @@ Use `/runners` or `SPC r r` in the TUI. The `doom-runner` CLI supports:
|
|
|
49
56
|
doom-runner list
|
|
50
57
|
doom-runner status <runner-id>
|
|
51
58
|
doom-runner logs <runner-id>
|
|
52
|
-
doom-runner input <runner-id> "y"
|
|
59
|
+
doom-runner input <runner-id> --text "y" --enter
|
|
53
60
|
doom-runner stop <runner-id>
|
|
54
61
|
doom-runner stop-all
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
The `input` command requires a running interactive process backed by RMUX.
|
|
65
|
+
|
|
57
66
|
Stop runs that are no longer required.
|
|
58
67
|
|
|
59
68
|
## Storage and limits
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
By default, session-scoped logs and registry data live under:
|
|
62
71
|
|
|
63
72
|
```text
|
|
64
73
|
~/.pi/agent/doom-runner/<session>/
|
|
65
74
|
```
|
|
66
75
|
|
|
67
|
-
Logs rotate at 5 MiB by default and are eligible for cleanup after seven days. Limits and the
|
|
76
|
+
Logs rotate at 5 MiB by default and are eligible for cleanup after seven days. Limits and the
|
|
77
|
+
promotion threshold can be configured through Runner's exported configuration helpers and
|
|
78
|
+
environment-backed runtime settings.
|
|
68
79
|
|
|
69
|
-
Commands inherit the DoomPi process environment and the operating
|
|
80
|
+
Commands inherit the DoomPi process environment and the operating system user's privileges. Logs
|
|
81
|
+
may contain prompts, source, command output, credentials, or other secrets; restrict access and
|
|
82
|
+
retention accordingly.
|
|
70
83
|
|
|
71
84
|
## Native artifacts
|
|
72
85
|
|
|
73
86
|
Runner selects one optional dependency automatically:
|
|
74
87
|
|
|
75
|
-
| Host | Package
|
|
76
|
-
| ----------- |
|
|
77
|
-
| macOS arm64 |
|
|
78
|
-
| macOS x64 |
|
|
79
|
-
| Linux arm64 |
|
|
80
|
-
| Linux x64 |
|
|
88
|
+
| Host | Package |
|
|
89
|
+
| ----------- | -------------------------------------------- |
|
|
90
|
+
| macOS arm64 | `@agimon-ai/doompi-runner-rmux-darwin-arm64` |
|
|
91
|
+
| macOS x64 | `@agimon-ai/doompi-runner-rmux-darwin-x64` |
|
|
92
|
+
| Linux arm64 | `@agimon-ai/doompi-runner-rmux-linux-arm64` |
|
|
93
|
+
| Linux x64 | `@agimon-ai/doompi-runner-rmux-linux-x64` |
|
|
81
94
|
|
|
82
|
-
Do not install these
|
|
95
|
+
Do not install these platform packages directly. They contain native RMUX executables and export
|
|
96
|
+
only package metadata.
|
|
83
97
|
|
|
84
98
|
## Public API
|
|
85
99
|
|
|
@@ -93,7 +107,8 @@ import {
|
|
|
93
107
|
} from '@agimon-ai/doompi-runner';
|
|
94
108
|
```
|
|
95
109
|
|
|
96
|
-
Declared subpaths expose process supervision, configuration, tools, response envelopes, and TUI
|
|
110
|
+
Declared subpaths expose process supervision, configuration, tools, response envelopes, and TUI
|
|
111
|
+
integration for host authors.
|
|
97
112
|
|
|
98
113
|
## Development
|
|
99
114
|
|
|
@@ -1,3 +1,3 @@
|
|
|
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");let s=require("@agimon-ai/doompi-extension-contracts/child-process"),c=require("@agimon-ai/doompi-extension-contracts/cordis-host"),l=require("@agimon-ai/doompi-extension-contracts/
|
|
2
|
-
`);
|
|
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");let s=require("@agimon-ai/doompi-extension-contracts/child-process"),c=require("@agimon-ai/doompi-extension-contracts/cordis-host"),l=require("@agimon-ai/doompi-extension-contracts/help"),u=require("@agimon-ai/doompi-extension-contracts/readiness"),d=require("@agimon-ai/doompi-extension-contracts/ui-hub"),f=require("@agimon-ai/doompi-telemetry");const p=`@agimon-ai/doompi-runner`,m=`runners`,h=`completed`,ee=`stopped`,g=`doom-runner-runners`;function _(c,l){let _=n.createRunnerContainer(),v=_.runnerRegistry,y=async()=>{try{v.close()}catch(e){process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(e)}`)}};c.effect(()=>()=>y(),`${p}/runtime`);let b=_.launcher,x=_.rmuxBackend,te=_.logReader,S=_.ptyHost,ne=_.bashRunService,C=_.paths,w=_.processControl,T=_.lifeline,E=!0,D=0,O,k,A=[],j,M,N,P=!1,F=!1,I,L,R,z,B,re=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 re,!E||!P||!O)throw Error(`doom-runner requires an active Pi session`)},ie={run:async e=>(await Y(),J(ne.run(e)))},X=(e,t=O)=>E&&!F&&e===D&&t===O,Z=e=>(N??=(0,f.createDoomTelemetry)({serviceName:`doom-runner`,packageName:`@agimon-ai/doompi-runner`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),N),ae=async e=>{let t=O,n=D;if(!t||!X(n,t))return;let i=await v.list();if(!X(n,t))return;let o=i;if(e){let e=await r.reconcileActiveRunners({registry:v,launcher:b,rmuxBackend:x,processControl:w,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;A=o.filter(e=>e.sessionId===t);let s=(k?o.filter(e=>(e.rootSessionId??e.sessionId)===k):[]).length;W!==s&&(W=s,M?.update(a.formatRunnerFooterContribution(s)),j?.hasUI&&j.ui.setStatus(g,a.formatRunnerStatus(s)));let c=[...K],u=await Promise.all(c.map(e=>v.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`}),j&&J(Z(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 H=!0,U||=e,I;let t=(async()=>{let t=e;do H=!1,U=!1,await ae(t),t=U;while(H&&!F)})().finally(()=>{I===t&&(I=void 0)});return I=t,t},oe=e=>{F||(j&&J(Z(j).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},se=e=>{Q(e).catch(oe)},ce=()=>{R||L||F||(R=setImmediate(()=>{if(R=void 0,F)return;let e=(async()=>{let e=C.sweepHistoryAsync?await C.sweepHistoryAsync(t.getLogTtlMs()):C.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(()=>{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)}`)}};y=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=V;V=void 0,t&&await $(`cancel standalone readiness work`,()=>t.dispose()),await Promise.allSettled(q);let n=I;n&&await $(`settle the runner refresh`,()=>n);let i=L;i&&await $(`settle runner history cleanup`,()=>i),await $(`dispose runner PTYs`,()=>S.disposeAll());let a=[];if(O)try{a=await v.listBySession(O)}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,b,x);if(!t&&w.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await v.complete(e.id,{reason:ee,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=j,s=N;N=void 0,j=void 0,O=void 0,k=void 0,A=[],G.clear(),K.clear(),await Promise.all([$(`dispose the runner lifeline`,()=>T.dispose()),...o?.hasUI?[$(`clear the runner status`,()=>o.ui.setStatus(g,void 0))]:[]]),await $(`close the runner registry`,()=>v.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},c.inject([d.DOOM_UI_HUB_SERVICE],e=>{let t=(0,d.requireDoomUiHub)(e).registerFooter({source:p,id:`runner-count`,order:10});return M=t,()=>{t.dispose(),M===t&&(M=void 0)}}),B=v.subscribe(()=>se(!1)),z=setInterval(()=>se(!0),500),z.unref?.(),i.registerBashTool(l,{bashRunService:ie,getSessionId:async()=>{if(await Y(),!O)throw Error(`doom-runner requires an active Pi session`);return O},onRunnerStarted:e=>{!E||!P||(K.add(e),se(!1))}}),e.registerRunnerCompactionRecovery(l,{getSessionId:async()=>{try{return await Y(),O}catch{return}},listBySession:async e=>{try{await Y()}catch{return[]}return E?v.listBySession(e):[]}}),l.registerCommand(m,{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 Y(),await Q(),!(!r||!X(n,r))&&await o.openRunnerSpace(t,{getRunners:()=>X(n,r)?A:[],getPtyRun:e=>X(n,r)?x.get(e)??S.get(e):void 0,readLog:e=>X(n,r)?te.read(e,{lines:1e3}).text:``,stopRunner:async(e,t)=>{if(!X(n,r))return;let i=A.find(t=>t.id===e);i&&(i.backend===`rmux`&&i.backendTarget?await x.stop(i.backendTarget,i.pid):await b.stop(i.pid),X(n,r)&&(await v.complete(i.id,{reason:ee,code:null,signal:null,stopReason:t},i.sessionId),X(n,r)&&await Q()))}})}}}),c.inject([d.DOOM_UI_HUB_SERVICE],e=>{let t=(0,d.requireDoomUiHub)(e).registerLeader({source:p,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,order:67},{key:`r`,label:`open`,detail:`background processes`}],command:{name:m}}]});return()=>t.dispose()}),l.on(`session_start`,(e,t)=>{if(!E)return;let n=t,i=n.sessionManager.getSessionId(),a=O,o=j,l=++D;O=i,k=(0,s.resolveRootSessionId)(i),j=n,P=!1,W=void 0,G.clear(),K.clear();let d=async e=>{let t=()=>!e.aborted&&X(l,i);if(!t())return[];if(a&&a!==i){await $(`dispose the previous session PTYs`,()=>S.disposeAll()),await $(`dispose the previous session lifeline`,()=>T.dispose()),o?.hasUI&&await $(`clear the previous session status`,()=>o.ui.setStatus(g,void 0));let e=N;if(N=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()||(C.setSessionId(i),await T.arm(i),!t()))return[];let c=await v.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:v,launcher:b,rmuxBackend:x,processControl:w,currentHostPid:process.pid,paths:C});if(!t())return[];let d=await r.reconcileActiveRunners({registry:v,launcher:b,rmuxBackend:x,processControl:w,currentHostPid:process.pid,startup:!0});if(!t()||(P=!0,await Q(!1),!t()))return[];ce();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=re.catch(()=>void 0),m=(async()=>{await f,X(l,i)&&await((0,u.readDoomReadinessCoordinator)(c)??(V??=(0,u.createDoomReadinessCoordinator)({notify:e=>{process.emitWarning(`${e.packageId} initialization ${e.state}: ${e.error?.message??e.diagnostics.join(`; `)}`)}}))).start(p,`${i}:${l}`,async e=>({value:void 0,diagnostics:await d(e)})).wait()})();re=m,J(m).catch(()=>void 0)})}async function v(e){let t=await(0,c.connectDoomCordisHost)(e,p),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([l.DOOM_HELP_SERVICE],e=>{let t=(0,l.requireDoomHelpService)(e).register({source:p,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":["createRunnerContainer","createDoomTelemetry","reconcileActiveRunners","formatRunnerFooterContribution","formatRunnerStatus","getLogTtlMs","stopRunnerProcess","DOOM_UI_HUB_SERVICE","requireDoomUiHub","openRunnerSpace","resolveRootSessionId","cleanupLegacyRunnerStore","readDoomReadinessCoordinator","createDoomReadinessCoordinator","connectDoomCordisHost"],"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 {\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\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 * 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 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 installRunnerRuntime(cordis, config.pi);\n}\n\nexport default runnerExtension;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extension.cjs","names":["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\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 * 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 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.cts","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"extension.d.cts","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAmjBsB,gBAAgB,IAAI,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAmjBsB,gBAAgB,IAAI,eAAe"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{registerRunnerCompactionRecovery as e}from"../../services/runs/compaction.mjs";import{getLogTtlMs as t}from"../../types/config.mjs";import{createRunnerContainer as n}from"../../container/index.mjs";import{cleanupLegacyRunnerStore as r,reconcileActiveRunners as i,stopRunnerProcess as a}from"../../services/runs/reconcile.mjs";import{registerBashTool as o}from"../../commands/bash/bashTool.mjs";import{formatRunnerFooterContribution as s,formatRunnerStatus as c}from"../../tui/format.mjs";import{openRunnerSpace as l}from"../../tui/runnerSpace.mjs";import{resolveRootSessionId as u}from"@agimon-ai/doompi-extension-contracts/child-process";import{connectDoomCordisHost as d}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{createDoomReadinessCoordinator as
|
|
2
|
-
`);
|
|
1
|
+
import{registerRunnerCompactionRecovery as e}from"../../services/runs/compaction.mjs";import{getLogTtlMs as t}from"../../types/config.mjs";import{createRunnerContainer as n}from"../../container/index.mjs";import{cleanupLegacyRunnerStore as r,reconcileActiveRunners as i,stopRunnerProcess as a}from"../../services/runs/reconcile.mjs";import{registerBashTool as o}from"../../commands/bash/bashTool.mjs";import{formatRunnerFooterContribution as s,formatRunnerStatus as c}from"../../tui/format.mjs";import{openRunnerSpace as l}from"../../tui/runnerSpace.mjs";import{resolveRootSessionId as u}from"@agimon-ai/doompi-extension-contracts/child-process";import{connectDoomCordisHost as d}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as f,requireDoomHelpService as p}from"@agimon-ai/doompi-extension-contracts/help";import{createDoomReadinessCoordinator as m,readDoomReadinessCoordinator as ee}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as te,requireDoomUiHub as ne}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as re}from"@agimon-ai/doompi-telemetry";const h=`@agimon-ai/doompi-runner`,ie=`runners`,g=`completed`,ae=`stopped`,_=`doom-runner-runners`;function v(d,f){let p=n(),v=p.runnerRegistry,y=async()=>{try{v.close()}catch(e){process.emitWarning(`Could not close a partially installed doom-runner registry: ${String(e)}`)}};d.effect(()=>()=>y(),`${h}/runtime`);let b=p.launcher,x=p.rmuxBackend,oe=p.logReader,S=p.ptyHost,se=p.bashRunService,C=p.paths,w=p.processControl,ce=p.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`)},le={run:async e=>(await Y(),J(se.run(e)))},X=(e,t=D)=>T&&!P&&e===E&&t===D,Z=e=>(M??=re({serviceName:`doom-runner`,packageName:`@agimon-ai/doompi-runner`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),M),ue=async e=>{let t=D,n=E;if(!t||!X(n,t))return;let r=await v.list();if(!X(n,t))return;let a=r;if(e){let e=await i({registry:v,launcher:b,rmuxBackend:x,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);a=r.filter(e=>!t.has(e.id))}}if(!X(n,t))return;k=a.filter(e=>e.sessionId===t);let o=(O?a.filter(e=>(e.rootSessionId??e.sessionId)===O):[]).length;W!==o&&(W=o,j?.update(s(o)),A?.hasUI&&A.ui.setStatus(_,c(o)));let l=[...K],u=await Promise.all(l.map(e=>v.get(e,t)));if(X(n,t))for(let[e,n]of u.entries()){let r=l[e];if(!r)continue;if(!n||n.sessionId!==t){K.delete(r);continue}if(n.state!==g||(K.delete(n.id),G.has(n.id)))continue;G.add(n.id);let i=n.exit?.reason??g,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
|
+
`);f.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 ue(t),t=U;while(H&&!P)})().finally(()=>{F===t&&(F=void 0)});return F=t,t},de=e=>{P||(A&&J(Z(A).recordError(`doom_runner.refresh_failed`,e)),process.emitWarning(`Could not refresh doom-runner: ${String(e)}`))},fe=e=>{Q(e).catch(de)},pe=()=>{L||I||P||(L=setImmediate(()=>{if(L=void 0,P)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(()=>{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)}`)}};y=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 r=I;r&&await $(`settle runner history cleanup`,()=>r),await $(`dispose runner PTYs`,()=>S.disposeAll());let i=[];if(D)try{i=await v.listBySession(D)}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 a(e,b,x);if(!t&&w.isAlive(e.pid)){process.emitWarning(`Could not stop runner ${e.id} during session shutdown`);return}await v.complete(e.id,{reason:ae,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`,()=>ce.dispose()),...o?.hasUI?[$(`clear the runner status`,()=>o.ui.setStatus(_,void 0))]:[]]),await $(`close the runner registry`,()=>v.close()),s&&(await $(`record the runner session finish`,()=>s.recordEvent(`doom_runner.session_finished`,{outcome:`stopped`})),await $(`stop runner telemetry`,()=>s.shutdown()))},d.inject([te],e=>{let t=ne(e).registerFooter({source:h,id:`runner-count`,order:10});return j=t,()=>{t.dispose(),j===t&&(j=void 0)}}),z=v.subscribe(()=>fe(!1)),R=setInterval(()=>fe(!0),500),R.unref?.(),o(f,{bashRunService:le,getSessionId:async()=>{if(await Y(),!D)throw Error(`doom-runner requires an active Pi session`);return D},onRunnerStarted:e=>{!T||!N||(K.add(e),fe(!1))}}),e(f,{getSessionId:async()=>{try{return await Y(),D}catch{return}},listBySession:async e=>{try{await Y()}catch{return[]}return T?v.listBySession(e):[]}}),f.registerCommand(ie,{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 l(t,{getRunners:()=>X(n,r)?k:[],getPtyRun:e=>X(n,r)?x.get(e)??S.get(e):void 0,readLog:e=>X(n,r)?oe.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 x.stop(i.backendTarget,i.pid):await b.stop(i.pid),X(n,r)&&(await v.complete(i.id,{reason:ae,code:null,signal:null,stopReason:t},i.sessionId),X(n,r)&&await Q()))}})}}}),d.inject([te],e=>{let t=ne(e).registerLeader({source:h,bindings:[{id:`runners.open`,path:[{key:`r`,label:`runners`,order:67},{key:`r`,label:`open`,detail:`background processes`}],command:{name:ie}}]});return()=>t.dispose()}),f.on(`session_start`,(e,t)=>{if(!T)return;let n=t,a=n.sessionManager.getSessionId(),o=D,s=A,c=++E;D=a,O=u(a),A=n,N=!1,W=void 0,G.clear(),K.clear();let l=async e=>{let t=()=>!e.aborted&&X(c,a);if(!t())return[];if(o&&o!==a){await $(`dispose the previous session PTYs`,()=>S.disposeAll()),await $(`dispose the previous session lifeline`,()=>ce.dispose()),s?.hasUI&&await $(`clear the previous session status`,()=>s.ui.setStatus(_,void 0));let e=M;if(M=void 0,e&&await $(`stop the previous session telemetry`,()=>e.shutdown()),!t())return[]}let l=Z(n);if(await l.recordEvent(`doom_runner.session_started`,{outcome:`started`}),!t()||(C.setSessionId(a),await ce.arm(a),!t()))return[];let u=await v.listAll(a);if(!t())return[];for(let e of u)e.state===g?G.add(e.id):e.promoted&&K.add(e.id);let d=await r({registry:v,launcher:b,rmuxBackend:x,processControl:w,currentHostPid:process.pid,paths:C});if(!t())return[];let f=await i({registry:v,launcher:b,rmuxBackend:x,processControl:w,currentHostPid:process.pid,startup:!0});if(!t()||(N=!0,await Q(!1),!t()))return[];pe();let p=[...d.errors,...f.errors],m=d.reclaimed.length+f.reclaimed.length;return m>0&&n.hasUI&&n.ui.notify(`Reclaimed ${m} stale runner record(s)`,`warning`),await l.recordEvent(`doom_runner.reconciled`,{"runner.reclaimed_count":m,"runner.error_count":p.length,outcome:p.length===0?`completed`:`degraded`}),p},f=B.catch(()=>void 0),p=(async()=>{await f,X(c,a)&&await(ee(d)??(V??=m({notify:e=>{process.emitWarning(`${e.packageId} initialization ${e.state}: ${e.error?.message??e.diagnostics.join(`; `)}`)}}))).start(h,`${a}:${c}`,async e=>({value:void 0,diagnostics:await l(e)})).wait()})();B=p,J(p).catch(()=>void 0)})}async function y(e){let t=await d(e,h),n=t.root.plugin(b,{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 b(e,t){e.inject([f],e=>{let t=p(e).register({source:h,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()}),v(e,t.pi)}export{y as default,y as runnerExtension,v 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 {\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\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 * 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 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 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\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 * 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 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,7 +1,6 @@
|
|
|
1
|
-
const e=require("../../types/config.cjs"),t=require("../../services/AnsiScrub/ansiScrub.cjs"),n=require("
|
|
2
|
-
`),{cause:e})}return
|
|
3
|
-
`));if(e.kind===`promoted`){let t=[`${
|
|
4
|
-
`);return
|
|
5
|
-
`),
|
|
6
|
-
`));return r.textResult(c,l)}exports.BASH_PROMPT_SNIPPET=c,exports.bashPromptGuidelines=l,exports.formatRunResult=d,exports.registerBashTool=u;
|
|
1
|
+
const e=require("../../types/config.cjs"),t=require("../../services/AnsiScrub/ansiScrub.cjs"),n=require("./responseEnvelope.cjs"),r=require("../../schemas/bashTool.cjs"),i=require("../../tui/bashRender.cjs"),a=1e3,o={requested:`Started in the background`,threshold:`Still running after the background threshold`,interactive:`Started interactively`},s=`Execute shell commands with bounded foreground output and supervised background runners`;function c(t=e.getBackgroundThresholdMs()){return[`A command still running after ${Math.round(t/a)} seconds remains active as a background runner with an id and streaming log path.`,`Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.`,`Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.`,`On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.`,`Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.`]}function l(e,t){e.registerTool({name:r.BASH_TOOL_NAME,label:r.BASH_TOOL_LABEL,description:`Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.`,promptSnippet:s,promptGuidelines:c(),parameters:r.BashParamsSchema,renderShell:`self`,async execute(e,r,i,o,s){let{command:c,timeout:l,background:u,interactive:d,name:p}=r,m;o&&o(n.textResult(`Starting ${d===!0?`interactive runner`:u===!0?`background runner`:`command`}...`)),u!==!0&&d!==!0&&o&&(m=e=>o(n.textResult(e)));let h;try{h=await t.bashRunService.run({command:c,timeoutMs:l===void 0?void 0:l*a,background:u,interactive:d,name:p,...m?{onOutput:m}:{},sessionId:await t.getSessionId()})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error([`Could not execute command: ${t}`,`Next: verify the command, runtime, and working directory. Retry only after correcting the cause.`].join(`
|
|
2
|
+
`),{cause:e})}return h.kind===`promoted`&&t.onRunnerStarted(h.id),f(h)},renderCall(e,t,n){return i.renderBashCall(e,t)},renderResult(e,t,n,r){return i.renderBashResult(e,{...t,isError:r.isError},n)}})}function u(e){return e.timedOut===!0||e.signal!==null||e.exitCode!==null&&e.exitCode!==0}function d(e){if(e.timedOut===!0)return`Timed out: exceeded the requested timeout.`;if(e.signal!==null)return`Signal: ${e.signal}`;if(e.exitCode===null)return`Exit status unavailable.`;if(e.exitCode!==0)return`Exit: ${e.exitCode}`}function f(e){if(e.kind===`failed`)throw Error([`Could not start runner "${e.name}": ${e.error}`,`Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.`].join(`
|
|
3
|
+
`));if(e.kind===`promoted`){let t=[`${o[e.reason]}: runner "${e.name}" (${e.id}).`,`Streaming log: ${e.logPath}`,`Inspect: doom-runner logs ${e.id}`].join(`
|
|
4
|
+
`);return n.textResult(t,{id:e.id,runner:e.name,pid:e.pid,logPath:e.logPath,promoted:!0,reason:e.reason})}let r=n.summarizeLog(e.logPath),i=r.tail.length===0&&e.output.length>0,a=i?e.output:r.tail,s=i?n.countLines(a):r.tailLines,c=Math.max(r.lines,s),l=c>s,f=t.stripAnsi(a).replace(/\r?\n$/,``),p=u(e),m=d(e),h=[];f.length===0?h.push(m===void 0?`Completed with no output.`:`No output.`):l?h.push(`Log tail (${s.toLocaleString(`en-US`)} of ${c.toLocaleString(`en-US`)} lines):\n${f}`):h.push(f),m!==void 0&&h.push(m),l?h.push(`Full log: ${e.logPath} (${n.formatSize(r.bytes)}, ${c.toLocaleString(`en-US`)} lines); inspect with doom-runner logs ${e.id}`):p&&f.length===0&&h.push(`Log: ${e.logPath}`,`Next: run one read-only diagnostic; retry only after correcting the cause.`);let g=h.join(`
|
|
5
|
+
`),_={id:e.id,runner:e.name,exitCode:e.exitCode,logPath:e.logPath,backend:e.backend,fileSize:r.bytes,lines:c,tail:a,tailLines:s,...e.timedOut?{timedOut:!0}:{}};if(p)throw Error(g);return n.textResult(g,_)}exports.BASH_PROMPT_SNIPPET=s,exports.bashPromptGuidelines=c,exports.formatRunResult=f,exports.registerBashTool=l;
|
|
7
6
|
//# sourceMappingURL=bashTool.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashTool.cjs","names":["getBackgroundThresholdMs","BASH_TOOL_NAME","BASH_TOOL_LABEL","BashParamsSchema","textResult","renderBashCall","renderBashResult","summarizeLog","stripAnsi","formatSize"],"sources":["../../../../src/commands/bash/bashTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, type BashParams, BashParamsSchema } from '../../schemas/bashTool.ts';\nimport { stripAnsi } from '../../services/AnsiScrub/ansiScrub';\nimport type { BashRunResult, IBashRunService } from '../../types/bashRunService';\nimport { renderBashCall, renderBashResult } from '../../tui/bashRender.ts';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { formatSize, summarizeLog, type ToolResult, textResult } from './responseEnvelope.ts';\n\nconst MS_PER_SECOND = 1000;\nconst ERROR_OPTIONS_HEADING = 'Options:';\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold, so it was moved to the background',\n interactive: 'Started on a terminal so it can prompt, which means it runs in the background',\n};\n\nexport const BASH_PROMPT_SNIPPET = 'Execute bash commands, with long-running ones supervised in the background';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner. You get its unique id and streaming log path, then inspect it with the doom-runner CLI.`,\n 'Pass background: true for commands you already know will not finish on their own (dev servers, watchers, tails). Waiting the full threshold first wastes a turn.',\n 'Pass interactive: true only when the command will prompt for confirmation or input. Use Runner Space for terminal input. Interactive logs are noisier, so avoid it otherwise.',\n 'Do not re-run a command to recover output. Read its saved log with doom-runner logs <id>.',\n 'Stop runners you no longer need. Every runner is stopped automatically when the session ends.',\n ];\n}\n\nexport interface BashToolDependencies {\n bashRunService: IBashRunService;\n getSessionId(): string | Promise<string>;\n /** Called after a runner is promoted, so UI state can refresh. */\n onRunnerStarted(id: string): void;\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function registerBashTool(pi: ExtensionAPI, dependencies: BashToolDependencies): void {\n pi.registerTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute a bash command in the current working directory. A command that outlives the background threshold keeps running under doom-runner and returns a unique id plus a streaming log path. Completed commands return a bounded log tail with exact file metadata. Optionally provide a timeout in seconds, background: true to background it immediately, or interactive: true if it will prompt.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute the command: ${message}`,\n '',\n ERROR_OPTIONS_HEADING,\n '- Check the command, runtime, and working directory, then retry once.',\n '- Use a read-only diagnostic to confirm the missing dependency or environment value.',\n '- Ask the user if recovery requires changing the requested approach.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result);\n },\n\n // Without these, pi falls back to echoing the raw command and the tail end of\n // the result text, which is the metadata footer rather than the log output.\n renderCall(args, theme, _context) {\n return renderBashCall(args as BashParams, theme);\n },\n\n renderResult(result, options, theme, context) {\n return renderBashResult(result, { ...options, isError: context.isError }, theme);\n },\n });\n}\n\nexport function formatRunResult(result: BashRunResult): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n '',\n ERROR_OPTIONS_HEADING,\n '- Correct the command or missing executable, then retry once.',\n '- Use a read-only diagnostic to inspect the runtime environment.',\n '- Ask the user if a different execution approach is required.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason} as runner \"${result.name}\".`,\n `Runner ID: ${result.id}`,\n `Backend: ${result.backend}`,\n `Log (streaming): ${result.logPath}`,\n `Inspect it with: doom-runner logs ${result.id}`,\n ]\n .filter((line) => line.length > 0)\n .join('\\n');\n\n return {\n ...textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n }),\n };\n }\n\n const log = summarizeLog(result.logPath);\n const notes: string[] = [];\n if (result.timedOut) notes.push('Command stopped: it exceeded the requested timeout.');\n else if (result.exitCode !== null) notes.push(`Exit: completed with code ${result.exitCode}.`);\n else if (result.signal !== null) notes.push(`Terminated by ${result.signal}`);\n\n // The model reads plain text; only the renderer gets the coloured copy below.\n const plainTail = stripAnsi(log.tail);\n const text = [\n plainTail.length > 0 ? `Log tail (${log.tailLines} lines):\\n${plainTail}` : 'Log tail: (no output)',\n `Runner ID: ${result.id}`,\n ...notes,\n `Log: ${result.logPath}`,\n `File size: ${formatSize(log.bytes)}`,\n `Lines: ${log.lines.toLocaleString('en-US')}`,\n ].join('\\n');\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: log.lines,\n // Carried structurally so renderResult shows the output instead of the footer.\n tail: log.tail,\n tailLines: log.tailLines,\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n if (result.timedOut || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0)) {\n throw new Error(\n [\n text,\n '',\n ERROR_OPTIONS_HEADING,\n `- Inspect the full log at ${result.logPath}, correct the cause, then retry once.`,\n '- Run a read-only diagnostic if the output does not explain the failure.',\n '- Ask the user if the failure requires changing the requested approach.',\n ].join('\\n'),\n );\n }\n return textResult(text, details);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bashTool.cjs","names":["getBackgroundThresholdMs","BASH_TOOL_NAME","BASH_TOOL_LABEL","BashParamsSchema","textResult","renderBashCall","renderBashResult","summarizeLog","countLines","stripAnsi","formatSize"],"sources":["../../../../src/commands/bash/bashTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, type BashParams, BashParamsSchema } from '../../schemas/bashTool.ts';\nimport { stripAnsi } from '../../services/AnsiScrub/ansiScrub';\nimport type { BashRunResult, CompletedRun, IBashRunService } from '../../types/bashRunService';\nimport { renderBashCall, renderBashResult } from '../../tui/bashRender.ts';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { countLines, formatSize, summarizeLog, type ToolResult, textResult } from './responseEnvelope.ts';\n\nconst MS_PER_SECOND = 1000;\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold',\n interactive: 'Started interactively',\n};\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\nexport interface BashToolDependencies {\n bashRunService: IBashRunService;\n getSessionId(): string | Promise<string>;\n /** Called after a runner is promoted, so UI state can refresh. */\n onRunnerStarted(id: string): void;\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function registerBashTool(pi: ExtensionAPI, dependencies: BashToolDependencies): void {\n pi.registerTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result);\n },\n\n // Without these, pi falls back to echoing the raw command and the tail end of\n // the result text, which is the metadata footer rather than the log output.\n renderCall(args, theme, _context) {\n return renderBashCall(args as BashParams, theme);\n },\n\n renderResult(result, options, theme, context) {\n return renderBashResult(result, { ...options, isError: context.isError }, theme);\n },\n });\n}\n\nfunction completionFailed(result: CompletedRun): boolean {\n return result.timedOut === true || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0);\n}\n\nfunction completionStatus(result: CompletedRun): string | undefined {\n if (result.timedOut === true) return 'Timed out: exceeded the requested timeout.';\n if (result.signal !== null) return `Signal: ${result.signal}`;\n if (result.exitCode === null) return 'Exit status unavailable.';\n if (result.exitCode !== 0) return `Exit: ${result.exitCode}`;\n return undefined;\n}\n\nexport function formatRunResult(result: BashRunResult): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n 'Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason}: runner \"${result.name}\" (${result.id}).`,\n `Streaming log: ${result.logPath}`,\n `Inspect: doom-runner logs ${result.id}`,\n ].join('\\n');\n\n return textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n });\n }\n\n const log = summarizeLog(result.logPath);\n const useCapturedOutput = log.tail.length === 0 && result.output.length > 0;\n const tail = useCapturedOutput ? result.output : log.tail;\n const tailLines = useCapturedOutput ? countLines(tail) : log.tailLines;\n const totalLines = Math.max(log.lines, tailLines);\n const truncated = totalLines > tailLines;\n const plainTail = stripAnsi(tail).replace(/\\r?\\n$/, '');\n const failed = completionFailed(result);\n const status = completionStatus(result);\n const textLines: string[] = [];\n\n if (plainTail.length === 0) {\n textLines.push(status === undefined ? 'Completed with no output.' : 'No output.');\n } else if (truncated) {\n textLines.push(\n `Log tail (${tailLines.toLocaleString('en-US')} of ${totalLines.toLocaleString('en-US')} lines):\\n${plainTail}`,\n );\n } else {\n textLines.push(plainTail);\n }\n if (status !== undefined) textLines.push(status);\n\n if (truncated) {\n textLines.push(\n `Full log: ${result.logPath} (${formatSize(log.bytes)}, ${totalLines.toLocaleString('en-US')} lines); inspect with doom-runner logs ${result.id}`,\n );\n } else if (failed && plainTail.length === 0) {\n textLines.push(\n `Log: ${result.logPath}`,\n 'Next: run one read-only diagnostic; retry only after correcting the cause.',\n );\n }\n\n const text = textLines.join('\\n');\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: totalLines,\n tail,\n tailLines,\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n\n if (failed) throw new Error(text);\n return textResult(text, details);\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { BashRunResult, IBashRunService } from "../../types/bashRunService.cjs";
|
|
|
2
2
|
import { ToolResult } from "./responseEnvelope.cjs";
|
|
3
3
|
import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
//#region src/commands/bash/bashTool.d.ts
|
|
5
|
-
declare const BASH_PROMPT_SNIPPET = "Execute
|
|
5
|
+
declare const BASH_PROMPT_SNIPPET = "Execute shell commands with bounded foreground output and supervised background runners";
|
|
6
6
|
/** Written at registration time so the stated threshold matches the configured one. */
|
|
7
7
|
declare function bashPromptGuidelines(thresholdMs?: number): string[];
|
|
8
8
|
interface BashToolDependencies {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashTool.d.cts","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"bashTool.d.cts","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"mappings":";;;;cAgBa;;iBAIG,qBAAqB;UAUpB;EACf,gBAAgB;EAChB,yBAAyB;;EAEzB,gBAAgB;;;;;;;;iBASF,iBAAiB,IAAI,cAAc,cAAc;iBA2EjD,gBAAgB,QAAQ,gBAAgB"}
|
|
@@ -2,7 +2,7 @@ import { BashRunResult, IBashRunService } from "../../types/bashRunService.mjs";
|
|
|
2
2
|
import { ToolResult } from "./responseEnvelope.mjs";
|
|
3
3
|
import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
//#region src/commands/bash/bashTool.d.ts
|
|
5
|
-
declare const BASH_PROMPT_SNIPPET = "Execute
|
|
5
|
+
declare const BASH_PROMPT_SNIPPET = "Execute shell commands with bounded foreground output and supervised background runners";
|
|
6
6
|
/** Written at registration time so the stated threshold matches the configured one. */
|
|
7
7
|
declare function bashPromptGuidelines(thresholdMs?: number): string[];
|
|
8
8
|
interface BashToolDependencies {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashTool.d.mts","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"bashTool.d.mts","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"mappings":";;;;cAgBa;;iBAIG,qBAAqB;UAUpB;EACf,gBAAgB;EAChB,yBAAyB;;EAEzB,gBAAgB;;;;;;;;iBASF,iBAAiB,IAAI,cAAc,cAAc;iBA2EjD,gBAAgB,QAAQ,gBAAgB"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import{getBackgroundThresholdMs as e}from"../../types/config.mjs";import{stripAnsi as t}from"../../services/AnsiScrub/ansiScrub.mjs";import{
|
|
2
|
-
`),{cause:e})}return
|
|
3
|
-
`));if(e.kind===`promoted`){let t=[`${f[e.reason]}
|
|
4
|
-
`);return
|
|
5
|
-
`),
|
|
6
|
-
`));return s(c,l)}export{p as BASH_PROMPT_SNIPPET,m as bashPromptGuidelines,g as formatRunResult,h as registerBashTool};
|
|
1
|
+
import{getBackgroundThresholdMs as e}from"../../types/config.mjs";import{stripAnsi as t}from"../../services/AnsiScrub/ansiScrub.mjs";import{countLines as n,formatSize as r,summarizeLog as i,textResult as a}from"./responseEnvelope.mjs";import{BASH_TOOL_LABEL as o,BASH_TOOL_NAME as s,BashParamsSchema as c}from"../../schemas/bashTool.mjs";import{renderBashCall as l,renderBashResult as u}from"../../tui/bashRender.mjs";const d=1e3,f={requested:`Started in the background`,threshold:`Still running after the background threshold`,interactive:`Started interactively`},p=`Execute shell commands with bounded foreground output and supervised background runners`;function m(t=e()){return[`A command still running after ${Math.round(t/d)} seconds remains active as a background runner with an id and streaming log path.`,`Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.`,`Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.`,`On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.`,`Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.`]}function h(e,t){e.registerTool({name:s,label:o,description:`Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.`,promptSnippet:p,promptGuidelines:m(),parameters:c,renderShell:`self`,async execute(e,n,r,i,o){let{command:s,timeout:c,background:l,interactive:u,name:f}=n,p;i&&i(a(`Starting ${u===!0?`interactive runner`:l===!0?`background runner`:`command`}...`)),l!==!0&&u!==!0&&i&&(p=e=>i(a(e)));let m;try{m=await t.bashRunService.run({command:s,timeoutMs:c===void 0?void 0:c*d,background:l,interactive:u,name:f,...p?{onOutput:p}:{},sessionId:await t.getSessionId()})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error([`Could not execute command: ${t}`,`Next: verify the command, runtime, and working directory. Retry only after correcting the cause.`].join(`
|
|
2
|
+
`),{cause:e})}return m.kind===`promoted`&&t.onRunnerStarted(m.id),v(m)},renderCall(e,t,n){return l(e,t)},renderResult(e,t,n,r){return u(e,{...t,isError:r.isError},n)}})}function g(e){return e.timedOut===!0||e.signal!==null||e.exitCode!==null&&e.exitCode!==0}function _(e){if(e.timedOut===!0)return`Timed out: exceeded the requested timeout.`;if(e.signal!==null)return`Signal: ${e.signal}`;if(e.exitCode===null)return`Exit status unavailable.`;if(e.exitCode!==0)return`Exit: ${e.exitCode}`}function v(e){if(e.kind===`failed`)throw Error([`Could not start runner "${e.name}": ${e.error}`,`Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.`].join(`
|
|
3
|
+
`));if(e.kind===`promoted`){let t=[`${f[e.reason]}: runner "${e.name}" (${e.id}).`,`Streaming log: ${e.logPath}`,`Inspect: doom-runner logs ${e.id}`].join(`
|
|
4
|
+
`);return a(t,{id:e.id,runner:e.name,pid:e.pid,logPath:e.logPath,promoted:!0,reason:e.reason})}let o=i(e.logPath),s=o.tail.length===0&&e.output.length>0,c=s?e.output:o.tail,l=s?n(c):o.tailLines,u=Math.max(o.lines,l),d=u>l,p=t(c).replace(/\r?\n$/,``),m=g(e),h=_(e),v=[];p.length===0?v.push(h===void 0?`Completed with no output.`:`No output.`):d?v.push(`Log tail (${l.toLocaleString(`en-US`)} of ${u.toLocaleString(`en-US`)} lines):\n${p}`):v.push(p),h!==void 0&&v.push(h),d?v.push(`Full log: ${e.logPath} (${r(o.bytes)}, ${u.toLocaleString(`en-US`)} lines); inspect with doom-runner logs ${e.id}`):m&&p.length===0&&v.push(`Log: ${e.logPath}`,`Next: run one read-only diagnostic; retry only after correcting the cause.`);let y=v.join(`
|
|
5
|
+
`),b={id:e.id,runner:e.name,exitCode:e.exitCode,logPath:e.logPath,backend:e.backend,fileSize:o.bytes,lines:u,tail:c,tailLines:l,...e.timedOut?{timedOut:!0}:{}};if(m)throw Error(y);return a(y,b)}export{p as BASH_PROMPT_SNIPPET,m as bashPromptGuidelines,v as formatRunResult,h as registerBashTool};
|
|
7
6
|
//# sourceMappingURL=bashTool.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashTool.mjs","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, type BashParams, BashParamsSchema } from '../../schemas/bashTool.ts';\nimport { stripAnsi } from '../../services/AnsiScrub/ansiScrub';\nimport type { BashRunResult, IBashRunService } from '../../types/bashRunService';\nimport { renderBashCall, renderBashResult } from '../../tui/bashRender.ts';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { formatSize, summarizeLog, type ToolResult, textResult } from './responseEnvelope.ts';\n\nconst MS_PER_SECOND = 1000;\nconst ERROR_OPTIONS_HEADING = 'Options:';\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold, so it was moved to the background',\n interactive: 'Started on a terminal so it can prompt, which means it runs in the background',\n};\n\nexport const BASH_PROMPT_SNIPPET = 'Execute bash commands, with long-running ones supervised in the background';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner. You get its unique id and streaming log path, then inspect it with the doom-runner CLI.`,\n 'Pass background: true for commands you already know will not finish on their own (dev servers, watchers, tails). Waiting the full threshold first wastes a turn.',\n 'Pass interactive: true only when the command will prompt for confirmation or input. Use Runner Space for terminal input. Interactive logs are noisier, so avoid it otherwise.',\n 'Do not re-run a command to recover output. Read its saved log with doom-runner logs <id>.',\n 'Stop runners you no longer need. Every runner is stopped automatically when the session ends.',\n ];\n}\n\nexport interface BashToolDependencies {\n bashRunService: IBashRunService;\n getSessionId(): string | Promise<string>;\n /** Called after a runner is promoted, so UI state can refresh. */\n onRunnerStarted(id: string): void;\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function registerBashTool(pi: ExtensionAPI, dependencies: BashToolDependencies): void {\n pi.registerTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute a bash command in the current working directory. A command that outlives the background threshold keeps running under doom-runner and returns a unique id plus a streaming log path. Completed commands return a bounded log tail with exact file metadata. Optionally provide a timeout in seconds, background: true to background it immediately, or interactive: true if it will prompt.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute the command: ${message}`,\n '',\n ERROR_OPTIONS_HEADING,\n '- Check the command, runtime, and working directory, then retry once.',\n '- Use a read-only diagnostic to confirm the missing dependency or environment value.',\n '- Ask the user if recovery requires changing the requested approach.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result);\n },\n\n // Without these, pi falls back to echoing the raw command and the tail end of\n // the result text, which is the metadata footer rather than the log output.\n renderCall(args, theme, _context) {\n return renderBashCall(args as BashParams, theme);\n },\n\n renderResult(result, options, theme, context) {\n return renderBashResult(result, { ...options, isError: context.isError }, theme);\n },\n });\n}\n\nexport function formatRunResult(result: BashRunResult): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n '',\n ERROR_OPTIONS_HEADING,\n '- Correct the command or missing executable, then retry once.',\n '- Use a read-only diagnostic to inspect the runtime environment.',\n '- Ask the user if a different execution approach is required.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason} as runner \"${result.name}\".`,\n `Runner ID: ${result.id}`,\n `Backend: ${result.backend}`,\n `Log (streaming): ${result.logPath}`,\n `Inspect it with: doom-runner logs ${result.id}`,\n ]\n .filter((line) => line.length > 0)\n .join('\\n');\n\n return {\n ...textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n }),\n };\n }\n\n const log = summarizeLog(result.logPath);\n const notes: string[] = [];\n if (result.timedOut) notes.push('Command stopped: it exceeded the requested timeout.');\n else if (result.exitCode !== null) notes.push(`Exit: completed with code ${result.exitCode}.`);\n else if (result.signal !== null) notes.push(`Terminated by ${result.signal}`);\n\n // The model reads plain text; only the renderer gets the coloured copy below.\n const plainTail = stripAnsi(log.tail);\n const text = [\n plainTail.length > 0 ? `Log tail (${log.tailLines} lines):\\n${plainTail}` : 'Log tail: (no output)',\n `Runner ID: ${result.id}`,\n ...notes,\n `Log: ${result.logPath}`,\n `File size: ${formatSize(log.bytes)}`,\n `Lines: ${log.lines.toLocaleString('en-US')}`,\n ].join('\\n');\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: log.lines,\n // Carried structurally so renderResult shows the output instead of the footer.\n tail: log.tail,\n tailLines: log.tailLines,\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n if (result.timedOut || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0)) {\n throw new Error(\n [\n text,\n '',\n ERROR_OPTIONS_HEADING,\n `- Inspect the full log at ${result.logPath}, correct the cause, then retry once.`,\n '- Run a read-only diagnostic if the output does not explain the failure.',\n '- Ask the user if the failure requires changing the requested approach.',\n ].join('\\n'),\n );\n }\n return textResult(text, details);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bashTool.mjs","names":[],"sources":["../../../../src/commands/bash/bashTool.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, type BashParams, BashParamsSchema } from '../../schemas/bashTool.ts';\nimport { stripAnsi } from '../../services/AnsiScrub/ansiScrub';\nimport type { BashRunResult, CompletedRun, IBashRunService } from '../../types/bashRunService';\nimport { renderBashCall, renderBashResult } from '../../tui/bashRender.ts';\nimport { getBackgroundThresholdMs } from '../../types/config.ts';\nimport { countLines, formatSize, summarizeLog, type ToolResult, textResult } from './responseEnvelope.ts';\n\nconst MS_PER_SECOND = 1000;\n\nconst PROMOTION_REASONS: Record<'requested' | 'threshold' | 'interactive', string> = {\n requested: 'Started in the background',\n threshold: 'Still running after the background threshold',\n interactive: 'Started interactively',\n};\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\nexport interface BashToolDependencies {\n bashRunService: IBashRunService;\n getSessionId(): string | Promise<string>;\n /** Called after a runner is promoted, so UI state can refresh. */\n onRunnerStarted(id: string): void;\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function registerBashTool(pi: ExtensionAPI, dependencies: BashToolDependencies): void {\n pi.registerTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result);\n },\n\n // Without these, pi falls back to echoing the raw command and the tail end of\n // the result text, which is the metadata footer rather than the log output.\n renderCall(args, theme, _context) {\n return renderBashCall(args as BashParams, theme);\n },\n\n renderResult(result, options, theme, context) {\n return renderBashResult(result, { ...options, isError: context.isError }, theme);\n },\n });\n}\n\nfunction completionFailed(result: CompletedRun): boolean {\n return result.timedOut === true || result.signal !== null || (result.exitCode !== null && result.exitCode !== 0);\n}\n\nfunction completionStatus(result: CompletedRun): string | undefined {\n if (result.timedOut === true) return 'Timed out: exceeded the requested timeout.';\n if (result.signal !== null) return `Signal: ${result.signal}`;\n if (result.exitCode === null) return 'Exit status unavailable.';\n if (result.exitCode !== 0) return `Exit: ${result.exitCode}`;\n return undefined;\n}\n\nexport function formatRunResult(result: BashRunResult): ToolResult {\n if (result.kind === 'failed') {\n throw new Error(\n [\n `Could not start runner \"${result.name}\": ${result.error}`,\n 'Next: correct the reported launch or supervision problem. Retry only after changing the command or environment.',\n ].join('\\n'),\n );\n }\n\n if (result.kind === 'promoted') {\n const reason = PROMOTION_REASONS[result.reason];\n const body = [\n `${reason}: runner \"${result.name}\" (${result.id}).`,\n `Streaming log: ${result.logPath}`,\n `Inspect: doom-runner logs ${result.id}`,\n ].join('\\n');\n\n return textResult(body, {\n id: result.id,\n runner: result.name,\n pid: result.pid,\n logPath: result.logPath,\n promoted: true,\n reason: result.reason,\n });\n }\n\n const log = summarizeLog(result.logPath);\n const useCapturedOutput = log.tail.length === 0 && result.output.length > 0;\n const tail = useCapturedOutput ? result.output : log.tail;\n const tailLines = useCapturedOutput ? countLines(tail) : log.tailLines;\n const totalLines = Math.max(log.lines, tailLines);\n const truncated = totalLines > tailLines;\n const plainTail = stripAnsi(tail).replace(/\\r?\\n$/, '');\n const failed = completionFailed(result);\n const status = completionStatus(result);\n const textLines: string[] = [];\n\n if (plainTail.length === 0) {\n textLines.push(status === undefined ? 'Completed with no output.' : 'No output.');\n } else if (truncated) {\n textLines.push(\n `Log tail (${tailLines.toLocaleString('en-US')} of ${totalLines.toLocaleString('en-US')} lines):\\n${plainTail}`,\n );\n } else {\n textLines.push(plainTail);\n }\n if (status !== undefined) textLines.push(status);\n\n if (truncated) {\n textLines.push(\n `Full log: ${result.logPath} (${formatSize(log.bytes)}, ${totalLines.toLocaleString('en-US')} lines); inspect with doom-runner logs ${result.id}`,\n );\n } else if (failed && plainTail.length === 0) {\n textLines.push(\n `Log: ${result.logPath}`,\n 'Next: run one read-only diagnostic; retry only after correcting the cause.',\n );\n }\n\n const text = textLines.join('\\n');\n const details = {\n id: result.id,\n runner: result.name,\n exitCode: result.exitCode,\n logPath: result.logPath,\n backend: result.backend,\n fileSize: log.bytes,\n lines: totalLines,\n tail,\n tailLines,\n ...(result.timedOut ? { timedOut: true } : {}),\n };\n\n if (failed) throw new Error(text);\n return textResult(text, details);\n}\n"],"mappings":"kaAQA,MAAM,EAAgB,IAEhB,EAA+E,CACnF,UAAW,4BACX,UAAW,+CACX,YAAa,uBACf,EAEa,EACX,0FAGF,SAAgB,EAAqB,EAAc,EAAyB,EAAa,CACvF,MAAO,CACL,iCAAiC,KAAK,MAAM,EAAc,CAAa,EAAE,mFACzE,iHACA,0KACA,+MACA,sHACF,CACF,CAeA,SAAgB,EAAiB,EAAkB,EAA0C,CAC3F,EAAG,aAAa,CACd,KAAM,EACN,MAAO,EACP,YACE,0TACF,cAAe,EACf,iBAAkB,EAAqB,EACvC,WAAY,EAIZ,YAAa,OAEb,MAAM,QAAQ,EAAa,EAAQ,EAAS,EAAU,EAA2B,CAC/E,GAAM,CAAE,UAAS,UAAS,aAAY,cAAa,QAAS,EACxD,EACA,GAGF,EAAS,EAAW,YADlB,IAAgB,GAAO,qBAAuB,IAAe,GAAO,oBAAsB,UACvD,IAAI,CAAC,EAExC,IAAe,IAAQ,IAAgB,IAAQ,IACjD,EAAY,GAAW,EAAS,EAAW,CAAM,CAAC,GAEpD,IAAI,EACJ,GAAI,CACF,EAAS,MAAM,EAAa,eAAe,IAAI,CAC7C,UACA,UAAW,IAAY,IAAA,GAAY,IAAA,GAAY,EAAU,EACzD,aACA,cACA,OACA,GAAI,EAAW,CAAE,UAAS,EAAI,CAAC,EAC/B,UAAW,MAAM,EAAa,aAAa,CAC7C,CAAC,CACH,OAAS,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,MAAU,MACR,CACE,8BAA8B,IAC9B,kGACF,CAAC,CAAC,KAAK;CAAI,EACX,CAAE,MAAO,CAAM,CACjB,CACF,CAGA,OADI,EAAO,OAAS,YAAY,EAAa,gBAAgB,EAAO,EAAE,EAC/D,EAAgB,CAAM,CAC/B,EAIA,WAAW,EAAM,EAAO,EAAU,CAChC,OAAO,EAAe,EAAoB,CAAK,CACjD,EAEA,aAAa,EAAQ,EAAS,EAAO,EAAS,CAC5C,OAAO,EAAiB,EAAQ,CAAE,GAAG,EAAS,QAAS,EAAQ,OAAQ,EAAG,CAAK,CACjF,CACF,CAAC,CACH,CAEA,SAAS,EAAiB,EAA+B,CACvD,OAAO,EAAO,WAAa,IAAQ,EAAO,SAAW,MAAS,EAAO,WAAa,MAAQ,EAAO,WAAa,CAChH,CAEA,SAAS,EAAiB,EAA0C,CAClE,GAAI,EAAO,WAAa,GAAM,MAAO,6CACrC,GAAI,EAAO,SAAW,KAAM,MAAO,WAAW,EAAO,SACrD,GAAI,EAAO,WAAa,KAAM,MAAO,2BACrC,GAAI,EAAO,WAAa,EAAG,MAAO,SAAS,EAAO,UAEpD,CAEA,SAAgB,EAAgB,EAAmC,CACjE,GAAI,EAAO,OAAS,SAClB,MAAU,MACR,CACE,2BAA2B,EAAO,KAAK,KAAK,EAAO,QACnD,iHACF,CAAC,CAAC,KAAK;CAAI,CACb,EAGF,GAAI,EAAO,OAAS,WAAY,CAE9B,IAAM,EAAO,CACX,GAFa,EAAkB,EAAO,QAE5B,YAAY,EAAO,KAAK,KAAK,EAAO,GAAG,IACjD,kBAAkB,EAAO,UACzB,6BAA6B,EAAO,IACtC,CAAC,CAAC,KAAK;CAAI,EAEX,OAAO,EAAW,EAAM,CACtB,GAAI,EAAO,GACX,OAAQ,EAAO,KACf,IAAK,EAAO,IACZ,QAAS,EAAO,QAChB,SAAU,GACV,OAAQ,EAAO,MACjB,CAAC,CACH,CAEA,IAAM,EAAM,EAAa,EAAO,OAAO,EACjC,EAAoB,EAAI,KAAK,SAAW,GAAK,EAAO,OAAO,OAAS,EACpE,EAAO,EAAoB,EAAO,OAAS,EAAI,KAC/C,EAAY,EAAoB,EAAW,CAAI,EAAI,EAAI,UACvD,EAAa,KAAK,IAAI,EAAI,MAAO,CAAS,EAC1C,EAAY,EAAa,EACzB,EAAY,EAAU,CAAI,CAAC,CAAC,QAAQ,SAAU,EAAE,EAChD,EAAS,EAAiB,CAAM,EAChC,EAAS,EAAiB,CAAM,EAChC,EAAsB,CAAC,EAEzB,EAAU,SAAW,EACvB,EAAU,KAAK,IAAW,IAAA,GAAY,4BAA8B,YAAY,EACvE,EACT,EAAU,KACR,aAAa,EAAU,eAAe,OAAO,EAAE,MAAM,EAAW,eAAe,OAAO,EAAE,YAAY,GACtG,EAEA,EAAU,KAAK,CAAS,EAEtB,IAAW,IAAA,IAAW,EAAU,KAAK,CAAM,EAE3C,EACF,EAAU,KACR,aAAa,EAAO,QAAQ,IAAI,EAAW,EAAI,KAAK,EAAE,IAAI,EAAW,eAAe,OAAO,EAAE,yCAAyC,EAAO,IAC/I,EACS,GAAU,EAAU,SAAW,GACxC,EAAU,KACR,QAAQ,EAAO,UACf,4EACF,EAGF,IAAM,EAAO,EAAU,KAAK;CAAI,EAC1B,EAAU,CACd,GAAI,EAAO,GACX,OAAQ,EAAO,KACf,SAAU,EAAO,SACjB,QAAS,EAAO,QAChB,QAAS,EAAO,QAChB,SAAU,EAAI,MACd,MAAO,EACP,OACA,YACA,GAAI,EAAO,SAAW,CAAE,SAAU,EAAK,EAAI,CAAC,CAC9C,EAEA,GAAI,EAAQ,MAAU,MAAM,CAAI,EAChC,OAAO,EAAW,EAAM,CAAO,CACjC"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Doom Pi Runner
|
|
2
|
+
|
|
3
|
+
## Resources
|
|
4
|
+
|
|
5
|
+
- [Package README](./README.md): Supervised Bash behavior, CLI controls, storage, limits, and native artifacts.
|
|
6
|
+
|
|
7
|
+
## How to use
|
|
8
|
+
|
|
9
|
+
- [doompi-use-runner](./src/prompts/doompi-use-runner/SKILL.md): Supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.
|
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.29",
|
|
4
4
|
"description": "Supervised shell execution, background process control, and run logs for Pi coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"skills"
|
|
25
|
+
"skills",
|
|
26
|
+
"llms.txt",
|
|
27
|
+
"src/prompts",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"package.json"
|
|
26
31
|
],
|
|
27
32
|
"type": "module",
|
|
28
33
|
"main": "./dist/index.cjs",
|
|
@@ -287,9 +292,9 @@
|
|
|
287
292
|
"@xterm/headless": "6.0.0",
|
|
288
293
|
"node-pty": "1.1.0",
|
|
289
294
|
"typebox": "1.3.16",
|
|
290
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
291
|
-
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.
|
|
292
|
-
"@agimon-ai/doompi-ui": "0.0.1-alpha.
|
|
295
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.29",
|
|
296
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.29",
|
|
297
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.29"
|
|
293
298
|
},
|
|
294
299
|
"devDependencies": {
|
|
295
300
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -313,10 +318,10 @@
|
|
|
313
318
|
}
|
|
314
319
|
},
|
|
315
320
|
"optionalDependencies": {
|
|
316
|
-
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.
|
|
317
|
-
"@agimon-ai/doompi-runner-rmux-
|
|
318
|
-
"@agimon-ai/doompi-runner-rmux-
|
|
319
|
-
"@agimon-ai/doompi-runner-rmux-linux-
|
|
321
|
+
"@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.29",
|
|
322
|
+
"@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.29",
|
|
323
|
+
"@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.29",
|
|
324
|
+
"@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.29"
|
|
320
325
|
},
|
|
321
326
|
"engines": {
|
|
322
327
|
"node": ">=22.19.0"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doompi-use-runner
|
|
3
|
+
description: Use Doom Pi Runner to supervise shell commands, inspect durable logs, provide interactive input, and stop background runs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Use Doom Pi Runner
|
|
7
|
+
|
|
8
|
+
Use Runner for shell work that may outlive one tool call, needs durable logs, or requires later inspection and control.
|
|
9
|
+
|
|
10
|
+
## Launch work
|
|
11
|
+
|
|
12
|
+
- Use the Runner-provided `bash` tool for shell commands.
|
|
13
|
+
- Let short commands return inline.
|
|
14
|
+
- Set `background: true` when the command should detach immediately.
|
|
15
|
+
- Set `interactive: true` only when the process genuinely needs terminal input.
|
|
16
|
+
- Give long foreground work a realistic timeout. Commands that cross the promotion threshold can continue as supervised runners.
|
|
17
|
+
|
|
18
|
+
## Inspect and control work
|
|
19
|
+
|
|
20
|
+
Use `/runners` or `SPC r r` in the TUI. For direct control, use:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
doom-runner list
|
|
24
|
+
doom-runner status <runner-id>
|
|
25
|
+
doom-runner logs <runner-id>
|
|
26
|
+
doom-runner input <runner-id> --text "y" --enter
|
|
27
|
+
doom-runner stop <runner-id>
|
|
28
|
+
doom-runner stop-all
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Input requires a running interactive process backed by RMUX. Preserve the returned runner identifier because it remains usable after transcript compaction.
|
|
32
|
+
|
|
33
|
+
## Close the lifecycle
|
|
34
|
+
|
|
35
|
+
- Inspect logs instead of relaunching a command whose status is uncertain.
|
|
36
|
+
- Stop watchers, servers, and failed interactive processes when they are no longer needed.
|
|
37
|
+
- Treat commands as running with the Doom Pi process environment and the operating-system user's privileges.
|
|
38
|
+
- Treat logs as sensitive because they may contain prompts, source, output, or credentials.
|