Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/terminal-composer-instruction-threaded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkeep/open-knowledge-app": patch
---

Fix the bottom "Ask AI" composer dropping the typed instruction when launching a Terminal CLI. A composer dispatch carries its instruction (and `@`-mentions / selection) in `input.compose`, but the docked-terminal launcher only checked the top-level `input.instruction` the toolbar popover uses — so every composer-typed message fell through to the bare "load OK, then stop" prompt and the agent never saw what the user asked. The terminal launcher now routes compose-scope dispatches through the same prompt assembler as the deep-link handoff, so the instruction threads through to the launched CLI exactly as it does to a Claude/Codex/Cursor deep link.
30 changes: 30 additions & 0 deletions packages/app/src/components/handoff/useHandoffDispatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,36 @@ describe('composeTerminalLaunchPrompt — docked-terminal bare launch is load +
expect(claudeOut).not.toContain(OK_TERMINAL_SURFACE_PREAMBLE);
});

test('composer (compose scope) threads the typed instruction — NOT a bare launch', async () => {
const { composeTerminalLaunchPrompt, selectScopedPrompt } = await import(
'./useHandoffDispatch'
);
const input = {
docContext: null,
compose: {
scope: 'doc' as const,
docRelativePath: 'notes/work-log.md',
instruction: 'What does this file do?',
mentions: [],
},
projectDir: '/proj',
docPath: '/proj/notes/work-log.md',
};
expect(composeTerminalLaunchPrompt(input, 'claude')).toBe(
selectScopedPrompt(input, 'claude-code', false),
);
expect(composeTerminalLaunchPrompt(input, 'codex')).toBe(
selectScopedPrompt(input, 'codex', false),
);
expect(composeTerminalLaunchPrompt(input, 'cursor')).toBe(
selectScopedPrompt(input, 'cursor', false),
);
const claudeOut = composeTerminalLaunchPrompt(input, 'claude');
expect(claudeOut).toContain('What does this file do?');
expect(claudeOut).not.toBe(composeTerminalBareLaunchPrompt('notes/work-log.md'));
expect(claudeOut).not.toContain(OK_TERMINAL_SURFACE_PREAMBLE);
});

test('whitespace-only instruction is treated as a bare launch', async () => {
const { composeTerminalLaunchPrompt } = await import('./useHandoffDispatch');
const out = composeTerminalLaunchPrompt(
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/components/handoff/useHandoffDispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export function selectScopedPrompt(

export function composeTerminalLaunchPrompt(input: HandoffDispatchInput, cli: TerminalCli): string {
const hasInstruction = typeof input.instruction === 'string' && input.instruction.trim() !== '';
if (input.createDescription !== undefined || hasInstruction) {
if (input.compose !== undefined || input.createDescription !== undefined || hasInstruction) {
return selectScopedPrompt(input, TERMINAL_CLIS[cli].handoffTarget, false);
}
return composeTerminalBareLaunchPrompt(input.docContext?.relativePath ?? null);
Expand Down