pi-interactive-shell 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/README.md +5 -3
- package/config.ts +10 -2
- package/headless-monitor.ts +6 -0
- package/index.ts +6 -1
- package/overlay-component.ts +17 -5
- package/package.json +3 -2
- package/reattach-overlay.ts +8 -5
- package/tool-schema.ts +4 -0
- package/types.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to the `pi-interactive-shell` extension will be documented i
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.0] - 2026-02-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `autoExitGracePeriod` config option (default: 30000ms, clamped 5000-120000ms) and `handsFree.gracePeriod` tool parameter override for startup quiet-kill grace control.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Default `overlayHeightPercent` increased from 45 to 60 for improved usable terminal rows on smaller displays.
|
|
14
|
+
- Overlay sizing now uses dynamic footer chrome: compact 2-line footer in normal states and full 6-line footer in detach dialog, increasing terminal viewport height during normal operation.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Dispatch/hands-free `autoExitOnQuiet` no longer kills sessions during startup silence; quiet timer now re-arms during grace period and applies auto-kill only after grace expires.
|
|
18
|
+
|
|
19
|
+
## [0.7.1] - 2026-02-03
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Added demo video and `pi.video` field to package.json for pi package browser.
|
|
23
|
+
|
|
7
24
|
## [0.7.0] - 2026-02-03
|
|
8
25
|
|
|
9
26
|
### Added
|
|
@@ -285,4 +302,3 @@ interactive_shell({ sessionId: "abc", input: "y", inputKeys: ["enter"] })
|
|
|
285
302
|
### Fixed
|
|
286
303
|
- Prevented TUI width crashes by avoiding unbounded terminal escape rendering.
|
|
287
304
|
- Reduced flicker by sanitizing/redrawing in a controlled overlay viewport.
|
|
288
|
-
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ Attach to review full output: interactive_shell({ attach: "calm-reef" })
|
|
|
115
115
|
|
|
116
116
|
The notification includes a brief tail (last 5 lines) and a reattach instruction. The PTY is preserved for 5 minutes so the agent can attach to review full scrollback.
|
|
117
117
|
|
|
118
|
-
Dispatch defaults `autoExitOnQuiet: true` — the session is killed after output goes silent (5s by default), which signals completion for task-oriented subagents. Opt out with `handsFree: { autoExitOnQuiet: false }` for long-running processes.
|
|
118
|
+
Dispatch defaults `autoExitOnQuiet: true` — the session gets a 30s startup grace period, then is killed after output goes silent (5s by default), which signals completion for task-oriented subagents. Opt out with `handsFree: { autoExitOnQuiet: false }` for long-running processes.
|
|
119
119
|
|
|
120
120
|
The overlay still shows for the user, who can Ctrl+T to transfer output, Ctrl+B to background, take over by typing, or Ctrl+Q for more options.
|
|
121
121
|
|
|
@@ -260,7 +260,7 @@ Configuration files (project overrides global):
|
|
|
260
260
|
```json
|
|
261
261
|
{
|
|
262
262
|
"overlayWidthPercent": 95,
|
|
263
|
-
"overlayHeightPercent":
|
|
263
|
+
"overlayHeightPercent": 60,
|
|
264
264
|
"scrollbackLines": 5000,
|
|
265
265
|
"exitAutoCloseDelay": 10,
|
|
266
266
|
"minQueryIntervalSeconds": 60,
|
|
@@ -271,6 +271,7 @@ Configuration files (project overrides global):
|
|
|
271
271
|
"handsFreeUpdateMode": "on-quiet",
|
|
272
272
|
"handsFreeUpdateInterval": 60000,
|
|
273
273
|
"handsFreeQuietThreshold": 5000,
|
|
274
|
+
"autoExitGracePeriod": 30000,
|
|
274
275
|
"handsFreeUpdateMaxChars": 1500,
|
|
275
276
|
"handsFreeMaxTotalChars": 100000,
|
|
276
277
|
"handoffPreviewEnabled": true,
|
|
@@ -284,7 +285,7 @@ Configuration files (project overrides global):
|
|
|
284
285
|
| Setting | Default | Description |
|
|
285
286
|
|---------|---------|-------------|
|
|
286
287
|
| `overlayWidthPercent` | 95 | Overlay width (10-100%) |
|
|
287
|
-
| `overlayHeightPercent` |
|
|
288
|
+
| `overlayHeightPercent` | 60 | Overlay height (20-90%) |
|
|
288
289
|
| `scrollbackLines` | 5000 | Terminal scrollback buffer |
|
|
289
290
|
| `exitAutoCloseDelay` | 10 | Seconds before auto-close after exit |
|
|
290
291
|
| `minQueryIntervalSeconds` | 60 | Rate limit between agent queries |
|
|
@@ -294,6 +295,7 @@ Configuration files (project overrides global):
|
|
|
294
295
|
| `completionNotifyMaxChars` | 5000 | Max chars in completion notification (1KB-50KB) |
|
|
295
296
|
| `handsFreeUpdateMode` | "on-quiet" | "on-quiet" or "interval" |
|
|
296
297
|
| `handsFreeQuietThreshold` | 5000 | Silence duration before update (ms) |
|
|
298
|
+
| `autoExitGracePeriod` | 30000 | Startup grace before `autoExitOnQuiet` kill (ms) |
|
|
297
299
|
| `handsFreeUpdateInterval` | 60000 | Max interval between updates (ms) |
|
|
298
300
|
| `handsFreeUpdateMaxChars` | 1500 | Max chars per update |
|
|
299
301
|
| `handsFreeMaxTotalChars` | 100000 | Total char budget for updates |
|
package/config.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface InteractiveShellConfig {
|
|
|
24
24
|
handsFreeUpdateMode: "on-quiet" | "interval";
|
|
25
25
|
handsFreeUpdateInterval: number;
|
|
26
26
|
handsFreeQuietThreshold: number;
|
|
27
|
+
autoExitGracePeriod: number;
|
|
27
28
|
handsFreeUpdateMaxChars: number;
|
|
28
29
|
handsFreeMaxTotalChars: number;
|
|
29
30
|
// Query rate limiting
|
|
@@ -33,7 +34,7 @@ export interface InteractiveShellConfig {
|
|
|
33
34
|
const DEFAULT_CONFIG: InteractiveShellConfig = {
|
|
34
35
|
exitAutoCloseDelay: 10,
|
|
35
36
|
overlayWidthPercent: 95,
|
|
36
|
-
overlayHeightPercent:
|
|
37
|
+
overlayHeightPercent: 60,
|
|
37
38
|
scrollbackLines: 5000,
|
|
38
39
|
ansiReemit: true,
|
|
39
40
|
handoffPreviewEnabled: true,
|
|
@@ -52,6 +53,7 @@ const DEFAULT_CONFIG: InteractiveShellConfig = {
|
|
|
52
53
|
handsFreeUpdateMode: "on-quiet" as const,
|
|
53
54
|
handsFreeUpdateInterval: 60000,
|
|
54
55
|
handsFreeQuietThreshold: 5000,
|
|
56
|
+
autoExitGracePeriod: 30000,
|
|
55
57
|
handsFreeUpdateMaxChars: 1500,
|
|
56
58
|
handsFreeMaxTotalChars: 100000,
|
|
57
59
|
// Query rate limiting (default 60 seconds between queries)
|
|
@@ -87,7 +89,7 @@ export function loadConfig(cwd: string): InteractiveShellConfig {
|
|
|
87
89
|
...merged,
|
|
88
90
|
exitAutoCloseDelay: clampInt(merged.exitAutoCloseDelay, DEFAULT_CONFIG.exitAutoCloseDelay, 0, 60),
|
|
89
91
|
overlayWidthPercent: clampPercent(merged.overlayWidthPercent, DEFAULT_CONFIG.overlayWidthPercent),
|
|
90
|
-
// Height: 20-90% range (default
|
|
92
|
+
// Height: 20-90% range (default 60%)
|
|
91
93
|
overlayHeightPercent: clampInt(merged.overlayHeightPercent, DEFAULT_CONFIG.overlayHeightPercent, 20, 90),
|
|
92
94
|
scrollbackLines: clampInt(merged.scrollbackLines, DEFAULT_CONFIG.scrollbackLines, 200, 50000),
|
|
93
95
|
ansiReemit: merged.ansiReemit !== false,
|
|
@@ -127,6 +129,12 @@ export function loadConfig(cwd: string): InteractiveShellConfig {
|
|
|
127
129
|
1000,
|
|
128
130
|
30000,
|
|
129
131
|
),
|
|
132
|
+
autoExitGracePeriod: clampInt(
|
|
133
|
+
merged.autoExitGracePeriod,
|
|
134
|
+
DEFAULT_CONFIG.autoExitGracePeriod,
|
|
135
|
+
5000,
|
|
136
|
+
120000,
|
|
137
|
+
),
|
|
130
138
|
handsFreeUpdateMaxChars: clampInt(
|
|
131
139
|
merged.handsFreeUpdateMaxChars,
|
|
132
140
|
DEFAULT_CONFIG.handsFreeUpdateMaxChars,
|
package/headless-monitor.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { InteractiveShellConfig } from "./config.js";
|
|
|
4
4
|
export interface HeadlessMonitorOptions {
|
|
5
5
|
autoExitOnQuiet: boolean;
|
|
6
6
|
quietThreshold: number;
|
|
7
|
+
gracePeriod?: number;
|
|
7
8
|
timeout?: number;
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -80,6 +81,11 @@ export class HeadlessDispatchMonitor {
|
|
|
80
81
|
this.quietTimer = setTimeout(() => {
|
|
81
82
|
this.quietTimer = null;
|
|
82
83
|
if (!this._disposed && this.options.autoExitOnQuiet) {
|
|
84
|
+
const gracePeriod = this.options.gracePeriod ?? this.config.autoExitGracePeriod;
|
|
85
|
+
if (Date.now() - this.startTime < gracePeriod) {
|
|
86
|
+
this.resetQuietTimer();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
83
89
|
this.session.kill();
|
|
84
90
|
this.handleCompletion(null, undefined, false, true);
|
|
85
91
|
}
|
package/index.ts
CHANGED
|
@@ -494,6 +494,7 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
|
|
|
494
494
|
autoExitOnQuiet: mode === "dispatch"
|
|
495
495
|
? handsFree?.autoExitOnQuiet !== false
|
|
496
496
|
: handsFree?.autoExitOnQuiet === true,
|
|
497
|
+
autoExitGracePeriod: handsFree?.gracePeriod ?? config.autoExitGracePeriod,
|
|
497
498
|
handoffPreviewEnabled: handoffPreview?.enabled,
|
|
498
499
|
handoffPreviewLines: handoffPreview?.lines,
|
|
499
500
|
handoffPreviewMaxChars: handoffPreview?.maxChars,
|
|
@@ -637,6 +638,7 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
|
|
|
637
638
|
const monitor = new HeadlessDispatchMonitor(session, config, {
|
|
638
639
|
autoExitOnQuiet: handsFree?.autoExitOnQuiet !== false,
|
|
639
640
|
quietThreshold: handsFree?.quietThreshold ?? config.handsFreeQuietThreshold,
|
|
641
|
+
gracePeriod: handsFree?.gracePeriod ?? config.autoExitGracePeriod,
|
|
640
642
|
timeout,
|
|
641
643
|
}, makeMonitorCompletionCallback(pi, id, startTime));
|
|
642
644
|
headlessMonitors.set(id, monitor);
|
|
@@ -695,6 +697,7 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
|
|
|
695
697
|
autoExitOnQuiet: mode === "dispatch"
|
|
696
698
|
? handsFree?.autoExitOnQuiet !== false
|
|
697
699
|
: handsFree?.autoExitOnQuiet === true,
|
|
700
|
+
autoExitGracePeriod: handsFree?.gracePeriod ?? config.autoExitGracePeriod,
|
|
698
701
|
handoffPreviewEnabled: handoffPreview?.enabled,
|
|
699
702
|
handoffPreviewLines: handoffPreview?.lines,
|
|
700
703
|
handoffPreviewMaxChars: handoffPreview?.maxChars,
|
|
@@ -760,6 +763,7 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
|
|
|
760
763
|
handsFreeUpdateMaxChars: handsFree?.updateMaxChars,
|
|
761
764
|
handsFreeMaxTotalChars: handsFree?.maxTotalChars,
|
|
762
765
|
autoExitOnQuiet: handsFree?.autoExitOnQuiet,
|
|
766
|
+
autoExitGracePeriod: handsFree?.gracePeriod ?? config.autoExitGracePeriod,
|
|
763
767
|
onHandsFreeUpdate: mode === "hands-free"
|
|
764
768
|
? (update) => {
|
|
765
769
|
let statusText: string;
|
|
@@ -974,7 +978,7 @@ function setupDispatchCompletion(
|
|
|
974
978
|
command: string;
|
|
975
979
|
reason?: string;
|
|
976
980
|
timeout?: number;
|
|
977
|
-
handsFree?: { autoExitOnQuiet?: boolean; quietThreshold?: number };
|
|
981
|
+
handsFree?: { autoExitOnQuiet?: boolean; quietThreshold?: number; gracePeriod?: number };
|
|
978
982
|
overlayStartTime?: number;
|
|
979
983
|
},
|
|
980
984
|
): void {
|
|
@@ -1030,6 +1034,7 @@ function setupDispatchCompletion(
|
|
|
1030
1034
|
const monitor = new HeadlessDispatchMonitor(bgSession.session, config, {
|
|
1031
1035
|
autoExitOnQuiet: ctx.handsFree?.autoExitOnQuiet !== false,
|
|
1032
1036
|
quietThreshold: ctx.handsFree?.quietThreshold ?? config.handsFreeQuietThreshold,
|
|
1037
|
+
gracePeriod: ctx.handsFree?.gracePeriod ?? config.autoExitGracePeriod,
|
|
1033
1038
|
timeout: remainingTimeout,
|
|
1034
1039
|
}, makeMonitorCompletionCallback(pi, bgId, bgStartTime));
|
|
1035
1040
|
headlessMonitors.set(bgId, monitor);
|
package/overlay-component.ts
CHANGED
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
type InteractiveShellOptions,
|
|
14
14
|
type DialogChoice,
|
|
15
15
|
type OverlayState,
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
HEADER_LINES,
|
|
17
|
+
FOOTER_LINES_COMPACT,
|
|
18
|
+
FOOTER_LINES_DIALOG,
|
|
18
19
|
formatDuration,
|
|
19
20
|
} from "./types.js";
|
|
20
21
|
|
|
@@ -80,7 +81,7 @@ export class InteractiveShellOverlay implements Component, Focusable {
|
|
|
80
81
|
const overlayWidth = Math.floor((tui.terminal.columns * this.config.overlayWidthPercent) / 100);
|
|
81
82
|
const overlayHeight = Math.floor((tui.terminal.rows * this.config.overlayHeightPercent) / 100);
|
|
82
83
|
const cols = Math.max(20, overlayWidth - 4);
|
|
83
|
-
const rows = Math.max(3, overlayHeight -
|
|
84
|
+
const rows = Math.max(3, overlayHeight - (HEADER_LINES + FOOTER_LINES_COMPACT + 2));
|
|
84
85
|
|
|
85
86
|
const ptyEvents = {
|
|
86
87
|
onData: () => {
|
|
@@ -449,6 +450,15 @@ export class InteractiveShellOverlay implements Component, Focusable {
|
|
|
449
450
|
if (this.state === "hands-free") {
|
|
450
451
|
// Auto-exit on quiet: kill session when output stops (agent likely finished task)
|
|
451
452
|
if (this.options.autoExitOnQuiet) {
|
|
453
|
+
const gracePeriod = this.options.autoExitGracePeriod ?? this.config.autoExitGracePeriod;
|
|
454
|
+
if (Date.now() - this.startTime < gracePeriod) {
|
|
455
|
+
if (this.hasUnsentData) {
|
|
456
|
+
this.emitHandsFreeUpdate();
|
|
457
|
+
this.hasUnsentData = false;
|
|
458
|
+
}
|
|
459
|
+
this.resetQuietTimer();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
452
462
|
// Emit final update with any pending output
|
|
453
463
|
if (this.hasUnsentData) {
|
|
454
464
|
this.emitHandsFreeUpdate();
|
|
@@ -1077,7 +1087,9 @@ export class InteractiveShellOverlay implements Component, Focusable {
|
|
|
1077
1087
|
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
1078
1088
|
|
|
1079
1089
|
const overlayHeight = Math.floor((this.tui.terminal.rows * this.config.overlayHeightPercent) / 100);
|
|
1080
|
-
const
|
|
1090
|
+
const footerHeight = this.state === "detach-dialog" ? FOOTER_LINES_DIALOG : FOOTER_LINES_COMPACT;
|
|
1091
|
+
const chrome = HEADER_LINES + footerHeight + 2;
|
|
1092
|
+
const termRows = Math.max(3, overlayHeight - chrome);
|
|
1081
1093
|
|
|
1082
1094
|
if (innerWidth !== this.lastWidth || termRows !== this.lastHeight) {
|
|
1083
1095
|
this.session.resize(innerWidth, termRows);
|
|
@@ -1136,7 +1148,7 @@ export class InteractiveShellOverlay implements Component, Focusable {
|
|
|
1136
1148
|
footerLines.push(row(dim("Ctrl+T transfer • Ctrl+B background • Ctrl+Q menu • Shift+Up/Down scroll")));
|
|
1137
1149
|
}
|
|
1138
1150
|
|
|
1139
|
-
while (footerLines.length <
|
|
1151
|
+
while (footerLines.length < footerHeight) {
|
|
1140
1152
|
footerLines.push(emptyRow());
|
|
1141
1153
|
}
|
|
1142
1154
|
lines.push(...footerLines);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-interactive-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Run AI coding agents as foreground subagents in pi TUI overlays with hands-free monitoring",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"pi": {
|
|
27
27
|
"extensions": [
|
|
28
28
|
"./index.ts"
|
|
29
|
-
]
|
|
29
|
+
],
|
|
30
|
+
"video": "https://github.com/nicobailon/pi-interactive-shell/raw/refs/heads/main/pi-interactive-shell-extension.mp4"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"node-pty": "^1.1.0",
|
package/reattach-overlay.ts
CHANGED
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
type InteractiveShellResult,
|
|
12
12
|
type DialogChoice,
|
|
13
13
|
type OverlayState,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
HEADER_LINES,
|
|
15
|
+
FOOTER_LINES_COMPACT,
|
|
16
|
+
FOOTER_LINES_DIALOG,
|
|
16
17
|
} from "./types.js";
|
|
17
18
|
|
|
18
19
|
export class ReattachOverlay implements Component, Focusable {
|
|
@@ -74,7 +75,7 @@ export class ReattachOverlay implements Component, Focusable {
|
|
|
74
75
|
const overlayWidth = Math.floor((tui.terminal.columns * this.config.overlayWidthPercent) / 100);
|
|
75
76
|
const overlayHeight = Math.floor((tui.terminal.rows * this.config.overlayHeightPercent) / 100);
|
|
76
77
|
const cols = Math.max(20, overlayWidth - 4);
|
|
77
|
-
const rows = Math.max(3, overlayHeight -
|
|
78
|
+
const rows = Math.max(3, overlayHeight - (HEADER_LINES + FOOTER_LINES_COMPACT + 2));
|
|
78
79
|
bgSession.session.resize(cols, rows);
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -400,7 +401,9 @@ export class ReattachOverlay implements Component, Focusable {
|
|
|
400
401
|
lines.push(border("├" + "─".repeat(width - 2) + "┤"));
|
|
401
402
|
|
|
402
403
|
const overlayHeight = Math.floor((this.tui.terminal.rows * this.config.overlayHeightPercent) / 100);
|
|
403
|
-
const
|
|
404
|
+
const footerHeight = this.state === "detach-dialog" ? FOOTER_LINES_DIALOG : FOOTER_LINES_COMPACT;
|
|
405
|
+
const chrome = HEADER_LINES + footerHeight + 2;
|
|
406
|
+
const termRows = Math.max(3, overlayHeight - chrome);
|
|
404
407
|
|
|
405
408
|
if (innerWidth !== this.lastWidth || termRows !== this.lastHeight) {
|
|
406
409
|
this.session.resize(innerWidth, termRows);
|
|
@@ -457,7 +460,7 @@ export class ReattachOverlay implements Component, Focusable {
|
|
|
457
460
|
footerLines.push(row(dim("Ctrl+T transfer • Ctrl+B background • Ctrl+Q menu • Shift+Up/Down scroll")));
|
|
458
461
|
}
|
|
459
462
|
|
|
460
|
-
while (footerLines.length <
|
|
463
|
+
while (footerLines.length < footerHeight) {
|
|
461
464
|
footerLines.push(emptyRow());
|
|
462
465
|
}
|
|
463
466
|
lines.push(...footerLines);
|
package/tool-schema.ts
CHANGED
|
@@ -236,6 +236,9 @@ export const toolParameters = Type.Object({
|
|
|
236
236
|
quietThreshold: Type.Optional(
|
|
237
237
|
Type.Number({ description: "Silence duration before emitting update in on-quiet mode (default: 5000ms)" }),
|
|
238
238
|
),
|
|
239
|
+
gracePeriod: Type.Optional(
|
|
240
|
+
Type.Number({ description: "Startup grace period before autoExitOnQuiet can kill the session (default: 30000ms)" }),
|
|
241
|
+
),
|
|
239
242
|
updateMaxChars: Type.Optional(
|
|
240
243
|
Type.Number({ description: "Max chars per update (default: 1500)" }),
|
|
241
244
|
),
|
|
@@ -299,6 +302,7 @@ export interface ToolParams {
|
|
|
299
302
|
updateMode?: "on-quiet" | "interval";
|
|
300
303
|
updateInterval?: number;
|
|
301
304
|
quietThreshold?: number;
|
|
305
|
+
gracePeriod?: number;
|
|
302
306
|
updateMaxChars?: number;
|
|
303
307
|
maxTotalChars?: number;
|
|
304
308
|
autoExitOnQuiet?: boolean;
|
package/types.ts
CHANGED
|
@@ -70,6 +70,7 @@ export interface InteractiveShellOptions {
|
|
|
70
70
|
onHandsFreeUpdate?: (update: HandsFreeUpdate) => void;
|
|
71
71
|
// Auto-exit when output stops (for agents that don't exit on their own)
|
|
72
72
|
autoExitOnQuiet?: boolean;
|
|
73
|
+
autoExitGracePeriod?: number;
|
|
73
74
|
// Auto-kill timeout
|
|
74
75
|
timeout?: number;
|
|
75
76
|
// Existing PTY session (for attach flow -- skip creating a new PTY)
|
|
@@ -80,9 +81,9 @@ export type DialogChoice = "kill" | "background" | "transfer" | "cancel";
|
|
|
80
81
|
export type OverlayState = "running" | "exited" | "detach-dialog" | "hands-free";
|
|
81
82
|
|
|
82
83
|
// UI constants
|
|
83
|
-
export const
|
|
84
|
+
export const FOOTER_LINES_COMPACT = 2;
|
|
85
|
+
export const FOOTER_LINES_DIALOG = 6;
|
|
84
86
|
export const HEADER_LINES = 4;
|
|
85
|
-
export const CHROME_LINES = HEADER_LINES + FOOTER_LINES + 2;
|
|
86
87
|
|
|
87
88
|
/** Format milliseconds to human-readable duration */
|
|
88
89
|
export function formatDuration(ms: number): string {
|