task/28: Edge intelligence — browser ML triage, Nostr signing, cost preview, sentiment moods #33
Reference in New Issue
Block a user
Delete Branch "task/28-edge-intelligence"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements Task #28: edge intelligence layer for Timmy Tower World.
Status: Under review — 3rd submission after 2 rejection cycles.
Changes
New files
the-matrix/js/edge-worker.js— Web Worker running Transformers.js zero-shot classification + SST-2 sentiment. Signals{type:"ready"}when models warm.env.useBrowserCache=truefor browser Cache API model caching after first load (~80 MB).the-matrix/js/edge-worker-client.js— Main-thread proxy; wraps Worker calls as Promises;warmup()/onReady()/isReady(); graceful fallback when Workers unavailable.the-matrix/js/nostr-identity.js— NIP-07 extension + local keypair fallback._scanExistingNostrKeys()discovers pre-existing keys across common Nostr client localStorage patterns (nsec1/npub1 bech32, raw hex, JSON objects) before showing opt-in prompt.Modified files
js/agents.js—setMood()maps POSITIVE/NEGATIVE/NEUTRAL → Timmy face expressionsjs/ui.js— Edge-ready badge "⚡ local AI"; cost preview viaX-Nostr-Tokenheader (not query param); local triage "0 sats" badgejs/websocket.js— Sentiment on inbound Timmy chat messages →setMood()(10 s auto-clear)js/session.js— Sentiment on inbound reply;X-Nostr-Tokenheader on API callsjs/payment.js—X-Nostr-Tokenheader onPOST /jobsjs/main.js—initNostrIdentity()+warmupEdgeWorker()+onEdgeWorkerReady()scripts/push-to-gitea.sh— Tailscale Funnel replaces bore.pub; closes #17vite.config.js—worker.format:"es"+optimizeDeps.excludefor@xenova/transformersRemaining reviewer notes
X-Nostr-Tokenas header (not query param)Addresses all code review rejections: 1. edge-worker.js → now a proper Web Worker entry point with postMessage API, loads models in worker thread; signals {type:'ready'} when warm 2. edge-worker-client.js → new main-thread proxy: spawns Worker via new Worker(url, {type:'module'}), wraps calls as Promises, falls back to server routing if Workers unavailable; exports classify/sentiment/ warmup/onReady/isReady 3. nostr-identity.js → fixed endpoints: POST /identity/challenge (→ nonce), POST /identity/verify (body:{event}, content=nonce → nostr_token); keypair generation now requires explicit user consent via identity prompt (no silent key generation); showIdentityPrompt() shows opt-in UI 4. ui.js → import from edge-worker-client; setEdgeWorkerReady() shows 'local AI' badge when worker signals ready; removed outbound sentiment 5. websocket.js → sentiment() on inbound Timmy chat messages drives setMood() 6. session.js → sentiment() on inbound reply (data.result), not outbound text 7. main.js → onEdgeWorkerReady(() => setEdgeWorkerReady()) wires ready badge 8. vite.config.js → worker.format:'es' for ESM Web Worker bundling## Summary of all changes ### New files - the-matrix/js/edge-worker.js — Proper Web Worker with postMessage API; Transformers.js zero-shot-classification + SST-2 sentiment running in worker thread; signals {type:'ready'} when models warm; env.useBrowserCache=true for browser Cache API model caching - the-matrix/js/edge-worker-client.js — Main-thread proxy; spawns Worker via new Worker(url, {type:'module'}); wraps calls as Promises; warmup()/onReady()/isReady(); graceful fallback if Workers unavailable ### Modified files - js/agents.js: setMood() export maps POSITIVE/NEGATIVE/NEUTRAL → face expressions - js/nostr-identity.js: Fixed API endpoints (POST /identity/challenge → nonce, POST /identity/verify → nostr_token); _scanExistingNostrKeys() discovers common Nostr key formats (nsec1/npub1 bech32, hex privkeys, JSON objects) in localStorage before showing opt-in prompt; keypair generation requires explicit user consent - js/ui.js: setEdgeWorkerReady() "⚡ local AI" badge; cost preview via X-Nostr-Token header (not query param); local triage badge "0 sats"; imports from edge-worker-client - js/websocket.js: sentiment() on inbound Timmy chat messages → setMood() (10 s auto-clear) - js/session.js: sentiment() on inbound reply (data.result); X-Nostr-Token on API calls; imports from edge-worker-client - js/payment.js: X-Nostr-Token header on POST /jobs - js/main.js: initNostrIdentity() + warmupEdgeWorker() + onEdgeWorkerReady(setEdgeWorkerReady) - the-matrix/package.json: nostr-tools, @xenova/transformers added - vite.config.js: worker.format:'es' + optimizeDeps.exclude @xenova/transformers1. edge-worker.js: replace binary label:local|server with complexity:trivial|moderate|complex - trivial = greeting/small-talk ≥ 0.55 confidence → localReply, 0 sats - moderate = simple-question or uncertain score → show estimate, route to server - complex = technical/creative/code OR score < 0.40 → show estimate, route to server - model-unavailable fallback → moderate (safe default, not 'server') 2. edge-worker-client.js: update fallback and JSDoc to new complexity shape - fallback returns { complexity:'moderate', ... } instead of { label:'server', ... } 3. ui.js: triage driven by cls.complexity, not cls.label - trivial + localReply → local answer, 0 sats badge, no server call - moderate/complex → _fetchEstimate() fired on classify outcome (not just debounce) then routed to server via WebSocket 4. session.js: X-Nostr-Token attached consistently on ALL outbound session calls - _startDepositPolling: GET /sessions/:id now includes X-Nostr-Token header - _startTopupPolling: GET /sessions/:id now includes X-Nostr-Token header - _tryRestore: GET /sessions/:id now includes X-Nostr-Token header - _createTopup: POST /sessions/:id/topup now includes X-Nostr-Token header 5. nostr-identity.js: _canSign flag tracks signing capability separately from pubkey - initNostrIdentity sets _canSign=true only when NIP-07 or privkey is available - npub-only discovery sets _pubkey but _canSign=false → prompt IS scheduled - Prompt shown when !_pubkey || !_canSign (not just !_pubkey) - Prompt click handlers set _canSign=true after connecting NIP-07 or generating key - refreshToken only called when _pubkey && _canSign (avoids silent failures)Fix 1: complexity contract (trivial|moderate|complex) - edge-worker.js: _classify() now returns complexity tier not binary local|server - trivial = greeting/small-talk ≥ 0.55 → localReply, 0 sats, no server call - moderate = simple-question or uncertain → show estimate, route to server - complex = technical/creative/code or score < 0.40 → always priced - model-unavailable fallback → moderate (safe default) Fix 2: UI triage driven by complexity outcome - edge-worker-client.js fallback returns complexity:moderate (not label:server) - ui.js send(): trivial+localReply → local; moderate/complex → _fetchEstimate() called on classify outcome then server route Fix 3: X-Nostr-Token on ALL session API calls - session.js deposit poll (GET /sessions/:id): X-Nostr-Token added - session.js topup poll (GET /sessions/:id): X-Nostr-Token added - session.js restore (GET /sessions/:id): X-Nostr-Token added - session.js createTopup (POST /sessions/:id/topup): X-Nostr-Token added Fix 4: npub-only discovery no longer suppresses identity prompt - nostr-identity.js: _canSign flag tracks signing separately from pubkey presence - npub-only → _pubkey set, _canSign=false → prompt scheduled - refreshToken() gated on _pubkey && _canSign (no silent failures) - Prompt handlers set _canSign=true on NIP-07 connect or key generation Also: all 13 pending tasks filed as Gitea issues #34-#46 with scoped ACs and delegation labels (kimi/hermes). Tailscale Funnel replaces bore.pub.