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.
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.
When reading statewait with the same state value, the server now waits
500ms instead of 5 seconds before returning. This makes state polling
much more responsive.
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.
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.
Background procs with bypass were hanging because the caller blocked on
<-startedCh waiting for the process to start, but bypass.Submit blocks
until approval. Now we signal started immediately when entering bypass
path (with Process: nil), so the agent sees the proc ID right away.
Also handle term/kill when proc.proc is nil — cancel the context to stop
the operation (e.g., abort a pending bypass request).
executeBypassDirect was missing the streamOut parameter, so background
processes running via bypass never had their output written to the proc
buffer. Now both bypass and sandboxed paths receive the streaming writer.
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
CreateEmpty now returns (session, created, error): if a session with the
given name already exists it is returned untouched instead of erroring,
and only a freshly created session receives the provided cwd/workflow/
variant. The o script no longer suppresses errors from session/new, so
real failures surface while re-creating an existing session stays a
no-op success.
Drop the OLLIE_KF5 CMake option and the entire Qt5/KF5 build branch;
delete KF5-only assets (99-ollie-kf5.sh, ollie-actions-kf5.desktop);
collapse all QT_VERSION_MAJOR and KTEXTEDITOR_VERSION_MAJOR conditionals
to the KF6 path in the KRunner, Kate, KIO, and GUI sources; update
Makefile, README, and docs. Verified: KF6 configure + full build of
ollie-gui, krunner_ollie, ollie_kate, kio_ollie.
Landlock cannot add rules for sockets (S_IFSOCK) or FIFOs (S_IFIFO).
Previously, attempting to add a path like /run/user/1000/wayland-0
would fail with EINVAL.
Now these special file types are detected and skipped gracefully.
The field is rejected by models that don't support thinking/reasoning
configuration. Check model name before setting the field; 'auto' and
unknown models skip it to avoid ValidationException errors.
Ollie is experimental and unstable; optimize for a clean minimal
codebase over preserving behavior. No backward-compatibility shims,
deprecation aliases, or legacy fallbacks — change things fully and
delete the old form. Standing preference, applied without asking, so
agent behavior doesn't need per-task course correction.
Make the namespace explain itself instead of requiring prior knowledge.
- ctl is self-describing: reading it (empty write) or writing 'help'
returns the valid verbs with one-line descriptions; an unknown verb
errors with the valid list. Refactor dispatch to an ordered []ctlCmd
carrying descriptions; drop the undocumented '.' alias and the
drift-prone hardcoded help verb. Keep 'i' (drop 'inject') for fast
injects. o's ctl usage now reads the live listing.
- Errors carry severity + remediation. backend.ClassifyError maps the
typed errors to transient/config/fatal with a one-line fix; the error
event renders [[[error:<severity>]]] and a 'remediation:' line so a
human knows whether to wait or intervene.
- Add a human status file: 'thinking · 12s', 'calling shell · 3s',
'idle' — distinct from the machine-facing raw state. Wire the TUI bar
to it.
- Bare 'o' shows an overview of running sessions/agents with status, so
you don't need to know any names to get oriented.
Tests: dispatch help/unknown/routing, ClassifyError severity table.
reasoningEffort (low|medium|high) is now the single human-facing control
for model deliberation. Backends that speak a discrete level (OpenAI,
OpenRouter, Copilot, Gemini, Kiro) send it verbatim; Anthropic, which
needs a numeric budget, translates via documented thresholds
(low=4096, medium=8192, high=16384). thinkingBudget remains an advanced
explicit override that wins when set.
- Add EffortLow/Medium/High, threshold consts, ValidReasoningEffort,
EffortThinkingBudget, and GenerationParams.ResolvedThinkingBudget.
- Anthropic: derive budget from effort, grow max_tokens above the budget
(Anthropic requires budget < max_tokens), and force temperature=1 when
thinking is enabled (both are Anthropic requirements).
- Validate reasoningEffort at config load so typos fail loudly instead
of silently no-opping.
- theo: drop explicit thinkingBudget/inflated maxTokens; reasoningEffort
high now implies the 16384 budget.
- Document thresholds and per-backend behavior in data/agents/README.md;
fix stale autoLoad/maxSteps references.
- Add unit tests for the mapping and Anthropic thinking behavior.
Theo is a security auditor reasoning about injection, TOCTOU races,
crypto misuse, and confused-deputy problems — adversarial edge-case work
that needs deep deliberation, not the low effort it was set to. Bump to
reasoningEffort=high and add thinkingBudget=16384 so depth carries over
to Anthropic extended thinking. Raise maxTokens to 24576 so it stays
above the thinking budget (Anthropic requires budget < max_tokens).
Reasoning effort is part of an agent's behavior definition, so set it
on all 14 profiles keyed to role (planning/review high, general medium,
lightweight assist low). Also rename the ThinkingBudget json tag from
the confusing bare "reasoning" to "thinkingBudget", distinct from the
reasoningEffort string knob. No config migration needed — no profile or
backends.conf used the old "reasoning" key.
The autoLoad name implied an automatic tool-loading path that no longer
exists; tools now come only from agent config plus the /tool_load ctl
command. Rename the AgentConfig.AutoLoad field (json autoLoad) to Tools
(json tools), rename LoadAutoLoadTools to LoadTools, and update all 14
agent JSON profiles and the tool-not-loaded error message.
Regression guard for tool-free model compatibility: an agent with an
empty tool set must produce requests with no "tools"/"tool_choice"
field. Covers OpenAI, OpenRouter, Ollama, and Anthropic.
GitSpawn class (Sep 2026): a repo's .git/config can set core.fsmonitor to
a command that executes on any git index refresh, silently and as the
user. Ollie never spawns git in its own plumbing (repo detection is
os.Stat), but model-run git inside a malicious repo would fire it.
- exec: force core.fsmonitor=false via GIT_CONFIG_* in sandboxed tool env
- turn: wrap repo AGENTS.md in <context> markers (untrusted data,
filtered by KDE chat rendering)
- AGENTS.md: document the no-git-in-plumbing invariant (lesson 16)
Session names like 'r7.20' caused 'duplicate session' errors because
tmux interprets dots as window.pane separators.
Fix: Use '=' prefix for exact match in all tmux -t arguments.
From tmux(1): 'If the session name is prefixed with an =, only an
exact match is accepted.'
Implement proper Plan 9/Acme mouse chording in the input area:
- B1 = select (TextArea default)
- B2 = execute selection as prompt
- B3 = plumb selection or context menu
- B1+B2 = cut (chord while selecting)
- B1+B3 = paste (chord while selecting)
Uses MouseArea overlay that detects when B1 is held while B2/B3
is pressed to trigger chord actions. Context menu items now show
the chord shortcuts (Cut B1+B2, Paste B1+B3).
Placeholder text shows the mouse action reference.
- Remove chatPane property from delegate (was shadowing id, causing binding loop)
- Remove contentArea MouseArea from inside Column (invalid anchor)
- Simplify hovered property to use only headerArea
- Edit button now copies to clipboard instead of trying to access chatPane
Add table rendering support to ChatBlockModel:
- isTableLine(): detect pipe-delimited table rows
- renderTable(): convert table lines to HTML <table>
- renderProse(): split prose into text/table segments
- incrementalAppend: handle table state transitions during streaming
Tables render with header row detection (before |---|---| separator)
and proper incremental updates as content streams in.
Add per-model pricing to /models output. Format:
backend<tab>model[<tab>in<tab>out<tab>cache_read<tab>cache_write]
Pricing sources:
- Anthropic: hardcoded from official pricing, includes cache rates
- OpenRouter: parsed from API response pricing field
- Other backends: static lookup table fallback, marked with (e)
Changes:
- backend: Add ModelPricing/ModelInfo types, ModelLister interface,
static price table (Claude, GPT, Gemini, DeepSeek), LookupStaticPricing()
- openai: Parse pricing from API, implement ModelsInfo()
- anthropic: Implement ModelsInfo() with hardcoded cache rates
- fs/cache: Use ModelsInfo when available, fall back to static lookup,
format prices per 1M tokens with (e) suffix for estimates
- agent/cost: Use shared LookupStaticPricing instead of duplicate table
Add a ▶ button next to the text area for mouse-based submission.
Extract shared doSubmit() on the input RowLayout. Button uses
focusPolicy: Qt.NoFocus so it doesn't steal focus from the TextArea.
Replace MouseArea overlay with TapHandler inside TextArea.
The MouseArea sat on top and intercepted left-button press events,
blocking click-and-drag text selection. TapHandler cooperates with
the TextArea's built-in selection handling.
The old word-boundary check falsely triggered when tool names appeared
in arguments (e.g., git commit messages mentioning native tools).
New logic: split on shell separators and only flag when a tool is the
first token of a sub-command or a path ending with /toolname.