Commit Graph

78 Commits

Author SHA1 Message Date
Levi Neely 445a5232c2 gui: B3 plumbs without selecting; _ included in word chars
B3 (right-click) on a word now plumbs it directly without visually
selecting it first. Double-click word selection includes underscores,
so snake_case selects the whole identifier.
2026-10-08 18:03:21 +02:00
Levi Neely 53a085f034 gui: exclude _ from word delimiters
Double-click word selection now stops at underscores, so snake_case
identifiers select one segment at a time.
2026-10-08 18:00:05 +02:00
Levi Neely 8147b3885f gui: use TapHandler for double-click pair selection
MouseArea's onDoubleClicked doesn't fire when placed on a TextEdit with
selectByMouse:true — the TextEdit's internal mouse handling consumes the
events. TapHandler coexists with TextEdit's mouse handling and reliably
receives double-tap events.

Double-click on a bracket or quote delimiter now selects the content
between the matching pair. Quotes use parity to decide opener/closer
direction. Falls back to word selection when no delimiter is adjacent.

Works in chat blocks (mainTextEdit, textComponent) and prompt textarea.
2026-10-08 17:57:40 +02:00
Levi Neely 840723de3b gui: fix coordinate mapping for prompt textarea double-click
The MouseArea (chordArea) is a sibling of the ScrollView containing
promptInput, so mouse.x/y are relative to the parent Item, not the
TextArea. Map coordinates via mapToItem before calling positionAt.
2026-10-08 17:49:53 +02:00
Levi Neely 7cfd472193 gui: shared TextSelection.js for bracket/quote pair selection
Extract selectBracketPair, selectWordAt, and helpers into TextSelection.js
and import it in both ChatBlockDelegate.qml and ChatPane.qml. Prompt
textarea now has the same double-click behavior as chat blocks:
delimiter adjacent to cursor -> select pair content, else -> word select.

Quotes use parity to decide direction and scan document-wide, so
multi-line content between the pair is selected in full.
2026-10-08 17:47:05 +02:00
Levi Neely 794cecfa6c gui: quote-pair selection spans full document, not one line
Quote matching now scans the whole block text and computes opener/closer
parity document-wide, so content between a '' "" `` pair that spans
multiple lines is selected in full. The adjacent quote (left or right of
the cursor) sets the direction; the matching end quote is found by
scanning that way.
2026-10-08 17:42:50 +02:00
Levi Neely 4b2c14943d gui: do word select ourselves on double-click
The overlay MouseArea swallows the B1 double-click, so returning
mouse.accepted=false did not make the TextEdit perform its native
word select — a non-delimiter double-click selected nothing. Now the
handler selects the word directly via selectWordAt when no bracket or
quote pair is under the cursor, so double-click is deterministic:
delimiter -> pair select, otherwise -> word select.
2026-10-08 17:37:03 +02:00
Levi Neely 5170361e90 gui: fix chat block binding loop breaking rendering
The Item wrapper added in 1cfa424 set height: mainTextEdit.height while
the TextEdit used anchors.fill: parent, so the TextEdit height depended
on the Item height and vice versa. The resulting binding loop collapsed
the block layout into visual garbage. Size the TextEdit from
width: parent.width and let its content height drive the Item.
2026-10-08 17:33:41 +02:00
Levi Neely 86977b3ca3 gui: extend double-click pair selection to quotes
selectBracketPair now also handles '' "" `` quote pairs. Since a
quote char opens and closes with the same glyph, a quote to the right
of the cursor scans forward for its match, and a quote to the left
scans backward.
2026-10-08 17:30:34 +02:00
Levi Neely 1cfa424220 gui: bracket-pair selection on double-click
Double-clicking next to a bracket (()[]{}) selects the text between
the matching pair, including nested pairs. Falls through to the
default word-select when no bracket is adjacent.

Also: B3-click on word now highlights it before plumbing.
2026-10-08 17:23:16 +02:00
Levi Neely 36e8b0abd3 gui: fix B2/B3 mouse handling in editable TextEdit
Handle events in onPressed instead of onClicked so they work
with editable text. Remove propagateComposedEvents since we
want to intercept middle/right clicks completely.
2026-10-08 16:23:12 +02:00
Levi Neely b119668965 gui: B3-click on word expands to file path (acme-style)
RMB on text without selection now expands to the word at cursor using
acme's isfilec character set: alphanumeric + . - + / : @

This matches acme behavior for clicking on file paths like foo.go:42
2026-10-08 16:20:29 +02:00
Levi Neely 5bf29198ff gui: pass session cwd as plumb wdir for relative path resolution
Plumb messages now include the current session's cwd as wdir.
Added sessionCwd() helper to ChatPane and ChatBlockDelegate.
Updated plumber.plumb() to accept optional wdir parameter.
2026-10-08 15:44:39 +02:00
Levi Neely 5add387cc8 gui: suppress RMB context menus in prompt and chat blocks
B3 (right-click) now only plumbs selection when available — no fallback
context menu. Mouse chording (B1+B2 cut, B1+B3 paste) still works.
Removed contextMenuComponent and promptContextMenu entirely.
2026-10-08 15:40:31 +02:00
Levi Neely fa58b0f033 gui: make chat block text editable for scratchpad use
Remove readOnly from TextEdit components in ChatBlockDelegate.
Users can now edit text in-place — changes are local only, not persisted.
2026-10-08 15:35:37 +02:00
Levi Neely 9618c79232 gui: native 9P plumb client with edit receiver and mouse chording
Replace QProcess plumb binary shell-out with proper 9P2000 plumb client:
- ninep.h/cpp: minimal 9P2000 protocol client for plan9port plumber
- plumbmsg.h/cpp: plumb message pack/unpack wire codec
- plumber.h/cpp: full plumb client with:
  - socketPath() resolving $NAMESPACE/plumb or display fallback
  - send()/sendTo() write packed messages to send port via 9P
  - PlumbReader thread blocks on edit port, emits edit(file,addr,wdir)
  - startReader() starts the receive loop
  - available() checks plumb socket existence

Add Acme-style mouse chording to ChatBlockDelegate.qml:
- B1+B2 = cut (snarf)
- B1+B3 = paste
- Context menu now includes Cut/Paste with chord hints

Legacy plumb()/plumbTo() methods preserved as aliases to send()/sendTo().
2026-10-08 15:34:29 +02:00
Levi Neely 4774cb9fe9 gui: convert ProcManager to separate Window, sync to active session
- Convert from Dialog to Window (not modal, separate window)
- Title shows current session name
- Syncs to activeSessionId/activeAgentId changes in main window
- Header shows which session is being displayed
- Double-click row to view output
- Sort processes by PID descending (newest first)
- Clear selection when session changes
2026-10-08 14:57:25 +02:00
Levi Neely 491f5ff2ae gui: add 'Open Workdir' context menu for sessions and agents
- Add openDirectory() method using QDesktopServices::openUrl
- Session context menu: opens session cwd
- Agent context menu: opens agent's effective cwd
2026-10-08 13:38:29 +02:00
Levi Neely 01360b961d gui: add process manager dialog
- Add procStarted signal to Ollie9pClient for proc.start events
- Add listProcs(), procTerm(), procKill(), procOutput() methods
- Create ProcManagerDialog.qml showing background processes for
  the active session/agent with status, runtime, commands
- Add 'Procs' button to action toolbar to open the dialog
- Auto-refresh every 2 seconds when dialog is visible
- Support terminate, kill, and view output actions
2026-10-08 12:14:02 +02:00
Levi Neely ab4f0ef785 Revert "gui: remove cut/copy/paste from prompt input"
This reverts commit eb09a95a64.
2026-10-07 17:35:10 +02:00
Levi Neely eb09a95a64 gui: remove cut/copy/paste from prompt input
- Remove cut/copy/paste menu items from context menu
- Remove paste button (📋) from input row
- Remove B1+B2/B1+B3 chord handling for cut/paste
- Simplify mouse handling to just B2=exec, B3=plumb
- Standard Ctrl+X/C/V still work via TextArea defaults
2026-10-07 17:30:28 +02:00
Levi Neely f7a52088db per-agent cwd override with session-level inheritance
- Agent cwd is optional; empty = inherit session cwd (the common case)
- Session cwd is required at creation and is the inheritance root
- toolsrv maintains agentCWD map, resolved per call from agent= field
- Override set via agent cfg (cwd=...) or ctl (cwd [<dir>|-])
- Agent.SyncCwdToToolServer re-pushes on every (re)connect
- GUI NewAgentDialog shows '(inherit: <sessionCwd>)' as placeholder
- proc_test.go covers per-agent cwd isolation
- Kate and acme scripts updated for new session/agent creation flow
- AGENTS.md documents the architecture
2026-10-07 15:14:04 +02:00
Levi Neely 1c02b50add proc events, bypass shortcuts, Kate session fix
Backend:
- Add proc.start/proc.exit events for background process lifecycle
- Add proc idx ctl command for machine-readable process listing (TSV)
- Add event.pub file for external event publishing
- Add ListProcsIdx to toolclient and toolsrv
- Fix bypass commands to use Setpgid for process group isolation

GUI:
- Add configurable bypass approval shortcuts (Ctrl+Y/Ctrl+N default)
- Add Keyboard Shortcuts section to Settings dialog
- Store shortcuts in theme.conf

Kate:
- Fix 'Start Session Here' - use rdwr for session/new endpoint
2026-10-07 14:22:44 +02:00
Levi Neely 91f295dc2d gui: add activeSessionPaused property for reactive paused state
The isSessionPaused() Q_INVOKABLE didn't trigger QML binding
re-evaluation on pause/resume. New property with proper signal
makes ChatPane bindings reactive.

- activeSessionPaused property tracks active session's paused state
- Updated on refreshSessions() and switchAgent()
- ChatPane.qml uses property instead of function call
2026-10-06 15:23:44 +02:00
Levi Neely 4be78447fa gui: restart agent streams on session resume
When resuming the active session, restart chat stream so user
can continue working without switching agents.
2026-10-06 15:20:50 +02:00
Levi Neely 7cbaaed014 gui: gray state indicator when session is paused
Session paused state overrides agent state for the indicator.
Reacts to sessionsChanged signal for pause/resume updates.
2026-10-06 15:16:57 +02:00
Levi Neely af772e5e8b gui: use relative geometry for state indicator
Size and margins scale with row height:
- height: 30% of row height
- width: same as height (circular)
- left margin: 15% of row height
- right margin: 25% of row height
2026-10-06 15:13:01 +02:00
Levi Neely 8c207a0f3a gui: add right margin to state indicator dot 2026-10-06 15:12:00 +02:00
Levi Neely 64559de661 gui: add agent state indicator dot to session tree
Small colored dot to the right of agent name shows execution state:
- Green: idle
- Blue: thinking
- Orange: calling tool
- Gray: paused

Updates reactively via agentStateChanged event signal.
2026-10-06 15:10:12 +02:00
Levi Neely b25e5e2fc6 bypass: emit resolved event for cross-client coordination
Server emits session.{sid}.agent.{aid}.bypass.resolved with id and
action (approved/denied) when a bypass is resolved by any client.

GUI handles bypass.resolved events to clear the banner and pending
count when CLI or another client resolves a bypass request.

This allows CLI 'o sess approve' to clear the GUI banner automatically.
2026-10-06 14:54:55 +02:00
Levi Neely 150ca5dfdd gui: add pending bypass indicator to agent tree
Track multiple concurrent pending bypasses per agent in C++ backend:
- Add m_pendingBypasses QHash<QString,QSet<QString>> keyed by session:agent
- Add pendingBypassCount(sessionId, agentId) Q_INVOKABLE
- Add pendingBypassCountChanged(sessionId, agentId) signal
- Update resolveBypass to take agentId and remove from tracking set

SessionTree.qml: show ⚠ indicator left of agent name when the agent
has pending bypass requests. Uses Connections to refresh on signal.

ChatPane.qml: pass agentId to resolveBypass calls.
2026-10-06 14:47:55 +02:00
Levi Neely f9e6785add gui: hide bypass banner when switching away from the requesting agent 2026-10-06 14:41:09 +02:00
Levi Neely a269a6f790 gui: fix bypass banner - use activeAgentId not agentId in QML 2026-10-06 14:37:48 +02:00
Levi Neely 6dc9ffe3b1 gui: show bypass banner per-agent, not globally
- Changed event topic: session.{sid}.agent.{aid}.bypass.request
- Added agentId parameter to bypassRequested signal
- Filter bypass events to show only for the active agent
2026-10-06 14:32:30 +02:00
Levi Neely 59d07d3b4a fix bypass event payload truncation at newlines
The shell command in bypass requests contains embedded newlines.
Escape them as \n in the event payload, unescape in GUI.
2026-10-06 14:28:10 +02:00
Levi Neely ceec28c2b9 gui: show bypass banner for any session, not just active
The GUI is a global view - show bypass requests immediately
regardless of which session is currently selected.
2026-10-06 14:07:05 +02:00
Levi Neely 5882192ae9 gui: fix bypass banner not appearing until session switch
QML's property var change detection doesn't reliably trigger
binding updates when comparing to null. Added explicit bool
hasPendingBypass property that properly triggers visibility.
2026-10-06 13:56:28 +02:00
Levi Neely 86fbc90b43 server: remove statewait file, use event stream instead
The event stream with filtering replaces statewait:
- echo filter | rdwrs event

