Files

199 lines
5.5 KiB
TypeScript

/**
* Shared types and interfaces for the interactive shell extension.
*/
export interface InteractiveShellResult {
exitCode: number | null;
signal?: number;
backgrounded: boolean;
backgroundId?: string;
cancelled: boolean;
timedOut?: boolean;
sessionId?: string;
userTookOver?: boolean;
/** When user triggers "Transfer" action, this contains the captured output */
transferred?: {
lines: string[];
totalLines: number;
truncated: boolean;
};
/** Captured before PTY disposal for dispatch mode completion notifications */
completionOutput?: {
lines: string[];
totalLines: number;
truncated: boolean;
};
handoffPreview?: {
type: "tail";
when: "exit" | "detach" | "kill" | "timeout" | "transfer";
lines: string[];
};
handoff?: {
type: "snapshot";
when: "exit" | "detach" | "kill" | "timeout" | "transfer";
transcriptPath: string;
linesWritten: number;
};
}
export interface HandsFreeUpdate {
status: "running" | "user-takeover" | "exited" | "killed" | "agent-resumed";
sessionId: string;
runtime: number;
tail: string[];
tailTruncated: boolean;
userTookOver?: boolean;
// Budget tracking
totalCharsSent?: number;
budgetExhausted?: boolean;
}
export type MonitorStrategy = "stream" | "poll-diff" | "file-watch";
export type MonitorThresholdOperator = "lt" | "lte" | "gt" | "gte";
export interface MonitorThresholdConfig {
captureGroup: number;
op: MonitorThresholdOperator;
value: number;
}
export interface MonitorTriggerConfig {
id: string;
literal?: string;
regex?: string;
cooldownMs?: number;
threshold?: MonitorThresholdConfig;
}
export interface MonitorFileWatchConfig {
path: string;
recursive?: boolean;
events?: Array<"rename" | "change">;
}
export interface MonitorConfig {
strategy?: MonitorStrategy;
triggers: MonitorTriggerConfig[];
fileWatch?: MonitorFileWatchConfig;
poll?: {
intervalMs?: number;
};
persistence?: {
stopAfterFirstEvent?: boolean;
maxEvents?: number;
};
throttle?: {
dedupeExactLine?: boolean;
cooldownMs?: number;
};
detector?: {
detectorCommand: string;
timeoutMs?: number;
};
}
export interface MonitorEventPayload {
sessionId: string;
eventId: number;
timestamp: string;
strategy: MonitorStrategy;
triggerId: string;
eventType: string;
matchedText: string;
lineOrDiff: string;
stream: "pty";
}
export type MonitorTerminalReason = "stream-ended" | "script-failed" | "stopped" | "timed-out";
export interface MonitorSessionState {
sessionId: string;
strategy: MonitorStrategy;
triggerIds: string[];
status: "running" | "stopped";
eventCount: number;
startedAt: string;
lastEventId?: number;
lastEventAt?: string;
lastTriggerId?: string;
endedAt?: string;
terminalReason?: MonitorTerminalReason;
exitCode?: number | null;
signal?: number;
}
/** Options for starting or reattaching an interactive shell session. */
export interface InteractiveShellOptions {
command: string;
cwd?: string;
name?: string;
reason?: string;
/** Original session start time in ms since epoch, preserved across background/reattach transitions. */
startedAt?: number;
handoffPreviewEnabled?: boolean;
handoffPreviewLines?: number;
handoffPreviewMaxChars?: number;
handoffSnapshotEnabled?: boolean;
handoffSnapshotLines?: number;
handoffSnapshotMaxChars?: number;
// Hands-free / dispatch / monitor mode
mode?: "interactive" | "hands-free" | "dispatch" | "monitor";
monitor?: MonitorConfig;
sessionId?: string; // Pre-generated sessionId for non-blocking modes
handsFreeUpdateMode?: "on-quiet" | "interval";
handsFreeUpdateInterval?: number;
handsFreeQuietThreshold?: number;
handsFreeUpdateMaxChars?: number;
handsFreeMaxTotalChars?: number;
onHandsFreeUpdate?: (update: HandsFreeUpdate) => void;
// Auto-exit when output stops (for agents that don't exit on their own)
autoExitOnQuiet?: boolean;
autoExitGracePeriod?: number;
// Auto-kill timeout
timeout?: number;
// When true, unregister active session on completion (blocking tool call path).
// When false/undefined, keep registered so agent can query result later.
streamingMode?: boolean;
// Existing PTY session (for attach flow -- skip creating a new PTY)
existingSession?: import("./pty-session.js").PtyTerminalSession;
onUnfocus?: () => void;
}
export type DialogChoice = "kill" | "background" | "transfer" | "cancel" | "return-to-agent";
export type OverlayState = "running" | "exited" | "detach-dialog" | "hands-free";
// UI constants
export const FOOTER_LINES_COMPACT = 2;
export const FOOTER_LINES_DIALOG = 6;
export const HEADER_LINES = 4;
/** Format milliseconds to human-readable duration */
export function formatDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
const hours = Math.floor(minutes / 60);
return `${hours}h ${minutes % 60}m`;
}
/** Format a key shortcut string for display (capitalize modifier names) */
export function formatShortcut(shortcut: string): string {
return shortcut
.replace(/ctrl/gi, "Ctrl")
.replace(/shift/gi, "Shift")
.replace(/alt/gi, "Alt");
}
/** Format milliseconds with ms precision for shorter durations */
export function formatDurationMs(ms: number): string {
if (ms < 1000) return `${ms}ms`;
const seconds = Math.floor(ms / 1000);
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
const hours = Math.floor(minutes / 60);
return `${hours}h ${minutes % 60}m`;
}