Skip to content

Roadmap #10 AI scene assistant + #11 generative 3D - #28

Merged
AlexZ005 merged 22 commits into
mainfrom
feature/ai-scene-assistant
Jul 20, 2026
Merged

Roadmap #10 AI scene assistant + #11 generative 3D#28
AlexZ005 merged 22 commits into
mainfrom
feature/ai-scene-assistant

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Roadmap #10 (AI scene assistant) + #11 (generative 3D)

Ships the AI feature line built on feature/ai-scene-assistant. Linear history on top of main (22 commits, no divergence).

Roadmap #10 — AI scene assistant

Track A — in-app assistant

  • src/lib/ai/providers.js — Grok / Gemini / custom vLLM presets via one OpenAI-compatible client (config list + active, aiReady()).
  • ai/client.js — plain-fetch OpenAI-compatible SSE streaming, index-keyed tool_call accumulation, runChat tool loop, testConnection, CORS-aware error naming.
  • history.jsbeginHistoryBatch/endHistoryBatch + aibatch composite kind (one undo per prompt).
  • ai/tools.jslist_scene / create_objects / update_objects / delete_objects / group_objects / clear_scene over the replicated mutation surface (explicit uuids = no selection steal).
  • ai/assistant.jsrunPrompt, aiMessages/aiBusy stores.
  • AiAssistant.svelte — chat window + backquote-toggled prompt pill (hidden by default), Settings AI section.
  • e2e: ai-assistant.test.cjs (single page + broadcast-capture stub, 12/12).

Track B — headless console agent (tools/agent/, own package)

  • @roamhq/wrtc peerjs-in-Node, peerBridge.js approval-dance state machine (omits the modules handshake), REPL + MCP modes, mcpServer.js stdio server.
  • Offline tests 10/10 (bridge) + 16/16 (mock-peer approval dance); real-browser round-trip passes on the self-hosted PeerJS after the reopen fix (real WebRTC doesn't signal the host's pre-approval conn-close → inbound conn drives a retrying reopen + stale-conn grace).

Roadmap #11 — generative 3D ("AI v2")

Text/image to textured mesh in-scene via a provider-adapter architecture:

  • ai/meshProviders.js + meshAdapters/{comfyui,meshy} + meshJobs.js runner (poll, cancel, 40MB guard, progress store).
  • ComfyUI adapter = self-hosted TRELLIS path ({{PROMPT}}/{{SEED}} workflow template → /prompt/history/view GLB); Meshy adapter = hosted REST task poll.
  • Import via fileHandler.importGeneratedGlb (standard replication, userData.aiGen provenance, Explorer "Generated" cache).
  • Entry points: assistant generate_mesh long-tool, Add-menu MeshGenModal, MeshJobsCard; console-agent parity via objectfile bytes.
  • e2e: mesh-generation.test.cjs 7/7 (mock ComfyUI + Meshy). Setup guide in docs/ai/generation.md.

Verification

  • npm run build green; svelte-check held at 502/77 baseline.
  • Per-phase e2e suites committed. Plan docs (docs/plan, docs/ai) intentionally uncommitted / gitignored.

🤖 Generated with Claude Code

AlexZ005 and others added 22 commits July 18, 2026 21:43
- src/lib/ai/providers.js: local provider config store (Grok/Gemini/custom
  vLLM presets); a list of saved {baseUrl,apiKey,model} configs + one active,
  JSON-blob persisted in localStorage like avatarConfig
- setters addAiProvider/updateAiProvider/removeAiProvider/setAiActiveProvider/
  setAiEnabled + activeAiConfig()/aiReady() helpers
- keys are the first credentials the app stores: plaintext localStorage,
  documented in the file header (Reset settings wipes them)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src/lib/ai/client.js: plain-fetch client (no SDK) for any OpenAI-compatible
  endpoint; SSE streaming with a line buffer carried across chunks and
  index-keyed tool_call fragment accumulation
- runChat() tool-loop: any accumulated tool_calls count as a tool turn
  (Gemini-compat defensiveness), malformed args feed an error result back so
  the model self-corrects, abort threaded through
- testConnection() (GET /models -> 1-token fallback) + describeAiError()
  mapping 401/429/404/5xx and naming CORS for self-hosted vLLM

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… A3)

- history.js: beginHistoryBatch/endHistoryBatch collect entries into one
  composite 'aibatch' step so a whole AI prompt is a single undo; the batch
  branch in recordEntry returns BEFORE redoStack is cleared (redo only clears
  when the batch commits)
- aibatch applier replays each sub-entry through the existing applyState
  (reverse order on undo) so create/move/color/delete sub-ops replicate their
  own peer messages for free
- materialsHandler.setObjectColor(): the missing exported color trio
  (color.set + peer color message + recordMaterialChange); the live gesture-
  debounced picker sites keep their inline path on purpose

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src/lib/ai/tools.js: OpenAI-format tools (list_scene, create_objects,
  update_objects, delete_objects, group_objects, clear_scene), batch-first
