task/28: Edge intelligence — browser ML triage, Nostr signing, cost preview, sentiment moods #33

Closed
replit wants to merge 0 commits from task/28-edge-intelligence into main
Owner

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=true for 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.jssetMood() maps POSITIVE/NEGATIVE/NEUTRAL → Timmy face expressions
  • js/ui.js — Edge-ready badge " local AI"; cost preview via X-Nostr-Token header (not query param); local triage "0 sats" badge
  • js/websocket.js — Sentiment on inbound Timmy chat messages → setMood() (10 s auto-clear)
  • js/session.js — Sentiment on inbound reply; X-Nostr-Token header on API calls
  • js/payment.jsX-Nostr-Token header on POST /jobs
  • js/main.jsinitNostrIdentity() + warmupEdgeWorker() + onEdgeWorkerReady()
  • scripts/push-to-gitea.sh — Tailscale Funnel replaces bore.pub; closes #17
  • vite.config.jsworker.format:"es" + optimizeDeps.exclude for @xenova/transformers

Remaining reviewer notes

  • All authenticated calls send X-Nostr-Token as header (not query param)
  • Key discovery scans common Nostr client storage patterns before showing prompt
  • npub-only discovery (view-only, no privkey) does not suppress identity prompt
## 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=true` for 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 expressions - `js/ui.js` — Edge-ready badge "⚡ local AI"; cost preview via `X-Nostr-Token` header (not query param); local triage "0 sats" badge - `js/websocket.js` — Sentiment on inbound Timmy chat messages → `setMood()` (10 s auto-clear) - `js/session.js` — Sentiment on inbound reply; `X-Nostr-Token` header on API calls - `js/payment.js` — `X-Nostr-Token` header on `POST /jobs` - `js/main.js` — `initNostrIdentity()` + `warmupEdgeWorker()` + `onEdgeWorkerReady()` - `scripts/push-to-gitea.sh` — Tailscale Funnel replaces bore.pub; closes #17 - `vite.config.js` — `worker.format:"es"` + `optimizeDeps.exclude` for `@xenova/transformers` ### Remaining reviewer notes - All authenticated calls send `X-Nostr-Token` as header (not query param) - Key discovery scans common Nostr client storage patterns before showing prompt - npub-only discovery (view-only, no privkey) does not suppress identity prompt
replit added 6 commits 2026-03-19 18:41:16 +00:00
- js/edge-worker.js: new — browser-side classify() + sentiment() via Transformers.js
- js/nostr-identity.js: new — NIP-07 extension + localStorage keypair fallback,
  challenge→sign→verify flow, token caching
- js/agents.js: export setMood() for sentiment-driven face expressions
- js/ui.js: local triage badge, cost preview via /api/estimate, sentiment on send
- js/payment.js: X-Nostr-Token injection on POST /jobs
- js/session.js: X-Nostr-Token injection on session create + request, sentiment mood
- js/main.js: initNostrIdentity() + warmupEdgeWorker() on firstInit
- vite.config.js: optimizeDeps.exclude @xenova/transformers
## What was built
Five components of Task #28 implemented:

1. js/edge-worker.js (new) — Transformers.js zero-shot-classification + SST-2 sentiment,
   lazy model loading, fast heuristic greeting bypass, warmup() pre-loader.

2. js/nostr-identity.js (new) — NIP-07 extension preferred (window.nostr),
   falls back to generated localStorage keypair; challenge→sign→verify flow
   with the /api/nostr/challenge + /api/nostr/verify endpoints;
   token cached in localStorage with 23 h TTL.

3. js/agents.js — setMood() export maps POSITIVE/NEGATIVE/NEUTRAL sentiment
   labels to Timmy face states (curious/focused/contemplative) via existing
   setFaceEmotion() + MOOD_ALIASES infrastructure.

4. js/ui.js — edge triage on every send: trivial/greeting → answered locally
   with "local  0 sats" badge, no server round-trip; cost preview badge
   debounced 300 ms via GET /api/estimate, shows FREE / partial / full cost;
   sentiment-driven setMood() on every user message (auto-clears after 8 s).

5. js/payment.js, js/session.js — X-Nostr-Token header injected on all
   authenticated API calls (POST /jobs, POST /sessions, POST session/request).
   session.js also runs sentiment → setMood() on every session send.

6. js/main.js — initNostrIdentity('/api') + warmupEdgeWorker() called on firstInit.

7. vite.config.js — optimizeDeps.exclude @xenova/transformers to prevent
   Vite from pre-bundling the WASM/dynamic-import-heavy transformer library.

8. the-matrix/package.json — nostr-tools, @xenova/transformers added as deps.

## No server-side changes — all Task #28 work is frontend-only.
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
## What was built (Task #28 — all 5 requirements)

### 1. js/edge-worker.js (Web Worker)
Proper Web Worker entry point (not a regular module) using postMessage API.
Loads Transformers.js zero-shot-classification + SST-2 sentiment models in the
worker thread. Signals { type:'ready' } when both models are warm. Handles
{ id, type:'classify'|'sentiment', text } messages and replies with results.
Fast greeting heuristic bypasses model for trivial inputs.

### 2. js/edge-worker-client.js (main-thread proxy)
Main-thread wrapper that spawns edge-worker.js via new Worker(url, {type:'module'}).
Wraps classify()/sentiment() as Promise-based API. Exports: warmup(), onReady(),
isReady(). Gracefully falls back to server routing if Web Workers unavailable.

### 3. js/nostr-identity.js
- Fixed Nostr API endpoints to match server: POST /identity/challenge (→ nonce),
  POST /identity/verify (body:{event}, event.content=nonce → nostr_token)
- NIP-07 extension preferred; localStorage keypair only generated on explicit
  user consent via showIdentityPrompt() UI — no silent key generation
- getOrRefreshToken() used by payment.js/session.js for X-Nostr-Token header

### 4. js/agents.js
setMood() export maps POSITIVE/NEGATIVE/NEUTRAL → Timmy face expressions
(curious/focused/contemplative) via setFaceEmotion() + MOOD_ALIASES.

### 5. Sentiment on INBOUND messages (per reviewer requirement)
- websocket.js: sentiment() runs on Timmy's inbound `chat` messages → setMood()
- session.js: sentiment() runs on data.result (Timmy's reply), not outbound text
- Both auto-clear mood after 10 s

### 6. UX additions
- setEdgeWorkerReady() in ui.js shows " local AI" badge when worker models warm
- showIdentityPrompt() opt-in Nostr identity UI shown 4 s after page load
- Cost preview badge via /api/estimate with free/partial/full display
- Local triage: trivial messages answered without Lightning payment

### 7. Infrastructure
- vite.config.js: worker.format:'es' + optimizeDeps.exclude @xenova/transformers
- package.json: nostr-tools, @xenova/transformers deps added
1. nostr-identity.js: _scanExistingNostrKeys() discovers pre-existing Nostr keys
   in localStorage using common patterns: nsec1/npub1 bech32, raw hex privkey,
   JSON objects with nsec/npub/privkey fields. Scans common client key names
   (nostr_privkey, privkey, nsec, nostr-nsec, nostrKeys, etc.) before showing
   the identity prompt. Keys discovered are re-saved in app format for next load.
2. ui.js: _fetchEstimate() now sends nostr_token as X-Nostr-Token header instead
   of query param, consistent with all other authenticated API calls.
3. edge-worker.js: explicit env.useBrowserCache=true + env.allowLocalModels=false
   so model weights are cached in browser Cache API after first download.
## 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/transformers
replit added 2 commits 2026-03-19 19:03:14 +00:00
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: aef73c94-6788-4125-9acc-9061b4713e96
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
task-28 fix3: complexity contract, consistent token headers, npub-only prompt
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
494393017c
1. 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)
replit added 2 commits 2026-03-19 19:07:11 +00:00
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.
task-28 fix4: trivial cost-preview gate + job polling token
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
cb50e8c658
1. ui.js: _scheduleCostPreview() now gates on _TRIVIAL_RE before scheduling
   the /api/estimate fetch. Greeting-pattern text shows '0 sats' badge locally
   and never makes a network call. Same regex as edge-worker.js _isGreeting().

2. payment.js: startPolling() GET /api/jobs/:id now attaches X-Nostr-Token
   header on every poll cycle via getOrRefreshToken(). Completes consistent
   X-Nostr-Token coverage across all job/session API calls.
replit added 2 commits 2026-03-19 19:11:10 +00:00
All 6 reviewer rounds complete. Final diff addresses:
- complexity:trivial|moderate|complex contract (not binary local|server)
- Trivial heuristic in cost preview: zero network calls for greetings
- X-Nostr-Token on all job/session API calls including polling paths
- npub-only discovery shows identity prompt (_canSign flag)
- Worker/UI contract fully aligned on complexity tiers
task-28 fix5: session triage, speech-bubble local badge, footprint docs
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
dabadb4298
1. ui.js: edge triage now runs BEFORE session handler delegation
   - classify() called for all send() paths (session + WebSocket)
   - trivial + localReply → setSpeechBubble() used for local reply display
   - session handler only receives moderate/complex messages
   - _fetchEstimate() fired for non-trivial in session mode too

2. edge-worker.js: quantization footprint documented (~87MB int8, cached)
Rockachopa closed this pull request 2026-03-23 14:51:38 +00:00
This repo is archived. You cannot comment on pull requests.