Removed:
- statewait file from agent namespace
- All non-historical references in docs and code

The state file remains for simple polling reads.
2026-10-06 12:44:02 +02:00
Levi Neely 021436bfbf gui: remove per-agent statewait stream, use server event bus
The server event stream already delivers state change events for all
agents. Removed the redundant per-agent statewait streamer - now agent
switching has no teardown/startup overhead for state monitoring.

- Removed m_state streamer entirely
- State updates come via event stream's session.{sid}.agent.{aid}.state events
- Only chat stream needs per-agent setup/teardown
2026-10-06 12:11:14 +02:00
Levi Neely f86c8d3cdc docs: update all references from eventwait to event 2026-10-06 12:08:35 +02:00
Levi Neely 25d7fbfc5b server: rename eventwait to event 2026-10-06 12:02:37 +02:00
Levi Neely adb08fc51d server: fix eventwait to use Stream instead of BlockOnce
EventValue was storing only the latest event and using hash comparison,
which caused events to be overwritten if they arrived faster than the
client could read them.

Now eventwait uses Stream mode with EventStream which delivers each
event as it arrives. Events won't be lost due to rapid arrival.
2026-10-06 12:01:26 +02:00
Levi Neely 70c00541df gui: poll bypass file on session switch
Events can be missed if GUI wasn't connected when they were published.
Now checkPendingBypass() reads the bypass file directly when switching
agents/sessions to catch any pending requests.
2026-10-06 11:51:27 +02:00
Levi Neely f2f1f80e4e gui: integrate bypass requests into chat UI
Server changes:
- Session tracks pending bypass request and exposes methods
- New 9P files: session/{sid}/bypass (read/write), bypasswait (blocking)
- Publish bypass.request events for GUI listeners

GUI changes:
- Handle bypass.request events from eventwait
- Show inline amber banner with command and cwd
- Approve/Deny buttons resolve via 9P

Desktop notifications still work in parallel for non-GUI usage.
2026-10-06 11:44:15 +02:00
Levi Neely 79738a4a39 gui: reduce stopWorker wait from 200ms to 10ms
The fid close should immediately unblock reads. Don't wait long.
2026-10-06 11:38:28 +02:00
Levi Neely 65014395b7 gui: fix slow agent switching by closing fid on stop
When switching agents, the old state/chat streams would block until
data arrived (up to 500ms for statewait, indefinitely for chat).

Now requestStop() closes the fid from the calling thread, which
unblocks the worker's blocking read immediately. This makes agent
switching instant instead of waiting for the read timeout.
2026-10-06 11:36:30 +02:00
Levi Neely 3655b16b4b gui: fix QThread crash in Lib9pStreamer::onWorkerFinished
QThread::~QThread() aborts if the thread is still running. The previous
code called delete unconditionally after a 100ms wait timeout. Now we
follow the same pattern as stopWorker(): check if wait() succeeded,
and if not, abandon the thread safely via deleteLater.
2026-10-06 11:29:26 +02:00
Levi Neely 3838078394 gui: fix compact button to send /compact command 2026-10-06 11:24:05 +02:00
Levi Neely 1dcc9638df gui: replace NativeStreamer with Lib9pStreamer
Switch streaming reads (chat, state, events) from subprocess-based
ollie-9p to native lib9p via worker threads. This eliminates:
- Process spawn/teardown overhead
- Subprocess failure modes
- Signal handling complexity

Lib9pStreamer uses QThread workers with blocking reads. On stop,
workers are given 200ms to exit cleanly, then abandoned (they'll
exit when the read completes or connection closes).

Also: make KRunner/Kate/KIO plugins optional in CMakeLists.txt
to fix build when KF6Runner etc aren't installed. Added
QT_DEFAULT_MAJOR_VERSION=6 to fix Qt6 detection with CMake 4.x.
2026-10-06 11:16:53 +02:00
Levi Neely cd76864f19 fix agent profile selection from GUI
Two bugs:
1. GUI sends 'agent=<profile>' but server only recognized 'profile='
   - Added 'agent' as alias for 'profile' in parseAgentNewRequest

2. Model dropdown showed cost columns appended to model names
   - /models format: backend<tab>model[<tab>in<tab>out...]
   - Parser took everything after first tab as model name
   - Now stops at second tab to extract just the model name
2026-10-06 10:42:35 +02:00