Skip to content

Interactive terminal sidecar for running jobs#194

Open
mikeangstadt wants to merge 54 commits into
mainfrom
fix/absolute-bash-path
Open

Interactive terminal sidecar for running jobs#194
mikeangstadt wants to merge 54 commits into
mainfrom
fix/absolute-bash-path

Conversation

@mikeangstadt
Copy link
Copy Markdown
Contributor

Summary

  • Interactive terminal: Click any running job to open a terminal window with an interactive Claude sidecar session
  • SIGSTOP/SIGCONT: Pauses the headless process group when the terminal opens, resumes when it closes
  • Sidecar model: Spawns claude --resume <sessionId> (or fresh session) alongside the paused original — user can give guidance, modify tasks, swap judges
  • Live event streaming: PTY output captured via @xterm/headless for clean text extraction, streamed to server as JSONL events
  • Token counting: Extracts real token counts from Claude's TUI status line
  • Shared disk state: Sidecar and original share claudeWorkDir — file/task changes visible to both
  • /bin/bash fix: Use absolute path in buildClaudePipeline to fix posix_spawnp failures in packaged macOS builds
  • interactiveTerminal default: Enabled by default with migration for existing installs
  • All commands: PLAN, EXECUTE, EVALUATE_*, DECOMPOSE, etc. all support terminal attach
  • Dedup: Spinner animation deduplication in live stream
  • Separate metrics: Sidecar writes to claude-output-interactive.jsonl / symphony-loop-interactive.log, merged at finalization

Architecture

Original (-p)  ──SIGSTOP────────────────────SIGCONT── continues ── exits ── onceComplete
                    │                          ↑
Terminal open       │                     Terminal close
                    │                          │
Sidecar (--resume)  └── user interacts ────────┘

Test plan

  • Start any job, click to open terminal, type guidance, close terminal — job continues and finalizes
  • Reopen terminal on same job — returns existing sidecar session
  • Live events stream shows user input and Claude responses
  • Token counts reflect sidecar usage at finalization
  • Packaged DMG build spawns processes without posix_spawnp errors

🤖 Generated with Claude Code

mikeangstadt and others added 30 commits May 13, 2026 21:21
In packaged macOS builds, Electron strips PATH to a minimal set.
Both child_process.spawn and node-pty use posix_spawnp to look up
the binary in the parent process's PATH — not the child's env.
Since "bash" isn't in Electron's minimal PATH, the spawn fails
with "posix_spawnp failed".

Use /bin/bash (absolute) in buildClaudePipeline so the lookup
doesn't depend on PATH at all. /bin/bash ships at this fixed
path on every macOS version.

Bump version 0.15.15 → 0.15.16.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Strip -p and --output-format stream-json from args when using
  the interactive terminal path so Claude stays in its interactive
  TUI instead of processing the prompt and exiting
- Spawn claude directly via absolute path instead of wrapping in
  a bash pipeline, avoiding posix_spawnp failures in packaged builds
- Pass prompt file content as positional arg so Claude starts with
  context but remains interactive for user input
- Enable JSONL capture via PTY onData handler + extractJsonlFromLog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The first commit only fixed the formatter branch. The no-formatter
fallback still used bare "bash", which fails the same way in
packaged builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Strip only -p (print mode) from args when using the interactive
  terminal so Claude stays in its TUI after the initial response
- Keep --output-format stream-json so JSONL extraction, token
  usage parsing, and completion detection still work on exit
- Spawn claude directly via absolute path (not bash pipeline)
- Pass prompt file content as positional arg for initial context
- Terminal window is a detachable view: open/close freely without
  affecting the running Claude process

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the user clicks "Open terminal" on a running job:
1. Kill the current detached -p process
2. Respawn Claude with --resume (no -p) in a PTY for interactive use

When the terminal window closes:
1. Kill the interactive PTY session
2. Respawn Claude with --resume + -p via detached bash pipeline
   so the job continues unattended

The mode switch is transparent — the user can open and close the
terminal window freely. The Claude conversation resumes seamlessly
via --resume in both directions.

Implementation:
- Store LoopSpawnConfig on RunningLoop entries for respawn
- modeSwitching flag suppresses finalization during kill/respawn
- switchToInteractive() and switchToDetached() exports handle the toggle
- IPC handler calls switchToInteractive, window.closed calls switchToDetached

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The exit event from the killed process fires after switchToInteractive
replaces the runningLoops entry, so reading modeSwitching from the
map finds the new entry (which has no flag set).

Fix: use a modeSwitchSuppressed closure variable owned by the
original handleLoopRequest scope. The RunningLoop entry carries a
suppressCompletion callback that sets this variable, so the
mode-switch functions can suppress finalization even after the
map entry has been replaced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Store loopSpawnConfig for PLAN/EXECUTE (run-loop.sh) path too,
  preventing crash if terminal button is ever shown for those commands
- Improve error message when spawnConfig is missing (old version or
  boot-recovered job) to explain the cause
- Reorder checks: return existing PTY session before checking config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When switching modes, the old process is killed and a new one
spawns with a different PID. The job store still had the old PID,
so enrichJobSnapshot saw a dead process and marked the job as
STOPPED, removing it from the UI.

Fix: update the job store PID after spawning the replacement
process in both switchToInteractive and switchToDetached.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- --resume prompts user to pick a session and opt in to file
  inspection. --continue auto-continues the most recent conversation
  without prompting — correct for both PTY and detached respawn.
- Filter stdin marker "-" from detached resume args since --continue
  picks up from the existing conversation without needing a prompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Read session ID from <claudeWorkDir>/session-id.txt to target
  the exact conversation when switching modes
- Fixes concurrent job confusion: --continue picks most recent
  conversation globally, --resume <id> targets the right one
- switchToInteractive: errors if session-id.txt missing (Claude
  hasn't started yet)
- switchToDetached: gracefully skips if no session ID (job done)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Direct claude commands (EVALUATE_*, DECOMPOSE, etc.) emit session_id
in the stream-json JSONL output but don't write session-id.txt
(that's only written by the run-loop.sh path for PLAN/EXECUTE).

Add readSessionId() helper that checks session-id.txt first, then
falls back to parsing session_id from the first JSONL line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The job disappeared from the UI after closing the terminal because
the detached resume process had no onceComplete wiring — when it
exited, handleProcessCompletion never ran, the job stayed RUNNING
with a dead PID, and enrichJobSnapshot marked it STOPPED.

Fix: replace the boolean modeSwitchSuppressed with a generation
counter. Each mode switch bumps the generation, which invalidates
the killed process's exit handler. The replacement process captures
the new generation and wires its exit into the original onceComplete,
so finalization runs correctly when it eventually exits.

Both bumpGeneration and onceComplete are carried forward across
mode switches so the job can toggle between interactive and
detached any number of times.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the user closes the terminal after Claude finished the work,
don't spawn a pointless --resume -p (which produces no artifacts).
Instead, run onceComplete directly with the PTY's exit code so
handleProcessCompletion finds the artifacts that were produced
during the interactive session.

Only spawn a detached --resume when the PTY is still running
(Claude hasn't finished yet and needs to continue unattended).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Artifacts may not be available when the PTY exits — the --resume -p
process handles artifact production and finalization. Always spawn
it regardless of whether the interactive session already exited.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all mode-switching infrastructure (switchToInteractive,
switchToDetached, LoopSpawnConfig, generation counters). The
interactive terminal is now a simple PTY-backed spawn:

- When interactiveTerminal is enabled and the command is in
  INTERACTIVE_TERMINAL_COMMANDS, spawn claude via PTY instead of
  detached child process. Same args including -p so Claude runs
  to completion unattended.
- The terminal window is a view into the PTY output with stdin
  forwarding. Closing the window detaches the view — the process
  keeps running.
- onceComplete fires normally when the PTY process exits,
  triggering handleProcessCompletion for artifact upload and
  finalization.
- No kill/resume cycle, no session ID tracking, no PID juggling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bash pipeline uses stdin redirect (< promptFile) to feed the
prompt when args contain "-p -". But PTY spawn has no stdin redirect,
so "-" reads nothing from the PTY and Claude exits immediately
(6 tokens, 2 turns, 0 work done).

Fix: filter out "-" from args and pass the prompt file content as
a positional arg to -p. Claude receives the full prompt and runs
to completion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all mode-switching complexity. The interactive terminal is
now a completely independent sidecar process:

- Initial spawn: always -p via detached bash pipeline (unchanged)
- Click "Open terminal": spawn a NEW claude --resume <sessionId>
  process in a PTY. The original -p process keeps running.
- Close terminal: nothing happens. Both processes keep running.
- Original -p process exits: onceComplete fires, artifacts upload,
  finalization runs as normal.

The sidecar reads the session ID from claude-output.jsonl (or
session-id.txt) and resumes the same conversation. The user can
interact with Claude while the background -p process continues.

Deleted: switchToInteractive, switchToDetached, LoopSpawnConfig,
bumpGeneration, modeSwitchGeneration, suppressCompletion, PID
juggling, and all related wiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Check hasSession before spawning sidecar PTY. If session exists
  and is still alive, return it (user reopened terminal). If it
  exited, clean up and re-spawn. Prevents "PTY session already
  exists" crash on terminal reopen.
- Both the main -p process and the sidecar write to the same
  claude-output.jsonl so all token usage and results are captured
  in one stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Give the interactive sidecar its own log and JSONL files so its
token usage, turn counts, and result events don't pollute the
main -p process's metrics that feed the output-tailer and cloud
upload stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Start a second output-tailer for the sidecar's
claude-output-interactive.jsonl that streams events to the cloud
API using "interactive:<loopId>" as the identifier. This keeps
the sidecar's token usage and events delineated from the main
-p process while ensuring the server sees both streams.

Also pass apiBaseUrl and loopToken from the job store to the
sidecar spawner so it has the credentials to post events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the headless -p process completes:
- Terminal window OPEN: defer finalization until the sidecar exits,
  so the user's in-progress work (added judges, guidance) is
  included in the final results
- Terminal window CLOSED: finalize immediately, even if the sidecar
  process is still running in the background
- Terminal never opened: finalize immediately (no sidecar exists)

Track window open/close state via markTerminalWindowOpen/Closed
called from the IPC handler's BrowserWindow lifecycle events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Send SIGSTOP to the headless -p process when the terminal window
opens, freezing it so it doesn't race the interactive sidecar.
Send SIGCONT when the window closes so it picks up where it left
off.

This eliminates the race condition where the headless process
finalizes with incomplete results while the user is still adding
work in the interactive session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Before handleProcessCompletion runs, append the sidecar's
claude-output-interactive.jsonl and symphony-loop-interactive.log
into the main files. This ensures parseTokenUsage sees the
combined token counts from both sessions, and the full log
history is available for diagnostics.

The sidecar's real-time tailer still streams with the
"interactive:" prefix for delineation on the server. The merge
only happens at finalization time for the consolidated result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The loopId is used for API routing and must be the real ID.
Interactive JSONL records already carry a distinct session_id
from the --resume spawn, which provides natural delineation
on the server side.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sidecar was spawning with just --resume <sessionId>, so Claude
output plain text. The JSONL file stayed empty and the tailer had
nothing to stream. Add --output-format stream-json and --verbose
so Claude emits structured JSONL that the output tailer picks up
for live events and audit log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--output-format stream-json produces nothing in --resume mode,
so Claude's interactive JSONL was always empty.

Instead, capture events at the WebSocket/PTY level:
- user_input: logged when the terminal sends keystrokes
- assistant_output: logged from PTY onData (Claude's responses)

Both are written as timestamped JSONL to
claude-output-interactive.jsonl. The output-tailer streams them
to the server under the real loopId. At finalization, they get
merged into the main JSONL for combined metrics.

Also remove --output-format stream-json from sidecar args since
it has no effect in --resume mode and could interfere with the TUI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SIGSTOP/SIGCONT:
- Use -pid (process group) instead of pid so Claude's child
  processes (tool calls, subagents) are also paused/resumed

Interactive JSONL events:
- Write events in the format the output-tailer recognizes:
  type "user" and "assistant" with content text blocks
- Buffer assistant output chunks and flush after 500ms pause
  to avoid flooding JSONL with per-character PTY events
- Previous custom event types (user_input, assistant_output)
  were silently dropped by the tailer's summarizeJsonlRecord

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Strip ANSI escape codes from PTY output before logging
- Increase flush interval to 2s and require 10+ visible chars,
  filtering out TUI rendering noise (cursor moves, spinners)
- Buffer user keystrokes and only log complete lines (on Enter),
  not individual characters
- Collapse whitespace in assistant output for readable log entries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the user types, the PTY echoes each keystroke back as output.
The assistant output buffer was capturing these echoes, producing
garbled "c a n y o u..." entries in the live stream.

Fix: set a 1s typing suppression window after each keystroke.
PTY output received during this window is forwarded to the
terminal display but not logged as assistant output. Once the
user stops typing and Claude responds, the assistant output
resumes logging normally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the terminal window closes, instead of SIGCONT-ing the stale
original process (which doesn't know about the sidecar's work):

1. Kill the original -p process (SIGTERM to process group)
2. Spawn --resume <sessionId> -p to continue from the sidecar's
   conversation endpoint — like merging a feature branch into main
3. Wire the replacement's exit into the original onceComplete so
   the same finalization pipeline (artifact upload, event posting)
   runs when the resumed process completes

The replacementPending flag on RunningLoop suppresses finalization
for the killed process's exit. The replacement process inherits
the spawn config and onceComplete reference.

Falls back to SIGCONT if no spawn config or session ID is available
(e.g., job started before this code was deployed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mikeangstadt and others added 17 commits May 14, 2026 14:09
- Raise minimum line length from 10 to 20 chars
- Skip key hints (Entertoconfirm, Esctocancel, esctointerrupt)
- Skip token counters (52215tokens)
- Skip spinner/status symbols only lines
- Skip spinner state labels (Thinking, Actioning, Churning, etc.)

Keeps: Tool results, Claude text responses, user input, and
other meaningful content lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parse token totals from Claude's TUI counter (e.g. "52215tokens")
and compute deltas between readings. Emit usage records with
actual token counts (80/20 input/output split) instead of
text-length estimates.

Also:
- Keep spinner labels (Thinking, Actioning, etc.) in live stream
- Remove estimated token counts from text events to avoid
  double-counting with the real TUI-extracted counts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User keystrokes were echoed by the PTY into the assistant line
buffer, producing garbled mixed output like "Ican you swap the
dependency readdependency judge...".

Fix:
- Clear the assistant line buffer on Enter so echoed keystrokes
  are discarded before Claude's response arrives
- Extend typing suppression to 3s after Enter to skip the TUI
  redraw between user input and Claude's actual response

User input is captured separately via the keystroke accumulator
and emitted as a clean "user" event on Enter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove INTERACTIVE_TERMINAL_COMMANDS gate. The sidecar model is
independent of how the original process spawns — it just does
--resume <sessionId> on a separate PTY. Every command (including
Plan, Execute, Bootstrap) now shows the terminal attach button
when interactiveTerminal is enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lower minimum line length from 20 to 3 chars
- Remove filters for spinner labels, thinking text, and key hints
  — only skip pure symbol-only lines and token counter patterns
- Reduce typing suppression from 3s/1s to 1.5s/500ms so Claude's
  response appears faster in the live stream

Claude's thinking blocks, tool calls, search results, and text
responses now flow through to the server live stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The --resume -p process on terminal close exits immediately with
nothing to do, triggering premature finalization. Remove the
resume-on-close machinery entirely.

On terminal close: SIGCONT the original process and let it finish
its own work. The sidecar's contributions (task changes, guidance)
are on disk — the original process picks them up as it continues.

Removed: LoopSpawnConfig, spawnConfig on RunningLoop, onceComplete
reference, resume spawn logic, SIGKILL cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On terminal close: kill the paused original (stale context) and
spawn --resume <sidecarSessionId> -p with a continuation prompt:
"Continue executing the original task, incorporating any
instructions or changes the user made during the interactive
session."

Claude loads the sidecar's full conversation history (including
user instructions like "swap the dependency judge") and the prompt
gives it work to do so -p doesn't exit immediately. This forces
Claude to adopt the sidecar's context and continue the task.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SIGKILL'd original's exit event fires onceComplete before
the resume process starts, causing premature finalization.

Fix: leave the original SIGSTOP'd forever. A paused process
never exits, so its onceComplete never fires. The resume
process's exit triggers finalization via its own wiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hand-rolled ANSI stripping with a proper headless terminal
emulator. Feed raw PTY bytes into xterm, read clean text lines
from its buffer — no regex hacks, no manual escape handling.

- @xterm/headless processes all escape sequences correctly
  (CSI, OSC, cursor moves, redraws, colors, etc.)
- Extract lines by reading the terminal buffer, not parsing bytes
- Dedup spinner animation: skip lines that differ only by leading
  spinner symbol (✳Running... ≡ ✶Running... ≡ ✻Running...)
- Token counter extraction from clean text
- Debounce extraction to 1s after last data
- Typing suppression: skip extraction during user input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use default import + destructure for CJS compatibility:
  import pkg from '@xterm/headless'; const { Terminal } = pkg;
- Replace single lastLine dedup with a Set of recently emitted
  lines (stripped of spinner prefixes). Catches repeats even
  when other lines are interleaved between spinner frames.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The buffer API requires allowProposedApi: true in xterm v6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- On Enter: reset lastLineCount to current buffer length so all
  echoed keystrokes are marked as "already seen" and skipped
- Strip leaked escape fragments like [O from line start
- Longer typing suppression on Enter (2s) vs regular keys (500ms)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- interactiveTerminalAvailable now uses shouldUseInteractiveTerminal
  directly — works for all commands including PLAN/EXECUTE
- Store spawnConfig for all spawn paths (spawnClaudePipeline and
  run-loop.sh) so the resume-on-close works for every command
- Store onceComplete reference on RunningLoop for resume wiring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--resume -p exits non-zero even when work completed successfully.
The resume is a continuation mechanism — actual success is
determined by handleProcessCompletion checking artifacts on disk.
Always pass 0 so finalization runs the success path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
shouldUseInteractiveTerminal is scoped inside the try block.
Use the getter directly for the job store upsert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Terminal close: just SIGCONT the original. No resume spawn,
  no kill, no finalization wiring.
- Sidecar: try --resume <sessionId> if available, fall back to
  fresh session if not (handles run-loop.sh where session may
  be locked by the orchestrating process).
- All commands show terminal button via getInteractiveTerminal().
- Remove LoopSpawnConfig, onceComplete from RunningLoop — no
  longer needed without resume-on-close.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mikeangstadt mikeangstadt requested a review from a team May 14, 2026 21:17
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/main/app.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Copy link
Copy Markdown
Contributor

@shafty023 shafty023 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workflow review findings posted inline.

Comment thread apps/desktop/package.json
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/main/app.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
mikeangstadt and others added 2 commits May 14, 2026 17:18
The original process's session is locked (SIGSTOP'd but alive).
--resume fails with "No conversation found" for PLAN/EXECUTE
and any command where the session is held by the paused process.

Start a fresh Claude session in the same claudeWorkDir instead.
The sidecar can read/modify files and tasks on shared disk
without needing the original's conversation history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidecar: --resume <sessionId> (works for evaluate, needs session
ID fix for PLAN/EXECUTE). Falls back to fresh session if no ID.

Terminal close: leave original paused, spawn headless
--resume <sessionId> -p with continuation prompt. The session
includes the sidecar's turns so Claude picks up user guidance.
Paused PIDs stack and get SIGKILL'd when the leaf completes.

Default: interactiveTerminal = false for slow roll release.
Remove the false→true migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor Author

@mikeangstadt mikeangstadt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — 4 HIGH, 3 MEDIUM findings. See inline comments below.

Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/main/app.ts
Comment thread apps/desktop/src/server/operations/symphony-loop.ts
Comment thread apps/desktop/src/server/operations/terminal-attach.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants