|
11 | 11 |
|
12 | 12 | import { swStartJob, swPauseJob, swCancelJob, swCancelJobById, swGetActiveJobId, type SwJobStartInput } from "./sw-job-executor"; |
13 | 13 | import { clearChatContext, upsertChatContext } from "../src/chat/context-store"; |
14 | | -import { createExecuteMap } from "../src/tools/internal-router"; |
| 14 | +import { createExecuteMap, resolveSession } from "../src/tools/internal-router"; |
15 | 15 | import { TOOL_SCHEMAS } from "../src/webmcp/tool-schemas"; |
16 | 16 | import type { ToolSchema } from "../src/webmcp/tool-schemas"; |
17 | 17 | import type { EntityType } from "../src/lib/entity-types"; |
| 18 | +import type { WritePreview } from "../src/bridge/confirm-bridge"; |
| 19 | +import { buildWebMcpWritePreview, isWebMcpReadOnlyInvocation } from "../src/webmcp/execution-policy"; |
18 | 20 |
|
19 | | -const WEBMCP_EXECUTE_MAP = createExecuteMap(); |
20 | | - |
21 | | -const WEBMCP_READ_ACTIONS: Record<string, Set<string>> = { |
22 | | - manage_settings: new Set(["get", "batch_get", "list_non_default"]), |
23 | | -}; |
24 | | - |
25 | | -function isWebMcpReadOnlyInvocation(tool: string, params: Record<string, unknown>): boolean { |
26 | | - const schema = TOOL_SCHEMAS.find((entry) => entry.name === tool); |
27 | | - if (!schema) return false; |
28 | | - if (schema.annotations?.readOnlyHint === true) return true; |
29 | | - |
30 | | - const allowedActions = WEBMCP_READ_ACTIONS[tool]; |
31 | | - if (!allowedActions) return false; |
32 | | - const action = params.action; |
33 | | - return typeof action === "string" && allowedActions.has(action); |
34 | | -} |
| 21 | +const WEBMCP_EXECUTE_MAP = createExecuteMap({ bypassWriteConfirmation: true }); |
35 | 22 |
|
36 | 23 | // -- Side panel activation ------------------------------------------------ |
37 | 24 |
|
@@ -135,6 +122,7 @@ export interface WebMcpExecuteToolMessage { |
135 | 122 | payload: { |
136 | 123 | tool: string; |
137 | 124 | params?: Record<string, unknown>; |
| 125 | + confirmed?: boolean; |
138 | 126 | }; |
139 | 127 | } |
140 | 128 |
|
@@ -278,20 +266,23 @@ chrome.runtime.onMessage.addListener( |
278 | 266 |
|
279 | 267 | async function handleWebMcpExecuteTool( |
280 | 268 | payload: WebMcpExecuteToolMessage["payload"], |
281 | | -): Promise<{ ok: boolean; result?: string; error?: string }> { |
| 269 | +): Promise<{ ok: boolean; result?: string; error?: string; needsConfirmation?: boolean; preview?: WritePreview }> { |
282 | 270 | const params = payload.params ?? {}; |
283 | 271 | const execute = WEBMCP_EXECUTE_MAP[payload.tool]; |
284 | 272 | if (!execute) { |
285 | 273 | return { ok: false, error: `Unknown tool: ${payload.tool}` }; |
286 | 274 | } |
287 | 275 |
|
288 | 276 | if (!isWebMcpReadOnlyInvocation(payload.tool, params)) { |
289 | | - return { |
290 | | - ok: false, |
291 | | - error: |
292 | | - "WebMCP write execution is temporarily disabled while the service-worker confirmation path is implemented. " + |
293 | | - "Read tools are available; use the side panel or Chat tab for confirmed writes for now.", |
294 | | - }; |
| 277 | + const session = await resolveSession(); |
| 278 | + if (!session) { |
| 279 | + return { ok: false, error: "Session not unlocked. Open the side panel and enter your PIN first." }; |
| 280 | + } |
| 281 | + |
| 282 | + const preview = buildWebMcpWritePreview(payload.tool, params, session.env); |
| 283 | + if (preview && payload.confirmed !== true) { |
| 284 | + return { ok: false, needsConfirmation: true, preview }; |
| 285 | + } |
295 | 286 | } |
296 | 287 |
|
297 | 288 | const result = await execute(params); |
|
0 commit comments