119 lines
6.1 KiB
Markdown
119 lines
6.1 KiB
Markdown
# ConPTY interactive sessions & launch specs
|
|
|
|
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
|
> Last verified against commit `f6cb825` (2026-08-05).
|
|
> Drift check: `git log --oneline f6cb825..HEAD -- src/ClaudeDo.Worker/Planning src/ClaudeDo.Worker/Hub src/ClaudeDo.Ui/ViewModels/MissionControlViewModel.cs src/ClaudeDo.Ui/Views/InteractiveTerminalView.axaml`
|
|
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
|
|
|
|
Covers `InteractiveLaunchSpecService` and the four kinds of embedded ConPTY session the UI
|
|
process hosts (real `claude` TUI in a Mission Control tile).
|
|
|
|
Autonomous queue tasks are **not** covered here — they stay on the stream-json path via
|
|
`TaskRunner`. See [worker-task-pipeline.md](worker-task-pipeline.md).
|
|
|
|
## The four session kinds
|
|
|
|
| Kind | Hub spec method | Notes |
|
|
|---|---|---|
|
|
| Task session | `GetInteractiveLaunchSpec` | Effort from the task/list model preset |
|
|
| Ad-hoc | `GetAdHocLaunchSpec` | Effort from the global default |
|
|
| Planning | (planning start/resume) | Effort from `PlanningAlias`; uses `--permission-mode default`, **not** `plan` |
|
|
| List handler | `GetMergeHelperLaunchSpec` | Effort from list config; `--permission-mode auto` (unattended) |
|
|
|
|
## ⚠️ Gotcha: never pass task free-text as a CLI argument
|
|
|
|
**No ConPTY path ever passes task free-text (title / description / brief) as a CLI argument.**
|
|
Every one of them writes it to a file first and hands `claude` a single-line kickoff pointing at
|
|
that file, exposed via `--add-dir`.
|
|
|
|
Two independent reasons:
|
|
|
|
1. The ConPTY host flattens `Args` into **one command line** to spawn the process, and `claude`
|
|
re-splits that line on whitespace. Any token starting with `-` in real task text (e.g. `->`,
|
|
`--abort`) is then misread as an unknown option.
|
|
2. A raw multi-line positional prompt truncates at its **first newline** regardless.
|
|
|
|
A fresh task session's brief lives at `~/.todo-app/task-sessions/<taskId>/brief.md`
|
|
(`InteractiveLaunchSpecService.BuildFreshTaskArgsAsync`). A task with neither title nor
|
|
description skips the file **and** the positional arg entirely.
|
|
|
|
## Argument ordering
|
|
|
|
Every spec passes `--effort <level>` from the relevant model's preset. It **leads** the args —
|
|
except for a fresh task session with a brief, where `--add-dir <sessionDir>` must come first so
|
|
`--effort` (a single-value flag) can sit directly before the positional kickoff.
|
|
|
|
`--model` is deliberately **NOT** forced on an interactive session — the user can still switch
|
|
models in the TUI.
|
|
|
|
## List handler ("Let Claude handle it")
|
|
|
|
`BuildForMergeHelperAsync` uses `--permission-mode auto` so it runs unattended. The
|
|
`--allowedTools` allowlist is the security boundary:
|
|
`mcp__claudedo__*,Read,Grep,Glob,Edit,Bash,WebFetch,WebSearch,Skill`.
|
|
|
|
`MCP_TOOL_TIMEOUT` is 200 s here — `TaskWaitMcpTools` clamps its own timeout to 170 s to stay
|
|
comfortably under it (see [external-mcp.md](external-mcp.md)).
|
|
|
|
### The host task and its commit range
|
|
|
|
The handler run **owns a real ClaudeDo task**, created by `CreateMergeHelperTask` (hub) →
|
|
`InteractiveLaunchSpecService.CreateMergeHelperTaskAsync`, called by the UI *before* it opens
|
|
the tile:
|
|
|
|
- One new task per run in that list, `Idle` + `IsManual=true` (never queued).
|
|
- Title/description localized via `missionControl.mergeHelperTaskTitle` /
|
|
`mergeHelperTaskDescriptionHeader`.
|
|
- `TaskEntity.HandlerBaseCommit` stamped to the list repo's current HEAD.
|
|
|
|
The host task **never gets a worktree of its own** — the handler commits straight into the
|
|
list's working dir and merges the tasks it handles itself. Consequences:
|
|
|
|
- `SubmitTaskForReview` branches on whether the task has a `WorktreeEntity`: with one, it
|
|
commits the worktree; without one, it stamps `HandlerHeadCommit` to the list repo's current
|
|
HEAD. Both paths then flip the task `Idle`/`Failed` → `WaitingForReview`.
|
|
- `GetTaskDiff` and the UI's `DetailsIslandViewModel` / `MergeSectionViewModel` fall back to the
|
|
`HandlerBaseCommit`..`HandlerHeadCommit` range whenever `Worktree` is null.
|
|
|
|
### UI flow
|
|
|
|
`MergeHelperSelectionModalViewModel` — checkbox picker over one list's non-terminal, non-manual
|
|
tasks, pre-ticking the actionable ones. **List-scoped only** (`Configure(listId, listName)`, no
|
|
global scope). Opened from the list row's context menu, which is hidden when the list has no
|
|
working dir.
|
|
|
|
On confirm: `ListsIslandViewModel` raises `LetClaudeHandleRequested` → shell →
|
|
`MissionControlViewModel.OpenMergeHelperConPtySessionAsync`, which calls
|
|
`CreateMergeHelperTaskAsync` and then opens a **task-based** tile (deduped by `TaskId` like
|
|
`OpenConPtySessionAsync`, **not** `CreateAdHoc`) running the five-phase handler prompt.
|
|
|
|
## Tile lifecycle
|
|
|
|
`ConPtyPaneViewModel` resolves its own launch spec — the ctor takes a descriptor **factory**,
|
|
the host wires handlers and then calls `Start()`. So the tile appears **immediately** with its
|
|
spinner while the worker is still preparing the worktree. A failed launch keeps the tile with an
|
|
inline error banner instead of the tile never appearing.
|
|
|
|
`Ellipse.spinner` (IslandStyles) is the shared indeterminate spinner — used for a starting pane
|
|
(`InteractiveTerminalViewModel.IsStarting`) and in place of the refine button while
|
|
`TaskRowViewModel.IsRefining`.
|
|
|
|
## Focus / key handling
|
|
|
|
`InteractiveTerminalView` lives in `MissionControlWindow`, so the `FocusClearing` Escape handler
|
|
(scoped to `MainWindow` via `AddClassHandler<MainWindow>`) never runs there — **Escape always
|
|
reaches the PTY**. See the note in `src/ClaudeDo.Ui/CLAUDE.md`.
|
|
|
|
`TaskRowViewModel.HasInteractiveSession` shows an accent "Interactive" chip instead of "Parked";
|
|
tapping it jumps to that Mission Control pane. `TasksIslandViewModel.SyncInteractiveSessions`
|
|
mirrors Mission Control's open panes onto the rows.
|
|
|
|
## Related hub methods
|
|
|
|
`GetInteractiveLaunchSpec`, `GetAdHocLaunchSpec`, `GetMergeHelperLaunchSpec`,
|
|
`CreateMergeHelperTask`, `SubmitTaskForReview`.
|
|
|
|
Planning sessions: `StartPlanningSession`, `ResumePlanningSession`, `DiscardPlanningSession`,
|
|
`FinalizePlanningSession`, `QueuePlanningSubtasks`, `GetPendingDraftCount`,
|
|
`GetPlanningAggregate`, `BuildPlanningIntegrationBranch`.
|