From 7fafe5f5152d4af6a91b8327b8714934c9bb90bb Mon Sep 17 00:00:00 2001 From: jaylfc Date: Sat, 13 Jun 2026 16:15:54 +0100 Subject: [PATCH 01/57] fix(store): rkllama model install when backend runs but is unregistered (#843) * fix(store): model install works when rkllama runs but is unregistered The model-store Install button failed for rkllama models on the Pi with 'backend install failed: script not found: scripts/install-rkllama.sh'. Two compounding bugs (found by live test of the store route on the Pi): 1. get_device_capability built installed_backends only from the registry, so a running-but-unregistered rkllama was treated as not installed and every model install took the install_chain branch. Now it also live-probes rkllama (new rkllama_is_running() checks the taOS + legacy ports) and adds it, so the resolver returns action=use and goes straight to the model pull. 2. The rkllama backend manifest's install.script pointed at a non-existent scripts/install-rkllama.sh; repointed to the real scripts/install-rknpu.sh as a safety net for when the chain is genuinely needed. This is the real symptom behind the #783 store-UI caveat; the rkllama connection logic itself was already correct (probes 7833 then falls back to 8080). Adds rkllama_is_running tests. * fix(store): offload the rkllama liveness probe to a thread Addresses a Gitar review note: rkllama_is_running() does blocking socket I/O, and get_device_capability is async, so run it via asyncio.to_thread to avoid stalling the event loop during a model install. * fix(store): drop the manifest script repoint from this PR Qodo flagged that scripts/install-rknpu.sh exit-0-false-succeeds in a non-interactive shell without TAOS_RKNPU_SETUP, so the install_chain would mark rkllama installed when it did nothing. That is worse than the original loud 'script not found'. The chain path is not the bug this PR fixes (the live-probe handles the running-rkllama case), so revert the manifest change and track the chain fix separately. * fix(store): log the rkllama runtime-detection fallback instead of swallowing it --- tests/test_rkllama_installer.py | 25 +++++++++++++++++++++ tinyagentos/installers/rkllama_installer.py | 12 ++++++++++ tinyagentos/routes/store_install.py | 14 ++++++++++++ 3 files changed, 51 insertions(+) diff --git a/tests/test_rkllama_installer.py b/tests/test_rkllama_installer.py index c217c229..2d743802 100644 --- a/tests/test_rkllama_installer.py +++ b/tests/test_rkllama_installer.py @@ -11,7 +11,32 @@ from tinyagentos.installers.rkllama_installer import ( parse_hf_resolve_url, resolve_rkllama_url, + rkllama_is_running, ) +from tinyagentos.installers import rkllama_installer + + +class TestRkllamaIsRunning: + def test_true_when_taos_port_responds(self, monkeypatch): + monkeypatch.setattr( + rkllama_installer, "_port_responds_with_rkllama", + lambda port, timeout=1.0: port == rkllama_installer._DEFAULT_RKLLAMA_PORT, + ) + assert rkllama_is_running() is True + + def test_true_when_only_legacy_port_responds(self, monkeypatch): + monkeypatch.setattr( + rkllama_installer, "_port_responds_with_rkllama", + lambda port, timeout=1.0: port == rkllama_installer._LEGACY_RKLLAMA_PORT, + ) + assert rkllama_is_running() is True + + def test_false_when_nothing_responds(self, monkeypatch): + monkeypatch.setattr( + rkllama_installer, "_port_responds_with_rkllama", + lambda port, timeout=1.0: False, + ) + assert rkllama_is_running() is False class TestParseHfResolveUrl: diff --git a/tinyagentos/installers/rkllama_installer.py b/tinyagentos/installers/rkllama_installer.py index 5513dc34..73aea082 100644 --- a/tinyagentos/installers/rkllama_installer.py +++ b/tinyagentos/installers/rkllama_installer.py @@ -59,6 +59,18 @@ def _port_responds_with_rkllama(port: int, timeout: float = 1.0) -> bool: return False +def rkllama_is_running() -> bool: + """True if a live rkllama server answers on either the taOS or legacy port. + + Used so a running-but-unregistered rkllama is treated as an installed + backend during model resolution (skips a needless reinstall chain). + """ + return ( + _port_responds_with_rkllama(_DEFAULT_RKLLAMA_PORT) + or _port_responds_with_rkllama(_LEGACY_RKLLAMA_PORT) + ) + + def default_rkllama_url() -> str: """Return the best local rkllama base URL. diff --git a/tinyagentos/routes/store_install.py b/tinyagentos/routes/store_install.py index a6460374..3e5cc5dd 100644 --- a/tinyagentos/routes/store_install.py +++ b/tinyagentos/routes/store_install.py @@ -9,6 +9,7 @@ """ from __future__ import annotations +import asyncio import logging from dataclasses import asdict from urllib.parse import urlparse @@ -104,6 +105,19 @@ async def get_device_capability(request: Request, target_remote: str | None) -> installed_backends = tuple(b for b in _KNOWN_BACKENDS if b in ids) except Exception: # noqa: BLE001 installed_backends = () + # A backend that is actually running but missing from the registry must + # still count as installed, so the resolver uses it (action="use") + # instead of trying to (re)install it. rkllama runs as a bare process on + # the Pi and is often not registered, which made every model install + # take a broken install_chain path (issue #783 follow-up). + if "rkllama" not in installed_backends: + try: + from tinyagentos.installers.rkllama_installer import rkllama_is_running + # Offload the blocking socket probe so it never stalls the loop. + if await asyncio.to_thread(rkllama_is_running): + installed_backends = installed_backends + ("rkllama",) + except Exception as exc: # noqa: BLE001 + logger.debug("rkllama runtime detection skipped: %s", exc) return DeviceCapability( device_id="local", targets=targets, From e8810f4a7e949ad3fdefab8b1a73a65f5c6334bc Mon Sep 17 00:00:00 2001 From: jaylfc Date: Sat, 13 Jun 2026 16:50:09 +0100 Subject: [PATCH 02/57] docs: rename brand to taOS + brand contact to info@taos.my (#847) * docs: brand contact details to info@taos.my and add taos.my website link Swaps the personal gmail for the branded info@taos.my across the README, LICENSE commercial-contact, code of conduct enforcement, CONTRIBUTING, and getting-started docs, and surfaces taos.my as the project website. Leaves the gitea-migration runbook config value (infra, not public contact) untouched. * docs(readme): rename the TinyAgentOS brand to taOS Replaces the long-form 'TinyAgentOS' display name with 'taOS' throughout the README, keeping one bracketed mention up top ('taOS (short for TinyAgentOS)') so first-time readers learn what taOS stands for. Lowercase tinyagentos in URLs, paths, and the package/repo slug is unchanged. --- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 2 +- LICENSE | 2 +- README.md | 35 ++++++++++++++++++----------------- docs/getting-started.md | 2 +- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4befe6ce..5d43e3c2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -jaylfc25@gmail.com. +info@taos.my. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a6668c7..66e5b0f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -233,4 +233,4 @@ Routes are registered in `app.py`. Route modules access stores via `request.app. ## Contact -Questions not suited for a GitHub issue? Email jaylfc25@gmail.com. +Questions not suited for a GitHub issue? Email info@taos.my. diff --git a/LICENSE b/LICENSE index fb05adda..60338ad9 100644 --- a/LICENSE +++ b/LICENSE @@ -49,7 +49,7 @@ free of charge and is not a Commercial Purpose, provided the Software (and anything built on it) is not offered, sold, hosted, or otherwise made available to any third party. -To obtain a commercial license, contact JAN LABS LTD at jaylfc25@gmail.com. +To obtain a commercial license, contact JAN LABS LTD at info@taos.my. 4. Trademarks diff --git a/README.md b/README.md index 6b71e9e7..9df79226 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@

-Self-hosted AI agent platform that runs on whatever hardware you have. An old laptop, a Raspberry Pi, a gaming PC, an SBC gathering dust, or all of them at once. TinyAgentOS turns your spare hardware into a distributed AI compute cluster. +Self-hosted AI agent platform that runs on whatever hardware you have. An old laptop, a Raspberry Pi, a gaming PC, an SBC gathering dust, or all of them at once. taOS (short for TinyAgentOS) turns your spare hardware into a distributed AI compute cluster. A full web desktop environment with 36 bundled apps, 108 catalog apps, 47 MCP plugins, 16 agent frameworks, a curated local model catalog of 112 manifests covering LLMs, vision, embeddings, audio, and image generation (including RK3588 NPU variants via c01zaut/happyme531), plus 167k+ searchable models from HuggingFace, agent deployment, training, image/video/audio generation, and full system monitoring, all from a single web dashboard. Supports Apple Silicon (MLX), NVIDIA, AMD, Rockchip NPU, Raspberry Pi, Android phones, and more. -**Framework-agnostic by design.** TinyAgentOS owns everything that matters: your agent's memory, files, communication channels, model access, and configuration. The agent framework is just a replaceable execution engine. Switch from SmolAgents to LangChain to OpenClaw and your agent keeps its entire history, all its Telegram/Discord/Slack connections, its trained LoRA adapters, its files, and its API keys. No migration, no data loss, no reconfiguration. This is possible because TinyAgentOS manages the full agent lifecycle outside the framework. +**Framework-agnostic by design.** taOS owns everything that matters: your agent's memory, files, communication channels, model access, and configuration. The agent framework is just a replaceable execution engine. Switch from SmolAgents to LangChain to OpenClaw and your agent keeps its entire history, all its Telegram/Discord/Slack connections, its trained LoRA adapters, its files, and its API keys. No migration, no data loss, no reconfiguration. This is possible because taOS manages the full agent lifecycle outside the framework. **[taOSmd](https://github.com/jaylfc/taosmd) — Framework-agnostic AI memory system.** 97.0% **end-to-end Judge accuracy** on [LongMemEval-S](https://github.com/xiaowu0162/LongMemEval) — retrieve → generate → judge-with-LLM-grader, 500 questions across 50+ sessions each. For context, the most-cited open comparators — MemPalace (96.6%) and agentmemory (95.2%) — publish **Recall@5** retrieval scores on the same dataset, which measures only whether the correct session lands in the top-5 (no generation, no judge). The metrics aren't apples-to-apples until one of us re-runs end-to-end; ours is the stricter measurement. Per-category on our hybrid-plus-query-expansion config: knowledge-update 100%, multi-session 98.5%, single-session-user 97.1%, single-session-assistant 96.4%, temporal-reasoning 94.0%, single-session-preference 90.0%. Everything runs on a £170 Orange Pi 5 Plus with no cloud dependencies. The stack: temporal knowledge graph with validity windows + contradiction detection, hybrid semantic+keyword vector search with cross-encoder rerank and LLM-assisted query expansion (the "Librarian" layer), zero-loss append-only archive, automatic fact extraction, intent-aware retrieval routing, multi-layer context assembly. Any agent framework can read/write through the HTTP API. @@ -72,7 +72,7 @@ Open `http://your-host:6969` (or `http://taos.local:6969` with mDNS). The root U ## Web Desktop Experience -TinyAgentOS ships with a full browser-based desktop environment. Open it at `http://your-host:6969/` and you get a window manager, dock, launchpad, notifications, widgets, and 36 bundled apps, no native install required. On phones and tablets it automatically swaps to a widget-first home screen with swipeable pages, a persistent dock, and desktop-style app windows with close/minimise title bars, installable as a fullscreen PWA from the browser's "Add to Home Screen". +taOS ships with a full browser-based desktop environment. Open it at `http://your-host:6969/` and you get a window manager, dock, launchpad, notifications, widgets, and 36 bundled apps, no native install required. On phones and tablets it automatically swaps to a widget-first home screen with swipeable pages, a persistent dock, and desktop-style app windows with close/minimise title bars, installable as a fullscreen PWA from the browser's "Add to Home Screen". - **Window manager.** Float, snap zones, drag, resize, minimise, maximise, close - **Top bar.** Global search (Ctrl+Space), clock, notifications, widget toggle @@ -190,7 +190,7 @@ Pick from 1,467 agent templates, 12 built-in plus 196 from awesome-openclaw-agen One-click install for agent frameworks, AI models, and services. Hardware-aware, only shows what works on your device. ### Agent Deployment -5-step wizard: pick framework → choose model → configure → deploy into an isolated container (LXC on bare metal, Docker on VPS, auto-detected). Each agent gets its own memory system (taOSmd instance), its own file storage, and its own network identity. The framework runs inside the container but TinyAgentOS manages everything around it: memory, channels, secrets, model access, scheduled tasks, and inter-agent communication. This means the framework is a swappable component, not a lock-in decision. +5-step wizard: pick framework → choose model → configure → deploy into an isolated container (LXC on bare metal, Docker on VPS, auto-detected). Each agent gets its own memory system (taOSmd instance), its own file storage, and its own network identity. The framework runs inside the container but taOS manages everything around it: memory, channels, secrets, model access, scheduled tasks, and inter-agent communication. This means the framework is a swappable component, not a lock-in decision. > **Running taOS *inside* an LXC (e.g. Proxmox)?** Deploying an agent creates a *nested* container, which an **unprivileged** LXC cannot do — the kernel can't remap the nested container's filesystem, so the deploy fails with an `idmapped storage / change ownership` error. Run the taOS LXC as **privileged with nesting enabled**. On Proxmox: untick *Unprivileged container* and set Options → Features → `nesting=1` (plus `keyctl=1`, `fuse=1`), then redeploy. Bare-metal and VM installs are unaffected. (taOS detects this and surfaces the fix in the deploy error.) @@ -201,7 +201,7 @@ One-click install for agent frameworks, AI models, and services. Hardware-aware,

The Agents app on mobile — one tap from empty to your first deployed agent.

### Channel Hub (Framework-Agnostic Messaging) -Most agent frameworks force you to wire up Telegram, Discord, or Slack directly into their code. If you switch frameworks, you rebuild all those integrations from scratch. TinyAgentOS flips this: the platform owns the messaging connections and routes messages to whichever framework the agent currently uses. Switch an agent from SmolAgents to LangChain and it keeps every channel, every conversation, every connection. The framework never touches the bot tokens. +Most agent frameworks force you to wire up Telegram, Discord, or Slack directly into their code. If you switch frameworks, you rebuild all those integrations from scratch. taOS flips this: the platform owns the messaging connections and routes messages to whichever framework the agent currently uses. Switch an agent from SmolAgents to LangChain and it keeps every channel, every conversation, every connection. The framework never touches the bot tokens. - **6 connectors**. Telegram, Discord, Slack, Email (IMAP/SMTP), Web Chat (WebSocket), Webhooks - **15 framework adapters.** Thin HTTP bridges (~25 lines each) that translate the universal message format to framework-specific APIs @@ -209,7 +209,7 @@ Most agent frameworks force you to wire up Telegram, Discord, or Slack directly - **Per-agent or shared bots.** Each agent gets its own bot, or share one across a group ### LLM Proxy (LiteLLM) -Hidden internal gateway that unifies all inference providers behind a single OpenAI-compatible API. Each agent gets a virtual API key with budget and rate limits. The proxy is auto-configured from your backend list. Switch from a local Ollama backend to a cloud provider (or add both as fallbacks) and no agent config changes. The agent just calls its local API key and TinyAgentOS routes to the best available backend. +Hidden internal gateway that unifies all inference providers behind a single OpenAI-compatible API. Each agent gets a virtual API key with budget and rate limits. The proxy is auto-configured from your backend list. Switch from a local Ollama backend to a cloud provider (or add both as fallbacks) and no agent config changes. The agent just calls its local API key and taOS routes to the best available backend. ### Dynamic Capabilities Features unlock automatically based on your hardware and cluster. Solo Pi sees core features. Add a GPU worker and image generation, video, and training appear. No configuration, the platform just knows what's possible. @@ -243,7 +243,7 @@ taOS wraps taOSmd with platform-specific scheduling (job queue, resource manager - **Browse / collections**. `GET /browse`, `GET /collections`, `POST /ingest`, `POST /delete-chunk` - **Memory browser.** Web UI to search across all agents' knowledge bases from one place - **Framework-independent.** Memory lives on the host, not in the framework or the container. Switch frameworks and the agent's entire knowledge base stays intact. -- **Portable.** Export an agent's config, channels, and memory. Import on another TinyAgentOS instance. +- **Portable.** Export an agent's config, channels, and memory. Import on another taOS instance. The embedding backend (`qmd.service`, port 7832) provides an Ollama-compatible embedding API with batch embedding and retry logic, backed by rkllama on RK3588 or node-llama-cpp elsewhere. LiteLLM also exposes a `/v1/embeddings` endpoint that routes to the same backends so frameworks using the OpenAI embeddings API work without any shim. @@ -347,7 +347,7 @@ Search across agents, apps, messages, and files from a single endpoint. Finds an ## Architecture ``` -TinyAgentOS Controller (FastAPI + htmx + React Desktop Shell) +taOS Controller (FastAPI + htmx + React Desktop Shell) ├── Web Desktop Shell (window manager, dock, launchpad, widgets, 36 bundled apps) ├── Mobile/Tablet Shell (widget home, dock, app title bars, swipeable pages, iOS PWA) ├── Skills & Plugins Registry (8 default skills, 15 framework adapters) @@ -495,7 +495,7 @@ The bytecode cleanup line is belt-and-braces; Python's mtime-based invalidation ## Service Management -TinyAgentOS ships with a systemd unit at `/etc/systemd/system/tinyagentos.service`. It auto-restarts on failure and auto-starts on boot. +taOS ships with a systemd unit at `/etc/systemd/system/tinyagentos.service`. It auto-restarts on failure and auto-starts on boot. ```bash sudo systemctl start tinyagentos @@ -518,7 +518,7 @@ See [docs/mirror-policy.md](docs/mirror-policy.md) for the mirror governance pol ## TurboQuant KV cache compression -**768K context window on a single RTX 3060 (12 GB).** TinyAgentOS integrates Google's TurboQuant (ICLR 2026) KV cache quantization via TheTom/llama-cpp-turboquant. Unlike weight quantization, which compresses model files, TurboQuant compresses the per-request KV cache -- the per-token memory that scales with context length and is the actual bottleneck on consumer hardware. +**768K context window on a single RTX 3060 (12 GB).** taOS integrates Google's TurboQuant (ICLR 2026) KV cache quantization via TheTom/llama-cpp-turboquant. Unlike weight quantization, which compresses model files, TurboQuant compresses the per-request KV cache -- the per-token memory that scales with context length and is the actual bottleneck on consumer hardware. Measured on Qwen3.5-9B-Q4_K_M, single RTX 3060 12 GB, decode speed stable at 52-62 t/s across the entire range: @@ -547,7 +547,7 @@ The llama.cpp CUDA build works on Debian 12 (glibc 2.36) and older distributions ## Exo Distributed Inference -TinyAgentOS integrates [exo](https://github.com/exo-explore/exo) for running models that are too large for any single device. While the TAOS cluster routes different tasks to different workers (task parallelism), exo splits a single large model across multiple devices (pipeline parallelism). The two are complementary. +taOS integrates [exo](https://github.com/exo-explore/exo) for running models that are too large for any single device. While the TAOS cluster routes different tasks to different workers (task parallelism), exo splits a single large model across multiple devices (pipeline parallelism). The two are complementary. **What exo enables:** Run 70B+ parameter models by pooling VRAM across multiple machines. A 70B model that needs ~40 GB can be split across a 12 GB desktop GPU + a 16 GB laptop + a 24 GB Mac, with exo handling the shard placement and inter-device communication automatically. @@ -594,7 +594,7 @@ uv run exo - [docs/design/framework-agnostic-runtime.md](docs/design/framework-agnostic-runtime.md). containers hold code, hosts hold state (load-bearing architectural rule) - [docs/superpowers/specs/2026-04-11-taos-framework-integration-bridge-design.md](docs/superpowers/specs/2026-04-11-taos-framework-integration-bridge-design.md). TAOS Framework Integration Bridge design (OpenClaw → Hermes → OpenClaw round-trip, not yet implemented) - [docs/mirror-policy.md](docs/mirror-policy.md). binary mirror governance: what is mirrored, SHA256 verification, self-hosting guide -- [docs/deploy/platform.md](docs/deploy/platform.md). Runbook for the tinyagentos.com platform LXC, covering landing page, docs site, and bittorrent tracker. Uses `scripts/install-platform-lxc.sh` on the Proxmox host to provision. Infrastructure for the project's public web presence, not part of the TinyAgentOS product itself. +- [docs/deploy/platform.md](docs/deploy/platform.md). Runbook for the tinyagentos.com platform LXC, covering landing page, docs site, and bittorrent tracker. Uses `scripts/install-platform-lxc.sh` on the Proxmox host to provision. Infrastructure for the project's public web presence, not part of the taOS product itself. ## Development @@ -691,18 +691,19 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and guidelines. Jo ## Support the Project -TinyAgentOS makes AI agents accessible on affordable hardware. +taOS makes AI agents accessible on affordable hardware. -- **Contact:** jaylfc25@gmail.com +- **Website:** [taos.my](https://taos.my) +- **Contact:** info@taos.my - **Donate:** [Buy Me a Coffee](https://buymeacoffee.com/jaylfc) - **Hardware donations/loans:** We test on real hardware. If you have spare SBCs, GPUs, or dev boards and want to help expand compatibility, reach out. ## Acknowledgments -TinyAgentOS stands on a lot of excellent community work, particularly on Rockchip. Shout-outs where they are earned: +taOS stands on a lot of excellent community work, particularly on Rockchip. Shout-outs where they are earned: - **[c01zaut](https://huggingface.co/c01zaut)**. Qwen2.5 1.5B → 14B RKLLM model ports that let chat work on RK3588 at all. -- **[NotPunchnox](https://github.com/NotPunchnox).** Original rkllama HTTP server that TinyAgentOS extends with a rerank patch. +- **[NotPunchnox](https://github.com/NotPunchnox).** Original rkllama HTTP server that taOS extends with a rerank patch. - **[tobi](https://github.com/tobi)** and contributors on [qmd](https://github.com/tobi/qmd), the embedding / reranker / query-expansion backend that taOSmd uses for vector operations, including the centralised `qmd serve` mode ([PR #511](https://github.com/tobi/qmd/pull/511)). If you maintain one of the libraries above and want a different phrasing or a link added, open an issue and I will fix it. @@ -720,4 +721,4 @@ taOS is better for the people testing it, filing issues, and sending fixes: taOS Sustainable Use License v0.1 — source-available, not open source. See [LICENSE](LICENSE). -Free to use, modify, and self-host for personal use and for your own organisation's internal business purposes — forever. A separate commercial license from JAN LABS LTD is required to sell taOS, host it as a paid service, or build it into a product or service you monetise (contact jaylfc25@gmail.com). Prior releases tagged under AGPL-3.0 remain available under AGPL-3.0. +Free to use, modify, and self-host for personal use and for your own organisation's internal business purposes — forever. A separate commercial license from JAN LABS LTD is required to sell taOS, host it as a paid service, or build it into a product or service you monetise (contact info@taos.my). Prior releases tagged under AGPL-3.0 remain available under AGPL-3.0. diff --git a/docs/getting-started.md b/docs/getting-started.md index ea54902f..c41f1f0a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -447,7 +447,7 @@ When filing a bug, it helps to include: - What you expected to happen vs. what actually happened - Relevant log output from `journalctl -u tinyagentos -n 50` -**Email:** [jaylfc25@gmail.com](mailto:jaylfc25@gmail.com) — for anything that doesn't fit a GitHub issue. +**Email:** [info@taos.my](mailto:info@taos.my) — for anything that doesn't fit a GitHub issue. **A note on maturity:** TinyAgentOS is in early development. If something doesn't work, it may genuinely be a bug rather than user error — please do report it. Contributions and hardware test reports are very welcome. From 9e1462a9300aa7dbf3cf0ea3103250a631824892 Mon Sep 17 00:00:00 2001 From: jaylfc Date: Sat, 13 Jun 2026 16:50:11 +0100 Subject: [PATCH 03/57] feat(theme): add the Light theme - Apple-grade light counterpart to Default (#848) Adds a third builtin theme (Default dark / Light / Matrix Terminal). Three parts: - builtin-themes.ts: the Light palette over the existing --color-shell-* tokens (cool off-white body, slate accent, frosted near-white chrome, softer shadows, light wallpaper). Contrast-checked: body ink hits ~14:1, secondary ~6.4:1. - theme-store.ts: applyThemeConfig now derives a light/dark scheme from the theme's --color-shell-bg luminance and tags the root data-scheme. Works for the builtin Light theme and any agent-generated light theme alike. - tokens.css: a light-scheme compatibility layer that inverts the ~800 hardcoded white-overlay utilities (bg-white/N, border-white/N) and ~140 text-white uses that apps still carry, so they read dark-on-light instead of vanishing. Gated on data-scheme=light via attribute selectors whose specificity beats Tailwind utilities without !important, so the dark theme is provably untouched. The dark Default theme is unchanged. Verified live: computed styles invert under the light scheme and stay identical under dark. --- .../src/stores/__tests__/theme-apply.test.ts | 16 ++++++ desktop/src/stores/theme-store.ts | 22 +++++++++ desktop/src/theme/builtin-themes.ts | 41 ++++++++++++++++ desktop/src/theme/tokens.css | 49 +++++++++++++++++++ 4 files changed, 128 insertions(+) diff --git a/desktop/src/stores/__tests__/theme-apply.test.ts b/desktop/src/stores/__tests__/theme-apply.test.ts index 1a2f0e97..04bcb3b9 100644 --- a/desktop/src/stores/__tests__/theme-apply.test.ts +++ b/desktop/src/stores/__tests__/theme-apply.test.ts @@ -15,4 +15,20 @@ describe("applyThemeConfig", () => { applyThemeConfig({ tokens: { "--evil": "x" } as Record, structure: {}, effects: [], requires: [] }); expect(document.documentElement.style.getPropertyValue("--evil")).toBe(""); }); + + it("tags the root data-scheme from the theme's bg luminance", () => { + // Light bg -> light scheme (drives the compatibility layer in tokens.css). + applyThemeConfig({ tokens: { "--color-shell-bg": "#f4f5f7" }, structure: {}, effects: [], requires: [] }); + expect(document.documentElement.dataset.scheme).toBe("light"); + // rgba dark bg -> dark. + applyThemeConfig({ tokens: { "--color-shell-bg": "rgba(22, 25, 32, 0.92)" }, structure: {}, effects: [], requires: [] }); + expect(document.documentElement.dataset.scheme).toBe("dark"); + // No bg override (default theme) -> dark base. + applyThemeConfig({ tokens: { "--color-accent": "#abc" }, structure: {}, effects: [], requires: [] }); + expect(document.documentElement.dataset.scheme).toBe("dark"); + // revert resets to dark. + applyThemeConfig({ tokens: { "--color-shell-bg": "#ffffff" }, structure: {}, effects: [], requires: [] }); + revertTheme(); + expect(document.documentElement.dataset.scheme).toBe("dark"); + }); }); diff --git a/desktop/src/stores/theme-store.ts b/desktop/src/stores/theme-store.ts index 22027e90..d1f6cd71 100644 --- a/desktop/src/stores/theme-store.ts +++ b/desktop/src/stores/theme-store.ts @@ -126,6 +126,26 @@ export const useThemeStore = create((set) => ({ let _applied: string[] = []; // token keys currently set, for revert +// Decide whether a theme reads as light or dark from its window-body colour, +// so the light-scheme compatibility layer in tokens.css (which inverts the +// hardcoded white overlays apps still use) keys off one attribute. Works for +// the builtin Light theme and any agent-generated light theme alike. +function schemeFromBg(bg: string | undefined): "light" | "dark" { + if (!bg) return "dark"; // no override (default theme) -> the dark base CSS + let r: number, g: number, b: number; + const hex = bg.trim().match(/^#([0-9a-fA-F]{6})$/); + if (hex) { + const n = parseInt(hex[1]!, 16); + [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255]; + } else { + const m = bg.match(/rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i); + if (!m) return "dark"; + [r, g, b] = [+m[1]!, +m[2]!, +m[3]!]; + } + const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; + return luminance > 0.55 ? "light" : "dark"; +} + export function applyThemeConfig(cfg: ThemeConfig) { revertTheme(); const root = document.documentElement; @@ -135,6 +155,7 @@ export function applyThemeConfig(cfg: ThemeConfig) { _applied.push(k); } } + root.dataset.scheme = schemeFromBg(cfg.tokens?.["--color-shell-bg"]); useThemeStore.setState({ structure: cfg.structure || {}, effects: cfg.effects || [] }); } @@ -142,6 +163,7 @@ export function revertTheme() { const root = document.documentElement; for (const k of _applied) root.style.removeProperty(k); _applied = []; + root.dataset.scheme = "dark"; // base shell is dark useThemeStore.setState({ structure: {}, effects: [] }); } diff --git a/desktop/src/theme/builtin-themes.ts b/desktop/src/theme/builtin-themes.ts index 9a61fd76..ec1a14c3 100644 --- a/desktop/src/theme/builtin-themes.ts +++ b/desktop/src/theme/builtin-themes.ts @@ -14,6 +14,47 @@ export const BUILTIN_THEMES: BuiltinTheme[] = [ builtin: true, config: { tokens: {}, structure: {}, effects: [], requires: ["assistant", "launcher"], wallpaper: null }, }, + { + theme_id: "light", + name: "Light", + builtin: true, + config: { + tokens: { + // Cool off-white window body and a slightly deeper sidebar layer. + "--color-shell-bg": "#f4f5f7", + "--color-shell-bg-deep": "#e9ebef", + // Surfaces invert from white-on-dark to subtle black-on-light fills. + "--color-shell-surface": "rgba(0, 0, 0, 0.035)", + "--color-shell-surface-hover": "rgba(0, 0, 0, 0.055)", + "--color-shell-surface-active": "rgba(0, 0, 0, 0.08)", + "--color-shell-border": "rgba(0, 0, 0, 0.09)", + "--color-shell-border-strong": "rgba(0, 0, 0, 0.15)", + // Near-black ink: 14:1 / 6.4:1 / 4.0:1 on the #f4f5f7 body. + "--color-shell-text": "rgba(0, 0, 0, 0.85)", + "--color-shell-text-secondary": "rgba(0, 0, 0, 0.55)", + "--color-shell-text-tertiary": "rgba(0, 0, 0, 0.42)", + // Slate accent — the dark theme's cool-neutral grey, darkened to read on light. + "--color-accent": "#5b6472", + "--color-accent-glow": "rgba(91, 100, 114, 0.25)", + // Frosted near-white chrome. + "--color-dock-bg": "rgba(245, 246, 248, 0.82)", + "--color-dock-border": "rgba(0, 0, 0, 0.1)", + "--color-topbar-bg": "rgba(245, 246, 248, 0.82)", + "--color-snap-preview": "rgba(91, 100, 114, 0.16)", + "--color-snap-border": "rgba(91, 100, 114, 0.45)", + // Lighter, softer shadows — heavy dark drops look wrong on a light surface. + "--shadow-window": "0 8px 32px rgba(0, 0, 0, 0.16)", + "--shadow-window-unfocused": "0 4px 16px rgba(0, 0, 0, 0.1)", + "--shadow-dock": "0 4px 24px rgba(0, 0, 0, 0.12)", + "--shadow-card": "0 1px 3px rgba(0, 0, 0, 0.1), 0 0 1px rgba(0, 0, 0, 0.06)", + "--shadow-card-hover": "0 8px 24px rgba(0, 0, 0, 0.14), 0 0 1px rgba(0, 0, 0, 0.08)", + }, + structure: {}, + effects: [], + requires: ["assistant", "launcher"], + wallpaper: "linear-gradient(160deg, #eef0f3 0%, #e6e9ee 45%, #dee2e8 100%)", + }, + }, { theme_id: "matrix-terminal", name: "Matrix Terminal", diff --git a/desktop/src/theme/tokens.css b/desktop/src/theme/tokens.css index 854d60e5..2232bc2c 100644 --- a/desktop/src/theme/tokens.css +++ b/desktop/src/theme/tokens.css @@ -48,6 +48,55 @@ --shadow-card-hover: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 1px rgba(255, 255, 255, 0.08); } +/* ================================================================== + Light-scheme compatibility layer + ================================================================== + The shell tokens above (window chrome, dock, top bar, text) already + flip per theme. But ~800 surfaces across the apps still use hardcoded + white-overlay utilities (bg-white/5, border-white/10) and ~140 use + hardcoded white text (text-white) — additive white that vanishes on a + light background. Rather than migrate every call site, this layer + inverts those utilities to dark-on-light, but ONLY when a light theme + is active (data-scheme="light", set from the theme's bg luminance in + theme-store.ts). The dark theme is never matched, so it is untouched. + + Specificity note: `:root[data-scheme="light"] [class~="x"]` is (0,3,0), + which beats Tailwind's single-class utilities (0,1,0) and hover + variants (0,2,0) without !important. Hover overlays get their own + :hover rules below so the affordance survives the inversion. + ================================================================== */ +:root[data-scheme="light"] [class~="bg-white/3"] { background-color: rgba(0, 0, 0, 0.03); } +:root[data-scheme="light"] [class~="bg-white/5"] { background-color: rgba(0, 0, 0, 0.04); } +:root[data-scheme="light"] [class~="bg-white/10"] { background-color: rgba(0, 0, 0, 0.06); } +:root[data-scheme="light"] [class~="bg-white/15"] { background-color: rgba(0, 0, 0, 0.08); } +:root[data-scheme="light"] [class~="bg-white/20"] { background-color: rgba(0, 0, 0, 0.10); } + +:root[data-scheme="light"] [class~="border-white/5"] { border-color: rgba(0, 0, 0, 0.08); } +:root[data-scheme="light"] [class~="border-white/8"] { border-color: rgba(0, 0, 0, 0.10); } +:root[data-scheme="light"] [class~="border-white/10"] { border-color: rgba(0, 0, 0, 0.12); } +:root[data-scheme="light"] [class~="border-white/15"] { border-color: rgba(0, 0, 0, 0.14); } +:root[data-scheme="light"] [class~="border-white/20"] { border-color: rgba(0, 0, 0, 0.16); } + +:root[data-scheme="light"] [class~="text-white"] { color: rgba(0, 0, 0, 0.88); } +:root[data-scheme="light"] [class~="text-white/90"] { color: rgba(0, 0, 0, 0.84); } +:root[data-scheme="light"] [class~="text-white/80"] { color: rgba(0, 0, 0, 0.78); } +:root[data-scheme="light"] [class~="text-white/70"] { color: rgba(0, 0, 0, 0.68); } +:root[data-scheme="light"] [class~="text-white/60"] { color: rgba(0, 0, 0, 0.58); } +:root[data-scheme="light"] [class~="text-white/50"] { color: rgba(0, 0, 0, 0.50); } +:root[data-scheme="light"] [class~="text-white/45"] { color: rgba(0, 0, 0, 0.48); } +:root[data-scheme="light"] [class~="text-white/40"] { color: rgba(0, 0, 0, 0.45); } +:root[data-scheme="light"] [class~="text-white/35"] { color: rgba(0, 0, 0, 0.44); } +:root[data-scheme="light"] [class~="text-white/30"] { color: rgba(0, 0, 0, 0.42); } +:root[data-scheme="light"] [class~="text-white/25"] { color: rgba(0, 0, 0, 0.40); } +:root[data-scheme="light"] [class~="text-white/20"] { color: rgba(0, 0, 0, 0.40); } +:root[data-scheme="light"] [class~="text-white/15"] { color: rgba(0, 0, 0, 0.38); } + +/* Hover overlays: re-assert the inverted fill on :hover so the affordance + does not flatten under the base-state override. */ +:root[data-scheme="light"] [class~="hover:bg-white/5"]:hover { background-color: rgba(0, 0, 0, 0.05); } +:root[data-scheme="light"] [class~="hover:bg-white/10"]:hover { background-color: rgba(0, 0, 0, 0.07); } +:root[data-scheme="light"] [class~="hover:bg-white/[0.06]"]:hover { background-color: rgba(0, 0, 0, 0.05); } + /* Wallpaper sizing ---------------- Desktop fills the viewport (cover crops edges if aspect mismatches). From 6502b93385af0fdce5d87a217d2a98a23f76206b Mon Sep 17 00:00:00 2001 From: jaylfc Date: Sat, 13 Jun 2026 16:50:13 +0100 Subject: [PATCH 04/57] chore(deps): bump esbuild to 0.28.1 (fixes RCE advisory), raise build target to es2022 (#849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dependabot flagged esbuild < 0.28.1 (missing binary integrity verification, RCE via NPM_CONFIG_REGISTRY). esbuild is a transitive dep of vite 6, and the bump needs a build target of es2022+ (vite's default es2020/chrome87 target hit an esbuild 0.28 destructuring-downlevel error). es2022 covers Chrome 94+, Safari 15.4+, Firefox 93+ — all 2021/2022, fine for a modern self-hosted desktop. Pinned via the existing overrides block. tsc + vite build verified green. --- desktop/package-lock.json | 221 ++++++++++++++++++-------------------- desktop/package.json | 3 +- desktop/vite.config.ts | 1 + 3 files changed, 110 insertions(+), 115 deletions(-) diff --git a/desktop/package-lock.json b/desktop/package-lock.json index cf388732..5f23b178 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -36,7 +36,6 @@ "lucide-react": "^0.500.0", "mathjs": "^15.2.0", "motion": "^12.40.0", - "pell": "^1.0.6", "plyr": "^3.8.4", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -974,9 +973,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -991,9 +990,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -1008,9 +1007,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -1025,9 +1024,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -1042,9 +1041,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -1059,9 +1058,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -1076,9 +1075,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -1093,9 +1092,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -1110,9 +1109,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -1127,9 +1126,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -1144,9 +1143,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -1161,9 +1160,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -1178,9 +1177,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -1195,9 +1194,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -1212,9 +1211,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -1229,9 +1228,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -1246,9 +1245,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -1263,9 +1262,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1280,9 +1279,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -1297,9 +1296,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -1314,9 +1313,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -1331,9 +1330,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -1348,9 +1347,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -1365,9 +1364,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1382,9 +1381,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1399,9 +1398,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -6108,9 +6107,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6121,32 +6120,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -8172,12 +8171,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pell": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pell/-/pell-1.0.6.tgz", - "integrity": "sha512-wuackvgjFCHmVABy7ACSmY2u53w+TlYFrVL2hN6V3rGL/iWWwVXMw2uphpZSXNnqFQTI8nMpD3UNNX8+R4BAYw==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/desktop/package.json b/desktop/package.json index 7b4795a1..1ebbe072 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -68,6 +68,7 @@ "vitest": "^4.1.0" }, "overrides": { - "dompurify": "^3.4.0" + "dompurify": "^3.4.0", + "esbuild": "^0.28.1" } } diff --git a/desktop/vite.config.ts b/desktop/vite.config.ts index 68fd24e2..33a61bc5 100644 --- a/desktop/vite.config.ts +++ b/desktop/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ alias: { "@": path.resolve(__dirname, "src") }, }, build: { + target: "es2022", outDir: "../static/desktop", emptyOutDir: true, // CodeMirror + mathjs + lucide each ship genuinely large libraries From a7114181fac7773020e65c7f85c8dcb167fd3297 Mon Sep 17 00:00:00 2001 From: jaylfc Date: Sat, 13 Jun 2026 17:35:50 +0100 Subject: [PATCH 05/57] feat(agent): unify the taOS Agent chat composer (attach/screenshot inside the input) (#850) The attach and screenshot buttons sat as detached siblings to the left of a tall textarea, sinking to the bottom-left and reading as out of place. Wrap them into one rounded composer (chat-app convention): attach + screenshot as borderless ghost icons inside on the left, the field borderless in the middle, a filled accent send button on the right, bottom-aligned so the icons sit on the last line as the field grows. Shell tokens only, so it flips for the Light theme; switched the send glyph to an up-arrow. --- desktop/src/components/TaosAssistantPanel.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/desktop/src/components/TaosAssistantPanel.tsx b/desktop/src/components/TaosAssistantPanel.tsx index 5f9ba499..a8797056 100644 --- a/desktop/src/components/TaosAssistantPanel.tsx +++ b/desktop/src/components/TaosAssistantPanel.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { - Send, + ArrowUp, Sparkles, X, Settings, @@ -332,7 +332,11 @@ export function TaosAssistantPanelInner({ embedded = false }: { embedded?: boole

)} -
+ {/* One unified composer: attach + screenshot inside on the left, the + field borderless in the middle, send on the right. Bottom-aligned so + the icons sit on the last line as the textarea grows (chat-app + convention). Shell tokens only, so it flips for the Light theme. */} +