Commit Graph
978 Commits
Author SHA1 Message Date
mika kuns 9ab48d7094 feat(interactive): fresh-worktree-on-demand + ad-hoc launch specs
BuildForTaskAsync now creates a worktree on demand (via WorktreeManager.CreateAsync,
the same path TaskRunner uses) when a task has a configured working dir but no
Active/Kept worktree, returning a fresh-start spec -- so never-run tasks can be
opened interactively. Adds BuildForDirectoryAsync + GetAdHocLaunchSpec hub/client
for ad-hoc sessions in an arbitrary directory (no task, no worktree, no skill seeding).
2026-07-23 16:47:15 +02:00
mika kuns 0513265c49 feat(interactive): host task-based ConPTY sessions in Command Center
Adds an 'Open ConPTY session' entry that fetches a task's launch spec and hosts
an embedded ConPTY terminal as a Mission Control pane, coexisting with the
streamed-log monitor panes (streaming stack untouched). Introduces IMissionControlPane
+ ConPtyPaneViewModel, a non-destructive Panes mirror (Monitors prefix + ConPtySessions
suffix) so unrelated monitor churn never tears down a live terminal, and a grid<->tabs
layout toggle. Launch failures surface via the footer error strip.
2026-07-23 16:47:15 +02:00
mika kuns 5f740c05d8 feat(interactive): embedded ConPTY terminal host in UI
Adds a self-contained terminal host: PtyTerminalSession drives a Porta.Pty child
directly (custom env) and pumps it through Iciclecreek's XTerm.NET renderer via
TerminalControl.Terminal, bypassing LaunchProcess() so a fully-populated
environment can be passed. InteractiveTerminalView/ViewModel host it with an
order-independent attach/start. Adds Iciclecreek.Avalonia.Terminal 2.0.3.
Sets Process="" to suppress the control's auto-launch of a stray shell.
2026-07-23 16:47:15 +02:00
mika kuns 1245e75902 feat(interactive): worker launch-spec for embedded ConPTY sessions
Adds InteractiveLaunchSpecService + GetInteractiveLaunchSpec hub method that
prepares a task worktree (session-skills seeding, run env) and returns a
LaunchSpec {cwd,exe,args,env} for a UI-hosted ConPTY terminal. Reuses
ISessionSkillSeeder, TaskRunner.UnionSkillNames, and WindowsTerminalLauncher
resume-arg/resolve logic. Guards mirror ResumeTaskInTerminal; a never-run task
yields a fresh-start spec instead of an error.
2026-07-23 16:47:15 +02:00
mika kuns 17235a6cde Fix empty error message 2026-07-23 16:47:15 +02:00
Mika Kuns 7c3c061428 feat(ui): session skills registry tab + per-level selectors 2026-07-23 16:47:14 +02:00
Mika Kuns b4c58087d2 feat(worker): session skills SignalR surface + per-level persistence 2026-07-23 16:47:14 +02:00
Mika Kuns 4626481359 feat(worker): resolve and seed session skills before each run 2026-07-23 16:47:14 +02:00
Mika Kuns dea2b7db8b feat(worker): session skill registry (install/update/remove, pinned clone) 2026-07-23 16:47:14 +02:00
Mika Kuns 54cdaf89d5 feat(data): session skills entity, repository, and migration 2026-07-23 16:47:14 +02:00
Mika Kuns eb88dc130c feat(ui): pick up a task's session in a terminal 2026-07-23 16:47:13 +02:00
Mika Kuns 140ae2fda1 feat(worker): resume a task's claude session in a terminal 2026-07-23 16:47:13 +02:00
Mika Kuns 865e12c0de fix(worker): seed planning brief via file to avoid newline truncation 2026-07-23 16:47:13 +02:00
Mika Kuns 711374e858 fix(worker): reap idle interactive sessions so they don't pile up
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 51s
Interactive/streaming sessions are persistent claude.exe processes that
wait on stdin and never exit on their own. The only teardown was an
explicit StopInteractiveSession from the UI — there is no client-disconnect
or shutdown sweep — so an abandoned chat (UI closed, navigated away,
crashed) kept its claude.exe (+ conhost) alive for the worker's whole
lifetime. Under a long-running autostart worker these accumulate to dozens
of orphaned child processes.

LiveSessionRegistry now tracks per-session activity (Touch on every output
line and user action) and exposes ReapIdleAsync, which stops sessions idle
past a timeout while skipping any with a turn in flight. IdleSessionReaper
(BackgroundService) sweeps every 5 min; idle timeout defaults to 30 min,
configurable via interactive_idle_timeout_minutes (0 disables).
2026-06-26 16:11:53 +02:00
Mika Kuns faf6104645 fix(worker): kill spawned claude trees when the worker dies
Spawned claude processes were only torn down on graceful cancellation
(Process.Kill(entireProcessTree)). A hard worker death — Task Manager
End Task, crash, OS restart, installer update — ran no cleanup, orphaning
the claude->node->conhost tree, which lingered and piled up across
restarts.

Assign every spawned claude process to a Windows Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The worker holds the only handle, so
when it terminates for any reason the OS tears down the whole job. No-op
on non-Windows; best-effort with a one-time warning if the Win32 calls
fail.
2026-06-26 16:11:53 +02:00
Mika Kuns afe7218b7c feat(ui): remove a queued interactive message with a ✕
Queued rows are now QueuedMessageViewModel (Text + RemoveCommand); each shows a
✕ (Icon.WinClose) that calls RemoveQueuedInteractiveMessageAsync(taskId, text).
The worker re-broadcasts the queue, rebuilding the strip without the removed
message. Adds session.composer.unqueue (en/de).
2026-06-26 16:11:53 +02:00
Mika Kuns fd1e38fb7f feat(worker): remove a queued interactive message
StreamingClaudeSession.RemoveQueuedAsync drops the first occurrence of a queued
message from _pending and re-broadcasts the updated queue. Wired through
InteractiveSessionService + WorkerHub.RemoveQueuedInteractiveMessage +
IWorkerClient.RemoveQueuedInteractiveMessageAsync. Removal by text (first match)
is robust to a turn flushing mid-click. Fakes + ILiveSession impls updated.
2026-06-26 16:11:53 +02:00
Mika Kuns 7c9ff18ced feat(ui): show queued interactive messages above the composer
A queued message now appears in a pending strip above the input box (driven by
InteractiveQueueChanged), not optimistically in the transcript. The transcript
user line is added on delivery via InteractiveMessageSent. SessionTerminalView
gains QueuedMessages/HasQueuedMessages styled props (Mission Control); WorkConsole
binds Monitor.* (task detail). Adds session.composer.queued (en/de).
2026-06-26 16:11:53 +02:00
Mika Kuns 84034e8395 feat(worker): broadcast interactive message queue + delivery
StreamingClaudeSession raises onQueueChanged (pending snapshot) and onUserMessageSent
(on delivery, incl. the seeded first prompt); InteractiveSessionService forwards these
as InteractiveQueueChanged/InteractiveMessageSent broadcasts. Lets the UI show queued
messages above the input and move a message into the transcript only when actually
delivered to Claude. Client events + fakes updated.
2026-06-26 16:11:52 +02:00
Mika Kuns 786eb2877f feat(ui): highlight user chat messages + opt-in interrupt (stop) button
LogKindForegroundConverter drives the log message foreground via a local
binding (beats the dim local value), so user messages render in the accent
color instead of vanishing into the transcript. Adds a small stop (Icon.Stop)
button next to Send in both composers (SessionTerminalView + WorkConsole) wired
to InterruptInteractiveCommand → InterruptInteractiveSessionAsync. Adds
session.composer.interrupt (en/de).
2026-06-26 16:11:52 +02:00
Mika Kuns bdda98eccd feat(worker): queue interactive messages by default, interrupt opt-in
StreamingClaudeSession now buffers a mid-turn user message in a FIFO queue and
flushes one when the turn's result arrives (no implicit interrupt). InterruptAsync
only writes the control_request (no-op when idle); the resulting turn-end then
flushes any queued message. New InteractiveSessionService.InterruptAsync +
WorkerHub.InterruptInteractiveSession + IWorkerClient.InterruptInteractiveSessionAsync.
2026-06-26 16:11:52 +02:00
Mika Kuns 1fe72a1fe2 feat(ui): interactive chat composer in the session terminal + work console
SessionTerminalView gains an opt-in composer (IsComposerVisible / ComposerText /
SubmitCommand / ComposerPlaceholder styled props); Mission Control binds it to the
monitor VM. Task detail's WorkConsole output tab gets a matching shell-prompt
composer bound through Monitor.*, shown only while an interactive session is live.
log-user lines render in the accent color. Adds session.composer.* (en/de).
2026-06-26 16:11:52 +02:00
Mika Kuns 140b8e1551 feat(ui): interactive chat composer state on the session monitor VM
TaskMonitorViewModel gains IsInteractiveLive + ComposerDraft + SubmitComposer
(optimistic LogKind.User echo, then SendInteractiveMessageAsync) + StopInteractive,
driven by the InteractiveSessionStarted/Ended events. Since DetailsIslandViewModel
embeds this monitor, both task detail and Mission Control get the composer. Mission
Control auto-creates a monitor on InteractiveSessionStarted. Adds LogKind.User.
2026-06-26 16:11:52 +02:00
Mika Kuns 9effddeb2c feat(ui): worker client surface for in-app interactive sessions
Adds SendInteractiveMessageAsync/StopInteractiveSessionAsync and the
InteractiveSessionStarted/Ended events to IWorkerClient + WorkerClient
(UI-thread dispatch mirroring TaskQuestionAsked). Updates the IWorkerClient
fakes in both test projects.
2026-06-26 16:11:52 +02:00
Mika Kuns 30e87e698e feat(worker): in-app interactive session service, replacing the wt terminal launch
InteractiveSessionService resolves a task's list working dir + seeded prompt,
spawns a StreamingClaudeSession (claude stream-json in the list dir, model+auto
as before), registers it in LiveSessionRegistry, streams output over TaskMessage,
and broadcasts InteractiveSessionStarted/Ended (an exit watcher fires Ended). The
hub's OpenInteractiveTerminalAsync now starts this in-app session; SendInteractiveMessage
and StopInteractiveSession route to it. The external Windows-Terminal interactive
launch (LaunchInteractiveAsync / InteractiveLaunchContext / OpenInteractiveAsync) is
removed; planning sessions keep their terminal launch.
2026-06-26 16:11:52 +02:00
Mika Kuns d8a043fae7 feat(worker): persistent streaming Claude session + live session registry
StreamingClaudeSession drives claude --input-format stream-json over a kept-
open stdin: sends user messages, interrupts the in-flight turn via the verified
control_request protocol, and tracks turn state from result events (treating an
interrupt-aborted error_during_execution result as turn-ended). IClaudeStreamTransport
abstracts the process I/O so it is unit-tested with a fake (no real claude).
LiveSessionRegistry maps taskId -> live session for the hub to route into.

Backs the upcoming in-app interactive sessions; autonomous task execution untouched.
2026-06-26 16:11:52 +02:00
Mika Kuns 917301d61c feat(ui): answer a running task's question inline in Mission Control
TaskMonitorViewModel surfaces a pending AskUser question (TaskQuestionAsked /
TaskQuestionResolved events) with an AnswerDraft + SubmitAnswerCommand that calls
the new IWorkerClient.AnswerTaskQuestionAsync; MonitorPaneView shows an accent
question banner with an input box above the terminal. Pending question is cleared
on answer/resolve/finish and re-hydrated on attach via GetPendingQuestionAsync.
en/de localization for missionControl.question.*; test fakes updated.
2026-06-26 16:11:52 +02:00
Mika Kuns c7f8280106 feat(worker): AskUser MCP tool so a running task can ask the user mid-run
A running task can call mcp__claudedo_run__AskUser(question) to block (up to 3
min) on a human answer. PendingQuestionRegistry holds the pending question +
TaskCompletionSource; the tool broadcasts TaskQuestionAsked, awaits the answer
(WorkerHub.AnswerTaskQuestion resolves it), and returns it as the tool result —
or a 'proceed on your judgment' fallback on timeout. The run stays Running
throughout (no status/schema change). ClaudeProcess raises MCP_TOOL_TIMEOUT so
the 60s HTTP-MCP cap doesn't kill the wait; the run MCP is now wired for every
task, not just standalone ones. System prompt updated to reconcile 'unattended'.
2026-06-26 16:11:51 +02:00
Mika Kuns bec26b2232 feat(ui): replace OLE task-row drag with custom ghost drag
Task rows now drive a hand-built pointer-capture drag instead of
DragDrop.DoDragDropAsync: armed on press, begins past a 4px threshold so a
plain click still selects. The ghost follows the screen cursor across windows;
on release the action is decided by what is under the cursor -- over the
Mission Control window queues the task (geometric DragHitTest, no OLE drop),
over another row in the same user list reorders, anywhere else cancels and
restores the row. Drag starts from any list kind (drag-to-queue everywhere)
but reorder-on-drop stays gated on CanReorder. Removes the obsolete OLE
TaskRowFormat path from both the source and MissionControlView (pane
PaneFormat reorder is untouched).
2026-06-26 16:11:51 +02:00
Mika Kuns 05aec8ebfa feat(ui): ghost-window drag infrastructure for task rows
Add the borderless, transparent, topmost, click-through DragGhostWindow that
hosts a tilted (~-6deg) translucent snapshot of the dragged row, a
TaskDragController that owns its lifecycle (snapshot -> show -> follow -> close),
and a pure DPI-aware DragHitTest helper (unit-tested) for the cross-window
screen hit test. Adds the TaskRowViewModel.IsDragging flag and the
'grabbed' Border.task-row.dragging style (lift + scale + lower opacity +
shadow). Not yet wired into the drag source.
2026-06-26 16:11:51 +02:00
Mika Kuns 3b629c218f feat(ui): drag a task into Mission Control to queue it 2026-06-26 16:11:51 +02:00
Mika Kuns 9eb54a0d2f feat(ui): read-only queue side strip in Mission Control 2026-06-26 16:11:51 +02:00
Mika Kuns 1c94fbdb14 feat(worker): batch MCP tools for the external endpoint
Add seven best-effort batch variants of the single-entity external MCP
tools: batch_get_tasks, batch_add_tasks, batch_update_task_status,
batch_cancel_tasks, batch_delete_tasks, batch_set_my_day, and
batch_cleanup_task_worktrees. Each loops the existing ExternalMcpService
methods sequentially (scoped DbContext is not thread-safe), returns a
per-item result array so a failing item never aborts the rest, and
rejects empty or over-100-item batches. Merge/review stay single-task.
2026-06-26 16:11:51 +02:00
Mika Kuns 7f4dc8b973 feat(ui): open Settings from the Mission Control header 2026-06-26 16:11:51 +02:00
Mika Kuns f6ecfc995f feat(ui): drag-reorder Mission Control panes by their header 2026-06-26 16:11:51 +02:00
Mika Kuns f63be285a2 fix(ui): scroll revealed task into view + stronger selection highlight 2026-06-26 16:11:51 +02:00
Mika Kuns e2fad88f37 feat(ui): mission control pane header actions + status tinting 2026-06-26 16:11:51 +02:00
Mika Kuns fbcffce79c feat(ui): mission control detach/redock toggle, clear review panes, reorder helper 2026-06-26 16:11:51 +02:00
Mika Kuns 5f6e7480f2 feat(ui): detach a monitor into its own window 2026-06-26 16:11:51 +02:00
Mika Kuns b1bd91292f feat(ui): open Mission Control from the title bar 2026-06-26 16:11:50 +02:00
Mika Kuns 283310a3fd feat(ui): add MissionControl window + grid 2026-06-26 16:11:50 +02:00
Mika Kuns 15a3e65508 feat(ui): add MonitorPaneView 2026-06-26 16:11:50 +02:00
Mika Kuns 5a21d673c1 feat(ui): reveal a task by id from anywhere 2026-06-26 16:11:50 +02:00
Mika Kuns 42da840066 feat(ui): add MissionControlViewModel 2026-06-26 16:11:50 +02:00
Mika Kuns aa7a49f634 feat(ui): extract TaskMonitorViewModel streaming core; DetailsIsland delegates 2026-06-26 16:11:50 +02:00
Mika Kuns 7b6a8f0852 refactor(ui): split LogLineViewModel into its own file 2026-06-26 16:11:50 +02:00
Mika Kuns d00899b655 style(ui): use gear icon for the lists settings button 2026-06-26 16:11:50 +02:00
Mika Kuns 66907d24c9 fix(settings): persist Online Inbox tab on settings save 2026-06-26 16:11:50 +02:00
Mika Kuns 38defee3d8 feat(ui): collapse parent task rows by default with granular row sync 2026-06-26 16:11:50 +02:00
Mika Kuns 178fd25b55 fix(ui): paint accent buttons with moss tokens instead of Fluent blue
Button.accent set Background on the control, but Fluent's built-in accent button
paints the ContentPresenter with SystemAccentColor (blue) at higher specificity,
so the moss intent never showed (e.g. the Approve & Merge button rendered blue).
Override at the /template/ ContentPresenter level for rest/hover/pressed with the
moss accent tokens, matching the ListBoxItem overrides already in App.axaml.
2026-06-26 16:11:50 +02:00