From 0426aafad2d2c45b68521833b6c999e209d47d19 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Tue, 12 May 2026 15:55:46 +0200 Subject: [PATCH] fix(headless): drop /sf prefix so typed commands route through extension dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit headless.ts was sending `/sf {subcommand} {args}` to the RPC session, but commands are registered without the sf namespace (e.g. 'todo', 'autonomous'). _tryExecuteExtensionCommand parsed commandName='sf', found no match, and the LLM handled the request instead of the typed backend. Fix: send `/{subcommand} {args}` directly — matches what registerSFCommands registers and what the TUI already uses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/headless.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/headless.ts b/src/headless.ts index 950ba795e..6319c7337 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -855,6 +855,27 @@ async function runHeadlessOnce( process.exit(exitCode); } + // import-backlog: deterministic text→DB transform, no LLM or RPC child needed. + if (options.command === "import-backlog") { + const { runImportBacklog } = await import( + "./headless-import-backlog.js" + ); + const filePath = options.commandArgs[0] ?? options.context; + if (!filePath) { + process.stderr.write( + "[headless] Error: import-backlog requires a file path as the first argument\n" + + " Usage: sf headless import-backlog \n", + ); + process.exit(1); + } + const exitCode = await runImportBacklog( + resolve(filePath), + process.cwd(), + { json: options.json }, + ); + process.exit(exitCode); + } + // Resolve CLI path for the child process const cliPath = process.env.SF_BIN_PATH || process.argv[1]; if (!cliPath) { @@ -1918,8 +1939,9 @@ async function runHeadlessOnce( ); } - // Send the command - const command = `/sf ${options.command}${options.commandArgs.length > 0 ? " " + options.commandArgs.join(" ") : ""}`; + // Send the command — use bare /{subcommand} form so _tryExecuteExtensionCommand + // can look up the registered command directly without an "sf" namespace wrapper. + const command = `/${options.command}${options.commandArgs.length > 0 ? " " + options.commandArgs.join(" ") : ""}`; try { await waitForHeadlessExtensionCommands(client); await client.prompt(command); @@ -1962,7 +1984,7 @@ async function runHeadlessOnce( try { await waitForHeadlessExtensionCommands(client); - await client.prompt("/sf autonomous"); + await client.prompt("/autonomous"); } catch (err) { process.stderr.write( `[headless] Error: Failed to start autonomous mode: ${err instanceof Error ? err.message : String(err)}\n`,