485 lines
21 KiB
TypeScript
485 lines
21 KiB
TypeScript
import { Type } from "typebox";
|
|
|
|
export const TOOL_NAME = "interactive_shell";
|
|
export const TOOL_LABEL = "Interactive Shell";
|
|
|
|
export const TOOL_DESCRIPTION = `Run an interactive CLI coding agent in an overlay.
|
|
|
|
Use this ONLY for delegating tasks to other AI coding agents (Claude Code, Cursor CLI, Gemini CLI, Codex, etc.) that have their own TUI and benefit from user interaction.
|
|
|
|
DO NOT use this for regular bash commands - use the standard bash tool instead.
|
|
|
|
MODES:
|
|
- interactive (default): User supervises and controls the session
|
|
- hands-free: Agent monitors with periodic updates, user can take over anytime by typing
|
|
- dispatch: Agent is notified on completion via triggerTurn (no polling needed)
|
|
- monitor: Run in background and wake the agent on structured monitor events (stream, poll-diff, or file-watch)
|
|
|
|
RECOMMENDED DEFAULT FOR DELEGATED TASKS:
|
|
- For fire-and-forget delegations and QA-style checks, prefer mode="dispatch".
|
|
- Dispatch is the safest choice when the agent should continue immediately and be notified automatically on completion.
|
|
|
|
The user will see the process in an overlay. They can:
|
|
- Watch output in real-time
|
|
- Scroll through output (Shift+Up/Down)
|
|
- Transfer output to you (Ctrl+T) - closes overlay and sends output as your context
|
|
- Background (Ctrl+B) - dismiss overlay, keep process running
|
|
- Detach (Ctrl+Q) for menu: transfer/background/kill
|
|
- In hands-free mode: type anything to take over control
|
|
|
|
HANDS-FREE MODE (NON-BLOCKING):
|
|
When mode="hands-free", the tool returns IMMEDIATELY with a sessionId.
|
|
The overlay opens for the user to watch, but you (the agent) get control back right away.
|
|
|
|
Workflow:
|
|
1. Start session: interactive_shell({ command: 'pi "Fix bugs"', mode: "hands-free" })
|
|
-> Returns immediately with sessionId
|
|
2. Check status/output: interactive_shell({ sessionId: "calm-reef" })
|
|
-> Returns current status and any new output since last check
|
|
3. When task is done: interactive_shell({ sessionId: "calm-reef", kill: true })
|
|
-> Kills session and returns final output
|
|
|
|
The user sees the overlay and can:
|
|
- Watch output in real-time
|
|
- Take over by typing (you'll see "user-takeover" status on next query)
|
|
- Kill/background via Ctrl+Q
|
|
|
|
QUERYING SESSION STATUS:
|
|
- interactive_shell({ sessionId: "calm-reef" }) - get status + rendered terminal output (default: 20 lines, 5KB)
|
|
- interactive_shell({ sessionId: "calm-reef", outputLines: 50 }) - get more lines (max: 200)
|
|
- interactive_shell({ sessionId: "calm-reef", outputMaxChars: 20000 }) - get more content (max: 50KB)
|
|
- interactive_shell({ sessionId: "calm-reef", outputOffset: 0, outputLines: 50 }) - pagination (lines 0-49)
|
|
- interactive_shell({ sessionId: "calm-reef", incremental: true }) - get next N unseen lines (server tracks position)
|
|
- interactive_shell({ sessionId: "calm-reef", drain: true }) - only NEW output since last query (raw stream)
|
|
- interactive_shell({ sessionId: "calm-reef", kill: true }) - end session
|
|
- interactive_shell({ sessionId: "calm-reef", input: "..." }) - send input
|
|
- interactive_shell({ monitorStatus: true, monitorSessionId: "calm-reef" }) - query monitor lifecycle/state
|
|
- interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef" }) - query monitor event history
|
|
- interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef", monitorSinceEventId: 42 }) - fetch events after a cursor
|
|
- interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef", monitorTriggerId: "error" }) - filter monitor history by trigger id
|
|
- interactive_shell({ monitorEvents: true, monitorSessionId: "calm-reef", monitorEventLimit: 50, monitorEventOffset: 20 }) - paginate monitor history
|
|
|
|
IMPORTANT: Don't query too frequently! Wait 30-60 seconds between status checks.
|
|
The user is watching the overlay in real-time - you're just checking in periodically.
|
|
|
|
RATE LIMITING:
|
|
Queries are limited to once every 60 seconds (configurable). If you query too soon,
|
|
the tool will automatically wait until the limit expires before returning.
|
|
|
|
SENDING INPUT:
|
|
- interactive_shell({ sessionId: "calm-reef", input: "/help", submit: true }) - type text and press Enter
|
|
- interactive_shell({ sessionId: "calm-reef", inputKeys: ["ctrl+c"] }) - named keys
|
|
- interactive_shell({ sessionId: "calm-reef", inputKeys: ["up", "up", "enter"] }) - multiple keys
|
|
- interactive_shell({ sessionId: "calm-reef", inputHex: ["0x1b", "0x5b", "0x41"] }) - raw escape sequences
|
|
- interactive_shell({ sessionId: "calm-reef", inputPaste: "multiline\\ntext" }) - bracketed paste (prevents auto-execution)
|
|
|
|
Named keys for inputKeys: up, down, left, right, enter, escape, tab, backspace, ctrl+c, ctrl+d, etc.
|
|
Modifiers: ctrl+x, alt+x, shift+tab, ctrl+alt+delete (or c-x, m-x, s-tab syntax)
|
|
For editor-based TUIs like pi, raw \`input\` only types text. It does NOT submit by itself. Prefer \`submit: true\` or \`inputKeys: ["enter"]\` instead of relying on \`\\n\`.
|
|
|
|
TIMEOUT (for TUI commands that don't exit cleanly):
|
|
Use timeout to auto-kill after N milliseconds. Useful for capturing output from commands like "pi --help":
|
|
- interactive_shell({ command: "pi --help", mode: "hands-free", timeout: 5000 })
|
|
|
|
DISPATCH MODE (NON-BLOCKING, NO POLLING):
|
|
When mode="dispatch", the tool returns IMMEDIATELY with a sessionId.
|
|
You do NOT need to poll. You'll be notified automatically when the session completes.
|
|
|
|
Workflow:
|
|
1. Start session: interactive_shell({ command: 'pi "Fix bugs"', mode: "dispatch" })
|
|
-> Returns immediately with sessionId
|
|
2. Do other work - no polling needed
|
|
3. When complete, you receive a notification with the session output
|
|
|
|
Dispatch defaults autoExitOnQuiet to true (opt-out with handsFree.autoExitOnQuiet: false).
|
|
You can still query with sessionId if needed, but it's not required.
|
|
|
|
BACKGROUND DISPATCH (HEADLESS):
|
|
Start a session without any overlay. Process runs headlessly, agent notified on completion:
|
|
- interactive_shell({ command: 'pi "fix bugs"', mode: "dispatch", background: true })
|
|
|
|
MONITOR MODE (EVENT-DRIVEN, HEADLESS):
|
|
Run a background process and wake the agent on structured monitor triggers:
|
|
- interactive_shell({ command: 'npm test --watch', mode: "monitor", monitor: { strategy: "stream", triggers: [{ id: "fail", literal: "FAIL" }] } })
|
|
- interactive_shell({ command: 'npm run dev', mode: "monitor", monitor: { strategy: "stream", triggers: [{ id: "warn", regex: "/error|warn/i" }] } })
|
|
- interactive_shell({ command: 'curl -sf http://localhost:3000/health', mode: "monitor", monitor: { strategy: "poll-diff", triggers: [{ id: "changed", regex: "/./" }], poll: { intervalMs: 5000 } } })
|
|
- interactive_shell({ mode: "monitor", monitor: { strategy: "file-watch", fileWatch: { path: "./uploads", recursive: true, events: ["rename", "change"] }, triggers: [{ id: "pdf", regex: "/\\.pdf$/i" }] } })
|
|
|
|
AGENT-INITIATED BACKGROUND:
|
|
Dismiss an existing overlay, keep the process running in background:
|
|
- interactive_shell({ sessionId: "calm-reef", background: true })
|
|
|
|
ATTACH (REATTACH TO BACKGROUND SESSION):
|
|
Open an overlay for a background session:
|
|
- interactive_shell({ attach: "calm-reef" }) - interactive (blocking)
|
|
- interactive_shell({ attach: "calm-reef", mode: "hands-free" }) - hands-free (poll)
|
|
- interactive_shell({ attach: "calm-reef", mode: "dispatch" }) - dispatch (non-blocking, notified)
|
|
|
|
LIST BACKGROUND SESSIONS:
|
|
- interactive_shell({ listBackground: true })
|
|
|
|
DISMISS BACKGROUND SESSIONS:
|
|
- interactive_shell({ dismissBackground: true }) - kill running, remove exited, clear all
|
|
- interactive_shell({ dismissBackground: "calm-reef" }) - dismiss specific session
|
|
|
|
When using raw \`command\`, this tool does NOT inject prompts for you.
|
|
If you want to start with a prompt, include it in the command using the CLI's own prompt form.
|
|
Structured \`spawn\` also supports a \`prompt\` field for Pi, Codex, Claude, and Cursor using their native startup prompt forms.
|
|
|
|
Examples:
|
|
- pi "Scan the current codebase"
|
|
- claude "Check the current directory and summarize"
|
|
- interactive_shell({ spawn: { agent: "codex" }, mode: "dispatch" })
|
|
- interactive_shell({ spawn: { agent: "cursor", prompt: "Review the diffs" }, mode: "dispatch" })
|
|
- interactive_shell({ spawn: { agent: "claude", prompt: "Review the diffs" }, mode: "dispatch" })
|
|
- interactive_shell({ spawn: { mode: "fork" } }) // pi-only fork of the current persisted session
|
|
- gemini (interactive, idle)
|
|
- aider --yes-always (hands-free, auto-approve)
|
|
- pi --help (with timeout: 5000 to capture help output)`;
|
|
|
|
export const toolParameters = Type.Object({
|
|
command: Type.Optional(
|
|
Type.String({
|
|
description: "The raw CLI command to run (e.g., 'pi \"Fix the bug\"'). Use this for arbitrary CLIs. Mutually exclusive with 'spawn'.",
|
|
}),
|
|
),
|
|
spawn: Type.Optional(
|
|
Type.Object({
|
|
agent: Type.Optional(Type.Union([
|
|
Type.Literal("pi"),
|
|
Type.Literal("codex"),
|
|
Type.Literal("claude"),
|
|
Type.Literal("cursor"),
|
|
], {
|
|
description: "Spawn agent to launch. Defaults to the configured spawn.defaultAgent.",
|
|
})),
|
|
mode: Type.Optional(Type.Union([
|
|
Type.Literal("fresh"),
|
|
Type.Literal("fork"),
|
|
], {
|
|
description: "Spawn mode. 'fork' is only supported for pi and requires a persisted current session.",
|
|
})),
|
|
worktree: Type.Optional(Type.Boolean({
|
|
description: "Launch in a separate git worktree. Defaults to spawn.worktree from config.",
|
|
})),
|
|
prompt: Type.Optional(Type.String({
|
|
description: "Optional startup prompt for pi, codex, claude, or cursor. Uses each CLI's native prompt-bearing startup form.",
|
|
})),
|
|
}, {
|
|
description: "Structured spawn request for pi, codex, claude, or cursor. Use this instead of building the command string manually when you want the extension's spawn defaults, Pi-only fork behavior, worktree support, or native startup prompts.",
|
|
}),
|
|
),
|
|
sessionId: Type.Optional(
|
|
Type.String({
|
|
description: "Session ID to interact with an existing hands-free session",
|
|
}),
|
|
),
|
|
kill: Type.Optional(
|
|
Type.Boolean({
|
|
description: "Kill the session (requires sessionId). Use when task appears complete.",
|
|
}),
|
|
),
|
|
outputLines: Type.Optional(
|
|
Type.Number({
|
|
description: "Number of lines to return when querying (default: 20, max: 200)",
|
|
}),
|
|
),
|
|
outputMaxChars: Type.Optional(
|
|
Type.Number({
|
|
description: "Max chars to return when querying (default: 5KB, max: 50KB)",
|
|
}),
|
|
),
|
|
outputOffset: Type.Optional(
|
|
Type.Number({
|
|
description: "Line offset for pagination (0-indexed). Use with outputLines to read specific ranges.",
|
|
}),
|
|
),
|
|
drain: Type.Optional(
|
|
Type.Boolean({
|
|
description: "If true, return only NEW output since last query (raw stream). More token-efficient for repeated polling.",
|
|
}),
|
|
),
|
|
incremental: Type.Optional(
|
|
Type.Boolean({
|
|
description: "If true, return next N lines not yet seen. Server tracks position - just keep calling to paginate through output.",
|
|
}),
|
|
),
|
|
settings: Type.Optional(
|
|
Type.Object({
|
|
updateInterval: Type.Optional(
|
|
Type.Number({ description: "Change max update interval for existing session (ms)" }),
|
|
),
|
|
quietThreshold: Type.Optional(
|
|
Type.Number({ description: "Change quiet threshold for existing session (ms)" }),
|
|
),
|
|
}),
|
|
),
|
|
input: Type.Optional(
|
|
Type.String({ description: "Raw text to send to the session (requires sessionId). This only types the text; it does not submit it. Use submit=true or inputKeys:['enter'] when you want to press Enter." }),
|
|
),
|
|
submit: Type.Optional(
|
|
Type.Boolean({ description: "Press Enter after sending any input. Prefer this when submitting slash commands or prompts to editor-based TUIs like pi. (requires sessionId)" }),
|
|
),
|
|
inputKeys: Type.Optional(
|
|
Type.Array(Type.String(), {
|
|
description: "Named keys with modifier support: up, down, enter, ctrl+c, alt+x, shift+tab, ctrl+alt+delete, etc. (requires sessionId)",
|
|
}),
|
|
),
|
|
inputHex: Type.Optional(
|
|
Type.Array(Type.String(), {
|
|
description: "Hex bytes to send as raw escape sequences (e.g., ['0x1b', '0x5b', '0x41'] for ESC[A). (requires sessionId)",
|
|
}),
|
|
),
|
|
inputPaste: Type.Optional(
|
|
Type.String({
|
|
description: "Text to paste with bracketed paste mode - prevents shells from auto-executing multiline input. (requires sessionId)",
|
|
}),
|
|
),
|
|
cwd: Type.Optional(
|
|
Type.String({
|
|
description: "Working directory for the command",
|
|
}),
|
|
),
|
|
name: Type.Optional(
|
|
Type.String({
|
|
description: "Optional session name (used for session IDs)",
|
|
}),
|
|
),
|
|
reason: Type.Optional(
|
|
Type.String({
|
|
description:
|
|
"Brief explanation shown in the overlay header only (not passed to the subprocess)",
|
|
}),
|
|
),
|
|
mode: Type.Optional(
|
|
Type.Union([
|
|
Type.Literal("interactive"),
|
|
Type.Literal("hands-free"),
|
|
Type.Literal("dispatch"),
|
|
Type.Literal("monitor"),
|
|
], {
|
|
description: "Mode: 'interactive' (default, user controls), 'hands-free' (agent monitors, user can take over), 'dispatch' (agent notified on completion, no polling needed), or 'monitor' (headless structured event monitor with stream/poll-diff/file-watch strategies).",
|
|
}),
|
|
),
|
|
monitor: Type.Optional(
|
|
Type.Object({
|
|
strategy: Type.Optional(Type.Union([
|
|
Type.Literal("stream"),
|
|
Type.Literal("poll-diff"),
|
|
Type.Literal("file-watch"),
|
|
], {
|
|
description: "Monitor strategy. stream = line-based trigger matching. poll-diff = periodic snapshot diffing. file-watch = first-class filesystem watch events.",
|
|
})),
|
|
triggers: Type.Array(Type.Object({
|
|
id: Type.String({ description: "Unique trigger id used in emitted event payloads." }),
|
|
literal: Type.Optional(Type.String({ description: "Literal substring trigger." })),
|
|
regex: Type.Optional(Type.String({ description: "Regex trigger string. Supports /pattern/flags format." })),
|
|
cooldownMs: Type.Optional(Type.Number({ description: "Optional per-trigger cooldown window in ms." })),
|
|
threshold: Type.Optional(Type.Object({
|
|
captureGroup: Type.Number({ description: "Regex capture group index parsed as number (requires regex matcher)." }),
|
|
op: Type.Union([
|
|
Type.Literal("lt"),
|
|
Type.Literal("lte"),
|
|
Type.Literal("gt"),
|
|
Type.Literal("gte"),
|
|
], { description: "Threshold operator." }),
|
|
value: Type.Number({ description: "Threshold numeric value." }),
|
|
})),
|
|
}), {
|
|
description: "Named trigger definitions. Each trigger must define exactly one matcher: literal or regex.",
|
|
}),
|
|
fileWatch: Type.Optional(Type.Object({
|
|
path: Type.String({ description: "Path to watch for strategy='file-watch'. Relative paths resolve from cwd." }),
|
|
recursive: Type.Optional(Type.Boolean({ description: "Watch subdirectories recursively (platform-dependent support)." })),
|
|
events: Type.Optional(Type.Array(Type.Union([
|
|
Type.Literal("rename"),
|
|
Type.Literal("change"),
|
|
]), { description: "Filesystem event names to emit." })),
|
|
})),
|
|
poll: Type.Optional(Type.Object({
|
|
intervalMs: Type.Optional(Type.Number({ description: "Poll interval in ms for strategy='poll-diff' (default: 5000)." })),
|
|
})),
|
|
persistence: Type.Optional(Type.Object({
|
|
stopAfterFirstEvent: Type.Optional(Type.Boolean({ description: "Stop monitor after first emitted event." })),
|
|
maxEvents: Type.Optional(Type.Number({ description: "Maximum emitted events before monitor stops." })),
|
|
})),
|
|
throttle: Type.Optional(Type.Object({
|
|
dedupeExactLine: Type.Optional(Type.Boolean({ description: "Suppress repeated exact line/diff payloads (default: true)." })),
|
|
cooldownMs: Type.Optional(Type.Number({ description: "Optional global cooldown in ms across triggers." })),
|
|
})),
|
|
detector: Type.Optional(Type.Object({
|
|
detectorCommand: Type.String({ description: "External detector command. Receives JSON candidate event on stdin and returns JSON decision on stdout." }),
|
|
timeoutMs: Type.Optional(Type.Number({ description: "Detector command timeout in ms (default: 3000)." })),
|
|
})),
|
|
}, {
|
|
description: "Structured monitor configuration required when mode='monitor'.",
|
|
}),
|
|
),
|
|
background: Type.Optional(
|
|
Type.Boolean({
|
|
description: "Run without overlay (with mode='dispatch' or mode='monitor') or dismiss existing overlay (with sessionId). Process runs in background, user can /attach.",
|
|
}),
|
|
),
|
|
attach: Type.Optional(
|
|
Type.String({
|
|
description: "Background session ID to reattach. Opens overlay with the specified mode.",
|
|
}),
|
|
),
|
|
listBackground: Type.Optional(
|
|
Type.Boolean({
|
|
description: "List all background sessions.",
|
|
}),
|
|
),
|
|
dismissBackground: Type.Optional(
|
|
Type.Union([Type.Boolean(), Type.String()], {
|
|
description: "Dismiss background sessions. true = all, string = specific session ID. Kills running sessions, removes exited ones.",
|
|
}),
|
|
),
|
|
monitorStatus: Type.Optional(
|
|
Type.Boolean({
|
|
description: "Query monitor lifecycle/state summary. Requires monitorSessionId or sessionId.",
|
|
}),
|
|
),
|
|
monitorEvents: Type.Optional(
|
|
Type.Boolean({
|
|
description: "Query structured monitor event history instead of session output. Requires monitorSessionId or sessionId.",
|
|
}),
|
|
),
|
|
monitorSessionId: Type.Optional(
|
|
Type.String({
|
|
description: "Target monitor session for monitorStatus/monitorEvents queries.",
|
|
}),
|
|
),
|
|
monitorEventLimit: Type.Optional(
|
|
Type.Number({
|
|
description: "Max monitor events to return (default: 20).",
|
|
}),
|
|
),
|
|
monitorEventOffset: Type.Optional(
|
|
Type.Number({
|
|
description: "How many newest monitor events to skip before returning results (default: 0).",
|
|
}),
|
|
),
|
|
monitorSinceEventId: Type.Optional(
|
|
Type.Number({
|
|
description: "Only return monitor events with eventId greater than this cursor.",
|
|
}),
|
|
),
|
|
monitorTriggerId: Type.Optional(
|
|
Type.String({
|
|
description: "Filter monitor events to a specific trigger id.",
|
|
}),
|
|
),
|
|
handsFree: Type.Optional(
|
|
Type.Object({
|
|
updateMode: Type.Optional(
|
|
Type.String({
|
|
description: "Update mode: 'on-quiet' (default, emit when output stops) or 'interval' (emit on fixed schedule)",
|
|
}),
|
|
),
|
|
updateInterval: Type.Optional(
|
|
Type.Number({ description: "Max interval between updates in ms (default: 60000)" }),
|
|
),
|
|
quietThreshold: Type.Optional(
|
|
Type.Number({ description: "Silence duration before emitting update in on-quiet mode (default: 8000ms)" }),
|
|
),
|
|
gracePeriod: Type.Optional(
|
|
Type.Number({ description: "Startup grace period before autoExitOnQuiet can kill the session (default: 15000ms)" }),
|
|
),
|
|
updateMaxChars: Type.Optional(
|
|
Type.Number({ description: "Max chars per update (default: 1500)" }),
|
|
),
|
|
maxTotalChars: Type.Optional(
|
|
Type.Number({ description: "Total char budget for all updates (default: 100000). Updates stop including content when exhausted." }),
|
|
),
|
|
autoExitOnQuiet: Type.Optional(
|
|
Type.Boolean({
|
|
description: "Auto-kill session when output stops (after quietThreshold). Defaults to false. Set to true for fire-and-forget single-task delegations.",
|
|
}),
|
|
),
|
|
}),
|
|
),
|
|
handoffPreview: Type.Optional(
|
|
Type.Object({
|
|
enabled: Type.Optional(Type.Boolean({ description: "Include last N lines in tool result details" })),
|
|
lines: Type.Optional(Type.Number({ description: "Tail lines to include (default from config)" })),
|
|
maxChars: Type.Optional(
|
|
Type.Number({ description: "Max chars to include in tail preview (default from config)" }),
|
|
),
|
|
}),
|
|
),
|
|
handoffSnapshot: Type.Optional(
|
|
Type.Object({
|
|
enabled: Type.Optional(Type.Boolean({ description: "Write a transcript snapshot on detach/exit" })),
|
|
lines: Type.Optional(Type.Number({ description: "Tail lines to capture (default from config)" })),
|
|
maxChars: Type.Optional(Type.Number({ description: "Max chars to write (default from config)" })),
|
|
}),
|
|
),
|
|
timeout: Type.Optional(
|
|
Type.Number({
|
|
description: "Auto-kill process after N milliseconds. Useful for TUI commands that don't exit cleanly (e.g., 'pi --help')",
|
|
}),
|
|
),
|
|
});
|
|
|
|
/** Parsed tool parameters type */
|
|
export interface ToolParams {
|
|
command?: string;
|
|
spawn?: { agent?: "pi" | "codex" | "claude" | "cursor"; mode?: "fresh" | "fork"; worktree?: boolean; prompt?: string };
|
|
sessionId?: string;
|
|
kill?: boolean;
|
|
outputLines?: number;
|
|
outputMaxChars?: number;
|
|
outputOffset?: number;
|
|
drain?: boolean;
|
|
incremental?: boolean;
|
|
settings?: { updateInterval?: number; quietThreshold?: number };
|
|
input?: string;
|
|
submit?: boolean;
|
|
inputKeys?: string[];
|
|
inputHex?: string[];
|
|
inputPaste?: string;
|
|
cwd?: string;
|
|
name?: string;
|
|
reason?: string;
|
|
mode?: "interactive" | "hands-free" | "dispatch" | "monitor";
|
|
background?: boolean;
|
|
monitor?: {
|
|
strategy?: "stream" | "poll-diff" | "file-watch";
|
|
triggers: Array<{
|
|
id: string;
|
|
literal?: string;
|
|
regex?: string;
|
|
cooldownMs?: number;
|
|
threshold?: { captureGroup: number; op: "lt" | "lte" | "gt" | "gte"; value: number };
|
|
}>;
|
|
fileWatch?: { path: string; recursive?: boolean; events?: Array<"rename" | "change"> };
|
|
poll?: { intervalMs?: number };
|
|
persistence?: { stopAfterFirstEvent?: boolean; maxEvents?: number };
|
|
throttle?: { dedupeExactLine?: boolean; cooldownMs?: number };
|
|
detector?: { detectorCommand: string; timeoutMs?: number };
|
|
};
|
|
attach?: string;
|
|
listBackground?: boolean;
|
|
dismissBackground?: boolean | string;
|
|
monitorStatus?: boolean;
|
|
monitorEvents?: boolean;
|
|
monitorSessionId?: string;
|
|
monitorEventLimit?: number;
|
|
monitorEventOffset?: number;
|
|
monitorSinceEventId?: number;
|
|
monitorTriggerId?: string;
|
|
handsFree?: {
|
|
updateMode?: "on-quiet" | "interval";
|
|
updateInterval?: number;
|
|
quietThreshold?: number;
|
|
gracePeriod?: number;
|
|
updateMaxChars?: number;
|
|
maxTotalChars?: number;
|
|
autoExitOnQuiet?: boolean;
|
|
};
|
|
handoffPreview?: { enabled?: boolean; lines?: number; maxChars?: number };
|
|
handoffSnapshot?: { enabled?: boolean; lines?: number; maxChars?: number };
|
|
timeout?: number;
|
|
}
|