Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
24de69d
feat(cloud-agent): add sendProactive to fcm dispatcher
equationalapplications Jun 29, 2026
5ed9c11
feat(cloud-agent): implement Expo Push fallback for closed voice sess…
equationalapplications Jun 29, 2026
9259c7f
feat(cloud-agent): add Cloud Scheduler trigger handler and route
equationalapplications Jun 29, 2026
35eeb69
fix(extension): require build-injected env at load time
equationalapplications Jun 29, 2026
7dbabf7
fix(firestore): allow approvalToken in auth doc client updates
equationalapplications Jun 29, 2026
165bdc2
docs(bridge): add phase 3 proactive scheduler implementation plan
equationalapplications Jun 29, 2026
5f72fdd
fix(bridge): address PR 494 review feedback on scheduler and auth
equationalapplications Jun 29, 2026
a56fef7
Merge pull request #494 from equationalapplications/phase3
equationalapplications Jun 29, 2026
968a0f8
feat(bridge): complete Phase 4 E2E validation of MV3 browser extensio…
equationalapplications Jun 30, 2026
6045e4c
docs(bridge): add Phase 4 MV3 browser extension bridge spec
equationalapplications Jun 30, 2026
19e618d
fix(extension): update content-bridge test for sendMessage flow
equationalapplications Jun 30, 2026
572852e
docs(flowcharts): regenerate auto-generated dependency charts
equationalapplications Jun 30, 2026
1ce2477
fix(bridge): address PR review feedback for pending-session polling
equationalapplications Jun 30, 2026
1e12dd6
Merge pull request #495 from equationalapplications/phase4
equationalapplications Jun 30, 2026
cfa6205
fix(bridge): align extension, docs, and drop pending-session poll
equationalapplications Jun 30, 2026
7be41ab
Merge pull request #496 from equationalapplications/kv/fixes
equationalapplications Jun 30, 2026
de57311
docs(extension): add CWS readiness spec for Phase 3 submission prep
equationalapplications Jun 30, 2026
a9a7420
docs(extension): patch June 29 spec — tabs permission + Expo Push pha…
equationalapplications Jun 30, 2026
eaae14f
docs(extension): update CWS spec §2.3 with full privacy policy addendum
equationalapplications Jun 30, 2026
21d4eab
feat(privacy): add browser extension data usage section, bump to v1.5
equationalapplications Jun 30, 2026
b381ac7
docs(extension): fix three inaccuracies in CWS readiness spec
equationalapplications Jun 30, 2026
6875b45
feat(extension): prepare CWS submission readiness
equationalapplications Jun 30, 2026
8a5834b
fix(extension): address PR 497 review feedback
equationalapplications Jun 30, 2026
024069a
Merge pull request #497 from equationalapplications/feat/extension
equationalapplications Jun 30, 2026
484c5fd
fix(cloud-agent): rate-limit scheduler trigger auth for CodeQL
equationalapplications Jun 30, 2026
d12ad20
fix: address PR 498 review feedback on scheduler idempotency and fall…
equationalapplications Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

# Checks
- After changing `root` or `functions/`, run: `npm run typecheck && npm run lint && npm run test`
- After changing `cloud-agent/`, run: `cd cloud-agent && npm run typecheck && npm test`
- After changing `extension/`, run: `cd extension && npm run typecheck && npm test`

# Git & Commits
- **Flow**: feature → `staging` → `main` (via PRs only).
Expand Down
22 changes: 21 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,30 @@ Fixes #456
### Running Tests

```bash
npm test # Run all tests
npm test # Run all tests (Expo app)
npm run test:watch # Run tests in watch mode
```

### Cloud Agent Tests

```bash
cd cloud-agent
npm run typecheck
npm test
```

### Desktop Bridge Extension Tests

```bash
cd extension
npm install # first time only
npm run typecheck
npm test
npm run build # esbuild → dist/ for unpacked Chrome load
```

The root `npm run typecheck` excludes `extension/` and `cloud-agent/` — run each package's typecheck separately. Root `tsconfig.json` excludes those directories; they have their own `tsconfig.json` files.

### Writing Tests

- Add tests for new features in `__tests__` directory
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### AI & Chat
- **[AI & Chat](docs/ai-and-chat.md)** — Chat response pipeline (`generateReply`), chat memory summarization, LLM Wiki Memory (structured facts/tasks/events), wiki state machine architecture, image generation (`generateImage`), and local `cloud-agent` dev (local Postgres + Vertex via ADC; see `docker-compose.local.yml` and `./scripts/migrate-dev.sh`).
- **[Browser Bridge](docs/browser-bridge.md)** — Clanker Desktop Bridge MV3 extension: cross-device web tasks via `browser_action`, Wake-and-Connect lifecycle, Firestore coordination, approval flow, pairing, and billing.
- **[Real-Time Voice Chat](docs/real-time-voice-chat.md)** — Talk tab live voice calls via Gemini Live (`/agent/live` WebSocket), XState session lifecycle, audio I/O, pre-flight checks, and native build requirements.
- **[Edge Agent](docs/edge-agent.md)** — On-device tool orchestration loop (BYOI proxy via `generateReply`), escalation paths, per-round billing, and security policy.

Expand Down
3 changes: 2 additions & 1 deletion app/privacy.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StyleSheet, ScrollView, View } from 'react-native'
import { Text, useTheme } from 'react-native-paper'
import { PRIVACY } from '~/config/privacyConfig'
import { PolicyMarkdown } from '~/components/PolicyMarkdown'

export default function Privacy() {
const { colors } = useTheme()
Expand All @@ -25,7 +26,7 @@ export default function Privacy() {

<View style={styles.separator} />
<ScrollView contentContainerStyle={styles.scrollView}>
<Text>{PRIVACY.privacy}</Text>
<PolicyMarkdown content={PRIVACY.privacy} />
</ScrollView>
</View>
)
Expand Down
6 changes: 4 additions & 2 deletions cloud-agent/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ DEPLOY_ARGS=(
--memory 512Mi
--timeout 540
--set-env-vars "GOOGLE_GENAI_USE_VERTEXAI=true,GOOGLE_CLOUD_PROJECT=${PROJECT_ID},GOOGLE_CLOUD_LOCATION=${GEMINI_LOCATION}"
# GEMINI_API_KEY secret was deleted (embeddings.ts migrated to Vertex AI ADC);
# remove the dangling secret-backed env var carried forward from prior revisions.
# Explicitly pin required secrets so every deploy is self-contained.
# --update-secrets adds/overwrites only these; other secrets (Cloud SQL x4) are preserved.
# GEMINI_API_KEY was deleted from Secret Manager; remove its binding if still present.
--update-secrets "SCHEDULER_SECRET=SCHEDULER_SECRET:latest"
--remove-secrets "GEMINI_API_KEY"
)
if [[ "${ALLOW_UNAUTHENTICATED}" == "true" ]]; then
Expand Down
24 changes: 21 additions & 3 deletions cloud-agent/src/handlers/authApprovalObserver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function baseDeps(over: Record<string, unknown> = {}) {
wakeExtension: async (...a: unknown[]) => { pushes.push({ type: 'wake', args: a }) },
sendTaskComplete: async (...a: unknown[]) => { pushes.push({ type: 'complete', args: a }) },
},
verifyToken: async () => ({ uid: 'uid1' }),
getExpoPushToken: async () => 'ExponentPushToken[x]',
firebaseUid: 'uid1',
sessionId: 'sid1',
Expand Down Expand Up @@ -53,11 +54,11 @@ test('denied writes aborted + sendTaskComplete and unsubscribes', async () => {
assert.ok(getUnsubbed())
})

test('approved sends FCM resume wake without token verification', async () => {
test('approved verifies token and sends FCM resume wake', async () => {
const { deps, getWatcher, pushes } = baseDeps()
startAuthApprovalObserver(deps as never)

getWatcher()!({ status: 'approved', approvalToken: null, approvedAt: null, expiresAt: 0, actionSummary: '' })
getWatcher()!({ status: 'approved', approvalToken: 'valid-token', approvedAt: null, expiresAt: 0, actionSummary: '' })
await new Promise((r) => setTimeout(r, 10))

assert.equal(pushes.length, 1)
Expand All @@ -75,7 +76,7 @@ test('failed FCM wake aborts task instead of resolving', async () => {
})
startAuthApprovalObserver(deps as never)

getWatcher()!({ status: 'approved', approvalToken: null, approvedAt: null, expiresAt: 0, actionSummary: '' })
getWatcher()!({ status: 'approved', approvalToken: 'valid-token', approvedAt: null, expiresAt: 0, actionSummary: '' })
await new Promise((r) => setTimeout(r, 20))

assert.ok(results.length >= 1)
Expand All @@ -85,6 +86,23 @@ test('failed FCM wake aborts task instead of resolving', async () => {
assert.match(result.error.message, /wake/i)
})

test('invalid approval token aborts task with AUTH_DENIED', async () => {
const { deps, getWatcher, results, pushes } = baseDeps({
verifyToken: async () => { throw new Error('invalid token') },
})
startAuthApprovalObserver(deps as never)

getWatcher()!({ status: 'approved', approvalToken: 'bad-token', approvedAt: null, expiresAt: 0, actionSummary: '' })
await new Promise((r) => setTimeout(r, 20))

assert.equal(results.length, 1)
const result = (results[0] as unknown[])[3] as { status: string; error: { code: string; message: string } }
assert.equal(result.status, 'aborted')
assert.equal(result.error.code, 'AUTH_DENIED')
assert.match(result.error.message, /invalid/i)
assert.equal(pushes.length, 1)
})

test('5-minute TTL aborts with AUTH_TIMEOUT and sendTaskComplete', async () => {
const { deps, results, pushes } = baseDeps({ authApprovalTtlMs: 30 })
startAuthApprovalObserver(deps as never)
Expand Down
9 changes: 9 additions & 0 deletions cloud-agent/src/handlers/authApprovalObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const AUTH_APPROVAL_TTL_MS = 5 * 60 * 1000
export interface AuthApprovalObserverDeps {
fs: FirestoreSession
fcmDispatcher?: FcmDispatcher
verifyToken: (token: string) => Promise<{ uid: string }>
getExpoPushToken?: (uid: string) => Promise<string | null>
firebaseUid: string
sessionId: string
Expand Down Expand Up @@ -75,6 +76,14 @@ export function startAuthApprovalObserver(deps: AuthApprovalObserverDeps): void

if (auth.status === 'approved') {
settle(async () => {
try {
const decoded = await deps.verifyToken(auth.approvalToken ?? '')
if (decoded.uid !== deps.firebaseUid) throw new Error('UID mismatch')
} catch {
await abortTask('AUTH_DENIED', 'Approval token invalid. The action was not completed.')
return
}

if (deps.fcmDispatcher && deps.deviceFcmToken) {
try {
await deps.fcmDispatcher.wakeExtension(deps.deviceFcmToken, deps.sessionId, deps.taskId, true)
Expand Down
Loading