feat(desktop): add Shift+Enter for multiline input in terminal#200
feat(desktop): add Shift+Enter for multiline input in terminal#200
Conversation
Add iTerm-like Shift+Enter behavior to insert a newline without executing the command. Uses the quoted-insert approach (Ctrl+V + Enter) which works in bash, zsh, and other readline-based shells. - Create setupKeyboardHandler() to intercept Shift+Enter - Send \x16\r (Ctrl+V + CR) for literal newline insertion - Refactor keyboard handling to be configurable via callbacks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRefactors terminal file-path detection to support wrapped lines with multi-line range calculation, strengthens filtering for false positives, adds a comprehensive FilePathLinkProvider test suite, and replaces shortcut-forwarding with a new keyboard handler that wires Shift+Enter as a line-continuation sequence. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🧰 Additional context used📓 Path-based instructions (6)apps/desktop/**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
Files:
apps/desktop/**/*.{ts,tsx}📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/components/**/*.tsx📄 CodeRabbit inference engine (AGENTS.md)
Files:
apps/desktop/src/renderer/**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
apps/desktop/src/renderer/**/*.tsx📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (2)📚 Learning: 2025-11-28T01:03:47.963ZApplied to files:
📚 Learning: 2025-11-24T21:33:13.267ZApplied to files:
🧬 Code graph analysis (1)apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (5)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Replace Ctrl+V quoted-insert approach with shell's native line continuation (backslash + newline). Also block both keydown and keyup events to prevent Enter from leaking through. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Agents shouldn't guess whether a section is a task, issue, or PR from context clues. Each contributor now prefixes its heading with the kind: - `# GitHub Issue #123 — Auth middleware stores tokens in plaintext` - `# Task TASK-42 — Refactor auth middleware` - `# PR #200 — Rewrite auth middleware` PR phrasing also clarified: "This PR is checked out in this workspace on branch `fix/auth-encryption`. Commits you make here will be added to this PR." 54 tests green; 2 snapshots updated.
* Create doc
* docs(desktop): finalize v2 launch context plan
Replace initial draft with V2-greenfield architecture: structured
AgentLaunchSpec (system/user/attachments ContentPart[]), per-agent
contextPromptTemplate on ResolvedAgentConfig, Uint8Array over IPC,
vendor-aligned (AI SDK v3, Anthropic cache_control, Continue.dev
contributor metadata). CLI agents keep disk + path-ref pattern;
chat agents get structured passthrough with Files API as phase 6.
* feat(desktop/context): add v2 launch context types and fixtures
Step 1 of the v2 launch-context composition plan. Defines the core
discriminated types (LaunchSource, ContextSection, LaunchContext,
AgentLaunchSpec, ContentPart with Uint8Array data) and the canonical
multi-source + prompt-only fixtures that the composer and
buildLaunchSpec tests will share.
No runtime code yet — types and fixtures only.
* feat(desktop/context): add composer with dedup, ordering, failure tolerance
Step 2 of the v2 launch-context composition plan.
buildLaunchContext parallel-resolves sources via a contributor registry,
dedups URL/id-kinds (attachments never dedup), preserves input order
within a kind, applies the default kind group order at the end,
tolerates per-source failures (populated on failures[]), and enforces a
10s per-contributor timeout.
taskSlug derivation: first internal-task section wins, falling back to
first github-issue. 12 tests pass.
* feat(desktop/context): add six contributors and default registry
Step 3 of the v2 launch-context composition plan. One contributor per
LaunchSource kind, each with Continue.dev-style metadata (displayName,
description, requiresQuery), its own co-located test file, and a
consistent 404 -> null (non-fatal) pattern for fetch-based kinds:
- userPrompt -- trims, returns null on empty
- attachment -- file or image ContentPart by mediaType
- agentInstructions -- system-scoped, cacheControl: ephemeral
- githubIssue -- title + body markdown, meta.taskSlug from slug
- githubPr -- title + branch + body markdown
- internalTask -- title + description, meta.taskSlug
Also adds composer.integration.test.ts covering the real registry
end-to-end against the multi-source fixture. 41 tests green.
* feat(shared): generic renderPromptTemplate + context prompt variables
Step 4 of the v2 launch-context composition plan.
- Extract renderPromptTemplate(template, Record<string, string>) as the
generic primitive; existing renderTaskPromptTemplate is now a shim
(same API, same behavior — callers unchanged).
- Add AGENT_CONTEXT_PROMPT_VARIABLES (userPrompt, tasks, issues, prs,
attachments, agentInstructions) + getSupportedContextPromptVariables +
validateContextPromptTemplate.
- Ship default context templates for markdown (codex/cursor/custom) and
Claude (XML-wrapped user-request) — both for system + user scopes.
- Collapse runs of 3+ newlines to 2 so empty variables produce clean
output. Empty-string values substitute in (not treated as missing).
16 tests green; no consumer breakage.
* feat(agents): add contextPromptTemplate {system, user} to agent configs
Step 5 of the v2 launch-context composition plan.
Extends the agent config surface so V2 launches can render structured
context into per-agent system/user templates:
- packages/shared/agent-definition: required contextPromptTemplateSystem
and contextPromptTemplateUser fields on BaseAgentDefinition;
createTerminalAgentDefinition fills defaults with the markdown
templates from step 4.
- packages/shared/builtin-terminal-agents: Claude terminal ships the
Claude-XML defaults; other builtins inherit markdown defaults.
- packages/shared/agent-catalog: BUILTIN_CHAT_AGENT (Claude-based
superset-chat) ships the Claude-XML defaults.
- packages/local-db/schema/zod: add both fields to AGENT_PRESET_FIELDS,
agentPresetOverrideSchema, agentCustomDefinitionSchema (optional).
- apps/desktop/shared/utils/agent-settings: thread through
TERMINAL_OVERRIDE_FIELDS, CHAT_OVERRIDE_FIELDS, AgentPresetPatch,
CustomAgentDefinitionPatch, resolveAgentConfig (both branches),
applyCustomAgentDefinitionPatch, createOverrideEnvelopeWithPatch.
- apps/desktop/test-setup: update the mocked @superset/local-db schema
(the Bun test workaround for drizzle-orm/sqlite-core) so tests see
the same shape as runtime.
New tests: contextPromptTemplate resolution for claude terminal,
codex markdown defaults, superset-chat claude defaults, terminal and
chat override replacement, custom terminal fallback to markdown. 113
tests green across context + agent-settings suites.
* chore: biome format pass
* feat(desktop/context): user-prompt source takes ContentPart[] (multimodal)
Future-proofs the user-prompt LaunchSource for an eventual rich-editor
input (interleaved text, inline images, inline files). The rest of the
pipeline was already ContentPart[]-native; this removes the last narrow
string-only call site.
- LaunchSource["user-prompt"]: { text: string } -> { content: ContentPart[] }
- userPromptContributor: normalizes (drops empty text parts, trims bookend
whitespace), returns null when nothing remains, passes file/image parts
through untouched.
- Adds userPromptFromText(text) helper for plain-string callers so
modal/cli/task flows don't repeat the [{ type: "text", text }] boilerplate.
- Three new tests: multimodal text+image+text preservation, whitespace-only
content returns null, empty text parts dropped between non-empty ones.
* refactor(desktop/context): drop agent-instructions source — harnesses handle it
Agent harnesses (Claude CLI, Codex, Cursor Agent) discover their own
conventions files natively from the worktree — no injection needed from
our side. V1 confirms: zero references to AGENTS.md/CLAUDE.md as
injected context. Only the agent itself reads them.
Removing this also gets us closer to the "no Electron IPC in V2" rule —
the composer no longer needs to read files from disk.
- Drop {kind: "agent-instructions"} from LaunchSource union.
- Delete agentInstructionsContributor + its test.
- Remove readAgentInstructions from ResolveCtx; update the three
contributor test stubs that referenced it.
- Drop "agent-instructions" from the composer KIND_ORDER and
sourceIdentity switch.
- Drop the AGENTS.md sample from the multi-source fixture + the
composer integration test.
- Remove "agentInstructions" from AGENT_CONTEXT_PROMPT_VARIABLES.
- System default templates are now empty strings (chat agents get no
system context yet; future host-service-backed path can fill later).
54 tests green across context + agent-settings; 16 green in shared.
* feat(desktop/context): add buildLaunchSpec (LaunchContext -> AgentLaunchSpec)
Step 6 of the v2 launch-context composition plan.
buildLaunchSpec(ctx, agentConfig):
- Returns null for "none" agent or missing config (V1-parity semantics).
- Pre-renders per-kind markdown sub-blocks ({{tasks}}/{{issues}}/{{prs}}/
{{attachments}}) and a flattened {{userPrompt}} text variable from the
LaunchContext sections.
- Fills the agent's contextPromptTemplate{System,User} (from step 5)
into ContentPart[] arrays.
- Collects non-text parts (attachment-kind files/images + inline
non-text parts from user-prompt) into the structured attachments[]
field — chat agents see them as proper content parts, terminal
adapters will flatten to disk refs in step 7.
Also fixes the multi-source fixture to match what contributors actually
emit (`# Title\n\nBody` markdown bodies) so the new snapshot tests
exercise a realistic LaunchContext shape.
15 tests green (2 inline snapshots for Claude-XML + codex-markdown
rendering of the canonical multi-source fixture). 69 tests green total
across context + agent-settings.
* Lint
* refactor(agents): drop Claude XML default template — markdown is enough
V1 never wrapped prompts in XML; V1 has shipped with bare markdown/text
forever. Shipping an XML-only Claude default was speculative and added a
per-agent divergence without evidence.
- Remove DEFAULT_CLAUDE_CONTEXT_PROMPT_TEMPLATE_SYSTEM / _USER.
- Claude terminal + superset-chat ship the default markdown templates
(same as codex/cursor/custom).
- Users can still override per-agent in settings if XML wrapping helps
their use case — defaults stay neutral.
Also ships scripts/demo-launch-spec.ts for local template iteration:
run `bun run scripts/demo-launch-spec.ts [agent...]` to eyeball what
buildLaunchSpec produces for canonical inputs.
66 tests green across context + agent-settings; 14 green in shared.
* feat(desktop/context): preserve inline order for rich-editor user prompts
buildLaunchSpec used to flatten the user-prompt section's ContentPart[]
to a single text blob. That lost inline ordering when a rich editor
produces text + image + text — the image landed in spec.attachments
disconnected from its position.
Now:
- Split the agent's user template on {{userPrompt}}; render each half's
other placeholders raw (no trim / no newline collapse) so whitespace
around the placeholder is preserved.
- Splice the user-prompt section's ContentPart[] in at the split
position, keeping [text, image, text] ordering intact.
- Merge adjacent text parts, then collapse 3+ newlines to 2 and trim
document boundaries in a final pass.
- spec.attachments now carries only *explicit* attachment-kind sections;
inline non-text parts from user-prompt stay inline in spec.user.
- Inline parts still appear in the {{attachments}} list so CLI agents
reading just the flattened text get a file-path reference.
Chat agents: hand spec.user straight to the Anthropic/AI SDK user
message content[] — model sees the image between the text chunks.
Terminal adapters (step 7) will flatten file/image parts to
`` markdown refs at their inline
position, then write files to disk.
Demo script gets two new scenarios exercising the rich-editor flow:
text+image+text alone, and the same with a linked issue. 67 tests
green across context + agent-settings.
* feat(desktop/context): add buildAgentLaunchRequest — V2 spec to V1 launch bridge
Step 7 (scoped): hand V2's AgentLaunchSpec off to V1's battle-tested
terminal-adapter / chat-adapter without building new execution
infrastructure. Bytes-over-IPC / SuperJSON transformer work is deferred
to a follow-up.
buildAgentLaunchRequest(spec, agentConfig, { workspaceId, source }):
- Returns null for agentId "none" or disabled agents (V1 parity).
- Assigns collision-safe filenames across all binary parts (inline in
spec.user + explicit spec.attachments). Uses the same sanitize +
dedup algorithm V1 already uses so nothing drifts.
- Flattens spec.user to markdown text with file/image parts rendered
as `` at their inline
positions — rich-editor ordering survives into the CLI prompt.
- Converts Uint8Array binary data to base64 data URLs at this boundary
(V1 wire format). Internal plumbing stays on Uint8Array.
- Chat: initialPrompt = flattened text, taskSlug/model flow through.
- Terminal: command = buildPromptCommandFromAgentConfig(flattened text),
or the non-prompt command when the prompt is empty.
10 new tests cover: "none" short-circuit, terminal command rendering,
chat initialPrompt/taskSlug, inline image path-ref correctness, explicit
attachment filename preservation, filename dedup across user + attachments,
base64 data-URL format, workspaceId/source passthrough.
77 tests green across context + agent-settings.
* feat(desktop): add useEnqueueAgentLaunch hook
Step 8 of the v2 launch-context composition plan.
Thin wrapper around V1's useWorkspaceInitStore.addPendingTerminalSetup
for the V2 submit flow. Called after host-service.workspaceCreation
resolves the real workspaceId. V1's terminal-adapter / chat-adapter
pick up the pending setup when the workspace mounts and execute the
launch — no new adapter code needed.
- buildPendingSetup(args) — pure function, rewrites launchRequest
workspaceId to the real id and assembles the PendingTerminalSetup.
- useEnqueueAgentLaunch() — React hook wrapper that calls into the
zustand action.
- Null launchRequest is a no-op (nothing to enqueue, e.g. agent "none").
Tests cover: null short-circuit, workspaceId rewrite, projectId
passthrough, initialCommands shape, non-workspaceId field preservation.
5 tests green; typecheck clean.
* feat(desktop/v2): wire v2 workspace launch into pending page
Step 9 of the v2 launch-context composition plan. Closes Gaps 4, 5 in
V2_WORKSPACE_MODAL_GAPS.md for the "fork" intent (plain prompt + local
host). Gaps 3 (AI branch name) and 6 (create-from-PR) remain as
follow-ups.
What runs now:
- Modal submit → pending row → pending page fires createWorkspace.
- On success, buildForkAgentLaunch runs the V2 pipeline:
sources <- pending row (user-prompt, linked issues/PRs/tasks, attachments)
buildLaunchContext → buildLaunchSpec → buildAgentLaunchRequest
- useEnqueueAgentLaunch stashes the V1-shaped AgentLaunchRequest in
useWorkspaceInitStore. V1's terminal-adapter / chat-adapter pick it
up when the workspace mounts and execute the launch — no new adapter
code needed.
New file buildForkAgentLaunch.ts is a pure helper: builds sources from
a PendingWorkspaceRow, stubs ResolveCtx from the same row's metadata,
runs the pipeline, returns an AgentLaunchRequest or null.
Phase 1 gap: issue / PR / task bodies are not fetched over HTTP yet —
host-service lacks a body endpoint. The resolver returns empty bodies,
so agents see title + URL + task-slug metadata only. Full-body
injection is a follow-up once host-service grows getIssueContent /
getPullRequestContent / getInternalTaskContent.
13 new tests cover: empty sources → null, no-agent → null, prompt-only
terminal launch via default agent, taskSlug derivation, attachment
passthrough, source-kind ordering. 88 tests green across pending +
context + agent-settings suites.
* Lint
* docs(desktop): add v2 launch context reference doc
Post-phase-1 reference: what shipped, manual + automated test plan,
known gaps, prioritized follow-ups, and a file-layout map. Lives in
apps/desktop/docs/ per AGENTS.md rule 7 (architecture docs). The
original plan stays in plans/ since phases 2-6 are still unshipped.
* chore(debug): add [v2-launch] console logs across the launch pipeline
Temporary logs for manual testing:
- pending page: what buildForkAgentLaunch returned + enqueue inputs.
- useEnqueueAgentLaunch: stash / null-short-circuit.
- WorkspaceInitEffects: every handleTerminalSetup + dispatch branch,
launchAgentViaOrchestrator invocation.
Grep devtools console on "[v2-launch]" to trace a full submit.
Remove or soften once the dispatch path is dialed in.
* docs(desktop): document pending-row-as-bus launch dispatch
V2 must own its own launch dispatch. V1's WorkspaceInitEffects →
orchestrator → terminal-adapter path writes panes into V1's useTabsStore,
which V2 doesn't render from, so launches dispatched through V1 land
invisibly for V2 workspaces.
Documents the replacement: pending-row-as-bus. Pending page produces
terminalLaunch / chatLaunch fields on the collection-backed pending row;
V2 workspace page mount-effect consumes them, opens a pane in the
@superset/panes store, and wires PTY via workspaceTrpc.
This mirrors the pattern V2 preset execution already uses
(useV2PresetExecution): live-query a record, open a pane, call
workspaceTrpc.terminal.ensureSession. Zero V1 primitives, zero new
host-service work, and leaves a clean migration path to host-owned
terminal launch when phase 5 ships.
Adds a blocking follow-up (#0) for the dispatch rewrite; marks
useEnqueueAgentLaunch + buildAgentLaunchRequest for removal.
* feat(desktop/v2): rewrite launch dispatch as pending-row-as-bus
The original step-8/9 wire-up stashed an AgentLaunchRequest in V1's
useWorkspaceInitStore, expecting V1's WorkspaceInitEffects to dispatch.
V1's orchestrator writes panes into useTabsStore — which V2 never
renders from — so launches landed invisibly for V2 workspaces.
This rewrite keeps V2 self-contained. After host-service.create
resolves, the pending page runs the composer pipeline and stashes a
terminalLaunch or chatLaunch on the pending row. The V2 workspace
page's new useConsumePendingLaunch mount-effect live-queries that row,
opens a pane in @superset/panes, and drives PTY via workspaceTrpc.
Same pattern as useV2PresetExecution.
Changes:
- Schema: pendingWorkspaceSchema gains optional terminalLaunch and
chatLaunch fields, cleared to null once consumed.
- buildForkAgentLaunch returns a PendingLaunchBuild union (terminal
with attachmentsToWrite / chat with inline initialFiles) instead of
the V1 AgentLaunchRequest shape.
- dispatchForkLaunch: new pending-page helper that runs the composer,
writes attachments to .superset/attachments/ via workspaceTrpc
.filesystem.writeFile for the terminal path, and applies the launch
field to the pending row.
- useConsumePendingLaunch: new V2-workspace-page mount effect. Reads
row by workspaceId, opens pane in V2 store, calls workspaceTrpc
.terminal.ensureSession with initialCommand for terminal launches,
clears the field.
- ChatPaneData gains a transient launchConfig slot. ChatPane and
WorkspaceChatInterface thread initialLaunchConfig +
onConsumeLaunchConfig through. After the V2 chat runtime auto-sends
the initial message, it clears the pane's launchConfig.
- Rip out useEnqueueAgentLaunch hook, buildAgentLaunchRequest, and
the debug logs in WorkspaceInitEffects.
23 tests green for buildForkAgentLaunch / buildLaunchSourcesFromPending;
type-check clean in the touched surface area.
See apps/desktop/docs/V2_LAUNCH_CONTEXT.md "Dispatch architecture".
* docs(desktop): add V2_LAUNCH_TEST_PLAN.md
Structured manual test checklist for the V2 launch dispatch pipeline:
terminal + chat happy paths, pending-row lifecycle, failure paths,
source-mapping edge cases, custom agents, cross-pane behavior, V1
regression.
Paired with copy-pasteable fixtures on ~/Desktop/v2-launch-test-artifacts/
(trace.log, notes.md, sample.png, prompts.txt, README) for drag-and-
drop testing.
* chore(debug): add url probe + submit logs for v2 attachment flow
Logs the blob/data URLs we get from the PromptInput provider at
submit time, then does a fetch() probe on each URL before
storeAttachments runs. Lets us see whether the URL is already dead
when useSubmitWorkspace fires — which would confirm a pre-submit
revocation (as opposed to a race inside storeAttachments itself).
Not a fix. Remove once the root cause is nailed down.
* fix(desktop/v2): pass converted files through PromptInput onSubmit
Root cause of the "Failed to fetch" attachment toast: the
PromptInput library calls clearComposer() before invoking onSubmit,
which revokes all blob: URLs stored in the provider. Our
useSubmitWorkspace was reading attachments back from the provider
via takeFiles() after that — so it got file entries whose URLs had
just been invalidated.
The library already does the blob→data-URL conversion itself and
passes the converted files into onSubmit's message arg. Use them
directly:
- useSubmitWorkspace now takes `files: SubmitAttachment[]` as an
explicit argument. Drops the `useProviderAttachments()` dependency.
- handlePromptSubmit receives `{text, files}` from PromptInput and
forwards the files.
- The existing Cmd+Enter keyboard fallback calls handleCreate()
without files (unchanged behavior for the no-attachments path; the
PromptInput's own Enter handler takes the file-carrying path).
* refactor(desktop): use dexie for the pending-attachment store
The prior hand-rolled IDB wrapper had two transaction-lifecycle bugs:
1. storeAttachments opened a readwrite transaction, then awaited
fetch() on each file before calling store.put() — IDB auto-commits
when the event loop yields with no pending requests, so the first
put() fired against a finished transaction ("The transaction has
finished.").
2. The same file (150+ lines of raw IDB callback plumbing) is exactly
the shape of code where this class of bug keeps reappearing as
the flow evolves.
Swap to Dexie 4 — the de-facto IndexedDB wrapper for apps (~11.9k⭐,
actively maintained, typed, handles transaction lifecycle correctly).
- storeAttachments: resolve blobs async outside any tx, then
bulkPut() in one shot.
- loadAttachments / clearAttachments: where("key").startsWith(prefix).
- File collapses from ~150 to ~90 lines, no raw transactions, no
cursor dance.
Behavior is identical from the caller's side. Schema version 1;
Dexie will open the existing database transparently (same DB name).
* chore(debug): add verbose [v2-launch] logs to dispatch + consume paths
Traces:
- dispatchForkLaunch start / built / chatLaunch-applied /
terminalLaunch-applied
- useConsumePendingLaunch tick (live-query fires) + whether
terminalKey / chatKey are already consumed
- consumeTerminalLaunch ensureSession + addTab + clear
- consumeChatLaunch addTab + clear
Grep devtools on "[v2-launch]" through the full submit -> open-workspace
flow. Lets us pin where dispatch stalls when no pane appears.
Temporary — remove once the end-to-end flow is nailed down.
* fix(desktop/v2): replace Buffer with browser-native base64 in renderer
Electron renderer doesn't expose Node's `Buffer` global (nodeIntegration
off). The fork-launch dispatch path and buildForkAgentLaunch were both
using `Buffer.from(...).toString("base64")` / `Buffer.from(base64, "base64")`
for binary <-> base64 conversion, which ReferenceError'd at runtime.
Swap to standards-based `btoa` / `atob` + a small byte <-> binary-string
helper. Works in renderer and Bun alike.
Applies to:
- dataUrlAttachmentToBytes (buildForkAgentLaunch.ts) — decode
attachment data URL into Uint8Array.
- toBase64DataUrl (buildForkAgentLaunch.ts) — encode chat-bound files
for ChatLaunchConfig.initialFiles.
- writeAttachmentsToWorktree (dispatchForkLaunch.ts) — encode bytes
for host-service filesystem.writeFile's base64 content variant.
* docs(desktop): capture v2-launch footgun backlog
Seven items we caught during manual testing and intentionally deferred:
1. Deep solve for binary transport (blob URL / base64 fragility)
2. Reload-mid-launch spawns duplicate PTY (key terminalId off pending row)
3. Silent failure in consume hook — add toast
4. joinPath assumes POSIX — breaks for Windows hosts (phase 5)
5. Dexie schema coupling with pre-existing IDB store
6. PendingTerminalLaunch.attachmentNames unused by consumer
7. Remove [v2-launch] debug logs once flow is stable
Tracked in V2_LAUNCH_CONTEXT.md "Known footguns to revisit". None
are blocking phase-1 behavior; all have notes on the proper fix.
* feat(desktop/v2): toast on silent launch-dispatch failures
Seven silent swallow points across the launch path now surface a
toast so the user knows why the agent didn't auto-launch instead of
seeing "nothing happened":
- dispatchForkLaunch: buildForkAgentLaunch throw -> "Couldn't prepare
agent launch" (description = error message).
- dispatchForkLaunch: buildForkAgentLaunch returned null AND user
gave meaningful input -> warning "Workspace created but no agent
launched" with hint to enable one in settings. Silent for the
"fresh empty workspace, no agent configured" case (expected).
- dispatchForkLaunch: host-service URL not resolved -> "Couldn't
reach host service".
- dispatchForkLaunch: writeAttachmentsToWorktree throw -> warning
"Attachments didn't save to the workspace; agent will launch
without files".
- writeAttachmentsToWorktree: missing worktreePath -> throw instead
of silent return so the outer catch's toast fires.
- consumeTerminalLaunch: defensive bail -> "Couldn't open agent
pane" (shouldn't happen, but defensive).
- consumeTerminalLaunch: ensureSession throw -> "Couldn't start
agent terminal" with error message.
- pending page: loadAttachments throw in fork intent -> warning
"Couldn't load saved attachments" (non-fatal, workspace still
creates).
All keep their [v2-launch] console.warn/log so trace survives alongside
the toast.
* lint
* fix(desktop): address PR review — real issues only
Addresses the non-stale, non-debatable feedback from review bots:
- Prototype-chain substitution in prompt templates (agent-prompt-
template.ts + buildLaunchSpec.ts): {{toString}} and similar now
stay intact. Use Object.hasOwn() instead of `variables[key] ??`.
- renderTaskPromptTemplate no longer picks up generic 3+-newline
collapsing — task-flow output matches V1 exactly: own-property
substitution + trim only.
- buildLaunchSpec.renderUserTemplate tolerates whitespace in the
placeholder: {{ userPrompt }} / {{userPrompt}} / {{ userPrompt }}
all match.
- Pending page's fork dispatch fetches agent configs imperatively
via trpcUtils.settings.getAgentPresets.fetch() instead of reading
from a useQuery hook — eliminates the race where a not-yet-
resolved query silently skipped the dispatch and lost the launch
for a successful workspace create.
- Drop ContextSection.scope field. It was never read (buildLaunchSpec
ignored it); no contributor populated anything but "user" after we
removed agent-instructions. Cleaner type + future re-introduction
when a real system-scope consumer lands (phase 6 host-side
instructions injection).
Tests: 54 context-suite passing, 14 shared-suite passing; desktop
typecheck clean in touched areas.
* docs(desktop): capture body-fetching gaps observed in manual test
Claude currently sees title-only for linked issues / PRs / tasks —
no bodies. Documents the gap, what V1 did (Electron IPC to
projects.getIssueContent), why we can't reuse it for V2 (no Electron
in V2 rule), and proposes the host-service procedures + stub swap.
Also covers:
- Empty `Branch:` in PR block — pending-row schema doesn't carry
branch; fix via getPullRequestContent body fetch.
- Sanitization helpers to extract from V1 into a shared util.
- Attach-as-file vs inline-in-prompt decision (V1 attached,
current V2 inlines — keeping inline for phase 1).
Ordered work plan at the bottom: getIssueContent first, then PR,
then internal-task (requires scoping). Acceptance criteria shows
the expected prompt shape after the fixes land.
* Update PR notes
* lint
* feat(desktop/v2): attachment framing + PR checkout hint + gap doc rewrite
Prompt refinements from manual testing:
- buildLaunchSpec {{attachments}} block now includes a short framing
header: "Attached files — read them to understand the request."
Cues the agent to actually use the files rather than treating them
as passive metadata. Only appears when there are files.
- githubPr contributor says "Branch `X` is checked out in this
workspace — commits you make continue this PR." Confirms to the
agent that the worktree is on the PR's branch, so it shouldn't
create a new branch or open a new PR.
- V2_LAUNCH_CONTEXT_GAPS.md rewritten with locked design decisions:
bodies inline in prompt (no file writes for linked context), no
truncation, no sanitization, PR checkout is true. Work plan:
host-service getIssueContent → getPullRequestContent → task body
API → swap stubs. Target prompt shape included.
54 tests green; 2 snapshots updated for new PR format.
* feat(desktop/context): explicit kind labels in contributor headers
Agents shouldn't guess whether a section is a task, issue, or PR from
context clues. Each contributor now prefixes its heading with the kind:
- `# GitHub Issue #123 — Auth middleware stores tokens in plaintext`
- `# Task TASK-42 — Refactor auth middleware`
- `# PR #200 — Rewrite auth middleware`
PR phrasing also clarified: "This PR is checked out in this workspace
on branch `fix/auth-encryption`. Commits you make here will be added
to this PR."
54 tests green; 2 snapshots updated.
* feat(desktop/v2): fetch issue + PR bodies via host-service, task via cloud API
The launch prompt now includes full bodies for linked GitHub issues,
PRs, and internal tasks instead of title-only stubs.
Host-service (packages/host-service):
- getGitHubPullRequestContent: new procedure wrapping octokit.pulls.get.
Returns body, branch, baseBranch, author, isDraft, timestamps.
(getGitHubIssueContent already existed.)
Client (apps/desktop pending page):
- buildForkAgentLaunch accepts an optional hostServiceClient. When
provided, the issue + PR resolvers call getGitHubIssueContent /
getGitHubPullRequestContent for full bodies. Falls back to
pending-row title-only if the call fails (non-fatal).
- Task resolver calls apiTrpcClient.task.byId (Superset cloud API,
same source as the task view) for description. Falls back to
title-only on failure.
- dispatchForkLaunch threads the host-service client through.
Contributors (already landed earlier this session):
- GitHub issue header: `# GitHub Issue #N — Title`
- PR header: `# PR #N — Title` + "This PR is checked out in this
workspace on branch `X`. Commits you make here will be added to
this PR."
- Task header: `# Task ID — Title`
- Attachments block: framing header cueing the agent to read the
files.
77 tests green. Typecheck clean.
* chore(desktop): fix biome warning + stale doc comment in fork launch
- internalTask.test.ts: replace `TASK.description!` non-null
assertion with `if (TASK.description)` guard (biome
lint/style/noNonNullAssertion).
- buildForkAgentLaunch.ts: update stale docstring that claimed
bodies aren't fetched yet — they are, via host-service and the
cloud task API.
77 tests green, biome clean, typecheck clean.
* fix(host-service): shell out to gh CLI for issue/PR content (V1 parity)
host-service's octokit path needs a GitHub token from
providers.credentials.getToken("github.com") — which most users don't
have set up (requires GITHUB_TOKEN env or git credential helper config
for github.com). Result: getGitHubIssueContent / getGitHubPullRequestContent
silently 500'd, buildForkAgentLaunch fell back to title-only, and the
agent received empty bodies for linked issues/PRs.
V1's projects.getIssueContent shells out to `gh issue view` via the
user's `gh auth login` — that works out of the box.
Port the same approach:
- New packages/host-service/src/trpc/router/workspace-creation/utils/exec-gh.ts
— promisified execFile("gh", ...) with user shell env so PATH
resolves on macOS GUI contexts.
- getGitHubIssueContent now calls `gh issue view <n> --repo owner/name
--json number,title,body,url,state,author,createdAt,updatedAt`.
- getGitHubPullRequestContent calls `gh pr view <n> --repo owner/name
--json number,title,body,url,state,author,headRefName,baseRefName,isDraft,...`.
- Zod-validate the JSON output before returning.
- Normalize state to lowercase (gh returns "OPEN"/"CLOSED" uppercase).
Drops the Octokit dependency on these two procedures. Other host-service
paths that still use ctx.github() unchanged.
* fix typecheck
* clean up
Add iTerm-like Shift+Enter behavior to insert a newline without executing the command. Uses the quoted-insert approach (Ctrl+V + Enter) which works in bash, zsh, and other readline-based shells.
🤖 Generated with Claude Code
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Summary by CodeRabbit
New Features
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.