Skip to content

Doors: progressive tool disclosure + agent hardening + web-pane migration completion#132

Merged
kordless merged 63 commits into
canaryfrom
mcp-tool-doors
Jul 3, 2026
Merged

Doors: progressive tool disclosure + agent hardening + web-pane migration completion#132
kordless merged 63 commits into
canaryfrom
mcp-tool-doors

Conversation

@kordless

@kordless kordless commented Jul 3, 2026

Copy link
Copy Markdown

Doors: progressive tool disclosure + agent hardening + web-pane migration completion

~65 commits. Three arcs:

1. Doors — progressive disclosure across both tool surfaces (epic #15)

  • Ghost agent: 11-tool core + 8 category doors, LRU cap (20 local / 24 cloud), auto-open on closed-door calls, door names callable in the Ollama structured-output enum. Live-verified: 11 tools/6.1KB -> 18/11KB only when needed (was 58KB flat).
  • External MCP (Phase 4): opt-in via config.agent.mcp_tool_doors (default OFF - external agents keep the full catalog). Filtering at list/call interception, process-global DoorState, byte-identical when off.
  • Phase 5: tools/listChanged rides the call's own SSE response (verified against rmcp transport); docs/doors.md.
  • Agent split/new targets the FOCUSED pane, not the caller's own pane/tab — 'split in your tab' lands elsewhere #119 fixed: omitted window/tab/pane -> focused/active defaults; terminal_new_tab window arg; +6 unit tests (doors suite 32/32).

2. Built-in agent (ghost) overhaul

  • OpenAI /v1/responses support (gpt-5-codex family), provider-true error labels, model registry (models.rs) ending 3x-duplicated defaults + stale claude-opus-4-7 catalog, cheap/fast defaults, installed :cloud ollama tags auto-offered.
  • Ollama path: live thinking streaming (was ~60s silent), single-generation (ends 3x VRAM contention), required reply field + tool-turn silence (self-talk fix), raw-content fallback hardening.
  • Context: 8k budget engages for small models, hard_trim_to_budget (LLM-free overflow guard), Token Maximus config UI + disabled-means-off gate on the extract path.
  • Behavior contract: full-send (consent layer prompts the human, agent never double-asks), act-in-turn doors, anti-fabrication (no tool result = no "Done"), open_web_pane routing, \r TUI submit, runaway-pane cap, fast Escape mid-generation, host-OS anchor.
  • /api/ghost/debug telemetry + headless driving via /api/ghost/chat.

3. Web panes + UX

  • Phase 6: legacy path fully removed (webviewTag off, guest handlers gone, -6 lint errors); WebContentsView manager owns everything.
  • Live-uid resolution fixes invisible context menus after pane adoption; mouse-out freeze-swap flash dwell-gated; agent-tab bell/flash reuses the standard bellMarkers + ringBell on title tick.
  • Hyperia Agent: dedicated deduped labeled tab; picker W/S/A hotkeys + remembered defaults + version/update row; /guide start page; omnibox DDG fallback; agent-config rework (key [set]/remove, Token Maximus, edit-config code sticky, service ports); shell HUD (TPS max/avg incl. usage-delta measurement, badges, model lights).
  • Dev-loop hardening: electronmon ignores renderer outputs (ends the 25-boot launch storm), single-instance hard-exit, scripts/dev.ps1 verified-clean launcher.

Known follow-ups (tracked): web-pane Phase 3 remainder (#9), web-pane pulse (#13), Sailfish-as-Maximus, agent-attributed shell turns (plan/agent-to-agent-shell.md), pane editor (plan/pane-editor-spec.md).

Pre-existing test debt unchanged (3 env-dependent failures in bootstub/compressor/fsnav); doors suite 32/32, bridge tests green, tsc clean, eslint improved.

🤖 Generated with Claude Code

Kord Campbell and others added 30 commits July 2, 2026 00:31
Full code-mapped plan: 57 ghost + 67 MCP tools -> core + doors (<=20 live),
DoorState (LRU, cap, auto-open-on-call), ghost loop wiring, identity-keyed
external MCP gating (flagged, compat default), 8k token budget, Sailfish
answers Q1-Q5 with file:line evidence, 6 tested phases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 baseline instrumentation, no behavior change.

- ghost run_loop: log per-iteration effective tool count + serialized
  schema byte size (target=doors) just before the provider stream call.
- mcp: hand-write ServerHandler call_tool/list_tools/get_tool verbatim
  from the #[tool_handler] macro so list_tools can log the full tool
  surface count + serialized bytes. Behavior is byte-identical to the
  macro; gating arrives in Phase 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1: new pure, unit-tested sidecar/src/doors.rs shared by both tool
surfaces (registered as `mod doors;` in main.rs). No wiring yet.

- Door taxonomy (plan §3): a single unified DOORS catalog where each Door
  carries a ghost_tools and an mcp_tools slice; surface-specific doors leave
  the other slice empty. GHOST_CORE (11) + 8 ghost doors partition all 57
  registry tools exactly; MCP_CORE (12) + 9 MCP doors cover the router.
- DoorState: LRU-ordered open Vec (front=oldest), live-tool cap (default 20,
  env HYPERIA_TOOL_CAP), enabled flag. open_door -> evicted list (never evicts
  the door being opened), touch (tool -> MRU), close_door, door_of lookup.
- 17 unit tests: cap enforcement, LRU order + eviction, single-oversized-door,
  touch/reopen MRU, per-surface partition, every door <= 10 tools, and a
  registry cross-check asserting every ghost tool_defs name is in exactly one
  door or core.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire DoorState into the ghost registry + agent loop behind
HYPERIA_TOOL_DOORS (default off). Flag off = byte-identical tool surface.

- registry: tool_defs(provider, model, doors) — Some+enabled emits core +
  open_tools/close_tools/tool_search meta + only open doors' schemas;
  None/disabled returns the full catalog unchanged. open_tools/close_tools
  ToolDefs added. handle_tool_search is door-aware (open/closed hints +
  unlock instruction) when a DoorState is passed.
- agent: base tool_defs moved inside run_loop per-iteration; throttle tiers
  filter on top. open_tools/close_tools intercepted in the executor like
  tool_mount (gather-on-entry text). Closed-door call guard auto-opens +
  annotates. Bare door names accepted as open_tools aliases. touch() on
  every executed tool. DoorState threaded run->run_loop->session, reset in
  reset(). Doors-mode system prompt swaps the honesty paragraph.
- settings/api.rs passes None (own DoorState is a later phase).

Tests: 22 doors tests pass (cargo test doors); cargo build --release passes.
Flag-off count/order asserted identical to the full catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x 400)

Compat endpoints (llama.cpp/Sailfish, vLLM, Ollama) keep max_tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 of MCP tool-doors. Delete the is_small_ollama allowlist (doors replaces it), add config.agent.tool_doors=on|off|auto (default auto), slim the doors-mode system prompt for small/local models, send temperature:0 on OpenAI-compatible endpoints, and add a hard context-token budget guard in the compressor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eate

Closing a sibling pane (BSP reparent) or any layout reshuffle remounts the
WebPane component, which fired destroy+create and rebuilt the native view —
full page reload, agent chat log wiped. Renderer-driven destroys are now
delayed 400ms (view hidden meanwhile); a create for the same uid cancels the
teardown and reuses the live view, skipping loadURL when the URL is unchanged.
Window teardown stays immediate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…for now

Chat still cleared on pane open/close: layout collapses can re-key the term
GROUP, so the remount arrives as destroy(oldUid)+create(newUid) and the
same-uid reuse never fired. create() now ADOPTS a pending-destroy view in the
same window with the same URL (re-keyed to the new uid) — page state survives.
Known follow-up: webContents handlers still capture the old uid, so pushed
title/loading state can go stale after adoption until next navigation.

Ferricula recall/remember disabled (empty url no-op) behind HYPERIA_FERRICULA=1
until the service returns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire Sailfish (local OpenAI-compatible llama.cpp CUDA endpoint) as a
first-class ghost provider per nuts.services/sailfish/HYPERIA_INTEGRATION.md.

- provider.rs: "sailfish" arm in AnyProvider::from_config → OpenAIProvider;
  provider_name() reports "sailfish" via a provider_label field carried from
  config.provider. Documented the no-timeout client for ~120s warmup.
- ghost/mod.rs load_config: sailfish is LOCAL (no api_key required, never
  falls back to ollama on empty key); default endpoint built from
  config.agent.services.sailfish.port (default 22343); default_model fallback
  "gemma4-e4b" (guide's id; /v1/models is authoritative).
- doors.rs: is_small_model classifies provider=="sailfish" as small (cap 20);
  added unit tests.
- agent-config.html: enabled the Sailfish provider button (cat:'sailfish'),
  CHEAP_PICK matches first model, warmup/down model note.
- ghost/api.rs get_agent_models: fresh (uncached) probe of sailfish
  /v1/models (2s timeout) injected as providers.sailfish {ok,default,models}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w_size

- The built-in agent rode a plain hyp_agent_ token and hit NeedConsent on
  every pane action — prompting the user to approve the thing they just asked
  it to do. Bridge now holds a trusted-token set (populated with the minted
  ghost token at startup; trust is by TOKEN, never spoofable name) and the
  three authorize_* short-circuit to Allow for it.
- Ghost catalog gains terminal_set_window_size (POST /api/window/size) in the
  terminal door — "resize to double width / height 1200" now has a real tool
  (tool_search correctly found nothing before; the tool did not exist).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…; local status lights

- Bridge trusted-token set: the minted ghost token bypasses authorize_drive/
  create/capability — the built-in agent stops asking the user to approve the
  actions they just requested. Trust by TOKEN, never name.
- terminal_set_window_size added to the ghost catalog (terminal door) — the
  "resize" ask now has a real tool.
- Config pane: live status lights on Ollama (daemon probe) and Sailfish
  (OpenAI-compat /v1/models probe) provider buttons — green answering, red not.
  Refreshed on the 5s services poll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + local status lights

- Repeat guard now also trips when the SAME tool+input fires 3x recently even
  if the output differs — terminal_split with no args spawns a new pane each
  call, so output never repeated and a small model (gemma4) split forever. The
  3rd identical call now gets the "you are repeating, stop" system note.
- Expanded tool rows in the shell show the FULL call: pretty-printed input args
  + output (was output-only, input just a compact label).
- Config pane: green/red status lights on Ollama + Sailfish provider buttons
  from the live endpoint probes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The sidecar/providers/level system=> lines moved into printBootBanner(),
  called only from boot() and guarded by bannerPrinted — re-probes (set-model,
  wipe) refresh the status strip silently instead of re-spamming the banner
  every config change.
- Local-provider detection now covers Sailfish: active provider shown first;
  sailfish reachability from the /api/agent/services probe (not just ollama).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… shell

- Renderer reports WindowBounds (OS pixel w/h/x/y) to the sidecar on window
  create + resize; cached per window id and attached to each window node in
  terminal_status. The agent can now answer "how big is the window" and resize
  relative to it (it already knew its own window/tab/pane).
- Shell HUD gains a live tok/s readout (output tokens over decode time, clock
  starts on first token so warmup doesn't drag it down).
- Click anywhere in the transcript focuses the input — but not when selecting
  text (drag-selects leave a selection → skipped) or clicking a link / button /
  input / expandable tool row / widget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ready-to-show fired before the sidecar WS was up, so terminal_status had no
window size. Now bounds also ride the focus handler (fires at launch) plus two
short delayed sends as a safety net.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Window create/focus/resize bounds sends fired before the sidecar WS
finished connecting (connect is the last step at launch), so send()
no-oped and terminal_status never learned window pixel size. Replay
bounds for all windows in the ws 'open' handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diagnostic tracing served its purpose; window pixel size is verified in
/api/status. Removed to keep the log clean (it fired on every move/resize).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lick

Opens the Hyperia shell (/shell agentic surface) in a new web-pane tab.
Web pane: native context menu emits 'open web pane req' on the window rpc.
Terminal: pane-band menu reuses the module-level openUrl() localhost path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The terminal body pops the main-window contextmenu.ts menu, not the
pane-band menu — add 'Open Hyperia' there via the existing
pane:openShellPane command so it appears when right-clicking a terminal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ext overflow + open_tools logging

Four related fixes for the "which model am I talking to / it 400'd" confusion:

- Titlebar model lights: a blinking dot per enabled model (sailfish/ollama
  from probes, frontier from keycheck), the active agent provider marked +
  brighter. Hybrid mode runs more than one brain; now you can see which.
- HUD tok/s split into t/s max (peak) and t/s avg (running, folds the live
  turn in so it settles as it streams). Reset clears both.
- Provider errors name the ACTUAL provider + host, not "OpenAI" — Sailfish
  rides the OpenAI code path, so an 8k-ctx llama.cpp 400 read as "OpenAI
  error", making a local model look like a cloud one.
- Dynamic context handling: local/small models (Sailfish/Ollama) default to
  an 8k window when config.agent.context_tokens is unset (was 0 = no trim →
  "exceeds context size" 400). Output reservation scales down on small
  windows. New hard_trim_to_budget() guarantees a mechanical fit even when
  the Ollama compressor is down or its summary fails (no more full-history
  fallback into a bounded window); strips orphaned tool results so the trim
  can't 400 the provider.
- open_tools/open_door_result now logs (target=doors): the requested door,
  unknown-door failures, and success with live/cap counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bug telemetry

- Runaway-pane guard: hard-cap view-creating tools (open_web_pane,
  terminal_new_tab/new_window/split) at 4 per user message. Each open is a
  fresh side effect so the identical-output repeat guard never tripped — a
  small model could open panes forever. Past the cap, refuse the side effect
  and nudge it to use existing panes / finish / ask.
- t/s HUD: decode meter now counts thinking-delta tokens too, not just final
  text. Thinking-heavy local turns produced sparse text_deltas so max/avg read
  ~0; shared accrueDecode() fixes both stats.
- /api/ghost/debug: per-turn telemetry ring buffer (provider, model, endpoint,
  in/out tokens per turn + total, decode tps, context budget, tools, stop
  reason, panes opened) + configured provider/model/doors. Lets a dev/agent
  troubleshoot the run loop without the human relaying the shell — pairs with
  the existing SSE /api/ghost/chat to drive + observe the agent headless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A small model reading raw width/height/x/y confused the y-offset for the
height ("1267 wide and 238 from the top" — 238 was y, height dropped). Add
an unambiguous `size` string ("1267 px wide × 783 px tall, positioned at
x=.. y=..") it can parrot; raw fields stay for resize math.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/ *-deep-research

Those models 404 on /v1/chat/completions ("Use the v1/responses endpoint
instead"). Route responses-only models to /v1/responses with the correct
request shape (flat tools, top-level `instructions`, `input` items with
function_call/function_call_output correlated by call_id, `max_output_tokens`)
and a typed-event SSE parser (response.output_text.delta,
function_call_arguments.delta keyed via item_id→call_id, response.completed
usage, response.failed/error). Base chat + standard reasoning models
(o1/o3/o4-mini) keep chat/completions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…testing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thful hybrid

models.rs is now the single source of truth for model knowledge. It ends the
drift that let a stale claude-opus-4-7 sit in the picker and claude-sonnet-4-6
be duplicated as the anthropic default in THREE places (ghost/mod.rs,
bootstub.rs, provider.rs):

- defaults per provider (anthropic bumped to claude-sonnet-5), curated ollama
  allowlist, responses-API routing (codex/-pro/deep-research), max_tokens vs
  max_completion_tokens keying, small-model classification (doors.rs now a
  shim), 8k default context for small models, compressor default model.
- capabilities now serves `model_defaults` — shell.html picker reads it
  instead of hardcoding model ids (skips providers without a default).
- catalog refresh: claude-opus-4-8 / claude-sonnet-5 / haiku-4-5 (drop 3-7),
  add gpt-5 + gpt-5-codex, o3-mini/o1 -> o4-mini.
- tool-harness slim for small models (slim_tool_defs): descriptions capped to
  first line, per-property schema descriptions stripped (structure/enums/
  required kept), open_tools door menu exempt. Frontier keeps full prose.
- hybrid greeting now truthful: names the ONE answering provider/model; local
  models are aux (Maximus compression), not a parallel brain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cap_not_breached_by_a_single_fitting_door hardcoded core=11 + terminal=9;
terminal_set_window_size made the terminal door 10. Size the cap from the
actual door definition so taxonomy growth can not silently break it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ornith:latest emitted valid schema JSON (thought + tool_name:"none") but
legally OMITTED reply — it was not in the required list — so the shell
showed thinking followed by pure silence. Require reply in the tools-mode
format schema (empty string allowed for tool turns), and belt-and-suspenders:
a no-tool turn that still arrives with an empty reply promotes its thought
to the visible reply instead of saying nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nswering turn

Making `reply` required (the ornith-silence fix) over-corrected: small models
now fill it with self-narration on every tool turn ("I am checking the
status..."), and the provider emitted any non-empty reply as visible text per
turn — a 4-turn status poll rendered as the agent talking to itself. Repro
also caught it parroting the compressor's synthetic "Context noted." message.

- Visible reply (and the thought-promotion fallback) now emit ONLY on
  non-tool turns; reasoning stays in the thinking row. Narration also stops
  accumulating in history, which starves the mimicry loop.
- Schema description hardened: reply MUST be "" when calling a tool, never
  tool narration, shown to the user verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'quick small notes' scale opened brand-new stickys at ~117x104 px —
ridiculously small. New stickys now open at the user's default size
(defaults.json, currently 466x417), and a hard floor (220x170) guards
against tiny opens from corrupt persisted geometry or tiny explicit sizes.
Restored notes keep their exact saved dimensions as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…num + display fixes

- Live streaming: single-generation stream:true path streams native
  message.thinking deltas to the shell AS THE MODEL GENERATES (was: ~60s of
  silence, then a fake word-by-word replay). Structured JSON content is
  accumulated and parsed at the end with the same validation. One generation
  instead of 3 parallel candidates also ends the VRAM contention on 12GB
  cards; the parallel-candidate path remains as transport-failure fallback,
  and a parse failure reuses the raw content instead of re-generating.
- Fast stop: stop_requested is honored per provider event mid-generation —
  Escape lands in seconds, not at the next turn boundary.
- Doors on the enum: door names are callable entries in the structured-output
  enum (agent.rs already aliases a bare door-name call to open_tools). The
  enum IS a structured-output model'\''s tool universe — without doors in it,
  "split the pane" dead-ended in "I don'\''t have terminal_split".
- Host anchor: system prompt names the real host OS/arch (ornith greeted a
  Windows user with a "2019 Apple Silicon MacBook Pro").
- Shell display: tool-row summaries are mouse-selectable (click-toggle skips
  when a selection exists), terminal-screen outputs collapse blank-line
  floods, and a truncated structured reply extracts the reply text to
  end-of-string instead of leaking a raw JSON fragment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kord Campbell and others added 28 commits July 2, 2026 15:37
…l config link

- The in-box "enter" badges are now the hotkey letters: URL box shows "w",
  shell/agent boxes show "s"/"a" (falls back to "enter" when no hotkey).
- Latest-version fetch accepts ONLY a strict version string — the endpoint
  can return an error page/file content today, which rendered as raw garbage
  (a stray Rust line) in the picker footer.
- Hyperia shell status strip gains a "config" item linking to /agent/config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dges carry the hint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lick menus

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lue flash, green lights, activity-based t/s avg

- Titlebar reads "hyperia shell" (rendered HYPERIA SHELL), no separator.
- Gear button next to reset opens /agent/config (status-strip config item
  removed in its favor).
- HUD metrics are bordered badges; a value change flashes the badge blue.
- Model lights are green only: steady green = enabled, pulsing green = the
  active answering model (no accent-colored blink).
- t/s avg measures ACTIVITY time only: the live window ends at the last
  token delta (not now()), and a >3s delta gap folds the burst and starts a
  fresh window — tool waits and idle no longer decay the average.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shell: HYPERIA SHELL title, gear-to-config by reset, badge metrics with blue
update-flash, green-only model lights, activity-based t/s avg (live window
ends at the last token delta; >3s gaps fold the burst — tool waits no longer
decay the average).

Defaults (models.rs): each provider now defaults to its top cheap/fast model
per user policy — claude-haiku-4-5, gpt-5-mini, gemini-3-flash-preview
(pulled from the live catalog). Picker still offers the full lists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…> code sticky

- Shell: HYPERIA SHELL title, gear-to-config by reset, badge metrics with
  blue update-flash, green-only lights, activity-based t/s avg.
- Defaults: top cheap/fast per provider - claude-haiku-4-5, gpt-5-mini,
  gemini-3-flash-preview (from the live catalog).
- Config page: "config.agent" is now a link that opens the raw config file
  in a code-mode sticky (syntax-highlighted, editable, bound to the file);
  get_agent_config serves config_path for it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One huge resize poisoned defaults.json (1313x1133) so every new sticky
opened huge. Resize persists per-note geometry only; the default stays
stable (reset to 480x420 on disk). saveStickyDefaultSize kept for future
explicit 'set as default' UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…button

- When a provider has a stored key: heading shows "API key [set]" and a
  remove button sits INSIDE the entry box on the left (sends the "-" clear
  sentinel, refreshes the keycheck dot). Placeholder flips to "key is set -
  paste to replace".
- Bottom button row gains "Open config" - opens the raw config file in a
  syntax-highlighted, editable code sticky (same as the config.agent link).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ve, no unconfigure

- Stored key: "API key [set]" heading, remove button INSIDE the entry box
  (flex-row box: wrapper carries border/bg, button + borderless input are
  children - cannot drift outside).
- "Open config" button opens the raw config in an editable code sticky.
- Save status is just "saved".
- Unconfigure removed everywhere: Hyperia is ALWAYS in the agent list and
  always configurable. Empty provider in POST /api/agent/config is ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ort under model; v0.15.12

- "Edit config" opens the config file in a syntax-highlighted code sticky
  via a dedicated system-side endpoint (POST /api/agent/config/edit) - the
  page fetch is anonymous so the general /api/notes create was silently
  identity-blocked. Fixed action: can only open THE config file.
- Button row: Back (left), Save, Edit config.
- Services: ollama row removed (its port now lives under the Model select
  when ollama is the provider, saved with Save); real default ports for
  shivvr (8085), grub (6792), transcription (8765); nuts.services note is
  now "WIP - allows use of nuts.services serverless services. Coming soon."
- Version bump 0.15.11 -> 0.15.12 for the local installer build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
….15.13

The route was registered in the GhostState section (E0308) so the 0.15.12
installer shipped WITHOUT the new sidecar - Edit config would 404 there.
Moved next to /api/notes (AppState). Bumped to 0.15.13; 0.15.12 exe on disk
is orphaned/do-not-ship.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toNavigableUrl treated ANY whitespace-free token as a host, so 'hello'
navigated to https://hello. Host rule now requires a dotted host or an
explicit host:port; bare words fall through to search. The open/split
web-pane rpc handlers also route through toNavigableUrl instead of naively
prefixing https:// (they bypassed the omnibox logic entirely). Unresolvable
dotted hosts still hit the web pane's did-fail-load -> DDG fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed to 'Picker'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Input first (flex:1, min-width:0 so it shrinks instead of pushing), button
last with white-space:nowrap and flex:0 0 auto - pinned to the right edge,
never wraps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… open

The native dropdown list is unstylable and square; the select box kept its
8px rounding while open, so the two shapes clashed. A tiny open/close class
toggle (mousedown/change/blur/Esc/Enter) zeroes the bottom radii only while
the list is showing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-turn

Live transcript: the agent said 'Open the terminal tool door for me and I'll
split it' — treating doors as human-gated, then self-diagnosed exactly the
right fix. Contract now states: doors are the AGENT'S to open (never ask,
never request permission — consent lives at the action layer); capability-
seeming-missing = tool_search -> open_tools -> ACT in the same turn, report
missing only after both fail; request-to-door routing hints (layout->terminal,
pages->web, notes->stickys); act first, commentary after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing)

During a streaming turn every token changed out/t-s, and each change
re-triggered the 450ms blue glow — the whole HUD strobed. Flash is now a
faint yellow border only (no box-shadow), throttled to at most one flash
per badge per 1.5s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt-attributed shell turns

Prompt fixes from live transcript failures (gpt-5-codex balking):
- FULL SEND default: never ask permission for routine actions (the consent
  layer prompts the human itself — asking first is double-asking); confirm
  only destructive/irreversible ops. Finish the WHOLE request before replying.
- Show pages with open_web_pane, never start/system browser (HN went to
  Chrome instead of a web pane).
- Typing into TUI panes: submit is a separate \r — trailing \n does not
  submit in Ink (agent typed into claude pane and never pressed Enter).

plan/agent-to-agent-shell.md: design for a second, agent-attributed entry
line in the shell when an external agent talks to the ghost (broadcast bus +
/api/ghost/events SSE fan-out + typing animation). Plan only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…broke the literal)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…D icons, remove-button vertical fix

- Prompt: NEVER claim done without the tool result this conversation — repro
  showed gpt-5.3-codex calling terminal_status then replying 'Done.' with
  zero split; if the tool isn't live, next action MUST be open_tools/search.
- HUD: t/s -> TPS labels; TPS now also measured from API usage deltas per
  stats event (cloud tool-call turns stream no visible text, so char-count
  TPS read 0 on codex). statsLastMs declared + reset.
- Gear is a bare emoji (no button chrome); reset is a red X with 'reset'
  hover tooltip.
- Config: remove button vertical mis-align root-caused — global
  button{margin-top:20px} margin box shoved it to the bottom of the flex row;
  margin:0 + align-self:center inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hell-pane menu trim, full-row service expand

- web-pane-manager: pane adoption re-keys the map but wireWebContents
  closures captured the ORIGINAL uid — every lookup went stale after a React
  remount, so the context menu (and zoom keys, find results, nav state,
  window-open splits) silently died in adopted panes. All handlers now
  resolve the live uid from the webContents (liveUidOf). This is why the
  Hyperia Agent pane had NO right-click menu.
- The agent-shell pane's menu omits the 'Hyperia Agent' item (it would only
  focus itself — the renderer already dedupes to one agent tab).
- Config services: the whole row header toggles expansion, not just the
  chevron.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Web panes are fully on native WebContentsView (app/web-pane-manager.ts),
so the <webview>-tag plumbing is dead code:

- window.ts: drop the entire did-attach-webview guest handler (session
  config, remote.enable(guest), guest context menu, guest
  setWindowOpenHandler with its web-pane-find / web-pane-open-split
  channels — all reimplemented in the manager and keyed by pane uid,
  not guest webContents id, which no longer exists)
- window.ts: drop webviewTag:true from BrowserWindow webPreferences and
  the now-unused ipcMain import; remoteEnable(window.webContents) stays
  (renderer pane/tab menus still use @electron/remote)
- web-pane.tsx: remove dead openOAuthExternal/_lastOAuthOpenAt (manager
  owns OAuth punting via will-navigate/will-redirect + window-open),
  the never-passed params/wc args + Inspect branch of handleContextMenu
  (in-page menu incl. Inspect-in-split lives in the manager), and the
  orphaned webview{} CSS rule; retire the stale "temporarily NOT
  wired" TODO — zoom keys / OAuth bail / focus ARE wired in main
- terms.tsx: drop the dead target.closest('webview') guard
- find-bar.tsx: comment accuracy (native view, not <webview>)

Live WebContentsView behavior is unchanged: navigation, find, zoom,
screenshot, context menu, OAuth hand-off, _blank splits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 4/5 of MCP tool doors, plus the #119 default-targeting fix.

Doors Phase 4 — opt-in progressive disclosure for the external MCP surface:
- config.agent.mcp_tool_doors ("on"|"off", default OFF) via new
  doors::resolve_mcp_door_config; HYPERIA_TOOL_DOORS / HYPERIA_TOOL_CAP override.
- MCP door state is process-global (rmcp stateless streamable-HTTP rebuilds the
  handler per request, so per-connection state is impractical — documented).
- list_tools filters to core + open doors and appends open_tools/close_tools/
  search_tools meta-tools; call_tool intercepts the meta-tools and auto-opens a
  closed door on a direct tool call (mirrors ghost/agent.rs). Doors off is
  byte-identical to the pre-doors server.

Doors Phase 5 — tools/listChanged:
- open/close/auto-open fire peer.notify_tool_list_changed(); capability is
  advertised in get_info only when doors are on. Verified it is delivered on the
  stateless HTTP transport (rides the tool call's own SSE response via
  OneshotTransport). docs/doors.md documents the model, config, meta-tools,
  auto-open, and the listChanged delivery nuance.

Fix #119 — post_split/post_new_tab default targeting:
- post_split now resolves the focused window's active tab's active pane even when
  window/tab/pane are all omitted (was passing a null uid, letting the renderer
  pick from divergent UI focus). Explicit-but-missing address -> 404; omitted with
  no sessions -> null (renderer bootstraps first pane).
- post_new_tab resolves a target window (focused if omitted) via new
  Bridge::resolve_window_id and passes windowId; terminal_new_tab gains a window arg.
- Unit tests for resolve_window_id and the MCP resolver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on agent-shell updates

Two user-facing fixes to web panes:

1. De-flash: the pane header's onMouseEnter/Leave hid+screenshotted the
   native WebContentsView on EVERY crossing (freeze-swap), so simply
   mousing out of a pane — or across it to the tab bar — flashed the
   page constantly. The swap now fires only after the cursor DWELLS on
   the header for 350ms (HEADER_HOVER_DWELL_MS), i.e. when the header's
   DOM tooltips actually need to paint over the page. Transient passes,
   mouse-out, and focus changes never touch native-view visibility.
   Real overlays (URL navigator, find bar, tab switch) are unchanged.

2. Background-tab notify for the Hyperia Agent pane: when a /shell web
   pane's page <title> changes (manager page-title-updated -> web-pane:state)
   while its tab is NOT active, raise the STANDARD tab bell — the same
   ui.bellMarkers 🔔/flash a terminal BEL sets (markTabBell), plus the
   same bell sound rung via a live Term's ringBell() (respects the
   bell=false config). Marker is keyed by the pane's term-group uid so
   it can never clobber a session bell; header.ts now counts group-uid
   markers for web leaves. Cleared when the human activates the tab
   (mirror of SESSION_SET_ACTIVE) or the pane unmounts. Never steals
   focus. NOTE: shell.html must tick document.title on completed turns
   for the signal to fire — reported upstream, page not edited here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bell/flash for a backgrounded agent tab

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kordless

kordless commented Jul 3, 2026

Copy link
Copy Markdown
Author

Ship it. 🚀

@kordless
kordless merged commit 82d2439 into canary Jul 3, 2026
2 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant