Task #17: Mode 2 Pre-funded Session API — all 20 tests passing #21

Closed
replit wants to merge 0 commits from task-17-mode2-session-api into main
Owner

Summary

Sessions API (routes/sessions.ts) was already fully implemented. This PR fixes the testkit so all 20 tests pass cleanly.

Changes

  • artifacts/api-server/src/routes/testkit.ts: Replace fixed sleep in tests 5 and 10 with polling loops (max 30s) so they handle real Anthropic eval latency without flaking.

Test results

PASS=20  FAIL=0  SKIP=0

All Mode 1 (tests 1–10) and Mode 2 session tests (tests 11–16) pass.

## Summary Sessions API (`routes/sessions.ts`) was already fully implemented. This PR fixes the testkit so all 20 tests pass cleanly. ## Changes - `artifacts/api-server/src/routes/testkit.ts`: Replace fixed `sleep` in tests 5 and 10 with polling loops (max 30s) so they handle real Anthropic eval latency without flaking. ## Test results ``` PASS=20 FAIL=0 SKIP=0 ``` All Mode 1 (tests 1–10) and Mode 2 session tests (tests 11–16) pass.
replit added 26 commits 2026-03-19 00:43:15 +00:00
Introduce streaming for AI job execution, implement rate limiting for API endpoints, enhance CORS configuration, and refactor event handling.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2967540c-7b01-4168-87be-fde774e32494
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
Fix #14 — AgentService stub mode for executeWorkStreaming:
- Remove static import of @workspace/integrations-anthropic-ai (throws on
  module load when env vars absent)
- Add STUB_MODE detection at startup using AI_INTEGRATIONS_ANTHROPIC_API_KEY
  and AI_INTEGRATIONS_ANTHROPIC_BASE_URL presence
- Lazy getClient() uses dynamic import, only called in live mode
- stub executeWork() and executeWorkStreaming() return canned responses;
  streaming stub emits word-by-word with 40 ms delay to exercise SSE path
- Define local AnthropicLike interface — no dependency on @anthropic-ai/sdk
  types or unbuilt integrations-anthropic-ai dist

Fix #16 — SSE stream registry race condition:
- After bus-listener wait, refresh BOTH stream registry AND job state from DB
- If job completed while waiting (stream already gone), replay full result
  immediately instead of emitting 'Stream not available' error
- Increase wait timeout from 60 s to 90 s for mainnet payment latency
- Cleaner event bus filter: any jobId event resolves the wait (not just
  job:state and job:paid) so job:completed also unblocks waiting clients
- Named branches with comments for each resolution path

Fix pre-existing typecheck errors (bonus):
- event-bus.ts: EventEmitter overload uses any[] not unknown[]
- testkit.ts: escape bash ${ELAPSED_T14} in TS template literal
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 6604e31a-435b-4a62-b765-45023a7e053e
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
Implements task #8 — Gitea push helper script (bore tunnel).

## What was built
- `scripts/push-to-gitea.sh` — single-command push to local Gitea through
  the bore tunnel, regardless of session port changes
- `.bore-port` added to `.gitignore` — session-scoped file never committed
- `replit.md` updated with a "Pushing to Gitea" section documenting the
  full workflow

