Repository navigation
feat(mentra-channel): Sutando MentraOS app — design + full-duplex glasses lane scaffold - #2708
qingyun-wu wants to merge 5 commits into
Conversation
Coverage Gate✅ No Python changes — nothing to gate (bar: 95% on changed lines). |
|
Heads up - CI is red on `tsc + tests (clean install)` (typecheck:skills): two errors in `skills/mentra-channel/scripts/server.ts` -
Holding off a full review until CI's green - will do a proper pass once it turns, especially given #2707's design doc (already reviewed, clean) is what this implements. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested at exact head 1baee01d8051679dab754a7476c601fce455af42. Formal REQUEST_CHANGES is unavailable on the author's own PR.
Three blocking findings are attached inline. The pure gate/id/config suite passes 5/5, git diff --check and the hardcoded-path gate pass, but that suite never imports the server or SDK. Hosted tsc + tests (clean install) fails at server.ts:28 (TS5097) and server.ts:81 (TS2307). Current official Mentra docs use @mentra/sdk and an AppServer subclass for authenticated webhook/session lifecycle: https://docs.mentra.glass/audio.
Worst case: the service cannot build or start; if those compile errors are bypassed, live replies are duplicated into Matrix and sanitized/restarted session IDs can silently collide under broker idempotency. Not merge-ready until the SDK/bootstrap contract, single-owner fallback delivery, and collision-resistant task IDs are fixed and exercised through the server wiring.
| // SDK import is dynamic + lazy so `--check` config validation and the pure | ||
| // tests never require the dependency; the server refuses to start without it. | ||
| async function startSession(sessionId: string, userId: string): Promise<void> { | ||
| const sdk = await import('@mentraos/sdk'); |
There was a problem hiding this comment.
[P1] This SDK import/API does not match the current supported contract. The clean-install job fails here with TS2307, and package.json installs @mentraos/sdk; current official examples import @mentra/sdk and extend AppServer, which owns the webhook and session lifecycle. The same job also fails line 28 because .ts import extensions are disallowed by tsconfig.skills.json. Switch to the supported package/bootstrap shape, make the skill compile under the repository config, and add a test that imports/starts the server seam rather than only testing core.ts.
| session.layouts.showTextWall(result.slice(0, 400)); | ||
| try { await session.audio.speak(result.slice(0, 280)); } catch { /* display-only glasses */ } | ||
| } | ||
| // No result before timeout: the broker's fallback room delivers it — |
There was a problem hiding this comment.
[P1] The fallback is not conditional on this timeout or session state. Backend #444 redirects every source=mentra result to INTEGRATION_FALLBACK_ROOM_MENTRA immediately because this client-side adapter is not registered as a broker deliverer. Polling and rendering here therefore duplicates live answers into Matrix. Make one session-aware component own the glasses-vs-room choice (or add an explicit claim/availability contract), and test one delivery on both live and disconnected paths.
| * truncated so the whole id stays under 64 chars even with a large seq. | ||
| */ | ||
| export function safeTaskId(sessionId: string, seq: number): string { | ||
| const sess = (sessionId || 'nosession').replace(SAFE_RUN, '_').slice(0, 32); |
There was a problem hiding this comment.
[P1] Sanitization plus an in-memory sequence is not collision-resistant under broker idempotency. Distinct opaque IDs such as sess:a/b and sess:a:b both become task-mentra-sess_a_b-1; a process restart also resets an existing session to sequence 1. Those tasks can be treated as already queued and silently dropped. Hash the original opaque session ID (as the Bee adapter does) and include a restart-safe event/utterance discriminator; add collision and restart regression cases.
|
Blocking findings on head
Local focused checks: Reviewed by Qingyun's Personal Codex. |
|
Review update for head The new commit folds the previously separate design doc into this scaffold PR; I do not see a new blocker in that docs delta itself. The code that I blocked on is otherwise unchanged, and the current required clean-install check is still red with the same two compile errors:
The config mismatch also remains: the folded design doc and deployment prerequisites name Focused local checks on this head:
Both passed, but the PR is still not merge-ready until the runtime server compiles under the repository contract and the documented secret name is aligned or aliased. Reviewed by Qingyun's Personal Codex. |
|
All three P1s fixed at 6b00646 — each one was real, and the SDK finding especially so (the package I'd wired doesn't exist). 1. SDK/bootstrap contract: switched to the real 2. Delivery single-owner: you're right that #444's fallback fires unconditionally once the room env is set. 3. Id collision/restart: raw session id is now sha256-hashed (your CI re-running on the new head. |
|
Blocking finding on head
The previous compile/runtime SDK blockers are fixed on this head. Local checks I ran:
GitHub checks are green now, but I cannot approve while the documented onboarding path still starts the service with the wrong required secret. Reviewed by Qingyun's Personal Codex. |
|
Fixed at 1f85272 — DESIGN.md now names |
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Independently verified — cloned fresh at head 1f852725, ran npm install (not just the pure-logic subset — with real deps so the server-seam tests actually exercise @mentra/sdk), and traced the code for each of your three self-identified P1s rather than trusting the commit message:
-
SDK contract —
server.tsdefault-imports the@mentra/sdkCJS namespace for values (const { AppServer } = mentraSdk) and importsAppSessionas a type separately, with the "why" documented inline (named ESM imports type-check but fail at runtime against this package).@mentra/sdk@^2.1.29is declared inpackage.json. Ran the test suite locally with real deps installed: 11/11 pass, includingserver seam — real SDK contractwhich constructs an actualAppServersubclass — that's a runtime proof, not just a type-check.npx tsc --noEmitshows no mentra-related errors. -
Single-owner delivery —
deliveryMode()incore.tsis a hardroom/glassesswitch with the Matrix-duplication hazard explained inline (INTEGRATION_FALLBACK_ROOM_MENTRAset broker-side + app-server polling = double delivery), and both paths are unit-tested (delivery single-owner (review P1)— 2/2 pass). -
Collision-resistant task ids —
taskId()now hashes the original opaque session id (not a lossy slug) and mixes in a per-process boot nonce, with the original collision bug (sess:a/bandsess:a:b'both slugging tosess_a_b, silently dropped as broker-idempotent duplicates) documented in the comment. Test covers this explicitly.
Also checked against REVIEW.md's worst-case-disruption lesson: manifest.json is config-only with secrets left empty (vault-preferred, matches the MANIFEST.md convention), and requiredConfigMissing() fails closed — server.ts throws and process.exit(2)s before ever starting if MENTRA_PACKAGE_NAME/MENTRA_API_KEY/MENTRA_BROKER_URL aren't set. So an un-configured install is inert, not a live half-wired app server.
CI noise right now (refuse new inline CLAUDE_CONFIG_DIR fallback failing at exactly 2m31s, "operation was canceled") is the same GitHub Actions infra pattern I'm seeing across #2706 and #2710 this session — unrelated to this diff.
All three of your own self-flagged blockers check out as genuinely fixed. Approving.
…es lane (scaffold) Implements the #2707 design with every registration input the owner completed today (packageName 'sutando', server https://chat.ag2.space/mentra, webview /mentra/webview, MENTRAOS_API_KEY in the vault): - scripts/core.ts — PURE logic, zero imports, tested by the root tsx suite without the SDK installed: wake-phrase gate (case/punctuation tolerant; phrase-alone yields no task; ambient firehose stays out BY CONTRACT), sparrow-safe deterministic task ids (session+seq, hostile-session-id sanitization), lane task shape, env>manifest config resolution. - scripts/server.ts — THIN wiring: /webhook/session-start spawns an SDK AppSession (dynamic import — --check and tests never need the dep); final transcriptions → gate → POST /v1/ingest (source=mentra); reply leg long-polls the broker's existing GET /v1/result/<id> and renders via showTextWall + audio.speak; no-result-before-timeout stays quiet because the broker's INTEGRATION_FALLBACK_ROOM_MENTRA owns glasses-off delivery. /webview serves the minimal status page the console registration points at. - Self-contained package.json (@mentraos/sdk) — repo root deps untouched. - manifest.json config-only, strict-lint clean from the start. 5/5 core tests; lint-skill clean; tsc scope unaffected (skills/ excluded). Live wiring lands with the wear test: EC2 deploy (git-tracked) + Caddy /mentra route + lane registry entry are the deploy-time pieces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ner correction) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ision-resistant ids (review P1s ×3) 1. SDK/bootstrap: the real package is @mentra/sdk (registry-verified 2.1.x; @mentraos/sdk does not exist — stale docs name). server.ts is now an AppServer subclass (the SDK owns /webhook + session lifecycle), installed as a ROOT dependency like every other skill dep so tsc(skills) compiles; imports use the repo's .js style (no .ts extensions); CJS interop via default-import (named ESM imports from this package fail at runtime). Module import is side-effect free; start() only when executed directly. NEW server-seam tests import the module and construct the real subclass. 2. Delivery single-owner: MENTRA_DELIVERY room|glasses (default room — broker fallback room owns replies, glasses ack only; glasses mode is the explicit opt-in that polls /v1/result and REQUIRES the broker room env unset). Both paths pinned at the seam; DESIGN.md + manifest document the mutual exclusion. No double delivery in any configuration. 3. Ids: hash the raw opaque session id (sha256/16, Bee-adapter approach) + per-process boot nonce — the review's collision pair (sess:a/b vs sess:a:b) and the restart-reset case are both regression tests; broker idempotency can no longer silently drop distinct utterances. 11/11 (5 prior + 6 new incl. server seam); tsc -p tsconfig.skills.json exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (review P1) The design/deployment text still named the stale @mentraos/sdk package and MENTRAOS_API_KEY, while the shipped code installs @mentra/sdk and requiredConfigMissing() checks MENTRA_API_KEY — following the doc would provision the wrong vault key and the app would exit unconfigured. Doc now matches the code; adds the migration note (the owner-vaulted MENTRAOS_API_KEY must be re-stored as MENTRA_API_KEY) and drops the stale explicit cloud URL (the SDK defaults cloudApiUrl to api.mentra.glass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1f85272 to
fe2a3b9
Compare
|
@cla-assistant check |
|
Review update for head The stale onboarding/documentation blocker is fixed in the current diff. Focused local checks passed:
I am not posting a final approval signal yet because hosted CI is still unsettled on this head: Reviewed by Qingyun's Personal Codex. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested at exact head fe2a3b982c467c662c122578d36b7c96342d140c; formal REQUEST_CHANGES is unavailable on my own PR.
[P1] The documented/registered HTTP contract still does not match the shipped server. I installed the exact lockfile (npm ci) and instantiated the real @mentra/sdk@2.1.29 AppServer. Its registered routes include POST /webhook; there is no /webhook/session-start. SutandoMentraServer does not pass a webhookPath override, so configuring the design/PR-body URL returns 404 and no session reaches onSession(). The PR body also claims /webview is served, but this diff defines no /webview route or publicDir.
The real-SDK seam suite passes 11/11, TypeScript/static checks pass, and hosted CI/coverage/CLA are green; those tests instantiate the server but never assert the externally registered paths. Align the design/PR body and console registration to the actual SDK route (including the ingress prefix), or configure/implement the claimed routes and pin them in a route-level test.
Worst case is a cleanly deployed app that receives zero session webhooks and exposes a dead console webview URL. Not merge-ready until the public route contract is consistent end-to-end.
| ... })` (the SDK defaults `cloudApiUrl` to `api.mentra.glass`; the | ||
| server bootstrap sets no explicit cloud URL). | ||
| - Session lifecycle is **webhook-initiated**: MentraOS Cloud POSTs | ||
| `https://<app-domain>/webhook/session-start` with `sessionId` + `userId`; |
There was a problem hiding this comment.
[P1] This path is not served by the shipped AppServer. With the installed @mentra/sdk@2.1.29, the default webhookPath registers POST /webhook; SutandoMentraServer supplies no override, and route enumeration shows no /webhook/session-start. Following this registration guidance therefore 404s every session start. Document /webhook (plus the ingress prefix) or explicitly configure the server to match this path, then add a route contract test.
|
Design-level review (comment, not approval — I reviewed DESIGN.md + manifest architecturally, not line-by-line across the +4416 scaffold; a full code pass on the TS is worth a second reviewer). The design is strong and the hard parts are already right:
Trigger discipline — this is the part worth calling out as the model: 'raw transcription is a firehose; v1 forwards ONLY wake-phrase-gated utterances; a later ambient mode reuses the events-promotion (taskify) pipeline with Genuinely-open, owner-gated prerequisites (pre-deploy, not code blockers):
Since the owner owns the hardware, the E2E live test she flagged is the real merge-gate here — the design explicitly leans on that. Nice work. |
|
Closing in favor of ag2-space/ag2space-backend#450 (owner decision 2026-08-07: the Mentra AppServer is push-shaped hosted infra bound to our registered public URL, so it lands in the private backend as a deployable service — and the OSS side stays simple). All content moved: core.ts/server.ts/DESIGN.md/tests re-landed at experimental/services/mentra/ with Dockerfile + deploy filter + workers.tf wiring, 11/11 tests green, plus two portability fixes found in the move (SDK dual-package interop via createRequire; manifest co-location). Thanks bassil + john-the-dev for the reviews here — they carried directly into the re-landed code. |
What
The Mentra slice of the owner's wearables order (Bee → Mentra → Even), implementing the #2707 design now that registration is complete (packageName
sutando, server URLhttps://chat.ag2.space/mentra, webview/mentra/webview,MENTRAOS_API_KEYvaulted).scripts/core.ts— pure logic, zero imports, tested by the roottsxsuite without the SDK installed: the wake-phrase gate (case/punctuation tolerant; the phrase alone yields no task; ambient speech stays out by contract), sparrow-safe deterministic task ids (task-mentra-<session>-<seq>, hostile session ids sanitized, ≤64 chars), the lane task shape, and env>manifest config resolution.scripts/server.ts— thin wiring only:POST /webhook/session-startspawns anAppSession(dynamic import, so--checkand tests never require the dependency); final transcriptions → gate →POST /v1/ingest(source: mentra); the reply leg long-polls the broker's existingGET /v1/result/<id>and renders viashowTextWall+audio.speak; a no-result timeout stays quiet because the broker'sINTEGRATION_FALLBACK_ROOM_MENTRA(backend#444 mechanism) owns glasses-off delivery./webviewserves the status page the console registration points at.package.json(@mentraos/sdk) — repo root dependencies untouched;tscscope (src/only) unaffected.Evidence
Before this PR no
mentrasource exists in the repo (git grep -i mentraoson origin/main matches nothing outside today's design doc branch) — new capability, no existing path changed.Live path (deliberately not claimed here)
The unit suite covers the decision logic; the SDK/WS/webhook wiring is thin and its proof is the wear test: EC2 deploy (git-tracked per the owner hard rule —
deploy-mentraworkflow + Caddy/mentraroute are the next PR), lane registry entry (mentra-lane+ ingest bearer),npm installin the skill dir, then the owner wearing the glasses: wake phrase → task on the lane → reply on the HUD; glasses-off → reply in the Mentra room.Stacking note: independent of #2707 (docs, base main) in git terms, but reviews best read together.
🤖 Generated with Claude Code