lua,ui: turn-boundary observability for supervision plugins - #876
Open
Firaenix wants to merge 3 commits into
Open
lua,ui: turn-boundary observability for supervision plugins#876Firaenix wants to merge 3 commits into
Firaenix wants to merge 3 commits into
Conversation
Plugins that need a picker rebuild one from scratch: a floating window,
hand-rolled fuzzy matching, their own key handling and section headers.
The result never quite matches the built-in pickers, and every plugin
pays the cost again. Meanwhile the real thing - the ratatui ListPicker
that /model and friends render with - already does filtering, sections,
scrolling, paste, theming and keybinds, and only lacks a way for a
plugin to feed it rows.
`maki.ui.picker(items, opts?)` opens exactly that component, populated
with plugin-supplied rows, and answers when the user decides:
- items: `{ label, detail?, suffix?, section?, highlighted?, data? }`
per row - the same axes PickerItem gives the native pickers. `data`
is an opaque JSON value echoed back on selection.
- opts: `title`, `initial` (label to preselect), and `keys` - extra
keybinds (`{ key = "R", hint = "refresh" }`) rendered in the footer.
Uppercase by convention, matching the model picker's shift-only rule,
so lowercase letters keep feeding the fuzzy filter. Pressing one
answers `{ key = "R" }` so the caller can act and reopen.
- Answers: `{ index, label, data }` on Enter (index is 1-based into
the caller's array), `{ key }` on a registered key, and
`(nil, "cancelled")` on dismissal - including when the overlay is
closed from outside, so the waiting coroutine never parks forever.
Plumbing follows the existing patterns: a `LuaPicker` component wraps
`ListPicker<Entry>` the same way ModelPicker does and joins the overlay
set; the request rides a new `UiAction::Picker` through the same
round-trip `maki.model` uses. ListPicker itself learns one small trick:
`set_footer_line`, a prebuilt footer for pickers whose hints are only
known at open time (the existing `with_footer_builder` is a plain fn
pointer and cannot capture the caller's keys).
With `maki.model.info`/`available`/`set`/`refresh`, a complete model
popup is now: list specs, map to rows with price hints, open the
picker, switch on the answer.
Firaenix
force-pushed
the
pr/plugin-observability
branch
2 times, most recently
from
August 27, 2026 18:30
a2d31e5 to
f20de63
Compare
Everything a plugin needs to supervise or route the agent's work without forking a builtin, in one connected change: Model catalog. Discovery persists to an on-disk cache and replays at startup; Anthropic /v1/models metadata (max_input_tokens, max_tokens) flows into ModelInfo; pricing distinguishes billed cost from list price for subscription-subsidised providers. Lua reads it all through maki.model.info(spec) and kicks re-discovery with maki.model.refresh(). Reviewer chain. maki.api.register_reviewer installs a chain link that classifies any tool call which would otherwise prompt the human: ALLOW runs it, DENY blocks it with the reason fed back to the agent, ASK escalates to the next link and finally the human. A link is either a model judging against policy text, or a plugin handler function that computes the verdict itself (rulebooks, quotas, external approval systems), receiving the decoded input, the derived permission scopes (for bash: the treesitter-parsed command segments), parseability, cwd, the last user message, and the attempt history for repeated calls. Model links fence everything as untrusted data with injection attempts called out as grounds to deny. Under yolo an unresolved chain denies with retry guidance (customisable per registration via redirect_guidance) instead of prompting, hardening to a stop instruction after three redirects in a turn, so unattended runs never stall. Model calls resolve through the normal provider stack, respect a per-link timeout, report usage into the session cost pipeline, and emit OTel tool decisions with a dedicated reviewer source. Registration is live (same-name upsert, /reload clears) and gated behind a new 'reviewers' grant in plugin.toml, since an always-allow link would silently bypass prompting. Plugins observe outcomes through the ToolReviewed autocmd, spend included; the UI folds reviewer usage into session totals and toasts denials. Task slots. The bundled task tool declares three extension slots - task.resolve_model, task.system_prompt, task.tools - so a plugin can reroute subagent model choice, steer subagent prompts, or filter their tools by wrapping a slot instead of replacing the tool.
A plugin that owns an objective (goal loops, budget watchdogs, context managers) needs to act at the turn boundary and know why it arrived there. TurnEnd now carries reason (finished|cancelled|limit) so an auto-continue plugin can appeal "finished", always yield to "cancelled", and back off on limits. TurnComplete, AutoCompacting, and CompactionDone become autocmds, exposing per-turn usage, cost, and context pressure, plus the compaction moments a context plugin must react to. maki.session.queue() reads the pending message count without consuming it, so an injector can yield when the human already typed. maki.ui.action's docs now list every builtin action (the parser always accepted them; the doc stopped at three). code_execution: a child call's dispatch deadline was silently capped at the script budget, killing explicit long-running calls (a bash build with timeout = 1200 died at 30s). The per-call deadline is now max(script budget, the call's own timeout input).
Firaenix
force-pushed
the
pr/plugin-observability
branch
from
August 28, 2026 07:07
f20de63 to
51374f4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #875 (top commit is this change).
Plugins that own an objective (goal loops, budget watchdogs, context managers) need the turn boundary: TurnEnd now carries reason (finished|cancelled|limit) so an auto-continue plugin can appeal a finished turn, always yield to a cancellation, and back off on limits. TurnComplete, AutoCompacting, and CompactionDone become autocmds exposing per-turn usage, cost, and context pressure. maki.session.queue() reads the pending message count without consuming it, so an injector yields when the human already typed.
Also: maki.ui.action docs list every builtin action, and code_execution child calls no longer have their dispatch deadline silently capped at the script budget when the call carries its own longer timeout input.