## Port detection (3-tier fallback, no pgrep needed)
The task spec mentioned `pgrep -a bore`, but bore runs on the Mac — not on
Replit — so pgrep always returns nothing from Replit's shell. Implemented
a practical 3-tier resolution instead:
  1. CLI argument `bash scripts/push-to-gitea.sh <PORT>` — saves to .bore-port
  2. `.bore-port` file in repo root (set once per session)
  3. Port embedded in the existing `git remote get-url gitea` URL (zero
     setup — works across sessions as long as the remote hasn't been wiped)

## Repo name detection
`basename "$REPO_ROOT"` returns `workspace` (Replit container name) rather
than `token-gated-economy`. Fixed to parse repo name from the existing gitea
remote URL via regex `/([^/]+)\.git$`.

## Tested live
- Zero-arg run with valid port in remote → pushed successfully to
  `feat/workshop-api-enhancements` on Gitea
- Bad port (99999) → prints clear error with actionable instructions, exits 1
- GITEA_TOKEN env var supported as override for credential rotation

## Files changed
- `scripts/push-to-gitea.sh` (new, chmod +x)
- `.gitignore` (+.bore-port entry)
- `replit.md` (+Pushing to Gitea section)
Implements task #8 — Gitea push helper script (bore tunnel).

## What was built
- `scripts/push-to-gitea.sh` — one-command push to local Gitea through the
  bore tunnel, automatically detecting the port across sessions
- `.gitea-credentials` and `.bore-port` added to `.gitignore`
- `replit.md` — new "Pushing to Gitea" section with full workflow docs

## Port detection (3-tier, no pgrep)
bore runs on the Mac, not Replit — pgrep would always return empty.
Implemented practical 3-tier resolution:
  1. CLI argument `bash scripts/push-to-gitea.sh <PORT>` — saves to .bore-port
  2. `.bore-port` file in repo root (written on first call, reused after)
  3. Port parsed from existing `gitea` remote URL — zero setup for ongoing
     sessions where the remote was already set

## Security
Token is never hard-coded. Resolution order:
  1. `GITEA_TOKEN` env var
  2. `.gitea-credentials` gitignored file (one line: the token)
  3. Fails with clear setup instructions if neither is present

## Repo name detection
`basename` of the Replit workspace returns `workspace`, not the Gitea repo
name. Fixed to regex-extract the repo name from the existing gitea remote URL.

## Tested live
- Zero-arg run with valid port in remote + GITEA_TOKEN env var → pushed
  successfully, printed branch URL and PR link
- Bad port (99999) → clear error, exits 1
- No GITEA_TOKEN → clear error with setup instructions, exits 1

## Files changed
- `scripts/push-to-gitea.sh` (new, chmod +x)
- `.gitignore` (+.bore-port, +.gitea-credentials)
- `replit.md` (+Pushing to Gitea section)
Update `replit.md` to explain that the new port must be passed once after a bore restart.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0b42068f-ff9e-4779-bc54-32680791d7ed
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
Implements task #9 — TypeScript quality gates.

## What was built
- `eslint.config.ts` — flat config with typescript-eslint, ignores generated dirs
- `package.json` — added `pnpm run lint` script using ESLint 10
- `.githooks/pre-commit` + `.githooks/pre-push` — run typecheck + lint, block on failure
- `Makefile` — `make install` activates hooks; `make check` runs both gates locally
- `.gitea/workflows/ci.yml` — Gitea Actions CI on PR to main (node:22-alpine)
- `AGENTS.md` — documents hooks, push workflow, branch conventions, stub modes

## Bug fixes required (pre-existing failures blocked the gate)
1. `lib/integrations-anthropic-ai/src/batch/utils.ts` — `pRetry.AbortError` does not
   exist on the default import in p-retry@7.x. Fixed: named import `{ AbortError }`.
2. `lib/integrations-anthropic-ai` missing `@types/node` devDep despite tsconfig using
   `"types": ["node"]`. Added `"@types/node": "catalog:"` to its package.json.
3. `lib/api-client-react` removed from root `tsconfig.json` references — generated
   files were empty (codegen requires running server) and no artifact imports the pkg.
4. `artifacts/api-server/src/lib/agent.ts` — `@ts-ignore` → `@ts-expect-error` (ESLint rule).
5. `artifacts/api-server/src/routes/sessions.ts` — `let` → `const` for reassignment-free var.
6. Generated files in `lib/api-zod/src/generated/` were accidentally deleted by a failed
   `pnpm --filter api-spec run codegen` call (orval cleans output dirs before failing).
   Restored from git HEAD (`git show HEAD:...`).

## Verified
- `pnpm run typecheck` exits 0
- `pnpm run lint` exits 0
- `make install` activates hooks and makes them executable
- `make check` runs both gates cleanly
Tracks Gitea issue token-gated-economy #19.

## Problem
LNbits 0.12 (released late 2024) removed the superuser wallet creation
API (POST /api/v1/wallet with X-Api-Key: <superuser-id>). The existing
script would silently fall through to generic manual instructions with
no explanation, leaving operators confused.

## Changes to scripts/bitcoin-ln-node/get-lnbits-key.sh
1. **Version detection** — calls GET /api/v1/health and parses
   `server_version` from the JSON response via python3. Version is
   printed at the start. If the endpoint is unreachable the script dies
   with a clear message. If the version field is absent (unexpected
   future change) it defaults to "0.12.0" (modern path) with a warning.

2. **Version-branched flow**
   - >= 0.12: skips superuser API entirely; prints a numbered 5-step
     walk-through to the Admin UI at /admin, then always prints the
     export template.
   - < 0.12: runs the existing superuser detection (env file → secrets
     file → lnbits.log grep) and wallet creation API. Falls back to
     manual instructions on failure, same as before.

3. **SQLite fallback removed** — the `sqlite3 "$DB_FILE" "SELECT id FROM
   accounts WHERE is_super_user=1"` block targeted the wrong schema on
   0.12+ and is redundant given the version branch. Deleted entirely.

4. **Export template always printed** — `print_export_template()` helper
   is called in every exit path (both version branches, all fallbacks).
   Template always shows:
     export LNBITS_URL="http://bore.pub:<PORT>"
     export LNBITS_API_KEY="..."

## Verified
- bash -n: syntax OK
- version_gte() passes all edge cases (0.12.0 == 0.12.0, 0.12.3 > 0.12.0,
  1.0.0 > 0.12.0, 0.11.9 < 0.12.0, 0.9.0 < 0.12.0)
- >= 0.12 output path confirmed via bash simulation
- sqlite3/SQLite strings absent from final file
- Script exits 0 in all manual-instruction paths (not an error)

## No changes to setup.sh (out of scope per task spec)
Tracks Gitea issue token-gated-economy #19.

## Problem
LNbits 0.12 (released late 2024) removed the superuser wallet creation
API (POST /api/v1/wallet with X-Api-Key: <superuser-id>). The existing
script would silently fall through to generic manual instructions with no
explanation, leaving operators confused.

## Changes to scripts/bitcoin-ln-node/get-lnbits-key.sh

1. **Unreachable health check — warning + exit 0** (was hard die)
   If curl to /api/v1/health returns empty, warns the operator to start
   LNbits and prints the export template before exiting 0.

2. **Version detection** — calls GET /api/v1/health and parses
   `server_version` via python3. Printed at start. Falls back to
   "0.12.0" (safe modern default) with a warning if unparseable.

3. **Version-branched flow**
   - >= 0.12: skips superuser API; prints 5-step Admin UI walk-through
     (/admin → Users → Create User → wallet → API Info → Admin key).
   - < 0.12: existing superuser detection (env file → secrets file →
     lnbits.log grep) + wallet creation API, unchanged.

4. **SQLite fallback removed** — the sqlite3 "SELECT id FROM accounts
   WHERE is_super_user=1" block targeted wrong schema on 0.12+. Deleted.

5. **Export template always printed** via print_export_template() helper
   called in every exit path: unreachable, >= 0.12, < 0.12 success, and
   all < 0.12 fallbacks.

## Verified
- bash -n: syntax OK
- Unreachable-LNbits path: warns, prints template, exits 0
- version_gte() passes all edge cases (==, >, < across 0.12 boundary)
- >= 0.12 output simulated correctly
- sqlite3/SQLite strings absent from final file
- Script exits 0 in all manual-instruction paths

## No changes to setup.sh (out of scope per task spec)
Tracks Gitea issue token-gated-economy #19.

## Problem
LNbits 0.12 removed the superuser wallet creation API. The old script
silently fell through to generic manual instructions with no explanation.
version_gte() using `sort -V` was also not supported on macOS (BSD sort).

## Changes to scripts/bitcoin-ln-node/get-lnbits-key.sh

1. **Unreachable health check — warning + exit 0** (was hard die)
   curl uses `|| true` — if response is empty, warns the operator to
   start LNbits, prints the export template, and exits 0.

2. **macOS-safe version_gte()** — replaced `sort -V -C` (GNU-only) with:
   - Primary: python3 inline script (parses major.minor.patch as int lists)
   - Fallback: pure-bash numeric comparison (no external tools required)
   All 6 test cases pass: 0.12.0==, 0.12.3>, 1.0.0>, 0.11.9<, 0.9.0<,
   0.12.0>0.11.9.

3. **Version detection** — calls GET /api/v1/health, parses server_version
   via python3, prints it. Falls back to "0.12.0" (safe modern default)
   with a warning if unparseable.

4. **Version-branched flow**
   - >= 0.12: skips superuser API; prints 5-step Admin UI walk-through
     (/admin → Users → Create User → wallet → API Info → Admin key).
   - < 0.12: existing superuser detection (env file → secrets file →
     lnbits.log grep) + wallet creation API, unchanged.

5. **SQLite fallback removed** — the sqlite3 "SELECT id FROM accounts
   WHERE is_super_user=1" block targeted wrong schema on 0.12+. Deleted.

6. **Export template always printed** via print_export_template() helper
   called in every exit path: unreachable, >=0.12, <0.12 success, fallbacks.

## Changes to scripts/bitcoin-ln-node/setup.sh
Added LNbits version compatibility note to the "Done" summary so operators
know upfront that >=0.12 requires the Admin UI path.

## Verified
- bash -n syntax OK on both files
- version_gte(): 6/6 test cases correct with python3 comparator
- Unreachable-LNbits path: warns, prints template, exits 0 (simulated)
- No sqlite3/sort -V references remain in get-lnbits-key.sh
Tracks Gitea issue perplexity/the-matrix #1.

## Context
The-matrix files did not exist in this workspace — the repo lives only on
the user's Mac-hosted Gitea behind a bore tunnel. The bore session was
expired (bore port 61049 unreachable). Created the-matrix as a standalone
Vite artifact in this Replit workspace from scratch.

## What was built: the-matrix/

### Build infrastructure
- `package.json` — three@0.171.0 (exact), vite@^5.4.0 devDep
  scripts: dev, build, preview
- `vite.config.js` — minimal config (root='.', outDir='dist', host:true)
- `.gitignore` — node_modules/ and dist/ entries
- `index.html` — single entry point <script type="module" src="./js/main.js">
  No importmap, no CDN link tags, no esm.sh references

### JS source files (all use bare specifiers, zero CDN URLs)
- `js/main.js` — orchestrates init + rAF loop, tracks FPS
- `js/world.js` — Three.js scene, camera, renderer, grid floor, fog, lights
- `js/agents.js` — 5 named agents (ALPHA/BETA/GAMMA/DELTA/EPSILON) rendered
  as icosahedra with glow rings, canvas-texture labels, point lights,
  pulse animation; connection lines between nearby agents
- `js/effects.js` — matrix rain particle system (2000 particles) + starfield
- `js/ui.js` — HUD overlay (FPS, agent count, job count, agent state list,
  chat panel) wired to DOM
- `js/interaction.js` — OrbitControls from three/addons/ with damping
- `js/websocket.js` — WS reconnect loop, handles agent_state/job_started/
  job_completed/chat messages; reads VITE_WS_URL env var

### Verified
- `npm run build` exits 0, produces dist/index.html + dist/assets/
- 13 Three.js modules transformed (including OrbitControls from three/addons/)
- Zero esm.sh/CDN/importmap references in any source file
- Three.js version locked at exactly 0.171.0 in package.json
- node_modules/ and dist/ in .gitignore
- Not added to pnpm-workspace.yaml (standalone app, managed by npm)
Add a central place to define agent properties and their positions
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 1s
4d7e6ad54f
Creates a new `agent-defs.js` file containing all agent properties and positions, establishing a single source of truth.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b40584db-edbf-4dd7-98aa-136408264a9a
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
Tracks Gitea issues perplexity/the-matrix #2 and #6.

## Changes to the-matrix/

### New: js/agent-defs.js
Single canonical source of truth for all five agents. Each entry carries:
  id, label, color (0xRRGGBB int), role, x, z (world-space position)
Also exports colorToCss(intColor) helper used by agents.js and ui.js.
Adding a new agent now requires editing only this one file.

### Updated: js/agents.js
- Removed local AGENT_DEFS array and separate GRID_POSITIONS array
- Imports AGENT_DEFS + colorToCss from ./agent-defs.js
- Agent constructor now takes a single def and builds THREE.Vector3 from
  def.x / def.z — no separate positional array needed
- colorToCss() replaces inline .toString(16) for canvas label rendering
- No rendering or animation logic changed

### Updated: js/websocket.js
- Imports AGENT_DEFS + colorToCss directly from ./agent-defs.js
  (previously relied on getAgentDefs() from agents.js for chat colors)
- Builds agentById lookup map at module init time for O(1) message dispatch
- crypto.randomUUID() used for subscribe message clientId — no Math.random()
  for ID generation anywhere in the codebase
- Removed unused getAgentDefs import from agents.js

### New: README.md
- Quick start (npm install / dev / build / preview)
- VITE_WS_URL config via .env.local
- "Adding Custom Agents" section pointing to js/agent-defs.js as the
  single file to edit, with a copy-paste example
- Architecture table and WebSocket protocol reference

## Also: Gitea push
- the-matrix forked to replit/the-matrix, made public
- PR #18 opened: replit:feat/vite-build-agent-defs → perplexity/the-matrix:main
  Covers issues #1 (Vite), #2 (agent-defs), #6 (crypto.randomUUID)

## Verified
- npm run build exits 0, 14 modules (13 + agent-defs.js)
- No duplicate agent color or position definitions remain
- No Math.random() used for ID generation
Tracks Gitea issues perplexity/the-matrix #2 and #6.

## New: js/agent-defs.js
Single canonical source of truth for the four agents (alpha, beta, gamma,
delta). Each entry carries: id, label, color (0xRRGGBB int), role,
direction (cardinal: north/east/south/west), x, z (world position).
Also exports colorToCss(intColor) helper to derive CSS hex strings.
Adding a new agent requires editing only this one file.

## Updated: js/agents.js
- Removed local AGENT_DEFS array and separate GRID_POSITIONS array
- Imports AGENT_DEFS + colorToCss from ./agent-defs.js
- Agent constructor takes a single def; THREE.Vector3 built from def.x/def.z
- colorToCss() used for canvas label rendering (no inline .toString(16))
- No rendering or animation logic changed; four agents render identically

## Updated: js/websocket.js
- Imports AGENT_DEFS + colorToCss directly from ./agent-defs.js
- agentById lookup map built from AGENT_DEFS at module init (O(1) dispatch)
- crypto.randomUUID() used for subscribe message clientId (issue #6)
- colorToCss(def.color) passed to appendChatMessage — no integer forwarding
- colorToCss(0x003300) used for system event log messages
- No Math.random() used for ID generation anywhere in codebase

## Updated: js/ui.js
- Imports colorToCss from ./agent-defs.js; used in renderAgentList
- appendChatMessage signature clarified: accepts CSS string, not integer
- No raw .toString(16) conversion anywhere in ui.js or websocket.js

## New: README.md
- Quick start, VITE_WS_URL config, architecture table, WS protocol reference
- "Adding Custom Agents" section pointing to js/agent-defs.js only

## Verified
- npm run build exits 0, 14 modules (agent-defs.js correctly in graph)
- 4 agents only: alpha, beta, gamma, delta (epsilon removed)
- All four: id, label, color, role, direction, x, z fields present
- No integer-to-CSS conversion outside colorToCss()
- No Math.random() for ID generation
Add a 'direction' field to agent definitions in the README and update color representation.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a9998b88-ca87-4d07-bf76-cd282f8a063b
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
Tracks Gitea issue perplexity/the-matrix #4.

## Storage helpers (ui.js)
- loadChatHistory(agentId) — reads matrix:chat:<agentId> from localStorage,
  returns [] on missing/corrupt data
- saveChatHistory(agentId, messages) — writes capped array (slice -100) to
  localStorage; silently ignores QuotaExceededError
- In-memory chatHistory map per agentId populated from both helpers
- Storage keys: matrix:chat:alpha, matrix:chat:beta, matrix:chat:gamma,
  matrix:chat:delta, matrix:chat:sys

## Timestamps
- appendChatMessage now stamps each message with Date.now()
- formatTimestamp(ts) formats as HH:MM for display
- Rendered as <span class="chat-ts">[HH:MM]</span> before the agent name
- Timestamp stored in persisted message object for sort-on-restore

## Restore on load
- loadAllHistories() called from initUI() combines all five stores (4 agents
  + sys), sorts by timestamp, renders last MAX_CHAT_ENTRIES (12) into panel
- No flash: history is populated synchronously before first rAF tick

## Persist on receive (websocket.js)
- appendChatMessage for chat messages: passes def.id as agentId (4th param)
- logEvent (SYS messages): passes 'sys' as agentId
- No changes to WS connect/message/reconnect logic

## Clear button (index.html + ui.js)
- #chat-clear-btn: fixed position, outside #ui-overlay, pointer-events:all
  so it remains clickable while the overlay is pointer-events:none
- Positioned bottom-right, left of OFFLINE/CONNECTED indicator
- On click: removes all five localStorage keys, clears DOM, resets chatEntries
- Hover brightens from dim green to full #00ff41 glow for discoverability

## Deviation from spec
- Task assumed per-agent panels with selectAgent(). Current UI has one shared
  global panel. Per-agent storage is implemented exactly as specified; restore
  on load shows combined last-12 across all agents (best fit for single panel).
  Clear wipes all stored histories (no per-agent panel to scope it to).

## Verified
- npm run build exits 0, 14 modules, no new warnings
Update chat history logic to cap in-memory storage and validate loaded data.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cdd99886-b240-46aa-ba25-e18c10732858
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
Tracks Gitea issue perplexity/the-matrix #3.

## main.js — lifecycle management
- Extracted Three.js setup into buildWorld(firstInit) returning {scene,renderer,ac}
- running flag gates the rAF loop; animate() returns immediately when false
- AbortController (ac) cleans up the resize event listener on teardown
- teardown({scene,renderer,ac}) calls all dispose functions in correct order
- webglcontextlost: event.preventDefault(), sets running=false, shows overlay
- webglcontextrestored: teardown → buildWorld(false) → hide overlay
- buildWorld(false) skips initUI() and initWebSocket() — DOM/WS survive context loss
- canvas element saved on first init; passed back to initWorld() on re-init so
  Three.js reuses the same DOM node (no duplicate canvas element in body)

## world.js — canvas reuse + scene dispose
- initWorld(existingCanvas?) — passes existing canvas to WebGLRenderer ctor;
  skips document.body.prepend on re-init
- disposeWorld(renderer, scene) — traverses scene graph and disposes all
  geometries, materials, textures; then calls renderer.dispose()

## agents.js — per-agent dispose
- Agent.dispose() — disposes core/ring/glow geo+mat, sprite map+mat
- disposeAgents() — calls agent.dispose() for all agents, disposes connection
  line geos/mats, clears agents Map and connectionLines array, nulls scene ref

## effects.js — particle dispose
- disposeEffects() — disposes rainParticles geo+mat, nulls all module refs
  (rainPositions, rainVelocities); starfield disposed via scene traverse in world

## interaction.js — OrbitControls dispose
- disposeInteraction() — calls controls.dispose(), nulls controls ref

## ui.js — double-init guard
- uiInitialized flag prevents loadAllHistories() and clearBtn listener from
  being registered twice if buildWorld is ever called with firstInit=true again

## index.html — recovery overlay
- #webgl-recovery-overlay: position:fixed inset:0 z-index:200, display:none by default
- Shows "GPU context lost — recovering..." in Matrix green with blink animation
- pointer-events:none so it does not block interaction during recovery

## Deviation from spec
- Task spec referenced a MatrixWorld class with _init() and dispose() methods.
  No such class exists; app is fully procedural. Implemented equivalent lifecycle
  as module functions: disposeAgents/Effects/Interaction/World and buildWorld().

## Verified
- npm run build exits 0, 14 modules, no new warnings
Tracks Gitea issue perplexity/the-matrix #3.

## main.js — lifecycle management
- buildWorld(firstInit, stateSnapshot): creates/rebuilds Three.js world
- canvas saved on first call, passed to initWorld() on re-init (same DOM node)
- firstInit=false skips initUI() and initWebSocket() — DOM + WS survive loss
- stateSnapshot applied immediately after initAgents() on restore
- AbortController removes resize listener on every teardown
- webglcontextlost: event.preventDefault(), running=false, overlay shown
- webglcontextrestored: snapshot=getAgentStates(), teardown, buildWorld(false,snap)

## agents.js — state snapshot + dispose
- getAgentStates() returns {agentId:state} map before teardown
- applyAgentStates(snapshot) reapplies state to fresh agents after initAgents()
- Agent.dispose() releases core/ring/glow geo+mat + sprite map+mat
- disposeAgents() iterates agents + connectionLines, clears map, nulls scene

## effects.js — particle dispose
- disposeEffects() disposes rainParticles geo+mat, nulls module refs

## world.js — canvas reuse + scene dispose
- initWorld(existingCanvas?) passes canvas to WebGLRenderer; skips prepend
- disposeWorld(renderer, scene) traverses scene, disposes all geo/mat/maps

## interaction.js — named handler + dispose
- contextmenu handler stored as named const _noCtxMenu (not inline arrow)
- initInteraction saves canvas ref as _canvas
- disposeInteraction: removeEventListener(_noCtxMenu) then controls.dispose()
  — fixes accumulation of contextmenu handlers across recovery cycles

## ui.js — double-init guard
- uiInitialized flag prevents loadAllHistories/clearBtn wiring on second call

## index.html — recovery overlay
- #webgl-recovery-overlay: fixed inset:0 z-index:200, display:none default
- "GPU context lost — recovering..." in Matrix green with ctx-blink animation
- pointer-events:none so it never blocks interaction

## Deviation from spec
- Task spec referenced a MatrixWorld class. App is fully procedural.
  Equivalent lifecycle implemented as buildWorld()/teardown() + dispose* fns.

## Verified
- npm run build exits 0, 14 modules, no new warnings
Refactor `disposeWorld` in `world.js` to explicitly dispose of world-owned objects (grid, plane, lights) stored in `_worldObjects` array, avoiding scene traversal and potential double-dispose issues.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: d0f1c2ec-8644-4ccc-9a17-c93c7742eb5e
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
Tracks Gitea issue perplexity/the-matrix #5.

## Icons (public/icons/)
- Generated 192×192 and 512×512 PNG icons with Matrix aesthetic using raw Node.js
  zlib deflate (no external image deps): near-black (#0a0a0a) background, dim green
  grid, outer ring, 6 spokes, bright central glow (#00ff88 icosahedron motif)
- Placed in public/icons/ so Vite copies them to dist/icons/ automatically
- Verified byte counts: 192px=14492B, 512px=67775B

## manifest.json (public/manifest.json)
- name: "The Matrix", short_name: "The Matrix"
- display: standalone, orientation: landscape
- background_color: #000000, theme_color: #00ff41
- Icons: 192px and 512px, purpose: "any maskable"
- Placed in public/ → served at /manifest.json in dev and prod

## sw.js (template at repo root)
- on install: caches PRECACHE_URLS (replaced at build time) + self.skipWaiting()
- on activate: deletes old CACHE_NAME entries + self.clients.claim()
- on fetch: cache-first GET, network fallback; caches successful network responses
  into CACHE_NAME for future offline use

## vite.config.js — generate-sw plugin
- build.manifest: true → emits dist/.vite/manifest.json with hashed filenames
- generate-sw plugin (apply:'build', closeBundle hook):
  - Reads dist/.vite/manifest.json, extracts /assets/*.js paths
  - Combines with static assets: /, /manifest.json, /icons/*.png
  - Replaces __PRECACHE_URLS__ placeholder in sw.js template
  - Writes dist/sw.js with injected list
  - Build outputs: 5 precache URLs (/, /manifest.json, 2 icons, hashed JS bundle)

## index.html
- <title> changed to "The Matrix" to match manifest name
- <link rel="manifest"> pointing to /manifest.json
- <meta name="theme-color" content="#00ff41">
- Apple PWA tags: apple-mobile-web-app-capable, status-bar-style:black-translucent,
  apple-mobile-web-app-title, apple-touch-icon (icon-192.png)

## js/main.js — SW registration
- navigator.serviceWorker.register('/sw.js') on window load event
- Guarded by 'serviceWorker' in navigator (Safari <11.1 safe)
- .catch(() => {}) silences dev-mode 404 errors

## Verified
- npm run build exits 0, 14 modules + dist/sw.js + dist/icons/* + dist/manifest.json
Conditionally register the service worker in `main.js` based on `import.meta.env.PROD` and update comments in `sw.js` to reflect this behavior.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: eecb93ce-3324-46fc-b68f-1653c6173946
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
## New files
- `artifacts/api-server/src/lib/logger.ts`
  Thin JSON logger (makeLogger(component) → {debug,info,warn,error}).
  Emits {timestamp,level,component,message,...ctx} lines to stdout/stderr.

- `artifacts/api-server/src/lib/histogram.ts`
  In-memory LatencyHistogram; bounded at 1000 samples/route.
  percentile(route, pct) + snapshot() → {p50,p95,count} per route key.
  Singleton `latencyHistogram` exported.

- `artifacts/api-server/src/lib/metrics.ts`
  MetricsService.snapshot(): async DB queries via Drizzle SQL aggregates:
  - jobs count + group by state (7 states)
  - invoices total + paid count → conversion_rate
  - sum(actual_amount_sats) → total_sats earned
  - process uptime_s (measured from module load)
  - latencyHistogram.snapshot() for per-route p50/p95
  Singleton `metricsService` exported.

- `artifacts/api-server/src/middlewares/response-time.ts`
  responseTimeMiddleware: records durationMs on res.on('finish'),
  emits structured {method,path,route,status,duration_ms,ip} log line,
  calls latencyHistogram.record(routeKey, durationMs).

- `artifacts/api-server/src/routes/metrics.ts`
  GET /api/metrics → metricsService.snapshot() as JSON. 503 on error.

## Modified files
- `src/routes/health.ts`: GET /api/healthz now returns
  {status:"ok", uptime_s:<n>, jobs_total:<n>} (non-breaking; drops HealthCheckResponse.parse).
  503 on DB error with structured error log.

- `src/routes/index.ts`: metricsRouter mounted before jobsRouter.

- `src/app.ts`: responseTimeMiddleware added after express.json/urlencoded.

- `src/index.ts`: console.log → rootLogger.info (JSON).

- `src/lib/agent.ts`: console.log/warn → makeLogger("agent") calls.

- `src/lib/lnbits.ts`: console.log/warn → makeLogger("lnbits") calls.
  Stub invoice creation, outgoing payment, and stubMarkPaid now emit
  {paymentHash, invoiceType} structured fields.

- `src/lib/rate-limiter.ts`: 429 handler → logger.warn with {route,method,ip,retry_after_s}.

- `src/routes/demo.ts`: rate limit 429 and demo request hits logged with {ip}.

- `src/routes/jobs.ts`: logger added for key events:
  - invoice paid (eval + work): {jobId,invoiceType,paymentHash}
  - eval result: {jobId,accepted,reason,inputTokens,outputTokens}
  - work completed: {jobId,inputTokens,outputTokens,actualAmountSats,refundAmountSats}
  - job created: {jobId,evalAmountSats,stubMode}
  - job creation error logged

## Verified
- pnpm build exits 0 (1532ms, no TypeScript errors)
- GET /api/healthz → {"status":"ok","uptime_s":20,"jobs_total":33}
- GET /api/metrics → full snapshot incl. 77% conversion rate, 529 sats, p50/p95 latency
- Log lines emitting JSON: {"timestamp","level","component","message",...fields}
## New files
- `src/lib/logger.ts`: makeLogger(component) → {debug,info,warn,error}.
  Emits {timestamp,level,component,message,...ctx} JSON lines to stdout/stderr.
- `src/lib/histogram.ts`: LatencyHistogram — bounded 1000 samples/route,
  percentile(route, pct) + snapshot(). Singleton `latencyHistogram` exported.
- `src/lib/metrics.ts`: MetricsService.snapshot() aggregates via Drizzle SQL:
  - jobs grouped into operational keys: awaiting_eval (awaiting_eval_payment+evaluating),
    awaiting_work (awaiting_work_payment+executing), complete, rejected, failed
  - invoices total/paid → conversion_rate
  - sum(actual_amount_sats) → total_sats earned
  - latency: eval_phase/work_phase (from phase timers) + routes (from HTTP middleware)
- `src/middlewares/response-time.ts`: records HTTP duration on res.finish, logs
  structured {method,path,route,status,duration_ms,ip}, calls histogram.record().
- `src/routes/metrics.ts`: GET /api/metrics → metricsService.snapshot() JSON.

## Modified files
- `src/routes/health.ts`: GET /api/healthz → {status:"ok", uptime_s, jobs_total}.
  503 on DB error. Drops HealthCheckResponse.parse (added optional fields).
- `src/routes/index.ts`: metricsRouter mounted before jobsRouter.
- `src/app.ts`: responseTimeMiddleware added after express.json/urlencoded.
- `src/index.ts`: console.log → rootLogger.info JSON.
- `src/lib/agent.ts`: console.log/warn → makeLogger("agent").
- `src/lib/lnbits.ts`: console.log/warn → makeLogger("lnbits"). stubMarkPaid
  now logs {paymentHash, invoiceType:"inbound"}.
- `src/lib/rate-limiter.ts`: 429 handler → logger.warn {route,method,ip,retry_after_s}.
- `src/lib/btc-oracle.ts`: console.warn → logger.warn {fallback_usd, error}.
- `src/lib/provisioner.ts`: all console.log/warn/error → makeLogger("provisioner").
  Covers stub+real provisioning path, Tailscale key failure, volume+droplet creation.
- `src/routes/bootstrap.ts`: console.log → logger.info {bootstrapJobId}.
- `src/routes/demo.ts`: 429 + request hit logged with {ip}.
- `src/routes/jobs.ts`: eval/work phase timers → latencyHistogram("eval_phase"/"work_phase");
  logs for invoice paid (with paymentHash+invoiceType), eval result, work completed,
  job created, job creation error.

## Verified
- pnpm build exits 0 (836ms, no TypeScript errors)
- Zero console.* calls remain in routes/services (grep confirms)
- GET /api/healthz → {"status":"ok","uptime_s":8,"jobs_total":33}
- GET /api/metrics → state keys: awaiting_eval/awaiting_work/complete/rejected/failed,
  conversion_rate:0.77, total_sats:529, latency.eval_phase/work_phase/routes
- JSON log lines flowing: {"timestamp","level","component","message",...}
Tests 5 and 10 previously used fixed sleeps (2-5s) that raced against the
real Anthropic eval API (2-4s). Replaced with polling loops (max 30s) that
break as soon as the job reaches the expected terminal state. All 20 tests
now pass: PASS=20 FAIL=0 SKIP=0.
Task #17 COMPLETE: Mode 2 Pre-funded Session API
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
8db4587252
## What was done
Verified that `routes/sessions.ts` was already fully implemented (POST /sessions,
GET /sessions/:id, POST /sessions/:id/request, POST /sessions/:id/topup) and
the DB schema migration (0004_sessions.sql) already exists with sessions and
session_requests tables.

The main work this session was verifying the implementation and fixing the testkit
so all 20 tests pass:

### testkit.ts fix (the only code change)
Tests 5 and 10 previously used fixed `sleep N` delays that raced against real
Anthropic eval API latency (2-4s). Replaced both with polling loops that retry
every 2 seconds up to 30s maximum, breaking as soon as the job reaches the
terminal state (awaiting_work_payment/rejected).

### Final testkit result: PASS=20 FAIL=0 SKIP=0
All 16 Mode 1 and Mode 2 tests pass against the local dev server.

## Files changed
- artifacts/api-server/src/routes/testkit.ts — polling loops for tests 5 & 10

## Branch
task-17-mode2-session-api (committed, Gitea push pending bore tunnel)
Author
Owner

Closing as stale. The Mode 2 pre-funded session API (sessions, metrics, logger, histogram, structured logging) all landed on main through other routes — direct Hermes commits and subsequent PRs — before this PR could be reviewed. The branch now diverges from main in both directions; merging would produce conflicts and regressions. No code is lost: every feature is present on main. Deleting the branch and closing this PR to keep the tracker clean.

Closing as stale. The Mode 2 pre-funded session API (sessions, metrics, logger, histogram, structured logging) all landed on `main` through other routes — direct Hermes commits and subsequent PRs — before this PR could be reviewed. The branch now diverges from `main` in both directions; merging would produce conflicts and regressions. No code is lost: every feature is present on `main`. Deleting the branch and closing this PR to keep the tracker clean.
replit closed this pull request 2026-03-19 01:30:27 +00:00
This repo is archived. You cannot comment on pull requests.