fix(desktop): /goal arg stays editable, kickoff queues when busy, header stops echoing long args - #68776
fix(desktop): /goal arg stays editable, kickoff queues when busy, header stops echoing long args#68776SHL0MS wants to merge 1 commit into
Conversation
…sh header stops echoing long args
Four symptoms from the same /goal flow on desktop:
- Typing '/goal <text>' sealed the command into a directive chip on
Space because /goal was registered without args:true, so the goal
prose rendered awkwardly after a pill. The registry row now matches
/personality and /tools: the arg stays editable text.
- The slash status header echoed the ENTIRE invocation ('slash:/goal
<whole goal prose>') in mono, immediately above the backend notice
that repeats the goal text again, and the kickoff user bubble that
repeats it a third time. The header now carries just the command
token (slash:/goal).
- When the session was busy, handleDispatch rendered 'session busy'
and dropped the dispatch message. For /goal that message is the
kickoff prompt, and the backend has ALREADY set the goal by then —
the goal existed but the agent never heard about it, and later turns
looked goal-unaware (NousResearch#63352). The busy path now queues the kickoff
on the composer queue: it sends on settle and is visible/editable in
the queue panel meanwhile. Falls back to the old message if the
queue rejects the entry.
- A slash command issued on a fresh draft created the backend session
with no preview, so the sidebar row sat as 'Untitled session' —
and when the kickoff was dropped, auto-title never fired either
(it needs a completed user->assistant exchange). ensureSessionId now
seeds the preview with the typed command.
Tests: registry row contract, busy-path queueing (kickoff neither
sends mid-turn nor vanishes), and the header-token assertion.
|
Salvaged onto current Your read on #63352 was the key one: you spotted that the '/goal then nothing reaches the backend' symptom had a mechanism nobody had isolated, and correctly called it separate from #63376's stale-prop bug rather than a duplicate. That turned out to be right, and it was the last of three independent failures in the same flow:
Without your note that (3) was distinct, it would have been closed as fixed by the other two and the kickoff would still be vanishing mid-turn. Two things about the salvage:
Your busy-queue test earned its keep, too — I sabotage-checked it by reverting the branch to the old drop-and-return behavior, and it failed alone and precisely. Closing this in favor of the salvage PR. |
What does this PR do?
Running
/goal <text>on desktop misbehaves four ways at once, and they compound into "the goal didn't stick":The typed text renders weirdly.
/goalwas registered withoutargs: true, so the trigger engine treated it as a no-arg command: pressing Space sealed it into a non-editable directive chip and the goal prose sat awkwardly after a pill./personalityand/toolsalready carryargs: true;/goalnow matches.The goal text was echoed three times. The slash status header used the full invocation (
slash:/goal <entire goal prose>in mono), directly above the backend notice that repeats the goal ("⊙ Goal set (20-turn budget): ...") and the kickoff user bubble that repeats it again. The header now carries just the command token.The kickoff was silently dropped when the session was busy — the real "goal didn't register" bug ([Bug]: Desktop submit fails after /goal; local session row created but message never reaches backend #63352). By the time
slash.execreturns itssenddispatch, the backend has already set the goal (GoalManager.setruns server-side).handleDispatchthen hitbusyRef.current, rendered "session busy — /interrupt ...", and returned — discarding the kickoff message. Result: the goal exists in the judge's state, but the agent never hears about it, so every subsequent turn looks goal-unaware. The busy path now queues the kickoff on the composer queue: it sends when the running turn settles, and it's visible and editable in the queue panel meanwhile. If the queue rejects the entry, the old message is preserved as fallback.A
/goal(or any slash) on a fresh chat left the session named "Untitled session". The slash path created the backend session with no preview seed, and auto-title needs a completed user→assistant exchange — which never happened when the kickoff was dropped.ensureSessionIdnow seeds the sidebar preview with the typed command, and fixing (3) restores the auto-title path.Related but out of scope: #54985 (gateway
/goallacksdraft/show/wait/unwaitparity — backend), #48236 (goal status bar — feature), #62202 (gateway post-turn goal continuation — backend). #63376 addresses a different submit race after/goal(staleactiveSessionIdprop inuseSubmitPrompt) and composes with this change rather than overlapping it.Related Issue
Fixes the kickoff-drop half of #63352.
Type of Change
Changes Made
apps/desktop/src/lib/desktop-slash-commands.ts:args: trueon the/goalregistry row.apps/desktop/src/app/session/hooks/use-prompt-actions/slash.ts:withSlashOutputrenders the status header with the command token (/${ctx.name}) instead of the full invocation.ensureSessionIdaccepts a preview andwithSlashOutputpasses the typed command, so slash-created sessions seed a sidebar row name.handleDispatchbusy guard enqueues the dispatch message on the composer queue (keyed viaresolveComposerSessionKey, mirroring the ChatBar queue scope) instead of dropping it; the inline status line reports the queue.desktop-slash-commands.test.ts:/goalregistry contract (exec + args).use-prompt-actions/index.test.tsx: busy-path test proving the kickoff neither submits mid-turn nor vanishes (it lands on the queue and the notice still renders), and a header-token test proving long args are no longer echoed in the header.How to Test
/goal finish the readmeand press Enter while the agent is busy./goal(with a space) in the composer: the text after the command stays editable prose instead of collapsing into a chip./goal <text>: the sidebar row is named from the command immediately, and auto-title runs after the kickoff exchange completes.cd apps/desktop && npx tsc -p . --noEmit && npx vitest run --project ui(1745 passed, 209 files), eslint clean on touched files.Checklist
Code
fix(scope):,feat(scope):, etc.)npx vitest run --project ui) and all tests pass;pytestN/A, no Python changesDocumentation & Housekeeping
docs/, docstrings) — or N/A (no user-facing docs describe the broken behaviors)cli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/A