diff --git a/.claude/skills/taos-development-skill/SKILL.md b/.claude/skills/taos-development-skill/SKILL.md new file mode 100644 index 000000000..fa69459da --- /dev/null +++ b/.claude/skills/taos-development-skill/SKILL.md @@ -0,0 +1,324 @@ +--- +name: taos-development-skill +description: TinyAgentOS (taOS) architecture map, contribution workflow, testing guide, common fix patterns, and coding conventions. Load when contributing to taOS - PRs, bug fixes, features, catalog additions. +--- + +# taos-development-skill + +Procedures and architecture for contributing to +[TinyAgentOS](https://github.com/jaylfc/taOS). The non-negotiable rules live in +`soul.md`; this skill is the *how*. + +> uses approximate counts (~N) as rough orientation only - actual numbers rot fast in a +> living repo. Trust the tree, not tallies. + +## Repository layout + +``` +/ + tinyagentos/ ← server package + app.py ← FastAPI app factory: lifespan, route registration + config.py ← Platform config, hardware detection + routes/ ← one APIRouter module per feature area (~86 modules) + templates/ ← minimal: agent_debugger.html only (frontend is React SPA) + channel_hub/ ← framework-agnostic messaging: connectors + MessageRouter + adapters/ ← thin per-framework agent adapters (~25 lines each) + cluster/ ← distributed compute: worker registry, task routing, GPU lease + worker/ ← cross-platform worker apps (system tray, Android, iOS) + stores/ ← data layer: aiosqlite (SQLite), one store per concern + chat/ projects/ mcp/ ← chat, project board/canvas/A2A, MCP proxy+permissions + installers/ containers/ ← model/app installers; Docker + LXC backends + migrations/ ← DB migrations + desktop/ ← React + TypeScript SPA (Vite) + app-catalog/ ← YAML app manifests + catalog.yaml (~108 apps) + tests/ ← pytest suite (~3,590 tests) + docs/ ← documentation; agent manual compiled from docs/agent-manual/ +``` + +## Key architectural patterns + +- **Routes** - each `routes/*.py` is an `APIRouter` registered in `app.py`'s `create_app()`. + `async def` handlers, `await` all I/O, Pydantic request/response models. Routes access stores + via `request.app.state` (dependency injection set up in the app lifespan) - they do **not** + import stores directly. **No cross-importing between route modules.** +- **Stores** - SQLite via `aiosqlite`, each with `init()`/`close()`, attached to + `request.app.state` in the lifespan (`app.state.metrics`, `app.state.secrets`, …). +- **Config** - `AppConfig` dataclass in `config.py`; YAML serialisation; async-locked saves via + `save_config_locked()`; typed backends (`rkllama`, `ollama`, `openai`, `anthropic`, …). +- **Templates** - **Pico CSS utility classes only** (no other CSS framework). htmx (`hx-get`, + `hx-target`, `hx-swap`) for dynamic partials. Semantic HTML; ARIA labels on interactive elements + without visible text. Templates are minimal - the frontend is a React SPA. +- **Frontend** - React + TypeScript SPA in `desktop/`. Built with Vite: `npm run build` outputs + to `static/desktop/` (gitignored). For development: `npm run dev` serves with hot reload on + port 5173. One concern per component; API calls in dedicated hooks or service files. +- **Cluster** - worker registration, routing to remote nodes, model archive/promotion on capable + hardware, GPU lease claim/release, hardware-tier compatibility. + +## Git workflow + +### Before starting + +1. **Sync from upstream:** + ```bash + git fetch origin dev + git checkout dev + git rebase origin/dev + ``` + Never branch from a stale `dev`. + +2. **Create an isolated worktree** (recommended for concurrent work): + ```bash + git worktree add /path/to/worktree dev + cd /path/to/worktree + ``` + When done: `git worktree remove /path/to/worktree` (or `--force` if needed). + +3. **Create a branch:** `feat/` or `fix/` matching the task. + +### After completing the work + +4. **Commit before anything else.** Code → test → `git add` → `git commit` (conventional message) → + push to fork. + +5. **Open a draft PR, then mark ready immediately:** + ```bash + gh pr create --repo jaylfc/taOS --head : --base dev --draft \ + --title "feat(scope): description" --body "Fixes #. Tests: N/N pass." + gh pr ready + ``` + Do NOT wait for CI - fork PRs are gated behind maintainer workflow approval. + Mark ready once the CODE is done and local tests pass. + +6. **Never commit directly to `dev` or `master`.** All work happens on branches. + Main only receives merges via upstream PR approval. + +### Branch naming + +`feat/` or `fix/`. Keep it short and descriptive. + +### Commit messages + +Conventional commits only: + +| Prefix | Use for | +|--------|---------| +| `feat:` | new feature | +| `fix:` | bug fix | +| `docs:` | documentation only | +| `refactor:` | code change with no behaviour change | +| `test:` | adding or updating tests | +| `chore:` | tooling, deps, CI | + +No AI tool attribution in commit messages. + +## Testing + +Run **targeted tests first**, then the fast parallel gate. **Never run the un-parallelised full +suite (`pytest tests/ -v`) locally** - it is massive and will take far too long. CI owns the +full 3.12–3.13 matrix (3.11 on nightly cron only). + +```bash +# 1. Targeted - the changed module + related tests, always first: +uv run pytest tests/test_.py tests// -v + +# 2. Canonical local gate - parallel, run before marking ready: +uv run pytest tests/ --ignore=tests/e2e -n auto +``` + +### Test conventions + +- `conftest.py`: `tmp_data_dir` fixture creates temp config + SQLite +- `app` fixture: `create_app(data_dir=tmp_data_dir)` +- `client` fixture: `AsyncClient(transport=ASGITransport(app=app))` - async HTTP test client +- Module mirroring: `tests/test_agents.py` tests `routes/agents.py` +- SPA stubs: conftest creates stub `index.html`/`sw.js` so tests don't need `npm run build` +- E2E (Playwright) tests excluded from CI and local gate + +### CI matrix + +- Python 3.12 + 3.13 on every PR/push; 3.11 on nightly cron only +- GitHub Actions: `.github/workflows/ci.yml` in upstream repo +- Uses `uv sync --frozen` and `pytest -n auto` + +## CLA - HUMAN signs + +taOS requires a Contributor License Agreement for first-time contributors. The CLA bot +flags the PR with a `cla: fail` check. + +**The agent does NOT sign the CLA.** Posting the acceptance text accepts a legal agreement - that is the human account-holder's action, not the agent's. + +### Procedure when `cla: fail` appears: + +1. Verify the commit author email matches a GitHub-verified email. + If the email was wrong, amend the commit with the correct email, force-push. +2. Surface the bot's comment + link to the human. Do NOT post the acceptance text yourself. + Do NOT post `recheck`. + +The bot accepts a PR comment in this format (for the human to post): +``` +I have read the CLA Document and I hereby sign the CLA +``` + +## PR / CI flow (fork specifics) + +1. After creating the draft PR, check both `gh pr checks ` **and** + `gh run list --repo jaylfc/taOS --branch ` - the matrix run may not show in + `pr checks` while it awaits approval. +2. **If the CI run shows `action_required`**: the first-time-contributor workflow-approval policy + is blocking it. Surface this to the human - do not re-poll, re-push, or re-create the PR. + Lightweight checks (CLA, Gitar, CodeRabbit) run independently and don't need approval. +3. Once code is done and local tests pass, `gh pr ready`. Address review feedback with additional + commits on the same branch. The maintainer merges upstream. + +## Post-Push Bot Review Cycle + +After pushing a PR and marking it ready, automated bots (Kilo, CodeRabbit) run reviews. +Address their findings **before** surfacing the PR for human maintainer review - this +eliminates the wasteful push→block→manual-check→unblock→re-dispatch cycle. + +### Procedure + +1. **Push PR and mark ready.** Wait ~10 minutes for bot reviews to complete. +2. **Pull bot comments:** + ```bash + gh pr view --repo jaylfc/taOS --json comments --jq \ + '.comments[] | select(.author.login == "kilo-code-bot" or .author.login == "coderabbitai[bot]")' + ``` +3. **If issues found:** fix all findings in a single commit, re-run local tests, push, + then go back to step 1 (max 2 cycles). +4. **Only block for maintainer review when bots are clean** - 0 CRITICAL, 0 WARNING. + If a SUGGESTION-only finding is genuinely not applicable, note the rationale in a + PR comment before blocking. + +### Severity tiers + +| Tier | Action | +|------|--------| +| CRITICAL | Must fix before blocking for review | +| WARNING | Must fix before blocking for review | +| SUGGESTION | Fix or explain why not applicable | + +### Time estimates + +| Phase | Duration | +|-------|----------| +| First bot pass (Kilo + CodeRabbit) | ~10 min | +| Fix cycle (if needed) | ~5–10 min | +| Second bot pass (if re-pushed) | ~10 min | +| **Worst case (2 cycles)** | **~30 min** | + +## Common fix patterns + +- **New route:** `routes/.py` with `router = APIRouter()` → register in `create_app()` → + tests in `tests/test_.py` using the `client` fixture. +- **New store:** class with `init()`/`close()` (aiosqlite) → attach in the lifespan → mock in + conftest if needed. +- **Config field:** add to config dataclass → update defaults + `to_dict()`/`from_dict()` → + `test_config.py`. +- **Catalog entry:** `manifest.yaml` under `app-catalog///` → add to `catalog.yaml` → + `pytest tests/test_catalog_sync.py`. +- **Debugging a test:** confirm it uses the async `client` fixture and that `tmp_data_dir` setup is + complete; check the store's `init()`; isolate with `pytest :: -v`. + +## Documentation gate + +A gate blocks PRs that add or remove certain feature code without a matching doc update +(configured in `docs/doc-gate.toml`): + +| Change | Requires editing | +|--------|-----------------| +| Desktop app under `desktop/src/apps/` added/removed | `README.md` | +| Route module under `tinyagentos/routes/` added/removed | `docs/agent-coordination.md` | +| Installer under `tinyagentos/installers/` or `scripts/install*` added/removed | `README.md` | +| Manifest under `app-catalog/` added/removed | `README.md` | + +If your PR trips a rule and there is genuinely nothing to document, add a trailer: +``` +Docs-Reviewed: no user-facing change, internal refactor only +``` + +Run `scripts/install-git-hooks.sh` to enable local hooks (`.githooks/pre-commit` and +`.githooks/commit-msg`) so the gate runs before you push. + +## Upstream conventions (from CONTRIBUTING.md) + +- **Target branch is `dev`, not `master`.** `master` is the stable live-install track. +- Branch naming: `feat/` or `fix/` +- Conventional commits (see table above) +- No AI tool attribution in commits +- Python 3.11+ floor (pyproject.toml: `>=3.11,<3.14`). `match`/`case` and `X | None` union syntax + are available. Most modules use `from __future__ import annotations`. +- Code style: match surrounding code, one concern per module +- Use `uv` for dependency management and test running: `uv sync --extra dev`, `uv run pytest` + +## Desktop SPA build + test + +```bash +cd desktop +npm install # Node.js 22+ +npm run build # tsc -b && vite build → outputs to static/desktop/ +npm run test # vitest (unit/component tests) +npm run test:e2e # Playwright browser tests (needs running server) +``` + +## Adding an app to the catalog + +1. Create directory: `app-catalog///` +2. Write `manifest.yaml` (use `app-catalog/agents/langroid/manifest.yaml` as template) +3. Add entry to `app-catalog/catalog.yaml` +4. Run `uv run pytest tests/test_catalog_sync.py -v` +5. Open a PR + +All fields in `manifest.yaml` except `config_schema` are required. The `hardware_tiers` block +controls which hardware profiles see the app as recommended. + +## Pitfalls + +- **Full test suite is too large to run locally without `-n auto`.** Use the canonical + gate: `uv run pytest tests/ --ignore=tests/e2e -n auto`. CI handles the full matrix. +- **CI may show `action_required` on every PR from a fork.** GitHub requires maintainer approval + for workflow runs from first-time contributor forks. This can re-trigger on each new PR even after + previous PRs were approved - it's per-workflow-run, not per-contributor. Surface to the human; + do NOT poll or re-push. +- **No lint/format tooling is configured.** There is no `.pre-commit-config.yaml`, no + `.ruff.toml`, and no `[tool.ruff]`, `[tool.black]`, or `[tool.mypy]` section in + `pyproject.toml`. Match the surrounding code style manually. +- **Secrets:** No dedicated secrets store in the current tree. The `stores/` directory pattern + handles data access; secrets management may live in the MCP permissions model or be handled + at the OS/deployment layer. When unsure, treat secrets as escalate-to-human. +- **CONTRIBUTING.md** says Python 3.10+, but `pyproject.toml` requires `>=3.11,<3.14`. + Python 3.11 is the effective floor. +- **Routes do not import stores directly.** They access them via `request.app.state`. + This is a common mistake - check existing routes for the pattern. +- **`static/desktop/` is gitignored.** The SPA build output is a generated artifact. + The conftest in `tests/` stubs the SPA build output so backend tests don't need `npm run build`. + +## Issue triage + +### Finding actionable issues +1. Filter GitHub issues by `good first issue` or `help wanted` labels +2. Check issue age - fresh issues (< 2 weeks) have highest chance of being unclaimed +3. Read the issue body carefully - look for clear reproduction steps +4. Check if anyone is already assigned + +### Difficulty estimation +- **Catalog app addition** (~30 min): New manifest.yaml + catalog.yaml entry +- **Bug fix** (1–3 hours): Reproduce → find root cause → fix + regression test +- **Feature** (3+ hours): Design → implement → tests → documentation + +### Before starting work +1. Sync from upstream: `git fetch origin dev` +2. Verify the issue is still open and unassigned +3. Comment on the issue: "Working on this - will open a draft PR" + +## First-run setup + +```bash +git clone https://github.com/jaylfc/taOS.git +cd tinyagentos +uv sync --extra dev +cd desktop && npm install && npm run build && cd .. +uv run pytest tests/ --ignore=tests/e2e -n auto +``` + +Python 3.11+ and Node.js 22+ are required. diff --git a/.claude/skills/taos-development-skill/soul.md b/.claude/skills/taos-development-skill/soul.md new file mode 100644 index 000000000..bafe236c3 --- /dev/null +++ b/.claude/skills/taos-development-skill/soul.md @@ -0,0 +1,96 @@ +# taOS Contributor - Non-Negotiable Rules + +You are a contributing engineer on **TinyAgentOS (taOS)** - a self-hosted AI agent platform +for low-power hardware (Raspberry Pi through x86 servers), maintained by +[jaylfc](https://github.com/jaylfc/taOS). You work autonomously from tasks in isolated +git worktrees and ship PRs to upstream `dev`. + +taOS is a FastAPI + SQLite (aiosqlite) + Jinja2/htmx application, with a React + TypeScript + Vite +desktop SPA and a YAML app catalog. It runs hardware-frugally across a Python 3.11–3.13 CI matrix. + +## Non-negotiable rules (override any default habit) + +1. **Target `dev`, never `master`.** `master` is the stable release track (live installs follow it); + active development happens on `dev`. PRs go to `jaylfc/taOS:dev`. +2. **Python 3.11 floor.** The pyproject.toml pins `>=3.11,<3.14`. `match`/`case` and `X | None` + unions are available; most modules use `from __future__ import annotations`. +3. **Conventional commits, no AI attribution.** `feat: fix: docs: refactor: test: chore:`. No + "Co-authored-by" or "Generated with" trailers. +4. **Draft-first; mark ready when the CODE is done, not when CI is green.** Fork PRs are gated + behind maintainer workflow approval - waiting for green CI on a fork PR is a deadlock. Create + the PR as draft, verify tests locally, then mark ready immediately. +5. **The human account-holder signs the CLA - not the agent.** An agent must NOT post the CLA + acceptance comment on the maintainer's behalf; it is a legal agreement. If the CLA check fails, + surface the bot's link to the human. +6. **One task = one focused branch = one atomic commit.** No bundling unrelated changes. + +## Safety (inviolable) + +NEVER reboot, poweroff, halt, or restart the host, and NEVER reset or reload GPU drivers, +autonomously - not via `reboot`, `shutdown`, `systemctl`, `nvidia-smi --gpu-reset`, or any other +path, even with sudo. If an infra fault blocks a task: surface the blocker and wait for a human. +System-level recovery is a human decision. + +## Judgment + +- When a task is ambiguous, when the schema/API changes in an unexpected way, or when the scope + balloons past the task as written: **stop and ask** rather than guess. +- Match the surrounding code. Read neighbouring modules before writing. +- Leave the tree cleaner than you found it - but never bundle unrelated changes to do so. + +## Code style (observe, don't impose) + +- **No lint/format tooling is configured.** There is no `.pre-commit-config.yaml`, no `.ruff.toml`, + and no `[tool.ruff]`, `[tool.black]`, or `[tool.mypy]` section in `pyproject.toml`. Match the + surrounding code style manually. +- **One concern per module.** No cross-importing between route modules. +- **Routes access stores via `request.app.state`** (dependency injection), not by importing stores + directly. Check existing routes for the pattern. +- **Pico CSS utility classes only** for any template work. No other CSS framework. +- **htmx** (`hx-get`, `hx-target`, `hx-swap`) for dynamic partials. +- **Semantic HTML; ARIA labels** on interactive elements without visible text. +- **`async def`** route handlers; **`await`** all I/O. +- **Pydantic** request/response models. +- **Use `uv`** for everything: `uv sync --extra dev`, `uv run pytest`, `uv run python`. + +## Testing rules + +- Run targeted tests first, then the parallel gate. Never run the un-parallelised full suite + locally - it takes far too long. +- Canonical local gate: `uv run pytest tests/ --ignore=tests/e2e -n auto` +- Tests mirror module structure: `tests/test_agents.py` tests `routes/agents.py` +- Every fix gets a regression test. Every feature gets coverage. + +## Documentation gate + +A gate blocks PRs that add or remove certain feature code without a matching doc update +(configured in `docs/doc-gate.toml`). When a rule fires and there is genuinely nothing to +document, add a `Docs-Reviewed:` trailer to a commit message. + +## PR workflow + +1. Sync from upstream `dev` before branching +2. Create a branch: `feat/` or `fix/` +3. Code → test → commit (single conventional commit) → push +4. Open draft PR against `dev`; mark ready immediately (do NOT wait for CI) +5. If CLA check fails: surface to human, do NOT sign it yourself +6. Address review feedback with additional commits on the same branch + +## Commit messages + +Only these prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`. +No AI tool attribution. No `Co-authored-by` trailers. + +## Architecture boundaries (do not cross) + +- **Routes** register in `app.py`'s `create_app()`. They access stores via `request.app.state`. +- **Stores** use `aiosqlite` with `init()`/`close()`, attached in the lifespan. +- **Config** uses the `AppConfig` dataclass; YAML serialisation; async-locked saves via + `save_config_locked()`. +- **Frontend** is a React SPA in `desktop/`. Templates are minimal. +- **Cluster** handles worker registration, task routing, GPU leases. + +## For every procedure + +Load the `taos-development-skill` (SKILL.md) - it contains the full git workflow, testing +guide, common fix patterns, catalog contribution steps, and architecture map. diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..56210ec5c --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# CodeRabbit auto-reviews only the default branch (master) by default. +# We develop on dev (integration) and promote to master (stable), so +# enable auto-review on both — otherwise dev PRs go unreviewed until the +# dev -> master promotion. +reviews: + auto_review: + enabled: true + base_branches: + - master + - dev diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 000000000..7e2913d70 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,45 @@ +#!/bin/bash +# Documentation-drift gate: commit-msg escape hatch. +# +# The pre-commit hook already checked the staged diff. If it failed only +# because a rule wants a doc edit that isn't in this commit, this hook still +# accepts the commit when the message carries a non-empty +# "Docs-Reviewed: " trailer line. Matches the trailer docs/doc-gate.toml +# configures for CI (default: "Docs-Reviewed:"). +set -euo pipefail + +msg_file="$1" +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +trailer="$(python3 "$repo_root/scripts/check_doc_gate.py" print-trailer 2>/dev/null || echo 'Docs-Reviewed:')" + +if python3 scripts/check_doc_gate.py diff-gate --staged >/dev/null 2>&1; then + exit 0 +fi + +trim() { + local s="$1" + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" + printf '%s' "$s" +} + +while IFS= read -r line; do + line="$(trim "$line")" + case "$line" in + "$trailer"*) + rest="$(trim "${line#"$trailer"}")" + if [ -n "$rest" ]; then + exit 0 + fi + ;; + esac +done < "$msg_file" + +python3 scripts/check_doc_gate.py diff-gate --staged || true +echo "" +echo "This commit changes feature code without a matching doc update." +echo "Fix the docs listed above, or add a line to your commit message:" +echo " Docs-Reviewed: " +exit 1 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..1bc1e82ae --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,30 @@ +#!/bin/bash +# Documentation-drift gate: local pre-commit hook. +# +# Runs the same two checks CI runs, against the staged index. This is a +# convenience/fast-feedback layer only -- .github/workflows/doc-gate.yml is +# the authoritative gate and cannot be bypassed with --no-verify. +# +# Enable with: scripts/install-git-hooks.sh +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +fail=0 + +if ! python3 scripts/check_doc_gate.py invariants; then + fail=1 +fi + +if ! python3 scripts/check_doc_gate.py diff-gate --staged; then + fail=1 +fi + +if [ "$fail" -ne 0 ]; then + echo "" + echo "Fix the docs, or add a 'Docs-Reviewed: ' line to your commit message (the commit-msg hook accepts that). CI enforces this regardless of --no-verify." + exit 1 +fi + +exit 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..be496f89f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +version: 2 +updates: + # Python (controller + worker) -- managed by uv (pyproject.toml + uv.lock) + - package-ecosystem: "uv" + directory: "/" + target-branch: "dev" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + groups: + python-deps: + patterns: + - "*" + ignore: + # fastapi 0.137 regressed include_router so sub-router routes are not + # registered (#903); pyproject pins fastapi<0.137. Stop dependabot + # proposing the cap-raise until the regression is fixed upstream. + - dependency-name: "fastapi" + versions: [">=0.137"] + + # Desktop SPA (Vite/React) + - package-ecosystem: "npm" + directory: "/desktop" + target-branch: "dev" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + groups: + spa-deps: + patterns: + - "*" + ignore: + # Major bumps of the heavy UI libs (tldraw 4->5, lucide-react 0->1, + # tsparticles 3->4, etc.) carry breaking API changes and must be migrated + # deliberately (e.g. the tldraw engine migration, #75), not auto-merged. + # Minor/patch bumps still flow through normally. + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + # GitHub Actions workflows + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "dev" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index f9df610ed..4c727431e 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -37,6 +37,7 @@ jobs: fi - name: Add to Project if: steps.cfg.outputs.skip != 'true' + continue-on-error: true uses: actions/add-to-project@v2 with: project-url: ${{ vars.PROJECT_URL }} diff --git a/.github/workflows/build-agent-images.yml b/.github/workflows/build-agent-images.yml index 29039f30f..b55f5da8a 100644 --- a/.github/workflows/build-agent-images.yml +++ b/.github/workflows/build-agent-images.yml @@ -5,32 +5,159 @@ on: branches: [master, fix/agent-creation-flow] paths: - 'app-catalog/agents/openclaw/**' + - 'tinyagentos/scripts/install_hermes.sh' - '.github/workflows/build-agent-images.yml' repository_dispatch: types: [openclaw-fork-published] - workflow_dispatch: {} + workflow_dispatch: + inputs: + force: + description: 'Rebuild every base regardless of change detection' + type: boolean + required: false + default: false schedule: - # Weekly refresh for security updates - Mondays 03:00 UTC. - - cron: '0 3 * * 1' + # Weekly refresh - Mondays 04:17 UTC (off-peak, non-:00 to dodge the + # top-of-hour scheduler congestion that delays cron-triggered runs). + - cron: '17 4 * * 1' permissions: contents: write jobs: + # Change detection: decide which framework bases actually need a rebuild so a + # scheduled run does not burn CI on unchanged bases. The published images live + # under a rolling `rolling-images` tag in jaylfc/tinyagentos-images; the release + # job stamps a base-versions.json asset recording the upstream version baked + # into each base. Here we read that prior manifest and compare it against the + # CURRENT upstream versions. Anything that moved (or is missing from the prior + # manifest) gets rebuilt. + # + # Gating only applies to scheduled runs. push / repository_dispatch always + # rebuild everything (preserving the historical behaviour), and a manual + # workflow_dispatch with force=true rebuilds everything too. + detect: + runs-on: ubuntu-latest + outputs: + openclaw: ${{ steps.decide.outputs.openclaw }} + generic: ${{ steps.decide.outputs.generic }} + hermes: ${{ steps.decide.outputs.hermes }} + cur_openclaw: ${{ steps.upstream.outputs.openclaw }} + cur_hermes: ${{ steps.upstream.outputs.hermes }} + steps: + - name: Resolve current upstream versions + id: upstream + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + # openclaw: latest published npm version (the base bakes openclaw@latest). + OPENCLAW=$(npm view openclaw version) + # hermes: NousResearch hermes-agent default-branch commit (the base bakes + # the installer from main); the commit sha is the freshness signal. + HERMES=$(gh api repos/NousResearch/hermes-agent/commits/main --jq '.sha') + echo "openclaw=$OPENCLAW" >> "$GITHUB_OUTPUT" + echo "hermes=$HERMES" >> "$GITHUB_OUTPUT" + echo "current openclaw=$OPENCLAW hermes=$HERMES" + + - name: Read previously published base versions + id: prev + env: + GH_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }} + run: | + set -euo pipefail + # Best-effort: a missing tag/asset (first run) leaves the file absent, + # which the decision step treats as "rebuild everything". + gh release download rolling-images \ + --repo jaylfc/tinyagentos-images \ + --pattern base-versions.json \ + --output prev-base-versions.json --clobber || true + if [ -f prev-base-versions.json ]; then + cat prev-base-versions.json + else + echo "no prior base-versions.json (first run or never stamped)" + fi + + - name: Decide which bases to rebuild + id: decide + env: + EVENT: ${{ github.event_name }} + FORCE: ${{ github.event.inputs.force }} + CUR_OPENCLAW: ${{ steps.upstream.outputs.openclaw }} + CUR_HERMES: ${{ steps.upstream.outputs.hermes }} + run: | + set -euo pipefail + prev_field() { + # Echo a field from the prior manifest, or empty if absent. The + # field name is passed via argv (sys.argv[1]) so it is never spliced + # into the Python source. + [ -f prev-base-versions.json ] || { echo ""; return; } + python3 -c "import json,sys; print(json.load(open('prev-base-versions.json')).get(sys.argv[1],''))" "$1" 2>/dev/null || echo "" + } + + # Non-scheduled events keep the historical "build everything" behaviour. + # schedule is the only change-gated trigger; force overrides the gate. + if [ "$EVENT" != "schedule" ] || [ "$FORCE" = "true" ]; then + echo "openclaw=true" >> "$GITHUB_OUTPUT" + echo "generic=true" >> "$GITHUB_OUTPUT" + echo "hermes=true" >> "$GITHUB_OUTPUT" + echo "event=$EVENT force=$FORCE -> rebuilding all bases" + exit 0 + fi + + PREV_OPENCLAW=$(prev_field openclaw) + PREV_HERMES=$(prev_field hermes) + + [ "$CUR_OPENCLAW" != "$PREV_OPENCLAW" ] && OC=true || OC=false + [ "$CUR_HERMES" != "$PREV_HERMES" ] && HM=true || HM=false + + # generic = common deps only (node/python/build tools). These move + # rarely, so on the weekly schedule we refresh them only when forced + # (above) or on a monthly cadence: rebuild on the first Monday of the + # month so security/base-package updates still land without weekly cost. + DOM=$(date -u +%d) + if [ "$DOM" -le 07 ]; then GN=true; else GN=false; fi + + echo "openclaw=$OC" >> "$GITHUB_OUTPUT" + echo "generic=$GN" >> "$GITHUB_OUTPUT" + echo "hermes=$HM" >> "$GITHUB_OUTPUT" + echo "openclaw cur=$CUR_OPENCLAW prev=$PREV_OPENCLAW -> $OC" + echo "hermes cur=$CUR_HERMES prev=$PREV_HERMES -> $HM" + echo "generic (day-of-month $DOM, monthly cadence) -> $GN" + build: + needs: detect strategy: fail-fast: false matrix: + # Two list dimensions form the cross-product (3 bases x 2 arches = 6 + # jobs). base: which framework prerequisites to bake on top of the + # common deps. "openclaw" keeps its historical alias for back-compat; + # "generic" is taos-base (common deps only) for any framework; + # "hermes" adds the NousResearch hermes-agent prerequisites. + base: [openclaw, generic, hermes] + arch: [x64, arm64] + # include maps each arch to its runner without adding extra jobs + # (the keys here already exist as a matrix dimension). include: - - runner: ubuntu-latest - arch: x64 + - arch: x64 + runner: ubuntu-latest debarch: amd64 - - runner: ubuntu-24.04-arm - arch: arm64 + - arch: arm64 + runner: ubuntu-24.04-arm debarch: arm64 runs-on: ${{ matrix.runner }} + # Build every base on every run (the historical default). A job-level `if:` + # cannot reference the `matrix` context (it is evaluated before the matrix + # expands), so the previous per-base skip caused a workflow startup failure + # that ran 0 jobs on every push. The detect job still stamps current upstream + # versions for the release manifest; re-introducing per-base gating needs the + # matrix itself to be emitted by detect (fromJSON), not a job-level if. + env: + # Map the "openclaw"/"generic"/"hermes" base label to its published alias. + ALIAS: ${{ matrix.base == 'openclaw' && 'taos-openclaw-base' || (matrix.base == 'generic' && 'taos-base' || 'taos-hermes-base') }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 # Approach A: install incus in the runner and build the base image # by launching a real container. GitHub's ubuntu-latest and @@ -76,11 +203,9 @@ jobs: sudo incus exec build-base -- ip route || true sudo iptables -L FORWARD -n -v | head -20 || true - - name: Install dependencies + openclaw inside the base container - env: - ARCH: ${{ matrix.arch }} + - name: Install common dependencies inside the base container run: | - sudo incus exec build-base --env ARCH="$ARCH" -- bash -eux <<'BASH' + sudo incus exec build-base -- bash -eux <<'BASH' export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq --no-install-recommends \ @@ -92,24 +217,65 @@ jobs: curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt-get install -y -qq --no-install-recommends nodejs - # openclaw prebuilt tarball from the fork's rolling release. - TARBALL="/tmp/openclaw-taos-fork-${ARCH}.tgz" - curl -fsSL --max-time 120 \ - -o "$TARBALL" \ - "https://github.com/jaylfc/openclaw/releases/latest/download/openclaw-taos-fork-linux-${ARCH}.tgz" - file "$TARBALL" | grep -qE "gzip|compressed" - npm install -g --unsafe-perm --ignore-scripts "$TARBALL" - rm -f "$TARBALL" + # Recycle-bin scaffolding (install.sh Layer 1). + mkdir -p /var/recycle-bin/files /var/recycle-bin/info + chmod 1777 /var/recycle-bin /var/recycle-bin/files /var/recycle-bin/info + BASH + + - name: Bake openclaw into the base container + if: matrix.base == 'openclaw' + run: | + sudo incus exec build-base -- bash -eux <<'BASH' + export DEBIAN_FRONTEND=noninteractive + # Upstream OpenClaw from npm (latest) — no fork. The deploy-time + # install.sh also installs openclaw@latest, so this just warms the + # base image with a current build. + npm install -g --unsafe-perm openclaw@latest # Sanity check test -f /usr/lib/node_modules/openclaw/dist/entry.js \ || test -f /usr/lib/node_modules/openclaw/dist/entry.mjs + BASH - # Recycle-bin scaffolding (install.sh Layer 1). - mkdir -p /var/recycle-bin/files /var/recycle-bin/info - chmod 1777 /var/recycle-bin /var/recycle-bin/files /var/recycle-bin/info + - name: Bake Hermes prerequisites into the base container + if: matrix.base == 'hermes' + run: | + # Mirrors the heavy, agent-independent steps in + # tinyagentos/scripts/install_hermes.sh (uv, the NousResearch + # hermes-agent installer with --skip-setup, and the pyyaml/httpx + # deps the config patch needs). Per-agent config (.env, config.yaml, + # systemd unit, bridge) stays at deploy time -- only prerequisites + # are baked so a hermes deploy from this image needs no live install. + sudo incus exec build-base -- bash -eux <<'BASH' + export DEBIAN_FRONTEND=noninteractive + export PATH="/root/.local/bin:$PATH" - # Shrink the image footprint. + # uv (idempotent), matching install_hermes.sh. + if ! command -v uv >/dev/null 2>&1 && [ ! -x /root/.local/bin/uv ]; then + curl -LsSf https://astral.sh/uv/install.sh | sh + fi + echo 'export PATH="/root/.local/bin:$PATH"' >> /root/.bashrc + + # NousResearch hermes-agent prerequisites (no setup; config is per-agent). + curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh \ + | bash -s -- --skip-setup + + # Deps the deploy-time config patch step relies on. + pip3 install --break-system-packages --quiet pyyaml httpx + + # Sanity check: the hermes binary must be resolvable. + HERMES_BIN="" + for c in /root/.local/bin/hermes /root/.hermes/hermes-agent/.venv/bin/hermes /root/.hermes/hermes-agent/venv/bin/hermes; do + [ -L "$c" -o -x "$c" ] && HERMES_BIN="$c" && break + done + [ -z "$HERMES_BIN" ] && HERMES_BIN=$(command -v hermes || true) + [ -z "$HERMES_BIN" ] && { echo "ERROR: hermes binary not found after bake"; exit 2; } + echo "hermes baked at $HERMES_BIN" + BASH + + - name: Shrink the image footprint + run: | + sudo incus exec build-base -- bash -eux <<'BASH' apt-get clean rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb /tmp/* /root/.npm BASH @@ -119,30 +285,30 @@ jobs: ARCH: ${{ matrix.arch }} run: | sudo incus stop build-base - sudo incus publish build-base --alias taos-openclaw-base \ - description="taOS openclaw base image ($ARCH)" - sudo incus image export taos-openclaw-base "./taos-openclaw-base-linux-$ARCH" - ls -la "./taos-openclaw-base-linux-$ARCH"* + sudo incus publish build-base --alias "$ALIAS" \ + description="taOS $ALIAS image ($ARCH)" + sudo incus image export "$ALIAS" "./$ALIAS-linux-$ARCH" + ls -la "./$ALIAS-linux-$ARCH"* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: - name: taos-openclaw-base-${{ matrix.arch }} - path: taos-openclaw-base-linux-${{ matrix.arch }}.tar.gz + name: ${{ env.ALIAS }}-${{ matrix.arch }} + path: ${{ env.ALIAS }}-linux-${{ matrix.arch }}.tar.gz if-no-files-found: error release: - needs: build + needs: [detect, build] runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: - pattern: taos-openclaw-base-* + pattern: taos-*-base-* merge-multiple: true - name: List downloaded assets - run: ls -la taos-openclaw-base-linux-*.tar.gz + run: ls -la taos-*-base-linux-*.tar.gz - name: Compute build identifier id: id @@ -150,30 +316,62 @@ jobs: echo "ts=$(date -u +%Y%m%dT%H%M%S)" >> "$GITHUB_OUTPUT" echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + # Stamp the upstream versions baked into this publish so the next run's + # detect job can tell what moved. On a partial rebuild we still record the + # full current set (unchanged bases keep their current upstream value), + # which keeps the comparison honest across runs. + - name: Stamp base versions manifest + env: + CUR_OPENCLAW: ${{ needs.detect.outputs.cur_openclaw }} + CUR_HERMES: ${{ needs.detect.outputs.cur_hermes }} + run: | + set -euo pipefail + python3 - <<'PY' + import json, os + json.dump( + {"openclaw": os.environ["CUR_OPENCLAW"], + "hermes": os.environ["CUR_HERMES"]}, + open("base-versions.json", "w"), + indent=2, + ) + PY + cat base-versions.json + # Images are published to the separate jaylfc/tinyagentos-images repo so # the main tinyagentos Releases page stays reserved for user-facing taOS # releases. Consumers (taOS deployer) point at the images repo directly. # Reuses ADD_TO_PROJECT_PAT (classic repo-scoped PAT already configured # on this repo) — it has Contents: write on jaylfc/tinyagentos-images. - - uses: softprops/action-gh-release@v2 + - uses: softprops/action-gh-release@v3 with: repository: jaylfc/tinyagentos-images token: ${{ secrets.ADD_TO_PROJECT_PAT }} tag_name: rolling-images - name: "taOS openclaw base image (${{ steps.id.outputs.ts }} - ${{ steps.id.outputs.sha }})" + name: "taOS agent base images (${{ steps.id.outputs.ts }} - ${{ steps.id.outputs.sha }})" body: | - Pre-built LXC base image for openclaw agents. + Pre-built LXC base images for taOS agents. Consumers (taOS deployer): import once with - `curl -fsSL | incus image import - --alias taos-openclaw-base`. + `curl -fsSL | incus image import - --alias `. Assets: - - `taos-openclaw-base-linux-arm64.tar.gz` - - `taos-openclaw-base-linux-x64.tar.gz` + - `taos-openclaw-base-linux-{arm64,x64}.tar.gz` -- openclaw warmed + - `taos-base-linux-{arm64,x64}.tar.gz` -- generic common deps + - `taos-hermes-base-linux-{arm64,x64}.tar.gz` -- Hermes prerequisites Source commit: ${{ github.sha }} + # A scheduled change-detected run may rebuild only some bases, so not + # every tarball is present. action-gh-release keeps prior assets that + # are not re-supplied; fail_on_unmatched_files lets the missing ones + # pass through instead of failing the publish. + fail_on_unmatched_files: false files: | taos-openclaw-base-linux-arm64.tar.gz taos-openclaw-base-linux-x64.tar.gz + taos-base-linux-arm64.tar.gz + taos-base-linux-x64.tar.gz + taos-hermes-base-linux-arm64.tar.gz + taos-hermes-base-linux-x64.tar.gz + base-versions.json make_latest: true prerelease: false diff --git a/.github/workflows/build-neko-cdp-image.yml b/.github/workflows/build-neko-cdp-image.yml new file mode 100644 index 000000000..b7d31f882 --- /dev/null +++ b/.github/workflows/build-neko-cdp-image.yml @@ -0,0 +1,110 @@ +name: Build Neko CDP image + +# Builds and pushes ghcr.io/jaylfc/taos-neko-cdp for arm64 (RK3588 / Pi) + +# amd64 (x86 nodes). Triggered on changes to the Dockerfile or on demand. +# +# The CDP-enabled image is the option C foundation: Chromium >=148 from +# trixie-security + DeveloperToolsAvailability=0 + CDP flags bound to +# 127.0.0.1:9222 + --enable-dev-shm-usage (requires --shm-size=4g at run). + +on: + push: + branches: [master, dev] + paths: + - 'app-catalog/streaming/neko-browser/Dockerfile.cdp' + - '.github/workflows/build-neko-cdp-image.yml' + workflow_dispatch: {} + schedule: + # Weekly rebuild to pick up trixie-security Chromium updates (Tuesdays 04:00 UTC). + - cron: '0 4 * * 2' + +permissions: + contents: read + packages: write + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + platform: linux/amd64 + - runner: ubuntu-24.04-arm + platform: linux/arm64 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v7 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract platform slug + id: slug + run: echo "slug=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" + + - name: Build and push per-arch digest + id: build + uses: docker/build-push-action@v6 + with: + context: app-catalog/streaming/neko-browser + file: app-catalog/streaming/neko-browser/Dockerfile.cdp + platforms: ${{ matrix.platform }} + push: true + outputs: type=image,name=ghcr.io/jaylfc/taos-neko-cdp,push-by-digest=true,name-canonical=true + cache-from: type=gha,scope=neko-cdp-${{ steps.slug.outputs.slug }} + cache-to: type=gha,mode=max,scope=neko-cdp-${{ steps.slug.outputs.slug }} + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - uses: actions/upload-artifact@v7 + with: + name: digest-${{ steps.slug.outputs.slug }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + needs: build + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - uses: actions/download-artifact@v8 + with: + pattern: digest-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push multi-arch manifest + working-directory: /tmp/digests + run: | + digests=$(ls | awk '{print "ghcr.io/jaylfc/taos-neko-cdp@sha256:" $1}') + docker buildx imagetools create \ + -t ghcr.io/jaylfc/taos-neko-cdp:latest \ + -t ghcr.io/jaylfc/taos-neko-cdp:${{ github.sha }} \ + $digests + + - name: Inspect final manifest + run: docker buildx imagetools inspect ghcr.io/jaylfc/taos-neko-cdp:latest diff --git a/.github/workflows/build-neko-rk3588-image.yml b/.github/workflows/build-neko-rk3588-image.yml new file mode 100644 index 000000000..8dd9ab8ee --- /dev/null +++ b/.github/workflows/build-neko-rk3588-image.yml @@ -0,0 +1,48 @@ +name: Build Neko RK3588 image + +# Builds the RK3588 VPU-encode neko image (MPP + gstreamer-rockchip compiled +# from source against trixie GStreamer 1.26) on a GitHub arm64 runner, so the +# Pi is never used for the build. The Pi only does the final validation run. +# On a PR the image is built but NOT pushed (compile validation only); on +# dev/master it is built and pushed to GHCR. +on: + push: + branches: [master, dev] + paths: + - 'app-catalog/streaming/neko-browser/Dockerfile.rk3588' + - '.github/workflows/build-neko-rk3588-image.yml' + pull_request: + paths: + - 'app-catalog/streaming/neko-browser/Dockerfile.rk3588' + - '.github/workflows/build-neko-rk3588-image.yml' + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v3 + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build (push only on dev/master) + uses: docker/build-push-action@v6 + with: + context: app-catalog/streaming/neko-browser + file: app-catalog/streaming/neko-browser/Dockerfile.rk3588 + platforms: linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: | + ghcr.io/jaylfc/taos-neko-rk3588:latest + ghcr.io/jaylfc/taos-neko-rk3588:${{ github.sha }} + cache-from: type=gha,scope=neko-rk3588 + cache-to: type=gha,mode=max,scope=neko-rk3588 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c0070908..191e08332 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [master] + branches: [master, dev] pull_request: - branches: [master] + branches: [master, dev] schedule: # 06:00 UTC daily run keeps the 3.11 leg honest without making every # PR wait 14 extra minutes on it. 3.11 is consistently ~2x slower @@ -13,8 +13,27 @@ on: # slow test), so a kernel-level interpreter perf issue rather than # something we can fix in our suite. - cron: "0 6 * * *" + release: + # Publishing a version release attaches its own prebuilt desktop bundle, so + # each release is a self-contained packaged download (see spa-build below). + types: [published] + +# Cancel superseded runs of the same ref so a rapid series of pushes to a PR +# branch does not stack up and starve the small self-hosted runner pool (this +# bit us once: three superseded runs queued behind each other on two runners). +# A PR push event carries ref refs/pull//merge, so successive pushes to the +# same PR share a group and the older run is cancelled. master/dev are excluded +# from cancellation: every promotion's CI (and its bundle publish) must finish. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/dev' }} jobs: + # The test matrix runs on GitHub-hosted runners. taOS is a public repo, so + # hosted Actions are free with high concurrency (~20 parallel jobs); many PRs + # run at once instead of queueing behind the 2 self-hosted boxes. The VPS + + # Fedora runners stay free for the kilo lane and GPU/bench work. + test: runs-on: ubuntu-latest # 45 min cap accommodates the cron run that includes 3.11 (~30 min); @@ -28,51 +47,69 @@ jobs: python-version: ${{ github.event_name == 'schedule' && fromJSON('["3.11", "3.12", "3.13"]') || fromJSON('["3.12", "3.13"]') }} steps: - - uses: actions/checkout@v5 - - - name: Set up Python - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + + # No actions/setup-python: it only ships prebuilt CPython for GitHub's + # ubuntu image, so on the self-hosted Fedora runner it fails with + # "version X not found for Fedora". uv provisions Python itself from + # distro-agnostic standalone builds, which works on Fedora, the Ubuntu + # VPS, and GitHub-hosted runners alike. + - name: Set up uv + uses: astral-sh/setup-uv@v7 with: - python-version: ${{ matrix.python-version }} + enable-cache: true - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + - name: Provision Python via uv + run: uv python install ${{ matrix.python-version }} + # Install the exact versions recorded in uv.lock (--frozen errors out + # if the lock is stale rather than silently re-resolving). This is the + # reproducibility guarantee: dependency versions come from the lock, + # not from a fresh unpinned resolve that can pull in a regressed + # release (see the fastapi 0.137 incident, #903). - name: Install dependencies - run: pip install -e ".[dev]" + run: uv sync --frozen --extra dev --python ${{ matrix.python-version }} # SPA bundle is stubbed by tests/conftest.py — see pytest_configure. # The real build is exercised in the spa-build job below. - name: Run tests - run: pytest tests/ -v --tb=short --ignore=tests/e2e + # Run across all runner cores. The serial suite (4845 tests) took + # ~22 min; -n auto cuts it to a few minutes so "merge on green" is + # practical. Dropped -v so xdist worker output stays readable. + run: uv run --no-sync pytest tests/ --tb=short --ignore=tests/e2e -n auto - name: Verify app starts - run: python -c "from tinyagentos.app import create_app; print('OK')" + run: uv run --no-sync python -c "from tinyagentos.app import create_app; print('OK')" lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.12" + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + - name: Install dependencies - run: pip install -e ".[dev]" + run: uv sync --frozen --extra dev --python 3.12 - name: Check for syntax errors - run: python -m compileall tinyagentos/ -q + run: uv run --no-sync python -m compileall tinyagentos/ -q spa-build: runs-on: ubuntu-latest + # Needs write access to manage the rolling 'bundle-latest' release below. + permissions: + contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Node uses: actions/setup-node@v4 @@ -86,3 +123,52 @@ jobs: cd desktop npm ci --silent npm run build + + # Gate the desktop vitest suite (~1,900 tests, ~25s). A small set of + # suites that drift against in-progress redesigns (#59, #66) or are + # order-dependent are quarantined in vite.config.ts (test.exclude, tagged + # #114); un-exclude each as its owning work lands. + - name: Run desktop tests (vitest) + env: + # Headroom for the jsdom suite so a worker is not OOM-killed on the + # 2-core runner (the fork children also get this via poolOptions in + # vite.config.ts). One automatic retry absorbs a rare transient + # worker death so a flake never blocks a release. + NODE_OPTIONS: --max-old-space-size=4096 + run: | + cd desktop + npx vitest run || (echo "vitest failed once, retrying..." && npx vitest run) + + # On master, publish the freshly-built SPA as a prebuilt bundle so the + # installer can download it instead of running the memory-heavy vite build + # locally (which OOMs on small machines like an 8GB WSL and used to leave + # installs silently stuck on the old UI). The bundle is keyed by the git + # tree SHA of desktop/, so it stays valid across every commit that doesn't + # touch the frontend. Rolling 'bundle-latest' prerelease, assets clobbered. + - name: Publish prebuilt desktop bundle + if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'release' + env: + GH_TOKEN: ${{ github.token }} + run: | + tree=$(git rev-parse HEAD:desktop) + tar -C static -czf desktop-bundle.tar.gz desktop + printf '%s\n' "$tree" > desktop-tree.txt + # Integrity digest the installer verifies before extracting. + sha256sum desktop-bundle.tar.gz | awk '{print $1}' > desktop-bundle.sha256 + notes="Auto-built SPA bundle, matching desktop/ tree ${tree}. The installer downloads this when the tree matches, verifies desktop-bundle.sha256, then skips the local vite build." + # Always keep the rolling 'bundle-latest' current (this is what the + # installer fetches by default). + if gh release view bundle-latest >/dev/null 2>&1; then + gh release upload bundle-latest desktop-bundle.tar.gz desktop-tree.txt desktop-bundle.sha256 --clobber + gh release edit bundle-latest --notes "$notes" + else + gh release create bundle-latest --prerelease \ + --title "Prebuilt desktop bundle (rolling)" --notes "$notes" \ + desktop-bundle.tar.gz desktop-tree.txt desktop-bundle.sha256 + fi + # On a version release, also attach the bundle to that release tag so + # each published version is a self-contained packaged download. + if [ "${{ github.event_name }}" = "release" ]; then + gh release upload "${{ github.event.release.tag_name }}" \ + desktop-bundle.tar.gz desktop-tree.txt desktop-bundle.sha256 --clobber + fi diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 000000000..12cead4da --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,44 @@ +name: CLA Assistant + +# Contributor License Agreement gate (CLA Assistant Lite, self-hosted — no +# third-party service). Contributors sign ONCE by commenting the exact phrase +# below on their PR; signatures are stored as JSON in the `cla-signatures` +# branch of this repo (nothing leaves GitHub). See CLA.md / CONTRIBUTING.md. +# Complements the DCO sign-off check (DCO = per-commit attestation; CLA = +# one-time legal agreement). +# +# Scoped to PRs targeting `dev` (incoming contributions); the internal +# dev->master release promotion is not gated here. + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, synchronize] + branches: [dev] + +permissions: + contents: write + pull-requests: write + statuses: write + +jobs: + cla: + runs-on: ubuntu-latest + steps: + - name: CLA Assistant + if: >- + (github.event.comment.body == 'recreate-cla' || + github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') + || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: 'https://github.com/jaylfc/taOS/blob/master/CLA.md' + # Dedicated branch for signature records — keeps dev/master clean and + # avoids the protected-branch push restrictions on master. + branch: 'cla-signatures' + # Founder + project bots don't sign the CLA. + allowlist: 'jaylfc,naira,dependabot[bot]' diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 000000000..55bd280de --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,31 @@ +name: Dependabot auto-merge + +# Auto-merges grouped patch/minor Dependabot PRs once required checks pass. +# Majors and Python (uv) updates are left for manual review. +on: pull_request_target + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + if: ${{ github.actor == 'dependabot[bot]' }} + runs-on: ubuntu-latest + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for npm / actions patch & minor + if: >- + (steps.meta.outputs.package-ecosystem == 'npm' || + steps.meta.outputs.package-ecosystem == 'github_actions') && + (steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor') + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/doc-gate.yml b/.github/workflows/doc-gate.yml new file mode 100644 index 000000000..0fc9a33cc --- /dev/null +++ b/.github/workflows/doc-gate.yml @@ -0,0 +1,45 @@ +name: Doc drift gate + +# Authoritative documentation-drift gate. Blocks PRs that change feature code +# (a desktop app, an API route module, an installer, a catalog manifest) +# without touching the doc that covers it, unless a commit in the PR carries +# a "Docs-Reviewed: " trailer. See docs/doc-gate.toml for the rules and +# scripts/check_doc_gate.py for the implementation. This job is authoritative +# regardless of local hooks (--no-verify does not skip it). + +on: + pull_request: + branches: [master, dev] + +jobs: + doc-gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Fetch base branch + env: + BASE_REF: ${{ github.base_ref }} + run: git fetch origin "$BASE_REF" + + - name: Invariants (Layer A) + run: python scripts/check_doc_gate.py invariants + + - name: Schema-migration guard (#1865) + run: python scripts/check_schema_migrations.py + + - name: Diff gate (Layer B) + env: + BASE_REF: ${{ github.base_ref }} + run: python scripts/check_doc_gate.py diff-gate --base "origin/$BASE_REF" + + - name: Managed backend manifest lint + run: | + python -m pip install --quiet pyyaml + python scripts/check_manifests.py managed-lint diff --git a/.github/workflows/fork-audit.yml b/.github/workflows/fork-audit.yml index 04332eff2..9b860b517 100644 --- a/.github/workflows/fork-audit.yml +++ b/.github/workflows/fork-audit.yml @@ -14,7 +14,7 @@ jobs: issues: write contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -45,7 +45,7 @@ jobs: PY - name: Upload report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: fork-audit path: fork-audit.json diff --git a/.github/workflows/pr-base-guard.yml b/.github/workflows/pr-base-guard.yml new file mode 100644 index 000000000..ddf32d3d2 --- /dev/null +++ b/.github/workflows/pr-base-guard.yml @@ -0,0 +1,38 @@ +name: PR base branch guard + +# Friendly nudge when a PR is opened against master. master is the stable +# branch that installs track; contributions belong on dev. We don't auto-close +# or auto-retarget — just leave a one-time comment so the author can switch the +# base (the commits and review carry over). + +on: + pull_request_target: + types: [opened] + branches: [master] + +permissions: + pull-requests: write + +jobs: + nudge: + runs-on: ubuntu-latest + steps: + - name: Comment with retarget guidance + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v7.1.0 + with: + script: | + const body = [ + "👋 Thanks for the PR! This one targets **`master`**, which is our", + "stable branch (it's what live installs track). Please retarget it to", + "**`dev`** — click **Edit** next to the PR title and change the base", + "branch dropdown from `master` to `dev`. Your commits and any review", + "carry over, nothing is lost.", + "", + "See [CONTRIBUTING.md](../blob/master/CONTRIBUTING.md) for the branch model.", + ].join("\n"); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body, + }); diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 53e680a61..27de99af3 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -15,7 +15,7 @@ jobs: dependency-audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.gitignore b/.gitignore index 44ebc8d7d..bb7dfe4d3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ data/.setup_complete data/.auth_password data/.auth_sessions data/.auth_local_token +data/mesh_credentials.json data/agents.json data/images/ data/videos/ @@ -88,3 +89,40 @@ models/minilm-onnx/minilm-l6-rk3588.rknn screenshots.zip data/.auth_user.json data/browser_cookie_key.hex + +# Vite/TS incremental build artifact — regenerated on every build, must not be tracked +# (a tracked copy dirties the tree and blocks the in-app git-pull update). +desktop/tsconfig.tsbuildinfo + +# never track installed deps (root-level node_modules can appear when a tool is +# run from the repo root; ignore it everywhere so it is never swept into a commit) +node_modules/ +desktop/node_modules + +# There is no root node project: the SPA lives in desktop/. A stray root +# package.json invites `npm install` at the repo root, which creates the +# root node_modules that lane worktrees then sweep into commits (see #1134). +# Lanes sometimes add these as a workaround for running vitest from root; the +# gate runs vitest from desktop/, so they are pure cruft. Ignore them, anchored +# to the repo root so desktop/package.json and desktop/vitest.config are kept. +/package.json +/package-lock.json +/vitest.config.ts +/vitest.config.js + +# Local-only operational playbook (contains LAN IP + ops detail; agents read it from the working tree) +docs/AGENT_HANDOFF.md +docs/audit/ +.understand-anything/ +docs/agent-jobs/ +.design/ + +# Update rollback target (written by the updater, never tracked) +.taos-rollback +docs/STATUS.md + +# Runtime restart marker (written by Settings-update flow; never track) +data/pending-restart.json +# Stray dev screenshot artifacts +desktop-initial.png +venv/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..d39b0e2d1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,467 @@ +# Changelog + +All notable changes to taOS are documented in this file. + +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +Versions follow semver beta: `1.0.0-beta.N`, bumped on each dev->master promotion. + +## [Unreleased] + +## [1.0.0-beta.40] - 2026-07-11 + +### Added +- Approving an external agent for the project-tasks scope now asks which project to bind it to, with an inline option to create a new one, and the approval adds the agent as a member of that project so it shows up in the project's Members and joins the project channel. Granting project-tasks without picking a project is refused, so an agent's own request can never bind it to a project you did not choose (#1777). +- taOS notifications can now reach your phone as native web-push. Install the PWA and allow notifications, and access requests and other alerts arrive as OS banners even when the app is closed, delivered best effort so a push failure never blocks the in-app feed (#1778). + +### Fixed +- Agent chat now sizes its history budget to the model's real context window instead of a fixed limit, so a small-context local model no longer overflows and loops. When several agents share one reply the budget follows the smallest known window, and any unknown window keeps the previous safe default (#1740, #1779). + +## [1.0.0-beta.39] - 2026-07-10 + +### Added +- Game Studio can now generate textures and sprites from a text prompt using a ComfyUI backend on a discrete-GPU worker, writing the image straight into the game's file set. On a host with no capable GPU the panel shows a clear "needs a GPU worker" state instead of failing (#1773). +- taOSgo cluster-join now completes the network side: a controller joins the account mesh over the system tailscale against the Headscale server, and the per-host service tokens the join returns are persisted host-locally (owner-only) so publishing and passkey fetches keep working after a join (#1770, #1772). +- Agents post to the coordination bus as themselves through an authenticated send proxy, so a message carries the agent's own identity and cannot be spoofed as another account (#1768). +- The cluster advertises the models a node can serve from its backend manifest, and installing a backend now registers it as a managed, node-local service that can be started, stopped, and health-checked per node (#1756, #1758, #1760, #1762). +- An approved external agent can be granted a least-privilege project-tasks scope to read and drive a single project's task board (claim, close, comment) with its own token, scoped so it can never reach another project (#1774). + +### Fixed +- Backend and worker robustness: the model VRAM check reserves atomically before a load so two loads cannot race the same memory, a malformed backend manifest no longer crashes the worker, and the VRAM guard fails closed rather than open on a probe error (#1725, #1767). +- The RK3588 (RKLLM) install path pins the rkllama server to the verified 1.3.0 reference and guards the fork patches, and a live rkllama port is treated as installed only when it is a managed service (#1755, #1764). +- Fixed six agent-framework catalog manifests that referenced install scripts which did not exist at the repo root (#1694). + +## [1.0.0-beta.38] - 2026-07-08 + +### Added +- The Agent conversation window now shows a live activity banner when a response is slow or has stalled. If no output arrives for a while it surfaces a "taking longer than usual" hint, escalating to a "may be stalled" warning with a shortcut to restart the AI services, so a stuck generation no longer looks like a frozen window. Requested by @mandresve (#1741). +- A "Restart AI Services" action in the Activity tab restarts the local inference backends (rkllama and qmd) without bouncing the controller or your agents, for recovering a stalled model on an edge device without a terminal. It asks for confirmation first and reports the result per service. Requested by @mandresve (#1743). + +### Fixed +- The Agent conversation window now scrolls when a conversation is longer than the visible area, so long responses and logs stay reachable instead of pushing earlier content out of view. Reported by @mandresve (#1742). +- The Agents view is now readable on a phone. Archived agent rows stack so the agent name is no longer squeezed to a single character, and the header condenses so nothing truncates. +- Several other app views now reflow correctly on a phone instead of overflowing: the Images studio edit and library panels, the Tasks and Observatory lists, the add-agent dialog, and the Mail reading toolbar. + +## [1.0.0-beta.37] - 2026-07-08 + +### Fixed +- An embedding model can no longer be assigned as an agent's chat model. Assigning one (for example qwen3-embedding-0.6b) now returns a clear error instead of silently accepting it and producing repeating, off-topic output, because an embedding model cannot do chat completion. Reported by @mandresve (#1740). +- A local RK3588 (RKLLM) model whose context window is too small for the agent harness now surfaces a non-blocking warning when it is assigned, so an over-small context (for example 4096 tokens) is flagged rather than silently truncating the agent prompt and looping (#1740). +- The RK3588 (RKLLM) backend now returns a structured context-overflow error when a prompt exceeds the model context, so a client can tell a context overflow apart from invalid input or a server fault instead of getting a bare 400. Reported by @mandresve (#1738). + +### Added +- A `taos recover-password` command for offline recovery of a local account password when the admin is locked out of the web login. It resets the password directly in the auth store (single-user, named multi-user, pending, or legacy) and revokes that account's sessions. + +## [1.0.0-beta.36] - 2026-07-08 + +### Fixed +- The RK3588 (RKLLM) install path no longer produces a broken rkllama service after an update. A previous pin bumped the rkllama server to a build that had dropped its startup preload flag, so the service failed to start on existing installs. The pin now points at a server that restores preload and adds a pre-flight context-length check, so a prompt longer than the model context returns a clear error instead of crashing the worker. Reported by @mandresve (#1730, #1732). +- SearXNG installs now enable the JSON output format by default, so an agent can use the local SearXNG as a search backend without hand-editing its settings (#969). +- Fixed a chat initialization error where a temporal-dead-zone reference could stop the conversation view from loading (#1720). + +### Security +- Per-app secret files created during install are now written owner-only (0600) and regenerated if a prior write left them empty or malformed, so a session-signing key can no longer be left world-readable on disk (#1734). + +### Changed +- taOS is now dual-licensed as AGPL-3.0-or-later plus a commercial option. The public core is AGPL-3.0, an OSI-approved license, with a separate commercial license available for uses that need different terms (#1721). + +## [1.0.0-beta.35] - 2026-07-07 + +### Fixed +- The taOS Agent now returns output when it runs on a local RK3588 (RKLLM) model. Two gaps combined to make the agent report "the agent backend returned no output": the local rkllama backend was registered under a name that did not match the RKLLM model manifests, so the model was never exposed to the LiteLLM proxy the agent calls, and the pinned rkllama server had a Python version incompatibility that made its chat endpoint fail before inference. The backend name now matches on load (existing installs self-heal on update, no reinstall) and the rkllama pin is bumped to the fixed server. Reported by @mandresve (#1710). +- Agent message bubbles are now selectable and show a copy button on hover, so an agent's reply can be copied without dragging across the whole conversation (#835). +- The desktop top bar now shows a badge when an update is available, and clicking it opens the Updates pane in Settings (#855). +- taOS now surfaces a clear message when your local branch has diverged from its tracked remote, instead of a confusing update-check state (#841). + +### Security +- Cluster GPU-lease endpoints now require admin authentication and validate their inputs, so a non-privileged LAN client cannot claim, release, or probe another node's GPU leases (#1675 follow-up). +- The bare-metal worker backend runs under process supervision with hardened handling, and container environment values now reject embedded newlines (#1691). +- Agent delegation and the org model were hardened against a stale-permission carry-over and a reporting-lock race (#174, #1661, #1662). + +## [1.0.0-beta.34] - 2026-07-07 + +### Fixed +- Downloaded RK3588 (rkllama) models now appear after a normal update, with no reinstall. rkllama's background service kept saving models to its old location even after the controller updated, and taOS did not look there. taOS now reads where the rkllama service actually writes (from its systemd unit) and scans that directory, so an existing install's downloaded models show up in the Models list. Reported by @mandresve (#1548). +- The local RKLLM provider no longer shows Error because of a stale port. Installs seeded before the taOS default port moved to 7833 kept a localhost:8080 provider URL, so the Providers page and model discovery polled a dead port. That URL is now healed to 7833 on load. Reported by @mandresve (#1697). +- The taOS Agent chat no longer reports "runtime unavailable" when opencode was installed by the operator under their own home. The controller runs as an unprivileged service user that could not see an opencode installed in a different user's home, so it now also checks a TAOS_OPENCODE_BIN override and trusted system locations. Reported by @mandresve (#1616). +- When an agent's model change cannot re-scope its per-agent key, the stale key is discarded and that discard is now persisted, so the next deploy correctly falls back to the master key instead of reusing a key scoped to the old model (#1686). + +### Security +- opencode discovery only probes trusted locations (system paths, the service user's own home) and an explicit operator override, never arbitrary users' home directories, so a non-privileged user cannot plant a binary the service would run (#1616). + +## [1.0.0-beta.33] - 2026-07-06 + +### Fixed +- Downloaded RK3588 (rkllama) models now appear in the Models list. rkllama downloaded and registered models correctly, but wrote them to its own directory that taOS never scanned, so a model that finished downloading never showed up. taOS now points rkllama at the unified model directory it already scans, and migrates any models you have already downloaded into it on upgrade, so nothing needs re-downloading. Reported by @mandresve (#1548). +- rkllama install failures now surface the real cause (e.g. HuggingFace unreachable) instead of a generic "model not registered", so a failed download is self-diagnosing (#1548). +- Scheduled backups (and any other scheduled task) actually run now. The scheduler stored the cron expression but had no execution engine, so due tasks never fired (#165). + +### Added +- Cluster GPU-lease coordination: agents claim and release a worker's GPU atomically over the A2A bus, and `/api/cluster/workers` now reports real-time free/used VRAM, so shared-hardware model loads on one node no longer collide. Archived models are promoted automatically when compatible hardware joins the cluster (#893, #333). + +### Changed +- Backends (rkllama and other GPU/NPU model servers) run as the unprivileged `taos` service user with the device-group access they need, rather than root. +- Non-admin users no longer see system-settings panels, a UX follow-up to the settings-router access gate (#163). +- The documentation gate no longer trips on test-only files (#171). +- Cleared seven Dependabot alerts by pinning `lodash-es`, `uuid`, and `nanoid` (#173), and added a safe cleanup policy for stale agent worktrees (#172). + +## [1.0.0-beta.32] - 2026-07-06 + +### Fixed +- Weather app location search works again. The app looks up cities and forecasts from the open-meteo API, but the Content-Security-Policy only allowed same-origin connections, so the browser silently blocked every lookup and the search field did nothing. The two open-meteo origins are now allowed. Reported by @mandresve (#1668). +- Desktop wallpaper no longer resets on login for anyone using a theme that declares a default wallpaper. Your explicitly chosen wallpaper is now authoritative on restore and is not overridden by the theme's default. Reported by @mandresve (#1603). + +### Added +- Groundwork for the native iOS and watchOS client: a per-user device registry with revocable per-device scoped tokens, device management endpoints, a device-token auth path, and an APNs push sender (inactive until configured). No user-facing app yet; this is the server foundation the mobile app will build on (#1671). + +### Changed +- Governance: answering a gated Decision no longer sends the asking agent a duplicate message, and delegation and retry replies now state honestly whether the action actually completed (#174). + +## [1.0.0-beta.31] - 2026-07-06 + +### Fixed +- Model downloads on RK3588 (rkllama backend) really do show progress now. The earlier fix assumed the rkllama pull stream was JSON, but it streams plain-text percentage lines; taOS now parses those (and still handles the JSON form), so the bar advances instead of sitting at 0%. Reported by @mandresve (#1648). + +### Added +- Agent org model: agents can carry a role and title and a reporting line (who reports to whom), viewable as an org tree, with cycle-safe validation. Agents can delegate a task to another agent through the existing governance gate, so a delegation is allowed, denied, or sent to the Decisions inbox for approval like any other gated action (#161). +- Agent heartbeat loop (opt-in, off by default): when enabled, taOS periodically wakes each idle running agent with its next ready task and that task's goal context, so agents pull and act on their queue on a schedule. Enable it with the `agent_heartbeat_enabled` setting (#164). + +### Security +- Bumped cryptography to 48.0.1 to clear a high-severity OpenSSL advisory in the bundled wheels; the new version keeps wheels for every supported platform (including Intel Mac and 32-bit Windows), so no platform loses coverage (#1653). + +## [1.0.0-beta.30] - 2026-07-05 + +### Fixed +- Model downloads on RK3588 (rkllama backend) no longer sit at 0% forever. Downloads that install through rkllama now report real progress as the weight is pulled, and a completed model is recorded and shown as installed immediately instead of looking stuck. Reported by @mandresve (#1648). + +### Added +- Agent governance: per-agent LLM budget hard-stops. You can set a spend cap per agent; once an agent reaches its cap its model calls are rejected with a clear over-budget error before any request is dispatched. Spend accrues from real usage and the cap is settable and resettable via an admin API (#160). + +### Security +- Updated frontend dependencies to clear known advisories (lodash-es code-injection and prototype-pollution, uuid, nanoid) via a grouped lockfile bump (#1655). + +## [1.0.0-beta.29] - 2026-07-05 + +### Added +- Coding Studio has a real live preview: it renders your workspace's actual index.html in a sandboxed iframe, with local CSS, JS and images inlined and nothing fetched over the network, plus working desktop/tablet/phone size toggles. This replaces the old static mock. +- Music Studio can bounce a song to a downloadable WAV file, rendered offline via Tone.Offline so the export matches what you hear. +- Game Studio ships four new playable starter templates (endless runner, neon snake, sky tapper, asteroid miner), each a self-contained canvas game you can generate from, edit and share. + +## [1.0.0-beta.28] - 2026-07-05 + +### Added +- App Studio is now real: describe an app in plain words and the taOS agent generates it, packages it, runs it through the security analyzer, installs it, and shows it running live in a sandboxed window, all in one flow. Generated apps ship as sandboxed web apps with no elevated permissions. +- Licensing transparency: services whose model weights are non-commercial (MusicGen, MusicGPT, FLUX-Fill) now carry accurate weight-license metadata (the code license was already MIT, but the weights are CC-BY-NC), the Store shows a "Non-commercial weights" badge, and installing such a service now requires a one-time license acceptance. Nothing non-commercial installs silently. + +### Fixed +- Video Studio generation is no longer a multi-minute blocking request that could time out or fail on a disconnect. Generation now runs as a background job: you get an immediate job id, the UI polls for progress, and a failed job always ends in a clear error state instead of hanging. + +## [1.0.0-beta.27] - 2026-07-04 + +### Added +- Office Suite is now complete: a Database view joins Write, Calc and Presentations, so you can build simple tables (typed columns, rows, inline editing) that save alongside your other documents. +- Office AI: Write now has working Rewrite, Shorten, Continue and Change tone actions, and Calc has a working "Ask your data" panel, both powered by your taOS agent. AI edits in Write are a single undo step (one Ctrl+Z restores your original text), and untrusted document/spreadsheet content is passed to the model as clearly delimited data, not instructions. +- Web Studio can now generate a real website from a prompt via your taOS agent (with a safe fallback to templates), previews it in a sandboxed frame, and shares or installs it as a taOS app. +- Music Studio is now a playable browser DAW: a Tone.js audio engine with a multi-track timeline, piano-roll editor, drum step-sequencer and mixer, songs that save to your cluster, and MIDI/JSON export. +- Design Studio designs now save: open, rename and delete your canvases, which persist across sessions (the editor itself was already fully featured). + +### Fixed +- Images Studio no longer misleads you when the Quality edit tier is unavailable: it now tells you when a request was served by the fast eraser (prompt ignored) and disables the Quality option when its model is not installed, instead of silently downgrading. Reported behavior aligned with what the backend actually does. +- Settings: changing a Dock setting no longer resets your wallpaper to the default on the next login. Partial settings saves now merge instead of overwriting the rest of your preferences. Reported by @mandresve (#1603, #1601). + +## [1.0.0-beta.26] - 2026-07-04 + +### Added +- Projects now give a task its full relational context: an agent sees the goal ancestry behind a task (its project and parent-task chain) and what is blocking it, and that "why" is surfaced in the task view and injected into the assigned agent's context when the task becomes ready or is claimed (#158). +- Routines & Schedules: a project can now run recurring or triggered routines (cron schedule, inbound webhook, or manual/API trigger) that automatically create a task on the board and wake the assigned agent. Managed from a Routines tab in the Projects app; webhook triggers are per-token, rate-limited, and owner-only (#159). +- Agent governance (first slice): execution policies decide whether a deployed agent's tool call is allowed, denied, or needs human approval. By default the sensitive actions (host code execution and arbitrary outbound HTTP) require an approval that lands in the Decisions inbox; once approved, a short-lived grant lets the agent proceed. Policies are a global default with per-agent overrides; admin operators are never gated (#160). + +## [1.0.0-beta.25] - 2026-07-04 + +### Security +- Fixed a high-severity missing-authorization vulnerability (GHSA-47g9-fwwp-hrfp, CWE-862) in the system settings router: every `/api/config` and `/api/settings/*` endpoint was served with no admin check, so an authenticated non-admin user could read and overwrite the full system configuration and trigger privileged actions (`git pull` + dependency reinstall + service restart, and switching the tracked update channel), causing configuration corruption or denial of service for the whole instance. The settings router now requires an admin session or the host local token; non-admin sessions are rejected with 403 before any handler runs. Reported by EQSTLab. + +## [1.0.0-beta.24] - 2026-07-04 + +### Fixed +- taOS Agent: the agent chat no longer wrongly reports "runtime unavailable" when opencode is installed system-wide. taOS now finds opencode on the PATH and at its default install location, and when the runtime genuinely can't start it shows the real error instead of a misleading generic message. The taOS Agent dialog also no longer opens as a blank window when launched from the Launchpad or Dock. Reported by @mandresve (#1615, #1616). +- Settings: theme, wallpaper, dock position and dock icon size now persist across logout/login. They were applying in-session but a restore that ran before login completed left them reverting to defaults on the next session. Reported by @mandresve (#1601, #1603). +- Models & Providers: when a downloaded model can't be used because the backend that serves it isn't running, taOS now says exactly that and how to fix it (install/start the backend) instead of a generic "not found anywhere", and provider connection tests report the real failure reason instead of "unknown error". Reported by @mandresve (#1599, #1600, #1614). + +## [1.0.0-beta.23] - 2026-07-04 + +### Security +- Fixed a high-severity missing-authorization vulnerability (GHSA-h24f-gp4c-8qjm, CWE-862): the skill-execution endpoint `POST /api/skill-exec/{skill_id}/call` ran built-in skills, including one that executes arbitrary Python on the host, without any authorization check, so an authenticated non-admin user could achieve remote code execution as the backend process user. Skill execution now requires an admin session or the host local token (the credential deployed agents authenticate with); a non-admin session is rejected with 403 before any skill code runs, with a defense-in-depth check at the code-execution sink. Reported by EQSTLab. + +## [1.0.0-beta.22] - 2026-07-04 + +### Added +- Game Studio is now a real AI-assisted game maker. Describe a game and the taOS agent generates a complete, playable game from a starter template; edit it with a file editor, a live sandboxed preview and an AI chat that proposes changes; save games to your cluster; and share a finished game as a sandboxed taOS app (it installs through the same security-analyzed app runtime as any other) or export it as a package (#1602). +- One-tap local model backend install, per hardware. On a supported machine the setup checklist offers to install a local LLM backend for your device in a single tap, creating and starting the service and confirming it actually answers before marking it done. Rockchip installs rkllama; NVIDIA, AMD, Apple Silicon and CPU-only machines install a llama.cpp server that serves chat plus embeddings and reranking from one process, so the taOS agent and taOS memory share a single backend (#1597, #1608). +- Settings account: the taOS account is framed as the key to taOSgo, app sharing and a reserved taOS username for a future website and social presence, with a prompt to reserve your username; onboarding gains an optional step to sign in to your taOS account (#1593, #1595). + +### Fixed +- Store: installing an NPU/local backend now genuinely installs and starts it. A backend that showed as installed but never actually ran is repaired: the install creates and enables the service, self-heals a half-installed machine, and only reports success once the backend answers (#1598). +- Models: models you download are now selectable in the taOS agent and accepted at deploy. RKLLM models register with rkllama on download instead of silently landing on disk where the agent could not find them, and the agent model picker lists locally downloaded models, not just cloud ones. Reported by @mandresve (#1599, #1600). +- Settings: theme and wallpaper choices now persist across sessions, and Desktop & Dock settings (dock size and position) actually take effect. Reported by @mandresve (#1601, #1603). +- Text Editor: typing works continuously again; the editor no longer loses focus after each keystroke. Reported by @mandresve (#1596). +- Files: deleting a file or folder now moves it to the Recycle Bin instead of permanently removing it, across your own workspace, agent workspaces and project files; restore or empty it from the Recycle Bin. Reported by @mandresve (#1604). +- Projects: the New Project dialog no longer opens behind the Projects window. Reported by @mandresve (#1605). +- App Runtime: the install-time permission consent dialog can no longer be dismissed mid-request, and skips a redundant lookup when no consent is needed (#1592). + +## [1.0.0-beta.21] - 2026-07-03 + +### Added +- App Runtime: install-time permission consent. Installing a sandboxed app now shows a dialog listing the capabilities it requests, with sensitive ones (network, agent, model, memory) highlighted, and you grant or deny before the app can use them (#1579). +- App Runtime: container app tier. Apps can now ship as their own container alongside sandboxed web apps, deployed on a per-app port bound to localhost with memory and CPU caps and reached through an isolated proxy (#1580). + +### Fixed +- Store: installing an agent framework (Hermes, OpenClaw) now actually does something. It enables the framework for deployment in the Agents app, downloads its base image in the background with real progress, and notifies you when it is ready; the framework's Open action now takes you to the Agents app to deploy. Previously the install silently did nothing while showing "installed". Reported by @mandresve (#1582). +- Models: deleting a model now removes it on the backend instead of only hiding it in the UI, and a freshly downloaded model shows as installed immediately without reopening the dialog. Reported by @mandresve (#1581, #1548). +- Providers: providers no longer show a false Error (or a contradictory Running and Error together) on a healthy install. The panel reports true backend status, the rkllama default port matches the installer, and the toggle switches render correctly. Reported by @mandresve (#1578). +- Settings: the Logs pane now shows real system logs (controller, model backends, LLM proxy) with a live tail, not just browser-side errors, so backend failures are actually visible. Reported by @mandresve (#1583). +- Text Editor: creating notes and documents works again over plain http. The editor no longer crashes on a missing secure-context API, and the same class of bug was hardened across the assistant panel, push registration, and Web Studio. Reported by @mandresve (#1584). + +## [1.0.0-beta.20] - 2026-07-03 + +### Added +- Video Studio: a new AI video-generation studio. Describe a scene, pick a resolution and duration, and generate a clip on any discovered video backend (WanGP / Wan 2.1). Generated clips land in a library with inline playback, download and delete (#1572). +- App Studio: a static security analyzer now scans AI-authored app source before install. It runs server-side on every install regardless of how the package was submitted or its type, flags risky patterns (unvalidated postMessage origins, storage exfiltration, code that executes strings), and blocks an install outright on a critical finding. App Studio's Publish view surfaces findings while the app is still just generated text (#1573). +- Security: app capabilities are now keyed to provenance. First-party apps keep the full capability set; AI-generated and user-uploaded apps are ceilinged to notifications and their own window; unknown-provenance apps get nothing until the user grants more (#1574). + +### Fixed +- The macOS .app build works against current dependencies again, and its SPA static layout is resolved correctly whether or not the frontend build nests its output (#1557). + +## [1.0.0-beta.19] - 2026-07-03 + +### Added +- Design Studio is now a real canvas editor. Select, move, resize and rotate elements; add editable text, shapes and images; manage layers; zoom and pan; undo and redo; and export the artboard to PNG. AI-generated images from the Magic view drop straight onto the canvas as editable elements (#1566). +- Web Studio: a new AI-assisted, Wix-style website builder. Describe a site or start from a template, then edit it as stacked sections (hero, features, gallery, contact and more) with inline text, image swaps, live theming, and add/remove/reorder. Preview responsively across desktop, tablet and mobile, and export a self-contained static HTML page. Sites persist on your own cluster (#1567). + +### Changed +- Office Suite is now Office Studio, and it is a real office suite. Write is a full rich-text word processor (bold/italic/underline, headings, lists, links); Calc is a real spreadsheet with a formula engine (cell references, SUM/AVERAGE/MIN/MAX/COUNT/IF), multiple sheets, sort/filter and CSV import/export; and Slides is a real presentation editor with layouts, images, a fullscreen present mode and PDF export. All three save to your cluster (#1565, #1568, #1569). + +### Fixed +- Models: downloading a model actually downloads it now. The Models app was showing a fake progress bar and a false "installed" state without ever contacting the backend, so the model vanished on reopen and no file was fetched. Download now calls the real backend, shows true progress, reflects the backend's installed state, and surfaces a real error with a Retry button on failure. Reported by @mandresve (#1548). + +## [1.0.0-beta.18] - 2026-07-03 + +### Fixed +- Installer: agent-container runtime install now prefers the `incus-base` package over the full `incus` metapackage, whose extras have unsatisfiable dependencies on Debian Bookworm ARM64 (held broken packages); falls back to `incus` where incus-base is not a candidate (#1555). +- Models: a download that finishes the transfer but wrote no data (or the wrong number of bytes) is no longer marked complete. Both the torrent and HTTP paths now validate the file exists, is non-empty, matches the expected size, and passes its checksum before the model is reported installed (#1548). +- Installer: the RK3588 NPU install no longer aborts on the first clean run on boards that have binutils. The librknnrt version check piped `strings` (a 7MB binary) into an `awk` that exited on the first match; the early exit SIGPIPEd `strings`, which under `set -o pipefail` + `set -e` killed the whole install. It succeeded on a second run only because the pin was already applied and the block was skipped. The check now reads to EOF and is guarded (#1560, #1543). Reported by @mandresve. + +## [1.0.0-beta.17] - 2026-07-02 + +### Fixed +- Models: a failed model download no longer looks like an instant successful install. The failure now stays on the model card with the actual cause from the backend (for example a checksum mismatch or an unreachable download host) and a Retry button, instead of the progress UI silently disappearing (#1548). +- Installer: the Docker Compose v2 plugin now installs on Debian (including vendor Pi images) by trying the Debian package name (`docker-compose-plugin`) before the Ubuntu one (`docker-compose-v2`), and the engine + plugin install in separate apt transactions so a missing plugin name can no longer prevent Docker itself from installing (#1541). +- Installer: install-rknpu.sh no longer aborts right after replacing librknnrt.so when `ldconfig` exits non-zero on vendor images with merged /lib layouts; the cache refresh is best-effort and rkllama now installs in the same run (#1543). +- Installer: the prebuilt desktop bundle is used on re-runs again. Re-runs as root over the taos-owned checkout tripped git's dubious-ownership check, which silently disabled the prebuilt path and forced a local vite build every time; the tree check now runs as the owning user, logs say whether the bundle channel was unreachable or genuinely mismatched, and installs pinned to a release tag fall back to the bundle attached to that release (#1544). +- Installer: install-rknpu.sh now installs the OpenCV runtime libraries rkllama needs (libGL, GLib, libSM, libXext); vendor Debian images ship without them and rkllama.service crashlooped on "ImportError: libGL.so.1" (#1545). +- Installer: when a vendor image ships with Docker preinstalled, incus is now installed as well (it is the preferred runtime for agent containers; skip with TAOS_NO_INCUS=1). Previously the installer treated the pre-existing Docker as sufficient and only a later warning told the user to install incus by hand and re-run (#1546). + +## [1.0.0-beta.16] - 2026-07-02 + +### Added +- On Rockchip boards the setup checklist now includes an "Install the NPU backend" step: it appears only when an NPU is detected, opens the Store where the rkllama backend installs with one click, and ticks itself once the backend is running. Previously nothing in the setup flow ever surfaced the NPU install. + +### Fixed +- A controller update or restart no longer silently strands agents in a paused state: agents whose framework handled the shutdown protocol itself, hostless agents, and agents whose containers boot slower than the controller are all resumed at boot (with background retries), and anything that still cannot be resumed raises a visible warning instead of staying paused quietly. + +## [1.0.0-beta.15] - 2026-07-02 + +### Fixed +- The Rockchip NPU installer no longer fails on fresh installs with "reference is not a tree": the rkllama pin points at the proven production commit (made reachable for fresh clones again), and a stale pin fails with a clear explanation instead of a raw git error. This also keeps the generated rkllama service startable: the newer rkllama default branch dropped the preload option the service relies on. Reported by @mandresve (#1527, #1529). +- Reconnecting a comms channel (Telegram/Slack/Discord/email/Matrix/webchat) after a token rotation now stops the previous connector instead of silently leaking its background task, concurrent reconnects for the same agent can no longer orphan a connector, and a malformed Matrix reconnect fails cleanly without tearing down the working connector. +- The LLM proxy self-heal is more robust: it locates the install root at any venv layout, only trusts our own pyproject when doing so, and its pip fallback installs just the proxy requirements instead of re-resolving every dependency. +- Seeding the internal driver agents now requires naming each pre-existing handle explicitly to adopt it; a blanket adopt flag is rejected so a handle claimed by someone else can never be vouched for as a side effect. + +## [1.0.0-beta.14] - 2026-07-01 + +### Added +- Channel Hub: Matrix connector. An agent can be reached over Matrix (homeserver + access token) the same way as Telegram/Slack/Discord; the connector mirrors the others and coexists with the A2A bus (channels are human-to-agent transport, A2A is agent-to-agent). +- Secrets: SSH keys are first-class on agent deploy. A secret in the `ssh-keys` category is materialized inside the agent container as `~/.ssh/` with 0600 perms (path-traversal-guarded), so tools like git and ssh can use it directly instead of only as an env var. +- Store: the four optional social apps (Reddit, YouTube, GitHub, X) are installable again from the Store's taOS Apps section. +- Live notifications: a shared real-time event stream now pushes updates (starting with the notification bell) straight to the desktop, no more waiting on a poll or a page refresh. +- Observatory: per-session approval-mode control (default / accept-edits / don't-ask), the storage and API foundation for finer-grained control over how much an agent can do without asking. +- Action receipts: every tool call an agent makes is now recorded in an append-only, content-hashed audit trail (inputs and outputs fingerprinted), the foundation for verifiable replay. +- Agents panel now updates live as agents are minted, approved, or revoked, instead of needing a manual refresh. +- Auth: agents authenticate to the A2A bus with their own identity/token instead of borrowing the owner's session, and an admin can now adopt a pre-existing agent identity into the registry rather than being blocked. +- Contributor tooling: an enforced documentation-drift gate (local hook + CI) keeps README/docs in sync when scripts or install paths change. + +### Changed +- Agent consent requests are now non-blocking: a bell entry and toast with inline Allow/Deny, instead of a desktop-blocking popup. Nothing is lost, decisions are archived to History. +- In-app updates now install exactly the dependency versions pinned in the lockfile (uv sync --frozen) instead of re-resolving on every update, so an update can no longer pull in an untested dependency version. +- The install directory moved from `/opt/tinyagentos` to `/opt/taos`; existing installs keep working with zero migration required. + +### Fixed +- **Critical**: Settings > Install Update no longer uninstalls the LLM proxy (litellm); earlier updates could silently strip it and disable all agent LLM routing. +- The LLM proxy now self-heals: if litellm is missing at startup (e.g. left over from an update before the fix above), it reinstalls itself once automatically and comes back online. +- The desktop no longer breaks after an update: it reliably loads the new version after a redeploy instead of getting stuck on stale cached code or crashing when opening an app, on Chrome, Safari, and Firefox alike. +- A failed background rebuild during boot no longer crash-loops the controller; it now falls back to the last working build and logs a warning instead. +- Opening Settings > Account no longer flashes the login screen and bounces you back to System Info when you are not signed into a taOS cloud account; the account service's expected "not signed in" response is no longer mistaken for your device session expiring. + +## [1.0.0-beta.13] - 2026-06-28 + +### Added +- Notes and Todo: tracked edits. An entry's text is editable and every change is an immutable revision tagged with editor and timestamp, stored as a diff with a full snapshot checkpoint every 20 edits so any past state can be reconstructed (Time Machine foundation). New history and at-revision endpoints expose the log and reconstructed text. +- Todo app: a checklist companion to Notes for `kind=list` documents, with per-task done checkboxes (completed tasks struck through), shared sharing/permission/agent-action controls, and the same tracked-edit history. Notes and Todo each show only their own document kind. +- Agent tool `notes_set_done`: an agent shared on a list (contributor or editor) can mark a task done or reopen it, completing the agent surface for shared todos. Membership, permission, archived-doc, and entry-belongs-to-doc are all enforced. +- Observatory fleet endpoint returns a health summary (total/working/idle/stale counts, stale handles, and an active/degraded/idle status) so the UI can show fleet status at a glance without recomputing. +- Notes "Discuss" agent action: an agent shared on a doc with the discuss action now gets a dedicated threaded topic channel (one per doc and agent, reused across entries, with the agent as a lead so it actively asks clarifying questions) instead of a DM ping, and falls back to the DM if the channel cannot be created. +- Observatory lane framework badges: the fleet endpoint now carries each agent's framework (kilo/opencode/hermes/...), and the Observatory app shows it as a small badge next to each lane handle. +- Coding sessions: the launch alias is editable. `PATCH /api/coding-sessions/{id}` renames a session, and the change is reflected in its agent-registry entry so the Agents/Registry app stays in sync. +- Coding sessions: host-folder launcher. `POST /api/coding-sessions/{id}/start` runs the chosen CLI in a detached tmux session scoped to the workdir, `/transcript` returns the captured terminal output (append-only), and `/stop` kills the tmux session (a no-op on an archived session). +- Frameworks: OpenCrabs registered as a beta agent framework (adolfousier/opencrabs, a single-binary Rust agent inspired by OpenClaw). A subprocess adapter drives `opencrabs run --format json` and maps the result onto the chat reply. + +## [1.0.0-beta.12] - 2026-06-28 + +### Added +- Decisions app: the human-in-the-loop inbox. Store + API backend, desktop app, notification routing, answers routed back to the asking agent on the A2A bus, L1 supersede with history lineage, per-project Decisions archive tab, and a `request_decision` agent tool. +- Observatory app: fleet view of which agents are working on what, idle-agent surfacing, queue-control pause (global and per-lane), steer v1 (global and per-lane concurrency caps with server-rejection surfacing), and a stale-claim badge. +- Agent-native tools: `list_projects`, `list_tasks`, `list_files`, `list_frameworks`, `list_store_apps`, `get_capabilities` (hardware-aware advice), `notify_user`, and `request_decision`, plus a screen-aware desktop layout-read API for window management. +- Projects canvas: migration onto an MIT renderer (Konva foundation, Excalidraw read-only board with CanvasElement mapping), real mermaid/flowchart diagram rendering, GitHub issue to board-card sync, and channel project tagging with filter. +- Agent deploy: framework-aware prebuilt base image for a Hermes fast-path, Base Images management (API plus desktop pane: list/import/prune/prefetch), and an Import Agent wizard that uploads a Hermes profile bundle. +- Cluster: deploy an agent onto a cluster worker. An explicit target_worker pin creates the agent container on that worker's nested incus, with the controller reached over the LAN or tailnet. +- Notes and Todo: shareable notes and lists with an API (create/list docs, entries, members). A doc can be shared with agents, and a new entry notifies each agent member on its DM channel with the member's standing instruction so the agent can act. +- App permissions: a closed capability vocabulary with manifest validation, an `app_grants` ledger feeding the capability broker, and a request-consent endpoint that raises an app-grant Decision. +- Secrets broker: grant ledger and lifecycle (P0) plus routes and service wiring with request notifications (P1). +- Agent-model API: owner key-management (mint/list/revoke) and a `/v1/chat/completions` consent contract. +- Account pane shows the local signed-in identity and defaults the account base URL to taos.my. +- Logging: server-side and front-end crash capture with an in-OS Logs viewer. +- Messages groups agent DMs into Live / Suspended / Archived sections. +- Memory: arctic-embed-s as the recommended embedder default. +- taosctl gained command groups for decisions, observatory, agent-registry, dashboard, benchmarks, recycle, office, catalog, knowledge, templates, mail, store, settings, providers, secrets, themes, and notifications. +- Frameworks: added DeerFlow support. + +### Changed +- Deploy Agent wizard shows Hermes as beta. +- Desktop app error boundary logs the real underlying error. + +### Fixed +- Worker-LXC bring-up completed on Linux so workers are deploy-capable. +- Security: the skill workspace resolver rejects a traversal `agent_name`. +- Hermes profile import passes `--name` so the default profile is no longer rejected. +- Client-log ring buffer prunes correctly across rowid gaps and uses a rowid tie-breaker. +- Agent archive fails soft on snapshot-restricted projects and resolves or cleans orphaned containers on delete; an agent DM channel is archived on every removal path rather than orphaned. +- Auth: a correct password is no longer refused during lockout. +- Consent: request-consent skips capabilities with a pending Decision, de-dupes capabilities, and logs ledger failures; Decisions de-duplicate colliding option values and default an option value to its label. +- Observatory writes pause state atomically and no longer reverts an optimistic steer value mid-write. +- Update: dirty tracked source is stashed before pull instead of returning a 500. +- Dependencies: cryptography bumped to 48.0.1 for a vulnerable OpenSSL. + +## [1.0.0-beta.11] - 2026-06-21 + +### Fixed +- Agent deploy: framework agents (Hermes, OpenClaw, etc.) failed to deploy on hosts that cannot mint per-agent LiteLLM virtual keys (ARM / Pi where prisma cannot start, and any install without Postgres). The deployer now falls back to the shared LiteLLM master key in genuine routing-only mode (single-user instances, with a loud warning; opt out via `TAOS_DISABLE_AGENT_MASTER_KEY_FALLBACK=1`). A DB-configured-but-broken mint still fails loudly so a real fault is never masked. +- Agent deploy: containers in a restricted multi-user incus project (e.g. `user-999`) failed at creation because proxy devices were forbidden. `add_proxy_device` now self-heals by allowing proxy devices on the named project and retrying once. +- Provider model picker: a newly-added cloud provider (e.g. DeepSeek) whose `/models` probe needs a key now surfaces its seeded models, and the taOS agent model chooser lists the same models as the agent deploy picker. +- Activity NPU card hidden on hardware with no NPU; desktop widgets default off until redesigned. + +### Added +- Cluster: free-tier manual worker pairing. A worker prints its LAN address and a PIN; the user adds it from Cluster > Add worker with no network discovery (taOSgo remains the automated path). + +### Changed +- Hermes is the recommended default agent framework (shown first and pre-selected in the deploy wizard); OpenClaw second. +- Dev/master version reconciled (beta.6 drift fixed) and bumped to `1.0.0-beta.11`. + +## [1.0.0-beta.9] - 2026-06-21 + +### Fixed +- Install: a re-install over an existing virtualenv built with an unsupported Python (e.g. a 3.14 venv from an attempt before beta.8) reused that venv and failed with "requires a different Python: 3.14.x not in <3.14,>=3.11". The installer now detects an out-of-range venv interpreter and recreates the venv with a supported 3.11 to 3.13 Python. + +## [1.0.0-beta.8] - 2026-06-21 + +### Fixed +- Install: the controller venv now uses a litellm-compatible Python (3.11 to 3.13). A fresh distro that defaults python3 to 3.14 (e.g. WSL on Ubuntu 26.04) previously aborted with "No matching distribution found for litellm>=1.89.3", because litellm supports only >=3.10,<3.14. The installer now picks a supported interpreter, installs python3.13 if none is present, and fails with a clear message otherwise; requires-python is capped at <3.14 to match. + +## [1.0.0-beta.7] - 2026-06-21 + +### Fixed +- Install: libtorrent is no longer a core dependency, so a fresh install no longer aborts with "No matching distribution found for libtorrent>=2.0.9" on platforms without a libtorrent wheel (e.g. WSL). It is now an optional `torrent` extra; the model torrent mesh is enabled only where the OS-level package is present, and hosts without it fall back to a direct download. + +## [1.0.0-beta.6] - 2026-06-21 + +### Added +- Coding Studio gains a model-agnostic tool-calling loop: agents read, edit, and verify files inside a workspace-jailed sandbox using filesystem tool primitives, driven by a LiteLLM-backed model step. +- Cluster capability map: worker registration and heartbeats populate a per-node capability and hardware map with admin endpoints, plus a non-destructive stale-node offline sweep. +- Append-only board audit log: every task transition is recorded, with a project-scoped activity feed and a task audit endpoint, indexed for unbounded growth. +- `taos rollback`: a CLI recovery path that restores the previous branch and version, so a broken update can be recovered even when the dashboard is unreachable. + +### Changed +- One Browser app: the separate streamed-browser app is gone. The Browser app attaches a Neko streamed session through a toggle, and a RAM-capable Pi host can serve the session itself instead of reporting that it is not capable. +- The default store no longer seeds the X, Reddit, YouTube, and GitHub apps; they are optional installs. + +### Fixed +- Browser sessions resolve the target worker before creating the session row, so a failed placement no longer leaves an orphaned session. +- Auto-expiring notification toasts no longer archive themselves into the History view. +- Dependabot majors updated: actions/checkout v7, dependabot/fetch-metadata v3, and the dev Python dependency group. + +## [1.0.0-beta.5] - 2026-06-20 + +### Added +- Browser app redesigned to the current design bar with a collapsible sidebar. +- Coding Studio: workspace-scoped agent file edits with a build loop and inline diff review. + +### Changed +- CI runs the test matrix on GitHub-hosted runners, cancels superseded runs per ref, and auto-merges low-risk Dependabot patch and minor updates on green. + +### Fixed +- Streamed browser now connects over Tailscale and other non-LAN addresses: WebRTC advertises the single connecting-host IP, fixing the white screen the previous comma-separated NAT mapping caused. +- The "connecting" overlay can no longer hang over a session that is already live. +- Hardened the streamed-browser iframe sandbox and several store and coding-studio endpoints: IDOR guard on submission reads, symlink-safe workspace writes, and an admin gate on install-registry mutations. +- Store submissions return 400 on invalid input instead of 500. +- Security: dompurify updated to 3.4.11; cryptography and pydantic-settings advisories cleared. +- Install: the core install no longer aborts when optional components fail, and drops to the service user without assuming sudo (WSL robustness). + +## [1.0.0-beta.4.1] - 2026-06-20 + +### Changed +- Installs and in-app updates verify the prebuilt bundle's SHA256 before extracting; a corrupted or tampered bundle is rejected and falls back to a local build. +- Re-installs update the existing install in place instead of forking a second copy. + +### Fixed +- Symlink-safe staging (no fixed /tmp paths as root), atomic-rename swap, and a fix so the bundle is no longer treated as perpetually stale. +- README corrected (installs download a prebuilt bundle, no local build) and links rebranded to jaylfc/taOS. + +## [1.0.0-beta.4] - 2026-06-20 + +### Added +- "Reduce effects" toggle (Settings, Accessibility) for low-end devices: disables background blur, heavy shadows, and continuous animations for a smoother UI on older hardware. + +### Changed +- The installer and in-app update download a prebuilt UI bundle instead of building it locally, so installs and upgrades are faster and no longer fail or silently stay on the old version on low-memory machines including WSL. A local build, when still needed, now fails with a clear message instead of half-updating. +- CI runs on self-hosted runners and gates the desktop test suite. + +## [1.0.0-beta.3] - 2026-06-16 + +### Added +- Mobile Store redesigned into an Apple App Store-style layout: bottom tab bar (Discover/Apps/Agents/Search/Updates), a featured hero, horizontal app carousels with Get pills and star counts, full-screen search, and a device filter. +- Real cover banners and icons across the Store: OpenClaw, Hermes, Ollama, ComfyUI, n8n, and the self-hosted apps, plus a shared Stable Diffusion banner (the AUTOMATIC1111 build shown in grayscale to distinguish it). A shared AppIcon component falls back to a branded monogram when no logo exists, so no tile renders blank. + +### Fixed +- Installed apps in the mobile Store no longer show a non-interactive "Open" control; they show an honest installed status. +- Failed Store installs now surface a Retry action instead of failing silently. +- Store icons and cover images reset correctly when a reused tile switches to a different app. + +## [1.0.0-beta.2] - 2026-06-16 + +### Added +- Mail app with IMAP/SMTP account setup, message list, read, and send. +- Reddit, YouTube, GitHub, and X apps available as optional Store installs. +- Agent-callable screenshot endpoint for desktop-control workflows. + +### Changed +- Browser app redesigned with the Store/Images design bar and taos.my set as the default homepage, with automatic dark/light scheme applied to proxied sites. +- Projects app shell redesigned with a Workspace hero tab. +- Notification bell wired to the backend feed with actionable click routing to the originating app or agent. +- Updates panel now shows version numbers (e.g. 1.0.0-beta.2) as the primary display, with commit SHAs as a secondary detail. + +### Fixed +- Controller restart time reduced from ~46 s to ~7 s by eliminating the graceful-stop hang. +- Projects canvas crash caused by malformed element payloads written by agents. +- Window move and resize jitter under rapid pointer events. + +## [1.0.0-beta.1] - 2026-06-09 + +Initial source-available public beta release under the taOS Sustainable Use License v0.1. diff --git a/CLA.md b/CLA.md new file mode 100644 index 000000000..f65f7f304 --- /dev/null +++ b/CLA.md @@ -0,0 +1,21 @@ +# taOS Contributor License Agreement (Individual) — v0.1 + +> Interim version, to be ratified by an IP lawyer. By contributing to taOS you agree to the terms below so the project can stay sustainable: source-available for everyone, with a separate commercial license for commercial use. + +Thank you for contributing to **taOS** (the "Project"), maintained by **jaylfc** (the "Maintainer"). + +By submitting any contribution (code, documentation, configuration, or other material) to the Project, you agree to the following: + +1. **Grant of rights.** You grant the Maintainer a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license to use, reproduce, modify, prepare derivative works of, publicly display, sublicense, and distribute your contribution and such derivative works, **and to relicense your contribution under any license terms the Maintainer chooses** — including source-available and commercial licenses. + +2. **Original work / right to submit.** Each contribution is your own original creation, or you otherwise have the right to submit it, and submitting it does not violate any third party's rights or any agreement you are bound by. + +3. **Retained ownership.** You retain all right, title, and interest in your contributions. This is a license to the Maintainer, **not** an assignment of copyright. + +4. **No obligation.** The Maintainer is under no obligation to use or include your contribution. + +5. **No warranty.** Your contribution is provided "as is", without warranties of any kind. + +## How to agree + +The first time you open a pull request, the CLA check will ask you to confirm agreement (a one-time signature that covers all your future contributions). For contributions made before this CLA was adopted, you may confirm agreement by commenting on the project's relicensing/CLA issue. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4befe6ce2..5d43e3c25 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/COMMERCIAL-LICENSE.md b/COMMERCIAL-LICENSE.md new file mode 100644 index 000000000..eb6885850 --- /dev/null +++ b/COMMERCIAL-LICENSE.md @@ -0,0 +1,26 @@ +# Commercial License + +taOS is licensed to the public under the GNU Affero General Public License v3.0 +or later (AGPL-3.0-or-later). See [LICENSE](LICENSE). For most use, self-hosting, +internal business use, personal, academic, and research, the AGPL is all you need +and taOS is free. + +## When you may want a commercial license + +The AGPL requires that if you run a modified version of taOS to provide a network +service to others, you make the complete corresponding source of your modified +version available to those users, also under the AGPL. A commercial license removes +that obligation for you. + +Consider a commercial license if you want to: + +- offer taOS, or a modified version, as a hosted or managed service without + releasing your modifications under the AGPL; +- embed or bundle taOS inside a proprietary product you distribute; or +- otherwise use taOS on terms the AGPL does not grant you. + +## How to get one + +Commercial licenses are granted by jaylfc. To discuss terms, contact info@taos.my. + +Copyright (c) 2026 jaylfc. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dea8338e..53907a338 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,28 @@ -# Contributing to TinyAgentOS +# Contributing to taOS -Welcome — and thanks for your interest in contributing. TinyAgentOS is a self-hosted AI agent platform for low-power hardware. Before diving in, please read the [README](README.md) for a project overview. +Welcome — and thanks for your interest in contributing. taOS is a self-hosted AI agent platform for low-power hardware. Before diving in, please read the [README](README.md) for a project overview. > **Note:** The project is in early development. APIs and interfaces may change. That is fine — contributions of all sizes are welcome. --- +## License & Contributor License Agreement + +taOS is open source under the **GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later; see [`LICENSE`](LICENSE)). A separate commercial license is available from jaylfc for uses the AGPL does not grant, for example embedding taOS in a proprietary product or offering it as a hosted service without releasing your modifications under the AGPL (see [`COMMERCIAL-LICENSE.md`](COMMERCIAL-LICENSE.md)). + +To keep this sustainable, **all contributors must agree to the Contributor License Agreement ([`CLA.md`](CLA.md))** before their contributions are merged. The CLA grants jaylfc the right to include and **relicense** your contributions under the project's licenses; **you keep ownership of your work**. You sign once — on your first pull request, comment **"I have read the CLA Document and I hereby sign the CLA"** and the CLA check turns green; it then covers all your future contributions. + +--- + ## Getting Started for Contributors ```bash git clone https://github.com/jaylfc/tinyagentos.git cd tinyagentos -python3 -m venv venv -source venv/bin/activate -pip install -e ".[dev]" +uv sync --extra dev # Build the desktop SPA — static/desktop/ is gitignored (generated artifact) cd desktop && npm install && npm run build && cd .. -pytest tests/ -v +uv run pytest tests/ --ignore=tests/e2e -n auto ``` Python 3.10 or later and Node.js 22 or later are required. @@ -50,15 +56,22 @@ The app catalog is one of the easiest ways to contribute. See [Adding an App to 1. Fork the repository 2. Create a branch: `git checkout -b feat/my-feature` 3. Make your changes and add tests -4. Run `pytest tests/ -v` — all tests must pass -5. Open a pull request against `main` +4. Run `uv run pytest tests/ --ignore=tests/e2e -n auto` - all tests must pass +5. Open a pull request against **`dev`**, not `master` Keep pull requests focused. One feature or fix per PR is easier to review. +> **Branches:** `master` is the stable branch that installs track, so it only +> receives tested changes promoted from `dev`. All contributions target `dev`. +> If you open a PR against `master` by mistake, no problem — we'll retarget it +> to `dev` (the commits and review carry over). + ### Documentation Documentation improvements are always welcome — typo fixes, clarifications, better examples. Open a PR directly. +The taOS agent manual is compiled: edit `docs/agent-manual/` and run `python3 scripts/build-agent-manual.py` to regenerate `docs/taos-agent-manual.md`. + --- ## Adding an App to the Catalog @@ -144,12 +157,13 @@ Submit a pull request. The CI will run the catalog tests automatically. Include - One concern per module; avoid cross-importing between route files - Use `async def` for route handlers; use `await` for all I/O -### Templates +### Frontend + +The UI is a React SPA (`desktop/`) built with Vite. Static assets are served from `static/desktop/` after `npm run build`. If you are adding a new UI surface: -- Use Pico CSS utility classes — do not introduce other CSS frameworks -- Use htmx attributes (`hx-get`, `hx-target`, `hx-swap`) for dynamic updates -- Write semantic HTML — use the right element for the job (`