Mobile app + vendored protocol (core-loop UI, EAS, runbooks)#2
Merged
Conversation
Approach A mock seam; parallel UI track on build/mobile-ui; backend (M0, PIN url) deferred until ColeMurray/background-agents@a7b968f is deployed.
…safe subset)
6-file type/model closure (types, integrations, triggers-{types,conditions}, models, git) + reproducible scripts/vendor-protocol.sh. PIN url=pending-deploy. Closure typechecks strict; no zod/node/cloudflare/crypto in closure.
…nav shell Approach-A seam: SessionGateway + MockSessionGateway + TanStack Query hooks; pure stream transforms (collapseTokenEvents/foldEvent) ported @ a7b968f. @expo/ui wrapper with Expo-Go-safe RN primitives. Expo Router shell + runnable placeholder screens. tsc clean; Metro/Hermes iOS bundle succeeds (punycode shim for markdown-it). Removed Expo template starter components.
…in, settings Five slices built in parallel against the frozen ui/data seam (exports intact, in-dir only, no new deps). Integrated: pnpm exec tsc --noEmit clean; Metro/Hermes iOS bundle succeeds. Profiles use an in-memory store seam (AsyncStorage not installed; reversible).
…auth gate + sheets 1) profile-store backed by expo-sqlite/kv-store (sync hydrate+persist, same API). 2) expo-updates + runtimeVersion=fingerprint in app.json + eas.json channels (dev/preview/production). 3) mock AuthProvider + Stack.Protected gate + formSheet detents for new/sign-in; sign-in flips auth. 4) docs/runbooks/dev-and-eas.md. tsc clean; Metro iOS bundle OK.
…/ top dead space)
…re credentials.json, document EAS Metadata
…s-EAS in pnpm monorepo)
…ack header; large titles; no dead top space)
…View (overlap/margins)
…alytics, needs native build)
… tfvars (mirrors background-agents)
… separate PR) Removes terraform/ + gateway TF spec and reverts apps/gateway/package.json to main; the rest is the build/mobile-ui app+protocol work with its full signed history.
There was a problem hiding this comment.
Pull request overview
Bootstraps an Expo SDK 55 React Native mobile app along with a vendored, Hermes-safe subset of the Open-Inspect protocol that it depends on. The mobile app implements the core-loop screens against a typed SessionGateway mock seam (TanStack Query + pure stream transforms) so the real HTTP/WS implementation can drop in later without screen changes, and ships EAS/TestFlight config and runbooks.
Changes:
- Vendor
packages/protocolfrombackground-agents@a7b968fvia a reproduciblescripts/vendor-protocol.sh(withPINprovenance and flattened/rewritten imports). - Add
apps/mobilecore-loop UI: list/detail/connect screens, mockSessionGateway+ event emitter + fixtures, TanStack Query data layer, stream transforms (live fold + replay coalesce), expo-sqlite/kv-store, mock auth gate, native UIKit headers. - EAS + ops:
eas.json, bundle id /runtimeVersion: appVersion,expo-updates/expo-insights, dev/EAS runbook, core-loop design spec, repo-wide secret.gitignorehardening, pnpmpackageManagerpin.
Reviewed changes
Copilot reviewed 65 out of 67 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/vendor-protocol.sh |
Reproducible vendor script: clones upstream, copies fixed file set with provenance header, rewrites cross-dir imports, writes PIN. |
packages/protocol/src/*.ts, PIN, package.json |
Vendored Hermes-safe protocol subset (types, integrations, triggers, models, git) + barrel + pinning metadata. |
apps/mobile/src/features/sessions/stream/transforms.ts |
foldEvent (live) and collapseTokenEvents (replay) — live path does not progressively expose tokens (see comment). |
apps/mobile/src/features/sessions/{list,detail,connect}/* |
Core-loop screens consuming the gateway via TanStack Query and useSessionStream. |
apps/mobile/src/data/{gateway.ts,queries.ts,mock/*} |
SessionGateway interface, query hooks, mock gateway + scenario fixtures + event emitter. |
apps/mobile/src/features/auth/*, native UIKit header components |
Mock auth gate and iOS-flavored chrome. |
apps/mobile/{app.json,eas.json,package.json,babel.config.js,...} |
Expo/EAS config: bundle id, runtimeVersion: appVersion, channels, expo-updates/expo-insights, expo-sqlite. |
pnpm-lock.yaml, root package.json |
New deps (expo-updates, expo-insights, expo-sqlite, punycode, @constructor/protocol workspace link) and pnpm pin. |
docs/superpowers/specs/2026-05-17-mobile-ui-core-loop-design.md, docs/runbooks/dev-and-eas.md |
Core-loop design spec (source of truth for live/replay semantics) and dev/EAS runbook. |
.gitignore |
Repo-wide hardening of secret/env patterns. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+55
to
+82
| export function foldEvent(prev: SandboxEvent[], event: SandboxEvent, pending: PendingRef): SandboxEvent[] { | ||
| if (event.type === 'token' && event.content && event.messageId) { | ||
| pending.current = { | ||
| content: event.content, | ||
| messageId: event.messageId, | ||
| sandboxId: event.sandboxId, | ||
| timestamp: event.timestamp, | ||
| }; | ||
| return prev; | ||
| } | ||
| if (event.type === 'execution_complete') { | ||
| const out = [...prev]; | ||
| if (pending.current) { | ||
| const p = pending.current; | ||
| pending.current = null; | ||
| out.push({ | ||
| type: 'token', | ||
| content: p.content, | ||
| messageId: p.messageId, | ||
| sandboxId: p.sandboxId, | ||
| timestamp: p.timestamp, | ||
| }); | ||
| } | ||
| out.push(event); | ||
| return out; | ||
| } | ||
| return [...prev, event]; | ||
| } |
Comment on lines
+49
to
+59
| subscribe(id: string, on: StreamListeners): StreamHandle { | ||
| const state = mockSessionState(id); | ||
| const snapshot: SubscribeSnapshot = { | ||
| state, | ||
| artifacts: [], | ||
| replay: { events: [], hasMore: false, cursor: null }, | ||
| }; | ||
| const script = state.status === 'failed' ? scenarioError() : scenarioHappy(); | ||
| const cancel = startScriptedStream(snapshot, script, on); | ||
| return { unsubscribe: cancel }; | ||
| } |
Comment on lines
+69
to
+102
| export function scenarioHappy(): SandboxEvent[] { | ||
| const t = () => Date.now() / 1000; | ||
| const mid = 'm_1'; | ||
| const partials = [ | ||
| 'Looking at the settings screen…', | ||
| 'Looking at the settings screen… adding a `Toggle`', | ||
| 'Looking at the settings screen… adding a `Toggle` bound to the theme store.', | ||
| 'Looking at the settings screen… adding a `Toggle` bound to the theme store.\n\n```tsx\n<Toggle value={dark} onValueChange={setDark} />\n```\n\nDone.', | ||
| ]; | ||
| const evts: SandboxEvent[] = [ | ||
| { type: 'user_message', content: 'Add a dark-mode toggle to the settings screen', messageId: mid, timestamp: t() }, | ||
| { type: 'step_start', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| { type: 'tool_call', tool: 'read_file', args: { path: 'src/app/settings.tsx' }, callId: 'c1', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| { type: 'tool_result', callId: 'c1', result: 'export default function Settings() { … }', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| ]; | ||
| for (const p of partials) { | ||
| evts.push({ type: 'token', content: p, messageId: mid, sandboxId: SBX, timestamp: t() }); | ||
| } | ||
| evts.push({ type: 'step_finish', cost: 0.0123, tokens: 1840, messageId: mid, sandboxId: SBX, timestamp: t() }); | ||
| evts.push({ type: 'execution_complete', messageId: mid, success: true, sandboxId: SBX, timestamp: t() }); | ||
| return evts; | ||
| } | ||
|
|
||
| /** Scenario B — error path. */ | ||
| export function scenarioError(): SandboxEvent[] { | ||
| const t = () => Date.now() / 1000; | ||
| const mid = 'm_err'; | ||
| return [ | ||
| { type: 'user_message', content: 'Refactor the auth module', messageId: mid, timestamp: t() }, | ||
| { type: 'step_start', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| { type: 'tool_call', tool: 'run_tests', args: {}, callId: 'c9', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| { type: 'error', error: 'Test suite failed: 3 failing in auth.test.ts', messageId: mid, sandboxId: SBX, timestamp: t() }, | ||
| { type: 'execution_complete', messageId: mid, success: false, error: 'aborted', sandboxId: SBX, timestamp: t() }, | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope
Expo SDK 55 RN app (the mobile deliverable) + the vendored protocol it depends on. Full signed commit history of the app+protocol work; gateway Terraform is a separate PR.
Contents
Verification
pnpm tsc --noEmit clean; expo export (iOS Metro/Hermes) bundles; every commit GPG-signed; no secrets committed.
Notes
Data layer is the mock gateway until ColeMurray/background-agents@a7b968f is deployed; the real HTTP/WS impl drops into the same seam with no screen changes.