@agimon-ai/doompi-runner 0.0.1-alpha.13 → 0.0.1-alpha.15

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 CHANGED
@@ -1,14 +1,16 @@
1
1
  # @agimon-ai/doompi-runner
2
2
 
3
- Pi bash tool replacement with background process supervision.
3
+ **A shell command should not hold the session hostage.**
4
4
 
5
- Replaces the built-in bash tool with a supervised one. Long commands are backgrounded
6
- instead of blocking, a process tool manages them, and detached runs reconcile themselves
7
- after the context is rewritten.
5
+ Doompi Runner replaces Pi's built-in bash tool with a supervised one. Short commands behave
6
+ normally. Long commands move into the background, keep writing to a saved log, and remain
7
+ controllable through the process tool and TUI instead of blocking the agent until a timeout.
8
8
 
9
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
10
- only the skills and tools you name. This package is composed by Doompi and is not usually
11
- installed on its own.
9
+ Detached runs reconcile after compaction and other context rewrites. The transcript can
10
+ change; the process does not lose its identity.
11
+
12
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). Most users load
13
+ it through a major-mode layer.
12
14
 
13
15
  ## Install
14
16
 
@@ -18,29 +20,34 @@ npm install @agimon-ai/doompi-runner
18
20
 
19
21
  ## How it loads
20
22
 
21
- Name it in a layer in `.doom/modes.yaml`, then add that layer to a major mode:
23
+ Put the bare package name in a layer, then add that layer to a major mode:
22
24
 
23
25
  ```yaml
24
26
  layers:
25
27
  runner:
26
- packages: ['@agimon-ai/doompi-runner/extensions/pi']
28
+ packages: ['@agimon-ai/doompi-runner']
27
29
 
28
30
  majorMode:
29
31
  dev: [runner]
30
32
  ```
31
33
 
34
+ The package's Pi manifest selects the Pi adapter. It replaces bash only in modes that name
35
+ the layer.
36
+
32
37
  ## Leader
33
38
 
34
- `SPC r r` opens background processes, once the extension is loaded.
39
+ `SPC r r` opens Runner Space, where background processes can be inspected and controlled.
35
40
 
36
- ## Notes
41
+ ## Runtime
37
42
 
38
- Ships a prebuilt `rmux` binary per platform through the `@agimon-ai/doompi-runner-rmux-*`
39
- optional dependencies.
43
+ The package ships the `doom-runner` CLI and chooses a prebuilt `rmux` binary from its
44
+ platform-specific optional dependencies. npm installs the matching binary package; users
45
+ do not pick one by hand.
40
46
 
41
- ## Entry points
47
+ ## Public API
42
48
 
43
- 51 subpaths are declared in the `exports` field of `package.json`.
49
+ The root export and declared subpaths expose the runner protocol, process supervision,
50
+ state, tools, and TUI. Use supported exports rather than reaching into `dist/`.
44
51
 
45
52
  ## License
46
53
 
@@ -1,6 +1,6 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./format.cjs");let t=require("@earendil-works/pi-tui"),n=require("@agimon-ai/doompi-ui/components/doomOverlay");const r=n.DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;var i=class extends n.DoomOverlay{options;done;selected=0;attached;viewedLog;unsubscribe;logRefresh;notice;stopReason;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r}handleInput(e){if(this.stopReason){this.handleStopReasonInput(e);return}if(this.attached){this.handleAttachedInput(e);return}if(this.viewedLog){(0,t.matchesKey)(e,`escape`)&&this.detach();return}this.handleListInput(e)}getChrome(){let e=this.options.getRunners();return{title:`RUNNER SPACE`,accent:n.DOOM_OVERLAY_ACCENT,breadcrumb:`SPC › r / runners › r / runner space`,headerRight:this.attached?`attached · ${this.attached.name}`:this.viewedLog?`logs · ${this.viewedLog.name}`:`${e.length} runners · this worktree`,footer:this.attached?`esc back · everything else goes to the runner`:this.viewedLog?`esc back · live stdout/stderr`:`↑↓/jk select · enter open · s stop · r reason · esc close`,footerHints:this.attached||this.viewedLog?void 0:[[`↑↓/jk`,`select`],[`enter`,`open`],[`s`,`stop`],[`r`,`reason`],[`esc`,`close`]],footerRight:this.attached?`live tty`:this.viewedLog?`live log`:e.length>0?`${Math.min(this.selected,e.length-1)+1}/${e.length}`:`empty`}}renderBody(e,n){let r=n=>(0,t.truncateToWidth)(n,e,`…`);return this.attached?this.renderAttached(r,n):this.viewedLog?this.renderLog(r,n):this.renderList(r,n)}handleAttachedInput(e){if((0,t.matchesKey)(e,`escape`)){this.detach();return}this.attached?.write(e)}handleListInput(e){let n=this.options.getRunners();if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)){this.close();return}(0,t.matchesKey)(e,`up`)||e===`k`?this.move(-1,n.length):(0,t.matchesKey)(e,`down`)||e===`j`?this.move(1,n.length):e===`\r`||e===`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./format.cjs");let t=require("@earendil-works/pi-tui"),n=require("@agimon-ai/doompi-ui/components/doomOverlay");const r=n.DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions,i=`${n.DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;var a=class extends n.DoomOverlay{options;done;selected=0;attached;viewedLog;unsubscribe;logRefresh;notice;stopReason;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r}handleInput(e){if(this.stopReason){this.handleStopReasonInput(e);return}if(this.attached){this.handleAttachedInput(e);return}if(this.viewedLog){(0,t.matchesKey)(e,`escape`)&&this.detach();return}this.handleListInput(e)}getChrome(){let e=this.options.getRunners();return{title:`RUNNER SPACE`,accent:n.DOOM_OVERLAY_ACCENT,breadcrumb:`SPC › r / runners › r / runner space`,headerRight:this.attached?`attached · ${this.attached.name}`:this.viewedLog?`logs · ${this.viewedLog.name}`:`${e.length} runners · this worktree`,footer:this.attached?`esc back · everything else goes to the runner`:this.viewedLog?`esc back · live stdout/stderr`:i,footerHints:this.attached||this.viewedLog?void 0:[[n.DOOM_NAVIGATION_KEYS.list,`select`],[`enter`,`open`],[`s`,`stop`],[`r`,`reason`],[`esc`,`close`]],footerRight:this.attached?`live tty`:this.viewedLog?`live log`:e.length>0?`${Math.min(this.selected,e.length-1)+1}/${e.length}`:`empty`}}renderBody(e,n){let r=n=>(0,t.truncateToWidth)(n,e,`…`);return this.attached?this.renderAttached(r,n):this.viewedLog?this.renderLog(r,n):this.renderList(r,n)}handleAttachedInput(e){if((0,t.matchesKey)(e,`escape`)){this.detach();return}this.attached?.write(e)}handleListInput(e){let n=this.options.getRunners();if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)){this.close();return}(0,t.matchesKey)(e,`up`)||e===`k`?this.move(-1,n.length):(0,t.matchesKey)(e,`down`)||e===`j`?this.move(1,n.length):e===`\r`||e===`
2
2
  `?this.attach(n):e===`s`?this.stop(n):e===`r`&&this.promptStopReason(n)}move(e,t){t!==0&&(this.selected=Math.max(0,Math.min(t-1,this.selected+e)),this.notice=void 0,this.tui.requestRender())}current(e){if(e.length!==0)return e[Math.min(this.selected,e.length-1)]}attach(e){let t=this.current(e);if(!t)return;let n=t.interactive?this.options.getPtyRun(t.name):void 0;this.notice=void 0,n?(this.attached=n,this.unsubscribe=n.onData(()=>this.tui.requestRender())):(this.viewedLog=t,this.logRefresh=setInterval(()=>this.tui.requestRender(!0),250),this.logRefresh.unref?.()),this.tui.requestRender(!0)}detach(){this.unsubscribe?.(),this.unsubscribe=void 0,this.logRefresh&&clearInterval(this.logRefresh),this.logRefresh=void 0,this.attached=void 0,this.viewedLog=void 0,this.tui.requestRender(!0)}stop(e){let t=this.current(e);t&&this.options.stopRunner(t.id).then(()=>{this.notice={text:`Stopped ${t.name}`,color:`success`},this.tui.requestRender(!0)}).catch(e=>{this.notice={text:e instanceof Error?e.message:String(e),color:`error`},this.tui.requestRender()})}promptStopReason(e){let t=this.current(e);t&&(this.stopReason={runner:t,text:``},this.tui.requestRender())}handleStopReasonInput(e){let n=this.stopReason;if(n){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)){this.stopReason=void 0,this.tui.requestRender();return}if(e===`\r`||e===`
3
3
  `){this.stopReason=void 0,this.options.stopRunner(n.runner.id,n.text.trim()||void 0).then(()=>{this.notice={text:`Stopped ${n.runner.name}`,color:`success`},this.tui.requestRender(!0)}).catch(e=>{this.notice={text:e instanceof Error?e.message:String(e),color:`error`},this.tui.requestRender()});return}(0,t.matchesKey)(e,`backspace`)?n.text=n.text.slice(0,-1):e.includes(`\x1B`)||(n.text+=e),this.tui.requestRender()}}close(){this.detach(),this.done(void 0)}renderList(t,n){let r=this.options.getRunners(),i=e.toRunnerRows(r,Date.now()),a=[];if(this.stopReason)return[t(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];if(i.length===0)a.push(t(this.theme.fg(`dim`,`No background runners. bash backgrounds anything that outlives the threshold.`)));else{let e=this.notice?2:0,r=Math.max(1,n-e),o=Math.max(0,i.length-r),s=Math.min(o,Math.max(0,this.selected-r+1));i.slice(s,s+r).forEach((e,n)=>{let r=s+n===Math.min(this.selected,i.length-1)?`›`:` `,o=e.interactive?` [tty]`:``;a.push(t(`${r} ${e.name}${o} ${this.theme.fg(`dim`,e.detail)}`))})}return this.notice&&a.push(``,t(this.theme.fg(this.notice.color,this.notice.text))),a.slice(0,n)}renderAttached(e,t){let n=this.attached;return n?n.screen().split(`
4
4
  `).slice(-t).map(e):[]}renderLog(e,t){let n=this.viewedLog;if(!n)return[];let r=this.options.readLog(n.logPath);return r?r.split(`
5
- `).slice(-t).map(e):[e(this.theme.fg(`dim`,`Waiting for output…`))]}};async function a(e,t){await e.ui.custom((e,n,r,a)=>new i(e,n,t,a),n.DOOM_FULLSCREEN_UI_OPTIONS)}exports.RUNNER_SPACE_OVERLAY_OPTIONS=r,exports.RunnerSpaceComponent=i,exports.openRunnerSpace=a;
5
+ `).slice(-t).map(e):[e(this.theme.fg(`dim`,`Waiting for output…`))]}};async function o(e,t){await e.ui.custom((e,n,r,i)=>new a(e,n,t,i),n.DOOM_FULLSCREEN_UI_OPTIONS)}exports.RUNNER_SPACE_OVERLAY_OPTIONS=r,exports.RunnerSpaceComponent=a,exports.openRunnerSpace=o;
6
6
  //# sourceMappingURL=runnerSpace.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runnerSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","toRunnerRows"],"sources":["../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../services/PtyHost/index.ts';\nimport type { RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = '↑↓/jk select · enter open · s stop · r reason · esc close';\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n ['↑↓/jk', 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runnerSpace.cjs","names":["DOOM_FULLSCREEN_UI_OPTIONS","DOOM_NAVIGATION_KEYS","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","toRunnerRows"],"sources":["../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../services/PtyHost/index.ts';\nimport type { RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runnerSpace.d.cts","names":[],"sources":["../../src/tui/runnerSpace.ts"],"mappings":";;;;;;;cAsBa;;;;;;UAYI;EACf,2BAA2B;;EAE3B,YAAY,iBAAiB;EAC7B,UAAU;EACV,aAAa,YAAY,oBAAoB;;cAGlC,6BAA6B;mBAYrB;mBACA;UAZX;UACA;UACA;UACA;UACA;UACA;UACA;EAGN,YAAA,KAAK,gBACL,OAAO,OACU,SAAS,oBACT,OAAO;EAK1B,YAAY;YAgBF,aAAa;YAgCb,WAAW,eAAe;UAO5B;UAQA;UAcA;UAOA;UAKA;UAiBA;UAUA;UAkBA;UAOA;UA2BA;UAKA;UA2BA;UAOA;;iBAUY,gBAAgB,KAAK,kBAAkB,SAAS,qBAAqB"}
1
+ {"version":3,"file":"runnerSpace.d.cts","names":[],"sources":["../../src/tui/runnerSpace.ts"],"mappings":";;;;;;;cAuBa;;;;;;UAYI;EACf,2BAA2B;;EAE3B,YAAY,iBAAiB;EAC7B,UAAU;EACV,aAAa,YAAY,oBAAoB;;cAGlC,6BAA6B;mBAYrB;mBACA;UAZX;UACA;UACA;UACA;UACA;UACA;UACA;EAGN,YAAA,KAAK,gBACL,OAAO,OACU,SAAS,oBACT,OAAO;EAK1B,YAAY;YAgBF,aAAa;YAgCb,WAAW,eAAe;UAO5B;UAQA;UAcA;UAOA;UAKA;UAiBA;UAUA;UAkBA;UAOA;UA2BA;UAKA;UA2BA;UAOA;;iBAUY,gBAAgB,KAAK,kBAAkB,SAAS,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"runnerSpace.d.mts","names":[],"sources":["../../src/tui/runnerSpace.ts"],"mappings":";;;;;;;cAsBa;;;;;;UAYI;EACf,2BAA2B;;EAE3B,YAAY,iBAAiB;EAC7B,UAAU;EACV,aAAa,YAAY,oBAAoB;;cAGlC,6BAA6B;mBAYrB;mBACA;UAZX;UACA;UACA;UACA;UACA;UACA;UACA;EAGN,YAAA,KAAK,gBACL,OAAO,OACU,SAAS,oBACT,OAAO;EAK1B,YAAY;YAgBF,aAAa;YAgCb,WAAW,eAAe;UAO5B;UAQA;UAcA;UAOA;UAKA;UAiBA;UAUA;UAkBA;UAOA;UA2BA;UAKA;UA2BA;UAOA;;iBAUY,gBAAgB,KAAK,kBAAkB,SAAS,qBAAqB"}
1
+ {"version":3,"file":"runnerSpace.d.mts","names":[],"sources":["../../src/tui/runnerSpace.ts"],"mappings":";;;;;;;cAuBa;;;;;;UAYI;EACf,2BAA2B;;EAE3B,YAAY,iBAAiB;EAC7B,UAAU;EACV,aAAa,YAAY,oBAAoB;;cAGlC,6BAA6B;mBAYrB;mBACA;UAZX;UACA;UACA;UACA;UACA;UACA;UACA;EAGN,YAAA,KAAK,gBACL,OAAO,OACU,SAAS,oBACT,OAAO;EAK1B,YAAY;YAgBF,aAAa;YAgCb,WAAW,eAAe;UAO5B;UAQA;UAcA;UAOA;UAKA;UAiBA;UAUA;UAkBA;UAOA;UA2BA;UAKA;UA2BA;UAOA;;iBAUY,gBAAgB,KAAK,kBAAkB,SAAS,qBAAqB"}
@@ -1,6 +1,6 @@
1
- import{toRunnerRows as e}from"./format.mjs";import{matchesKey as t,truncateToWidth as n}from"@earendil-works/pi-tui";import{DOOM_FULLSCREEN_UI_OPTIONS as r,DOOM_OVERLAY_ACCENT as i,DoomOverlay as a}from"@agimon-ai/doompi-ui/components/doomOverlay";const o=r.overlayOptions;var s=class extends a{options;done;selected=0;attached;viewedLog;unsubscribe;logRefresh;notice;stopReason;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r}handleInput(e){if(this.stopReason){this.handleStopReasonInput(e);return}if(this.attached){this.handleAttachedInput(e);return}if(this.viewedLog){t(e,`escape`)&&this.detach();return}this.handleListInput(e)}getChrome(){let e=this.options.getRunners();return{title:`RUNNER SPACE`,accent:i,breadcrumb:`SPC › r / runners › r / runner space`,headerRight:this.attached?`attached · ${this.attached.name}`:this.viewedLog?`logs · ${this.viewedLog.name}`:`${e.length} runners · this worktree`,footer:this.attached?`esc back · everything else goes to the runner`:this.viewedLog?`esc back · live stdout/stderr`:`↑↓/jk select · enter open · s stop · r reason · esc close`,footerHints:this.attached||this.viewedLog?void 0:[[`↑↓/jk`,`select`],[`enter`,`open`],[`s`,`stop`],[`r`,`reason`],[`esc`,`close`]],footerRight:this.attached?`live tty`:this.viewedLog?`live log`:e.length>0?`${Math.min(this.selected,e.length-1)+1}/${e.length}`:`empty`}}renderBody(e,t){let r=t=>n(t,e,`…`);return this.attached?this.renderAttached(r,t):this.viewedLog?this.renderLog(r,t):this.renderList(r,t)}handleAttachedInput(e){if(t(e,`escape`)){this.detach();return}this.attached?.write(e)}handleListInput(e){let n=this.options.getRunners();if(t(e,`escape`)||t(e,`ctrl+c`)){this.close();return}t(e,`up`)||e===`k`?this.move(-1,n.length):t(e,`down`)||e===`j`?this.move(1,n.length):e===`\r`||e===`
1
+ import{toRunnerRows as e}from"./format.mjs";import{matchesKey as t,truncateToWidth as n}from"@earendil-works/pi-tui";import{DOOM_FULLSCREEN_UI_OPTIONS as r,DOOM_NAVIGATION_KEYS as i,DOOM_OVERLAY_ACCENT as a,DoomOverlay as o}from"@agimon-ai/doompi-ui/components/doomOverlay";const s=r.overlayOptions,c=`${i.list} select · enter open · s stop · r reason · esc close`;var l=class extends o{options;done;selected=0;attached;viewedLog;unsubscribe;logRefresh;notice;stopReason;constructor(e,t,n,r){super(e,t),this.options=n,this.done=r}handleInput(e){if(this.stopReason){this.handleStopReasonInput(e);return}if(this.attached){this.handleAttachedInput(e);return}if(this.viewedLog){t(e,`escape`)&&this.detach();return}this.handleListInput(e)}getChrome(){let e=this.options.getRunners();return{title:`RUNNER SPACE`,accent:a,breadcrumb:`SPC › r / runners › r / runner space`,headerRight:this.attached?`attached · ${this.attached.name}`:this.viewedLog?`logs · ${this.viewedLog.name}`:`${e.length} runners · this worktree`,footer:this.attached?`esc back · everything else goes to the runner`:this.viewedLog?`esc back · live stdout/stderr`:c,footerHints:this.attached||this.viewedLog?void 0:[[i.list,`select`],[`enter`,`open`],[`s`,`stop`],[`r`,`reason`],[`esc`,`close`]],footerRight:this.attached?`live tty`:this.viewedLog?`live log`:e.length>0?`${Math.min(this.selected,e.length-1)+1}/${e.length}`:`empty`}}renderBody(e,t){let r=t=>n(t,e,`…`);return this.attached?this.renderAttached(r,t):this.viewedLog?this.renderLog(r,t):this.renderList(r,t)}handleAttachedInput(e){if(t(e,`escape`)){this.detach();return}this.attached?.write(e)}handleListInput(e){let n=this.options.getRunners();if(t(e,`escape`)||t(e,`ctrl+c`)){this.close();return}t(e,`up`)||e===`k`?this.move(-1,n.length):t(e,`down`)||e===`j`?this.move(1,n.length):e===`\r`||e===`
2
2
  `?this.attach(n):e===`s`?this.stop(n):e===`r`&&this.promptStopReason(n)}move(e,t){t!==0&&(this.selected=Math.max(0,Math.min(t-1,this.selected+e)),this.notice=void 0,this.tui.requestRender())}current(e){if(e.length!==0)return e[Math.min(this.selected,e.length-1)]}attach(e){let t=this.current(e);if(!t)return;let n=t.interactive?this.options.getPtyRun(t.name):void 0;this.notice=void 0,n?(this.attached=n,this.unsubscribe=n.onData(()=>this.tui.requestRender())):(this.viewedLog=t,this.logRefresh=setInterval(()=>this.tui.requestRender(!0),250),this.logRefresh.unref?.()),this.tui.requestRender(!0)}detach(){this.unsubscribe?.(),this.unsubscribe=void 0,this.logRefresh&&clearInterval(this.logRefresh),this.logRefresh=void 0,this.attached=void 0,this.viewedLog=void 0,this.tui.requestRender(!0)}stop(e){let t=this.current(e);t&&this.options.stopRunner(t.id).then(()=>{this.notice={text:`Stopped ${t.name}`,color:`success`},this.tui.requestRender(!0)}).catch(e=>{this.notice={text:e instanceof Error?e.message:String(e),color:`error`},this.tui.requestRender()})}promptStopReason(e){let t=this.current(e);t&&(this.stopReason={runner:t,text:``},this.tui.requestRender())}handleStopReasonInput(e){let n=this.stopReason;if(n){if(t(e,`escape`)||t(e,`ctrl+c`)){this.stopReason=void 0,this.tui.requestRender();return}if(e===`\r`||e===`
3
3
  `){this.stopReason=void 0,this.options.stopRunner(n.runner.id,n.text.trim()||void 0).then(()=>{this.notice={text:`Stopped ${n.runner.name}`,color:`success`},this.tui.requestRender(!0)}).catch(e=>{this.notice={text:e instanceof Error?e.message:String(e),color:`error`},this.tui.requestRender()});return}t(e,`backspace`)?n.text=n.text.slice(0,-1):e.includes(`\x1B`)||(n.text+=e),this.tui.requestRender()}}close(){this.detach(),this.done(void 0)}renderList(t,n){let r=this.options.getRunners(),i=e(r,Date.now()),a=[];if(this.stopReason)return[t(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];if(i.length===0)a.push(t(this.theme.fg(`dim`,`No background runners. bash backgrounds anything that outlives the threshold.`)));else{let e=this.notice?2:0,r=Math.max(1,n-e),o=Math.max(0,i.length-r),s=Math.min(o,Math.max(0,this.selected-r+1));i.slice(s,s+r).forEach((e,n)=>{let r=s+n===Math.min(this.selected,i.length-1)?`›`:` `,o=e.interactive?` [tty]`:``;a.push(t(`${r} ${e.name}${o} ${this.theme.fg(`dim`,e.detail)}`))})}return this.notice&&a.push(``,t(this.theme.fg(this.notice.color,this.notice.text))),a.slice(0,n)}renderAttached(e,t){let n=this.attached;return n?n.screen().split(`
4
4
  `).slice(-t).map(e):[]}renderLog(e,t){let n=this.viewedLog;if(!n)return[];let r=this.options.readLog(n.logPath);return r?r.split(`
5
- `).slice(-t).map(e):[e(this.theme.fg(`dim`,`Waiting for output…`))]}};async function c(e,t){await e.ui.custom((e,n,r,i)=>new s(e,n,t,i),r)}export{o as RUNNER_SPACE_OVERLAY_OPTIONS,s as RunnerSpaceComponent,c as openRunnerSpace};
5
+ `).slice(-t).map(e):[e(this.theme.fg(`dim`,`Waiting for output…`))]}};async function u(e,t){await e.ui.custom((e,n,r,i)=>new l(e,n,t,i),r)}export{s as RUNNER_SPACE_OVERLAY_OPTIONS,l as RunnerSpaceComponent,u as openRunnerSpace};
6
6
  //# sourceMappingURL=runnerSpace.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runnerSpace.mjs","names":[],"sources":["../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../services/PtyHost/index.ts';\nimport type { RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = '↑↓/jk select · enter open · s stop · r reason · esc close';\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n ['↑↓/jk', 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runnerSpace.mjs","names":[],"sources":["../../src/tui/runnerSpace.ts"],"sourcesContent":["/**\n * Runner Space: the `SPC r r` overlay.\n *\n * Two modes behind one component. The list shows every runner in this worktree\n * and can stop them; attaching to an interactive runner renders its live\n * terminal and forwards keystrokes to it, which is the whole reason PTY runs\n * exist. Rendering is pure, so the surface can be asserted as text.\n */\n\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { PtyRun } from '../services/PtyHost/index.ts';\nimport type { RunnerRecord } from '../services/RunnerRegistry/index.ts';\nimport { toRunnerRows } from './format.ts';\n\nexport const RUNNER_SPACE_OVERLAY_OPTIONS = DOOM_FULLSCREEN_UI_OPTIONS.overlayOptions;\n\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst TITLE = 'RUNNER SPACE';\nconst EMPTY_MESSAGE = 'No background runners. bash backgrounds anything that outlives the threshold.';\nconst LIST_FOOTER = `${DOOM_NAVIGATION_KEYS.list} select · enter open · s stop · r reason · esc close`;\nconst ATTACH_FOOTER = 'esc back · everything else goes to the runner';\nconst LOG_FOOTER = 'esc back · live stdout/stderr';\nconst LOG_REFRESH_MS = 250;\nconst ELLIPSIS = '…';\n\nexport interface RunnerSpaceOptions {\n getRunners: () => readonly RunnerRecord[];\n /** The hosted terminal for a runner, when this session owns one. */\n getPtyRun: (name: string) => PtyRun | undefined;\n readLog: (logPath: string) => string;\n stopRunner: (id: string, reason?: string) => Promise<void>;\n}\n\nexport class RunnerSpaceComponent extends DoomOverlay {\n private selected = 0;\n private attached: PtyRun | undefined;\n private viewedLog: RunnerRecord | undefined;\n private unsubscribe: (() => void) | undefined;\n private logRefresh: ReturnType<typeof setInterval> | undefined;\n private notice: { text: string; color: 'success' | 'error' } | undefined;\n private stopReason: { runner: RunnerRecord; text: string } | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly options: RunnerSpaceOptions,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (this.stopReason) {\n this.handleStopReasonInput(data);\n return;\n }\n if (this.attached) {\n this.handleAttachedInput(data);\n return;\n }\n if (this.viewedLog) {\n if (matchesKey(data, 'escape')) this.detach();\n return;\n }\n this.handleListInput(data);\n }\n\n protected getChrome(): DoomOverlayChrome {\n const runners = this.options.getRunners();\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: 'SPC › r / runners › r / runner space',\n headerRight: this.attached\n ? `attached · ${this.attached.name}`\n : this.viewedLog\n ? `logs · ${this.viewedLog.name}`\n : `${runners.length} runners · this worktree`,\n footer: this.attached ? ATTACH_FOOTER : this.viewedLog ? LOG_FOOTER : LIST_FOOTER,\n footerHints:\n this.attached || this.viewedLog\n ? undefined\n : [\n [DOOM_NAVIGATION_KEYS.list, 'select'],\n ['enter', 'open'],\n ['s', 'stop'],\n ['r', 'reason'],\n ['esc', 'close'],\n ],\n footerRight: this.attached\n ? 'live tty'\n : this.viewedLog\n ? 'live log'\n : runners.length > 0\n ? `${Math.min(this.selected, runners.length - 1) + 1}/${runners.length}`\n : 'empty',\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const truncate = (line: string): string => truncateToWidth(line, width, ELLIPSIS);\n if (this.attached) return this.renderAttached(truncate, height);\n if (this.viewedLog) return this.renderLog(truncate, height);\n return this.renderList(truncate, height);\n }\n\n private handleAttachedInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.detach();\n return;\n }\n this.attached?.write(data);\n }\n\n private handleListInput(data: string): void {\n const runners = this.options.getRunners();\n\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.close();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') this.move(-1, runners.length);\n else if (matchesKey(data, 'down') || data === 'j') this.move(1, runners.length);\n else if (data === KEY_ENTER || data === KEY_NEWLINE) this.attach(runners);\n else if (data === 's') this.stop(runners);\n else if (data === 'r') this.promptStopReason(runners);\n }\n\n private move(delta: number, count: number): void {\n if (count === 0) return;\n this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n this.notice = undefined;\n this.tui.requestRender();\n }\n\n private current(runners: readonly RunnerRecord[]): RunnerRecord | undefined {\n if (runners.length === 0) return undefined;\n return runners[Math.min(this.selected, runners.length - 1)];\n }\n\n private attach(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n const run = record.interactive ? this.options.getPtyRun(record.name) : undefined;\n this.notice = undefined;\n if (run) {\n this.attached = run;\n this.unsubscribe = run.onData(() => this.tui.requestRender());\n } else {\n this.viewedLog = record;\n this.logRefresh = setInterval(() => this.tui.requestRender(true), LOG_REFRESH_MS);\n this.logRefresh.unref?.();\n }\n this.tui.requestRender(true);\n }\n\n private detach(): void {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n if (this.logRefresh) clearInterval(this.logRefresh);\n this.logRefresh = undefined;\n this.attached = undefined;\n this.viewedLog = undefined;\n this.tui.requestRender(true);\n }\n\n private stop(runners: readonly RunnerRecord[]): void {\n const record = this.current(runners);\n if (!record) return;\n\n this.options\n .stopRunner(record.id)\n .then(() => {\n this.notice = { text: `Stopped ${record.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n // Shown rather than swallowed: a failed stop means the process is still\n // running and the operator needs to know.\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n }\n\n private promptStopReason(runners: readonly RunnerRecord[]): void {\n const runner = this.current(runners);\n if (!runner) return;\n this.stopReason = { runner, text: '' };\n this.tui.requestRender();\n }\n\n private handleStopReasonInput(data: string): void {\n const prompt = this.stopReason;\n if (!prompt) return;\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {\n this.stopReason = undefined;\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n this.stopReason = undefined;\n this.options\n .stopRunner(prompt.runner.id, prompt.text.trim() || undefined)\n .then(() => {\n this.notice = { text: `Stopped ${prompt.runner.name}`, color: 'success' };\n this.tui.requestRender(true);\n })\n .catch((error: unknown) => {\n this.notice = { text: error instanceof Error ? error.message : String(error), color: 'error' };\n this.tui.requestRender();\n });\n return;\n }\n if (matchesKey(data, 'backspace')) prompt.text = prompt.text.slice(0, -1);\n else if (!data.includes('\\u001b')) prompt.text += data;\n this.tui.requestRender();\n }\n\n private close(): void {\n this.detach();\n this.done(undefined);\n }\n\n private renderList(truncate: (line: string) => string, height: number): string[] {\n const runners = this.options.getRunners();\n const rows = toRunnerRows(runners, Date.now());\n const lines: string[] = [];\n\n if (this.stopReason) {\n return [truncate(`Stop reason for ${this.stopReason.runner.name}: ${this.stopReason.text}`)];\n }\n\n if (rows.length === 0) lines.push(truncate(this.theme.fg('dim', EMPTY_MESSAGE)));\n else {\n const noticeRows = this.notice ? 2 : 0;\n const capacity = Math.max(1, height - noticeRows);\n const maxStart = Math.max(0, rows.length - capacity);\n const start = Math.min(maxStart, Math.max(0, this.selected - capacity + 1));\n rows.slice(start, start + capacity).forEach((row, offset) => {\n const index = start + offset;\n const marker = index === Math.min(this.selected, rows.length - 1) ? '›' : ' ';\n const tty = row.interactive ? ' [tty]' : '';\n lines.push(truncate(`${marker} ${row.name}${tty} ${this.theme.fg('dim', row.detail)}`));\n });\n }\n\n if (this.notice) lines.push('', truncate(this.theme.fg(this.notice.color, this.notice.text)));\n return lines.slice(0, height);\n }\n\n private renderAttached(truncate: (line: string) => string, height: number): string[] {\n const run = this.attached;\n if (!run) return [];\n\n return run.screen().split('\\n').slice(-height).map(truncate);\n }\n\n private renderLog(truncate: (line: string) => string, height: number): string[] {\n const record = this.viewedLog;\n if (!record) return [];\n\n const text = this.options.readLog(record.logPath);\n if (!text) return [truncate(this.theme.fg('dim', 'Waiting for output…'))];\n return text.split('\\n').slice(-height).map(truncate);\n }\n}\n\nexport async function openRunnerSpace(ctx: ExtensionContext, options: RunnerSpaceOptions): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new RunnerSpaceComponent(tui, theme, options, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-runner",
3
- "version": "0.0.1-alpha.13",
3
+ "version": "0.0.1-alpha.15",
4
4
  "description": "Pi bash tool replacement with background process supervision",
5
5
  "homepage": "https://agimon.ai",
6
6
  "license": "MIT",
@@ -285,10 +285,10 @@
285
285
  "node-pty": "1.1.0",
286
286
  "reflect-metadata": "0.2.2",
287
287
  "typebox": "1.1.38",
288
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.13",
289
- "@agimon-ai/doompi-ui": "0.0.1-alpha.13",
290
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.13",
291
- "@agimon-ai/foundation-process-registry": "0.29.3"
288
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.15",
289
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.15",
290
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.15",
291
+ "@agimon-ai/foundation-process-registry": "0.29.5"
292
292
  },
293
293
  "devDependencies": {
294
294
  "@earendil-works/pi-coding-agent": "0.84.1",
@@ -312,10 +312,10 @@
312
312
  }
313
313
  },
314
314
  "optionalDependencies": {
315
- "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.13",
316
- "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.13",
317
- "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.13",
318
- "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.13"
315
+ "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.15",
316
+ "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.15",
317
+ "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.15",
318
+ "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.15"
319
319
  },
320
320
  "engines": {
321
321
  "node": ">=22.19.0"