()
| 57 | return `Computer use is in use by another Claude session (${holder.slice(0, 8)}…). Wait for that session to finish or run /exit there.`; |
| 58 | } |
| 59 | export function buildSessionContext(): ComputerUseSessionContext { |
| 60 | return { |
| 61 | // ── Read state fresh via the per-call ref ───────────────────────────── |
| 62 | getAllowedApps: () => tuc().getAppState().computerUseMcpState?.allowedApps ?? [], |
| 63 | getGrantFlags: () => tuc().getAppState().computerUseMcpState?.grantFlags ?? DEFAULT_GRANT_FLAGS, |
| 64 | // cc-2 has no Settings page for user-denied apps yet. |
| 65 | getUserDeniedBundleIds: () => [], |
| 66 | getSelectedDisplayId: () => tuc().getAppState().computerUseMcpState?.selectedDisplayId, |
| 67 | getDisplayPinnedByModel: () => tuc().getAppState().computerUseMcpState?.displayPinnedByModel ?? false, |
| 68 | getDisplayResolvedForApps: () => tuc().getAppState().computerUseMcpState?.displayResolvedForApps, |
| 69 | getLastScreenshotDims: (): ScreenshotDims | undefined => { |
| 70 | const d = tuc().getAppState().computerUseMcpState?.lastScreenshotDims; |
| 71 | return d ? { |
| 72 | ...d, |
| 73 | displayId: d.displayId ?? 0, |
| 74 | originX: d.originX ?? 0, |
| 75 | originY: d.originY ?? 0 |
| 76 | } : undefined; |
| 77 | }, |
| 78 | // ── Write-backs ──────────────────────────────────────────────────────── |
| 79 | // `setToolJSX` is guaranteed present — the gate in `main.tsx` excludes |
| 80 | // non-interactive sessions. The package's `_dialogSignal` (tool-finished |
| 81 | // dismissal) is irrelevant here: `setToolJSX` blocks the tool call, so |
| 82 | // the dialog can't outlive it. Ctrl+C is what matters, and |
| 83 | // `runPermissionDialog` wires that from the per-call ref's abortController. |
| 84 | onPermissionRequest: (req, _dialogSignal) => runPermissionDialog(req), |
| 85 | // Package does the merge (dedupe + truthy-only flags). We just persist. |
| 86 | onAllowedAppsChanged: (apps, flags) => tuc().setAppState(prev => { |
| 87 | const cu = prev.computerUseMcpState; |
| 88 | const prevApps = cu?.allowedApps; |
| 89 | const prevFlags = cu?.grantFlags; |
| 90 | const sameApps = prevApps?.length === apps.length && apps.every((a, i) => prevApps[i]?.bundleId === a.bundleId); |
| 91 | const sameFlags = prevFlags?.clipboardRead === flags.clipboardRead && prevFlags?.clipboardWrite === flags.clipboardWrite && prevFlags?.systemKeyCombos === flags.systemKeyCombos; |
| 92 | return sameApps && sameFlags ? prev : { |
| 93 | ...prev, |
| 94 | computerUseMcpState: { |
| 95 | ...cu, |
| 96 | allowedApps: [...apps], |
| 97 | grantFlags: flags |
| 98 | } |
| 99 | }; |
| 100 | }), |
| 101 | onAppsHidden: ids => { |
| 102 | if (ids.length === 0) return; |
| 103 | tuc().setAppState(prev => { |
| 104 | const cu = prev.computerUseMcpState; |
| 105 | const existing = cu?.hiddenDuringTurn; |
| 106 | if (existing && ids.every(id => existing.has(id))) return prev; |
| 107 | return { |
| 108 | ...prev, |
| 109 | computerUseMcpState: { |
| 110 | ...cu, |
| 111 | hiddenDuringTurn: new Set([...(existing ?? []), ...ids]) |
| 112 | } |
| 113 | }; |
| 114 | }); |
| 115 | }, |
| 116 | // Resolver writeback only fires under a pin when Swift fell back to main |
no test coverage detected