Commit Graph

22 Commits

Author SHA1 Message Date
alexpaynex
3a617669f0 Task #27: Apply 3 required fixes for cost-routing + free-tier gate
1. Add `estimateRequestCost(request, model)` to PricingService in pricing.ts
   - Unified method combining estimateInputTokens + estimateOutputTokens + calculateWorkFeeUsd
   - Replaces duplicated token estimation logic at call sites in jobs.ts, sessions.ts, estimate.ts

2. Move partial free-tier `recordGrant()` from invoice creation to post-work in runWorkInBackground
   - Previously called at invoice creation for partial path — economic DoS vulnerability
   - Now deferred to after work completes via new `partialAbsorbSats` param in runWorkInBackground
   - Fully-free jobs still record grant at eval time (no payment involved)

3. Sessions pre-gate: estimate → decide → execute → reconcile (with double-margin bug fix)
   - Free-tier `decide()` now runs on ESTIMATED cost BEFORE `executeWork()` is called
   - Fixed: estimateRequestCost already includes infra+margin via calculateWorkFeeUsd,
     so convert estimatedCostUsd directly to sats — no second calculateActualChargeUsd call
   - absorbedSats capped at actual cost post-execution (Math.min) to prevent over-absorption

4. Atomic pool deduction in recordGrant (free-tier.ts)
   - Replaced non-atomic read-then-write pattern with SQL GREATEST expression inside transaction
   - UPDATE timmyConfig SET value = GREATEST(value::int - absorbSats, 0)::text RETURNING value
   - Audit log (freeTierGrants) receives actual post-deduct value from DB; no oversubscription
   - Removed unused createHash import from free-tier.ts
2026-03-19 16:47:51 +00:00
alexpaynex
512089ca08 Task #27: Apply 3 required fixes for cost-routing + free-tier gate
1. Add `estimateRequestCost(request, model)` to PricingService in pricing.ts
   - Unified method combining estimateInputTokens + estimateOutputTokens + calculateWorkFeeUsd
   - Replaces duplicated token estimation logic at call sites in jobs.ts, sessions.ts, estimate.ts

2. Move partial free-tier `recordGrant()` from invoice creation to post-work in runWorkInBackground
   - Previously called at invoice creation for partial path (before user pays) — economic DoS vulnerability
   - Now deferred to after work completes, using new `partialAbsorbSats` parameter in runWorkInBackground
   - Fully-free jobs still record grant at eval time (no payment involved)

3. Sessions pre-gate refactor: estimate → decide → execute → reconcile
   - Free-tier `decide()` now runs on ESTIMATED cost BEFORE `executeWork()` is called
   - After execution, `absorbedSats` is capped at actual cost (Math.min) to prevent over-absorption
   - Uses new `estimateRequestCost()` for clean single-call estimation
2026-03-19 16:43:41 +00:00
alexpaynex
4c3a0e867a Task #27: Cost-routing + free-tier gate
## What was built

### DB schema
- `timmy_config` table: key/value store for the generosity pool balance
- `free_tier_grants` table: immutable audit log of every Timmy-absorbed request
- `jobs.free_tier` (boolean) + `jobs.absorbed_sats` (integer) columns

### FreeTierService (`lib/free-tier.ts`)
- Per-tier daily sats budgets (new=0, established=50, trusted=200, elite=1000)
  — all env-var overridable
- `decide(pubkey, estimatedSats)` → `{ serve: free|partial|gate, absorbSats, chargeSats }`
  — checks pool balance AND identity daily budget atomically
- `credit(paidSats)` — credits POOL_CREDIT_PCT (default 10%) of every paid
  work invoice back to the generosity pool
- `recordGrant(pubkey, reqHash, absorbSats)` — DB transaction: deducts pool,
  updates identity daily absorption counter, writes audit row
- `poolStatus()` — snapshot for metrics/monitoring

### Route integration
- `POST /api/jobs` (eval → work flow): after eval passes, `freeTierService.decide()`
  intercepts. Free → skip invoice, fire work directly. Partial → discounted invoice.
  Gate (anonymous/new tier/pool empty) → unchanged full-price flow.
- `POST /api/sessions/:id/request`: after compute, free-tier discount applied to
  balance debit. Session balance only reduced by `chargeSats`; absorbed portion
  comes from pool.
- Pool credited on every paid work completion (both jobs and session paths).
- Response fields: `free_tier: true`, `absorbed_sats: N` when applicable.

### GET /api/estimate
- Lightweight pre-flight cost estimator; no payment required
- Returns: estimatedSats, btcPriceUsd, tokenEstimate, identity.free_tier decision
  (if valid nostr_token provided), pool.balanceSats, pool.dailyBudgets

### Tests
- All 29 existing testkit tests pass (0 failures)
- Anonymous/new-tier users hit gate path correctly (verified manually)
- Pool seeds to 10,000 sats on first boot

## Architecture notes
- Free tier decision happens BEFORE invoice creation for jobs (save user the click)
- Partial grant recorded at invoice creation time (reserves pool capacity proactively)
- Free tier for sessions decided AFTER compute (actual cost known, applied to debit)
- Pool crediting is fire-and-forget (non-blocking)
2026-03-19 16:34:05 +00:00
Replit Agent
b0ac398cf2 fix(#26): apply decay before score mutations in recordSuccess/recordFailure
Previously recordSuccess/recordFailure read identity.trustScore (raw stored value)
and incremented/decremented from there. Long-absent identities could instantly
recover their pre-absence tier on the first interaction, defeating decay.

Fix: both methods now call applyDecay(identity) first to get the true current
baseline, then apply the score delta from there before persisting.
2026-03-19 16:11:36 +00:00
Replit Agent
9b778351e4 feat(#26): Nostr identity + trust engine
- New nostr_identities DB table (pubkey, trust_score, tier, interaction_count, sats_absorbed_today, last_seen)
- nullable nostr_pubkey FK on sessions + jobs tables; schema pushed
- TrustService: getTier, getOrCreate, recordSuccess/Failure, HMAC token (issue/verify)
- Soft score decay (lazy, on read) when identity absent > N days
- POST /api/identity/challenge + POST /api/identity/verify (NIP-01 sig verification)
- GET /api/identity/me — look up trust profile by X-Nostr-Token
- POST /api/sessions + POST /api/jobs accept optional nostr_token; bind pubkey to row
- GET /sessions/:id + GET /jobs/:id include trust_tier in response
- recordSuccess/Failure called after session request + job work completes
- X-Nostr-Token added to CORS allowedHeaders + exposedHeaders
- TIMMY_TOKEN_SECRET set as persistent shared env var
2026-03-19 15:59:14 +00:00
alexpaynex
abe9c221c7 feat(task-25): real LNbits mode on Hermes VPS — 29/29 testkit PASS
Task #25: Provision LNbits on Hermes VPS for real Lightning payments.

## Infrastructure (Hermes VPS 143.198.27.163)
- PostgreSQL 16 installed, lnbits DB + user created
- LNbits 0.12.12 installed in /opt/lnbits/.venv (Python 3.11 venv)
- /opt/lnbits/run.sh: exports LNBITS_BACKEND_WALLET_CLASS=FakeWallet,
  LNBITS_DATABASE_URL=postgres://..., starts lnbits on 0.0.0.0:5000
- systemd unit at /etc/systemd/system/lnbits.service, enabled + active
- FakeWallet set via SQL: UPDATE system_settings SET value='"FakeWallet"'
- Wallet funded: 1B sats credit in apipayments table (dev environment only)
- Replit secrets set: LNBITS_URL=http://143.198.27.163:5000, LNBITS_API_KEY=...

## Provisioning runbook
- scripts/hermes-lnbits/provision.sh: idempotent Ubuntu 24.04 setup script
  covering PostgreSQL, venv, run.sh, systemd unit, FakeWallet SQL, health check

## API server code changes (real-mode plumbing)
- lib/lnbits.ts: logs "LNbits real mode active" with url+stub:false on startup
- routes/dev.ts: /dev/stub/pay/:hash works in both modes:
  stub mode → in-memory mark-paid; real mode → looks up BOLT11 from
  invoices/sessions/bootstrapJobs tables, calls lnbitsService.payInvoice()
- routes/sessions.ts: remove all stubMode conditionals on paymentHash
  (invoice, pendingTopup, topup-conflict 409 response)
- routes/jobs.ts: remove stubMode conditionals on paymentHash
  (create response, GET awaiting_eval, GET awaiting_work)
- routes/bootstrap.ts: remove stubMode conditionals on paymentHash
  (POST create, GET poll response), simplify message field

## Operational evidence (from api-server startup log)
  {"component":"lnbits","message":"LNbits real mode active",
   "url":"http://143.198.27.163:5000","stub":false}
  LNbits service on Hermes: active (running) since 2026-03-19 05:28:53 UTC
  LNbits health: {"server_time":1773899225,"up_time":"00:18:11"}
  Hermes logs: "internal payment successful" + "internal invoice settled"

## Testkit: PASS=29 FAIL=0 SKIP=0 (real LNbits mode, 2026-03-19 05:48)
2026-03-19 05:49:46 +00:00
alexpaynex
71dbbd3f37 feat(task-20): Timmy responds to Workshop input bar with AI
## Task
Task #20: Timmy responds to Workshop input bar — make the "Say something
to Timmy…" input bar actually trigger an AI response shown in Timmy's
speech bubble.

## What was built

### Server (artifacts/api-server/src/lib/agent.ts)
- Added `chatReply(userText)` method to AgentService
- Uses claude-haiku (cheaper eval model) with a wizard persona system prompt
- 150-token limit so replies fit in the speech bubble
- Stub mode: returns one of 4 wizard-themed canned replies after 400ms delay
- Real mode: calls Anthropic with wizard persona, truncates to 250 chars

### Server (artifacts/api-server/src/routes/events.ts)
- Imported agentService
- Added per-visitor rate limit system: 3 replies/minute per visitorId (in-memory Map)
- Added broadcastToAll() helper for broadcasting to all WS clients
- Updated visitor_message handler:
  1. Broadcasts visitor message to all watchers as before
  2. Checks rate limit — if exceeded, sends polite "I need a moment…" reply
  3. Fire-and-forget async AI call:
     - Broadcasts agent_state: gamma=working (crystal ball pulses)
     - Calls agentService.chatReply()
     - Broadcasts agent_state: gamma=idle
     - Broadcasts chat: agentId=timmy, text=reply to ALL clients
     - Logs world event "visitor:reply"

### Frontend (the-matrix/js/websocket.js)
- Updated case 'chat' handler to differentiate message sources:
  - agentId === 'timmy': speech bubble + event log entry "Timmy: <text>"
  - agentId === 'visitor': event log only (don't hijack speech bubble)
  - everything else (delta/alpha/beta payment notifications): speech bubble

## What was already working (no change needed)
- Enter key on input bar (ui.js already had keydown listener)
- Input clearing after send (already in ui.js)
- Speech bubble rendering (setSpeechBubble already existed in agents.js)
- WebSocket sendVisitorMessage already exported from websocket.js

## Tests
- 27/27 testkit PASS (no regressions)
- TypeScript: 0 errors
- Vite build: clean (the-matrix rebuilt)
2026-03-19 02:52:49 +00:00
a70898e939 feat(epic222): Workshop — Timmy as wizard presence, world state, WS bootstrap (#31) 2026-03-18 22:15:46 -04:00
83a2ec19e2 fix(testkit): macOS compat + fix test 8c ordering (#24) 2026-03-18 21:01:13 -04:00
alexpaynex
ab2cc06a79 Add session mode for pre-funded request processing
Implement session-based API endpoints for creating, managing, and interacting with pre-funded sessions, including deposit and top-up invoice generation, macaroon authentication, and per-request debiting of compute costs.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2dc3847e-7186-4a22-9c7e-16cd31bca8d9
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 20:00:24 +00:00
alexpaynex
dfc9ecdc7b Add honest accounting and automatic refund mechanism for completed jobs
Implement honest accounting post-job completion, calculating actual costs, adding margin, and enabling automatic refunds for overpayments via a new endpoint.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c6386de2-d5f4-47cc-a557-73416f09e118
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 19:32:34 +00:00
alexpaynex
69eba6190d Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
  fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
  overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
  estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
  async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
  workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
  stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
  actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
  pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
alexpaynex
2245be0eaf Update provisioning URL and streamline SSH key delivery
Fixes the hardcoded 'https://' in the stub provisioner's lnbitsUrl to 'http://' and implements an atomic, first-retrieval SSH private key delivery mechanism.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2f0c982b-02f6-4381-9fc4-34f489842999
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 19:10:30 +00:00
alexpaynex
2cab3ef907 Fix review findings #2: template escaping, ops.sh on node, fee NaN guard
1. Escape ${i} bash loop vars in TypeScript template literal (provisioner.ts)
   - Four occurrences: Bitcoin RPC wait, LND REST wait, macaroon wait, LNbits wait
   - Changed ${i}x5s → \${i}x5s so TypeScript doesn't try to resolve 'i'
   - Confirmed: tsc reports no errors in provisioner.ts after fix

2. Install minimal ops.sh on provisioned node via cloud-init (provisioner.ts)
   - Cloud-init step 15 writes /opt/timmy-node/ops.sh with sync/lnd/lnbits/logs cmds
   - Uses single-quoted heredoc (<<'OPSSH') to prevent bash expanding ops.sh's
     own $CMD / ${1:-help} / ${2:-bitcoin} variables during cloud-init execution
   - chmod +x applied after write
   - sync command: docker exec bitcoin bitcoin-cli getblockchaininfo | jq summary
   - lnd, lnbits, logs subcommands also included

3. Update nextSteps to reference installed ops.sh (bootstrap.ts)
   - "Monitor Bitcoin sync (takes 1-2 weeks to reach 100%): bash /opt/timmy-node/ops.sh sync"
   - All other nextSteps reference files/URLs actually present on the node

4. Harden BOOTSTRAP_FEE_SATS parsing against NaN (pricing.ts)
   - parseInt on empty/invalid env var → NaN
   - Added Number.isFinite(rawFee) && rawFee > 0 guard → falls back to 10_000
   - Same pattern could be applied to other numeric env vars as follow-up

End-to-end verified: POST → pay → provisioning → ready with correct nextSteps
2026-03-18 19:04:03 +00:00
alexpaynex
4162ef0edc Fix Task #5 review findings: race guard, full stack cloud-init, volume, node:crypto SSH
4 changes to address code review rejections:

1. Race condition fix (bootstrap.ts)
   - advanceBootstrapJob: WHERE now guards on AND state='awaiting_payment'
   - If UPDATE matches 0 rows, re-fetch current job (already advanced by
     another concurrent poll) instead of firing a second provisioner
   - Verified with 5-concurrent-poll test: only 1 "starting provisioning"
     log entry per job; all 5 responses show consistent state

2. Complete cloud-init to full Bitcoin + LND + LNbits stack (provisioner.ts)
   - Phase 1: packages, Docker, Tailscale, UFW, block volume mount
   - Phase 2: Bitcoin Core started; polls for RPC availability (max 5 min)
   - Phase 3: LND started; waits for REST API (max 6 min)
   - Phase 4: non-interactive LND wallet init via REST:
     POST /v1/genseed → POST /v1/initwallet with base64 password
     (no lncli, no interactive prompts, no expect)
   - Phase 5: waits for admin.macaroon to appear on mounted volume
   - Phase 6: LNbits started with LndRestWallet backend; mounts LND
     data dir so it reads tls.cert + admin.macaroon automatically
   - Phase 7: saves all credentials (RPC pass, LND wallet pass + seed
     mnemonic, LNbits URL) to chmod 600 /root/node-credentials.txt

3. DO block volume support (provisioner.ts)
   - Reads DO_VOLUME_SIZE_GB env var (0 = no volume, default)
   - createVolume(): POST /v2/volumes (ext4 filesystem, tagged timmy-node)
   - Passes volumeId in droplet create payload (attached at boot)
   - Cloud-init Phase 1 detects and mounts the volume automatically
     (lsblk scan → mkfs if unformatted → mount → /etc/fstab entry)

4. SSH keypair via node:crypto (no ssh-keygen) (provisioner.ts)
   - generateKeyPairSync('rsa', { modulusLength: 4096 })
   - Public key: PKCS#1 DER → OpenSSH wire format via manual DER parser
     (pkcs1DerToSshPublicKey): reads SEQUENCE → n, e INTEGERs → ssh-rsa
     base64 string with proper mpint encoding (leading 0x00 for high bit)
   - Private key: PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----)
   - Both stub and real paths use the same generateSshKeypair() function
   - Removes runtime dependency on host ssh-keygen binary entirely
2026-03-18 18:58:40 +00:00
alexpaynex
a3acb4a0c6 Fix Task #5 review findings: race guard, full stack cloud-init, volume, node:crypto SSH
4 changes to address code review rejections:

1. Race condition fix (bootstrap.ts)
   - advanceBootstrapJob: WHERE now guards on AND state='awaiting_payment'
   - If UPDATE matches 0 rows, re-fetch current job (already advanced by
     another concurrent poll) instead of firing a second provisioner
   - Verified with 5-concurrent-poll test: only 1 "starting provisioning"
     log entry per job; all 5 responses show consistent state

2. Complete cloud-init to full Bitcoin + LND + LNbits stack (provisioner.ts)
   - Phase 1: packages, Docker, Tailscale, UFW, block volume mount
   - Phase 2: Bitcoin Core started; polls for RPC availability (max 5 min)
   - Phase 3: LND started; waits for REST API (max 6 min)
   - Phase 4: non-interactive LND wallet init via REST:
     POST /v1/genseed → POST /v1/initwallet with base64 password
     (no lncli, no interactive prompts, no expect)
   - Phase 5: waits for admin.macaroon to appear on mounted volume
   - Phase 6: LNbits started with LndRestWallet backend; mounts LND
     data dir so it reads tls.cert + admin.macaroon automatically
   - Phase 7: saves all credentials (RPC pass, LND wallet pass + seed
     mnemonic, LNbits URL) to chmod 600 /root/node-credentials.txt

3. DO block volume support (provisioner.ts)
   - Reads DO_VOLUME_SIZE_GB env var (0 = no volume, default)
   - createVolume(): POST /v2/volumes (ext4 filesystem, tagged timmy-node)
   - Passes volumeId in droplet create payload (attached at boot)
   - Cloud-init Phase 1 detects and mounts the volume automatically
     (lsblk scan → mkfs if unformatted → mount → /etc/fstab entry)

4. SSH keypair via node:crypto (no ssh-keygen) (provisioner.ts)
   - generateKeyPairSync('rsa', { modulusLength: 4096 })
   - Public key: PKCS#1 DER → OpenSSH wire format via manual DER parser
     (pkcs1DerToSshPublicKey): reads SEQUENCE → n, e INTEGERs → ssh-rsa
     base64 string with proper mpint encoding (leading 0x00 for high bit)
   - Private key: PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----)
   - Both stub and real paths use the same generateSshKeypair() function
   - Removes runtime dependency on host ssh-keygen binary entirely
2026-03-18 18:55:40 +00:00
alexpaynex
f43e782c50 Task #5: Lightning-gated node bootstrap (proof-of-concept)
Pay a Lightning invoice → Timmy auto-provisions a Bitcoin full node on DO.

New: lib/db/src/schema/bootstrap-jobs.ts
- bootstrap_jobs table: id, state, amountSats, paymentHash, paymentRequest,
  dropletId, nodeIp, tailscaleHostname, lnbitsUrl, sshPrivateKey,
  sshKeyDelivered (bool), errorMessage, createdAt, updatedAt
- States: awaiting_payment | provisioning | ready | failed
- Payment data stored inline (no FK to jobs/invoices tables — separate entity)
- db:push applied to create table in Postgres

New: artifacts/api-server/src/lib/provisioner.ts
- ProvisionerService: stubs when DO_API_TOKEN absent, real otherwise
- Stub mode: generates a real RSA 4096-bit SSH keypair via ssh-keygen,
  returns RFC 5737 test IP + fake Tailscale hostname after 2s delay
- Real mode: upload SSH public key to DO → generate Tailscale auth key →
  create DO droplet with cloud-init user_data → poll for public IP (2 min)
- buildCloudInitScript(): non-interactive bash that installs Docker + Tailscale
  + UFW + Bitcoin Knots via docker-compose; joins Tailscale if authkey provided
- provision() designed as fire-and-forget (void); updates DB to ready/failed

New: artifacts/api-server/src/routes/bootstrap.ts
- POST /api/bootstrap: create job + LNbits invoice, return paymentRequest
- GET /api/bootstrap/🆔 poll-driven state machine
  * awaiting_payment: checks payment, fires provisioner on confirm
  * provisioning: returns progress message
  * ready: delivers credentials; SSH private key delivered once then cleared
  * failed: returns error message
- Stub mode message includes the exact /dev/stub/pay URL for easy testing
- nextSteps array guides user through post-provision setup

Updated: artifacts/api-server/src/lib/pricing.ts
- Added bootstrapFee field reading BOOTSTRAP_FEE_SATS env var (default 10000)
- calculateBootstrapFeeSats() method

Updated: artifacts/api-server/src/routes/index.ts
- Mounts bootstrapRouter

Updated: replit.md
- Documents all 7 new env vars (DO_API_TOKEN, DO_REGION, DO_SIZE, etc.)
- Full curl-based flow example with annotated response shape

End-to-end verified in stub mode: POST → pay → provisioning → ready (SSH key)
→ second GET clears key and shows sshKeyNote
2026-03-18 18:47:48 +00:00
alexpaynex
44f7e24b45 Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully

LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard

AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
  uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration

PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM

Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes

replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
alexpaynex
fbc9bbc046 Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully

LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard

AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
  uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)

PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic

Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed

replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
alexpaynex
e163a5d0fe Task #2: MVP Foundation — DB schema, LNbits stub, Anthropic agent
- Added jobs and invoices Drizzle schemas (lib/db/src/schema/)
- Updated DB schema barrel to export all four tables (jobs, invoices, conversations, messages)
- Applied schema to PostgreSQL via drizzle-kit push
- Set up Anthropic AI integration (claude-haiku-4-5 for eval, claude-sonnet-4-6 for work)
- Copied integrations-anthropic-ai template into lib/
- Added @workspace/integrations-anthropic-ai dep to api-server and tsconfig references
- Created pricing.ts: eval fee = 10 sats fixed, work fee = 50/100/250 sats by request length
- Created agent.ts: evaluateRequest (Haiku, JSON structured output) + executeRequest (Sonnet)
- Created lnbits.ts: stubbed payment layer (in-memory Set, markInvoicePaid for testing)
  - Real LNbits swap-in requires only LNBITS_URL + LNBITS_API_KEY env vars
2026-03-18 15:09:48 +00:00
alexpaynex
b095efcfd3 Add AI agent capabilities and integrate with Anthropic and LNbits
Integrate Anthropic AI for agent capabilities, introduce database schemas for jobs and invoices, and set up LNbits for payment processing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cce28acc-aeac-46ff-80ec-af4ade39e30f
Replit-Helium-Checkpoint-Created: true
2026-03-18 14:59:02 +00:00
agent
c8ed262197 Initial commit 2026-03-13 23:21:55 +00:00