Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.
- design-agent system prompt + in-process design toolset (parity-locked with
the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
(per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
bubbles, model-pill bottom toolbar, header, empty-state pills, the
PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
(switch / close / + / Cmd+T) with each run bound to its tab
Large checkpoint commit spanning the working tree (Rust shell crates).
The product is now Rust (crates/) + the Zig agent runtime
(packages/agent-native) + the wasm-backed web SDK (packages/op-web-sdk*).
Delete the retired TypeScript editor/desktop/CLI (apps/*), the pen-*
packages, and their orphaned TS tooling, Dockerfile, and root tsconfig.
Rust-side hooks into the deleted TS are rehomed so the workspace still
builds, tests, and releases green:
- op-mcp: vendor the 10 element-tool-defs shards it include_str!'d from
pen-mcp into crates/op-mcp/assets/element-tool-defs/ (git renames), and
repoint the compile-time includes + the runtime parity test's read_dir.
- op-orchestrator: reword the parity panic that named the retired
dump-planner-golden.ts generator (goldens stay as frozen baselines).
- CI: drop the TS-derive golden-drift + planner-parity oracle jobs and the
TS path filters from rust-check.yml; repoint the cargo-bundle icon in
rust-release.yml to crates/op-host-desktop/assets/icon.png.
- Release (Windows): recover the deleted apps/desktop/build/icon.ico into
crates/op-host-desktop/assets/icon.ico and repoint the NSIS installer
(rust-release.yml /DICON_FILE + package-windows.nsi ICON_FILE fallback +
its example invocation), so the Windows release build still finds it.
- Remove the dead .githooks/post-commit hook — it rebuilt the deleted
out/mcp-server.cjs via the removed mcp:compile script and only watched
now-deleted pen-* paths; the Rust MCP is built by cargo.
- Recover apps/web/public/logo-discord.svg to screenshot/ and repoint the
15 READMEs' logo/link paths.
package.json is now Rust-first (dev/build/test -> cargo + serve-web) and
docs (CLAUDE.md/AGENTS.md/packages+crates CLAUDE.md) describe the Rust
product. The retired TS remains recoverable via git (last tag v0.7.5).
Verified: cargo check --workspace green; cargo test -p op-mcp 353 passed;
cargo test -p op-orchestrator 848 passed; cargo test --workspace --no-run
compiles all targets; rust-release.yml parses and references no deleted
build assets.
Pre-commit fmt hook bypassed: its cargo fmt --check is pre-red from a
repo-wide stable-vs-nightly rustfmt config drift affecting unrelated files;
the .rs files touched here produce no fmt diff.
Drives the three-OS CI matrix verification of the skia-safe + glutin +
glow + winit dep stack per Step 1a spec §7.
- examples/p0_probe.rs: stencil_visibility + readback chain runner (must
own a real OS main thread because winit on macOS rejects
EventLoop::new() from cargo test worker threads).
- tests/p0_probe.rs: subprocess-invoke wrapper, gated
#[ignore = "P0_PROBE_GATE"] so default cargo test stays untouched.
- Cargo.toml: add transient [target.'cfg(not(target_arch = "wasm32"))'.
dev-dependencies] block (skia-safe 0.97 + glutin 0.32.3 + glutin-winit
0.5.0 + glow 0.17.0 + raw-window-handle 0.6.2 + scopeguard 1.2.0 +
winit defaults). Pinned to versions resolved in /tmp/skia-glow-probe.
- .github/workflows/rust-check.yml: install Linux GL prereqs (xvfb,
mesa, libxkbcommon, libwayland) and add a P0-probe-gate step running
cargo test --ignored on each OS (Linux through xvfb-run; Windows
early-returns per spec §8.2 WINDOWS_GPU_DEFERRED_NO_RUNNER).
All three artefacts are TRANSIENT — reverted in a follow-up cleanup
commit after CI is green and the loader-compat notes commit lands.
Task 1 owns the permanent integration.
Drives the three-OS CI matrix verification of the skia-safe + glutin +
glow + winit dep stack per Step 1a spec §7.
- examples/p0_probe.rs: stencil_visibility + readback chain runner (must
own a real OS main thread because winit on macOS rejects
EventLoop::new() from cargo test worker threads).
- tests/p0_probe.rs: subprocess-invoke wrapper, gated
#[ignore = "P0_PROBE_GATE"] so default cargo test stays untouched.
- Cargo.toml: add transient [target.'cfg(not(target_arch = "wasm32"))'.
dev-dependencies] block (skia-safe 0.97 + glutin 0.32.3 + glutin-winit
0.5.0 + glow 0.17.0 + raw-window-handle 0.6.2 + scopeguard 1.2.0 +
winit defaults). Pinned to versions resolved in /tmp/skia-glow-probe.
- .github/workflows/rust-check.yml: install Linux GL prereqs (xvfb,
mesa, libxkbcommon, libwayland) and add a P0-probe-gate step running
cargo test --ignored on each OS (Linux through xvfb-run; Windows
early-returns per spec §8.2 WINDOWS_GPU_DEFERRED_NO_RUNNER).
All three artefacts are TRANSIENT — reverted in a follow-up cleanup
commit after CI is green and the loader-compat notes commit lands.
Task 1 owns the permanent integration.
Previous version did `require('undici')` inside a try/catch on the
theory that would let it run on both CJS and ESM. In Vite/Nitro's dev
path the helper loads as an ESM module, where `require` is undefined —
the call threw `ReferenceError: require is not defined`, the catch
block silenced it, `configured=true` still got flipped, and every
subsequent call short-circuited. Net effect: the proxy was never
installed in the very dev environment the fix was meant to repair, so
image-search kept ECONNREFUSED-ing on Openverse + Wikimedia and
landing zero filled placeholders.
Switch to a static `import { setGlobalDispatcher, EnvHttpProxyAgent }
from 'undici'`. undici is a transitive dep of h3 in this workspace
(verified resolved in node_modules), and it's also the package Node 18+
uses internally for fetch — pinning it as a direct dep on apps/web
makes the resolution intentional rather than reliant on the h3 chain.
Also swapped the hand-rolled ProxyAgent for undici's built-in
`EnvHttpProxyAgent`: it reads HTTPS_PROXY / HTTP_PROXY / NO_PROXY
itself (case-insensitive) and applies the no-proxy bypass list, which
saves us from re-implementing those rules.
Verified with both `bun -e` (workspace deps) AND a direct ESM Node
context: with HTTPS_PROXY set, `fetch(api.openverse.org/...)` now
returns 240 results for "salmon sushi" instead of the earlier
ECONNREFUSED. The "configured" guard still makes calls idempotent so
multiple endpoints can opt in without coordinating.
Root cause for all-blank-placeholders on the food-app brief: Node's
native fetch (used by the Nitro dev server's image-search endpoint)
ignores the system proxy by default. On machines that route outbound
HTTPS through a local proxy (clash / mihomo / corporate gateway —
mine sits at 127.0.0.1:7897), every Openverse + Wikimedia call from
the server silently ECONNREFUSEDs. The endpoint's catch block returns
`null` for Openverse → falls back to Wikimedia → that ECONNREFUSEDs
too → returns `[]`. Browser shows zero filled images.
Direct curl from the same machine uses HTTPS_PROXY automatically, which
is why a manual API check (e.g. `curl https://api.openverse.org/...`)
returned 240 results for "salmon sushi" while
`/api/ai/image-search?query=salmon%20sushi` returned `{results:[]}`.
`apps/web/server/utils/proxy-dispatcher.ts::configureProxyDispatcher`:
- Reads HTTPS_PROXY / https_proxy / HTTP_PROXY / http_proxy.
- If set, installs `undici.ProxyAgent` as the global fetch dispatcher
via `setGlobalDispatcher`. From that point on every server-side
`fetch()` routes through the proxy.
- Idempotent — multiple endpoints can call it without re-installing.
- No-op when no proxy env var is present (production / CI).
- Dynamic `require('undici')` so a build target that strips undici
doesn't crash at import time.
Wired into `image-search.ts` at module top so the dispatcher is
configured before the first request lands. Other endpoints making
external fetches can opt in with the same single-line call.
Verified standalone via Bun: with the helper in place,
`fetch('https://api.openverse.org/v1/images/?q=salmon+sushi')` returns
240 results. The dev server itself needs a restart to pick up the
server-side change (Vite server-code HMR doesn't re-evaluate Nitro
modules).