diff --git a/docs/open.md b/docs/open.md index 7bea8891..dc279b7c 100644 --- a/docs/open.md +++ b/docs/open.md @@ -18,6 +18,12 @@ Kein Code-Aufwand, nur Durchspielen mit explizit notiertem Pass-Kriterium. Der G - **UI-Sichtprüfung (neu, 2026-06-19, Rider-Style 3-Pane Merge-Editor):** Echten Konflikt auslösen (Single-Task-Approve mit Konflikt **und** Planning-Unit-Merge) und prüfen: drei Panes (Ours read-only | Result editierbar | Theirs read-only), Konfliktblöcke rot / aufgelöst grün in allen Panes, Inline-Accept `›`/`‹` in den Zwischen-Guttern landen die jeweilige Seite im Result, nur Konfliktregionen im Result editierbar (Stable read-only), synchrones vertikales Scrollen, File-Switcher bei mehreren Dateien, `M conflicts · K resolved`-Readout, Continue erst bei allen Konflikten gelöst, Binär-Guard. **Bekannte Kanten:** (1) Konflikt mit leerer Ours-Seite → Result-Region ist null-lang (Gutter via 1-Zeichen-Probe positioniert, Accept funktioniert; nur Hand-Tippen in die leere Region ist fummelig). (2) Gutter-Y nutzt `TranslatePoint` vom Result-`TextView` — bei sehr hohen Fenstern / großen Scrollständen die Ausrichtung gegenprüfen. (3) Blöcke richten sich nur über Stable-Text aus; nach einem Konflikt mit unterschiedlicher Zeilenzahl je Seite driften nachfolgende Blöcke vertikal (aligned/virtual-space Scroll ist bewusst zurückgestellt). - **Worker-Autostart am Gerät:** Logoff/Logon-Autostart, Update-Pfad, Uninstall entfernt die Startup-`.lnk`. - **In-App Interactive Sessions (2026-06-26, REMOVED 2026-07-23):** der In-App-Streaming-Chat (`StreamingClaudeSession`, Composer/Queue auf `TaskMonitorViewModel`/`SessionTerminalView`) wurde komplett entfernt und durch die **embedded ConPTY**-Sessions ersetzt (echte `claude`-TUI im UI-Prozess, siehe `docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md`). Kein offener Punkt mehr — nur zur Historie. +- **Embedded ConPTY Sessions (neu, 2026-07-23):** Command Center hostet echte `claude`-TUI-Kacheln (`Iciclecreek.Avalonia.Terminal` 2.0.3 via `TerminalControl.LaunchProcess()`). Rendering/Input/Tempo vom User verifiziert. **Noch durchzuspielen:** + - Task-basiert (Kontextmenü „Open ConPTY session"): frischer Task → Worktree wird on-demand angelegt, `claude` startet mit Task-Prompt (Title+Description als positionaler Prompt) — **verifizieren, dass `claude ""` interaktiv wirklich SENDET**, nicht nur vorbefüllt. + - Ad-hoc („New session"-Button → Ordnerwahl): freie Session im gewählten Verzeichnis. + - Grid↔Tabs-Toggle, Close killt Session + entfernt Kachel, mehrere Sessions parallel, Pane-Resize reflowt. + - Resume einer interaktiven Session: nur via claude-eigenes `claude --continue`/`--resume` im Worktree-Dir (ClaudeDo speichert die Session-Id NICHT — ConPTY ist opak). + - **Zurückgestellt:** Avalonia-12.1-Upgrade (braucht .NET-9-SDK-Floor wg. Roslyn-4.14-XAML-Generator; CI-Risiko) — bleibt auf 12.0.x. - **Pick up in terminal (neu, 2026-07-01):** neue Aktion, die die Claude-Session einer Task per `claude --resume ` in einem **echten** `wt`-Terminal fortsetzt (echte TUI: Permission-Prompts/Fragen inklusive) — bewusst NICHT der In-App-Streaming-Chat. Real-CLI-Smoke (kein Claude in Tests): - Kontextmenü einer Task in **WaitingForReview** oder **Failed** → „Pick up in terminal" sowie der Terminal-Button (Icon `ArrowOut`) im Detail-Header sind sichtbar; bei anderen Status (Idle/Running/Queued/Done) NICHT. - Klick → neues Windows-Terminal im Worktree-Verzeichnis der Task, Claude nimmt die letzte Session mit erhaltenem Kontext wieder auf. diff --git a/docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md b/docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md index 39079403..46780d73 100644 --- a/docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md +++ b/docs/superpowers/specs/2026-07-23-conpty-interactive-sessions-design.md @@ -53,10 +53,19 @@ LaunchSpec { } ``` -The command construction reuses the existing logic in -`WindowsTerminalLauncher.BuildResumeCommand`. Guards mirror -`ResumeTaskInTerminal`: task not Running/Queued, a persisted SessionId exists, -worktree exists and is Active/Kept. +The command construction reuses `WindowsTerminalLauncher.BuildResumeArgs`/`Resolve`. +Guards mirror `ResumeTaskInTerminal` for Running/Queued (rejected). Worktree +handling (FINAL): +- Existing Active/Kept worktree + persisted SessionId → `--resume `. +- No usable worktree but the list has a WorkingDir (git repo) → create a worktree + **on demand** via `WorktreeManager.CreateAsync` (the same path autonomous runs + use), then a fresh-start spec. This lets never-run tasks be opened interactively. +- Fresh (non-resume) session → the task's prompt (title + description) is passed + as claude's positional prompt so the session starts on the task. +- No worktree and no WorkingDir → clear error. +Interactive sessions are detached: ClaudeDo does NOT record their claude session +id (ConPTY is opaque, no stream-json), so resuming a specific past interactive +conversation is only via claude's own `--continue`/`--resume` in the worktree dir. ### Free / ad-hoc sessions @@ -74,31 +83,38 @@ It needs Avalonia >= 12.0.2; the repo is on 12.0.4 → compatible, no bump. Visual pass (user, 2026-07-23): the real `claude` TUI renders correctly inside the embedded control — Claude Code opened and was usable. -**Binding approach — drive Porta.Pty ourselves, reuse Iciclecreek's VT engine.** -The convenience `TerminalControl.LaunchProcess()` spawns the child itself and -never sets `PtyOptions.Environment`, and there is no settable pty/env seam on -`TerminalControl`/`TerminalView` (env-building lives in the non-virtual -`TerminalView.LaunchProcess()` using private fields). But the VT parser/renderer -is the public `XTerm.Terminal` object exposed via `TerminalControl.Terminal`, so -we reuse it without forking: +**Binding approach — use the library's own `LaunchProcess()` (FINAL).** +An initial attempt drove `Porta.Pty` ourselves (own read loop, key tunneling via +`GenerateKeyInput`/`GenerateCharInput`, manual resize) to bypass +`LaunchProcess()` and inject a custom `PtyOptions.Environment`. That was a +mistake: it rendered wrong, lagged, and dropped input. The spike had proven the +library's own `TerminalControl.LaunchProcess()` pipeline renders correctly, stays +responsive, and handles input/resize/focus. So `PtyTerminalSession` is a thin +wrapper (`src/ClaudeDo.Ui/Services/PtyTerminalSession.cs`): -- `Porta.Pty.PtyProvider.SpawnAsync(new PtyOptions { Name, App, Cwd, CommandLine, - Environment = }, ct)` → `IPtyConnection`. -- Do **not** call `TerminalControl.LaunchProcess()` (leaves its `_ptyConnection` - null). -- Get `TerminalControl.Terminal` (public, `XTerm.Terminal`) after template apply. -- Pump `IPtyConnection.ReaderStream` → `Terminal.Write(text)`; subscribe - `Terminal.DataReceived` → `IPtyConnection.WriterStream`; mirror - `Terminal.Resized` ↔ `IPtyConnection.Resize(cols, rows)`. -- Implement our **own** exit-code / kill / wait-for-exit against our - `IPtyConnection` — do NOT touch `TerminalControl.ExitCode`/`Pid`/`Kill()`/ - `WaitForExit()`, they NRE on the null `_ptyConnection`. +- Set `control.Process = descriptor.Exe`, `control.Args = descriptor.Args`, + `control.StartingDirectory = descriptor.Cwd`, then `await control.LaunchProcess()`. +- Relay the control's own `ProcessExited` event and `Kill()`. +- `Process=""` stays on the AXAML `TerminalControl` to suppress the library's + auto-launch-on-load, so exactly one process starts (our manual launch). -Note on environment: `Porta.Pty.SpawnAsync` already seeds the child from the -current (ClaudeDo) process environment (PATH/APPDATA/etc.) and merges -`PtyOptions.Environment` on top — so we only need to ADD our custom vars (MCP -config path, `MAX_THINKING_TOKENS`, …), not rebuild the whole environment. (The -earlier spike's "child gets zero env vars" claim was wrong.) +**Environment:** `LaunchProcess()` gives no per-launch env dict, but +`Porta.Pty.SpawnAsync` inherits the *current process* environment. So apply +`descriptor.Env` entries via `Environment.SetEnvironmentVariable(key, value)` +(process scope) before `LaunchProcess()`. The only var needed is +`MCP_TOOL_TIMEOUT`; session-skills/agent-files/MCP-config are on disk / globally +registered, independent of env. (The earlier "child gets zero env vars" claim was +wrong — Porta.Pty seeds from the process env.) + +**Sizing gotcha (critical):** the control derives Cols/Rows from +`arranged-size / character-cell-size`. Without an EXPLICIT monospace font the cell +metrics are wrong and the child TUI renders into the wrong area. Set +`FontFamily="Cascadia Mono,Consolas,monospace"`, `FontSize`, `BufferSize`, and +`HorizontalAlignment/VerticalAlignment=Stretch` on the `TerminalControl` (matching +the spike) — this is what made rendering correct in the pane. + +**Lesson:** do not hand-roll pty/input/render around this control — use its +`LaunchProcess()` pipeline. ### Command Center layout @@ -139,3 +155,17 @@ inherited process env via `PtyOptions.Environment`); library + binding seam. - No screen-scraping of terminal output back into task status/diff/review. - No change to the autonomous queue execution path. + +## Status (2026-07-23) + +Implemented on main (not pushed) and visually verified by the user (rendering, +input, responsiveness correct after switching to `LaunchProcess()` + setting a +monospace font). Done: worker launch-spec (task + ad-hoc, on-demand worktree, +prompt seeding), UI terminal host, Command Center hosting (grid↔tabs), streaming +stack removed. Permission mode: left to claude's default (not forced), per user. + +Deferred: **Avalonia 12.1 upgrade** — blocked, not adopted. 12.1's XAML source +generator needs Roslyn 4.14 (.NET 9.0.3xx SDK); the repo pins .NET 8 in +`global.json`, and bumping the SDK floor risks the Gitea Actions release build. +Staying on Avalonia 12.0.x (Iciclecreek 2.0.3 works there). Revisit only with a +deliberate SDK-floor decision.