feat(cockpit): approval preview — command/diff above Approve/Reject (W1)
Remote one-tap approval was blind (you'd tap Approve without seeing Claude wants to run `rm -rf` or rewrite a config). The pending tool's actual command / diff now renders above the approval bar, on every attached device, riding the same broadcast + late-joiner rails as the existing `gate` field. - src/http/approval-preview.ts (new, pure, never-throws): deriveApprovalPreview — Bash → command; Edit/Write/MultiEdit/NotebookEdit → a synthetic DiffFile; else null. Every line sanitized via sanitizeField (strips control/ANSI); caps 40 lines / 200 chars/line / 4KB (security limits, UTF-8-safe byte clamp). - src/types.ts: additive optional `preview?: ApprovalPreview` on the status ServerMessage + handleHookEvent (older clients ignore it). - src/server.ts /hook/permission: derive preview from tool_input, store on the PendingApproval entry, re-send to late joiners exactly like `gate`. - manager.ts threads it; public/tabs.ts renderApprovalPreview (command → <pre> textContent; diff → reused innerHTML-free renderDiffFile). Unknown tools / plan gates fall back to today's name-only bar. Attacker-influenced tool input → rendered via textContent/diff-renderer only, never innerHTML. Verified independently: typecheck + build:web clean, 1692 pass.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
import type {
|
||||
ApprovalPreview,
|
||||
ClaudeStatus,
|
||||
Config,
|
||||
Dims,
|
||||
@@ -218,7 +219,9 @@ export function createSessionManager(
|
||||
* H2/H3: a Claude Code hook reported activity for `sessionId`. Update the
|
||||
* status, append a timeline event (A4) when `eventClass` is supplied and the
|
||||
* timeline is enabled, and broadcast a `status` frame — carrying `gate` (B4)
|
||||
* when the held approval is a 'tool'/'plan' gate. No-op for an unknown session.
|
||||
* when the held approval is a 'tool'/'plan' gate, and `preview` (W1) when a
|
||||
* bounded command/diff preview of the held tool was derived. No-op for an
|
||||
* unknown session.
|
||||
*/
|
||||
function handleHookEvent(
|
||||
sessionId: string,
|
||||
@@ -228,6 +231,7 @@ export function createSessionManager(
|
||||
gate?: PermissionGate,
|
||||
eventClass?: string,
|
||||
toolName?: string,
|
||||
preview?: ApprovalPreview,
|
||||
): void {
|
||||
const session = sessions.get(sessionId);
|
||||
if (session === undefined) return;
|
||||
@@ -246,6 +250,7 @@ export function createSessionManager(
|
||||
if (detail !== undefined) msg.detail = detail;
|
||||
if (pending) msg.pending = true;
|
||||
if (gate !== undefined) msg.gate = gate;
|
||||
if (preview !== undefined) msg.preview = preview;
|
||||
broadcast(session, msg);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user