mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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<unkn
|
||||
})
|
||||
}
|
||||
|
||||
async function executeBrowser(args: Deps, request: OnceRequest): Promise<unknown> {
|
||||
return executeMobileWebBrowserOperation({
|
||||
operation: request.operation,
|
||||
payload: request.payload,
|
||||
client: args.connectedClient(),
|
||||
workspaceAuthority: args.workspaceAuthority
|
||||
})
|
||||
}
|
||||
|
||||
async function executeTerminal(args: Deps, request: OnceRequest): Promise<unknown> {
|
||||
return args.terminalStreams.handle(request.payload, args.connectedClient())
|
||||
}
|
||||
@@ -158,7 +147,6 @@ export const MOBILE_WEB_ONCE_CAPABILITY_ARMS: Partial<Record<MobileWebBridgeCapa
|
||||
navigation: executeNavigation,
|
||||
agentHistory: (args) => 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<Record<MobileWebBridgeCapa
|
||||
task: executeTask
|
||||
}
|
||||
|
||||
async function subscribeBrowser(args: Deps, request: SubscriptionRequest): Promise<unknown> {
|
||||
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<unknown> {
|
||||
if (request.operation === 'hostSubscribe') {
|
||||
args.hostSubscriptions.start({
|
||||
@@ -228,7 +204,6 @@ export const MOBILE_WEB_SUBSCRIPTION_CAPABILITY_ARMS: Partial<
|
||||
Record<MobileWebBridgeCapability, SubscriptionArm>
|
||||
> = {
|
||||
account: (args) => executeMobileWebAccountCapability(args),
|
||||
browser: subscribeBrowser,
|
||||
workspace: subscribeWorkspace,
|
||||
terminal: subscribeTerminal,
|
||||
speech: subscribeSpeech
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -43,13 +43,6 @@ const REAUTHORIZATION_SITES: Record<string, number> = {
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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<void> => {}
|
||||
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
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user