Skip to content

feat(bridge): Phase 1 MV3 read-only browser extension bridge#490

Merged
equationalapplications merged 11 commits into
stagingfrom
kv/feat
Jun 29, 2026
Merged

feat(bridge): Phase 1 MV3 read-only browser extension bridge#490
equationalapplications merged 11 commits into
stagingfrom
kv/feat

Conversation

@equationalapplications

Copy link
Copy Markdown
Owner

Description

Phase 1 of the MV3 browser extension bridge — enables read-only remote browser actions (extract, read_dom, summarize, scroll, open/focus tab) triggered from the cloud agent via Firestore + FCM + WebSocket relay.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Changes Made

Spec & Plan

  • Full design spec (docs/superpowers/specs/2026-06-29-mv3-browser-extension-bridge-design.md)
  • Phase 1 implementation plan (docs/superpowers/plans/2026-06-29-mv3-bridge-phase1.md)

Shared

  • shared/dsl-types.ts — canonical wire types (SingleAction, SequenceAction, TaskIntent, TaskResult, etc.)
  • shared/dsl-schema.ts — Zod validation + helpers (actionTier, validateTaskIntent)
  • shared/constants.ts — Layer-1 classifier (intentRequiresAuth, DESTRUCTIVE_ACTION_PATTERN)
  • shared/hostPolicy.ts — cloud-side URL blocklist (rejects chrome://, file://, etc.)

Cloud Agent

  • firestoreSession.ts — Firestore CRUD for sessions/tasks/devices
  • fcmDispatcher.ts — FCM v1 wake dispatch
  • sessionBridge.ts — in-memory WS registry
  • browserAction.tsbrowser_action FunctionTool (host blocklist → device lookup → billing → Firestore persist → FCM wake → durable timeout → result delivery)
  • wsBrowserAgentHandler.ts — browser extension WebSocket endpoint (auth, task dispatch, result relay)
  • register-device route for extension device upsert
  • UID split: firebaseUid (Firestore paths) vs userId (Postgres billing)

Extension (extension/)

  • Manifest V3 Chrome extension: service worker, side panel, auth bridge (offscreen doc), WS client, content bridge, task dispatcher, DOM executor
  • content-bridge.ts — host permission gate with pendingHost/pendingOrigin for grant UX
  • panel.ts — reactive Grant Access button, pause toggle, action log
  • esbuild.mjs — build-time env injection from repo .env

Infra

  • firestore.rules — security rules for users/{uid}/sessions and devices
  • firestore.indexes.json — composite indexes for session/task queries
  • firebase.json — Firestore target

Testing

Test Results (all green)

  • Shared: 14 pass (constants, dsl-schema, hostPolicy)
  • Cloud Agent: 146 pass, 1 skip (live Vertex test)
  • Extension: 22 pass

Manual E2E (Task 27 — not yet executed)

Requires real Firebase project with Firestore Native mode, FCM configured, and extension loaded unpacked. Tracked as follow-up.

Platforms Tested

  • Web (cloud agent + extension)
  • iOS (N/A — server-side + extension only)
  • Android (N/A)

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

  • Phase 1 is read-only only — stateful actions (fill, click) are wire-stable but fail-closed
  • Manual E2E gate (Task 27) and console infra (Firestore TTL, FCM Sender ID) are deployment follow-ups
  • Phases 2–4 (stateful actions, approval flow, proactive/scheduler, CWS publish) will be separate PRs

Made with Cursor

equationalapplications and others added 8 commits June 29, 2026 08:59
Wake-and-Connect architecture: FCM silent push wakes MV3 service
worker, opens WebSocket to /agent/browser, SESSION_END tears down.
Firestore as cross-instance session rendezvous. FCM physical-tap
approval gate for destructive actions (Phase 2). Task DSL with
two-layer destructive action classifier (Cloud Coordinator + local
extension DOM validator). Supersedes April 2026 draft spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cross-instance routing: voice-side watchTasks listener owns result
  delivery, not sessionBridge.voiceWs (which is same-instance only)
- browser_action tool schema added to buildLiveTools and edge tools;
  includes credit billing and voice-side Firestore listener pattern
- Firebase auth in MV3 SW: side panel stores refreshToken, SW mints
  idToken via Secure Token REST API on each wake
- FCM push corrected to chrome.gcm API (not firebase/messaging SDK);
  Admin SDK accepts GCM tokens natively
- Phase 1 scope expanded to all 6 read-only/navigation actions
- Billing: 1 credit per browser_action, refund on EXTENSION_OFFLINE
- Manifest key note: private .pem must not be committed to VCS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use Firestore-backed wake timeouts, strict security rules, cloud-only
browser_action, offscreen Firebase Auth, and active-device selection.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fix silent double-billing: billingTimer must pause during browser_action
on voice path; pauseBilling()/resumeBilling() contract now explicit.

Replace 60-min Cloud Run assumption with 30s Promise.race keyed to GCP
LB backend timeout; adds EXECUTION_TIMEOUT error code to cover extension-
connected-but-hung case on text path.

Replace "cryptographic proof of intent" with "deterministic proof of
identity" for approval flow; clarify approvalToken proves uid, not action.

Add isPaused to device doc; getActiveDevice returns null (no credit spent)
when device paused — prevents billing bleed on Pause kill switch.

Drop @google-cloud/firestore prereq (firebase-admin already exposes it).
Add chrome.gcm deprecation and Cloud Run scale-down to risk table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
27-task TDD plan covering cloud-agent coordinator + MV3 extension.
Shared DSL types/schema, Firestore rules, browser_action tool with
contextual billing, /agent/browser WS handler, and extension scaffold
through manual E2E gate. Phases 2-4 deferred to separate plans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ship wake-and-connect coordination via Firestore, browser_action tool,
/agent/browser WebSocket, and the desktop extension scaffold.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add voice execution timeout, Layer-1 intentRequiresAuth classifier,
lastSeenAt refresh on session_ready, paused-device WS rejection,
and build-time extension env injection. Update design spec.

Remove superseded ephemeral superpowers plans.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Split BrowserActionDeps.uid into firebaseUid (Firestore paths) and
  userId (Postgres billing) to fix E2E UID mismatch
- Add shared/hostPolicy.ts with cloud-side blocklist rejecting
  chrome://, file://, etc. before credit spend or FCM dispatch
- Wire HOST_NOT_ALLOWED check in browserAction tool
- Extension content-bridge writes pendingHost/pendingOrigin on
  HOST_PERMISSION_REQUIRED for side-panel grant flow
- Service-worker opens side panel on notification click
- Panel.ts shows/hides Grant Access button reactively

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db578557-9d3a-4eea-a2e7-ef87b98dcd87

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

equationalapplications and others added 2 commits June 29, 2026 11:16
Reflect MV3 Desktop Bridge architecture in C4 diagrams and regenerate
HOOKS and MACHINES dependency charts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Exclude extension and shared bridge tests from Jest (they use node:test
with .js ESM imports) and run them in the staging workflow via tsx.

Co-authored-by: Cursor <cursoragent@cursor.com>
@equationalapplications

Copy link
Copy Markdown
Owner Author

/fix-pr follow-up

Commit: 3d915dbdd9e94b3b597b7a0a415df77416453ede

Review resolution

  • Staging workflow failure (Jest module resolution): Fixed — extension and shared bridge tests use node:test with ESM .js imports and were incorrectly picked up by Jest. Excluded them from jest.config.js and added dedicated staging steps for node --import tsx/esm --test (shared) and extension/npm test.

Verification

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (1)
cloud-agent/src/tools/browserAction.test.ts (1)

73-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression for post-charge setup failures.

This suite still doesn't cover the case where createSession, writeTask, or wakeExtension throws after spendCredit() succeeds. That gap would miss the leaked pause/refund path in the voice flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-agent/src/tools/browserAction.test.ts` around lines 73 - 131, Add a
regression test in browserAction.test.ts for the voice flow where spendCredit()
has already run but a later setup step fails, covering createSession, writeTask,
or wakeExtension throwing after the charge. Update the browserActionTool execute
path test setup to simulate one of those post-charge failures and assert the
failure is surfaced while the paused billing state is cleaned up correctly. Use
the browserActionTool and execute symbols to locate the flow, and verify the
refund/resumeBilling path is not leaked when the exception happens after
charging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cloud-agent/src/handlers/wsBrowserAgentHandler.test.ts`:
- Around line 82-88: The invalid-device test in wsBrowserAgentHandler.test.ts is
using a non-UUID sessionId, so auth fails before the validateDevice path in
handleBrowserWsUpgrade can run. Update the test data in rejects auth when
deviceId invalid to use a valid UUID-formatted sessionId (and keep the invalid
deviceId) so the request reaches the device validation branch and asserts the
correct close code.

In `@cloud-agent/src/handlers/wsBrowserAgentHandler.ts`:
- Around line 23-29: The task_error frame schema in wsBrowserAgentHandler is too
permissive because errorFrameSchema currently accepts any code and failedAction
values before casting them into BridgeErrorCode and SingleAction. Tighten the
validation by using the shared DSL/types for these fields directly in the schema
and in the task_error handling path so malformed frames are rejected before
being written to Firestore; update the parsing logic around errorFrameSchema and
the task_error processing code to rely on the shared contract instead of unsafe
coercion.
- Around line 60-83: Handle rejections from the async WebSocket handlers instead
of firing them with void: in wsBrowserAgentHandler’s onAuth flow and the
message/error handlers around it, wrap calls like verifyToken, resolveUserId,
validateDevice, fs.getSession, fs.getFirstTask, and fs.markBrowserConnected in a
try/catch (or central promise handler) so any failure closes the socket with a
deterministic terminal response and logs the error. Use the existing onAuth and
related handler wiring to locate the fix, and avoid leaving rejected promises
unobserved.
- Around line 70-72: The session gate in wsBrowserAgentHandler should treat
aborted sessions as terminal too, not just closed. Update the check after
fs.getSession(resolved, sid) so that the WebSocket is rejected when
session.status is either 'closed' or 'aborted', matching the terminal behavior
already modeled by closeSession and preventing reconnects for aborted sessions.

In `@cloud-agent/src/handlers/wsLiveAgentHandler.test.ts`:
- Around line 123-126: The test in wsLiveAgentHandler.test.ts has a duplicate
fakeSetInterval declaration, causing a parse-time duplicate identifier error.
Update the billing controller test setup in the pauseBilling/resume block so
fakeSetInterval is defined only once, and keep the single declaration wired into
makeBillingController and the interval-spending assertions.

In `@cloud-agent/src/handlers/wsLiveAgentHandler.ts`:
- Around line 380-384: The delayed browser result in pushToLive is using a new
random UUID instead of the original Gemini FunctionCall.id, so the response
cannot be matched correctly. Update wsLiveAgentHandler’s browser_action flow to
capture the original call id when the function is invoked and pass that same id
into pushToLive, so geminiSession.sendToolResponse sends a functionResponse with
the original identifier rather than crypto.randomUUID().

In `@cloud-agent/src/index.ts`:
- Around line 323-338: The browser bridge routes are mounted even when the Admin
SDK is not initialized, so authenticated requests can reach handlers like
register-device and later fail inside
admin.firestore()/defaultFirestoreSession(). Add an early guard in the browser
HTTP/WS entry points (for example in runAgentReal() setup and the route handlers
around register-device / related browser bridge methods) that checks whether the
bridge is configured and returns a clear “bridge unavailable” response instead
of proceeding. Use the existing bridge setup condition based on
admin.apps.length > 0 to gate these endpoints consistently.
- Around line 311-320: The upsert logic in upsertDevice currently overwrites
device pause state and the original registration timestamp on every
re-registration. Update the firestore write in the upsertDevice callback so
isPaused is only included when body.isPaused is explicitly provided, and make
registeredAt set only on first insert rather than being reset by later merges.
Add a regression test covering the omitted-isPaused path to verify a token
refresh does not unpause the device or تغيير the first-paired timestamp.

In `@cloud-agent/src/services/firestoreSession.ts`:
- Around line 43-53: The `getActiveDevice()` query in `firestoreSession.ts` is
stricter than the authentication logic in `cloud-agent/src/index.ts` because it
only accepts `isPaused === false`, while the browser connect path treats a
missing `isPaused` as active. Update `getActiveDevice()` to use the same
semantics as the connect flow (or ensure the field is backfilled consistently)
so older `DeviceDoc` records can still be selected; focus on the
`getActiveDevice` method and its `where('isPaused', '==', false)` filter.
- Around line 69-74: The state transition in markBrowserConnected is split
across two independent Firestore writes, so the session can be left in routing
if the task update fails. Update this flow to use a single Firestore transaction
or batch that writes both sessionPath(...) and taskPath(...) together, or add a
rollback path that restores the session if the task update does not succeed.

In `@cloud-agent/src/services/sessionBridge.ts`:
- Around line 24-27: The deregistration logic in sessionBridge.deregister
currently removes the entire session entry, which causes wsBrowserAgentHandler’s
browser socket close path to tear down voiceWs and firestoreUnsub too. Split the
cleanup in sessionBridge so browser-side disconnects only clear the browser
socket state, and preserve the voice side plus watcher until the whole session
is actually finished; only fully delete the map entry when both sides are done.

In `@cloud-agent/src/tools/browserAction.ts`:
- Around line 84-99: The browser action flow in the main handler pauses billing
and charges a credit, but failures in createSession, writeTask, or wakeExtension
can exit without restoring state. Wrap the post-charge setup in a try/catch
around the browserAction path so that any rejection after spendCredit() triggers
deps.resumeBilling() and deps.creditService.refundCredit(txId) when txId exists,
while still preserving the existing out-of-credits early return behavior.
- Around line 120-133: The timeout path in waitForTerminalTask currently returns
executionTimeoutTask() without stopping the active Firestore listener or
wakeTimer started by watch/fs.watchTask. Update the browserAction.ts logic so
the Promise.race timeout cleanup unsubscribes the watch and clears the timer
before returning the timeout task, using the existing watch(), unsub, wakeTimer,
and settled flow to ensure no later enforceWakeTimeout() callback can fire after
EXECUTION_TIMEOUT.

---

Nitpick comments:
In `@cloud-agent/src/tools/browserAction.test.ts`:
- Around line 73-131: Add a regression test in browserAction.test.ts for the
voice flow where spendCredit() has already run but a later setup step fails,
covering createSession, writeTask, or wakeExtension throwing after the charge.
Update the browserActionTool execute path test setup to simulate one of those
post-charge failures and assert the failure is surfaced while the paused billing
state is cleaned up correctly. Use the browserActionTool and execute symbols to
locate the flow, and verify the refund/resumeBilling path is not leaked when the
exception happens after charging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21d1d6b6-4e16-4b4e-b878-50e3f3770f5e

📥 Commits

Reviewing files that changed from the base of the PR and between 94bb545 and 3d915db.

⛔ Files ignored due to path filters (4)
  • extension/icons/icon-128.png is excluded by !**/*.png
  • extension/icons/icon-16.png is excluded by !**/*.png
  • extension/icons/icon-48.png is excluded by !**/*.png
  • extension/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (80)
  • .github/workflows/staging-test.yml
  • cloud-agent/src/agent.live.test.ts
  • cloud-agent/src/handlers/wsBrowserAgentHandler.test.ts
  • cloud-agent/src/handlers/wsBrowserAgentHandler.ts
  • cloud-agent/src/handlers/wsLiveAgentHandler.test.ts
  • cloud-agent/src/handlers/wsLiveAgentHandler.ts
  • cloud-agent/src/index.test.ts
  • cloud-agent/src/index.ts
  • cloud-agent/src/registerDevice.test.ts
  • cloud-agent/src/services/agentCore.ts
  • cloud-agent/src/services/fcmDispatcher.test.ts
  • cloud-agent/src/services/fcmDispatcher.ts
  • cloud-agent/src/services/firestoreSession.test.ts
  • cloud-agent/src/services/firestoreSession.ts
  • cloud-agent/src/services/instanceId.ts
  • cloud-agent/src/services/liveToolAdapter.ts
  • cloud-agent/src/services/sessionBridge.test.ts
  • cloud-agent/src/services/sessionBridge.ts
  • cloud-agent/src/tools/browserAction.test.ts
  • cloud-agent/src/tools/browserAction.ts
  • cloud-agent/src/tools/browserActionWiring.test.ts
  • docs/flowcharts/HOOKS.md
  • docs/flowcharts/MACHINES.md
  • docs/flowcharts/c4/containers.md
  • docs/flowcharts/c4/system-context.md
  • docs/superpowers/plans/2026-06-19-agent-tool-mixing-live-test-plan.md
  • docs/superpowers/plans/2026-06-19-web-search-tool-plan.md
  • docs/superpowers/plans/2026-06-20-cloud-document-conversion-plan.md
  • docs/superpowers/plans/2026-06-20-document-upload-progress-feedback-plan.md
  • docs/superpowers/plans/2026-06-20-document-upload-reliability-hardening.md
  • docs/superpowers/plans/2026-06-20-organization-schema.md
  • docs/superpowers/plans/2026-06-23-cloud-ontology-graph-traversal-plan.md
  • docs/superpowers/plans/2026-06-23-phase2-edge-loop-ontology-ui-design.md
  • docs/superpowers/plans/2026-06-25-websocket-agent-streaming.md
  • docs/superpowers/plans/2026-06-26-live-voice-backend.md
  • docs/superpowers/plans/2026-06-26-real-time-voice-chat.md
  • docs/superpowers/plans/2026-06-26-web-voice.md
  • docs/superpowers/plans/2026-06-27-native-live-voice-playback.md
  • docs/superpowers/plans/2026-06-29-mv3-bridge-phase1.md
  • docs/superpowers/specs/2026-06-29-mv3-browser-extension-bridge-design.md
  • extension/esbuild.mjs
  • extension/manifest.json
  • extension/package.json
  • extension/src/background/auth-bridge.test.ts
  • extension/src/background/auth-bridge.ts
  • extension/src/background/content-bridge.test.ts
  • extension/src/background/content-bridge.ts
  • extension/src/background/service-worker.ts
  • extension/src/background/task-dispatcher.test.ts
  • extension/src/background/task-dispatcher.ts
  • extension/src/background/ws-client.test.ts
  • extension/src/background/ws-client.ts
  • extension/src/content/dom-extractor.test.ts
  • extension/src/content/dom-extractor.ts
  • extension/src/content/executor.test.ts
  • extension/src/content/executor.ts
  • extension/src/content/safety-classifier.test.ts
  • extension/src/content/safety-classifier.ts
  • extension/src/env.ts
  • extension/src/offscreen/auth.html
  • extension/src/offscreen/auth.ts
  • extension/src/shared/constants.ts
  • extension/src/shared/dsl-types.ts
  • extension/src/ui/popup/index.html
  • extension/src/ui/popup/popup.ts
  • extension/src/ui/side-panel/index.html
  • extension/src/ui/side-panel/panel.ts
  • extension/test/chrome-stub.ts
  • extension/tsconfig.json
  • firebase.json
  • firestore.indexes.json
  • firestore.rules
  • jest.config.js
  • shared/constants.test.ts
  • shared/constants.ts
  • shared/dsl-schema.test.ts
  • shared/dsl-schema.ts
  • shared/dsl-types.ts
  • shared/hostPolicy.test.ts
  • shared/hostPolicy.ts
💤 Files with no reviewable changes (13)
  • docs/superpowers/plans/2026-06-20-organization-schema.md
  • docs/superpowers/plans/2026-06-20-document-upload-reliability-hardening.md
  • docs/superpowers/plans/2026-06-26-web-voice.md
  • docs/superpowers/plans/2026-06-23-cloud-ontology-graph-traversal-plan.md
  • docs/superpowers/plans/2026-06-20-cloud-document-conversion-plan.md
  • docs/superpowers/plans/2026-06-25-websocket-agent-streaming.md
  • docs/superpowers/plans/2026-06-27-native-live-voice-playback.md
  • docs/superpowers/plans/2026-06-19-web-search-tool-plan.md
  • docs/superpowers/plans/2026-06-26-real-time-voice-chat.md
  • docs/superpowers/plans/2026-06-20-document-upload-progress-feedback-plan.md
  • docs/superpowers/plans/2026-06-23-phase2-edge-loop-ontology-ui-design.md
  • docs/superpowers/plans/2026-06-19-agent-tool-mixing-live-test-plan.md
  • docs/superpowers/plans/2026-06-26-live-voice-backend.md

Comment thread cloud-agent/src/handlers/wsBrowserAgentHandler.test.ts
Comment thread cloud-agent/src/handlers/wsBrowserAgentHandler.ts Outdated
Comment thread cloud-agent/src/handlers/wsBrowserAgentHandler.ts
Comment thread cloud-agent/src/handlers/wsBrowserAgentHandler.ts
Comment thread cloud-agent/src/handlers/wsLiveAgentHandler.test.ts
Comment thread cloud-agent/src/services/firestoreSession.ts
Comment thread cloud-agent/src/services/firestoreSession.ts
Comment thread cloud-agent/src/services/sessionBridge.ts
Comment thread cloud-agent/src/tools/browserAction.ts Outdated
Comment thread cloud-agent/src/tools/browserAction.ts Outdated
Harden browser bridge auth/validation, billing cleanup, device upsert
semantics, and session lifecycle handling from review threads.

Co-authored-by: Cursor <cursoragent@cursor.com>
@equationalapplications

Copy link
Copy Markdown
Owner Author

/fix-pr follow-up

Commit: 60b4cdc4

Review resolution

  • Invalid-device test uses non-UUID sessionIdFixed: test now uses SESSION_ID so validation reaches validateDevice (wsBrowserAgentHandler.test.ts)
  • task_error schema too permissiveFixed: validate with shared taskErrorFrameSchema instead of unsafe casts (shared/dsl-schema.ts, wsBrowserAgentHandler.ts)
  • Async handler rejections droppedFixed: .catch() on onAuth / onResult closes socket with 1011 (wsBrowserAgentHandler.ts)
  • Aborted sessions not rejectedFixed: gate now blocks both closed and aborted (wsBrowserAgentHandler.ts)
  • Duplicate fakeSetInterval declarationNot applied: only one declaration exists in current code; file parses and tests pass
  • Delayed browser result uses fresh UUIDFixed: pushToLive reuses captured browser_action call id (wsLiveAgentHandler.ts)
  • Device upsert overwrites pause state / registeredAtFixed: extracted upsertDeviceRecord with conditional fields + regression tests (deviceUpsert.ts, deviceUpsert.test.ts, index.ts)
  • Browser bridge mounted without Admin SDKFixed: 503 on register-device and WS destroy on /agent/browser when bridge unavailable (index.ts)
  • isPaused semantics mismatchFixed: getActiveDevice treats missing isPaused as active, matching connect validation (firestoreSession.ts)
  • markBrowserConnected not atomicFixed: Firestore batch when available (firestoreSession.ts)
  • Browser disconnect tears down voice bridgeFixed: added deregisterBrowser that only clears browser socket (sessionBridge.ts, wsBrowserAgentHandler.ts)
  • Post-charge setup failures leave billing pausedFixed: try/catch refunds credit, aborts session, resumes billing (browserAction.ts)
  • Execution timeout leaves Firestore watch aliveFixed: timeout path unsubscribes watch and clears wake timer (browserAction.ts)

Verification

  • typecheck — pass (cd cloud-agent && npm run typecheck)
  • lint — N/A (no lint script in cloud-agent/package.json; root npm run lint is Expo app scope)
  • tests — pass (cd cloud-agent && npm test targeted suites; handler + service + browserAction tests all green)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant