From 4bb3f2e6e64b82389cc9764e4f32f4e0c0ea0f42 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 7 Sep 2026 15:07:47 -0400 Subject: [PATCH] refactor(mobile): delete the shell browser translators, streams and grants The browser capability is gone from the bridge registry, so the shell no longer names a browser RPC, sanitizes a tab URL or chunks a frame. hostRequest carries pointer traffic now, so its bucket takes over the retired pointer grant's rate. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../mobile-web-capability-broker.ts | 1 - ...ile-web-capability-dispatch-census.test.ts | 2 +- .../mobile-web-capability-execution-arms.ts | 25 ---------- ...e-web-capability-execution-dependencies.ts | 2 - .../mobile-web-capability-subscriptions.ts | 8 +--- ...eb-mutation-reauthorization-census.test.ts | 11 +---- .../mobile-web-production-grants.ts | 4 +- .../mobile-web-request-accounting.ts | 1 - ...le-web-subscription-closure-frames.test.ts | 46 +------------------ ...mobile-web-subscription-error-code.test.ts | 34 -------------- .../mobile-web/bridge-operation-registry.ts | 10 ---- 11 files changed, 7 insertions(+), 137 deletions(-) diff --git a/mobile/src/mobile-web/mobile-web-capability-broker.ts b/mobile/src/mobile-web/mobile-web-capability-broker.ts index 605d3a9bba7..295379a0c09 100644 --- a/mobile/src/mobile-web/mobile-web-capability-broker.ts +++ b/mobile/src/mobile-web/mobile-web-capability-broker.ts @@ -244,7 +244,6 @@ export class MobileWebCapabilityBroker { agentHistoryPager: this.authorities.agentHistoryPager, agentHistoryResume: this.authorities.agentHistoryResume, accountSubscriptions: this.subscriptions.account, - browserStreams: this.subscriptions.browser, sourceControlBranchCompare: this.authorities.sourceControlBranchCompare, speechAuthority: this.speechAuthority, workspaceSubscriptions: this.subscriptions.workspace, diff --git a/mobile/src/mobile-web/mobile-web-capability-dispatch-census.test.ts b/mobile/src/mobile-web/mobile-web-capability-dispatch-census.test.ts index 699b55d83b3..900289e2cc0 100644 --- a/mobile/src/mobile-web/mobile-web-capability-dispatch-census.test.ts +++ b/mobile/src/mobile-web/mobile-web-capability-dispatch-census.test.ts @@ -49,7 +49,7 @@ describe('mobile web capability dispatch census', () => { }) expect(unresolved.map(({ capability, operation }) => `${capability}.${operation}`)).toEqual([]) - expect(registeredOperations()).toHaveLength(199) + expect(registeredOperations()).toHaveLength(191) }) it('carries a dispatch arm for exactly the capabilities that own operations of that mode', () => { diff --git a/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts b/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts index fc96f592881..48b72a06464 100644 --- a/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts +++ b/mobile/src/mobile-web/mobile-web-capability-execution-arms.ts @@ -1,4 +1,3 @@ -import { MobileWebBrowserStreamPayloadSchema } from '../../../src/shared/mobile-web/browser-operation-contract' import { MobileWebWorkspaceSubscribePayloadSchema } from '../../../src/shared/mobile-web/bridge-operation-contract' import type { MobileWebBridgePageMessage } from '../../../src/shared/mobile-web/bridge-contract' import type { MobileWebBridgeCapability } from '../../../src/shared/mobile-web/bridge-operation-registry' @@ -7,7 +6,6 @@ import { executeWorkspace } from './mobile-web-workspace-capability' import { executeMobileWebAccountCapability } from './mobile-web-account-capability' import { executeMobileWebAgentHistoryOperation } from './mobile-web-agent-history-operations' import { MobileWebBrokerError } from './mobile-web-broker-error' -import { executeMobileWebBrowserOperation } from './mobile-web-browser-operations' import type { MobileWebCapabilityExecutionDependencies } from './mobile-web-capability-execution-dependencies' import { executeMobileWebFileOperation } from './mobile-web-file-operations' import { executeMobileWebMarkdownOperation } from './mobile-web-markdown-operations' @@ -53,15 +51,6 @@ async function executeNavigation(args: Deps, request: OnceRequest): Promise { - return executeMobileWebBrowserOperation({ - operation: request.operation, - payload: request.payload, - client: args.connectedClient(), - workspaceAuthority: args.workspaceAuthority - }) -} - async function executeTerminal(args: Deps, request: OnceRequest): Promise { return args.terminalStreams.handle(request.payload, args.connectedClient()) } @@ -158,7 +147,6 @@ export const MOBILE_WEB_ONCE_CAPABILITY_ARMS: Partial executeMobileWebAgentHistoryOperation(args), account: (args) => executeMobileWebAccountCapability(args), - browser: executeBrowser, workspace: executeWorkspace, settings: executeWorkspace, terminal: executeTerminal, @@ -169,18 +157,6 @@ export const MOBILE_WEB_ONCE_CAPABILITY_ARMS: Partial { - requireSubscribeOperation(request) - MobileWebBrowserStreamPayloadSchema.parse(request.payload) - args.browserStreams.start({ - requestId: request.requestId, - subscriptionId: request.subscriptionId, - payload: request.payload, - client: args.connectedClient() - }) - return null -} - async function subscribeWorkspace(args: Deps, request: SubscriptionRequest): Promise { if (request.operation === 'hostSubscribe') { args.hostSubscriptions.start({ @@ -228,7 +204,6 @@ export const MOBILE_WEB_SUBSCRIPTION_CAPABILITY_ARMS: Partial< Record > = { account: (args) => executeMobileWebAccountCapability(args), - browser: subscribeBrowser, workspace: subscribeWorkspace, terminal: subscribeTerminal, speech: subscribeSpeech diff --git a/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts b/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts index f1318338797..da72a658218 100644 --- a/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts +++ b/mobile/src/mobile-web/mobile-web-capability-execution-dependencies.ts @@ -5,7 +5,6 @@ import type { MobileWebAccountSubscriptions } from './mobile-web-account-subscri import type { MobileWebAgentHistoryAuthority } from './mobile-web-agent-history-authority' import type { MobileWebAgentHistoryPager } from './mobile-web-agent-history-pager' import type { MobileWebAgentHistoryResume } from './mobile-web-agent-history-resume' -import type { MobileWebBrowserStreams } from './mobile-web-browser-streams' import type { MobileWebCommitMessageGeneration } from './mobile-web-commit-message-generation' import type { MobileWebNavigationAuthority } from './mobile-web-navigation-operations' import type { MobileWebNativeCapabilityAuthority } from './mobile-web-native-capability-authority' @@ -33,7 +32,6 @@ export type MobileWebCapabilityExecutionDependencies = { agentHistoryResume: MobileWebAgentHistoryResume hostSubscriptions: MobileWebHostSubscriptions accountSubscriptions: MobileWebAccountSubscriptions - browserStreams: MobileWebBrowserStreams sourceControlBranchCompare: MobileWebSourceControlBranchComparePager speechAuthority: MobileWebSpeechAuthority workspaceSubscriptions: MobileWebWorkspaceSubscriptions diff --git a/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts b/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts index 1b23a842e3e..aaaef8e3f8c 100644 --- a/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts +++ b/mobile/src/mobile-web/mobile-web-capability-subscriptions.ts @@ -5,14 +5,12 @@ import type { MobileWebSubscriptionLedgerConfig, MobileWebSubscriptionLedgerHandle } from './mobile-web-subscription-ledger' -import { MobileWebBrowserStreams } from './mobile-web-browser-streams' import type { MobileWebWorkspaceAuthority } from './mobile-web-workspace-authority' import { MobileWebWorkspaceSubscriptions } from './mobile-web-workspace-subscriptions' export class MobileWebCapabilitySubscriptions { readonly host: MobileWebHostSubscriptions readonly account: MobileWebAccountSubscriptions - readonly browser: MobileWebBrowserStreams readonly workspace: MobileWebWorkspaceSubscriptions private readonly ledgers: MobileWebSubscriptionLedgerHandle[] @@ -31,12 +29,8 @@ export class MobileWebCapabilitySubscriptions { workspaceAuthority: args.workspaceAuthority }) this.account = new MobileWebAccountSubscriptions(shared) - this.browser = new MobileWebBrowserStreams({ - ...shared, - workspaceAuthority: args.workspaceAuthority - }) this.workspace = new MobileWebWorkspaceSubscriptions(shared) - this.ledgers = [this.host, this.account, this.browser, this.workspace] + this.ledgers = [this.host, this.account, this.workspace] } countForOperation(operationKey: string): number { diff --git a/mobile/src/mobile-web/mobile-web-mutation-reauthorization-census.test.ts b/mobile/src/mobile-web/mobile-web-mutation-reauthorization-census.test.ts index 54863942db5..eb680a3cb4c 100644 --- a/mobile/src/mobile-web/mobile-web-mutation-reauthorization-census.test.ts +++ b/mobile/src/mobile-web/mobile-web-mutation-reauthorization-census.test.ts @@ -43,13 +43,6 @@ const REAUTHORIZATION_SITES: Record = { // Device-only mutations and handles consumed in one awaited call have no reauthorization window. const NO_REAUTHORIZATION_WINDOW: readonly string[] = [ 'workspace.activate', - 'browser.back', - 'browser.dialog', - 'browser.forward', - 'browser.keyboard', - 'browser.navigate', - 'browser.pointer', - 'browser.reload', 'file.releaseTerminalArtifact', 'native.alert', 'native.clipboardWrite', @@ -167,7 +160,7 @@ describe('mobile web mutation reauthorization census', () => { } expect(unaccounted).toEqual([]) - expect(mutations()).toHaveLength(113) + expect(mutations()).toHaveLength(106) }) it('exempts only registered mutations', () => { @@ -185,6 +178,6 @@ describe('mobile web mutation reauthorization census', () => { const capabilities = Object.keys(MOBILE_WEB_BRIDGE_OPERATIONS) as MobileWebBridgeCapability[] expect([...kinds].sort()).toEqual(['mutation', 'read', 'subscription']) - expect(capabilities).toHaveLength(14) + expect(capabilities).toHaveLength(13) }) }) diff --git a/mobile/src/mobile-web/mobile-web-production-grants.ts b/mobile/src/mobile-web/mobile-web-production-grants.ts index 354e9a7ff39..6dda665d957 100644 --- a/mobile/src/mobile-web/mobile-web-production-grants.ts +++ b/mobile/src/mobile-web/mobile-web-production-grants.ts @@ -1,4 +1,3 @@ -import { MOBILE_WEB_PRODUCTION_BROWSER_GRANTS } from './mobile-web-production-browser-grants' import { MOBILE_WEB_PRODUCTION_FILE_GRANTS } from './mobile-web-production-file-grants' import { capabilityGrants, grantLimits, indexGrants } from './mobile-web-production-grant-table' import { MOBILE_WEB_PRODUCTION_NAVIGATION_GRANTS } from './mobile-web-production-navigation-grants' @@ -16,7 +15,7 @@ export type { MobileWebOperationGrant } from './mobile-web-production-grant-tabl export const MOBILE_WEB_PRODUCTION_GRANTS = [ ...capabilityGrants('workspace', { hostSubscribe: grantLimits(600 * 1024, 1024, 8, 8, 2), - hostRequest: grantLimits(600 * 1024, 600 * 1024, 16, 32, 4), + hostRequest: grantLimits(600 * 1024, 600 * 1024, 16, 48, 24), snapshot: grantLimits(1 * 1024, 128 * 1024, 2, 4, 1), repositories: grantLimits(256, 128 * 1024, 2, 4, 1), subscribe: grantLimits(256, 1 * 1024, 1, 4, 1), @@ -40,7 +39,6 @@ export const MOBILE_WEB_PRODUCTION_GRANTS = [ }), ...MOBILE_WEB_PRODUCTION_SESSION_GRANTS, ...MOBILE_WEB_PRODUCTION_TERMINAL_GRANTS, - ...MOBILE_WEB_PRODUCTION_BROWSER_GRANTS, ...MOBILE_WEB_PRODUCTION_FILE_GRANTS, ...MOBILE_WEB_PRODUCTION_SOURCE_CONTROL_GRANTS, ...MOBILE_WEB_PRODUCTION_SPEECH_GRANTS, diff --git a/mobile/src/mobile-web/mobile-web-request-accounting.ts b/mobile/src/mobile-web/mobile-web-request-accounting.ts index 64652b7a041..fb50a84817d 100644 --- a/mobile/src/mobile-web/mobile-web-request-accounting.ts +++ b/mobile/src/mobile-web/mobile-web-request-accounting.ts @@ -18,7 +18,6 @@ export function mobileWebRequestExpectsSubscription(request: { request.capability === 'session' || request.capability === 'sourceControl' || request.capability === 'terminal' || - request.capability === 'browser' || request.capability === 'nativeChat' || request.capability === 'speech') && request.operation === 'subscribe') diff --git a/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts b/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts index a29565a3b00..f383e03c817 100644 --- a/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts +++ b/mobile/src/mobile-web/mobile-web-subscription-closure-frames.test.ts @@ -2,11 +2,9 @@ import { describe, expect, it, vi } from 'vitest' import type { MobileWebSubscriptionClosure } from './mobile-web-subscription-closure' import type { RpcClient } from '../transport/rpc-client' import { MobileWebAccountSubscriptions } from './mobile-web-account-subscriptions' -import { MobileWebBrowserStreams } from './mobile-web-browser-streams' import { MobileWebSpeechSubscriptions } from './mobile-web-speech-subscriptions' import type { MobileWebSpeechEvent } from '../../../src/shared/mobile-web/speech-operation-contract' import { MobileWebWorkspaceSubscriptions } from './mobile-web-workspace-subscriptions' -import { MobileWebWorkspaceAuthority } from './mobile-web-workspace-authority' const SUBSCRIPTION_ID = 'subscription-1' @@ -18,17 +16,13 @@ type Posts = { type LedgerCase = { name: string - // Browser drops an unparseable frame instead of retiring, so it has no invalid-message closure. + // A push-driven ledger has no host frame to reject, so it has no invalid-message closure. invalidCode: 'invalid_message' | null invalid: unknown valid: unknown open: (posts: Posts) => Promise<(value: unknown) => void> } -function randomBytes(length: number): Uint8Array { - return new Uint8Array(length).fill(4) -} - function hostClient(): { client: RpcClient; emit: (value: unknown) => void } { let listener: ((value: unknown) => void) | undefined const client = { @@ -54,12 +48,6 @@ function hostClient(): { client: RpcClient; emit: (value: unknown) => void } { return { client, emit: (value) => listener?.(value) } } -function pageWorkspace(): { authority: MobileWebWorkspaceAuthority; pageWorkspaceId: string } { - const authority = new MobileWebWorkspaceAuthority(randomBytes) - authority.synchronize([{ workspaceId: 'workspace-1', repoId: 'repo-1' }]) - return { authority, pageWorkspaceId: authority.pageWorkspaceId('workspace-1') } -} - const LEDGER_CASES: LedgerCase[] = [ { name: 'account', @@ -91,36 +79,6 @@ const LEDGER_CASES: LedgerCase[] = [ return host.emit } }, - { - name: 'browser', - invalidCode: null, - invalid: { type: 'bogus' }, - valid: { - type: 'ready', - browserPageId: 'raw-page', - tab: { url: 'https://example.com', title: 'Example', canGoBack: false, canGoForward: false } - }, - open: async (posts) => { - const host = hostClient() - const { authority, pageWorkspaceId } = pageWorkspace() - new MobileWebBrowserStreams({ ...posts, workspaceAuthority: authority }).start({ - requestId: 'request-1', - subscriptionId: SUBSCRIPTION_ID, - payload: { - workspaceId: pageWorkspaceId, - pageId: 'raw-page', - format: 'jpeg', - quality: 72, - maxWidth: 800, - maxHeight: 600, - everyNthFrame: 1, - minFrameIntervalMs: 100 - }, - client: host.client - }) - return host.emit - } - }, { name: 'speech', // Push-driven from the shell's dictation runtime, so no host frame can be unusable. @@ -135,7 +93,7 @@ const LEDGER_CASES: LedgerCase[] = [ } ] -// Ledgers that retire on an unusable host message; browser drops the frame instead, so it is absent. +// Ledgers that retire on an unusable host message; a push-driven ledger has none, so it is absent. const RETIRING_LEDGER_CASES = LEDGER_CASES.filter( (ledger): ledger is LedgerCase & { invalidCode: 'invalid_message' } => ledger.invalidCode !== null ) diff --git a/mobile/src/mobile-web/mobile-web-subscription-error-code.test.ts b/mobile/src/mobile-web/mobile-web-subscription-error-code.test.ts index 82100c4a3a5..54d6b7edd07 100644 --- a/mobile/src/mobile-web/mobile-web-subscription-error-code.test.ts +++ b/mobile/src/mobile-web/mobile-web-subscription-error-code.test.ts @@ -6,7 +6,6 @@ import type { } from '../../../src/shared/mobile-web/bridge-contract' import type { RpcClient } from '../transport/rpc-client' import { MobileWebAccountSubscriptions } from './mobile-web-account-subscriptions' -import { MobileWebBrowserStreams } from './mobile-web-browser-streams' import { isRetryableMobileWebBridgeError, mobileWebBridgeErrorCode @@ -16,9 +15,6 @@ import { mobileWebBridgeRequestMessage } from './mobile-web-bridge-roundtrip-fixture' import { MobileWebWorkspaceSubscriptions } from './mobile-web-workspace-subscriptions' -import { MobileWebWorkspaceAuthority } from './mobile-web-workspace-authority' - -const randomBytes = (length: number): Uint8Array => new Uint8Array(length).fill(4) function stubClient(): RpcClient { return { subscribe: vi.fn(() => () => {}) } as unknown as RpcClient @@ -29,20 +25,9 @@ function ledgerStarters(): { name: string; start: (subscriptionId: string) => vo const postEvent = async (): Promise => {} const isActive = (): boolean => true const client = stubClient() - const workspaceAuthority = new MobileWebWorkspaceAuthority(randomBytes) - workspaceAuthority.synchronize([{ workspaceId: 'host-workspace', repoId: 'repo-1' }]) - const pageWorkspaceId = workspaceAuthority.pageWorkspaceId('host-workspace') - const pageId = 'raw-page' - const postClosed = (): void => {} const account = new MobileWebAccountSubscriptions({ isActive, postEvent, postClosed }) const workspace = new MobileWebWorkspaceSubscriptions({ isActive, postEvent, postClosed }) - const browser = new MobileWebBrowserStreams({ - isActive, - workspaceAuthority, - postEvent, - postClosed - }) return [ { name: 'account', @@ -51,25 +36,6 @@ function ledgerStarters(): { name: string; start: (subscriptionId: string) => vo { name: 'workspace', start: (subscriptionId) => workspace.start({ requestId: 'r', subscriptionId, client }) - }, - { - name: 'browser', - start: (subscriptionId) => - browser.start({ - requestId: 'r', - subscriptionId, - payload: { - workspaceId: pageWorkspaceId, - pageId, - format: 'jpeg', - quality: 72, - maxWidth: 800, - maxHeight: 600, - everyNthFrame: 1, - minFrameIntervalMs: 100 - }, - client - }) } ] } diff --git a/src/shared/mobile-web/bridge-operation-registry.ts b/src/shared/mobile-web/bridge-operation-registry.ts index bbc58ae2b59..0f05197b9c5 100644 --- a/src/shared/mobile-web/bridge-operation-registry.ts +++ b/src/shared/mobile-web/bridge-operation-registry.ts @@ -167,16 +167,6 @@ export const MOBILE_WEB_BRIDGE_OPERATIONS = { manageReview: 'mutation', submitReview: 'mutation' }, - browser: { - subscribe: 'subscription', - navigate: 'mutation', - back: 'mutation', - forward: 'mutation', - reload: 'mutation', - dialog: 'mutation', - pointer: 'mutation', - keyboard: 'mutation' - }, account: { snapshot: 'read', select: 'mutation',