feat: AI command intent analysis on approval prompts - #468
Open
jasmine889966 wants to merge 1 commit into
Open
Conversation
jasmine889966
force-pushed
the
upstream/approval-intent-analysis
branch
from
August 29, 2026 04:36
f25d271 to
6fcaf4d
Compare
… UI's language Rebased onto current main (permission modes / reviewer architecture): - The analysis now runs only when a card will actually be shown: after the shadow-review spawn, right before PERMISSION_REQUIRED is emitted. A call the reviewer (or a standing rule) resolves never spends the extra round trip. - The annotation rides the payload alongside the upstream fields (provenance, readonly_ok, standing_target) and PermissionRequest.intent, so parked Inbox approvals keep it across reconnects. - Output language follows the UI language: bilingual prompt templates (en default), a language pref beside the toggle, re-synced on languageChanged. - GUI: restrained annotation block on the live card and the parked card, a Settings toggle, i18n keys for both locales. A routine file write upgrades from the compact row to the full card so the annotation is visible. - Timeout/raise/user-stop all degrade to 'no annotation' — the approval flow itself never breaks. Off by default; None stays a strict no-op.
jasmine889966
force-pushed
the
upstream/approval-intent-analysis
branch
from
August 31, 2026 06:37
6fcaf4d to
131b4f7
Compare
Author
|
Rebased onto current
Backend: 45 new tests ( |
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.
What
Before surfacing an approval prompt, ask the model to summarize what the command will do in a couple of bullet points, so the consequences are clear at a glance.
When the engine decides a tool call
needs_user, it runs one extra single-turnprovider.complete(tools disabled, mirroring the compactionsummarize_spanpattern) and attaches the result asintenton thePERMISSION_REQUIREDevent. The approval card renders it as a short, restrained annotation — transparent background, a small CSS dot, secondary-grey text, with the most critical terms bolded.The annotation language follows the UI language (English and Chinese templates ship; the GUI re-syncs the language pref when the user switches locales) — building on the i18n system that just landed via #127.
Screenshots
Close-up of the card — the annotation sits between the command preview and the action buttons. Note the bolded consequence ("the existing file is permanently overwritten"), which is exactly the fact a non-shell-literate approver needs and cannot get from the raw command:
In context, inside the session (English and Chinese UI both supported; the annotation language follows the interface):
Without the feature (or when the analyzer times out), the card is unchanged: the same raw command, no annotation — strictly no-op.
Why
Approval prompts today show the raw command and ask the user to decide. For anyone who isn't fluent in shell (or the specific tool's args), "is this safe to allow?" is a real question. A two-bullet, plain-language summary of the consequences turns the approve/deny decision from "do I trust this string?" into "do I accept these outcomes?".
This slots into the direction
mainhas been moving — provenance on the card, egress labels, plain-words scope notes all give the human more context; this PR extends the same idea from where the action goes to what the action will do, per command, in plain language.How
TurnEnginegains an optionalintent_analyzercallable (defaultNone= feature off, upstream behavior unchanged).Noneshort-circuits before any work, so this is a strict no-op for callers that don't opt in.PERMISSION_REQUIREDis emitted. A call that the reviewer (or a standing rule) resolves never spends the round trip — pinned bytest_no_card_no_analysis._authorize, before the event is emitted, so the card appears already annotated (no loading flicker). The call sits inside_interruptibleso a user Stop resolves immediately, and await_fortimeout is swallowed bytry/except— the approval flow never crashes on a slow/failed analysis (it degrades to no annotation).provenance,readonly_ok,standing_target), andPermissionRequest.intentflows into the Inbox snapshot, so unattended/reconnected cards show it too.languageChanged.Notes
Relation to the permission-modes work (#534)
Composes rather than competes: the reviewer decides (a risk verdict that can turn "ask the human" into "go ahead"), the intent annotation explains (plain-language consequences for the human who still has to decide). When the reviewer clears a call, no card is shown and the analyzer never runs; when the human still decides, they decide informed. The payload field sits beside
approval_extras-style upstream data without touching them.Files
coworker/intent_analysis/— new module:prompts.py(bilingual system prompts),analyzer.py(extract the operation description from the tool call, run one round trip, clean to bullet lines).coworker/engine.py—PermissionRequest.intent,TurnEngine.__init__params, the post-reviewer analysis branch in_authorize.coworker/agent.py—build_engineforwardsintent_analyzer.coworker/server/manager.py— injects the analyzer at bothbuild_enginesites when the pref is on (binding the annotation language), carries intent throughapproval_prompt_data, exposes the pref + language viaget_settings/set_intent_analysis.coworker/server/app.py—POST /v1/settings/intent-analysis.languageChangedre-sync.Tests
tests/test_intent_analysis.py(25) — prompt construction incl. language selection/fallback/clamping, input extraction per tool kind, output cleaning, analyzer success/failure/empty/prose-degrade, single-turn + tools-disabled contract.tests/test_engine_intent.py(20) — DI plumbing; theNone/success/timeout-no-crash/raises/stop-before/stop-mid branches of_authorize; no-card-no-analysis (standing allow ⇒ analyzer never invoked); payload coexistence withreadonly_ok;build_enginepassthrough; manager injection on/off with language binding; Inbox carry-through; REST roundtrip incl. the language field.ApprovalCard.test.tsx— annotation renders when present (bold terms emphasized), is absent when missing, and forces the full card for routine file writes (which otherwise render as a compact one-line row).All backend tests pass on top of current
main(the analyzer only consumes a provider response on the paths where a card is shown, so scripted-provider approval tests are untouched); frontend tests are type-checked and follow the existing render/getByText pattern.