# Changelog All notable changes to wpide-server will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning is [SemVer](https://semver.org/spec/v2.0.0.html). ## [0.6.3] — 2026-05-26 ### Changed - **Request logging back to `!isDev` (silent in prod).** Was temporarily forced on to debug the live browser-direct tool round-trip with the plugin on Hostinger (see plugin 56.95/56.96). Now that browser-direct is verified end-to-end (744 ms tool round-trip from `mersaai.diretenders.com`), prod logs go quiet again — every 30s `/v1/health` from Docker's healthcheck no longer fills the log file. ### Kept - The targeted diagnostic lines added in 0.6.2 stay in for future support work: - `browser-direct: awaiting tool result from browser` (orchestrator) - `browser-direct: tool result TIMED OUT after 90s` (orchestrator) - `tool_result POST received … matched:true|false` (runs route) These are low-volume and useful for diagnosing future host quirks. ## [0.6.2] — 2026-05-26 ### Fixed - **`z.coerce.boolean()` read every boolean env var as `true`.** `Boolean("false")` is `true` in JS, so `ALLOW_INSECURE_TLS=false`, `REQUIRE_LICENSE=false`, and `FREE_TIER_ACTIVE=false` were all silently `true`. Replaced with a `boolish()` parser that treats `0/false/no/off` as false and `1/true/yes/on` as true. This bit on the first real VPS deploy: TLS verification stayed disabled and license gating would have switched on unexpectedly. ### Added - **Production deploy artifacts (`deploy/`).** `vps-bootstrap.sh` (Ubuntu prep: update, ufw, base tools) and `docker-compose.coolify.yml` (runs the container behind Coolify's existing Traefik proxy on the external `coolify` network, with Let's Encrypt labels). First live deploy: Contabo VPS → Coolify/Traefik → `https://api.qbirr.com`. ## [0.6.1] — 2026-05-26 ### Fixed - **SSE stream endpoint now sends CORS headers.** The `/v1/runs/:id/stream` reply is hijacked (raw), so the cors plugin didn't run on it. Added `access-control-allow-origin` (echoes request origin) so the plugin's browser-direct `EventSource` can open the stream cross-origin (browser on the WP site → server on another domain). Without this the browser blocked the stream. ## [0.6.0] — 2026-05-25 — Memory, teams, browser-direct tools ### Added - **Server-side conversation memory (`orchestrator/memory.ts`).** Persists each run's user goal + final answer per `session_id`; recalls the last ~12 turns and merges (de-duplicated) into context on the next run. Runs now have continuity even when the plugin sends little history. (DB migration v3: `conversation_turns`.) - **Multi-tenant teams (`accounts/teams.ts`, `routes/teams.ts`).** Orgs + members + roles; `POST/GET /v1/teams`, member add/remove. A member **inherits the org owner's subscription tier** (team owner pays once, seats share the tier) — wired into `resolveAccess`. (Migration v3: `orgs`, `org_members`, `subscriptions.org_id`.) - **Browser-direct tool execution (cap-immune transport, server side).** `browser_tools: true` on a run makes the loop emit `tool_call` SSE events and await `POST /v1/runs/:id/tool_result` from the browser, instead of calling back into the plugin. Removes the long-lived request from the WP host entirely → works on any shared host. Relay mode (plugin callback) remains the default; this is additive. Proven end-to-end via test (tool_call emitted → result posted → run completed using the posted data). ### Notes - The browser-direct **plugin JS** (browser opens EventSource + runs the tool via admin-ajax + POSTs the result) is the remaining client-side step; the server fully supports it now. ## [0.5.0] — 2026-05-25 — SaaS platform foundation ### Added - **Accounts & auth.** Email+password (scrypt, no native deps), Google + GitHub OAuth (authorization-code flow), session JWT in an httpOnly cookie. `/v1/auth/{register,login,logout,me,set-password}` and `/v1/auth/oauth/:provider/{start,callback}`. - **Licensing & subscriptions (no credits).** Each account gets a license key + a subscription row (tier `basic|pro|max`, status). The run path resolves the license → tier and gates on an active subscription (`REQUIRE_LICENSE`, default off in dev so local testing needs no account; `DEV_DEFAULT_TIER` applies when off). - **Model routing by tier** (`routing/policy.ts`): tier sets the model ceiling — basic→flash, pro→+thinking, max→+pro-max — combined with mode/complexity. Explicit model picks are honored only within the tier, else downgraded. DeepSeek `thinking` param wired through. - **Stripe billing** (`fetch`, no SDK): `/v1/billing/{checkout,portal,webhook}` with manual webhook signature verification; subscription state mirrored into the DB. Disabled cleanly when keys absent. - **Dashboard** at `/app`: signup/login (email + Google + GitHub), license key with copy, plan + tier picker + manage-billing. Single inline HTML page, no build step. - **DB migration v2:** users, oauth_identities, licenses, subscriptions, sites (site registry); runs gain user_id/tier/model. - **Deploy:** `SETUP.md` operator checklist, `docker-compose.prod.yml` + `Caddyfile.prod` for a plain VPS (Coolify uses the Dockerfile directly). SQLite on a persistent volume for v1; Postgres is the documented scale-up path. ### Notes - Credits/metering intentionally deferred — access is subscription-gated, tier gates models. Multi-tenant teams, server-side memory, and the browser-direct SSE transport remain as later sub-projects. ## [0.4.1] — 2026-05-25 ### Fixed - **Reasoning rendered one token per line in the chat.** The agentic loop was emitting a `thinking` event per reasoning token; the plugin relays each as a `thought` event, and the browser appends one list item per thought — so each reasoning token landed on its own line. Now the loop accumulates `reasoning_content` per step and emits a single `thinking` event when the step's LLM call finishes (matches the local orchestrator's one-thought-per-step model). Content tokens still stream individually into the live bubble. ## [0.4.0] — 2026-05-25 ### Added - **SSE token streaming.** `GET /v1/runs/:run_id/stream` streams a run's live events (`token`, `thinking`, `tool_call`, `tool_result`, `status`, `done`, `error`, `end`) with `?since=` resume. Backed by a per-run event buffer in the registry (`addEvent`). - `OpenAIClient.chatStream()` — streaming chat completions over fetch: parses SSE token deltas, accumulates `content` / `reasoning_content` / `tool_calls`, returns the same shape as `chat()`. Connect-only retry (safe to retry before any token; never mid-stream). - The agentic loop now uses `chatStream` and emits token/tool events into the run's buffer as they happen. ### Fixed - **DeepSeek v4 thinking mode HTTP 400** — the loop now echoes the assistant message's `reasoning_content` back on the next turn after a tool call, as DeepSeek v4 requires. (`chat()` and `chatStream()` both capture the field; the loop re-sends it.) ## [0.3.0] — 2026-05-25 ### Added - **Async run model — removes the synchronous timeout ceiling.** - `POST /v1/runs/start` registers a run, kicks off the orchestrator in the background, and returns `{ run_id, session_id }` immediately. - `GET /v1/runs/:run_id/status` reports live progress (`status`, `steps_done`, `tools_used`, `elapsed_ms`, `partial_content`) and the full PHP-shape `response` once finished. - In-memory run registry (`src/orchestrator/registry.ts`) with a 30-min TTL for finished runs. `process_request` shares its step/tool arrays with the registry so status reflects progress as it happens. - Old synchronous `POST /v1/runs` retained for non-browser callers. - **Built-in server-side `wait` tool** — lets agents pace long-running diagnostics without a DB `SLEEP()` (SQLite has none). Handled in-loop, never calls back to the plugin. Max 30s per call. - LLM chat retry hardened: 6 attempts, 20s per-attempt timeout (fails fast on a hung VPN connection and retries), abort/timeout treated as retryable. Step cap raised 25 → 40. - `AGENT_STEP tool_call → ` logged at info level for live flow visibility. ## [0.2.1] — 2026-05-25 ### Added - xAI (Grok) provider client — OpenAI-compatible base URL `https://api.x.ai/v1`. - DeepSeek provider client — base URL `https://api.deepseek.com/v1`. - Provider auto-routing in `pickProvider()`: explicit override → model-name prefix (`deepseek*`, `grok*`, `gpt-*`) → first configured key (deepseek → xai → openai). `defaultModelFor()` picks a sane model per provider. - `ALLOW_INSECURE_TLS` (default on in dev) — skips outbound TLS verification for machines behind a VPN/MITM whose root CA Node doesn't trust. Set off in prod. - Tool-manifest sanitizer (`src/tools/manifest.ts`) — coerces PHP's empty `[]` to `{}` where objects are required and strips null values, so strict validators (DeepSeek) stop rejecting the plugin's 95 tool schemas with HTTP 400. - LLM chat calls now retry up to 3× with backoff on network errors (connect timeout, DNS, TLS) and 5xx/429 — rides through flaky-VPN blips. 4xx are treated as real errors and not retried. - Detailed logging on tool-exec callback failures (URL, status, headers, body length) to diagnose cross-machine / rewrite issues. ### Changed - Server binds to `0.0.0.0` by default (was `127.0.0.1`) so other LAN machines can reach it. Default port `3017`. - Default model is now `deepseek-chat`. ## [0.2.0] — 2026-05-21 ### Added - `GET /` root route returns an API directory (name, version, endpoint hints) — friendlier than the previous 404 when poking around in a browser. - `POST /v1/runs` — main orchestrator entrypoint. Accepts `{ goal, context, options, tools_manifest, callback_url, callback_secret, license_key, site_url }` and returns the exact response shape PHP's `wp_ide_process_agentic()` uses: `{ success, content, tool_results, execution: { run_id, session_id, mode, steps, status_messages, ... }, approval_payload }`. - `GET /v1/runs/:run_id` — fetch a stored run record. - Greeting + simple paths fully working. Greetings need no LLM call; simple invokes OpenAI chat/completions (default `gpt-4o-mini`). Agentic path is a placeholder that returns a "step 5" message. - Orchestrator router (`src/orchestrator/router.ts`) — port of `WP_IDE_AI_Router::classify`; cheap regex/keyword heuristics that pick greeting / simple / agentic without an LLM call. - OpenAI client over fetch (`src/providers/openai.ts`) — no SDK dependency. Provider router (`src/providers/index.ts`) dispatches by model name; Anthropic / xAI plug in here in later steps. - Runs table CRUD (`src/db/runs.ts`) — persists every run with status transitions so a future status endpoint can answer "what happened". ### Notes - `OPENAI_API_KEY` is read from `.env` on boot. Without it the simple path returns a clear actionable error in the standard response shape; the greeting path still works because it doesn't call any LLM. ## [0.1.0] — 2026-05-21 ### Added - Initial Fastify scaffold (Node 20, TypeScript, ESM). - `GET /v1/health` returns name, version, uptime, Node version, timestamp. - SQLite via `better-sqlite3` as the dev database; WAL mode + foreign keys enabled. Postgres URL recognised for later Coolify deploy but not yet wired. - Initial schema: `schema_version`, `runs` tables (more added per milestone). - Zod-validated env loader, pino logger, CORS, graceful shutdown. - `start-dev.bat` for one-command Windows dev startup. - `Dockerfile` for future Coolify deployment (not used locally). - Optional `Caddyfile` for local TLS via `wpide.local`.