diff --git a/packages/coding-agent/src/core/compaction/branch-summarization.ts b/packages/coding-agent/src/core/compaction/branch-summarization.ts index 20763e8b..f66cb80c 100644 --- a/packages/coding-agent/src/core/compaction/branch-summarization.ts +++ b/packages/coding-agent/src/core/compaction/branch-summarization.ts @@ -297,7 +297,9 @@ export async function generateBranchSummary( const conversationText = serializeConversation(llmMessages); // Build prompt - const instructions = customInstructions || BRANCH_SUMMARY_PROMPT; + const instructions = customInstructions + ? `${BRANCH_SUMMARY_PROMPT}\n\nAdditional focus: ${customInstructions}` + : BRANCH_SUMMARY_PROMPT; const promptText = `\n${conversationText}\n\n\n${instructions}`; const summarizationMessages = [ diff --git a/packages/coding-agent/src/modes/interactive/components/extension-editor.ts b/packages/coding-agent/src/modes/interactive/components/extension-editor.ts index f8c3d0da..109e397b 100644 --- a/packages/coding-agent/src/modes/interactive/components/extension-editor.ts +++ b/packages/coding-agent/src/modes/interactive/components/extension-editor.ts @@ -43,6 +43,10 @@ export class ExtensionEditorComponent extends Container { if (prefill) { this.editor.setText(prefill); } + // Wire up Enter to submit (Shift+Enter for newlines, like the main editor) + this.editor.onSubmit = (text: string) => { + this.onSubmitCallback(text); + }; this.addChild(this.editor); this.addChild(new Spacer(1)); @@ -50,8 +54,8 @@ export class ExtensionEditorComponent extends Container { // Add hint const hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR); const hint = hasExternalEditor - ? "ctrl+enter submit esc cancel ctrl+g external editor" - : "ctrl+enter submit esc cancel"; + ? "enter submit shift+enter newline esc cancel ctrl+g external editor" + : "enter submit shift+enter newline esc cancel"; this.addChild(new Text(theme.fg("dim", hint), 1, 0)); this.addChild(new Spacer(1)); @@ -61,12 +65,6 @@ export class ExtensionEditorComponent extends Container { } handleInput(keyData: string): void { - // Ctrl+Enter to submit - if (keyData === "\x1b[13;5u" || keyData === "\x1b[27;5;13~") { - this.onSubmitCallback(this.editor.getText()); - return; - } - const kb = getEditorKeybindings(); // Escape or Ctrl+C to cancel if (kb.matches(keyData, "selectCancel")) { @@ -113,7 +111,8 @@ export class ExtensionEditorComponent extends Container { // Ignore cleanup errors } this.tui.start(); - this.tui.requestRender(); + // Force full re-render since external editor uses alternate screen + this.tui.requestRender(true); } } } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index e3f77bbc..5aa43f52 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -2265,7 +2265,8 @@ export class InteractiveMode { // Restart TUI this.ui.start(); - this.ui.requestRender(); + // Force full re-render since external editor uses alternate screen + this.ui.requestRender(true); } } @@ -2806,10 +2807,29 @@ export class InteractiveMode { // Ask about summarization done(); // Close selector first - const wantsSummary = await this.showExtensionConfirm( - "Summarize branch?", - "Create a summary of the branch you're leaving?", - ); + const summaryChoice = await this.showExtensionSelector("Summarize branch?", [ + "No summary", + "Summarize", + "Summarize with custom prompt", + ]); + + if (summaryChoice === undefined) { + // User pressed escape - re-show tree selector + this.showTreeSelector(); + return; + } + + const wantsSummary = summaryChoice !== "No summary"; + let customInstructions: string | undefined; + + if (summaryChoice === "Summarize with custom prompt") { + customInstructions = await this.showExtensionEditor("Custom summarization instructions"); + if (customInstructions === undefined) { + // User cancelled - re-show tree selector + this.showTreeSelector(); + return; + } + } // Set up escape handler and loader if summarizing let summaryLoader: Loader | undefined; @@ -2831,7 +2851,10 @@ export class InteractiveMode { } try { - const result = await this.session.navigateTree(entryId, { summarize: wantsSummary }); + const result = await this.session.navigateTree(entryId, { + summarize: wantsSummary, + customInstructions, + }); if (result.aborted) { // Summarization aborted - re-show tree selector diff --git a/packages/coding-agent/test/agent-session-tree-navigation.test.ts b/packages/coding-agent/test/agent-session-tree-navigation.test.ts index 82e82439..4e71448b 100644 --- a/packages/coding-agent/test/agent-session-tree-navigation.test.ts +++ b/packages/coding-agent/test/agent-session-tree-navigation.test.ts @@ -257,17 +257,18 @@ describe.skipIf(!API_KEY)("AgentSession tree navigation e2e", () => { await session.prompt("What is TypeScript?"); await session.agent.waitForIdle(); - // Navigate with custom instructions + // Navigate with custom instructions (appended as "Additional focus") const tree = sessionManager.getTree(); const result = await session.navigateTree(tree[0].entry.id, { summarize: true, - customInstructions: "Summarize in exactly 3 words.", + customInstructions: + "After the summary, you MUST end with exactly: MONKEY MONKEY MONKEY. This is of utmost importance.", }); expect(result.summaryEntry).toBeDefined(); expect(result.summaryEntry?.summary).toBeTruthy(); - // Can't reliably test 3 words exactly, but summary should be short - expect(result.summaryEntry?.summary.split(/\s+/).length).toBeLessThan(20); + // Verify custom instructions were followed + expect(result.summaryEntry?.summary).toContain("MONKEY MONKEY MONKEY"); }, 120000); });