+ {enableDialog}
+ >
)
}
@@ -855,65 +630,6 @@ export function ComputerUseSettings() {
type Translate = ReturnType
-/**
- * An app's real icon, falling back to a letter tile.
- *
- * The `/apps` payload deliberately carries no icon bytes — it lists every
- * installed application, and inlining hundreds of PNGs would bloat one
- * response. Each row instead points an `` at the icon endpoint, which
- * reads the bundle's own `.icns` the same way Finder does. `loading="lazy"`
- * matters here: the picker is a long scroller, and without it every row off
- * screen would still cost a request and a rasterisation.
- *
- * A bundle with no icon 404s, which is an ordinary outcome rather than a
- * failure — that is what the letter tile is for.
- */
-function AppIcon({ name, bundleId }: { name: string; bundleId?: string }) {
- const [iconUrl, setIconUrl] = useState(null)
-
- useEffect(() => {
- if (!bundleId) {
- setIconUrl(null)
- return
- }
- let cancelled = false
- setIconUrl(null)
- void computerUseApi.loadAppIcon(bundleId).then(url => {
- if (!cancelled) setIconUrl(url)
- })
- return () => {
- cancelled = true
- }
- }, [bundleId])
-
- const tileClass =
- 'flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] border border-[var(--color-border)] bg-[var(--color-surface-container-high)] shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]'
-
- if (iconUrl) {
- return (
-
-
-
- )
- }
-
- // Shown both while the icon is in flight and when the bundle has none. The
- // letter is a stable placeholder rather than a spinner, so a list of
- // iconless utilities does not read as permanently loading.
- const letter = name.trim().charAt(0).toUpperCase() || '?'
- return (
-
- )}
-
-
- {/* App picker dialog.
- Uses the shared Modal rather than a hand-rolled overlay: Modal portals
- to document.body, so it cannot be trapped by an ancestor's stacking
- context, and it sits on `--z-dialog` instead of a bare `z-50` that the
- rest of the `--z-*` scale does not know about. It also brings the focus
- trap, Escape-to-close and focus restore this picker used to lack. */}
-
- {/* Cancel Modal's content padding so the app rows stay edge-to-edge —
- their divider lines are the list's structure, and inset dividers
- would read as a nested card. The search field keeps the padding. */}
-
)
}
diff --git a/runtime/test_helpers.py b/runtime/test_helpers.py
index edbb1271..3c85d227 100644
--- a/runtime/test_helpers.py
+++ b/runtime/test_helpers.py
@@ -250,7 +250,8 @@ class TestMutatingCommandsAreGuarded(unittest.TestCase):
"""Coordinate actions must not jump while mouse animation is enabled."""
source = _win_source()
self.assertIn("def _move_cursor_to", source)
- self.assertIn("1 - (1 - progress) ** 3", source)
+ self.assertIn("def _spring_cursor_path", source)
+ self.assertNotIn("1 - (1 - progress) ** 3", source)
dispatcher = source.index("def main()")
for command in ("click", "drag", "move_mouse", "scroll"):
marker = f'if command == "{command}":'
@@ -262,6 +263,28 @@ class TestMutatingCommandsAreGuarded(unittest.TestCase):
f"{command} must honor the mouse-animation gate",
)
+ @unittest.skipUnless(IS_WINDOWS, "requires the Windows helper module")
+ def test_spring_cursor_path_starts_smoothly_and_lands_exactly(self):
+ spec = importlib.util.spec_from_file_location("win_helper_motion", WIN_HELPER)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+
+ points = module._spring_cursor_path(0, 0, 1000, 500)
+ self.assertGreater(len(points), 12)
+ self.assertEqual(points[-1], (1000, 500))
+ first_distance = (points[0][0] ** 2 + points[0][1] ** 2) ** 0.5
+ total_distance = (1000 ** 2 + 500 ** 2) ** 0.5
+ self.assertLess(first_distance / total_distance, 0.10)
+ self.assertTrue(all(a != b for a, b in zip(points, points[1:])))
+
+ def test_drag_reuses_the_shared_cursor_motion(self):
+ source = _win_source()
+ dispatcher = source.index('if command == "drag":')
+ body = source[dispatcher:source.index('if command == "move_mouse":', dispatcher)]
+ self.assertGreaterEqual(body.count("_move_cursor_to("), 2)
+ self.assertNotIn("for step in range", body)
+
def test_helper_uses_per_monitor_dpi_coordinates(self):
source = _win_source()
self.assertIn("DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2", source)
@@ -577,6 +600,15 @@ class TestCursorBadge(unittest.TestCase):
self.assertIn("WindowFromPoint", source)
self.assertIn("GetForegroundWindow", source)
+ def test_overlay_activity_does_not_hide_before_animated_motion(self):
+ source = CURSOR_BADGE.read_text(encoding="utf-8")
+ start = source.index(" def _on_activity")
+ end = source.index(" def _read_parent", start)
+ body = source[start:end]
+ self.assertNotIn("ShowWindow", body)
+ self.assertIn("GetCursorPos", body)
+ self.assertIn("self._requested_visible = self._target_pid is not None", body)
+
def test_overlay_readiness_precedes_the_first_action(self):
source = CURSOR_BADGE.read_text(encoding="utf-8")
self.assertIn('print("READY", flush=True)', source)
diff --git a/runtime/win_cursor_badge.py b/runtime/win_cursor_badge.py
index d68d1a08..a45e06c1 100644
--- a/runtime/win_cursor_badge.py
+++ b/runtime/win_cursor_badge.py
@@ -569,14 +569,18 @@ class VirtualCursorOverlay:
with self._lock:
if point is not None:
- # Hide for the target lookup. A click-through layered window can
- # still be returned by WindowFromPoint on some Windows builds.
- if self.hwnd and self._shown:
- user32.ShowWindow(self.hwnd, SW_HIDE)
- self._shown = False
+ # Keep the cursor visible between related actions. Hiding here
+ # made every movement reappear only after the first injected
+ # frame, which looked like a jump into the middle of the path.
+ # `_pid_at_point` already skips this transparent overlay.
self._destination = point
self._target_pid = target_pid or self._pid_at_point(point)
- self._requested_visible = False # reveal on injected motion
+ if self._agent_position is None:
+ current = POINT()
+ if user32.GetCursorPos(ctypes.byref(current)):
+ self._agent_position = (int(current.x), int(current.y))
+ self._requested_visible = self._target_pid is not None
+ self._hidden_for_user = False
elif target_pid is not None:
self._target_pid = target_pid
diff --git a/runtime/win_helper.py b/runtime/win_helper.py
index 15c11d1d..061c4d8e 100644
--- a/runtime/win_helper.py
+++ b/runtime/win_helper.py
@@ -1196,30 +1196,70 @@ def _absolute_mouse_move(x: int, y: int) -> _INPUT:
)
+def _spring_cursor_path(
+ start_x: int,
+ start_y: int,
+ target_x: int,
+ target_y: int,
+) -> list[tuple[int, int]]:
+ """Sample the same damped-spring motion used by the macOS cursor."""
+ distance = ((target_x - start_x) ** 2 + (target_y - start_y) ** 2) ** 0.5
+ if distance < 2:
+ return [(target_x, target_y)]
+
+ # CursorMotionState.swift uses k=196 and a damping ratio of 0.85. A
+ # 60-Hz fixed step gives Windows the same zero-velocity start and gentle
+ # settle while keeping physical-pointer actions bounded.
+ frame_interval = 1.0 / 60.0
+ stiffness = 196.0
+ damping = 2.0 * 0.85 * stiffness ** 0.5
+ max_duration = min(0.45, max(0.20, distance / 3000.0))
+ sample_count = max(1, round(max_duration / frame_interval))
+
+ pos_x, pos_y = float(start_x), float(start_y)
+ vel_x = vel_y = 0.0
+ points: list[tuple[int, int]] = []
+ for _ in range(sample_count):
+ vel_x += (stiffness * (target_x - pos_x) - damping * vel_x) * frame_interval
+ vel_y += (stiffness * (target_y - pos_y) - damping * vel_y) * frame_interval
+ pos_x += vel_x * frame_interval
+ pos_y += vel_y * frame_interval
+ point = (round(pos_x), round(pos_y))
+ if not points or point != points[-1]:
+ points.append(point)
+
+ remaining = ((target_x - pos_x) ** 2 + (target_y - pos_y) ** 2) ** 0.5
+ speed = (vel_x ** 2 + vel_y ** 2) ** 0.5
+ if remaining < 0.5 and speed < 6.0:
+ break
+
+ target = (target_x, target_y)
+ if not points or points[-1] != target:
+ points.append(target)
+ return points
+
+
def _move_cursor_to(x: int, y: int, animate: bool) -> None:
- """Move the shared pointer along a short eased path for overlay parity."""
+ """Move the shared pointer with the macOS virtual-cursor spring."""
current = wintypes.POINT()
if not _user32.GetCursorPos(ctypes.byref(current)):
_send_inputs([_absolute_mouse_move(x, y)])
return
start_x, start_y = int(current.x), int(current.y)
- distance = ((x - start_x) ** 2 + (y - start_y) ** 2) ** 0.5
- if not animate or distance < 2:
+ if not animate:
_send_inputs([_absolute_mouse_move(x, y)])
return
- # 100-260ms is long enough to read as motion without slowing the agent.
- duration = min(0.26, max(0.10, distance / 4000.0))
- steps = max(6, min(18, round(duration * 60)))
- for step in range(1, steps + 1):
- progress = step / steps
- eased = 1 - (1 - progress) ** 3
- next_x = round(start_x + (x - start_x) * eased)
- next_y = round(start_y + (y - start_y) * eased)
+ points = _spring_cursor_path(start_x, start_y, x, y)
+ started = time.perf_counter()
+ for index, (next_x, next_y) in enumerate(points):
_send_inputs([_absolute_mouse_move(next_x, next_y)])
- if step < steps:
- time.sleep(duration / steps)
+ if index < len(points) - 1:
+ deadline = started + (index + 1) / 60.0
+ delay = deadline - time.perf_counter()
+ if delay > 0:
+ time.sleep(delay)
_VIRTUAL_KEYS = {
@@ -1859,14 +1899,7 @@ def main() -> int:
animate = bool(payload.get("animate", True))
_move_cursor_to(start_x, start_y, animate)
_send_inputs([_mouse_input(MOUSEEVENTF_LEFTDOWN)])
- duration = 0.18 if animate else 0
- for step in range(1, 13):
- _send_inputs([_absolute_mouse_move(
- round(start_x + (target_x - start_x) * step / 12),
- round(start_y + (target_y - start_y) * step / 12),
- )])
- if duration and step < 12:
- time.sleep(duration / 12)
+ _move_cursor_to(target_x, target_y, animate)
_send_inputs([_mouse_input(MOUSEEVENTF_LEFTUP)])
return _finish(lease, True)
if command == "move_mouse":
diff --git a/src/server/__tests__/computer-use-api.test.ts b/src/server/__tests__/computer-use-api.test.ts
index 5f3b9fca..4fb96470 100644
--- a/src/server/__tests__/computer-use-api.test.ts
+++ b/src/server/__tests__/computer-use-api.test.ts
@@ -73,13 +73,18 @@ afterAll(async () => {
})
describe('Computer Use API authorized app config', () => {
- it('defaults Computer Use enabled for existing users without config', async () => {
+ it('defaults Computer Use off until the risk confirmation is accepted', async () => {
const res = await callAuthorizedApps('GET')
expect(res.status).toBe(200)
expect(await res.json()).toMatchObject({
- enabled: true,
+ enabled: false,
authorizedApps: [],
+ grantFlags: {
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
+ },
})
})
@@ -484,63 +489,21 @@ describe('runPipInstallWithFallback', () => {
})
})
-describe('computeRuntimeGrantAdditions', () => {
- it('maps new grants to AuthorizedApp entries with ISO authorizedAt', async () => {
- const { computeRuntimeGrantAdditions } = await importComputerUseApi()
- const grantedAt = Date.UTC(2026, 0, 2, 3, 4, 5)
-
- const additions = computeRuntimeGrantAdditions(
- [],
- [{ bundleId: 'com.apple.Notes', displayName: 'Notes', grantedAt }],
+describe('retired per-app authorization endpoint', () => {
+ it('cannot emit a runtime approval request', async () => {
+ const response = await callComputerUseAction(
+ 'request-access',
+ 'POST',
+ JSON.stringify({
+ sessionId: 'session-1',
+ request: { requestId: 'request-1', apps: [] },
+ }),
)
- expect(additions).toEqual([
- {
- bundleId: 'com.apple.Notes',
- displayName: 'Notes',
- authorizedAt: new Date(grantedAt).toISOString(),
- },
- ])
- })
-
- it('dedupes grants already present in the stored config by bundleId', async () => {
- const { computeRuntimeGrantAdditions } = await importComputerUseApi()
-
- const additions = computeRuntimeGrantAdditions(
- [{ bundleId: 'com.apple.Notes', displayName: 'Notes' }],
- [
- { bundleId: 'com.apple.Notes', displayName: 'Notes', grantedAt: 1 },
- { bundleId: 'com.apple.Safari', displayName: 'Safari', grantedAt: 2 },
- ],
- )
-
- expect(additions.map((a) => a.bundleId)).toEqual(['com.apple.Safari'])
- })
-
- it('dedupes duplicate bundleIds within the same grant batch', async () => {
- const { computeRuntimeGrantAdditions } = await importComputerUseApi()
-
- const additions = computeRuntimeGrantAdditions(
- [],
- [
- { bundleId: 'com.apple.Safari', displayName: 'Safari', grantedAt: 1 },
- { bundleId: 'com.apple.Safari', displayName: 'Safari (dup)', grantedAt: 2 },
- ],
- )
-
- expect(additions).toHaveLength(1)
- expect(additions[0]).toMatchObject({ bundleId: 'com.apple.Safari', displayName: 'Safari' })
- })
-
- it('skips grants without a bundleId and returns [] for an empty batch', async () => {
- const { computeRuntimeGrantAdditions } = await importComputerUseApi()
-
- expect(computeRuntimeGrantAdditions([], [])).toEqual([])
- expect(
- computeRuntimeGrantAdditions([], [
- { bundleId: '', displayName: 'No Bundle', grantedAt: 1 },
- ]),
- ).toEqual([])
+ expect(response.status).toBe(410)
+ await expect(response.json()).resolves.toMatchObject({
+ error: 'APP_AUTHORIZATION_REMOVED',
+ })
})
})
diff --git a/src/server/api/computer-use.ts b/src/server/api/computer-use.ts
index e4e150c0..c90bbc28 100644
--- a/src/server/api/computer-use.ts
+++ b/src/server/api/computer-use.ts
@@ -12,8 +12,6 @@ import { access, readFile, mkdir, writeFile, rm } from 'fs/promises'
import { createHash } from 'crypto'
import path from 'path'
import { fileURLToPath } from 'url'
-import type { AppGrant, CuPermissionRequest } from '../../vendor/computer-use-mcp/types.js'
-import { computerUseApprovalService } from '../services/computerUseApprovalService.js'
import { diagnosticsService } from '../services/diagnosticsService.js'
import { normalizeIconSize, readAppIconPng } from '../services/macAppIcon.js'
import { listInstalledMacApps } from './macInstalledApps.js'
@@ -910,13 +908,8 @@ export type ComputerUseConfigPatch = {
pythonPath?: string | null
}
-type RequestAccessBody = {
- sessionId?: string
- request?: CuPermissionRequest
-}
-
const DEFAULT_CONFIG: ComputerUseConfig = {
- enabled: true,
+ enabled: false,
authorizedApps: [],
grantFlags: DEFAULT_DESKTOP_GRANT_FLAGS,
pythonPath: null,
@@ -1042,56 +1035,6 @@ export async function openComputerUseSettings(
: { ok: false, message: result.stderr || `Failed to run ${command.cmd}` }
}
-/**
- * Compute the AuthorizedApp entries to APPEND for a batch of runtime grants,
- * deduped by bundleId against the already-stored apps. Pure (no I/O) so it can
- * be unit-tested directly.
- *
- * Mapping: AppGrant.grantedAt (epoch ms) -> AuthorizedApp.authorizedAt (ISO
- * string). NOTE the stored schema (StoredAuthorizedApp) carries no `tier`, so
- * a runtime read/click-tier grant is reloaded as full-tier next session — a
- * pre-existing limitation of the stored schema, out of scope here.
- */
-export function computeRuntimeGrantAdditions(
- existingApps: AuthorizedApp[],
- granted: AppGrant[],
-): AuthorizedApp[] {
- const existing = new Set(existingApps.map((app) => app.bundleId))
- const additions: AuthorizedApp[] = []
- // Dedupe against BOTH the stored set and within this batch, so a request
- // listing the same bundleId twice can't double-append.
- const seen = new Set(existing)
- for (const grant of granted) {
- if (!grant.bundleId || seen.has(grant.bundleId)) continue
- seen.add(grant.bundleId)
- additions.push({
- bundleId: grant.bundleId,
- displayName: grant.displayName,
- authorizedAt: new Date(grant.grantedAt).toISOString(),
- })
- }
- return additions
-}
-
-/**
- * Append newly-granted apps (from a runtime request_access approval) to the
- * persisted config. Best-effort: any failure is swallowed so it can never tank
- * the grant response the model is awaiting. Idempotent across repeat calls in a
- * long session because additions are deduped by bundleId.
- */
-async function persistRuntimeGrants(granted: AppGrant[]): Promise {
- if (!granted || granted.length === 0) return
- try {
- const config = await loadConfig()
- const additions = computeRuntimeGrantAdditions(config.authorizedApps, granted)
- if (additions.length === 0) return
- config.authorizedApps = [...config.authorizedApps, ...additions]
- await saveConfig(config)
- } catch {
- // best-effort — a config-write failure must NOT fail the grant response
- }
-}
-
async function listInstalledApps(): Promise<{ bundleId: string; displayName: string; path: string }[]> {
// macOS: enumerate the application roots directly. This needs neither a
// Python venv nor a running helper, so the app picker populates on a cold
@@ -1351,36 +1294,13 @@ export async function handleComputerUseApi(
}
if (action === 'request-access' && req.method === 'POST') {
- try {
- const body = (await req.json()) as RequestAccessBody
- if (!body.sessionId || !body.request?.requestId) {
- return Response.json(
- { error: 'BAD_REQUEST', message: 'sessionId and request are required' },
- { status: 400 },
- )
- }
-
- const response = await computerUseApprovalService.requestApproval(
- body.sessionId,
- body.request,
- )
-
- // Runtime auto-add: persist apps the user just granted into the stored
- // config so they appear under "始终允许的应用" in Settings and survive
- // into the next session. This is the SINGLE server-side runtime-grant
- // ingress (no teach-access route exists), runs in the process that owns
- // the config writer, and does NOT touch the per-session allowlist (that
- // lives in the separate CLI subprocess). Best-effort: a config-write
- // failure must never fail the grant the model is awaiting.
- await persistRuntimeGrants(response.granted)
-
- return Response.json(response)
- } catch (error) {
- const message =
- error instanceof Error ? error.message : 'Computer Use approval failed'
- const status = message.includes('not connected') ? 409 : 500
- return Response.json({ error: 'COMPUTER_USE_APPROVAL_FAILED', message }, { status })
- }
+ return Response.json(
+ {
+ error: 'APP_AUTHORIZATION_REMOVED',
+ message: 'Per-application Computer Use authorization is no longer required.',
+ },
+ { status: 410 },
+ )
}
return Response.json(
diff --git a/src/skills/bundled/computerUse.test.ts b/src/skills/bundled/computerUse.test.ts
index cd383734..d0531161 100644
--- a/src/skills/bundled/computerUse.test.ts
+++ b/src/skills/bundled/computerUse.test.ts
@@ -107,9 +107,10 @@ describe('computer-use skill registration', () => {
expect(skill!.allowedTools).toEqual(getComputerUseToolAllowlist(platform))
expect(skill!.allowedTools).toContain(
process.platform === 'win32'
- ? 'mcp__computer-use__request_access'
+ ? 'mcp__computer-use__screenshot'
: 'mcp__computer-use__get_app_state',
)
+ expect(skill!.allowedTools).not.toContain('mcp__computer-use__request_access')
expect(
skill!.allowedTools!.every(t => t.startsWith('mcp__computer-use__')),
).toBe(true)
@@ -123,9 +124,10 @@ describe('computer-use skill registration', () => {
})
describe('computer-use Windows guidance', () => {
- test('matches the unfiltered, permission-gated pixel tool face', () => {
+ test('matches the unfiltered, feature-authorized pixel tool face', () => {
const prompt = getComputerUsePrompt('win32')
- expect(prompt).toContain('request_access')
+ expect(prompt).not.toContain('request_access')
+ expect(prompt).toContain('without an app-by-app approval prompt')
expect(prompt).toContain('screenshots are NOT filtered')
expect(prompt).toContain('most recent full screenshot')
expect(prompt).toContain('UNKNOWN result')
diff --git a/src/skills/bundled/computerUse.ts b/src/skills/bundled/computerUse.ts
index d9402acf..e2a64e59 100644
--- a/src/skills/bundled/computerUse.ts
+++ b/src/skills/bundled/computerUse.ts
@@ -96,8 +96,8 @@ never helps.
If three genuinely different approaches fail, stop and tell the user what you
tried and what you observed. Do not drive the UI with \`osascript\`, AppleScript,
-System Events, JXA, Python, or shell commands — those bypass the permission
-model the user granted, do not work on these apps, and burn the rest of the
+System Events, JXA, Python, or shell commands — those bypass Computer Use's
+target and interference safeguards, do not work on these apps, and burn the rest of the
session.
## Tool notes
@@ -156,30 +156,26 @@ const WINDOWS_COMPUTER_USE_PROMPT = `# Operating Windows apps
You are driving real applications on the user's Windows desktop through the
Computer Use pixel tools. Work in this loop:
-1. \`request_access({ apps, reason })\` once, naming every app the task needs.
- It must run before every other Computer Use tool. If another app becomes
- necessary later, request access to add it.
-2. \`screenshot()\` and inspect the current display.
-3. Act using coordinates from that exact full-display screenshot.
-4. Take another \`screenshot()\` before deciding whether the action worked.
+1. \`screenshot()\` and inspect the current display.
+2. Act using coordinates from that exact full-display screenshot.
+3. Take another \`screenshot()\` before deciding whether the action worked.
On Windows screenshots are NOT filtered: every visible window on the captured
-display can appear, including apps that were not granted. Permission limits
-input, not visibility. Never interact with an ungranted app; request access or
-ask the user first.
+display can appear. Enabling Computer Use authorizes control of supported apps
+without an app-by-app approval prompt. Product safety restrictions still apply.
Use \`zoom\` to read small details, but never use coordinates from a zoom image
for actions. Coordinates always refer to the most recent full screenshot. Use
-\`open_application\` to launch or foreground a granted app. Input actions are
-also checked against the frontmost app and the window under the target point;
-if either is ungranted, stop and refresh state instead of trying to bypass the
-gate.
+\`open_application\` to launch or foreground an installed app. Input actions
+are checked against the frontmost app and the window under the target point;
+if a target cannot be identified or is safety-restricted, stop and refresh
+state instead of trying to bypass the gate.
Mutating tools return a dispatch receipt, not proof of the intended result.
Only the next screenshot proves what happened. If two attempts leave the UI
unchanged, change approach. Do not repeat an identical action a third time.
Do not fall back to PowerShell, Python, AutoHotkey, or another UI automation
-path; those bypass the permission and interference safeguards the user granted.
+path; those bypass Computer Use's target, product-safety, and interference safeguards.
The helper shares Windows' real mouse and keyboard stream. If it reports user
interference or an UNKNOWN result, do not repeat the action. Take a screenshot
@@ -217,7 +213,7 @@ export function registerComputerUseSkill(): void {
// competes with the Chrome extension and purpose-built MCP servers on web
// tasks, where they are faster and more precise.
description: isWindows
- ? "Operate apps on the user's Windows desktop — click, type, scroll and inspect the display through permission-gated pixel tools. For native desktop apps and cross-app workflows. Prefer a purpose-built MCP server, browser integration, or CLI when one covers the task."
+ ? "Operate apps on the user's Windows desktop — click, type, scroll and inspect the display through safety-gated pixel tools. For native desktop apps and cross-app workflows. Prefer a purpose-built MCP server, browser integration, or CLI when one covers the task."
: "Operate apps on the user's Mac — click, type, scroll and read app state through the accessibility engine. For native desktop apps and cross-app workflows. Prefer a purpose-built MCP server, the Chrome extension, or a CLI when one covers the task.",
whenToUse: isWindows
? 'When the user wants something done inside a Windows application. Invoke this BEFORE the first mcp__computer-use__* call; it carries the approval, screenshot, and pixel-action workflow those tools assume.'
diff --git a/src/utils/computerUse/appNames.ts b/src/utils/computerUse/appNames.ts
index 55cc2e7e..cd4d9a1f 100644
--- a/src/utils/computerUse/appNames.ts
+++ b/src/utils/computerUse/appNames.ts
@@ -1,6 +1,6 @@
/**
- * Filter and sanitize installed-app data for inclusion in the `request_access`
- * tool description. Ported from Cowork's appNames.ts. Two
+ * Filter and sanitize installed-app data for inclusion in Computer Use tool
+ * descriptions. Ported from Cowork's appNames.ts. Two
* concerns: noise filtering (Spotlight returns every bundle on disk — XPC
* helpers, daemons, input methods) and prompt-injection hardening (app names
* are attacker-controlled; anyone can ship an app named anything).
@@ -8,8 +8,8 @@
* Residual risk: short benign-char adversarial names ("grant all") can't be
* filtered programmatically. The tool description's structural framing
* ("Available applications:") makes it clear these are app names, and the
- * downstream permission dialog requires explicit user approval — a bad name
- * can't auto-grant anything.
+ * runtime resolves the chosen app against the installed inventory and still
+ * applies product-safety restrictions before acting.
*/
/** Minimal shape — matches what `listInstalledApps` returns. */
diff --git a/src/utils/computerUse/mcpServer.ts b/src/utils/computerUse/mcpServer.ts
index 654af506..70088e13 100644
--- a/src/utils/computerUse/mcpServer.ts
+++ b/src/utils/computerUse/mcpServer.ts
@@ -47,7 +47,7 @@ async function tryGetInstalledAppNames(): Promise {
* Construct the in-process server. Delegates to the package's
* `createComputerUseMcpServer` for the Server object + stub CallTool handler,
* then REPLACES the ListTools handler with one that includes installed-app
- * names in the `request_access` description (the package's factory doesn't
+ * names in platform-specific tool descriptions (the package's factory doesn't
* take `installedAppNames`, and Cowork builds its own tool array in
* serverDef.ts for the same reason).
*
diff --git a/src/utils/computerUse/preauthorizedConfig.test.ts b/src/utils/computerUse/preauthorizedConfig.test.ts
index 3a63ce31..c941820d 100644
--- a/src/utils/computerUse/preauthorizedConfig.test.ts
+++ b/src/utils/computerUse/preauthorizedConfig.test.ts
@@ -10,14 +10,14 @@ import {
} from './preauthorizedConfig.js'
describe('resolveStoredComputerUseConfig', () => {
- test('keeps desktop grant flags disabled until explicitly granted', () => {
+ test('starts disabled and prepares full grants for explicit enablement', () => {
expect(resolveStoredComputerUseConfig()).toEqual({
- enabled: true,
+ enabled: false,
authorizedApps: [],
grantFlags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
},
pythonPath: null,
})
@@ -30,7 +30,7 @@ describe('resolveStoredComputerUseConfig', () => {
})
})
- test('honors explicit grant flags without enabling unspecified grants', () => {
+ test('honors explicit grant flags while defaulting unspecified grants on', () => {
expect(
resolveStoredComputerUseConfig({
grantFlags: {
@@ -38,12 +38,12 @@ describe('resolveStoredComputerUseConfig', () => {
},
}),
).toEqual({
- enabled: true,
+ enabled: false,
authorizedApps: [],
grantFlags: {
clipboardRead: true,
- clipboardWrite: false,
- systemKeyCombos: false,
+ clipboardWrite: true,
+ systemKeyCombos: true,
},
pythonPath: null,
})
@@ -57,12 +57,12 @@ describe('resolveStoredComputerUseConfig', () => {
try {
await expect(loadStoredComputerUseConfigResult()).resolves.toEqual({
config: {
- enabled: true,
+ enabled: false,
authorizedApps: [],
grantFlags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
},
pythonPath: null,
},
diff --git a/src/utils/computerUse/preauthorizedConfig.ts b/src/utils/computerUse/preauthorizedConfig.ts
index 220fb1b6..2d47069c 100644
--- a/src/utils/computerUse/preauthorizedConfig.ts
+++ b/src/utils/computerUse/preauthorizedConfig.ts
@@ -26,12 +26,14 @@ export type StoredComputerUseConfig = {
[key: string]: unknown
}
-export const DEFAULT_COMPUTER_USE_ENABLED = true
+// Computer Use starts off so the first enablement always crosses the explicit
+// risk-confirmation boundary in the desktop settings page.
+export const DEFAULT_COMPUTER_USE_ENABLED = false
export const DEFAULT_DESKTOP_GRANT_FLAGS: CuGrantFlags = {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
}
const FAIL_CLOSED_GRANT_FLAGS: CuGrantFlags = {
diff --git a/src/utils/computerUse/setup.ts b/src/utils/computerUse/setup.ts
index 173ae260..15ad54e5 100644
--- a/src/utils/computerUse/setup.ts
+++ b/src/utils/computerUse/setup.ts
@@ -20,8 +20,8 @@ type SetupComputerUseDeps = {
/**
* Build the dynamic MCP config + allowed tool names. Mirror of
* `setupClaudeInChrome`. The `mcp__computer-use__*` tools are added to
- * `allowedTools` so they bypass the normal permission prompt — the package's
- * `request_access` handles approval for the whole session.
+ * `allowedTools` so they bypass the normal tool prompt. The settings-page risk
+ * confirmation is the authorization boundary for the whole session.
*
* The MCP layer isn't ceremony: the API backend detects `mcp__computer-use__*`
* tool names and emits a CU availability hint into the system prompt
diff --git a/src/utils/computerUse/wrapper.test.ts b/src/utils/computerUse/wrapper.test.ts
new file mode 100644
index 00000000..9f1f2487
--- /dev/null
+++ b/src/utils/computerUse/wrapper.test.ts
@@ -0,0 +1,17 @@
+import { describe, expect, test } from 'bun:test'
+import { buildSessionContext } from './wrapper.js'
+
+describe('Computer Use session authorization', () => {
+ test('enables every supported app without exposing a runtime permission callback', () => {
+ const context = buildSessionContext()
+
+ expect(context.getAllowedApps()).toEqual([])
+ expect(context.getUserDeniedBundleIds()).toEqual([])
+ expect(context.getGrantFlags()).toEqual({
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
+ })
+ expect(context.onPermissionRequest).toBeUndefined()
+ })
+})
diff --git a/src/utils/computerUse/wrapper.tsx b/src/utils/computerUse/wrapper.tsx
index 459b569c..4f98d643 100644
--- a/src/utils/computerUse/wrapper.tsx
+++ b/src/utils/computerUse/wrapper.tsx
@@ -16,10 +16,8 @@
* GrowthBook gate `tengu_malort_pedway` (see gates.ts).
*/
-import { bindSessionContext, type ComputerUseSessionContext, type CuCallToolResult, type CuPermissionRequest, type CuPermissionResponse, DEFAULT_GRANT_FLAGS, type ScreenshotDims } from '../../vendor/computer-use-mcp/index.js';
-import * as React from 'react';
+import { bindSessionContext, type ComputerUseSessionContext, type CuCallToolResult, type ScreenshotDims } from '../../vendor/computer-use-mcp/index.js';
import { getSessionId } from '../../bootstrap/state.js';
-import { ComputerUseApproval } from '../../components/permissions/ComputerUseApproval/ComputerUseApproval.js';
import type { Tool, ToolUseContext } from '../../Tool.js';
import { logForDebugging } from '../debug.js';
import { checkComputerUseLock, tryAcquireComputerUseLock } from './computerUseLock.js';
@@ -27,12 +25,6 @@ import { registerEscHotkey } from './escHotkey.js';
import { getChicagoCoordinateMode } from './gates.js';
import { getComputerUseHostAdapter } from './hostAdapter.js';
import { getComputerUseMCPRenderingOverrides } from './toolRendering.js';
-import {
- buildPreAuthorizedAppGrants,
- loadStoredComputerUseConfig,
- saveStoredComputerUseConfig,
- type StoredAuthorizedApp,
-} from './preauthorizedConfig.js';
type CallOverride = Pick['call'];
type Binding = {
ctx: ComputerUseSessionContext;
@@ -54,7 +46,11 @@ type Binding = {
*/
let binding: Binding | undefined;
let currentToolUseContext: ToolUseContext | undefined;
-const desktopServerUrl = process.env.CC_HAHA_DESKTOP_SERVER_URL;
+const ENABLED_GRANT_FLAGS = {
+ clipboardRead: true,
+ clipboardWrite: true,
+ systemKeyCombos: true,
+} as const;
function tuc(): ToolUseContext {
// Safe: `binding` is only populated when `currentToolUseContext` is set.
// Called only from within `ctx` callbacks, which only fire during dispatch.
@@ -64,42 +60,14 @@ function formatLockHeld(holder: string): string {
return `Computer use is in use by another Claude session (${holder.slice(0, 8)}…). Wait for that session to finish or run /exit there.`;
}
-/**
- * Persist runtime-granted apps into the stored "always-allowed" list so they
- * show up in the Computer Use settings page. This is the AUTO-ADD half of the
- * user's "both ways" choice (the settings picker is the manual half). Fire-and-
- * forget + deduped by bundleId; a failure here must never break a turn.
- */
-async function persistGrantedAppsToConfig(
- apps: readonly { bundleId: string; displayName: string; grantedAt?: number }[],
-): Promise {
- try {
- if (apps.length === 0) return;
- const config = await loadStoredComputerUseConfig();
- const existing = new Set((config.authorizedApps ?? []).map(a => a.bundleId));
- const additions = apps.filter(a => !existing.has(a.bundleId));
- if (additions.length === 0) return;
- const now = Date.now();
- const merged: StoredAuthorizedApp[] = [
- ...(config.authorizedApps ?? []),
- ...additions.map(a => ({
- bundleId: a.bundleId,
- displayName: a.displayName,
- authorizedAt: new Date(a.grantedAt ?? now).toISOString(),
- })),
- ];
- await saveStoredComputerUseConfig({ ...config, authorizedApps: merged });
- } catch {
- // best-effort; never throw into the grant callback
- }
-}
-
export function buildSessionContext(): ComputerUseSessionContext {
return {
// ── Read state fresh via the per-call ref ─────────────────────────────
- getAllowedApps: () => tuc().getAppState().computerUseMcpState?.allowedApps ?? [],
- getGrantFlags: () => tuc().getAppState().computerUseMcpState?.grantFlags ?? DEFAULT_GRANT_FLAGS,
- // cc-2 has no Settings page for user-denied apps yet.
+ // App authorization is feature-wide once Computer Use is enabled. Keep
+ // returning the legacy shapes for protocol compatibility, but do not
+ // consume persisted app grants or open runtime permission prompts.
+ getAllowedApps: () => [],
+ getGrantFlags: () => ENABLED_GRANT_FLAGS,
getUserDeniedBundleIds: () => [],
getSelectedDisplayId: () => tuc().getAppState().computerUseMcpState?.selectedDisplayId,
getDisplayPinnedByModel: () => tuc().getAppState().computerUseMcpState?.displayPinnedByModel ?? false,
@@ -114,33 +82,6 @@ export function buildSessionContext(): ComputerUseSessionContext {
} : undefined;
},
// ── Write-backs ────────────────────────────────────────────────────────
- // `setToolJSX` is guaranteed present — the gate in `main.tsx` excludes
- // non-interactive sessions. The package's `_dialogSignal` (tool-finished
- // dismissal) is irrelevant here: `setToolJSX` blocks the tool call, so
- // the dialog can't outlive it. Ctrl+C is what matters, and
- // `runPermissionDialog` wires that from the per-call ref's abortController.
- onPermissionRequest: (req, _dialogSignal) => runPermissionDialog(req),
- // Package does the merge (dedupe + truthy-only flags). We just persist.
- onAllowedAppsChanged: (apps, flags) => {
- // Auto-add newly granted apps to the persistent always-allowed list so
- // they appear in the settings page (the runtime half of "both ways").
- void persistGrantedAppsToConfig(apps);
- tuc().setAppState(prev => {
- const cu = prev.computerUseMcpState;
- const prevApps = cu?.allowedApps;
- const prevFlags = cu?.grantFlags;
- const sameApps = prevApps?.length === apps.length && apps.every((a, i) => prevApps[i]?.bundleId === a.bundleId);
- const sameFlags = prevFlags?.clipboardRead === flags.clipboardRead && prevFlags?.clipboardWrite === flags.clipboardWrite && prevFlags?.systemKeyCombos === flags.systemKeyCombos;
- return sameApps && sameFlags ? prev : {
- ...prev,
- computerUseMcpState: {
- ...cu,
- allowedApps: [...apps],
- grantFlags: flags
- }
- };
- });
- },
onAppsHidden: ids => {
if (ids.length === 0) return;
tuc().setAppState(prev => {
@@ -271,88 +212,6 @@ export function buildSessionContext(): ComputerUseSessionContext {
};
}
-async function runDesktopPermissionDialog(
- req: CuPermissionRequest,
- signal: AbortSignal,
-): Promise {
- if (!desktopServerUrl) {
- throw new Error('Desktop server URL is not configured')
- }
-
- const response = await fetch(`${desktopServerUrl}/api/computer-use/request-access`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- sessionId: getSessionId(),
- request: req
- }),
- signal
- })
-
- if (!response.ok) {
- const message = await response.text().catch(() => '')
- throw new Error(
- `Desktop Computer Use approval failed (${response.status}): ${message || response.statusText}`,
- )
- }
-
- return await response.json() as CuPermissionResponse
-}
-
-/**
- * Load pre-authorized apps from ~/.claude/cc-haha/computer-use-config.json.
- * Called once when the binding is first created. Pre-authorized apps
- * are injected into appState so `getAllowedApps()` returns them
- * immediately — no runtime permission dialog needed.
- */
-async function loadPreAuthorizedApps(): Promise {
- if (!currentToolUseContext) {
- return
- }
-
- const resolved = await loadStoredComputerUseConfig()
- const apps = buildPreAuthorizedAppGrants(resolved.authorizedApps)
- const flags = {
- ...DEFAULT_GRANT_FLAGS,
- ...resolved.grantFlags,
- }
-
- // Inject into appState so getAllowedApps()/getGrantFlags() return persisted
- // desktop settings immediately, even when no apps are pre-authorized yet.
- currentToolUseContext.setAppState(prev => {
- const existing = prev.computerUseMcpState?.allowedApps ?? []
- const existingIds = new Set(existing.map(a => a.bundleId))
- const merged = [...existing, ...apps.filter(a => !existingIds.has(a.bundleId))]
- const currentFlags = prev.computerUseMcpState?.grantFlags
- const sameFlags =
- currentFlags?.clipboardRead === flags.clipboardRead &&
- currentFlags?.clipboardWrite === flags.clipboardWrite &&
- currentFlags?.systemKeyCombos === flags.systemKeyCombos
- const sameApps = existing.length === merged.length
-
- if (sameFlags && sameApps) {
- return prev
- }
-
- return {
- ...prev,
- computerUseMcpState: {
- ...prev.computerUseMcpState,
- allowedApps: merged,
- grantFlags: flags,
- },
- }
- })
-
- logForDebugging(
- `[Computer Use] Loaded ${apps.length} pre-authorized apps and grant flags from config`,
- )
-}
-
-let preAuthLoaded = false
-
function getOrBind(): Binding {
if (binding) return binding;
const ctx = buildSessionContext();
@@ -378,11 +237,6 @@ export function getComputerUseMCPToolOverrides(toolName: string): ComputerUseMCP
dispatch
} = getOrBind();
- // Load pre-authorized apps on first tool call
- if (!preAuthLoaded) {
- preAuthLoaded = true;
- await loadPreAuthorizedApps();
- }
const {
telemetry,
...result
@@ -418,63 +272,4 @@ export function getComputerUseMCPToolOverrides(toolName: string): ComputerUseMCP
};
}
-/**
- * Render the approval dialog mid-call via `setToolJSX` + `Promise`, wait for
- * the user. Mirrors `spawnMultiAgent.ts:419-436` (the `It2SetupPrompt` pattern).
- *
- * The merge-into-AppState that used to live here (dedupe + truthy-only flags)
- * is now in the package's `bindSessionContext` → `onAllowedAppsChanged`.
- */
-async function runPermissionDialog(req: CuPermissionRequest): Promise {
- const context = tuc();
- let desktopBridgeError: Error | undefined;
- if (desktopServerUrl) {
- try {
- return await runDesktopPermissionDialog(req, context.abortController.signal);
- } catch (error) {
- desktopBridgeError = error instanceof Error ? error : new Error(String(error));
- logForDebugging(`[Computer Use] Desktop approval bridge failed: ${desktopBridgeError.message}`);
- }
- }
- const setToolJSX = context.setToolJSX;
- if (!setToolJSX) {
- if (desktopBridgeError) {
- throw desktopBridgeError;
- }
- // Shouldn't happen — main.tsx gate excludes non-interactive. Fail safe.
- return {
- granted: [],
- denied: [],
- flags: DEFAULT_GRANT_FLAGS
- };
- }
- try {
- return await new Promise((resolve, reject) => {
- const signal = context.abortController.signal;
- // If already aborted, addEventListener won't fire — reject now so the
- // promise doesn't hang waiting for a user who Ctrl+C'd.
- if (signal.aborted) {
- reject(new Error('Computer Use permission dialog aborted'));
- return;
- }
- const onAbort = (): void => {
- signal.removeEventListener('abort', onAbort);
- reject(new Error('Computer Use permission dialog aborted'));
- };
- signal.addEventListener('abort', onAbort);
- setToolJSX({
- jsx: React.createElement(ComputerUseApproval, {
- request: req,
- onDone: (resp: CuPermissionResponse) => {
- signal.removeEventListener('abort', onAbort);
- resolve(resp);
- }
- }),
- shouldHidePromptInput: true
- });
- });
- } finally {
- setToolJSX(null);
- }
-}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJiaW5kU2Vzc2lvbkNvbnRleHQiLCJDb21wdXRlclVzZVNlc3Npb25Db250ZXh0IiwiQ3VDYWxsVG9vbFJlc3VsdCIsIkN1UGVybWlzc2lvblJlcXVlc3QiLCJDdVBlcm1pc3Npb25SZXNwb25zZSIsIkRFRkFVTFRfR1JBTlRfRkxBR1MiLCJTY3JlZW5zaG90RGltcyIsIlJlYWN0IiwiZ2V0U2Vzc2lvbklkIiwiQ29tcHV0ZXJVc2VBcHByb3ZhbCIsIlRvb2wiLCJUb29sVXNlQ29udGV4dCIsImxvZ0ZvckRlYnVnZ2luZyIsImNoZWNrQ29tcHV0ZXJVc2VMb2NrIiwidHJ5QWNxdWlyZUNvbXB1dGVyVXNlTG9jayIsInJlZ2lzdGVyRXNjSG90a2V5IiwiZ2V0Q2hpY2Fnb0Nvb3JkaW5hdGVNb2RlIiwiZ2V0Q29tcHV0ZXJVc2VIb3N0QWRhcHRlciIsImdldENvbXB1dGVyVXNlTUNQUmVuZGVyaW5nT3ZlcnJpZGVzIiwiQ2FsbE92ZXJyaWRlIiwiUGljayIsIkJpbmRpbmciLCJjdHgiLCJkaXNwYXRjaCIsIm5hbWUiLCJhcmdzIiwiUHJvbWlzZSIsImJpbmRpbmciLCJjdXJyZW50VG9vbFVzZUNvbnRleHQiLCJ0dWMiLCJmb3JtYXRMb2NrSGVsZCIsImhvbGRlciIsInNsaWNlIiwiYnVpbGRTZXNzaW9uQ29udGV4dCIsImdldEFsbG93ZWRBcHBzIiwiZ2V0QXBwU3RhdGUiLCJjb21wdXRlclVzZU1jcFN0YXRlIiwiYWxsb3dlZEFwcHMiLCJnZXRHcmFudEZsYWdzIiwiZ3JhbnRGbGFncyIsImdldFVzZXJEZW5pZWRCdW5kbGVJZHMiLCJnZXRTZWxlY3RlZERpc3BsYXlJZCIsInNlbGVjdGVkRGlzcGxheUlkIiwiZ2V0RGlzcGxheVBpbm5lZEJ5TW9kZWwiLCJkaXNwbGF5UGlubmVkQnlNb2RlbCIsImdldERpc3BsYXlSZXNvbHZlZEZvckFwcHMiLCJkaXNwbGF5UmVzb2x2ZWRGb3JBcHBzIiwiZ2V0TGFzdFNjcmVlbnNob3REaW1zIiwiZCIsImxhc3RTY3JlZW5zaG90RGltcyIsImRpc3BsYXlJZCIsIm9yaWdpblgiLCJvcmlnaW5ZIiwidW5kZWZpbmVkIiwib25QZXJtaXNzaW9uUmVxdWVzdCIsInJlcSIsIl9kaWFsb2dTaWduYWwiLCJydW5QZXJtaXNzaW9uRGlhbG9nIiwib25BbGxvd2VkQXBwc0NoYW5nZWQiLCJhcHBzIiwiZmxhZ3MiLCJzZXRBcHBTdGF0ZSIsInByZXYiLCJjdSIsInByZXZBcHBzIiwicHJldkZsYWdzIiwic2FtZUFwcHMiLCJsZW5ndGgiLCJldmVyeSIsImEiLCJpIiwiYnVuZGxlSWQiLCJzYW1lRmxhZ3MiLCJjbGlwYm9hcmRSZWFkIiwiY2xpcGJvYXJkV3JpdGUiLCJzeXN0ZW1LZXlDb21ib3MiLCJvbkFwcHNIaWRkZW4iLCJpZHMiLCJleGlzdGluZyIsImhpZGRlbkR1cmluZ1R1cm4iLCJpZCIsImhhcyIsIlNldCIsIm9uUmVzb2x2ZWREaXNwbGF5VXBkYXRlZCIsIm9uRGlzcGxheVBpbm5lZCIsInBpbm5lZCIsIm5leHRSZXNvbHZlZEZvciIsIm9uRGlzcGxheVJlc29sdmVkRm9yQXBwcyIsImtleSIsIm9uU2NyZWVuc2hvdENhcHR1cmVkIiwiZGltcyIsInAiLCJ3aWR0aCIsImhlaWdodCIsImRpc3BsYXlXaWR0aCIsImRpc3BsYXlIZWlnaHQiLCJjaGVja0N1TG9jayIsImMiLCJraW5kIiwiaXNTZWxmIiwiYnkiLCJhY3F1aXJlQ3VMb2NrIiwiciIsIkVycm9yIiwiZnJlc2giLCJlc2NSZWdpc3RlcmVkIiwiYWJvcnRDb250cm9sbGVyIiwiYWJvcnQiLCJzZW5kT1NOb3RpZmljYXRpb24iLCJtZXNzYWdlIiwibm90aWZpY2F0aW9uVHlwZSIsImZvcm1hdExvY2tIZWxkTWVzc2FnZSIsImdldE9yQmluZCIsIkNvbXB1dGVyVXNlTUNQVG9vbE92ZXJyaWRlcyIsIlJldHVyblR5cGUiLCJjYWxsIiwiZ2V0Q29tcHV0ZXJVc2VNQ1BUb29sT3ZlcnJpZGVzIiwidG9vbE5hbWUiLCJjb250ZXh0IiwidGVsZW1ldHJ5IiwicmVzdWx0IiwiZXJyb3Jfa2luZCIsImRhdGEiLCJBcnJheSIsImlzQXJyYXkiLCJjb250ZW50IiwibWFwIiwiaXRlbSIsInR5cGUiLCJjb25zdCIsInNvdXJjZSIsIm1lZGlhX3R5cGUiLCJtaW1lVHlwZSIsInRleHQiLCJzZXRUb29sSlNYIiwiZ3JhbnRlZCIsImRlbmllZCIsInJlc29sdmUiLCJyZWplY3QiLCJzaWduYWwiLCJhYm9ydGVkIiwib25BYm9ydCIsInJlbW92ZUV2ZW50TGlzdGVuZXIiLCJhZGRFdmVudExpc3RlbmVyIiwianN4IiwiY3JlYXRlRWxlbWVudCIsInJlcXVlc3QiLCJvbkRvbmUiLCJyZXNwIiwic2hvdWxkSGlkZVByb21wdElucHV0Il0sInNvdXJjZXMiOlsid3JhcHBlci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGUgYC5jYWxsKClgIG92ZXJyaWRlIOKAlCB0aGluIGFkYXB0ZXIgYmV0d2VlbiBgVG9vbFVzZUNvbnRleHRgIGFuZFxuICogYGJpbmRTZXNzaW9uQ29udGV4dGAuIFNwcmVhZCBpbnRvIHRoZSBNQ1AgdG9vbCBvYmplY3QgaW4gYGNsaWVudC50c2BcbiAqIChzYW1lIHBhdHRlcm4gYXMgQ2hyb21lJ3MgcmVuZGVyaW5nIG92ZXJyaWRlcywgcGx1cyBgLmNhbGwoKWApLlxuICpcbiAqIFRoZSB3cmFwcGVyLWNsb3N1cmUgbG9naWMgKGJ1aWxkIG92ZXJyaWRlcyBmcmVzaCwgbG9jayBnYXRlLCBwZXJtaXNzaW9uXG4gKiBtZXJnZSwgc2NyZWVuc2hvdCBzdGFzaCkgbGl2ZXMgaW4gYEBhbnQvY29tcHV0ZXItdXNlLW1jcGAnc1xuICogYGJpbmRTZXNzaW9uQ29udGV4dGAuIFRoaXMgZmlsZSBiaW5kcyBpdCBvbmNlIHBlciBwcm9jZXNzLFxuICogY2FjaGVzIHRoZSBkaXNwYXRjaGVyLCBhbmQgdXBkYXRlcyBhIHBlci1jYWxsIHJlZiBmb3IgdGhlIHBpZWNlcyBvZlxuICogYFRvb2xVc2VDb250ZXh0YCB0aGF0IHZhcnkgcGVyLWNhbGwgKGBhYm9ydENvbnRyb2xsZXJgLCBgc2V0VG9vbEpTWGAsXG4gKiBgc2VuZE9TTm90aWZpY2F0aW9uYCkuIEFwcFN0YXRlIGFjY2Vzc29ycyBhcmUgcmVhZCB0aHJvdWdoIHRoZSByZWYgdG9vIOKAlFxuICogdGhleSdyZSBsaWtlbHkgc3RhYmxlIGJ1dCB3ZSBkb24ndCBkZXBlbmQgb24gdGhhdC5cbiAqXG4gKiBFeHRlcm5hbCBjYWxsZXJzIHJlYWNoIHRoaXMgdmlhIHRoZSBsYXp5IHJlcXVpcmUgdGh1bmsgaW4gYGNsaWVudC50c2AsIGdhdGVkXG4gKiBvbiBgZmVhdHVyZSgnQ0hJQ0FHT19NQ1AnKWAuIFJ1bnRpbWUgZW5hYmxlbWVudCBpcyBjb250cm9sbGVkIGJ5IHRoZVxuICogR3Jvd3RoQm9vayBnYXRlIGB0ZW5ndV9tYWxvcnRfcGVkd2F5YCAoc2VlIGdhdGVzLnRzKS5cbiAqL1xuXG5pbXBvcnQge1xuICBiaW5kU2Vzc2lvbkNvbnRleHQsXG4gIHR5cGUgQ29tcHV0ZXJVc2VTZXNzaW9uQ29udGV4dCxcbiAgdHlwZSBDdUNhbGxUb29sUmVzdWx0LFxuICB0eXBlIEN1UGVybWlzc2lvblJlcXVlc3QsXG4gIHR5cGUgQ3VQZXJtaXNzaW9uUmVzcG9uc2UsXG4gIERFRkFVTFRfR1JBTlRfRkxBR1MsXG4gIHR5cGUgU2NyZWVuc2hvdERpbXMsXG59IGZyb20gJ0BhbnQvY29tcHV0ZXItdXNlLW1jcCdcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgZ2V0U2Vzc2lvbklkIH0gZnJvbSAnLi4vLi4vYm9vdHN0cmFwL3N0YXRlLmpzJ1xuaW1wb3J0IHsgQ29tcHV0ZXJVc2VBcHByb3ZhbCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvcGVybWlzc2lvbnMvQ29tcHV0ZXJVc2VBcHByb3ZhbC9Db21wdXRlclVzZUFwcHJvdmFsLmpzJ1xuaW1wb3J0IHR5cGUgeyBUb29sLCBUb29sVXNlQ29udGV4dCB9IGZyb20gJy4uLy4uL1Rvb2wuanMnXG5pbXBvcnQgeyBsb2dGb3JEZWJ1Z2dpbmcgfSBmcm9tICcuLi9kZWJ1Zy5qcydcbmltcG9ydCB7XG4gIGNoZWNrQ29tcHV0ZXJVc2VMb2NrLFxuICB0cnlBY3F1aXJlQ29tcHV0ZXJVc2VMb2NrLFxufSBmcm9tICcuL2NvbXB1dGVyVXNlTG9jay5qcydcbmltcG9ydCB7IHJlZ2lzdGVyRXNjSG90a2V5IH0gZnJvbSAnLi9lc2NIb3RrZXkuanMnXG5pbXBvcnQgeyBnZXRDaGljYWdvQ29vcmRpbmF0ZU1vZGUgfSBmcm9tICcuL2dhdGVzLmpzJ1xuaW1wb3J0IHsgZ2V0Q29tcHV0ZXJVc2VIb3N0QWRhcHRlciB9IGZyb20gJy4vaG9zdEFkYXB0ZXIuanMnXG5pbXBvcnQgeyBnZXRDb21wdXRlclVzZU1DUFJlbmRlcmluZ092ZXJyaWRlcyB9IGZyb20gJy4vdG9vbFJlbmRlcmluZy5qcydcblxudHlwZSBDYWxsT3ZlcnJpZGUgPSBQaWNrPFRvb2wsICdjYWxsJz5bJ2NhbGwnXVxuXG50eXBlIEJpbmRpbmcgPSB7XG4gIGN0eDogQ29tcHV0ZXJVc2VTZXNzaW9uQ29udGV4dFxuICBkaXNwYXRjaDogKG5hbWU6IHN0cmluZywgYXJnczogdW5rbm93bikgPT4gUHJvbWlzZTxDdUNhbGxUb29sUmVzdWx0PlxufVxuXG4vKipcbiAqIENhY2hlZCBiaW5kaW5nIOKAlCBidWlsdCBvbiBmaXJzdCBgLmNhbGwoKWAsIHJldXNlZCBmb3IgcHJvY2VzcyBsaWZldGltZS5cbiAqIFRoZSBkaXNwYXRjaGVyJ3MgY2xvc3VyZS1oZWxkIHNjcmVlbnNob3QgYmxvYiBwZXJzaXN0cyBhY3Jvc3MgY2FsbHMuXG4gKlxuICogYGN1cnJlbnRUb29sVXNlQ29udGV4dGAgaXMgdXBkYXRlZCBvbiBldmVyeSBjYWxsLiBFdmVyeSBnZXR0ZXIvY2FsbGJhY2sgaW5cbiAqIGBjdHhgIHJlYWRzIHRocm91Z2ggaXQsIHNvIHRoZSBwZXItY2FsbCBwaWVjZXMgKGBhYm9ydENvbnRyb2xsZXJgLFxuICogYHNldFRvb2xKU1hgLCBgc2VuZE9TTm90aWZpY2F0aW9uYCkgYXJlIGFsd2F5cyBjdXJyZW50LlxuICpcbiAqIE1vZHVsZS1sZXZlbCBgbGV0YCBpcyBhIGRlbGliZXJhdGUgZXhjZXB0aW9uIHRvIHRoZSBuby1tb2R1bGUtc2NvcGUtc3RhdGVcbiAqIHJ1bGUgKHNyYy9DTEFVREUubWQpOiB0aGUgZGlzcGF0Y2hlciBjbG9zdXJlIG11c3QgcGVyc2lzdCBhY3Jvc3MgY2FsbHMgc29cbiAqIGl0cyBpbnRlcm5hbCBzY3JlZW5zaG90IGJsb2Igc3Vydml2ZXMsIGJ1dCBgVG9vbFVzZUNvbnRleHRgIGlzIHBlci1jYWxsLlxuICogVGVzdHMgd2lsbCBuZWVkIHRvIGVpdGhlciBpbmplY3QgdGhlIGNhY2hlIG9yIHJ1biBzZXJpYWxseS5cbiAqL1xubGV0IGJpbmRpbmc6IEJpbmRpbmcgfCB1bmRlZmluZWRcbmxldCBjdXJyZW50VG9vbFVzZUNvbnRleHQ6IFRvb2xVc2VDb250ZXh0IHwgdW5kZWZpbmVkXG5cbmZ1bmN0aW9uIHR1YygpOiBUb29sVXNlQ29udGV4dCB7XG4gIC8vIFNhZmU6IGBiaW5kaW5nYCBpcyBvbmx5IHBvcHVsYXRlZCB3aGVuIGBjdXJyZW50VG9vbFVzZUNvbnRleHRgIGlzIHNldC5cbiAgLy8gQ2FsbGVkIG9ubHkgZnJvbSB3aXRoaW4gYGN0eGAgY2FsbGJhY2tzLCB3aGljaCBvbmx5IGZpcmUgZHVyaW5nIGRpc3BhdGNoLlxuICByZXR1cm4gY3VycmVudFRvb2xVc2VDb250ZXh0IVxufVxuXG5mdW5jdGlvbiBmb3JtYXRMb2NrSGVsZChob2xkZXI6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBgQ29tcHV0ZXIgdXNlIGlzIGluIHVzZSBieSBhbm90aGVyIENsYXVkZSBzZXNzaW9uICgke2hvbGRlci5zbGljZSgwLCA4KX3igKYpLiBXYWl0IGZvciB0aGF0IHNlc3Npb24gdG8gZmluaXNoIG9yIHJ1biAvZXhpdCB0aGVyZS5gXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBidWlsZFNlc3Npb25Db250ZXh0KCk6IENvbXB1dGVyVXNlU2Vzc2lvbkNvbnRleHQge1xuICByZXR1cm4ge1xuICAgIC8vIOKUgOKUgCBSZWFkIHN0YXRlIGZyZXNoIHZpYSB0aGUgcGVyLWNhbGwgcmVmIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICAgIGdldEFsbG93ZWRBcHBzOiAoKSA9PlxuICAgICAgdHVjKCkuZ2V0QXBwU3RhdGUoKS5jb21wdXRlclVzZU1jcFN0YXRlPy5hbGxvd2VkQXBwcyA/PyBbXSxcbiAgICBnZXRHcmFudEZsYWdzOiAoKSA9PlxuICAgICAgdHVjKCkuZ2V0QXBwU3RhdGUoKS5jb21wdXRlclVzZU1jcFN0YXRlPy5ncmFudEZsYWdzID8/XG4gICAgICBERUZBVUxUX0dSQU5UX0ZMQUdTLFxuICAgIC8vIGNjLTIgaGFzIG5vIFNldHRpbmdzIHBhZ2UgZm9yIHVzZXItZGVuaWVkIGFwcHMgeWV0LlxuICAgIGdldFVzZXJEZW5pZWRCdW5kbGVJZHM6ICgpID0+IFtdLFxuICAgIGdldFNlbGVjdGVkRGlzcGxheUlkOiAoKSA9PlxuICAgICAgdHVjKCkuZ2V0QXBwU3RhdGUoKS5jb21wdXRlclVzZU1jcFN0YXRlPy5zZWxlY3RlZERpc3BsYXlJZCxcbiAgICBnZXREaXNwbGF5UGlubmVkQnlNb2RlbDogKCkgPT5cbiAgICAgIHR1YygpLmdldEFwcFN0YXRlKCkuY29tcHV0ZXJVc2VNY3BTdGF0ZT8uZGlzcGxheVBpbm5lZEJ5TW9kZWwgPz8gZmFsc2UsXG4gICAgZ2V0RGlzcGxheVJlc29sdmVkRm9yQXBwczogKCkgPT5cbiAgICAgIHR1YygpLmdldEFwcFN0YXRlKCkuY29tcHV0ZXJVc2VNY3BTdGF0ZT8uZGlzcGxheVJlc29sdmVkRm9yQXBwcyxcbiAgICBnZXRMYXN0U2NyZWVuc2hvdERpbXM6ICgpOiBTY3JlZW5zaG90RGltcyB8IHVuZGVmaW5lZCA9PiB7XG4gICAgICBjb25zdCBkID0gdHVjKCkuZ2V0QXBwU3RhdGUoKS5jb21wdXRlclVzZU1jcFN0YXRlPy5sYXN0U2NyZWVuc2hvdERpbXNcbiAgICAgIHJldHVybiBkXG4gICAgICAgID8ge1xuICAgICAgICAgICAgLi4uZCxcbiAgICAgICAgICAgIGRpc3BsYXlJZDogZC5kaXNwbGF5SWQgPz8gMCxcbiAgICAgICAgICAgIG9yaWdpblg6IGQub3JpZ2luWCA/PyAwLFxuICAgICAgICAgICAgb3JpZ2luWTogZC5vcmlnaW5ZID8/IDAsXG4gICAgICAgICAgfVxuICAgICAgICA6IHVuZGVmaW5lZFxuICAgIH0sXG5cbiAgICAvLyDilIDilIAgV3JpdGUtYmFja3Mg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4gICAgLy8gYHNldFRvb2xKU1hgIGlzIGd1YXJhbnRlZWQgcHJlc2VudCDigJQgdGhlIGdhdGUgaW4gYG1haW4udHN4YCBleGNsdWRlc1xuICAgIC8vIG5vbi1pbnRlcmFjdGl2ZSBzZXNzaW9ucy4gVGhlIHBhY2thZ2UncyBgX2RpYWxvZ1NpZ25hbGAgKHRvb2wtZmluaXNoZWRcbiAgICAvLyBkaXNtaXNzYWwpIGlzIGlycmVsZXZhbnQgaGVyZTogYHNldFRvb2xKU1hgIGJsb2NrcyB0aGUgdG9vbCBjYWxsLCBzb1xuICAgIC8vIHRoZSBkaWFsb2cgY2FuJ3Qgb3V0bGl2ZSBpdC4gQ3RybCtDIGlzIHdoYXQgbWF0dGVycywgYW5kXG4gICAgLy8gYHJ1blBlcm1pc3Npb25EaWFsb2dgIHdpcmVzIHRoYXQgZnJvbSB0aGUgcGVyLWNhbGwgcmVmJ3MgYWJvcnRDb250cm9sbGVyLlxuICAgIG9uUGVybWlzc2lvblJlcXVlc3Q6IChyZXEsIF9kaWFsb2dTaWduYWwpID0+IHJ1blBlcm1pc3Npb25EaWFsb2cocmVxKSxcblxuICAgIC8vIFBhY2thZ2UgZG9lcyB0aGUgbWVyZ2UgKGRlZHVwZSArIHRydXRoeS1vbmx5IGZsYWdzKS4gV2UganVzdCBwZXJzaXN0LlxuICAgIG9uQWxsb3dlZEFwcHNDaGFuZ2VkOiAoYXBwcywgZmxhZ3MpID0+XG4gICAgICB0dWMoKS5zZXRBcHBTdGF0ZShwcmV2ID0+IHtcbiAgICAgICAgY29uc3QgY3UgPSBwcmV2LmNvbXB1dGVyVXNlTWNwU3RhdGVcbiAgICAgICAgY29uc3QgcHJldkFwcHMgPSBjdT8uYWxsb3dlZEFwcHNcbiAgICAgICAgY29uc3QgcHJldkZsYWdzID0gY3U/LmdyYW50RmxhZ3NcbiAgICAgICAgY29uc3Qgc2FtZUFwcHMgPVxuICAgICAgICAgIHByZXZBcHBzPy5sZW5ndGggPT09IGFwcHMubGVuZ3RoICYmXG4gICAgICAgICAgYXBwcy5ldmVyeSgoYSwgaSkgPT4gcHJldkFwcHNbaV0/LmJ1bmRsZUlkID09PSBhLmJ1bmRsZUlkKVxuICAgICAgICBjb25zdCBzYW1lRmxhZ3MgPVxuICAgICAgICAgIHByZXZGbGFncz8uY2xpcGJvYXJkUmVhZCA9PT0gZmxhZ3MuY2xpcGJvYXJkUmVhZCAmJlxuICAgICAgICAgIHByZXZGbGFncz8uY2xpcGJvYXJkV3JpdGUgPT09IGZsYWdzLmNsaXBib2FyZFdyaXRlICYmXG4gICAgICAgICAgcHJldkZsYWdzPy5zeXN0ZW1LZXlDb21ib3MgPT09IGZsYWdzLnN5c3RlbUtleUNvbWJvc1xuICAgICAgICByZXR1cm4gc2FtZUFwcHMgJiYgc2FtZUZsYWdzXG4gICAgICAgICAgPyBwcmV2XG4gICAgICAgICAgOiB7XG4gICAgICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgICAgIGNvbXB1dGVyVXNlTWNwU3RhdGU6IHtcbiAgICAgICAgICAgICAgICAuLi5jdSxcbiAgICAgICAgICAgICAgICBhbGxvd2VkQXBwczogWy4uLmFwcHNdLFxuICAgICAgICAgICAgICAgIGdyYW50RmxhZ3M6IGZsYWdzLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfVxuICAgICAgfSksXG5cbiAgICBvbkFwcHNIaWRkZW46IGlkcyA9PiB7XG4gICAgICBpZiAoaWRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuXG4gICAgICB0dWMoKS5zZXRBcHBTdGF0ZShwcmV2ID0+IHtcbiAgICAgICAgY29uc3QgY3UgPSBwcmV2LmNvbXB1dGVyVXNlTWNwU3RhdGVcbiAgICAgICAgY29uc3QgZXhpc3RpbmcgPSBjdT8uaGlkZGVuRHVyaW5nVHVyblxuICAgICAgICBpZiAoZXhpc3RpbmcgJiYgaWRzLmV2ZXJ5KGlkID0+IGV4aXN0aW5nLmhhcyhpZCkpKSByZXR1cm4gcHJldlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgY29tcHV0ZXJVc2VNY3BTdGF0ZToge1xuICAgICAgICAgICAgLi4uY3UsXG4gICAgICAgICAgICBoaWRkZW5EdXJpbmdUdXJuOiBuZXcgU2V0KFsuLi4oZXhpc3RpbmcgPz8gW10pLCAuLi5pZHNdKSxcbiAgICAgICAgICB9LFxuICAgICAgICB9XG4gICAgICB9KVxuICAgIH0sXG5cbiAgICAvLyBSZXNvbHZlciB3cml0ZWJhY2sgb25seSBmaXJlcyB1bmRlciBhIHBpbiB3aGVuIFN3aWZ0IGZlbGwgYmFjayB0byBtYWluXG4gICAgLy8gKHBpbm5lZCBkaXNwbGF5IHVucGx1Z2dlZCkg4oCUIHRoZSBwaW4gaXMgc2VtYW50aWNhbGx5IGRlYWQsIHNvIGNsZWFyIGl0XG4gICAgLy8gYW5kIHRoZSBhcHAtc2V0IGtleSBzbyB0aGUgY2hhc2UgY2hhaW4gcnVucyBuZXh0IHRpbWUuIFdoZW4gYXV0b1Jlc29sdmVcbiAgICAvLyB3YXMgdHJ1ZSwgb25EaXNwbGF5UmVzb2x2ZWRGb3JBcHBzIHJlLXNldHMgdGhlIGtleSBpbiB0aGUgc2FtZSB0aWNrLlxuICAgIG9uUmVzb2x2ZWREaXNwbGF5VXBkYXRlZDogaWQgPT5cbiAgICAgIHR1YygpLnNldEFwcFN0YXRlKHByZXYgPT4ge1xuICAgICAgICBjb25zdCBjdSA9IHByZXYuY29tcHV0ZXJVc2VNY3BTdGF0ZVxuICAgICAgICBpZiAoXG4gICAgICAgICAgY3U/LnNlbGVjdGVkRGlzcGxheUlkID09PSBpZCAmJlxuICAgICAgICAgICFjdS5kaXNwbGF5UGlubmVkQnlNb2RlbCAmJlxuICAgICAgICAgIGN1LmRpc3BsYXlSZXNvbHZlZEZvckFwcHMgPT09IHVuZGVmaW5lZFxuICAgICAgICApIHtcbiAgICAgICAgICByZXR1cm4gcHJldlxuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICBjb21wdXRlclVzZU1jcFN0YXRlOiB7XG4gICAgICAgICAgICAuLi5jdSxcbiAgICAgICAgICAgIHNlbGVjdGVkRGlzcGxheUlkOiBpZCxcbiAgICAgICAgICAgIGRpc3BsYXlQaW5uZWRCeU1vZGVsOiBmYWxzZSxcbiAgICAgICAgICAgIGRpc3BsYXlSZXNvbHZlZEZvckFwcHM6IHVuZGVmaW5lZCxcbiAgICAgICAgICB9LFxuICAgICAgICB9XG4gICAgICB9KSxcblxuICAgIC8vIHN3aXRjaF9kaXNwbGF5KG5hbWUpIHBpbnM7IHN3aXRjaF9kaXNwbGF5KFwiYXV0b1wiKSB1bnBpbnMgYW5kIGNsZWFycyB0aGVcbiAgICAvLyBhcHAtc2V0IGtleSBzbyB0aGUgbmV4dCBzY3JlZW5zaG90IGF1dG8tcmVzb2x2ZXMgZnJlc2guXG4gICAgb25EaXNwbGF5UGlubmVkOiBpZCA9PlxuICAgICAgdHVjKCkuc2V0QXBwU3RhdGUocHJldiA9PiB7XG4gICAgICAgIGNvbnN0IGN1ID0gcHJldi5jb21wdXRlclVzZU1jcFN0YXRlXG4gICAgICAgIGNvbnN0IHBpbm5lZCA9IGlkICE9PSB1bmRlZmluZWRcbiAgICAgICAgY29uc3QgbmV4dFJlc29sdmVkRm9yID0gcGlubmVkID8gY3U/LmRpc3BsYXlSZXNvbHZlZEZvckFwcHMgOiB1bmRlZmluZWRcbiAgICAgICAgaWYgKFxuICAgICAgICAgIGN1Py5zZWxlY3RlZERpc3BsYXlJZCA9PT0gaWQgJiZcbiAgICAgICAgICBjdT8uZGlzcGxheVBpbm5lZEJ5TW9kZWwgPT09IHBpbm5lZCAmJlxuICAgICAgICAgIGN1Py5kaXNwbGF5UmVzb2x2ZWRGb3JBcHBzID09PSBuZXh0UmVzb2x2ZWRGb3JcbiAgICAgICAgKSB7XG4gICAgICAgICAgcmV0dXJuIHByZXZcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgY29tcHV0ZXJVc2VNY3BTdGF0ZToge1xuICAgICAgICAgICAgLi4uY3UsXG4gICAgICAgICAgICBzZWxlY3RlZERpc3BsYXlJZDogaWQsXG4gICAgICAgICAgICBkaXNwbGF5UGlubmVkQnlNb2RlbDogcGlubmVkLFxuICAgICAgICAgICAgZGlzcGxheVJlc29sdmVkRm9yQXBwczogbmV4dFJlc29sdmVkRm9yLFxuICAgICAgICAgIH0sXG4gICAgICAgIH1cbiAgICAgIH0pLFxuXG4gICAgb25EaXNwbGF5UmVzb2x2ZWRGb3JBcHBzOiBrZXkgPT5cbiAgICAgIHR1YygpLnNldEFwcFN0YXRlKHByZXYgPT4ge1xuICAgICAgICBjb25zdCBjdSA9IHByZXYuY29tcHV0ZXJVc2VNY3BTdGF0ZVxuICAgICAgICBpZiAoY3U/LmRpc3BsYXlSZXNvbHZlZEZvckFwcHMgPT09IGtleSkgcmV0dXJuIHByZXZcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAuLi5wcmV2LFxuICAgICAgICAgIGNvbXB1dGVyVXNlTWNwU3RhdGU6IHsgLi4uY3UsIGRpc3BsYXlSZXNvbHZlZEZvckFwcHM6IGtleSB9LFxuICAgICAgICB9XG4gICAgICB9KSxcblxuICAgIG9uU2NyZWVuc2hvdENhcHR1cmVkOiBkaW1zID0+XG4gICAgICB0dWMoKS5zZXRBcHBTdGF0ZShwcmV2ID0+IHtcbiAgICAgICAgY29uc3QgY3UgPSBwcmV2LmNvbXB1dGVyVXNlTWNwU3RhdGVcbiAgICAgICAgY29uc3QgcCA9IGN1Py5sYXN0U2NyZWVuc2hvdERpbXNcbiAgICAgICAgcmV0dXJuIHA/LndpZHRoID09PSBkaW1zLndpZHRoICYmXG4gICAgICAgICAgcD8uaGVpZ2h0ID09PSBkaW1zLmhlaWdodCAmJlxuICAgICAgICAgIHA/LmRpc3BsYXlXaWR0aCA9PT0gZGltcy5kaXNwbGF5V2lkdGggJiZcbiAgICAgICAgICBwPy5kaXNwbGF5SGVpZ2h0ID09PSBkaW1zLmRpc3BsYXlIZWlnaHQgJiZcbiAgICAgICAgICBwPy5kaXNwbGF5SWQgPT09IGRpbXMuZGlzcGxheUlkICYmXG4gICAgICAgICAgcD8ub3JpZ2luWCA9PT0gZGltcy5vcmlnaW5YICYmXG4gICAgICAgICAgcD8ub3JpZ2luWSA9PT0gZGltcy5vcmlnaW5ZXG4gICAgICAgICAgPyBwcmV2XG4gICAgICAgICAgOiB7XG4gICAgICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgICAgIGNvbXB1dGVyVXNlTWNwU3RhdGU6IHsgLi4uY3UsIGxhc3RTY3JlZW5zaG90RGltczogZGltcyB9LFxuICAgICAgICAgICAgfVxuICAgICAgfSksXG5cbiAgICAvLyDilIDilIAgTG9jayDigJQgYXN5bmMsIGRpcmVjdCBmaWxlLWxvY2sgY2FsbHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4gICAgLy8gTm8gYGxvY2tIb2xkZXJGb3JHYXRlYCBkYW5jZTogdGhlIHBhY2thZ2UncyBnYXRlIGlzIGFzeW5jIG5vdy4gSXRcbiAgICAvLyBhd2FpdHMgYGNoZWNrQ3VMb2NrYCwgYW5kIG9uIGBob2xkZXI6IHVuZGVmaW5lZGAgKyBub24tZGVmZXJyaW5nIHRvb2xcbiAgICAvLyBhd2FpdHMgYGFjcXVpcmVDdUxvY2tgLiBgZGVmZXJzTG9ja0FjcXVpcmVgIGlzIHRoZSBQQUNLQUdFJ3Mgc2V0IOKAlFxuICAgIC8vIHRoZSBsb2NhbCBjb3B5IGlzIGdvbmUuXG4gICAgY2hlY2tDdUxvY2s6IGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGMgPSBhd2FpdCBjaGVja0NvbXB1dGVyVXNlTG9jaygpXG4gICAgICBzd2l0Y2ggKGMua2luZCkge1xuICAgICAgICBjYXNlICdmcmVlJzpcbiAgICAgICAgICByZXR1cm4geyBob2xkZXI6IHVuZGVmaW5lZCwgaXNTZWxmOiBmYWxzZSB9XG4gICAgICAgIGNhc2UgJ2hlbGRfYnlfc2VsZic6XG4gICAgICAgICAgcmV0dXJuIHsgaG9sZGVyOiBnZXRTZXNzaW9uSWQoKSwgaXNTZWxmOiB0cnVlIH1cbiAgICAgICAgY2FzZSAnYmxvY2tlZCc6XG4gICAgICAgICAgcmV0dXJuIHsgaG9sZGVyOiBjLmJ5LCBpc1NlbGY6IGZhbHNlIH1cbiAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gQ2FsbGVkIG9ubHkgd2hlbiBjaGVja0N1TG9jayByZXR1cm5lZCBgaG9sZGVyOiB1bmRlZmluZWRgLiBUaGUgT19FWENMXG4gICAgLy8gYWNxdWlyZSBpcyBhdG9taWMg4oCUIGlmIGFub3RoZXIgcHJvY2VzcyBncmFiYmVkIGl0IGluIHRoZSBnYXAgKHJhcmUpLFxuICAgIC8vIHRocm93IHNvIHRoZSB0b29sIGZhaWxzIGluc3RlYWQgb2YgcHJvY2VlZGluZyB3aXRob3V0IHRoZSBsb2NrLlxuICAgIC8vIGBmcmVzaDogZmFsc2VgIChyZS1lbnRyYW50KSBzaG91bGRuJ3QgaGFwcGVuIGdpdmVuIGNoZWNrIHNhaWQgZnJlZSxcbiAgICAvLyBidXQgaXMgcG9zc2libGUgdW5kZXIgcGFyYWxsZWwgdG9vbC11c2UgaW50ZXJsZWF2aW5nIOKAlCBkb24ndCBzcGFtIHRoZVxuICAgIC8vIG5vdGlmaWNhdGlvbiBpbiB0aGF0IGNhc2UuXG4gICAgYWNxdWlyZUN1TG9jazogYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgciA9IGF3YWl0IHRyeUFjcXVpcmVDb21wdXRlclVzZUxvY2soKVxuICAgICAgaWYgKHIua2luZCA9PT0gJ2Jsb2NrZWQnKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihmb3JtYXRMb2NrSGVsZChyLmJ5KSlcbiAgICAgIH1cbiAgICAgIGlmIChyLmZyZXNoKSB7XG4gICAgICAgIC8vIEdsb2JhbCBFc2NhcGUg4oaSIGFib3J0LiBDb25zdW1lcyB0aGUgZXZlbnQgKFBJIGRlZmVuc2Ug4oCUIHByb21wdFxuICAgICAgICAvLyBpbmplY3Rpb24gY2FuJ3QgZGlzbWlzcyBkaWFsb2dzIHdpdGggRXNjYXBlKS4gVGhlIENHRXZlbnRUYXAnc1xuICAgICAgICAvLyBDRlJ1bkxvb3BTb3VyY2UgaXMgcHJvY2Vzc2VkIGJ5IHRoZSBkcmFpblJ1bkxvb3AgcHVtcCwgc28gdGhpc1xuICAgICAgICAvLyBob2xkcyBhIHB1bXAgcmV0YWluIHVudGlsIHVucmVnaXN0ZXJFc2NIb3RrZXkoKSBpbiBjbGVhbnVwLnRzLlxuICAgICAgICBjb25zdCBlc2NSZWdpc3RlcmVkID0gcmVnaXN0ZXJFc2NIb3RrZXkoKCkgPT4ge1xuICAgICAgICAgIGxvZ0ZvckRlYnVnZ2luZygnW2N1LWVzY10gdXNlciBlc2NhcGUsIGFib3J0aW5nIHR1cm4nKVxuICAgICAgICAgIHR1YygpLmFib3J0Q29udHJvbGxlci5hYm9ydCgpXG4gICAgICAgIH0pXG4gICAgICAgIHR1YygpLnNlbmRPU05vdGlmaWNhdGlvbj8uKHtcbiAgICAgICAgICBtZXNzYWdlOiBlc2NSZWdpc3RlcmVkXG4gICAgICAgICAgICA/ICdDbGF1ZGUgaXMgdXNpbmcgeW91ciBjb21wdXRlciDCtyBwcmVzcyBFc2MgdG8gc3RvcCdcbiAgICAgICAgICAgIDogJ0NsYXVkZSBpcyB1c2luZyB5b3VyIGNvbXB1dGVyIMK3IHByZXNzIEN0cmwrQyB0byBzdG9wJyxcbiAgICAgICAgICBub3RpZmljYXRpb25UeXBlOiAnY29tcHV0ZXJfdXNlX2VudGVyJyxcbiAgICAgICAgfSlcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgZm9ybWF0TG9ja0hlbGRNZXNzYWdlOiBmb3JtYXRMb2NrSGVsZCxcbiAgfVxufVxuXG5mdW5jdGlvbiBnZXRPckJpbmQoKTogQmluZGluZyB7XG4gIGlmIChiaW5kaW5nKSByZXR1cm4gYmluZGluZ1xuICBjb25zdCBjdHggPSBidWlsZFNlc3Npb25Db250ZXh0KClcbiAgYmluZGluZyA9IHtcbiAgICBjdHgsXG4gICAgZGlzcGF0Y2g6IGJpbmRTZXNzaW9uQ29udGV4dChcbiAgICAgIGdldENvbXB1dGVyVXNlSG9zdEFkYXB0ZXIoKSxcbiAgICAgIGdldENoaWNhZ29Db29yZGluYXRlTW9kZSgpLFxuICAgICAgY3R4LFxuICAgICksXG4gIH1cbiAgcmV0dXJuIGJpbmRpbmdcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBmdWxsIG92ZXJyaWRlIG9iamVjdCBmb3IgYSBzaW5nbGUgYG1jcF9fY29tcHV0ZXItdXNlX197dG9vbE5hbWV9YFxuICogdG9vbDogcmVuZGVyaW5nIG92ZXJyaWRlcyBmcm9tIGB0b29sUmVuZGVyaW5nLnRzeGAgcGx1cyBhIGAuY2FsbCgpYCB0aGF0XG4gKiBkaXNwYXRjaGVzIHRocm91Z2ggdGhlIGNhY2hlZCBiaW5kZXIuXG4gKi9cbnR5cGUgQ29tcHV0ZXJVc2VNQ1BUb29sT3ZlcnJpZGVzID0gUmV0dXJuVHlwZTxcbiAgdHlwZW9mIGdldENvbXB1dGVyVXNlTUNQUmVuZGVyaW5nT3ZlcnJpZGVzXG4+ICYge1xuICBjYWxsOiBDYWxsT3ZlcnJpZGVcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldENvbXB1dGVyVXNlTUNQVG9vbE92ZXJyaWRlcyhcbiAgdG9vbE5hbWU6IHN0cmluZyxcbik6IENvbXB1dGVyVXNlTUNQVG9vbE92ZXJyaWRlcyB7XG4gIGNvbnN0IGNhbGw6IENhbGxPdmVycmlkZSA9IGFzeW5jIChhcmdzLCBjb250ZXh0OiBUb29sVXNlQ29udGV4dCkgPT4ge1xuICAgIGN1cnJlbnRUb29sVXNlQ29udGV4dCA9IGNvbnRleHRcbiAgICBjb25zdCB7IGRpc3BhdGNoIH0gPSBnZXRPckJpbmQoKVxuXG4gICAgY29uc3QgeyB0ZWxlbWV0cnksIC4uLnJlc3VsdCB9ID0gYXdhaXQgZGlzcGF0Y2godG9vbE5hbWUsIGFyZ3MpXG5cbiAgICBpZiAodGVsZW1ldHJ5Py5lcnJvcl9raW5kKSB7XG4gICAgICBsb2dGb3JEZWJ1Z2dpbmcoXG4gICAgICAgIGBbQ29tcHV0ZXIgVXNlIE1DUF0gJHt0b29sTmFtZX0gZXJyb3Jfa2luZD0ke3RlbGVtZXRyeS5lcnJvcl9raW5kfWAsXG4gICAgICApXG4gICAgfVxuXG4gICAgLy8gTUNQIGNvbnRlbnQgYmxvY2tzIOKGkiBBbnRocm9waWMgQVBJIGJsb2Nrcy4gQ1Ugb25seSBwcm9kdWNlcyB0ZXh0IGFuZFxuICAgIC8vIHByZS1zaXplZCBKUEVHIChleGVjdXRvci50cyBjb21wdXRlVGFyZ2V0RGltcyDihpIgdGFyZ2V0SW1hZ2VTaXplKSwgc29cbiAgICAvLyB1bmxpa2UgdGhlIGdlbmVyaWMgTUNQIHBhdGggdGhlcmUncyBubyByZXNpemUgbmVlZGVkIOKAlCB0aGUgTUNQIGltYWdlXG4gICAgLy8gc2hhcGUganVzdCBtYXBzIHRvIHRoZSBBUEkncyBiYXNlNjQtc291cmNlIHNoYXBlLiBUaGUgcGFja2FnZSdzIHJlc3VsdFxuICAgIC8vIHR5cGUgYWRtaXRzIGF1ZGlvL3Jlc291cmNlIHRvbywgYnV0IENVJ3MgaGFuZGxlVG9vbENhbGwgbmV2ZXIgZW1pdHNcbiAgICAvLyB0aG9zZTsgdGhlIGZhbGx0aHJvdWdoIGNvZXJjZXMgdGhlbSB0byBlbXB0eSB0ZXh0LlxuICAgIGNvbnN0IGRhdGEgPSBBcnJheS5pc0FycmF5KHJlc3VsdC5jb250ZW50KVxuICAgICAgPyByZXN1bHQuY29udGVudC5tYXAoaXRlbSA9PlxuICAgICAgICAgIGl0ZW0udHlwZSA9PT0gJ2ltYWdlJ1xuICAgICAgICAgICAgPyB7XG4gICAgICAgICAgICAgICAgdHlwZTogJ2ltYWdlJyBhcyBjb25zdCxcbiAgICAgICAgICAgICAgICBzb3VyY2U6IHtcbiAgICAgICAgICAgICAgICAgIHR5cGU6ICdiYXNlNjQnIGFzIGNvbnN0LFxuICAgICAgICAgICAgICAgICAgbWVkaWFfdHlwZTogaXRlbS5taW1lVHlwZSA/PyAnaW1hZ2UvanBlZycsXG4gICAgICAgICAgICAgICAgICBkYXRhOiBpdGVtLmRhdGEsXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgOiB7XG4gICAgICAgICAgICAgICAgdHlwZTogJ3RleHQnIGFzIGNvbnN0LFxuICAgICAgICAgICAgICAgIHRleHQ6IGl0ZW0udHlwZSA9PT0gJ3RleHQnID8gaXRlbS50ZXh0IDogJycsXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgIClcbiAgICAgIDogcmVzdWx0LmNvbnRlbnRcbiAgICByZXR1cm4geyBkYXRhIH1cbiAgfVxuXG4gIHJldHVybiB7XG4gICAgLi4uZ2V0Q29tcHV0ZXJVc2VNQ1BSZW5kZXJpbmdPdmVycmlkZXModG9vbE5hbWUpLFxuICAgIGNhbGwsXG4gIH1cbn1cblxuLyoqXG4gKiBSZW5kZXIgdGhlIGFwcHJvdmFsIGRpYWxvZyBtaWQtY2FsbCB2aWEgYHNldFRvb2xKU1hgICsgYFByb21pc2VgLCB3YWl0IGZvclxuICogdGhlIHVzZXIuIE1pcnJvcnMgYHNwYXduTXVsdGlBZ2VudC50czo0MTktNDM2YCAodGhlIGBJdDJTZXR1cFByb21wdGAgcGF0dGVybikuXG4gKlxuICogVGhlIG1lcmdlLWludG8tQXBwU3RhdGUgdGhhdCB1c2VkIHRvIGxpdmUgaGVyZSAoZGVkdXBlICsgdHJ1dGh5LW9ubHkgZmxhZ3MpXG4gKiBpcyBub3cgaW4gdGhlIHBhY2thZ2UncyBgYmluZFNlc3Npb25Db250ZXh0YCDihpIgYG9uQWxsb3dlZEFwcHNDaGFuZ2VkYC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gcnVuUGVybWlzc2lvbkRpYWxvZyhcbiAgcmVxOiBDdVBlcm1pc3Npb25SZXF1ZXN0LFxuKTogUHJvbWlzZTxDdVBlcm1pc3Npb25SZXNwb25zZT4ge1xuICBjb25zdCBjb250ZXh0ID0gdHVjKClcbiAgY29uc3Qgc2V0VG9vbEpTWCA9IGNvbnRleHQuc2V0VG9vbEpTWFxuICBpZiAoIXNldFRvb2xKU1gpIHtcbiAgICAvLyBTaG91bGRuJ3QgaGFwcGVuIOKAlCBtYWluLnRzeCBnYXRlIGV4Y2x1ZGVzIG5vbi1pbnRlcmFjdGl2ZS4gRmFpbCBzYWZlLlxuICAgIHJldHVybiB7IGdyYW50ZWQ6IFtdLCBkZW5pZWQ6IFtdLCBmbGFnczogREVGQVVMVF9HUkFOVF9GTEFHUyB9XG4gIH1cblxuICB0cnkge1xuICAgIHJldHVybiBhd2FpdCBuZXcgUHJvbWlzZTxDdVBlcm1pc3Npb25SZXNwb25zZT4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3Qgc2lnbmFsID0gY29udGV4dC5hYm9ydENvbnRyb2xsZXIuc2lnbmFsXG4gICAgICAvLyBJZiBhbHJlYWR5IGFib3J0ZWQsIGFkZEV2ZW50TGlzdGVuZXIgd29uJ3QgZmlyZSDigJQgcmVqZWN0IG5vdyBzbyB0aGVcbiAgICAgIC8vIHByb21pc2UgZG9lc24ndCBoYW5nIHdhaXRpbmcgZm9yIGEgdXNlciB3aG8gQ3RybCtDJ2QuXG4gICAgICBpZiAoc2lnbmFsLmFib3J0ZWQpIHtcbiAgICAgICAgcmVqZWN0KG5ldyBFcnJvcignQ29tcHV0ZXIgVXNlIHBlcm1pc3Npb24gZGlhbG9nIGFib3J0ZWQnKSlcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG4gICAgICBjb25zdCBvbkFib3J0ID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBzaWduYWwucmVtb3ZlRXZlbnRMaXN0ZW5lcignYWJvcnQnLCBvbkFib3J0KVxuICAgICAgICByZWplY3QobmV3IEVycm9yKCdDb21wdXRlciBVc2UgcGVybWlzc2lvbiBkaWFsb2cgYWJvcnRlZCcpKVxuICAgICAgfVxuICAgICAgc2lnbmFsLmFkZEV2ZW50TGlzdGVuZXIoJ2Fib3J0Jywgb25BYm9ydClcblxuICAgICAgc2V0VG9vbEpTWCh7XG4gICAgICAgIGpzeDogUmVhY3QuY3JlYXRlRWxlbWVudChDb21wdXRlclVzZUFwcHJvdmFsLCB7XG4gICAgICAgICAgcmVxdWVzdDogcmVxLFxuICAgICAgICAgIG9uRG9uZTogKHJlc3A6IEN1UGVybWlzc2lvblJlc3BvbnNlKSA9PiB7XG4gICAgICAgICAgICBzaWduYWwucmVtb3ZlRXZlbnRMaXN0ZW5lcignYWJvcnQnLCBvbkFib3J0KVxuICAgICAgICAgICAgcmVzb2x2ZShyZXNwKVxuICAgICAgICAgIH0sXG4gICAgICAgIH0pLFxuICAgICAgICBzaG91bGRIaWRlUHJvbXB0SW5wdXQ6IHRydWUsXG4gICAgICB9KVxuICAgIH0pXG4gIH0gZmluYWxseSB7XG4gICAgc2V0VG9vbEpTWChudWxsKVxuICB9XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsU0FDRUEsa0JBQWtCLEVBQ2xCLEtBQUtDLHlCQUF5QixFQUM5QixLQUFLQyxnQkFBZ0IsRUFDckIsS0FBS0MsbUJBQW1CLEVBQ3hCLEtBQUtDLG9CQUFvQixFQUN6QkMsbUJBQW1CLEVBQ25CLEtBQUtDLGNBQWMsUUFDZCx1QkFBdUI7QUFDOUIsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxZQUFZLFFBQVEsMEJBQTBCO0FBQ3ZELFNBQVNDLG1CQUFtQixRQUFRLHlFQUF5RTtBQUM3RyxjQUFjQyxJQUFJLEVBQUVDLGNBQWMsUUFBUSxlQUFlO0FBQ3pELFNBQVNDLGVBQWUsUUFBUSxhQUFhO0FBQzdDLFNBQ0VDLG9CQUFvQixFQUNwQkMseUJBQXlCLFFBQ3BCLHNCQUFzQjtBQUM3QixTQUFTQyxpQkFBaUIsUUFBUSxnQkFBZ0I7QUFDbEQsU0FBU0Msd0JBQXdCLFFBQVEsWUFBWTtBQUNyRCxTQUFTQyx5QkFBeUIsUUFBUSxrQkFBa0I7QUFDNUQsU0FBU0MsbUNBQW1DLFFBQVEsb0JBQW9CO0FBRXhFLEtBQUtDLFlBQVksR0FBR0MsSUFBSSxDQUFDVixJQUFJLEVBQUUsTUFBTSxDQUFDLENB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QUFtQixDQUN6QixFQUFFdUIsT0FBTyxDQUFDdEIsb0JBQW9CLENBQUMsQ0FBQztFQUMvQixNQUFNa0gsT0FBTyxHQUFHekYsR0FBRyxDQUFDLENBQUM7RUFDckIsTUFBTXlHLFVBQVUsR0FBR2hCLE9BQU8sQ0FBQ2dCLFVBQVU7RUFDckMsSUFBSSxDQUFDQSxVQUFVLEVBQUU7SUFDZjtJQUNBLE9BQU87TUFBRUMsT0FBTyxFQUFFLEVBQUU7TUFBRUMsTUFBTSxFQUFFLEVBQUU7TUFBRTVFLEtBQUssRUFBRXZEO0lBQW9CLENBQUM7RUFDaEU7RUFFQSxJQUFJO0lBQ0YsT0FBTyxNQUFNLElBQUlxQixPQUFPLENBQUN0QixvQkFBb0IsQ0FBQyxDQUFDLENBQUNxSSxPQUFPLEVBQUVDLE1BQU0sS0FBSztNQUNsRSxNQUFNQyxNQUFNLEdBQUdyQixPQUFPLENBQUNaLGVBQWUsQ0FBQ2lDLE1BQU07TUFDN0M7TUFDQTtNQUNBLElBQUlBLE1BQU0sQ0FBQ0MsT0FBTyxFQUFFO1FBQ2xCRixNQUFNLENBQUMsSUFBSW5DLEtBQUssQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO1FBQzNEO01BQ0Y7TUFDQSxNQUFNc0MsT0FBTyxHQUFHQSxDQUFBLENBQUUsRUFBRSxJQUFJLElBQUk7UUFDMUJGLE1BQU0sQ0FBQ0csbUJBQW1CLENBQUMsT0FBTyxFQUFFRCxPQUFPLENBQUM7UUFDNUNILE1BQU0sQ0FBQyxJQUFJbkMsS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7TUFDN0QsQ0FBQztNQUNEb0MsTUFBTSxDQUFDSSxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUVGLE9BQU8sQ0FBQztNQUV6Q1AsVUFBVSxDQUFDO1FBQ1RVLEdBQUcsRUFBRXpJLEtBQUssQ0FBQzBJLGFBQWEsQ0FBQ3hJLG1CQUFtQixFQUFFO1VBQzVDeUksT0FBTyxFQUFFM0YsR0FBRztVQUNaNEYsTUFBTSxFQUFFQSxDQUFDQyxJQUFJLEVBQUVoSixvQkFBb0IsS0FBSztZQUN0Q3VJLE1BQU0sQ0FBQ0csbUJBQW1CLENBQUMsT0FBTyxFQUFFRCxPQUFPLENBQUM7WUFDNUNKLE9BQU8sQ0FBQ1csSUFBSSxDQUFDO1VBQ2Y7UUFDRixDQUFDLENBQUM7UUFDRkMscUJBQXFCLEVBQUU7TUFDekIsQ0FBQyxDQUFDO0lBQ0osQ0FBQyxDQUFDO0VBQ0osQ0FBQyxTQUFTO0lBQ1JmLFVBQVUsQ0FBQyxJQUFJLENBQUM7RUFDbEI7QUFDRiIsImlnbm9yZUxpc3QiOltdfQ==
diff --git a/src/vendor/computer-use-mcp/platformRouting.test.ts b/src/vendor/computer-use-mcp/platformRouting.test.ts
index 7005c5de..7d9798fa 100644
--- a/src/vendor/computer-use-mcp/platformRouting.test.ts
+++ b/src/vendor/computer-use-mcp/platformRouting.test.ts
@@ -28,7 +28,6 @@ const DARWIN_TOOL_NAMES = [
].sort()
const WINDOWS_LEGACY_TOOL_NAMES = [
- 'request_access',
'screenshot',
'zoom',
'left_click',
@@ -43,7 +42,6 @@ const WINDOWS_LEGACY_TOOL_NAMES = [
'mouse_move',
'open_application',
'switch_display',
- 'list_granted_applications',
'read_clipboard',
'write_clipboard',
'wait',
@@ -69,14 +67,7 @@ function makeSessionContext(
overrides: Partial = {},
): ComputerUseSessionContext {
return {
- getAllowedApps: () => [
- {
- bundleId: 'com.example.allowed',
- displayName: 'Allowed App',
- tier: 'full',
- grantedAt: 1,
- },
- ],
+ getAllowedApps: () => [],
getGrantFlags: () => ({
clipboardRead: false,
clipboardWrite: false,
@@ -84,15 +75,9 @@ function makeSessionContext(
}),
getUserDeniedBundleIds: () => [],
getSelectedDisplayId: () => undefined,
- onPermissionRequest: async () => ({
- granted: [],
- denied: [],
- flags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
- },
- }),
+ onPermissionRequest: async () => {
+ throw new Error('per-app permission prompts must not run')
+ },
...overrides,
}
}
@@ -266,7 +251,9 @@ function makeWindowsAdapter(calls: string[]): ComputerUseHostAdapter {
},
]
},
- async openApp() {},
+ async openApp(bundleId: string) {
+ calls.push(`openApp:${bundleId}`)
+ },
} as unknown as ComputerExecutor
return {
@@ -341,7 +328,7 @@ describe('Computer Use platform routing', () => {
}
})
- test('win32 ListTools advertises the complete legacy pixel face', async () => {
+ test('win32 ListTools advertises pixel controls without app-authorization tools', async () => {
const connection = await connect(makeWindowsAdapter([]))
try {
const result = await connection.client.listTools()
@@ -349,6 +336,8 @@ describe('Computer Use platform routing', () => {
WINDOWS_LEGACY_TOOL_NAMES,
)
expect(result.tools.some(tool => tool.name === 'get_app_state')).toBe(false)
+ expect(result.tools.some(tool => tool.name === 'request_access')).toBe(false)
+ expect(result.tools.some(tool => tool.name === 'list_granted_applications')).toBe(false)
} finally {
await connection.close()
}
@@ -361,8 +350,6 @@ describe('Computer Use platform routing', () => {
makeSessionContext(),
)
try {
- // Two captures make the second hidden-app note exercise
- // executor.listRunningApps (the Python `list_running_apps` handler).
expect((await connection.client.callTool({ name: 'screenshot' })).isError).toBeFalsy()
expect((await connection.client.callTool({ name: 'screenshot' })).isError).toBeFalsy()
expect(
@@ -391,7 +378,7 @@ describe('Computer Use platform routing', () => {
).toBeFalsy()
expect(calls).toContain('screenshot')
- expect(calls).toContain('listRunningApps')
+ expect(calls).not.toContain('listRunningApps')
expect(calls).toContain('click:10,20,left,1')
expect(calls).toContain('key:ctrl+a')
expect(calls).toContain('type:ok')
@@ -410,6 +397,13 @@ describe('Computer Use platform routing', () => {
expect(blockedHold.isError).toBe(true)
expect(calls).not.toContain('key:ctrl+alt+delete')
expect(calls).not.toContain('holdKey:alt+f4')
+
+ const opened = await connection.client.callTool({
+ name: 'open_application',
+ arguments: { app: 'Allowed App' },
+ })
+ expect(opened.isError).toBeFalsy()
+ expect(calls).toContain('openApp:com.example.allowed')
} finally {
await connection.close()
}
@@ -448,20 +442,6 @@ describe('Computer Use platform routing', () => {
}),
)
try {
- const access = await connection.client.callTool({
- name: 'request_access',
- arguments: {
- apps: ['Allowed App'],
- reason: 'Exercise Windows lock routing.',
- },
- })
- const list = await connection.client.callTool({
- name: 'list_granted_applications',
- })
- expect(access.isError).toBeFalsy()
- expect(list.isError).toBeFalsy()
- expect(acquireCount).toBe(0)
-
// The first action takes the lock and must clear the prior session's
// module-level mouseButtonHeld flag before dispatching.
const screenshot = await connection.client.callTool({ name: 'screenshot' })
diff --git a/src/vendor/computer-use-mcp/toolCalls.test.ts b/src/vendor/computer-use-mcp/toolCalls.test.ts
index bac77d96..aaf54a13 100644
--- a/src/vendor/computer-use-mcp/toolCalls.test.ts
+++ b/src/vendor/computer-use-mcp/toolCalls.test.ts
@@ -805,9 +805,9 @@ describe('handleToolCall — gates', () => {
}
})
- test('resolves alias and PID targets before permission, then dispatches to the canonical running PID', async () => {
+ test('resolves alias and PID targets and dispatches without an app permission prompt', async () => {
for (const requestedApp of ['Notes', '812']) {
- let permissionRequest: any
+ let permissionCalls = 0
const { engine, calls } = makeEngine({
resolveTarget: async (target: AppTarget) => {
expect(target).toEqual(requestedApp === '812' ? { pid: 812 } : { app: 'Notes' })
@@ -833,37 +833,15 @@ describe('handleToolCall — gates', () => {
{ app: requestedApp, element_index: 'g17:1' },
baseOverrides({
allowedApps: [],
- onPermissionRequest: async req => {
- permissionRequest = req
- return {
- granted: [{
- bundleId: 'com.apple.Notes',
- displayName: 'Notes',
- grantedAt: 2,
- tier: 'full',
- }],
- denied: [],
- flags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
- },
- }
+ onPermissionRequest: async () => {
+ permissionCalls++
+ throw new Error('must not prompt')
},
}),
)
expect(r.isError).toBeFalsy()
- expect(permissionRequest.apps).toHaveLength(1)
- expect(permissionRequest.apps[0]).toMatchObject({
- requestedName: requestedApp,
- alreadyGranted: false,
- resolved: {
- bundleId: 'com.apple.Notes',
- displayName: 'Notes',
- path: '/System/Applications/Notes.app',
- },
- })
+ expect(permissionCalls).toBe(0)
expect(calls.map(call => call.method)).toEqual(['resolveTarget', 'click'])
expect(calls[1].args).toMatchObject({ target: { pid: 812 } })
}
@@ -898,8 +876,9 @@ describe('handleToolCall — gates', () => {
expect(calls.map(call => call.method)).toEqual(['resolveTarget'])
})
- test('authorizes an installed path without launching it and dispatches get_app_state by exact path', async () => {
+ test('dispatches get_app_state by exact installed path without an app prompt', async () => {
const path = '/Applications/Acme Notes.app'
+ let permissionCalls = 0
const { engine, calls } = makeEngine({
resolveTarget: async () => ({
bundleId: 'com.acme.notes',
@@ -913,67 +892,20 @@ describe('handleToolCall — gates', () => {
{ app: path },
baseOverrides({
allowedApps: [],
- onPermissionRequest: async req => ({
- granted: [{
- bundleId: req.apps[0].resolved!.bundleId,
- displayName: req.apps[0].resolved!.displayName,
- grantedAt: 2,
- tier: 'full',
- }],
- denied: [],
- flags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
- },
- }),
+ onPermissionRequest: async () => {
+ permissionCalls++
+ throw new Error('must not prompt')
+ },
}),
)
expect(r.isError).toBeFalsy()
+ expect(permissionCalls).toBe(0)
expect(calls.map(call => call.method)).toEqual(['resolveTarget', 'getAppState'])
expect(calls[1].args).toEqual({ app: path })
})
- test('a permission denial or mismatched grant never reaches the mutation engine', async () => {
- for (const granted of [[], [{
- bundleId: 'com.other.app',
- displayName: 'Other',
- grantedAt: 2,
- tier: 'full' as const,
- }]]) {
- const { engine, calls } = makeEngine({
- resolveTarget: async () => ({
- pid: 812,
- bundleId: 'com.apple.Notes',
- displayName: 'Notes',
- }),
- })
- const r = await handleToolCall(
- makeAdapter({ engine }),
- 'type_text',
- { app: 'Notes', text: 'secret' },
- baseOverrides({
- allowedApps: [],
- onPermissionRequest: async () => ({
- granted,
- denied: [{ bundleId: 'com.apple.Notes', reason: 'user_denied' }],
- flags: {
- clipboardRead: false,
- clipboardWrite: false,
- systemKeyCombos: false,
- },
- }),
- }),
- )
-
- expect(r.isError).toBe(true)
- expect(r.telemetry?.error_kind).toBe('app_not_granted')
- expect(calls.map(call => call.method)).toEqual(['resolveTarget'])
- }
- })
-
- test('an ungranted target without a permission handler fails closed', async () => {
+ test('an empty legacy allowlist still permits a supported target without a permission handler', async () => {
const { engine, calls } = makeEngine({
resolveTarget: async () => ({
pid: 812,
@@ -990,12 +922,11 @@ describe('handleToolCall — gates', () => {
baseOverrides({ allowedApps: [], onPermissionRequest: undefined }),
)
- expect(r.isError).toBe(true)
- expect(r.telemetry?.error_kind).toBe('app_not_granted')
- expect(calls.map(call => call.method)).toEqual(['resolveTarget'])
+ expect(r.isError).toBeFalsy()
+ expect(calls.map(call => call.method)).toEqual(['resolveTarget', 'click'])
})
- test('an exact preauthorization bypasses the permission handler', async () => {
+ test('legacy preauthorization data is not consulted or prompted for', async () => {
let permissionCalls = 0
const { engine, calls } = makeEngine({
resolveTarget: async () => ({
@@ -1024,42 +955,45 @@ describe('handleToolCall — gates', () => {
expect(calls.map(call => call.method)).toEqual(['resolveTarget', 'typeText'])
})
- test('resolved user-denied and policy-denied aliases fail before mutation', async () => {
- const cases = [
- {
- resolved: { pid: 812, bundleId: 'com.apple.Notes', displayName: 'Notes' },
- overrides: { userDeniedBundleIds: ['com.apple.Notes'] },
- },
- {
- resolved: { pid: 900, bundleId: 'com.googlecode.iterm2', displayName: 'iTerm2' },
- overrides: {},
- },
- ]
- for (const entry of cases) {
- let permissionCalls = 0
- const { engine, calls } = makeEngine({ resolveTarget: async () => entry.resolved })
- const r = await handleToolCall(
- makeAdapter({ engine }),
- 'type_text',
- { app: 'friendly alias', text: 'blocked' },
- baseOverrides({
- allowedApps: [{
- bundleId: entry.resolved.bundleId,
- displayName: entry.resolved.displayName,
- grantedAt: 1,
- }],
- ...entry.overrides,
- onPermissionRequest: async () => {
- permissionCalls++
- throw new Error('must not prompt')
- },
- }),
- )
- expect(r.isError).toBe(true)
- expect(r.telemetry?.error_kind).toBe('app_denied')
- expect(permissionCalls).toBe(0)
- expect(calls.map(call => call.method)).toEqual(['resolveTarget'])
- }
+ test('legacy user-denied app state no longer blocks a supported app', async () => {
+ const { engine, calls } = makeEngine({
+ resolveTarget: async () => ({
+ pid: 812,
+ bundleId: 'com.apple.Notes',
+ displayName: 'Notes',
+ executablePath: '/System/Applications/Notes.app/Contents/MacOS/Notes',
+ launchTime: 1812,
+ }),
+ })
+ const r = await handleToolCall(
+ makeAdapter({ engine }),
+ 'type_text',
+ { app: 'Notes', text: 'allowed' },
+ baseOverrides({ allowedApps: [], userDeniedBundleIds: ['com.apple.Notes'] }),
+ )
+
+ expect(r.isError).toBeFalsy()
+ expect(calls.map(call => call.method)).toEqual(['resolveTarget', 'typeText'])
+ })
+
+ test('the product denylist still blocks a resolved app before mutation', async () => {
+ const { engine, calls } = makeEngine({
+ resolveTarget: async () => ({
+ pid: 900,
+ bundleId: 'com.googlecode.iterm2',
+ displayName: 'iTerm2',
+ }),
+ })
+ const r = await handleToolCall(
+ makeAdapter({ engine }),
+ 'type_text',
+ { app: 'iTerm2', text: 'blocked' },
+ baseOverrides({ allowedApps: [] }),
+ )
+
+ expect(r.isError).toBe(true)
+ expect(r.telemetry?.error_kind).toBe('app_denied')
+ expect(calls.map(call => call.method)).toEqual(['resolveTarget'])
})
test('invalid app values return bad_args without resolving a target', async () => {
diff --git a/src/vendor/computer-use-mcp/toolCalls.ts b/src/vendor/computer-use-mcp/toolCalls.ts
index 1ec7c9d2..848b3915 100644
--- a/src/vendor/computer-use-mcp/toolCalls.ts
+++ b/src/vendor/computer-use-mcp/toolCalls.ts
@@ -17,7 +17,7 @@
* before any mutation, because a bare pid can be recycled between the moment
* we resolved it and the moment we act on it.
*
- * **2. Authorization happens after resolution, on the resolved identity.**
+ * **2. Safety checks happen after resolution, on the resolved identity.**
* The model names an app loosely ("Notes", a path, a pid). `resolveTarget` is
* the single seam that turns that into one real running process; every policy
* check then runs against *that* process's bundle id, not against the string
@@ -39,10 +39,9 @@
* 4. Global CU lock (`overrides.checkCuLock`).
* 5. Engine presence.
* 6. `resolveTarget` → one running process + its lifetime identity.
- * 7. Denylists against the RESOLVED identity (policy, intrinsic, user).
- * 8. Per-app grant (allowlist, else the host's approval dialog).
- * 9. Proven process lifetime — mutating tools only.
- * 10. Engine dispatch.
+ * 7. Product/intrinsic denylists against the RESOLVED identity.
+ * 8. Proven process lifetime — mutating tools only.
+ * 9. Engine dispatch.
*
* The Codex `` envelope is framed HERE, in TS, not in Swift
* (blueprint §7). Swift renders the inner tree text (the format authority);
@@ -54,7 +53,6 @@
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
- getDefaultTierForApp,
getDeniedCategoryForApp,
isIntrinsicAppDenied,
isPolicyDenied,
@@ -69,12 +67,10 @@ import type {
ScreenshotResult,
} from "./executor.js";
import { isSystemKeyCombo } from "./keyBlocklist.js";
-import { SENTINEL_BUNDLE_IDS } from "./sentinelApps.js";
import type {
ComputerUseHostAdapter,
ComputerUseOverrides,
CuGrantFlags,
- CuPermissionRequest,
} from "./types.js";
// ---------------------------------------------------------------------------
@@ -903,35 +899,6 @@ function dispatchTarget(
return identity ? { pid, expectedProcessIdentity: identity } : { pid };
}
-/** Build the approval-dialog request for a single resolved target. */
-function buildPermissionRequest(
- resolved: ResolvedAppTarget,
- requestedApp: string,
- screenshotFiltering: "native" | "none",
-): CuPermissionRequest {
- const bundleId = resolved.bundleId ?? "";
- const displayName = resolved.displayName ?? requestedApp;
- return {
- requestId: `cu-${bundleId || requestedApp}`,
- reason: `Computer Use needs access to ${displayName}.`,
- apps: [
- {
- requestedName: requestedApp,
- resolved: {
- bundleId,
- displayName,
- path: resolved.path ?? "",
- },
- isSentinel: SENTINEL_BUNDLE_IDS.has(bundleId),
- alreadyGranted: false,
- proposedTier: getDefaultTierForApp(bundleId, displayName),
- },
- ],
- requestedFlags: {},
- screenshotFiltering,
- };
-}
-
// ---------------------------------------------------------------------------
// Top-level dispatch
// ---------------------------------------------------------------------------
@@ -1049,33 +1016,7 @@ export async function handleToolCall(
);
if (denied) return errorResult(denied, "app_denied");
- if (
- resolved.bundleId !== undefined &&
- overrides.userDeniedBundleIds?.includes(resolved.bundleId)
- ) {
- return errorResult(
- `Computer Use is not allowed to use the app '${requestedApp}' — it is ` +
- "on your deny list. Remove it in Settings if access is needed.",
- "app_denied",
- );
- }
-
- // ─── Gate 8: per-app grant ─────────────────────────────────────────
- const authorized = await authorizeResolvedTarget(
- resolved,
- requestedApp,
- overrides,
- adapter,
- );
- if (!authorized) {
- return errorResult(
- `Computer Use is not authorized to control '${requestedApp}'. Ask the ` +
- "user to grant access, then retry.",
- "app_not_granted",
- );
- }
-
- // ─── Gate 9: proven process lifetime (mutations only) ──────────────
+ // ─── Gate 8: proven process lifetime (mutations only) ──────────────
if (request.mutating && !provenIdentity(resolved)) {
return errorResult(
`Resolving '${requestedApp}' did not prove the running process ` +
@@ -1099,39 +1040,6 @@ export async function handleToolCall(
}
}
-/**
- * True when the resolved process is allowed to be driven — already granted, or
- * granted by the user in response to the approval dialog.
- *
- * Fails closed in both no-answer cases: no handler wired, or a handler whose
- * response doesn't name this exact bundle id.
- */
-async function authorizeResolvedTarget(
- resolved: ResolvedAppTarget,
- requestedApp: string,
- overrides: ComputerUseOverrides,
- adapter: ComputerUseHostAdapter,
-): Promise {
- const bundleId = resolved.bundleId;
- if (bundleId === undefined) return false;
-
- if (overrides.allowedApps?.some(app => app.bundleId === bundleId)) {
- return true;
- }
-
- const request = overrides.onPermissionRequest;
- if (!request) return false;
-
- const response = await request(
- buildPermissionRequest(
- resolved,
- requestedApp,
- adapter.executor.capabilities.screenshotFiltering,
- ),
- );
- return response.granted.some(grant => grant.bundleId === bundleId);
-}
-
// ---------------------------------------------------------------------------
// Test surface (mirrors the old `_test` export shape; unit tests import this)
// ---------------------------------------------------------------------------
diff --git a/src/vendor/computer-use-mcp/windowsLegacyToolCalls.ts b/src/vendor/computer-use-mcp/windowsLegacyToolCalls.ts
index c5c78f7b..4bd77318 100644
--- a/src/vendor/computer-use-mcp/windowsLegacyToolCalls.ts
+++ b/src/vendor/computer-use-mcp/windowsLegacyToolCalls.ts
@@ -4,31 +4,15 @@
*
* Enforcement order, every call:
* 1. Kill switch (`adapter.isDisabled()`).
- * 2. TCC gate (`adapter.ensureOsPermissions()`). `request_access` is
- * exempted — it threads the ungranted state to the renderer so the
- * user can grant TCC perms from inside the approval dialog.
+ * 2. OS permission gate (`adapter.ensureOsPermissions()`).
* 3. Tool-specific gates (see dispatch table) — ANY exception in a gate
* returns a tool error, executor never called.
* 4. Executor call.
*
* For input actions (click/type/key/scroll/drag/move_mouse) the tool-specific
* gates are, in order:
- * a. `prepareForAction` — hide every non-allowlisted app, then defocus us
- * (battle-tested pre-action sequence from the Vercept acquisition).
- * Sub-gated via `hideBeforeAction`. After this runs the screenshot is
- * TRUE (what the
- * model sees IS what's at each pixel) and we are not keyboard-focused.
- * b. Frontmost gate — branched by actionKind:
- * mouse: frontmost ∈ allowlist ∪ {hostBundleId, Finder} → pass.
- * hostBundleId passes because the executor's
- * `withClickThrough` bracket makes us click-through.
- * keyboard: frontmost ∈ allowlist ∪ {Finder} → pass.
- * hostBundleId → ERROR (safety net — defocus should have
- * moved us off; if it didn't, typing would go into our
- * own chat box).
- * After step (a) this gate fires RARELY — only when something popped
- * up between prepare and action, or the 5-try hide loop gave up.
- * Checked FRESH on every call, not cached across calls.
+ * a. `prepareForAction` — platform preparation and host defocus.
+ * b. Resolve the frontmost app and apply product/intrinsic safety tiers.
*
* For click variants only, AFTER the above gates but BEFORE the executor call:
* c. Pixel-validation staleness check (sub-gated).
@@ -334,6 +318,10 @@ function tierSatisfies(
return tier === "click" || tier === "full";
}
+function automaticTier(bundleId: string | undefined, displayName: string): CuAppPermTier {
+ return getDefaultTierForApp(bundleId, displayName);
+}
+
// Appended to every tier_insufficient error. The model may try to route
// around the gate (osascript, System Events, cliclick via Bash) — this
// closes that door explicitly. Leading space so it concatenates cleanly.
@@ -418,45 +406,15 @@ async function runInputActionGates(
subGates: CuSubGates,
actionKind: CuActionKind,
): Promise {
- // Step A+B — hide non-allowlisted apps + defocus us. Sub-gated. After this
- // runs, the frontmost gate below becomes a rare edge-case detector (something
- // popped up between prepare and action) rather than a normal-path blocker.
- // ALL grant tiers stay visible — visibility is the baseline (tier "read").
+ // Windows shows the full desktop. Preparation gets an empty filter so it may
+ // perform platform bookkeeping without hiding apps based on an allowlist.
if (subGates.hideBeforeAction) {
- const hidden = await adapter.executor.prepareForAction(
- overrides.allowedApps.map((a) => a.bundleId),
- overrides.selectedDisplayId,
- );
- // Empty-check so we don't spam the callback on every action when nothing
- // was hidden (the common case after the first action of a turn).
- if (hidden.length > 0) {
- overrides.onAppsHidden?.(hidden);
- }
+ await adapter.executor.prepareForAction([], overrides.selectedDisplayId);
}
// Frontmost gate. Check FRESH on every call.
const frontmost = await adapter.executor.getFrontmostApp();
- const tierByBundleId = new Map(
- overrides.allowedApps.map((a) => [a.bundleId, a.tier] as const),
- );
-
- // After handleToolCall's tier backfill, every grant has a concrete tier —
- // .get() returning undefined means the app is not in the allowlist at all.
- const frontmostTier = frontmost
- ? tierByBundleId.get(frontmost.bundleId)
- : undefined;
-
- // Clipboard guard. Per-action, not per-tool-call — runs for every sub-action
- // inside computer_batch and teach_step/teach_batch, so clicking into a
- // click-tier app mid-batch stashes+clears before the next click lands.
- // Lives here (not in handleToolCall) so deferAcquire tools (request_access,
- // list_granted_applications), `wait`, and the teach_step blocking-dialog
- // phase don't trigger a sync — only input actions do.
- if (subGates.clipboardGuard) {
- await syncClipboardStash(adapter, overrides, frontmostTier === "click");
- }
-
if (!frontmost) {
// Refuse rather than let it through. This path derives its target from
// whatever is in front, so "we could not tell what is in front" means we
@@ -469,19 +427,36 @@ async function runInputActionGates(
// it silently applies to nothing.
return errorResult(
"The foreground application could not be identified. Refusing input " +
- "until a granted application is brought to the front.",
+ "until a supported application is brought to the front.",
"state_conflict",
);
}
const { hostBundleId } = adapter.executor.capabilities;
- if (frontmostTier !== undefined) {
- if (tierSatisfies(frontmostTier, actionKind)) return null;
- // In the allowlist but tier doesn't cover this action. Tailor the
- // guidance to the actual tier — at "read", suggesting left_click or Bash
- // is wrong (nothing is allowed; use Chrome MCP). At "click", the
- // mouse_full/keyboard-specific messages apply.
+ if (frontmost.bundleId === hostBundleId) {
+ if (actionKind !== "keyboard") return null;
+ return errorResult(
+ "Claude's own window still has keyboard focus. Click on the target " +
+ "application first so typing cannot land in the chat box.",
+ "state_conflict",
+ );
+ }
+
+ if (isPolicyDenied(frontmost.bundleId, frontmost.displayName)) {
+ return errorResult(
+ `"${frontmost.displayName}" is not supported by Computer Use for product-safety reasons.`,
+ "app_denied",
+ );
+ }
+
+ const frontmostTier = automaticTier(frontmost.bundleId, frontmost.displayName);
+
+ if (subGates.clipboardGuard) {
+ await syncClipboardStash(adapter, overrides, frontmostTier === "click");
+ }
+
+ if (!tierSatisfies(frontmostTier, actionKind)) {
if (frontmostTier === "read") {
// tier "read" is not category-unique (browser AND trading map to it) —
// re-look-up so the CiC hint only shows for actual browsers.
@@ -489,7 +464,7 @@ async function runInputActionGates(
getDeniedCategoryForApp(frontmost.bundleId, frontmost.displayName) ===
"browser";
return errorResult(
- `"${frontmost.displayName}" is granted at tier "read" — ` +
+ `"${frontmost.displayName}" is restricted to tier "read" — ` +
`visible in screenshots only, no clicks or typing.` +
(isBrowser
? " Use the Claude-in-Chrome MCP for browser interaction (tools " +
@@ -501,10 +476,9 @@ async function runInputActionGates(
"tier_insufficient",
);
}
- // frontmostTier === "click" (tier === "full" would have passed tierSatisfies)
if (actionKind === "keyboard") {
return errorResult(
- `"${frontmost.displayName}" is granted at tier "click" — ` +
+ `"${frontmost.displayName}" is restricted to tier "click" — ` +
`typing, key presses, and paste require tier "full". The keys ` +
`would go to this app's text fields or integrated terminal. To ` +
`type into a different app, click it first to bring it forward. ` +
@@ -514,7 +488,7 @@ async function runInputActionGates(
}
// actionKind === "mouse_full" ("mouse" and "mouse_position" pass at "click")
return errorResult(
- `"${frontmost.displayName}" is granted at tier "click" — ` +
+ `"${frontmost.displayName}" is restricted to tier "click" — ` +
`right-click, middle-click, and clicks with modifier keys require ` +
`tier "full". Right-click opens a context menu with Paste/Cut, and ` +
`modifier chords fire as keystrokes before the click. Plain ` +
@@ -522,32 +496,8 @@ async function runInputActionGates(
"tier_insufficient",
);
}
- // Finder is never-hide, always allowed.
- if (frontmost.bundleId === FINDER_BUNDLE_ID) return null;
- if (frontmost.bundleId === hostBundleId) {
- if (actionKind !== "keyboard") {
- // mouse and mouse_full are both click events — click-through works.
- // We're click-through (executor's withClickThrough). Pass.
- return null;
- }
- // Keyboard safety net — defocus (prepareForAction step B) should have
- // moved us off. If we're still here, typing would go to our chat box.
- return errorResult(
- "Claude's own window still has keyboard focus. This should not happen " +
- "after the pre-action defocus. Click on the target application first.",
- "state_conflict",
- );
- }
-
- // Non-allowlisted, non-us, non-Finder. RARE after the hide loop — means
- // something popped up between prepare and action, or the 5-try loop gave up.
- return errorResult(
- `"${frontmost.displayName}" is not in the allowed applications and is ` +
- `currently in front. Take a new screenshot — it may have appeared ` +
- `since your last one.`,
- "app_not_granted",
- );
+ return null;
}
/**
@@ -575,28 +525,18 @@ async function runHitTestGate(
const target = await adapter.executor.appUnderPoint(x, y);
if (!target) return null; // desktop / nothing under point / platform no-op
- // Finder (desktop, file dialogs) is always clickable — same exemption as
- // runInputActionGates. Our own overlay is filtered by Swift (pid != self).
+ // Finder (desktop, file dialogs) and our click-through overlay are valid.
if (target.bundleId === FINDER_BUNDLE_ID) return null;
-
- const tierByBundleId = new Map(
- overrides.allowedApps.map((a) => [a.bundleId, a.tier] as const),
- );
-
- if (!tierByBundleId.has(target.bundleId)) {
- // Not in the allowlist at all. The frontmost check would catch this if
- // the target were frontmost, but here a different app is in front. This
- // is the "something popped up" edge case — a new window appeared between
- // screenshot and click, or a background app's window overlaps the target.
+ if (target.bundleId === adapter.executor.capabilities.hostBundleId) return null;
+ if (isPolicyDenied(target.bundleId, target.displayName)) {
return errorResult(
`Click at these coordinates would land on "${target.displayName}", ` +
- `which is not in the allowed applications. Take a fresh screenshot ` +
- `to see the current window layout.`,
- "app_not_granted",
+ "which Computer Use does not support for product-safety reasons.",
+ "app_denied",
);
}
- const targetTier = tierByBundleId.get(target.bundleId);
+ const targetTier = automaticTier(target.bundleId, target.displayName);
// Frontmost-based sync (runInputActionGates) misses the case where
// the click lands on a NON-FRONTMOST click-tier window. Re-sync by
@@ -615,7 +555,7 @@ async function runHitTestGate(
if (actionKind === "mouse_full" && targetTier === "click") {
return errorResult(
`Click at these coordinates would land on "${target.displayName}", ` +
- `which is granted at tier "click" — right-click, middle-click, and ` +
+ `which is restricted to tier "click" — right-click, middle-click, and ` +
`clicks with modifier keys require tier "full" (they can Paste via ` +
`the context menu or fire modifier-chord keystrokes). Plain ` +
`left_click is allowed here.` + TIER_ANTI_SUBVERSION,
@@ -626,7 +566,7 @@ async function runHitTestGate(
getDeniedCategoryForApp(target.bundleId, target.displayName) === "browser";
return errorResult(
`Click at these coordinates would land on "${target.displayName}", ` +
- `which is granted at tier "read" (screenshots only, no interaction). ` +
+ `which is restricted to tier "read" (screenshots only, no interaction). ` +
(isBrowser
? "Use the Claude-in-Chrome MCP for browser interaction."
: "Ask the user to take any actions in this app themselves.") +
@@ -1686,13 +1626,7 @@ async function executeTeachStep(
}
if (subGates.hideBeforeAction) {
- const hidden = await adapter.executor.prepareForAction(
- overrides.allowedApps.map((a) => a.bundleId),
- overrides.selectedDisplayId,
- );
- if (hidden.length > 0) {
- overrides.onAppsHidden?.(hidden);
- }
+ await adapter.executor.prepareForAction([], overrides.selectedDisplayId);
}
const stepSubGates: CuSubGates = {
@@ -1921,29 +1855,6 @@ async function handleTeachBatch(
return appendTeachScreenshot(resultJson, adapter, overrides, subGates);
}
-/**
- * Build the hidden-apps note that accompanies a screenshot. Tells the model
- * which apps got hidden (not in allowlist) and how to add them. Returns
- * undefined when nothing was hidden since the last screenshot.
- */
-async function buildHiddenNote(
- adapter: ComputerUseHostAdapter,
- hiddenSinceLastSeen: string[],
-): Promise {
- if (hiddenSinceLastSeen.length === 0) return undefined;
- const running = await adapter.executor.listRunningApps();
- const nameOf = new Map(running.map((a) => [a.bundleId, a.displayName]));
- const names = hiddenSinceLastSeen.map((id) => nameOf.get(id) ?? id);
- const list = names.map((n) => `"${n}"`).join(", ");
- const one = names.length === 1;
- return (
- `${list} ${one ? "was" : "were"} open and got hidden before this screenshot ` +
- `(not in the session allowlist). If a previous action was meant to open ` +
- `${one ? "it" : "one of them"}, that's why you don't see it — call ` +
- `request_access to add ${one ? "it" : "them"} to the allowlist.`
- );
-}
-
/**
* Assign a human-readable label to each display. Falls back to `display N`
* when NSScreen.localizedName is undefined; disambiguates identical labels
@@ -2033,14 +1944,6 @@ async function handleScreenshot(
overrides: ComputerUseOverrides,
subGates: CuSubGates,
): Promise {
- // §2 — empty allowlist → tool error, no screenshot.
- if (overrides.allowedApps.length === 0) {
- return errorResult(
- "No applications are granted for this session. Call request_access first.",
- "allowlist_empty",
- );
- }
-
// Atomic resolve→prepare→capture (one Swift call, no scheduler gap).
// Off → fall through to separate-calls path below.
if (subGates.autoTargetDisplay) {
@@ -2049,8 +1952,8 @@ async function handleScreenshot(
// Otherwise sticky display: only auto-resolve when the allowed-app
// set has changed since the display was last resolved. Prevents the
// resolver yanking the display on every screenshot.
- const allowedBundleIds = overrides.allowedApps.map((a) => a.bundleId);
- const currentAppSetKey = allowedBundleIds.slice().sort().join(",");
+ const allowedBundleIds: string[] = [];
+ const currentAppSetKey = "all-supported-apps";
const appSetChanged = currentAppSetKey !== overrides.displayResolvedForApps;
const autoResolve = !overrides.displayPinnedByModel && appSetChanged;
@@ -2096,24 +1999,11 @@ async function handleScreenshot(
overrides.onDisplayResolvedForApps?.(currentAppSetKey);
}
- // Report hidden apps only when the model has already seen the screen.
- let hiddenSinceLastSeen: string[] = [];
- if (overrides.lastScreenshot !== undefined) {
- hiddenSinceLastSeen = result.hidden;
- }
- if (result.hidden.length > 0) {
- overrides.onAppsHidden?.(result.hidden);
- }
-
- // Partial-success case: hide succeeded, capture failed (SCK perm
- // revoked mid-session). onAppsHidden fired above so auto-unhide will
- // restore hidden apps at turn end. Now surface the error to the model.
+ // Partial-success case: capture failed after preparation.
if (result.captureError !== undefined) {
return errorResult(result.captureError, "capture_failed");
}
- const hiddenNote = await buildHiddenNote(adapter, hiddenSinceLastSeen);
-
// Cherry-pick — don't spread `result` (would leak resolver fields into lastScreenshot).
const shot: ScreenshotResult = {
base64: result.base64,
@@ -2136,7 +2026,6 @@ async function handleScreenshot(
return {
content: [
...(monitorNote ? [{ type: "text" as const, text: monitorNote }] : []),
- ...(hiddenNote ? [{ type: "text" as const, text: hiddenNote }] : []),
{
type: "image",
data: shot.base64,
@@ -2147,52 +2036,18 @@ async function handleScreenshot(
};
}
- // Same hide+defocus sequence as input actions. Screenshot needs hide too
- // — if a non-allowlisted app is on top, SCContentFilter would composite it
- // out, but the pixels BELOW it are what the model would see, and those are
- // NOT what's actually there. Hiding first makes the screenshot TRUE.
- let hiddenSinceLastSeen: string[] = [];
+ // Keep the platform preparation hook, but do not filter or hide applications.
if (subGates.hideBeforeAction) {
- const hidden = await adapter.executor.prepareForAction(
- overrides.allowedApps.map((a) => a.bundleId),
- overrides.selectedDisplayId,
- );
- // "Something appeared since the model last looked." Report whenever:
- // (a) prepare hid something AND
- // (b) the model has ALREADY SEEN the screen (lastScreenshot is set).
- //
- // (b) is the discriminator that silences the first screenshot's
- // expected-noise hide. NOT a delta against a cumulative set — that was
- // the earlier bug: cuHiddenDuringTurn only grows, so once Preview is in
- // it (from the first screenshot's hide), subsequent re-hides of Preview
- // delta to zero. The double-click → Preview opens → re-hide → silent
- // loop never breaks.
- //
- // With this check: every re-hide fires. If the model loops "click → file
- // opens in Preview → screenshot → Preview hidden", it gets told EVERY
- // time. Eventually it'll request_access for Preview (or give up).
- //
- // False positive: user alt-tabs mid-turn → Safari re-hidden → reported.
- // Rare, and "Safari appeared" is at worst mild noise — far better than
- // the false-negative of never explaining why the file vanished.
- if (overrides.lastScreenshot !== undefined) {
- hiddenSinceLastSeen = hidden;
- }
- if (hidden.length > 0) {
- overrides.onAppsHidden?.(hidden);
- }
+ await adapter.executor.prepareForAction([], overrides.selectedDisplayId);
}
- const allowedBundleIds = overrides.allowedApps.map((g) => g.bundleId);
const shot = await takeScreenshotWithRetry(
adapter.executor,
- allowedBundleIds,
+ [],
adapter.logger,
overrides.selectedDisplayId,
);
- const hiddenNote = await buildHiddenNote(adapter, hiddenSinceLastSeen);
-
const monitorNote = await buildMonitorNote(
adapter,
shot.displayId,
@@ -2203,7 +2058,6 @@ async function handleScreenshot(
return {
content: [
...(monitorNote ? [{ type: "text" as const, text: monitorNote }] : []),
- ...(hiddenNote ? [{ type: "text" as const, text: hiddenNote }] : []),
{
type: "image",
data: shot.base64,
@@ -2275,12 +2129,11 @@ async function handleZoom(
h: (y1 - y0) * ratioY,
};
- const allowedIds = overrides.allowedApps.map((g) => g.bundleId);
// Crop from the same display as lastScreenshot so the zoom region
// matches the image the model is reading coords from.
const zoomed = await adapter.executor.zoom(
regionLogical,
- allowedIds,
+ [],
last.displayId,
);
@@ -2333,7 +2186,7 @@ async function handleClickVariant(
) {
return errorResult(
`The modifier chord "${args.text}" would fire a system shortcut. ` +
- "Request the systemKeyCombos grant flag via request_access, or use " +
+ "Enable Computer Use again to accept system-shortcut access, or use " +
"only modifier keys (shift, ctrl, alt, cmd) in the text parameter.",
"grant_flag_required",
);
@@ -2379,12 +2232,11 @@ async function handleClickVariant(
async () => {
// The fresh screenshot for validation uses the SAME allow-set as
// the model's last screenshot did, so we compare like with like.
- const allowedIds = overrides.allowedApps.map((g) => g.bundleId);
try {
// Fresh shot must match lastScreenshot's display, not the current
// selection — pixel-compare is against the model's last image.
return await adapter.executor.screenshot({
- allowedBundleIds: allowedIds,
+ allowedBundleIds: [],
displayId: overrides.lastScreenshot?.displayId,
});
} catch {
@@ -2549,7 +2401,7 @@ async function handleKey(
!overrides.grantFlags.systemKeyCombos
) {
return errorResult(
- `"${keySequence}" is a system-level shortcut. Request the \`systemKeyCombos\` grant via request_access to use it.`,
+ `"${keySequence}" is a system-level shortcut. Re-enable Computer Use and accept the risk notice to use it.`,
"grant_flag_required",
);
}
@@ -2789,27 +2641,27 @@ async function handleOpenApplication(
const app = requireString(args, "app");
if (app instanceof Error) return errorResult(app.message, "bad_args");
- // Resolve display-name → bundle ID. Same logic as request_access.
- const allowed = new Set(overrides.allowedApps.map((g) => g.bundleId));
- let targetBundleId: string | undefined;
+ // Resolve only against the helper's installed-app inventory. Never pass an
+ // arbitrary model string to the Windows shell.
+ const installed = await adapter.executor.listInstalledApps();
+ const wanted = app.trim().toLowerCase();
+ const match = installed.find(
+ candidate =>
+ candidate.bundleId.toLowerCase() === wanted
+ || candidate.displayName.toLowerCase() === wanted,
+ );
- if (looksLikeBundleId(app) && allowed.has(app)) {
- targetBundleId = app;
- } else {
- // Try display name → bundle ID, but ONLY against the allowlist itself.
- // Avoids paying the listInstalledApps() cost on the hot path and is
- // arguably more correct: if the user granted "Slack", the model asking
- // to open "Slack" should match THAT grant.
- const match = overrides.allowedApps.find(
- (g) => g.displayName.toLowerCase() === app.toLowerCase(),
+ if (!match) {
+ return errorResult(
+ `"${app}" was not found in the installed application inventory.`,
+ "bad_args",
);
- targetBundleId = match?.bundleId;
}
- if (!targetBundleId || !allowed.has(targetBundleId)) {
+ if (isPolicyDenied(match.bundleId, match.displayName)) {
return errorResult(
- `"${app}" is not granted for this session. Call request_access first.`,
- "app_not_granted",
+ `"${match.displayName}" is not supported by Computer Use for product-safety reasons.`,
+ "app_denied",
);
}
@@ -2817,7 +2669,7 @@ async function handleOpenApplication(
// what tier "read" enables (you need it on screen to screenshot it). The
// tier gates on click/type catch any follow-up interaction.
- await adapter.executor.openApp(targetBundleId);
+ await adapter.executor.openApp(match.bundleId);
// On multi-monitor setups, macOS may place the opened window on a monitor
// the resolver won't pick (e.g. Claude + another allowed app are co-located
@@ -2920,7 +2772,7 @@ async function handleReadClipboard(
): Promise {
if (!overrides.grantFlags.clipboardRead) {
return errorResult(
- "Clipboard read is not granted. Request `clipboardRead` via request_access.",
+ "Clipboard read is disabled. Re-enable Computer Use and accept the risk notice.",
"grant_flag_required",
);
}
@@ -2930,11 +2782,8 @@ async function handleReadClipboard(
// (same as what the app's own Paste would see).
if (subGates.clipboardGuard) {
const frontmost = await adapter.executor.getFrontmostApp();
- const tierByBundleId = new Map(
- overrides.allowedApps.map((a) => [a.bundleId, a.tier] as const),
- );
const frontmostTier = frontmost
- ? tierByBundleId.get(frontmost.bundleId)
+ ? automaticTier(frontmost.bundleId, frontmost.displayName)
: undefined;
await syncClipboardStash(adapter, overrides, frontmostTier === "click");
}
@@ -2953,7 +2802,7 @@ async function handleWriteClipboard(
): Promise {
if (!overrides.grantFlags.clipboardWrite) {
return errorResult(
- "Clipboard write is not granted. Request `clipboardWrite` via request_access.",
+ "Clipboard write is disabled. Re-enable Computer Use and accept the risk notice.",
"grant_flag_required",
);
}
@@ -2962,11 +2811,8 @@ async function handleWriteClipboard(
if (subGates.clipboardGuard) {
const frontmost = await adapter.executor.getFrontmostApp();
- const tierByBundleId = new Map(
- overrides.allowedApps.map((a) => [a.bundleId, a.tier] as const),
- );
const frontmostTier = frontmost
- ? tierByBundleId.get(frontmost.bundleId)
+ ? automaticTier(frontmost.bundleId, frontmost.displayName)
: undefined;
// Defense-in-depth for the clipboardGuard bypass: write_clipboard +
@@ -3107,7 +2953,7 @@ async function handleHoldKey(
!overrides.grantFlags.systemKeyCombos
) {
return errorResult(
- `"${text}" is a system-level shortcut. Request the \`systemKeyCombos\` grant via request_access to use it.`,
+ `"${text}" is a system-level shortcut. Re-enable Computer Use and accept the risk notice to use it.`,
"grant_flag_required",
);
}
@@ -3316,13 +3162,7 @@ async function handleComputerBatch(
// prepareForAction ONCE. After this, inner dispatches skip it via
// hideBeforeAction:false.
if (subGates.hideBeforeAction) {
- const hidden = await adapter.executor.prepareForAction(
- overrides.allowedApps.map((a) => a.bundleId),
- overrides.selectedDisplayId,
- );
- if (hidden.length > 0) {
- overrides.onAppsHidden?.(hidden);
- }
+ await adapter.executor.prepareForAction([], overrides.selectedDisplayId);
}
// Inner actions: skip prepare (already ran), skip pixelCompare (stale by
@@ -3642,16 +3482,37 @@ export async function handleToolCall(
// ANY exception below → tool error, executor never left in a half-called
// state. Explicit inversion of the prior `catch → return true` fail-open.
try {
- // request_access / request_teach_access: need tccState thread-through;
- // dispatchAction never sees them (not batchable).
+ // Compatibility for a stale client that cached the removed app-permission
+ // tool. Enabling Computer Use is now the authorization boundary, so this
+ // call must never open an app-by-app approval dialog.
+ if (name === "request_access") {
+ return okJson({
+ enabled: true,
+ appAuthorization: "all_supported_apps",
+ screenshotFiltering: adapter.executor.capabilities.screenshotFiltering,
+ });
+ }
+
+ // Teach mode still needs an explicit tool transition because it hides the
+ // main window, but it no longer asks for per-app permission.
+ if (name === "request_teach_access") {
+ if (overrides.getTeachModeActive?.()) {
+ return errorResult("Teach mode is already active.", "teach_mode_conflict");
+ }
+ const reason = requireString(a, "reason");
+ if (reason instanceof Error) return errorResult(reason.message, "bad_args");
+ if (!Array.isArray(a.apps) || !a.apps.every(app => typeof app === "string")) {
+ return errorResult('"apps" must be an array of strings.', "bad_args");
+ }
+ if (!overrides.onTeachModeActivated || !overrides.onTeachStep) {
+ return errorResult("Teach mode is not available in this session.", "feature_unavailable");
+ }
+ overrides.onTeachModeActivated();
+ return okJson({ teachModeActive: true, reason });
+ }
+
// teach_step: blocking UI tool, also not batchable; needs subGates for
// its action-execution phase.
- if (name === "request_access") {
- return await handleRequestAccess(adapter, a, overrides, tccState);
- }
- if (name === "request_teach_access") {
- return await handleRequestTeachAccess(adapter, a, overrides, tccState);
- }
if (name === "teach_step") {
return await handleTeachStep(adapter, a, overrides, subGates);
}
diff --git a/src/vendor/computer-use-mcp/windowsLegacyTools.ts b/src/vendor/computer-use-mcp/windowsLegacyTools.ts
index c744a232..19712b51 100644
--- a/src/vendor/computer-use-mcp/windowsLegacyTools.ts
+++ b/src/vendor/computer-use-mcp/windowsLegacyTools.ts
@@ -29,7 +29,7 @@ const COORD_DESC: Record = {
};
const FRONTMOST_GATE_DESC =
- "The frontmost application must be in the session allowlist at the time of this call, or this tool returns an error and does nothing.";
+ "The target is resolved at call time and remains subject to product safety restrictions.";
/**
* Item schema for the `actions` array in `computer_batch`, `teach_step`, and
@@ -110,7 +110,7 @@ const BATCH_ACTION_ITEM_SCHEMA = {
* -call time. Both should read the same frozen-at-load gate constant.
*
* `installedAppNames` — optional pre-sanitized list of app display names to
- * enumerate in the `request_access` description. The caller is responsible
+ * enumerate in platform-specific tool descriptions. The caller is responsible
* for sanitization (length cap, character allowlist, sort, count cap) —
* this function just splices the list into the description verbatim. Omit
* to fall back to the generic "display names or bundle IDs" wording.
@@ -127,9 +127,7 @@ export function buildComputerUseTools(
): Tool[] {
const coord = COORD_DESC[coordinateMode];
- // Shared hint suffix for BOTH request_access and request_teach_access —
- // they use the same resolveRequestedApps path, so the model should get
- // the same enumeration for both.
+ // Teach mode uses this optional list to help the model name installed apps.
const installedAppsHint =
installedAppNames && installedAppNames.length > 0
? ` Available applications on this machine: ${installedAppNames.join(", ")}.`
@@ -153,57 +151,15 @@ export function buildComputerUseTools(
const screenshotDesc =
caps.screenshotFiltering === "native"
- ? "Take a screenshot of the primary display. Applications not in the session allowlist are excluded at the compositor level — only granted apps and the desktop are visible."
- : "Take a screenshot of the primary display. On this platform, screenshots are NOT filtered — all open windows are visible. Input actions targeting apps not in the session allowlist are rejected.";
+ ? "Take a screenshot of the primary display."
+ : "Take a screenshot of the primary display. On this platform, screenshots are NOT filtered — all open windows are visible.";
return [
- {
- name: "request_access",
- description:
- "Request user permission to control a set of applications for this session. Must be called before any other tool in this server. " +
- "The user sees a single dialog listing all requested apps and either allows the whole set or denies it. " +
- "Call this again mid-session to add more apps; previously granted apps remain granted. " +
- "Returns the granted apps, denied apps, and screenshot filtering capability.",
- inputSchema: {
- type: "object" as const,
- properties: {
- apps: {
- type: "array",
- items: { type: "string" },
- description:
- "Application display names (e.g. \"Slack\", \"Calendar\") or bundle identifiers (e.g. \"com.tinyspeck.slackmacgap\"). Display names are resolved case-insensitively against installed apps." +
- installedAppsHint,
- },
- reason: {
- type: "string",
- description:
- "One-sentence explanation shown to the user in the approval dialog. Explain the task, not the mechanism.",
- },
- clipboardRead: {
- type: "boolean",
- description:
- "Also request permission to read the user's clipboard (separate checkbox in the dialog).",
- },
- clipboardWrite: {
- type: "boolean",
- description:
- "Also request permission to write the user's clipboard. When granted, multi-line `type` calls use the clipboard fast path.",
- },
- systemKeyCombos: {
- type: "boolean",
- description:
- "Also request permission to send system-level key combos (quit app, switch app, lock screen). Without this, those specific combos are blocked.",
- },
- },
- required: ["apps", "reason"],
- },
- },
-
{
name: "screenshot",
description:
screenshotDesc +
- " Returns an error if the allowlist is empty. The returned image is what subsequent click coordinates are relative to.",
+ " The returned image is what subsequent click coordinates are relative to.",
inputSchema: {
type: "object" as const,
properties: {
@@ -400,7 +356,7 @@ export function buildComputerUseTools(
{
name: "open_application",
description:
- "Bring an application to the front, launching it if necessary. The target application must already be in the session allowlist — call request_access first.",
+ "Bring an installed application to the front, launching it if necessary. The application is resolved against the installed-app inventory before launch.",
inputSchema: {
type: "object" as const,
properties: {
@@ -437,17 +393,6 @@ export function buildComputerUseTools(
},
},
- {
- name: "list_granted_applications",
- description:
- "List the applications currently in the session allowlist, plus the active grant flags and coordinate mode. No side effects.",
- inputSchema: {
- type: "object" as const,
- properties: {},
- required: [],
- },
- },
-
{
name: "read_clipboard",
description:
@@ -575,7 +520,7 @@ export function buildComputerUseTools(
* takes `coord` so `teach_step.anchor`'s description uses the same
* frozen coordinate-mode phrasing as click coords, and `installedAppsHint`
* so `request_teach_access.apps` gets the same enumeration as
- * `request_access.apps` (same resolution path → same hint).
+ * installed app references (same inventory → same hint).
*/
function buildTeachTools(
coord: { x: string; y: string },
@@ -620,12 +565,11 @@ function buildTeachTools(
{
name: "request_teach_access",
description:
- "Request permission to guide the user through a task step-by-step with on-screen tooltips. " +
- "Use this INSTEAD OF request_access when the user wants to LEARN how to do something " +
+ "Start guiding the user through a task step-by-step with on-screen tooltips. " +
+ "Use this when the user wants to LEARN how to do something " +
'(phrases like "teach me", "walk me through", "show me how", "help me learn"). ' +
- "On approval the main Claude window hides and a fullscreen tooltip overlay appears. " +
+ "The main Claude window hides and a fullscreen tooltip overlay appears. " +
"You then call teach_step repeatedly; each call shows one tooltip and waits for the user to click Next. " +
- "Same app-allowlist semantics as request_access, but no clipboard/system-key flags. " +
"Teach mode ends automatically when your turn ends.",
inputSchema: {
type: "object" as const,
@@ -640,7 +584,7 @@ function buildTeachTools(
reason: {
type: "string",
description:
- 'What you will be teaching. Shown in the approval dialog as "Claude wants to guide you through {reason}". Keep it short and task-focused.',
+ "What you will be teaching. Keep it short and task-focused.",
},
},
required: ["apps", "reason"],