MCP v2 PR-1b: action dispatcher, confirm modal, set_label end-to-end
App.tsx now listens on "mcp://request" and resolves each call:
needsConfirm=true queues a confirm modal (Accept/Reject, or
"Always allow <tool>" which appends the bare tool name to the
policy's allow bucket on the fly); needsConfirm=false runs straight
through. Replies via mcp_action_reply with externally-tagged
Result. The only wired-up tool for now is set_label, which delegates
to the existing ops.setLabel path.
McpConfirm.tsx (new) — themed amber-bordered modal sibling to the
existing overlays. Enter = accept, Esc = reject. Shows tool, the
policy reason that triggered the prompt, a human-readable summary
("Rename pane X → Y"), and an expandable raw-args section.
Audit log: subscription lifted from AuditTab up to App.tsx so events
fired while the panel is closed (or on Config/Policy tab) still land
in the ring. AuditTab becomes presentational; McpPanel forwards
entries + clearAudit + computes the unread badge from a baseline
seen-count.
StrictMode race fix: both new App-level listeners (mcp://audit and
mcp://request) use the cancelled-flag pattern so a late-resolving
listen() Promise after a strict-mode pretend-unmount tears itself
down instead of leaking a second subscription. Previously this
manifested as duplicate audit rows and a need-to-click-twice on
modal buttons.
This commit is contained in:
parent
464c576b79
commit
26ffe8859a
6 changed files with 397 additions and 49 deletions
|
|
@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from "react";
|
|||
import {
|
||||
writeText as clipboardWriteText,
|
||||
} from "@tauri-apps/plugin-clipboard-manager";
|
||||
import type { McpStatus } from "../ipc";
|
||||
import type { McpStatus, McpAuditEntry } from "../ipc";
|
||||
import AuditTab from "./AuditTab";
|
||||
import PolicyTab from "./PolicyTab";
|
||||
import "./McpPanel.css";
|
||||
|
|
@ -18,6 +18,9 @@ interface McpPanelProps {
|
|||
allowedPaneCount: number;
|
||||
/** Total pane count for context. */
|
||||
totalPaneCount: number;
|
||||
/** Persistent audit log, owned by App so it survives panel close. */
|
||||
auditEntries: McpAuditEntry[];
|
||||
onClearAudit: () => void;
|
||||
}
|
||||
|
||||
type TabId = "config" | "audit" | "policy";
|
||||
|
|
@ -30,12 +33,17 @@ export default function McpPanel({
|
|||
onClose,
|
||||
allowedPaneCount,
|
||||
totalPaneCount,
|
||||
auditEntries,
|
||||
onClearAudit,
|
||||
}: McpPanelProps) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [revealToken, setRevealToken] = useState(false);
|
||||
const [regenBusy, setRegenBusy] = useState(false);
|
||||
const [tab, setTab] = useState<TabId>("config");
|
||||
const [auditUnread, setAuditUnread] = useState(false);
|
||||
// Unread badge on Audit tab: count of entries arrived since the user last
|
||||
// visited Audit. Tracked via a baseline count, reset on switch-to-audit.
|
||||
const [auditSeenCount, setAuditSeenCount] = useState(auditEntries.length);
|
||||
const auditUnread = auditEntries.length > auditSeenCount;
|
||||
|
||||
useEffect(() => {
|
||||
function onKey(e: KeyboardEvent) {
|
||||
|
|
@ -81,7 +89,7 @@ export default function McpPanel({
|
|||
|
||||
function switchTab(id: TabId) {
|
||||
setTab(id);
|
||||
if (id === "audit") setAuditUnread(false);
|
||||
if (id === "audit") setAuditSeenCount(auditEntries.length);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -286,10 +294,7 @@ export default function McpPanel({
|
|||
)}
|
||||
|
||||
{tab === "audit" && (
|
||||
<AuditTab
|
||||
active={tab === "audit"}
|
||||
onUnread={() => setAuditUnread(true)}
|
||||
/>
|
||||
<AuditTab entries={auditEntries} onClear={onClearAudit} />
|
||||
)}
|
||||
|
||||
{tab === "policy" && <PolicyTab />}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue