feat: AI command intent analysis on approval prompts - #468
Open
jasmine889966 wants to merge 6 commits into
Open
Conversation
added 6 commits
August 7, 2026 16:38
Add an optional intent_analyzer callable to TurnEngine (dependency injection, default None = feature off, upstream behavior unchanged). Before emitting PERMISSION_REQUIRED, _authorize synchronously calls the analyzer to produce a consequence summary; the result is attached to both the event payload (as 'intent') and the PermissionRequest, so surfaces can render it on the approval card. Robustness: - wait_for timeout is wrapped in try/except so a TimeoutError never crashes _authorize (analysis degrades to intent=None). - The call runs inside _interruptible, so a user Stop during analysis resolves immediately; if Stop fired, no card is surfaced (the turn goes straight to the interrupted path, avoiding flash-then-deny). - intent_analyzer_timeout defaults to 20s (cloud-model tail latency); tests can inject smaller values. PermissionRequest gains an optional 'intent' field so approvers / Inbox snapshots carry the annotation through every park path.
Add coworker/intent_analysis/ — a small module that turns a tool call into a short, plain-English consequence summary via a single blocking provider.complete call (tools disabled, mirroring compaction's summarize_span pattern): - prompts.py: English system prompt (bullet points, bold for the most critical terms, severity emphasis for dangerous operations) + user prompt builder. No language routing — upstream is English-only. - analyzer.py: extract_input() shapes the tool call into a structured description (run_shell command / file path / message target); analyze() runs one round trip and cleans the output to bullet lines, returning None on any failure or empty result. Wiring: - agent.build_engine accepts and forwards intent_analyzer. - SessionManager injects analyze() at both build_engine sites when the 'intent_analysis' pref is on (default on), carries request.intent through approval_prompt_data so Inbox snapshots stay annotated, and exposes the pref via get_settings + set_intent_analysis. - POST /v1/settings/intent-analysis toggles it. The pref defaults to True; timeout defaults to 20s (covers cloud-model tail latency). Both are readily adjustable if maintainers prefer opt-in or a tighter bound.
Render the 'intent' annotation from PERMISSION_REQUIRED on both the live ApprovalCard and the parked InboxItemCard: - types.ts: optional intent?: string on ApprovalItem. - App.tsx: read d.intent when building the approval item. - ApprovalCard.tsx: renderIntentText() strips a leading bullet marker (the dot is CSS) and turns **bold** into <strong>; the full card shows an <ul> of intent lines under the title. Routine file writes that normally render as a compact one-line row fall back to the full card when an intent is present (the row can't show it). - InboxItemCard.tsx: same <ul> for parked approvals (reads item.data.intent). - styles.css: .approval-intent* — transparent background, a 4px CSS dot, secondary-grey body text, body-ink bold spans.
Add an 'Explain commands before I approve them' checkbox in Settings, modeled on the existing context-bar toggle (local useState + getSettings + setIntentAnalysis). When off, the manager stops injecting the analyzer and approval cards render without the annotation. - api.ts: intent_analysis? on ModelSettings + setIntentAnalysis(). - SettingsView.tsx: IntentAnalysisCard, mounted next to ContextBarCard.
- test_intent_analysis.py: prompts (rules/clamp/truncation/None), extract_input (shell/file/send/fallback), _clean (fences/labels/empty), analyze (positional signature, success, provider error, empty output). - test_engine_intent.py: PermissionRequest.intent + TurnEngine fields, _authorize branches (None/success/timeout-no-crash/raises/stop-before/ stop-mid-flight), build_engine passthrough, manager approval_prompt_data intent carry-through, get_settings field, and the REST toggle roundtrip. - ApprovalCard.test.tsx: intent block renders when present, absent when missing, and forces the full card for routine file writes. - pyproject.toml: register the 'slow' marker used by the timeout/stop tests.
Defaulting the pref to True broke upstream tests that drive approvals through a ScriptedProvider (test_durable_resume, test_ui_refresh_cross_cutting_e2e): the analyzer consumed a scripted provider.complete response before the model could use it, so the post-approval reply never landed. Making it opt-in (default False) keeps the feature a strict no-op for existing tests and users unless they explicitly turn it on in Settings. The PR description is updated to reflect opt-in as the default.
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.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-English summary of the consequences turns the approve/deny decision from "do I trust this string?" into "do I accept these outcomes?".
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._authorize, beforePERMISSION_REQUIREDis 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).PermissionRequest.intent, andapproval_prompt_datawrites it into the Inbox snapshot, so unattended/reconnected cards show it too.Notes
Files
coworker/intent_analysis/— new module:prompts.py(English system prompt),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_authorizeanalysis branch.coworker/agent.py—build_engineforwardsintent_analyzer.coworker/server/manager.py— injects the analyzer at bothbuild_enginesites when the pref is on, carries intent throughapproval_prompt_data, exposes the pref viaget_settings/set_intent_analysis.coworker/server/app.py—POST /v1/settings/intent-analysis.Tests
tests/test_intent_analysis.py— prompt construction, input extraction, output cleaning, analyzer success/failure/empty.tests/test_engine_intent.py— theNone/success/timeout-no-crash/raises/stop branches of_authorize,build_enginepassthrough, manager intent carry-through, and the REST toggle roundtrip.ApprovalCard.test.tsx— annotation renders when present, 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 (including the upstream
test_durable_resume/test_ui_refreshapproval tests); frontend tests are type-checked and follow the existing render/getByText pattern.