{"version":3,"file":"chain-clarify.d.ts","sourceRoot":"","sources":["../../../../src/runs/foreground/chain-clarify.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Chain Clarification TUI Component\n *\n * Shows templates and resolved behaviors for each step in a chain.\n * Supports runtime editing of templates, output paths, reads lists, and progress toggle.\n */\n\nimport type { Theme } from \"@lpb-work/pi-coding-agent\";\nimport type { Component, TUI } from \"@lpb-work/pi-tui\";\nimport { matchesKey, truncateToWidth, visibleWidth } from \"@lpb-work/pi-tui\";\nimport type { AgentConfig } from \"../../agents/agents.ts\";\nimport {\n\tfindModelInfo,\n\tgetSupportedThinkingLevels,\n\ttype ModelInfo,\n\ttype ThinkingLevel,\n} from \"../../shared/model-info.ts\";\nimport type { ResolvedStepBehavior } from \"../../shared/settings.ts\";\nimport { resolveModelCandidate, splitThinkingSuffix } from \"../shared/model-fallback.ts\";\n\ntype ClarifyMode = \"single\" | \"parallel\" | \"chain\";\n\nexport interface BehaviorOverride {\n\toutput?: string | false;\n\treads?: string[] | false;\n\tprogress?: boolean;\n\tmodel?: string;\n\tskills?: string[] | false;\n}\n\nexport interface ChainClarifyResult {\n\tconfirmed: boolean;\n\ttemplates: string[];\n\tbehaviorOverrides: (BehaviorOverride | undefined)[];\n\trunInBackground?: boolean;\n}\n\ntype EditMode = \"template\" | \"output\" | \"reads\" | \"model\" | \"thinking\" | \"skills\";\n\ninterface TextEditorState {\n\tbuffer: string;\n\tcursor: number;\n\tviewportOffset: number;\n}\n\nfunction createEditorState(initial = \"\"): TextEditorState {\n\treturn { buffer: initial, cursor: 0, viewportOffset: 0 };\n}\n\nfunction wrapText(text: string, width: number): { lines: string[]; starts: number[] } {\n\tif (width <= 0) return { lines: [text], starts: [0] };\n\tif (text.length === 0) return { lines: [\"\"], starts: [0] };\n\n\tconst lines: string[] = [];\n\tconst starts: number[] = [];\n\tlet offset = 0;\n\tconst segments = text.split(\"\\n\");\n\tfor (const [index, segment] of segments.entries()) {\n\t\tif (segment.length === 0) {\n\t\t\tstarts.push(offset);\n\t\t\tlines.push(\"\");\n\t\t} else {\n\t\t\tlet lineStart = 0;\n\t\t\tlet pos = 0;\n\t\t\tlet lineWidth = 0;\n\t\t\twhile (pos < segment.length) {\n\t\t\t\tconst char = String.fromCodePoint(segment.codePointAt(pos)!);\n\t\t\t\tconst charWidth = visibleWidth(char);\n\t\t\t\tif (lineWidth > 0 && lineWidth + charWidth > width) {\n\t\t\t\t\tstarts.push(offset + lineStart);\n\t\t\t\t\tlines.push(segment.slice(lineStart, pos));\n\t\t\t\t\tlineStart = pos;\n\t\t\t\t\tlineWidth = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tpos += char.length;\n\t\t\t\tlineWidth += charWidth;\n\t\t\t}\n\t\t\tstarts.push(offset + lineStart);\n\t\t\tlines.push(segment.slice(lineStart));\n\t\t}\n\t\toffset += segment.length + (index < segments.length - 1 ? 1 : 0);\n\t}\n\tif (!text.endsWith(\"\\n\") && text.length > 0 && visibleWidth(lines[lines.length - 1] ?? \"\") === width) {\n\t\tstarts.push(text.length);\n\t\tlines.push(\"\");\n\t}\n\treturn { lines, starts };\n}\n\nfunction getCursorDisplayPos(cursor: number, starts: number[]): { line: number; col: number } {\n\tfor (let i = starts.length - 1; i >= 0; i--) {\n\t\tif (cursor >= starts[i]!) return { line: i, col: cursor - starts[i]! };\n\t}\n\treturn { line: 0, col: 0 };\n}\n\nfunction ensureCursorVisible(cursorLine: number, viewportHeight: number, currentOffset: number): number {\n\tif (cursorLine < currentOffset) return Math.max(0, cursorLine);\n\tif (cursorLine >= currentOffset + viewportHeight) return Math.max(0, cursorLine - viewportHeight + 1);\n\treturn Math.max(0, currentOffset);\n}\n\nfunction isWordChar(ch: string): boolean {\n\tconst code = ch.charCodeAt(0);\n\treturn (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;\n}\n\nfunction wordBackward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos > 0 && !isWordChar(buffer[pos - 1]!)) pos--;\n\twhile (pos > 0 && isWordChar(buffer[pos - 1]!)) pos--;\n\treturn pos;\n}\n\nfunction wordForward(buffer: string, cursor: number): number {\n\tlet pos = cursor;\n\twhile (pos < buffer.length && isWordChar(buffer[pos]!)) pos++;\n\twhile (pos < buffer.length && !isWordChar(buffer[pos]!)) pos++;\n\treturn pos;\n}\n\nfunction normalizeInsertText(data: string): string | null {\n\tlet text = data.split(\"\\x1b[200~\").join(\"\").split(\"\\x1b[201~\").join(\"\");\n\ttext = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\tconst newline = text.indexOf(\"\\n\");\n\tif (newline !== -1) text = text.slice(0, newline);\n\ttext = text.replace(/\\t/g, \"    \");\n\tif (text.length === 0) return null;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tif (text.charCodeAt(i) < 32) return null;\n\t}\n\treturn text;\n}\n\nfunction handleEditorInput(state: TextEditorState, data: string, textWidth: number): TextEditorState | null {\n\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\") || matchesKey(data, \"return\")) return null;\n\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, textWidth);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\n\tif (matchesKey(data, \"alt+left\") || matchesKey(data, \"ctrl+left\"))\n\t\treturn { ...state, cursor: wordBackward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"alt+right\") || matchesKey(data, \"ctrl+right\"))\n\t\treturn { ...state, cursor: wordForward(state.buffer, state.cursor) };\n\tif (matchesKey(data, \"left\")) return state.cursor > 0 ? { ...state, cursor: state.cursor - 1 } : state;\n\tif (matchesKey(data, \"right\"))\n\t\treturn state.cursor < state.buffer.length ? { ...state, cursor: state.cursor + 1 } : state;\n\tif (matchesKey(data, \"up\") && cursorPos.line > 0) {\n\t\tconst targetLine = cursorPos.line - 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"down\") && cursorPos.line < wrapped.length - 1) {\n\t\tconst targetLine = cursorPos.line + 1;\n\t\treturn { ...state, cursor: starts[targetLine]! + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) };\n\t}\n\tif (matchesKey(data, \"home\")) return { ...state, cursor: starts[cursorPos.line]! };\n\tif (matchesKey(data, \"end\"))\n\t\treturn { ...state, cursor: starts[cursorPos.line]! + (wrapped[cursorPos.line]?.length ?? 0) };\n\tif (matchesKey(data, \"ctrl+home\")) return { ...state, cursor: 0 };\n\tif (matchesKey(data, \"ctrl+end\")) return { ...state, cursor: state.buffer.length };\n\tif (matchesKey(data, \"alt+backspace\")) {\n\t\tconst target = wordBackward(state.buffer, state.cursor);\n\t\treturn target === state.cursor\n\t\t\t? state\n\t\t\t: { ...state, buffer: state.buffer.slice(0, target) + state.buffer.slice(state.cursor), cursor: target };\n\t}\n\tif (matchesKey(data, \"backspace\")) {\n\t\treturn state.cursor > 0\n\t\t\t? {\n\t\t\t\t\t...state,\n\t\t\t\t\tbuffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor),\n\t\t\t\t\tcursor: state.cursor - 1,\n\t\t\t\t}\n\t\t\t: state;\n\t}\n\tif (matchesKey(data, \"delete\")) {\n\t\treturn state.cursor < state.buffer.length\n\t\t\t? { ...state, buffer: state.buffer.slice(0, state.cursor) + state.buffer.slice(state.cursor + 1) }\n\t\t\t: state;\n\t}\n\n\tconst insert = normalizeInsertText(data);\n\treturn insert\n\t\t? {\n\t\t\t\t...state,\n\t\t\t\tbuffer: state.buffer.slice(0, state.cursor) + insert + state.buffer.slice(state.cursor),\n\t\t\t\tcursor: state.cursor + insert.length,\n\t\t\t}\n\t\t: null;\n}\n\nfunction renderWithCursor(text: string, cursorPos: number): string {\n\tconst before = text.slice(0, cursorPos);\n\tconst cursorChar = text[cursorPos] ?? \" \";\n\tconst after = text.slice(cursorPos + 1);\n\treturn `${before}\\x1b[7m${cursorChar}\\x1b[27m${after}`;\n}\n\nfunction renderEditor(state: TextEditorState, width: number, viewportHeight: number): string[] {\n\tconst { lines: wrapped, starts } = wrapText(state.buffer, width);\n\tconst cursorPos = getCursorDisplayPos(state.cursor, starts);\n\tconst lines: string[] = [];\n\tfor (let i = 0; i < viewportHeight; i++) {\n\t\tconst lineIdx = state.viewportOffset + i;\n\t\tlet content = lineIdx < wrapped.length ? (wrapped[lineIdx] ?? \"\") : \"\";\n\t\tif (lineIdx === cursorPos.line) content = renderWithCursor(content, cursorPos.col);\n\t\tlines.push(content);\n\t}\n\treturn lines;\n}\n\n/**\n * TUI component for chain clarification.\n * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component\n */\nexport class ChainClarifyComponent implements Component {\n\treadonly width = 84;\n\n\tprivate selectedStep = 0;\n\tprivate editingStep: number | null = null;\n\tprivate editMode: EditMode = \"template\";\n\tprivate editState: TextEditorState = createEditorState();\n\n\tprivate readonly EDIT_VIEWPORT_HEIGHT = 12;\n\tprivate behaviorOverrides: Map<number, BehaviorOverride> = new Map();\n\tprivate modelSearchQuery: string = \"\";\n\tprivate modelSelectedIndex: number = 0;\n\tprivate filteredModels: ModelInfo[] = [];\n\tprivate readonly MODEL_SELECTOR_HEIGHT = 10;\n\tprivate thinkingSelectedIndex: number = 0;\n\tprivate skillSearchQuery: string = \"\";\n\tprivate skillSelectedNames: Set<string> = new Set();\n\tprivate skillCursorIndex: number = 0;\n\tprivate filteredSkills: Array<{ name: string; source: string; description?: string }> = [];\n\tprivate noticeMessage: { text: string; type: \"info\" | \"error\" } | null = null;\n\tprivate noticeMessageTimer: ReturnType<typeof setTimeout> | null = null;\n\t/** Run in background (async) mode */\n\tprivate runInBackground = false;\n\tprivate tui: TUI;\n\tprivate theme: Theme;\n\tprivate agentConfigs: AgentConfig[];\n\tprivate templates: string[];\n\tprivate originalTask: string;\n\tprivate chainDir: string | undefined;\n\tprivate resolvedBehaviors: ResolvedStepBehavior[];\n\tprivate availableModels: ModelInfo[];\n\tprivate preferredProvider: string | undefined;\n\tprivate availableSkills: Array<{ name: string; source: string; description?: string }>;\n\tprivate done: (result: ChainClarifyResult) => void;\n\tprivate mode: ClarifyMode;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\ttheme: Theme,\n\t\tagentConfigs: AgentConfig[],\n\t\ttemplates: string[],\n\t\toriginalTask: string,\n\t\tchainDir: string | undefined,\n\t\tresolvedBehaviors: ResolvedStepBehavior[],\n\t\tavailableModels: ModelInfo[],\n\t\tpreferredProvider: string | undefined,\n\t\tavailableSkills: Array<{ name: string; source: string; description?: string }>,\n\t\tdone: (result: ChainClarifyResult) => void,\n\t\tmode: ClarifyMode = \"chain\",\n\t) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.agentConfigs = agentConfigs;\n\t\tthis.templates = templates;\n\t\tthis.originalTask = originalTask;\n\t\tthis.chainDir = chainDir;\n\t\tthis.resolvedBehaviors = resolvedBehaviors;\n\t\tthis.availableModels = availableModels;\n\t\tthis.preferredProvider = preferredProvider;\n\t\tthis.availableSkills = availableSkills;\n\t\tthis.done = done;\n\t\tthis.mode = mode;\n\t\tthis.filteredModels = [...availableModels];\n\t\tthis.filteredSkills = [...availableSkills];\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Helper methods for rendering\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Pad string to specified visible width */\n\tprivate pad(s: string, len: number): string {\n\t\tconst vis = visibleWidth(s);\n\t\treturn s + \" \".repeat(Math.max(0, len - vis));\n\t}\n\n\t/** Create a row with border characters */\n\tprivate row(content: string): string {\n\t\tconst innerW = this.width - 2;\n\t\treturn this.theme.fg(\"border\", \"│\") + this.pad(content, innerW) + this.theme.fg(\"border\", \"│\");\n\t}\n\n\t/** Render centered header line with border */\n\tprivate renderHeader(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", `╭${\"─\".repeat(padLeft)}`) +\n\t\t\tthis.theme.fg(\"accent\", text) +\n\t\t\tthis.theme.fg(\"border\", `${\"─\".repeat(padRight)}╮`)\n\t\t);\n\t}\n\n\t/** Render centered footer line with border */\n\tprivate renderFooter(text: string): string {\n\t\tconst innerW = this.width - 2;\n\t\tconst padLen = Math.max(0, innerW - visibleWidth(text));\n\t\tconst padLeft = Math.floor(padLen / 2);\n\t\tconst padRight = padLen - padLeft;\n\t\treturn (\n\t\t\tthis.theme.fg(\"border\", `╰${\"─\".repeat(padLeft)}`) +\n\t\t\tthis.theme.fg(\"dim\", text) +\n\t\t\tthis.theme.fg(\"border\", `${\"─\".repeat(padRight)}╯`)\n\t\t);\n\t}\n\n\t/** Exit edit mode and reset state */\n\tprivate exitEditMode(): void {\n\t\tthis.editingStep = null;\n\t\tthis.editState = createEditorState();\n\t\tthis.tui.requestRender();\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Full edit mode methods\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Render the full-edit takeover view */\n\tprivate renderFullEditMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst textWidth = innerW - 2; // 1 char padding on each side\n\t\tconst lines: string[] = [];\n\n\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\tthis.editState = {\n\t\t\t...this.editState,\n\t\t\tviewportOffset: ensureCursorVisible(cursorPos.line, this.EDIT_VIEWPORT_HEIGHT, this.editState.viewportOffset),\n\t\t};\n\n\t\t// Header (truncate agent name to prevent overflow)\n\t\tconst fieldName = this.editMode === \"template\" ? \"task\" : this.editMode;\n\t\tconst rawAgentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst maxAgentLen = innerW - 30; // Reserve space for \" Editing X (Step/Task N: ) \"\n\t\tconst agentName = rawAgentName.length > maxAgentLen ? `${rawAgentName.slice(0, maxAgentLen - 1)}…` : rawAgentName;\n\t\t// Use mode-appropriate terminology\n\t\tconst stepLabel =\n\t\t\tthis.mode === \"single\"\n\t\t\t\t? agentName\n\t\t\t\t: this.mode === \"parallel\"\n\t\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Editing ${fieldName} (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst editorLines = renderEditor(this.editState, textWidth, this.EDIT_VIEWPORT_HEIGHT);\n\t\tfor (const line of editorLines) {\n\t\t\tlines.push(this.row(` ${line}`));\n\t\t}\n\n\t\tconst linesBelow = wrapped.length - this.editState.viewportOffset - this.EDIT_VIEWPORT_HEIGHT;\n\t\tconst hasMore = linesBelow > 0;\n\t\tconst hasLess = this.editState.viewportOffset > 0;\n\t\tlet scrollInfo = \"\";\n\t\tif (hasLess) scrollInfo += \"↑\";\n\t\tif (hasMore) scrollInfo += `↓ ${linesBelow}+`;\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst footerText = scrollInfo\n\t\t\t? ` [Esc] Done • [Ctrl+C] Discard • ${scrollInfo} `\n\t\t\t: \" [Esc] Done • [Ctrl+C] Discard \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t// ─────────────────────────────────────────────────────────────────────────────\n\t// Behavior helpers\n\t// ─────────────────────────────────────────────────────────────────────────────\n\n\t/** Get effective behavior for a step (with user overrides applied) */\n\tprivate getEffectiveBehavior(stepIndex: number): ResolvedStepBehavior {\n\t\tconst base = this.resolvedBehaviors[stepIndex]!;\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (!override) return base;\n\n\t\treturn {\n\t\t\toutput: override.output !== undefined ? override.output : base.output,\n\t\t\toutputMode: base.outputMode,\n\t\t\treads: override.reads !== undefined ? override.reads : base.reads,\n\t\t\tprogress: override.progress !== undefined ? override.progress : base.progress,\n\t\t\tskills: override.skills !== undefined ? override.skills : base.skills,\n\t\t\tmodel: override.model !== undefined ? override.model : base.model,\n\t\t};\n\t}\n\n\t/** Get the effective model for a step (override or agent default) */\n\tprivate getEffectiveModel(stepIndex: number): string {\n\t\tconst override = this.behaviorOverrides.get(stepIndex);\n\t\tif (override?.model) return this.resolveModelFullId(override.model);\n\n\t\tconst baseModel = this.resolvedBehaviors[stepIndex]?.model;\n\t\tif (baseModel) return this.resolveModelFullId(baseModel);\n\t\treturn \"default\";\n\t}\n\n\t/** Resolve a model name to its full provider/model format */\n\tprivate resolveModelFullId(modelName: string): string {\n\t\treturn resolveModelCandidate(modelName, this.availableModels, this.preferredProvider) ?? modelName;\n\t}\n\n\t/** Update a behavior override for a step */\n\tprivate updateBehavior(\n\t\tstepIndex: number,\n\t\tfield: keyof BehaviorOverride,\n\t\tvalue: string | boolean | string[] | false,\n\t): void {\n\t\tconst existing = this.behaviorOverrides.get(stepIndex) ?? {};\n\t\tthis.behaviorOverrides.set(stepIndex, { ...existing, [field]: value });\n\t}\n\n\tprivate showNotice(text: string, type: \"info\" | \"error\"): void {\n\t\tthis.noticeMessage = { text, type };\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = setTimeout(() => {\n\t\t\tthis.noticeMessage = null;\n\t\t\tthis.noticeMessageTimer = null;\n\t\t\tthis.tui.requestRender();\n\t\t}, 2000);\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") {\n\t\t\t\tthis.handleModelSelectorInput(data);\n\t\t\t} else if (this.editMode === \"thinking\") {\n\t\t\t\tthis.handleThinkingSelectorInput(data);\n\t\t\t} else if (this.editMode === \"skills\") {\n\t\t\t\tthis.handleSkillSelectorInput(data);\n\t\t\t} else {\n\t\t\t\tthis.handleEditInput(data);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.done({ confirmed: false, templates: [], behaviorOverrides: [] });\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst overrides: (BehaviorOverride | undefined)[] = [];\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\toverrides.push(this.behaviorOverrides.get(i));\n\t\t\t}\n\t\t\tthis.done({\n\t\t\t\tconfirmed: true,\n\t\t\t\ttemplates: this.templates,\n\t\t\t\tbehaviorOverrides: overrides,\n\t\t\t\trunInBackground: this.runInBackground,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\") || matchesKey(data, \"k\")) {\n\t\t\tthis.selectedStep = Math.max(0, this.selectedStep - 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\") || matchesKey(data, \"j\")) {\n\t\t\tconst maxStep = Math.max(0, this.agentConfigs.length - 1);\n\t\t\tthis.selectedStep = Math.min(maxStep, this.selectedStep + 1);\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"e\") {\n\t\t\tthis.enterEditMode(\"template\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"m\") {\n\t\t\tthis.enterModelSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"t\") {\n\t\t\tthis.enterThinkingSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"s\") {\n\t\t\tthis.editingStep = this.selectedStep;\n\t\t\tthis.editMode = \"skills\";\n\t\t\tthis.skillSearchQuery = \"\";\n\t\t\tthis.skillCursorIndex = 0;\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t\tconst current = this.getEffectiveBehavior(this.selectedStep).skills;\n\t\t\tthis.skillSelectedNames.clear();\n\t\t\tif (current !== false && current.length > 0) {\n\t\t\t\tcurrent.forEach((skillName) => this.skillSelectedNames.add(skillName));\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"w\" && this.mode !== \"parallel\") {\n\t\t\tthis.enterEditMode(\"output\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"r\" && this.mode === \"chain\") {\n\t\t\tthis.enterEditMode(\"reads\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"p\" && this.mode === \"chain\") {\n\t\t\tconst anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\t\tconst newState = !anyEnabled;\n\t\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\t\tthis.updateBehavior(i, \"progress\", newState);\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \"b\") {\n\t\t\tthis.runInBackground = !this.runInBackground;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate enterEditMode(mode: EditMode): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = mode;\n\t\tlet buffer = \"\";\n\n\t\tif (mode === \"template\") {\n\t\t\tconst template = this.templates[this.selectedStep] ?? \"\";\n\t\t\tbuffer = template.split(\"\\n\")[0] ?? \"\";\n\t\t} else if (mode === \"output\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.output === false ? \"\" : behavior.output || \"\";\n\t\t} else if (mode === \"reads\") {\n\t\t\tconst behavior = this.getEffectiveBehavior(this.selectedStep);\n\t\t\tbuffer = behavior.reads === false ? \"\" : behavior.reads?.join(\", \") || \"\";\n\t\t}\n\n\t\tthis.editState = createEditorState(buffer);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Enter model selector mode */\n\tprivate enterModelSelector(): void {\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"model\";\n\t\tthis.modelSearchQuery = \"\";\n\t\tthis.modelSelectedIndex = 0;\n\t\tthis.filteredModels = [...this.availableModels];\n\t\tconst currentModel = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep)).baseModel;\n\t\tconst currentIndex = this.filteredModels.findIndex((m) => m.fullId === currentModel || m.id === currentModel);\n\t\tif (currentIndex >= 0) {\n\t\t\tthis.modelSelectedIndex = currentIndex;\n\t\t}\n\n\t\tthis.tui.requestRender();\n\t}\n\n\t/** Filter models based on search query */\n\tprivate filterModels(): void {\n\t\tconst query = this.modelSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredModels = [...this.availableModels];\n\t\t} else {\n\t\t\tthis.filteredModels = this.availableModels.filter(\n\t\t\t\t(m) =>\n\t\t\t\t\tm.fullId.toLowerCase().includes(query) ||\n\t\t\t\t\tm.id.toLowerCase().includes(query) ||\n\t\t\t\t\tm.provider.toLowerCase().includes(query),\n\t\t\t);\n\t\t}\n\t\tthis.modelSelectedIndex = Math.min(this.modelSelectedIndex, Math.max(0, this.filteredModels.length - 1));\n\t}\n\n\tprivate handleModelSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = this.filteredModels[this.modelSelectedIndex];\n\t\t\tif (selected) {\n\t\t\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep!));\n\t\t\t\tconst requestedLevel = thinkingSuffix.slice(1);\n\t\t\t\tconst selectedModel = findModelInfo(selected.fullId, this.availableModels, this.preferredProvider);\n\t\t\t\tconst suffix = getSupportedThinkingLevels(selectedModel).some((level) => level === requestedLevel)\n\t\t\t\t\t? thinkingSuffix\n\t\t\t\t\t: \"\";\n\t\t\t\tthis.updateBehavior(this.editingStep!, \"model\", `${selected.fullId}${suffix}`);\n\t\t\t}\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex =\n\t\t\t\t\tthis.modelSelectedIndex === 0 ? this.filteredModels.length - 1 : this.modelSelectedIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredModels.length > 0) {\n\t\t\t\tthis.modelSelectedIndex =\n\t\t\t\t\tthis.modelSelectedIndex === this.filteredModels.length - 1 ? 0 : this.modelSelectedIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.modelSearchQuery.length > 0) {\n\t\t\t\tthis.modelSearchQuery = this.modelSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterModels();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.modelSearchQuery += data;\n\t\t\tthis.filterModels();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate getAvailableThinkingLevels(stepIndex: number): ThinkingLevel[] {\n\t\treturn getSupportedThinkingLevels(\n\t\t\tfindModelInfo(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider),\n\t\t);\n\t}\n\n\t/** Enter thinking level selector mode */\n\tprivate enterThinkingSelector(): void {\n\t\tif (!this.getEffectiveBehavior(this.selectedStep).model) {\n\t\t\tthis.showNotice(\"Select a model first\", \"error\");\n\t\t\treturn;\n\t\t}\n\t\tthis.editingStep = this.selectedStep;\n\t\tthis.editMode = \"thinking\";\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.selectedStep);\n\t\tconst { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep));\n\t\tconst suffix = thinkingSuffix.slice(1);\n\t\tconst levelIdx = levels.indexOf(suffix as ThinkingLevel);\n\t\tthis.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf(\"off\"));\n\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate handleThinkingSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) return;\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selectedLevel = levels[this.thinkingSelectedIndex] ?? \"off\";\n\t\t\tthis.applyThinkingLevel(selectedLevel);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tthis.thinkingSelectedIndex =\n\t\t\t\tthis.thinkingSelectedIndex === 0 ? levels.length - 1 : this.thinkingSelectedIndex - 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tthis.thinkingSelectedIndex =\n\t\t\t\tthis.thinkingSelectedIndex === levels.length - 1 ? 0 : this.thinkingSelectedIndex + 1;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/** Apply thinking level to the current step's model */\n\tprivate applyThinkingLevel(level: ThinkingLevel): void {\n\t\tconst stepIndex = this.editingStep!;\n\t\tconst currentModel = this.getEffectiveBehavior(stepIndex).model;\n\t\tif (!currentModel) return;\n\n\t\tconst { baseModel } = splitThinkingSuffix(currentModel);\n\t\tconst newModel = level === \"off\" ? baseModel : `${baseModel}:${level}`;\n\t\tthis.updateBehavior(stepIndex, \"model\", newModel);\n\t}\n\n\tprivate filterSkills(): void {\n\t\tconst query = this.skillSearchQuery.toLowerCase();\n\t\tif (!query) {\n\t\t\tthis.filteredSkills = [...this.availableSkills];\n\t\t} else {\n\t\t\tthis.filteredSkills = this.availableSkills.filter(\n\t\t\t\t(s) => s.name.toLowerCase().includes(query) || (s.description?.toLowerCase().includes(query) ?? false),\n\t\t\t);\n\t\t}\n\t\tthis.skillCursorIndex = Math.min(this.skillCursorIndex, Math.max(0, this.filteredSkills.length - 1));\n\t}\n\n\tprivate handleSkillSelectorInput(data: string): void {\n\t\tif (matchesKey(data, \"escape\") || matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"return\")) {\n\t\t\tconst selected = [...this.skillSelectedNames];\n\t\t\tthis.updateBehavior(this.editingStep!, \"skills\", selected);\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data === \" \") {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tconst skill = this.filteredSkills[this.skillCursorIndex];\n\t\t\t\tif (skill) {\n\t\t\t\t\tif (this.skillSelectedNames.has(skill.name)) {\n\t\t\t\t\t\tthis.skillSelectedNames.delete(skill.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.skillSelectedNames.add(skill.name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"up\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex =\n\t\t\t\t\tthis.skillCursorIndex === 0 ? this.filteredSkills.length - 1 : this.skillCursorIndex - 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"down\")) {\n\t\t\tif (this.filteredSkills.length > 0) {\n\t\t\t\tthis.skillCursorIndex =\n\t\t\t\t\tthis.skillCursorIndex === this.filteredSkills.length - 1 ? 0 : this.skillCursorIndex + 1;\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"backspace\")) {\n\t\t\tif (this.skillSearchQuery.length > 0) {\n\t\t\t\tthis.skillSearchQuery = this.skillSearchQuery.slice(0, -1);\n\t\t\t\tthis.filterSkills();\n\t\t\t}\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (data.length === 1 && data.charCodeAt(0) >= 32) {\n\t\t\tthis.skillSearchQuery += data;\n\t\t\tthis.filterSkills();\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate handleEditInput(data: string): void {\n\t\tconst textWidth = this.width - 4; // Must match render: innerW - 2 = (width - 2) - 2\n\t\tif (matchesKey(data, \"shift+up\") || matchesKey(data, \"pageUp\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.max(0, cursorPos.line - this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetStart = starts[targetLine];\n\t\t\tif (targetStart === undefined) return;\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: targetStart + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"shift+down\") || matchesKey(data, \"pageDown\")) {\n\t\t\tconst { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth);\n\t\t\tconst cursorPos = getCursorDisplayPos(this.editState.cursor, starts);\n\t\t\tconst targetLine = Math.min(wrapped.length - 1, cursorPos.line + this.EDIT_VIEWPORT_HEIGHT);\n\t\t\tconst targetStart = starts[targetLine];\n\t\t\tif (targetStart === undefined) return;\n\t\t\tconst targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0);\n\t\t\tthis.editState = { ...this.editState, cursor: targetStart + targetCol };\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"tab\")) return;\n\n\t\tconst nextState = handleEditorInput(this.editState, data, textWidth);\n\t\tif (nextState) {\n\t\t\tthis.editState = nextState;\n\t\t\tthis.tui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.saveEdit();\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.exitEditMode();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprivate saveEdit(): void {\n\t\tconst stepIndex = this.editingStep!;\n\n\t\tif (this.editMode === \"template\") {\n\t\t\t// For template, preserve other lines if they existed\n\t\t\tconst original = this.templates[stepIndex] ?? \"\";\n\t\t\tconst originalLines = original.split(\"\\n\");\n\t\t\toriginalLines[0] = this.editState.buffer;\n\t\t\tthis.templates[stepIndex] = originalLines.join(\"\\n\");\n\t\t} else if (this.editMode === \"output\") {\n\t\t\t// Capture OLD output before updating (for downstream propagation)\n\t\t\tconst oldBehavior = this.getEffectiveBehavior(stepIndex);\n\t\t\tconst oldOutput = typeof oldBehavior.output === \"string\" ? oldBehavior.output : null;\n\n\t\t\t// Empty string or whitespace means disable output\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tconst newOutput = trimmed === \"\" ? false : trimmed;\n\t\t\tthis.updateBehavior(stepIndex, \"output\", newOutput);\n\n\t\t\t// Propagate output filename change to downstream steps' reads\n\t\t\tif (oldOutput && typeof newOutput === \"string\" && oldOutput !== newOutput) {\n\t\t\t\tthis.propagateOutputChange(stepIndex, oldOutput, newOutput);\n\t\t\t}\n\t\t} else if (this.editMode === \"reads\") {\n\t\t\t// Parse comma-separated list, empty means disable reads\n\t\t\tconst trimmed = this.editState.buffer.trim();\n\t\t\tif (trimmed === \"\") {\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", false);\n\t\t\t} else {\n\t\t\t\tconst files = trimmed\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((f) => f.trim())\n\t\t\t\t\t.filter((f) => f !== \"\");\n\t\t\t\tthis.updateBehavior(stepIndex, \"reads\", files.length > 0 ? files : false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * When a step's output filename changes, update downstream steps that read from it.\n\t * This maintains the chain dependency automatically.\n\t */\n\tprivate propagateOutputChange(changedStepIndex: number, oldOutput: string, newOutput: string): void {\n\t\t// Check all downstream steps (steps that come after the changed step)\n\t\tfor (let i = changedStepIndex + 1; i < this.agentConfigs.length; i++) {\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\n\t\t\t// Skip if reads is disabled or empty\n\t\t\tif (behavior.reads === false || !behavior.reads || behavior.reads.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if this step reads the old output file\n\t\t\tconst readsArray = behavior.reads;\n\t\t\tconst oldIndex = readsArray.indexOf(oldOutput);\n\n\t\t\tif (oldIndex !== -1) {\n\t\t\t\t// Replace old filename with new filename in reads\n\t\t\t\tconst newReads = [...readsArray];\n\t\t\t\tnewReads[oldIndex] = newOutput;\n\t\t\t\tthis.updateBehavior(i, \"reads\", newReads);\n\t\t\t}\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tlet lines: string[] = [];\n\t\tif (this.editingStep !== null) {\n\t\t\tif (this.editMode === \"model\") lines = this.renderModelSelector();\n\t\t\telse if (this.editMode === \"thinking\") lines = this.renderThinkingSelector();\n\t\t\telse if (this.editMode === \"skills\") lines = this.renderSkillSelector();\n\t\t\telse lines = this.renderFullEditMode();\n\t\t} else {\n\t\t\tswitch (this.mode) {\n\t\t\t\tcase \"single\":\n\t\t\t\t\tlines = this.renderSingleMode();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"parallel\":\n\t\t\t\t\tlines = this.renderParallelMode();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"chain\":\n\t\t\t\t\tlines = this.renderChainMode();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst renderWidth = Math.max(0, Math.min(this.width, Math.floor(width)));\n\t\treturn lines.map((line) => truncateToWidth(line, renderWidth));\n\t}\n\n\t/** Render the model selector view */\n\tprivate renderModelSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\t// Header (mode-aware terminology)\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel =\n\t\t\tthis.mode === \"single\"\n\t\t\t\t? agentName\n\t\t\t\t: this.mode === \"parallel\"\n\t\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Select Model (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst searchPrefix = th.fg(\"dim\", \"Search: \");\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\"; // Reverse video space for cursor\n\t\tconst searchDisplay = this.modelSearchQuery + cursor;\n\t\tlines.push(this.row(` ${searchPrefix}${searchDisplay}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentModelBase = splitThinkingSuffix(currentModel).baseModel;\n\t\tconst currentLabel = th.fg(\"dim\", \"Current: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"warning\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tif (this.filteredModels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching models\")}`));\n\t\t} else {\n\t\t\tconst maxVisible = this.MODEL_SELECTOR_HEIGHT;\n\t\t\tlet startIdx = 0;\n\n\t\t\tif (this.filteredModels.length > maxVisible) {\n\t\t\t\tstartIdx = Math.max(0, this.modelSelectedIndex - Math.floor(maxVisible / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredModels.length - maxVisible);\n\t\t\t}\n\n\t\t\tconst endIdx = Math.min(startIdx + maxVisible, this.filteredModels.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", `  ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst model = this.filteredModels[i]!;\n\t\t\t\tconst isSelected = i === this.modelSelectedIndex;\n\t\t\t\tconst isCurrent = model.fullId === currentModelBase || model.id === currentModelBase;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \"  \";\n\t\t\t\tconst modelText = isSelected ? th.fg(\"accent\", model.id) : model.id;\n\t\t\t\tconst providerBadge = th.fg(\"dim\", ` [${model.provider}]`);\n\t\t\t\tconst currentBadge = isCurrent ? th.fg(\"success\", \" current\") : \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredModels.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", `  ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 18;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = \" [Enter] Select • [Esc] Cancel • Type to search \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\t/** Render the thinking level selector view */\n\tprivate renderThinkingSelector(): string[] {\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel =\n\t\t\tthis.mode === \"single\"\n\t\t\t\t? agentName\n\t\t\t\t: this.mode === \"parallel\"\n\t\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tconst headerText = ` Thinking Level (${stepLabel}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst currentModel = this.getEffectiveModel(this.editingStep!);\n\t\tconst currentLabel = th.fg(\"dim\", \"Model: \");\n\t\tlines.push(this.row(` ${currentLabel}${th.fg(\"accent\", currentModel)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Select thinking level (extended thinking budget):\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst levelDescriptions: Record<ThinkingLevel, string> = {\n\t\t\toff: \"No extended thinking\",\n\t\t\tminimal: \"Brief reasoning\",\n\t\t\tlow: \"Light reasoning\",\n\t\t\tmedium: \"Moderate reasoning\",\n\t\t\thigh: \"Deep reasoning\",\n\t\t\txhigh: \"Maximum reasoning (ultrathink)\",\n\t\t\tmax: \"Maximum available reasoning\",\n\t\t};\n\n\t\tconst levels = this.getAvailableThinkingLevels(this.editingStep!);\n\t\tif (levels.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No supported thinking levels\")}`));\n\t\t} else {\n\t\t\tfor (let i = 0; i < levels.length; i++) {\n\t\t\t\tconst level = levels[i]!;\n\t\t\t\tconst isSelected = i === this.thinkingSelectedIndex;\n\t\t\t\tconst prefix = isSelected ? th.fg(\"accent\", \"→ \") : \"  \";\n\t\t\t\tconst levelText = isSelected ? th.fg(\"accent\", level) : level;\n\t\t\t\tconst desc = th.fg(\"dim\", ` - ${levelDescriptions[level]}`);\n\t\t\t\tlines.push(this.row(` ${prefix}${levelText}${desc}`));\n\t\t\t}\n\t\t}\n\n\t\tconst contentLines = lines.length;\n\t\tconst targetHeight = 16;\n\t\tfor (let i = contentLines; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tconst footerText = levels.length === 0 ? \" [Esc] Cancel \" : \" [Enter] Select • [Esc] Cancel • ↑↓ Navigate \";\n\t\tlines.push(this.renderFooter(footerText));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderSkillSelector(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[this.editingStep!]?.name ?? \"unknown\";\n\t\tconst stepLabel =\n\t\t\tthis.mode === \"single\"\n\t\t\t\t? agentName\n\t\t\t\t: this.mode === \"parallel\"\n\t\t\t\t\t? `Task ${this.editingStep! + 1}: ${agentName}`\n\t\t\t\t\t: `Step ${this.editingStep! + 1}: ${agentName}`;\n\t\tlines.push(this.renderHeader(` Select Skills (${stepLabel}) `));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst cursor = \"\\x1b[7m \\x1b[27m\";\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Search: \")}${this.skillSearchQuery}${cursor}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selected = [...this.skillSelectedNames].join(\", \") || th.fg(\"dim\", \"(none)\");\n\t\tlines.push(this.row(` ${th.fg(\"dim\", \"Selected: \")}${truncateToWidth(selected, innerW - 12)}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst selectorHeight = 10;\n\t\tif (this.filteredSkills.length === 0) {\n\t\t\tlines.push(this.row(` ${th.fg(\"dim\", \"No matching skills\")}`));\n\t\t} else {\n\t\t\tlet startIdx = 0;\n\t\t\tif (this.filteredSkills.length > selectorHeight) {\n\t\t\t\tstartIdx = Math.max(0, this.skillCursorIndex - Math.floor(selectorHeight / 2));\n\t\t\t\tstartIdx = Math.min(startIdx, this.filteredSkills.length - selectorHeight);\n\t\t\t}\n\t\t\tconst endIdx = Math.min(startIdx + selectorHeight, this.filteredSkills.length);\n\n\t\t\tif (startIdx > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", `  ↑ ${startIdx} more`)}`));\n\t\t\t}\n\n\t\t\tfor (let i = startIdx; i < endIdx; i++) {\n\t\t\t\tconst skill = this.filteredSkills[i]!;\n\t\t\t\tconst isCursor = i === this.skillCursorIndex;\n\t\t\t\tconst isSelected = this.skillSelectedNames.has(skill.name);\n\n\t\t\t\tconst prefix = isCursor ? th.fg(\"accent\", \"→ \") : \"  \";\n\t\t\t\tconst checkbox = isSelected ? th.fg(\"success\", \"[x]\") : \"[ ]\";\n\t\t\t\tconst nameText = isCursor ? th.fg(\"accent\", skill.name) : skill.name;\n\t\t\t\tconst sourceBadge = th.fg(\"dim\", ` [${skill.source}]`);\n\t\t\t\tconst desc = skill.description ? th.fg(\"dim\", ` - ${truncateToWidth(skill.description, 25)}`) : \"\";\n\n\t\t\t\tlines.push(this.row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`));\n\t\t\t}\n\n\t\t\tconst remaining = this.filteredSkills.length - endIdx;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(this.row(` ${th.fg(\"dim\", `  ↓ ${remaining} more`)}`));\n\t\t\t}\n\t\t}\n\n\t\tconst targetHeight = 18;\n\t\tfor (let i = lines.length; i < targetHeight; i++) {\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tlines.push(this.renderFooter(\" [Enter] Confirm • [Space] Toggle • [Esc] Cancel \"));\n\t\treturn lines;\n\t}\n\n\tprivate getShortcutHelpText(): string {\n\t\tconst th = this.theme;\n\t\tconst shortcut = (key: string, label: string) => th.fg(\"accent\", `[${key}]`) + th.fg(\"dim\", label);\n\t\tconst backgroundState = this.runInBackground ? \"ON\" : \"OFF\";\n\t\tconst background = shortcut(\"b\", `Background:${backgroundState}`);\n\n\t\tswitch (this.mode) {\n\t\t\tcase \"single\":\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase \"parallel\":\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t\tcase \"chain\":\n\t\t\t\treturn ` ${shortcut(\"e\", \"Task\")} ${shortcut(\"m\", \"Model\")} ${shortcut(\"t\", \"Think\")} ${shortcut(\"w\", \"Output\")} ${shortcut(\"r\", \"Reads\")} ${shortcut(\"p\", \"Prog\")} ${shortcut(\"s\", \"Skills\")} ${background}`;\n\t\t}\n\t}\n\n\tprivate getFooterText(): string {\n\t\treturn this.mode === \"single\"\n\t\t\t? \" [Enter] Run • [Esc] Cancel \"\n\t\t\t: \" [Enter] Run • [Esc] Cancel • [↑↓/jk] Navigate \";\n\t}\n\n\tprivate appendNotice(lines: string[]): void {\n\t\tif (!this.noticeMessage) return;\n\t\tconst color = this.noticeMessage.type === \"error\" ? \"error\" : \"success\";\n\t\tlines.push(this.row(` ${this.theme.fg(color, this.noticeMessage.text)}`));\n\t}\n\n\tprivate renderSingleMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst agentName = this.agentConfigs[0]?.name ?? \"unknown\";\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Agent: ${truncateToWidth(agentName, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst config = this.agentConfigs[0]!;\n\t\tconst behavior = this.getEffectiveBehavior(0);\n\n\t\tconst stepLabel = config.name;\n\t\tlines.push(this.row(` ${th.fg(\"accent\", `▶ ${stepLabel}`)}`));\n\n\t\tconst template = (this.templates[0] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\tconst taskLabel = th.fg(\"dim\", \"task: \");\n\t\tlines.push(this.row(`     ${taskLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\tconst effectiveModel = this.getEffectiveModel(0);\n\t\tconst override = this.behaviorOverrides.get(0);\n\t\tconst isOverridden = override?.model !== undefined;\n\t\tconst modelValue = isOverridden ? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\") : effectiveModel;\n\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\tlines.push(this.row(`     ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\tconst writesValue =\n\t\t\tbehavior.output === false ? th.fg(\"dim\", \"(disabled)\") : behavior.output || th.fg(\"dim\", \"(none)\");\n\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\tlines.push(this.row(`     ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\tconst skillsValue =\n\t\t\tbehavior.skills === false\n\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t: behavior.skills?.length\n\t\t\t\t\t? behavior.skills.join(\", \")\n\t\t\t\t\t: th.fg(\"dim\", \"(none)\");\n\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\tlines.push(this.row(`     ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderParallelMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst headerText = ` Parallel Tasks (${this.agentConfigs.length}) `;\n\t\tlines.push(this.renderHeader(headerText));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \"  \";\n\t\t\tconst taskPrefix = `Task ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - taskPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen ? `${config.name.slice(0, maxNameLen - 1)}…` : config.name;\n\t\t\tconst taskLabel = `${taskPrefix}${agentName}`;\n\t\t\tlines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`));\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst taskTextLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(`     ${taskTextLabel}${truncateToWidth(template, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden ? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\") : effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(`     ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\t\t\tconst skillsValue =\n\t\t\t\tbehavior.skills === false\n\t\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t\t: behavior.skills?.length\n\t\t\t\t\t\t? behavior.skills.join(\", \")\n\t\t\t\t\t\t: th.fg(\"dim\", \"(none)\");\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(`     ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tprivate renderChainMode(): string[] {\n\t\tconst innerW = this.width - 2;\n\t\tconst th = this.theme;\n\t\tconst lines: string[] = [];\n\n\t\tconst chainLabel = this.agentConfigs.map((c) => c.name).join(\" → \");\n\t\tconst maxHeaderLen = innerW - 4;\n\t\tconst headerText = ` Chain: ${truncateToWidth(chainLabel, maxHeaderLen - 9)} `;\n\t\tlines.push(this.renderHeader(headerText));\n\n\t\tlines.push(this.row(\"\"));\n\n\t\tconst taskPreview = truncateToWidth(this.originalTask, innerW - 16);\n\t\tlines.push(this.row(` Original Task: ${taskPreview}`));\n\t\tconst chainDirPreview = truncateToWidth(this.chainDir ?? \"\", innerW - 12);\n\t\tlines.push(this.row(` Chain Dir: ${th.fg(\"dim\", chainDirPreview)}`));\n\n\t\tconst progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);\n\t\tconst progressValue = progressEnabled ? th.fg(\"success\", \"enabled\") : th.fg(\"dim\", \"disabled\");\n\t\tlines.push(this.row(` Progress: ${progressValue} ${th.fg(\"dim\", \"(press [p] to toggle)\")}`));\n\t\tlines.push(this.row(\"\"));\n\n\t\tfor (let i = 0; i < this.agentConfigs.length; i++) {\n\t\t\tconst config = this.agentConfigs[i]!;\n\t\t\tconst isSelected = i === this.selectedStep;\n\t\t\tconst behavior = this.getEffectiveBehavior(i);\n\n\t\t\tconst color = isSelected ? \"accent\" : \"dim\";\n\t\t\tconst prefix = isSelected ? \"▶ \" : \"  \";\n\t\t\tconst stepPrefix = `Step ${i + 1}: `;\n\t\t\tconst maxNameLen = innerW - 4 - prefix.length - stepPrefix.length;\n\t\t\tconst agentName = config.name.length > maxNameLen ? `${config.name.slice(0, maxNameLen - 1)}…` : config.name;\n\t\t\tconst stepLabel = `${stepPrefix}${agentName}`;\n\t\t\tlines.push(this.row(` ${th.fg(color, prefix + stepLabel)}`));\n\n\t\t\tconst template = (this.templates[i] ?? \"\").split(\"\\n\")[0] ?? \"\";\n\t\t\tconst highlighted = template\n\t\t\t\t.replace(/\\{task\\}/g, th.fg(\"success\", \"{task}\"))\n\t\t\t\t.replace(/\\{previous\\}/g, th.fg(\"warning\", \"{previous}\"))\n\t\t\t\t.replace(/\\{chain_dir\\}/g, th.fg(\"accent\", \"{chain_dir}\"));\n\n\t\t\tconst templateLabel = th.fg(\"dim\", \"task: \");\n\t\t\tlines.push(this.row(`     ${templateLabel}${truncateToWidth(highlighted, innerW - 12)}`));\n\n\t\t\tconst effectiveModel = this.getEffectiveModel(i);\n\t\t\tconst override = this.behaviorOverrides.get(i);\n\t\t\tconst isOverridden = override?.model !== undefined;\n\t\t\tconst modelValue = isOverridden ? th.fg(\"warning\", effectiveModel) + th.fg(\"dim\", \" ✎\") : effectiveModel;\n\t\t\tconst modelLabel = th.fg(\"dim\", \"model: \");\n\t\t\tlines.push(this.row(`     ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));\n\n\t\t\tconst writesValue =\n\t\t\t\tbehavior.output === false ? th.fg(\"dim\", \"(disabled)\") : behavior.output || th.fg(\"dim\", \"(none)\");\n\t\t\tconst writesLabel = th.fg(\"dim\", \"writes: \");\n\t\t\tlines.push(this.row(`     ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));\n\n\t\t\tconst readsValue =\n\t\t\t\tbehavior.reads === false\n\t\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t\t: behavior.reads && behavior.reads.length > 0\n\t\t\t\t\t\t? behavior.reads.join(\", \")\n\t\t\t\t\t\t: th.fg(\"dim\", \"(none)\");\n\t\t\tconst readsLabel = th.fg(\"dim\", \"reads: \");\n\t\t\tlines.push(this.row(`     ${readsLabel}${truncateToWidth(readsValue, innerW - 13)}`));\n\n\t\t\tconst skillsValue =\n\t\t\t\tbehavior.skills === false\n\t\t\t\t\t? th.fg(\"dim\", \"(disabled)\")\n\t\t\t\t\t: behavior.skills?.length\n\t\t\t\t\t\t? behavior.skills.join(\", \")\n\t\t\t\t\t\t: th.fg(\"dim\", \"(none)\");\n\t\t\tconst skillsLabel = th.fg(\"dim\", \"skills: \");\n\t\t\tlines.push(this.row(`     ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));\n\n\t\t\tif (progressEnabled) {\n\t\t\t\tconst isFirstStep = i === 0;\n\t\t\t\tconst progressAction = isFirstStep\n\t\t\t\t\t? th.fg(\"success\", \"writes progress.md\")\n\t\t\t\t\t: th.fg(\"accent\", \"reads progress.md\");\n\t\t\t\tconst progressLabel = th.fg(\"dim\", \"progress: \");\n\t\t\t\tlines.push(this.row(`     ${progressLabel}${progressAction}`));\n\t\t\t}\n\n\t\t\tif (i < this.agentConfigs.length - 1) {\n\t\t\t\tconst nextStepUsePrevious = (this.templates[i + 1] ?? \"\").includes(\"{previous}\");\n\t\t\t\tif (nextStepUsePrevious) {\n\t\t\t\t\tconst indicator = th.fg(\"dim\", \"     ↳ response → \") + th.fg(\"warning\", \"{previous}\");\n\t\t\t\t\tlines.push(this.row(indicator));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlines.push(this.row(\"\"));\n\t\t}\n\n\t\tif (this.agentConfigs.length > 0) lines.pop();\n\t\tlines.push(this.row(this.getShortcutHelpText()));\n\t\tthis.appendNotice(lines);\n\t\tlines.push(this.renderFooter(this.getFooterText()));\n\n\t\treturn lines;\n\t}\n\n\tinvalidate(): void {}\n\tdispose(): void {\n\t\tif (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer);\n\t\tthis.noticeMessageTimer = null;\n\t}\n}\n"]}