- executeAiTool maps each tool onto the existing REPLICATED mutation surface
  (createGeometry/createLight/createGroup with an explicit uuid = no selection
  steal, move/color/name/group/delete messages, renameObject/moveObjectToGroup/
  deleteObjectsByUuid/setObjectColor/setMaterialParam); never throws, returns
  results so the model self-corrects; skips objects locked by other peers
- summarizeScene() walks objectsGroup into a compact uuid/name/transform/color
  list; buildSystemPrompt() gives Y-up ground-plane guidance

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src/lib/ai/assistant.js: module-level conversation state (survives closing
  the window); runPrompt() seeds system prompt + a fresh scene summary each
  turn (multi-turn refine), streams the model, runs tool calls
- wraps the whole run in beginHistoryBatch/endHistoryBatch (in finally) so a
  partial run on abort/error still commits as one undo step
- guards no-provider by toasting + deep-linking Settings; stopAi() aborts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- AiAssistant.svelte: floating window modeled on Chat (dragWindow/focusStack/
  tabbable), streaming message list by role, provider picker, Send/Stop
- quick prompt pill hidden by default, toggled by the backquote (`) shortcut
  (registered via the shortcuts registry so it binds AND lists in Settings);
  submitting the pill opens the window and runs the prompt
- appStore: aiAssistantHidden + aiPromptBarOpen (session-only) stores; mount
  AiAssistant in Menu.svelte next to Chat

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Settings.svelte: new AI accordion item between Scene and Shortcuts (legacy-
  mode file, on:/bind: directives) - enable toggle, saved-provider rows with
  active radio + Edit/Remove, add/edit form (preset prefills baseUrl+model,
  password key field), Test connection button, plaintext-key warning
- settingsSection 'ai' deep-links + expands the section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tests/e2e/ai-assistant.test.cjs: drives the assistant against a mocked
  OpenAI-compatible endpoint (page.route -> canned SSE tool_calls); asserts
  a prompt creates 3 boxes locally, broadcasts the standard create/color/move
  replication messages (captured via a send stub), one undo removes the whole
  batch (aibatch) + broadcasts 3 deletes, and redo restores + re-broadcasts
- App.svelte: expose ai/providers, ai/tools, ai/assistant on the __stores hook
- receive-path replication rides existing message handlers covered by other
  two-peer suites; the public cloud is too flaky to gate this suite on

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tools/agent: new Node package (own package.json, ESM) for a console agent
  that joins a session as a peer; deps peerjs + @roamhq/wrtc + ws
- polyfills.js installs RTCPeerConnection/WebSocket/navigator globals BEFORE
  peerjs imports; RTC impl isolated so it is swappable
- spike.js proves viability: @roamhq/wrtc installs + loads on win32/Node 20
  (the main risk), and the peerjs WebSocket handshake reaches the signaling
  server (public cloud currently 429-rate-limits repeated connects, which
  itself proves the server was reached). GO: no Playwright-page fallback needed
- peerjs Peer constructor is at (await import('peerjs')).default.Peer in ESM

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- messages.js: pure protocol builders + catalogs (create/light/move/color/name/
  group/delete/objectParameters + minimal handshake); no peerjs/THREE, unit-tested
- peerBridge.js: connection state machine + approval dance (connect -> host closes
  unknown conn -> human Approves -> host connects back + gossips hosts -> agent
  reopens a fresh outgoing conn + sends minimal handshake -> connected); broadcasts
  to every open conn (full mesh); OMITS the modules handshake (would toast module
  mismatches); send queue gated on conn.open; signaling retry with backoff + a
  process guard that swallows peerjs's transient uncaught ws 429 crash
- registry.js: scene tracking from own sends + observed peer messages; GLTF object
  syncs become uuid stubs (tracked:'stub')
- cli.js --smoke: scripted create/move/color/delete to prove the pipe
- test/messages.test.mjs: 10 pure unit tests (builders + registry), all green

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tools.js: same tool names/shapes as the in-app assistant (list_scene,
  create_objects, update_objects, delete_objects, group_objects, get_status)
  mapped onto peer messages via messages.js + bridge.broadcast; never throws,
  returns JSON results; systemPrompt() for the REPL/MCP drivers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- llmClient.js: minimal OpenAI-compatible client (non-streaming tool loop) for
  any endpoint (vLLM/Hermes/Grok/Gemini)
- repl.js: readline loop driving the scene via tool calls; slash commands
  /status /list /raw /quit; config via --api-url/--api-key/--model or env

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- mcpServer.js: stdio MCP server exposing the scene tools + a connect tool so any
  MCP host (Claude Code) can drive the session; starts immediately and connects in
  the background, tools report "awaiting approval" until connected so registration
  never blocks on the human step
- verified with a real MCP client: lists all 7 tools, get_status + create route
  correctly (create reports not-connected without hanging)
- add @modelcontextprotocol/sdk dependency

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p 10 B6)

- test/bridge.test.mjs: deterministic mock-peer test of the FULL approval dance
  (connect -> host closes unknown -> awaiting-approval -> host connects back +
  gossips hosts -> agent reopens conn -> handshake + flush queue -> connected),
  send-queue flush, handshake shape (locked/hosts/userdata, modules OMITTED),
  registry, and shutdown. 16/16 green with no network
- tests/e2e/agent-bridge.test.cjs: real browser round-trip (agent joins a session,
  human Approve, create/move/color/delete land on the host). Rides the public
  PeerJS cloud + Node WebRTC, so it is flaky/rate-limited (429) like every two-peer
  suite; re-run after a cooldown. The deterministic bridge/messages/registry tests
  + the MCP client test cover the same logic offline
- README.md: install, modes (repl/mcp/smoke), flags, architecture, limitations
- cli.js: drop the redundant uncaughtException guard (peerBridge installs it)
- npm test runs both offline unit suites

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2e (roadmap 10)

- peerBridge: real WebRTC rarely signals the host's pre-approval conn close, so
  the post-approval reopen was blocked. Now the inbound conn from the host (the
  approve-connects-back signal) drives a retrying reopen of our outgoing conn, and
  _openOutgoing replaces a stale never-opened conn after an 8s grace (mirrors
  peerHandler's "Restoring connection"). Whitelist inbound peers. Server opts gain
  a key + default path /peerjs to match the app's self-hosted convention
- cli --peer-server defaults to 443 + /peerjs + optional --peer-key
- agent-bridge.test.cjs: reads VITE_PEER_* from .env so the agent joins the SAME
  self-hosted signaling server the browser uses; Users-list check is now polled.
  FULL round-trip now PASSES (connect + create/color/delete land on the host)
- ai-assistant.test.cjs: reload before the pill check to dodge the dev-server HMR
  dual-module-instance split (the hook + the component's static import can diverge);
  12/12 PASS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lits

The AI prompt-pill check flaked because the debugStores hook reaches singletons
via dynamic import(), which on an HMR-churned dev server can bind a SECOND module
instance — so a value set through window.__stores.<mod> is not the store the
component reads via its static import, and the component renders stale.

- helpers.freshReload(peer): reload + wait for the hook, collapsing to one module
  graph. Seed localStorage-persisted state first, reload, then assert component UI;
  set session-only stores after the reload.
- ai-assistant.test.cjs uses it for the pill check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src/lib/ai/meshProviders.js: LOCAL mesh-backend provider store mirroring
  ai/providers.js — kinds comfyui (self-hosted TRELLIS) + meshy (hosted); list +
  active + meshGenEnabled; meshGenReady() gates the feature (comfyui needs a
  workflow, meshy needs a key). Plaintext keys, same caveat as LLM keys
- Settings.svelte: a "Mesh generation" block in the AI section — enable toggle,
  provider rows, add/edit form (ComfyUI workflow-JSON paste + output node, or
  Meshy key + preview/refine mode)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- meshAdapters/: one small async interface (submit/poll/fetchResult) per backend.
  comfyui.js = the self-hosted TRELLIS path (substitute {{PROMPT}}/{{SEED}} into an
  API-format workflow -> POST /prompt -> poll /history -> GET /view GLB; bearer +
  --enable-cors-header). meshy.js = hosted (preview->refine task poll -> model GLB)
- meshJobs.js: runner — submit, poll to completion (10min cap, cancel), download,
  import; 40MB guard; progress via the meshJobs store; best-effort cache into the
  Explorer "Generated" folder
- fileHandler.importGeneratedGlb(): parse GLB bytes, stamp userData.aiGen
  provenance, place + replicate + record undo via the EXISTING addImported path
  (standard object sync — no new message types)
- MeshJobsCard.svelte: progress cards (bar, cancel, dismiss); App debugStores hook
  exposes meshProviders + meshJobs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…map 11 G6)

- tools.js: generate_mesh LONG tool (async fire-and-forget so the chat loop is not
  blocked for minutes; the job runner places the mesh when ready). getAiTools()
  includes it only when a mesh provider is ready; buildSystemPrompt mentions it
- assistant.js uses getAiTools() per turn
- addObjects.buildAddChildren: "Generate 3D model" viewport-menu entry when ready
- MeshGenModal.svelte: direct (no-LLM) generate dialog (provider + prompt + name,
  hosted-credit note); meshGenModalOpen store; mounted in Menu with MeshJobsCard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- exports a real GLB from a box in-page, mocks ComfyUI (/prompt, /history, /view)
  and Meshy (task + download) via page.route, and asserts: provider ready after
  freshReload, ComfyUI generate -> import with aiGen provenance, Meshy generate ->
  import, and undo removes the last generated mesh. 7/7 PASS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- meshGen.js: compact Node port of the ComfyUI + Meshy adapters (submit/poll/
  download GLB bytes)
- tools.js generate_mesh: async fire-and-forget; on completion pushes the GLB via
  the objectfile raw-bytes message (the agent can't run three.js). Registry stub
- cli.js: mesh backend from --mesh-kind/--mesh-url/--mesh-key/--mesh-workflow(file)/
  --mesh-mode or AGENT_MESH_* env; messages.objectFileMsg builder

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AlexZ005
AlexZ005 merged commit 31d62e5 into main Jul 20, 2026
@AlexZ005
AlexZ005 deleted the feature/ai-scene-assistant branch August 5, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant