199 lines
5.5 KiB
TypeScript
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`;
|
|
}
|