release: promote dev to master (v1.0.0-beta.48, sync branch) - #2366
Conversation
Design spec for AI-assisted SDR/radio analysis studio (Sparky pattern). Covers studio app layout, rf-scanning skills, Hermes/ARM prism blocker, SDR USB passthrough, receive-only defaults, and Phase 1 cut.
Radio Studio - SPEC: AI-assisted SDR/radio analysis studio (Sparky pattern)
…#2288) * feat(projects): doc-review stamp store + routes (#1802 slice 3) (#1835) * fix(projects): show consent-flow external agents in the External section (#1784) Approved external CLI agents (grok, kilo) were appearing in the plain Members list instead of under "External / Connected agents" next to the other connected agents. The Members panel only classified a member as external when its member_id matched a registry agent's handle, but the consent flow registers these agents with an empty handle and adds the project member row keyed by the canonical id. So the match never fired and they fell through to the main list. Match external registry agents by canonical id as well as handle (older identities like the assistant reference by handle, consent-flow agents by canonical id), and map the "grok" framework, not only "grok-build", to the Grok label so the badge reads correctly. * test(secrets): add coverage for the Secrets app (#1785) Covers the mount-time /api/secrets fetch and loading state, masked value rendering, the empty and failed-fetch fallbacks, reveal and hide via the per-secret API, add and delete through the dialog, and category filtering. The GitHub integration is mocked so its on-mount identity fetch does not interfere with the secrets assertions. * test(notes): add vitest coverage for NotesApp/TodoApp mounted behavior (#1787) Render NotesApp and TodoApp, mock the /api/notes fetch on mount, and assert real behavior: kind filtering, empty states, detail load on select, and the create flow. * test(chess): add vitest coverage for ChessApp (#1788) Cover render, legal moves, turn changes, checkmate status, new game reset, and vs-agent mode, with the on-mount agents fetch mocked. * test(imageviewer): add vitest coverage for ImageViewerApp (#1789) Render the app and assert real behavior: empty state, file load, zoom in/out with min/max clamps, 90-degree rotation, reset on new image, and object URL revocation. Stub fetch and URL.createObjectURL so on-mount integrations do not interfere. * fix(desktop): Registry poll no longer resets scroll (#1761) (#1786) * fix(desktop): keep Registry panel scroll stable across 5s polls (#1761) Quiet background polls no longer flip loading (which unmounted the list) and setEntries is a no-op when id/content are unchanged, so scroll and in-progress interaction are preserved. Add registryEntriesEqual helper and vitest coverage for the poll no-op path. * fix(desktop): guard registryEntriesEqual index access and drop em dashes The poll no-op comparison read a[i]/b[i] without a guard, which fails the strict noUncheckedIndexedAccess build (spa-build). Add an explicit undefined guard, and remove the em dashes from the added comments. * chore(deps): bump the python-deps group with 3 updates (#1790) Updates the requirements on [uvicorn[standard]](https://github.com/Kludex/uvicorn), [croniter](https://github.com/pallets-eco/croniter) and [litellm[proxy]](https://github.com/BerriAI/litellm) to permit the latest version. Updates `uvicorn[standard]` to 0.51.0 - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.50.0...0.51.0) Updates `croniter` from 6.2.3 to 6.2.4 - [Release notes](https://github.com/pallets-eco/croniter/releases) - [Changelog](https://github.com/pallets-eco/croniter/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pallets-eco/croniter/compare/6.2.3...6.2.4) Updates `litellm[proxy]` to 1.92.0 - [Release notes](https://github.com/BerriAI/litellm/releases) - [Commits](https://github.com/BerriAI/litellm/commits) --- updated-dependencies: - dependency-name: uvicorn[standard] dependency-version: 0.51.0 dependency-type: direct:production dependency-group: python-deps - dependency-name: croniter dependency-version: 6.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-deps - dependency-name: litellm[proxy] dependency-version: 1.92.0 dependency-type: direct:production dependency-group: python-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump the spa-deps group in /desktop with 16 updates (#1791) --- updated-dependencies: - dependency-name: "@codemirror/state" dependency-version: 6.7.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@codemirror/view" dependency-version: 6.43.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-dialog" dependency-version: 1.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-dropdown-menu" dependency-version: 2.1.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-select" dependency-version: 2.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-switch" dependency-version: 1.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-tabs" dependency-version: 1.1.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-tooltip" dependency-version: 1.2.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/extension-link" dependency-version: 3.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/extension-underline" dependency-version: 3.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/pm" dependency-version: 3.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/react" dependency-version: 3.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/starter-kit" dependency-version: 3.27.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@types/three" dependency-version: 0.185.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: vite dependency-version: 8.1.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: vitest dependency-version: 4.1.10 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: spa-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs(design): account model, free username plus paid chosen subdomains (#1792) * docs(design): Hailo-10H LLM backend, zero-touch install parity with RK3588 (#1793) * docs(design): hub.taos.my local-first P2P social network foundation (#1794) * feat(hailo): reserve port 7836 and map hailo-ollama llm-chat capability (#1795) Slice 1 of the Hailo-10H LLM backend design (docs/design/hailo-llm-backend.md): add 7836 to RESERVED_PORTS so apps cannot squat the NPU backend port, and register hailo-ollama with llm-chat in BACKEND_CAPABILITIES. Closes the first shippable piece of #1771. * feat(account): controller proxy actions for subdomain check/claim/release (slice 3) (#1796) * feat(account): proxy subdomain check/claim/release actions (slice 3) Add /api/account/subdomains/{check,claim,release} routes to account_proxy.py that forward to the taos.my subdomain claims service with the session cookie passthrough. The name field is validated rid-style before it can reach the upstream URL, so a crafted name cannot inject path/query (SSRF/path-traversal guard). Implements account design doc slice 3. * test(account): cover subdomain proxy forwarding, 503, and name validation Add tests alongside the existing account_proxy suite: forwarding of check (query name) and claim/release (body name) with cookie passthrough, 503 when the account service is unconfigured, and 400 on an invalid name with no upstream call. * docs(design): Projects app nested elements, one project with typed elements (#1797) * fix(models): VRAM reservation TTL sweep + #1766 acceptance coverage (#1798) Completes the remaining #1766 work after the #1767 hotfix (fail-open on no-probe hardware, backend-level min_ram_mb gate, asyncio.to_thread probe). - Reclaim VramReservation entries older than a configurable TTL (default 1h) so a hung installer cannot hold capacity until controller restart. Sweep runs from reserve(), available_vram(), stats(), and public sweep_stale(). - Extract _estimated_vram_mb() so the rkllama pull gate clearly reads requires.backends[].min_ram_mb (max across backends, variant fallback). - Tests: no-probe + real backend min_ram proceeds (no 503), concurrent large reserves on NVIDIA still atomic, TTL reclaim, and route-level 503 when free VRAM is measurable and insufficient. Closes #1766 * feat(account): frontend types + Account panel split for username/subdomains (slice 4) (#1801) Implements slice 4 of docs/design/account-username-subdomain-model.md. - account-client: add SubdomainClaim/SubdomainCheck types, Account.username and Account.subdomains, deprecate Account.handle; add checkSubdomain, claimSubdomain, releaseSubdomain helpers with the same degrade-to-state (AuthError, never throw) error style as the auth actions. - AccountPanel: split the old ReserveHandleCard into a free UsernameCard (no taOSgo mention, no .taos.my suffix) and a SubdomainsCard (claim list with active/grace badges, inline availability check, release, disabled claim UI when unsubscribed). Update the section intro copy. - Tests: account-client subdomain helper coverage (mocked website endpoints); AccountPanel coverage for free-username copy, claim list rendering, disabled claim when unsubscribed, and grace badge. * feat(hailo): slice 2 hailo-ollama installer (#1771) (#1803) * feat(hailo): slice 2 hailo-ollama installer (#1771) Implements docs/design/hailo-llm-backend.md slice S2 (section C): scripts/install-hailo.sh mirrors scripts/install-rknpu.sh structure and safety contract. Detects Hailo-10H via /dev/hailo0 + lspci/hailortcli, installs HailoRT (>= 5.1.0 firmware floor) on Raspberry Pi OS, clones hailo-ollama at a pinned ref remapped to port 7836, installs a systemd unit with orphan-reap ExecStartPre, and health-waits on /api/tags. Verification: bash -n, shellcheck, and a non-Hailo host prints the no-detection notice and exits 0 without touching the system. * chore(hailo): doc-gate trailer for install-hailo.sh The installer is specified line by line in docs/design/hailo-llm-backend.md section C (slice S2), which is already merged on dev. Docs-Reviewed: implements the merged design doc docs/design/hailo-llm-backend.md section C, no separate doc change needed * feat(hailo): slice 3 hailo-ollama managed service manifest (#1804) * feat(hailo): slice 3 hailo-ollama managed service manifest Add app-catalog/services/hailo-ollama/manifest.yaml per the managed-backend contract (lifecycle.auto_manage, unit, scope=system, health on 7836). The backend flows through load_managed_backends() with no new plumbing. Adds a unit test that loads the real manifest and asserts the backend is returned by load_managed_backends(). Docs-Reviewed: implements the merged design doc docs/design/hailo-llm-backend.md * fix(hailo): declare the proprietary license in the hailo-ollama manifest test_services_manifests requires a license key on every service manifest. The runtime is Hailo proprietary software behind an install-time EULA acceptance, so the manifest now says exactly that. Docs-Reviewed: license posture is specified in docs/design/hailo-llm-backend.md security and licensing section * feat(account): onboarding free username claim step (slice 5) (#1805) Adds a free taOS username claim step to OnboardingScreen, shown right after local account creation. Clearly labeled free, never gated behind taOSgo, and the user can always finish (claim is optional and failures degrade to a Settings pointer). Public subdomain publishing is deferred to Settings so onboarding never dead-ends on the paid path. Bounded to OnboardingScreen.tsx plus its test, per the slice plan. * feat(hub): identity keypair keystore + directory registration proxy (slice 1) (#1806) Implements slice 1 of the hub.taos.my own-your-posts social network design (docs/design/hub-social-network-foundation.md). Controller side, the taos.my directory endpoints are the contract (mocked in tests): - tinyagentos/hub/identity.py: node keystore that mints an Ed25519 signing key and an X25519 encryption key on first use, persists them 0600 under <data_dir>/hub/identity.json (mesh_credentials.py pattern: atomic write, allowlisted fields, TAOS_DATA_DIR override), and exposes the registerable public view, the SHA-256 author fingerprint, and a challenge-proof signer plus verifier. - account_proxy.py: _ACTIONS additions and same-origin routes for hub identity register / lookup / rotate, forwarding to /api/hub/identity/* with session cookie pass-through; lookup validates the username as an rid-style token before it can reach the upstream URL. Tests: keystore round-trip + 0600 perms + stable fingerprint, proxy forwarding + 503 (unconfigured and unreachable), challenge proof rejects a wrong key, lookup relays the append-only key log. * feat(hailo): slice 4 install-time gates for Hailo-10H (per design doc) (#1807) Mirror the Rockchip RKNPU install-time gates for the Hailo-10H NPU across install.sh, scripts/install-server.sh and scripts/install-worker.sh: detect /dev/hailo0 with 10H vs 8L discrimination (lspci/hailortcli, with TAOS_FORCE_HAILO override), chain into scripts/install-hailo.sh under TAOS_HAILO_SETUP=1, fail-soft on chain failure, and document the new env vars in each script header. Part of #1771. Docs-Reviewed: implements the merged design doc (doc-gate requires it) * feat(hailo): slice 5 runtime detection + provider adapter for Hailo-10H (per design doc) (#1808) Add the hailo-ollama backend end to end with no new hardware needed for CI: - worker probe candidate on the taOS remap port 7836, Ollama-compatible - OllamaCompatAdapter entry for hailo-ollama - litellm_config ollama-compat membership extended to hailo-ollama - provider type registered so auto_register_from_manifest seeds local-hailo-ollama on Hailo-10H hardware (mirrors rkllama local-rkllama) - tests for detection, LiteLLM ollama/<model> prefix, and seed * feat(hub): profile object + local hub store (slice 2) (#1809) * feat(hub): profile object + local hub store (slice 2) Implements slice 2 of the hub.taos.my own-your-posts social network design (docs/design/hub-social-network-foundation.md). - tinyagentos/hub/store.py: canonical-JSON encode/hash/sign/verify helpers and a SQLite HubStore (objects, blobs, authors tables). Objects are canonical-JSON encoded, content-addressed by SHA-256 of the canonical bytes excluding the signature, and signed by the slice-1 Ed25519 keystore. Profiles are the one mutable object with highest-version-wins semantics; put_profile ignores a stale (lower-or-equal version) replica so it can never clobber a newer one. - tinyagentos/routes/hub.py: local API the Hub app consumes. Render the node's own profile and create/update it with a version bump, each response carrying an explicit degrade state (no-identity / no-profile / ok). The store is opened lazily and colocated with the identity keystore under the data dir. Wired into routes/__init__.py. No peer networking; directory calls stay in account_proxy. Tests: canonicalization vectors, sign/verify (good sig verifies, tamper and wrong key do not), version-wins, object/blob/author store round-trip, and the profile routes end to end (degrade states, create + version bump, kind validation, signature check). * chore(hub): doc-gate trailer for the hub store slice The profile object and local hub store are specified in docs/design/hub-social-network-foundation.md (slice 2), merged on dev. Docs-Reviewed: implements the merged design doc docs/design/hub-social-network-foundation.md slice 2, no separate doc change needed * feat(hub): follow / friend / circle model + request brokering (slice 3) (#1810) Implement hub social slice 3 from docs/design/hub-social-network-foundation.md: - signed follow and cache-grant statements (cache-grant stored, not yet acted on; the cache worker lands in slice 6), - friend-request send/accept/decline flows that broker through the directory and record the local accepted edge, - local block (severs every edge to the peer and asks the hub to revoke the server-side edge) and mute operations, - a presence gate that denies lookup without an accepted edge, - directory proxy entries for requests, presence, and edge revoke. Tests cover edge authorization (presence denied without an accepted edge), rate-limit behavior, and block severing the edge. Docs-Reviewed: implements the merged design doc (doc-gate requires it) * feat(projects): nested element store, CRUD routes, and task element tags (slice 1) (#1811) * feat(projects): implement nested element store, CRUD routes, and task element tags (slice 1) Adds project elements (one level of nesting per the design) with a dedicated store and owner-gated CRUD routes, an element_id tag on tasks with create and update validation plus list and ready filtering, and the Beads snapshot carrying the tag. Group/promote and assignment are later slices. Docs-Reviewed: implements the merged design doc docs/design/projects-nested-elements.md slice 1. * test(projects): slice 1 element store, CRUD route, and task tag coverage Adds the element store unit tests and route-level coverage for element CRUD, tag validation, the 409 delete guard, untag mode, and element_id filtering on list/ready. Proves an external agent token filters by element with no auth change. Docs-Reviewed: implements the merged design doc docs/design/projects-nested-elements.md slice 1. * feat(projects): kanban element filter bar (slice 2) (#1812) Add a persistent element axis to the kanban board: element client API and element_id on task types, a pure element filter in boardFiltering, a new ElementFilterBar rendered from the toolbar (All | element chips | Project- level), an element badge on cards when the board is unfiltered, and element fetching wired through useBoardData. Zero-element projects stay untouched (the bar does not render). Tests added and existing board tests updated. Docs-Reviewed: implements the merged design doc (doc-gate requires it for scripts/, app-catalog/, tinyagentos/ changes) * feat(hub): post objects, chain logic, image ingest, composer + own-timeline (slice 4) (#1813) Implements slice 4 of docs/design/hub-social-network-foundation.md: a per-author hash chain (seq/prev), signed append plus verify and tamper detection, signed tombstones that drop content while keeping the chain verifiable, and image ingest that re-encodes and strips EXIF. Adds the local post, timeline, and delete routes plus the Hub app: a composer with a loud friends-only-by-default visibility switch and an own-timeline read from the local store. No peer sync yet (that is slice 5). Tests cover chain append/verify, tamper detection, tombstone drops content and keeps the chain verifiable, and EXIF stripped. Docs-Reviewed: implements the merged design doc (doc-gate requires it for scripts/, app-catalog/, tinyagentos/ changes) * fix: map violet and red note colors to valid tldraw palette names (#1815) Canvas notes with payload.color violet or red fell back to yellow because the tldraw COLOR_MAP in NoteShape.tsx was missing those entries. Added violet, red, and light-blue mappings so the note shape renders with the correct background color. Added tests to verify the color strings survive element-to-shape coercion and that COLOR_MAP has the expected entries. * docs(readme): External Coding Agents section (bring your own AI team) (#1816) * docs(readme): External Coding Agents section (registry, consent onboarding, kanban + a2a work loop) The external-agent collaboration flow (access requests approved from the phone, scoped registry identities, board claim/PR/close loop, a2a coordination) had no README presence despite being live and proven. Docs-Reviewed: readme-only change describing the shipped flow documented in docs/design/external-agent-onboarding.md * docs(readme): reference only docs that exist on dev doc-gate verifies every mentioned path exists; the project-invite design doc lives on a branch, so the section links only the onboarding doc. Docs-Reviewed: readme-only change describing the shipped flow documented in docs/design/external-agent-onboarding.md * fix(hub): serialize chain appends so racing posts are not orphaned (#1817) next_chain_position read the chain head and put_chain_object inserted with INSERT OR IGNORE, so two concurrent appends for the same author computed the same seq and the loser was silently dropped from the chain index while its body stayed in hub_objects. A per-store asyncio lock now serializes the read-position-then-insert section in append_post and delete_post; the local node is the only writer of its own chain, so this closes the race. Regression test proves three concurrent appends land as seq 1,2,3. Docs-Reviewed: hardening of the merged design doc docs/design/hub-social-network-foundation.md slice 4, no doc change needed * fix(canvas): map text elements to visible taos-text shapes (#1819) * fix(canvas): map text elements to visible taos-text shapes element-to-shape.ts only mapped note/link/image to custom shape types; text (and mermaid label) fell through to taos-generic whose props only carry geometry, so the payload never reached a visible label. Add a taos-text shape util and map kind=text to it, coercing payload.text to a string with empty-string default so imperfect agent writes still render. Add tests for text kind mapping, payload coercion, and fallback behavior. * chore(canvas): doc-gate trailer for text shape New desktop source (TextShape.tsx) rendering the canvas text kind; no behavioral doc needed, the canvas element kinds are covered by the design. Docs-Reviewed: frontend-only canvas fix for the boarded canvas text render bug, no doc change needed * feat(projects): element overview grid, creation flow, and drill-in navigation (slice 3) (#1820) * feat(projects): element overview grid, creation flow, and drill-in navigation (slice 3) Implements slice 3 of docs/design/projects-nested-elements.md on the frontend: - New elements/ registry (types.ts) with the seven known types, their icons, and type-driven landing-tab order. - ElementGrid: overview grid shown when a project has elements, with a fixed Project card and an Add element tile; zero-element projects keep today's workspace pane untouched (back-compat invariant). - ElementCard: type icon, name, type label, open/total task counts, owner chip, and a recent-activity line. - ElementCreateDialog: create a single nested element (name, slug, type, optional owner) within an existing project. - CreateProjectDialog: optional second step to seed nested elements; skipping yields a project identical to today's. - ProjectWorkspace: element drill-in scopes the board to the element and lands on the type's preferred tab, with a breadcrumb and the element id carried on the URL for deep links. - ProjectBoard/BoardToolbar: accept a scoped element id and hide the element filter bar while scoped. Tests cover the grid, card, create dialog, the zero-element regression, drill-in with breadcrumb, and the creation-flow step two. * chore(projects): doc-gate trailer for elements slice 3 UI New desktop sources under ProjectsApp/elements/ implementing slice 3 of the merged nested-elements design; frontend-only, no behavioral doc change. Docs-Reviewed: implements the merged design doc docs/design/projects-nested-elements.md slice 3 * Add confirm guard before video delete (#1821) Docs-Reviewed: frontend change to the shipped Video Studio surface. * Projects elements slice 4: element-scoped canvas + files (#1822) Add an element_id tag to canvas items (store column + ALTER migration, element-filtered list and create on the canvas routes), an adopt-existing element files subfolder helper, untag-on-delete for canvas items, and wire the frontend so the canvas honors the active element filter and the Files tab mounts the element subfolder. Docs-Reviewed: implements the boarded task, frontend/backend change to shipped surface. * feat(projects): doc-review stamp store + routes (#1802 slice 3) Add per-document review_state machine (awaiting_review/approved/changes_requested) with actor recording and timestamps, project-scoped agent-token gated routes, and the desktop stamp badge column plus typed API client. Docs-Reviewed: doc-review stamp store + routes slice per document-review-surface.md * feat(projects): add doc-review stamp badge to FilesApp and fix projects.ts types - Add ReviewBadge component with onClick support for cycling review state - Add cycleDocReview callback for in-place state transitions - Wire badge into FileRow (list view) and grid cards (project: locations only) - Fetch review states on project-location navigation via projectsApi.docReviews.list - Remove duplicate DocReviewState/DocReview type definitions from projects.ts - Remove duplicate docReview API block; keep single canonical docReviews namespace - Use DocReview | DocReviewMissing union for GET response type - Apply encodeURIComponent to state filter query parameter Docs-Reviewed: doc-review stamp store + routes slice per document-review-surface.md --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(installer): normalise tree ownership before re-run update (#1840) A re-run of install-server.sh over an existing checkout drops to the repo-owning user (the 'taos' service user) for the git fetch + reset, to avoid running git as root inside a user-writable tree. But it only reads the TOP-LEVEL dir owner. If a prior install was interrupted mid-chown (or a root step wrote a few paths back), the tree has MIXED ownership: the owning user then cannot unlink the still-root-owned paths, so the reset fails with 'unable to unlink old ...: Permission denied' -> 'Could not reset index file to revision origin/master', bricking every subsequent re-run. Normalise ownership to the owning user (chown -R, run by root) right before the update so the reset can rewrite the whole tree. Safe: root does the chown and git still runs unprivileged. Reported on #2 (fresh Orange Pi 5 Plus, retry after a partial first run). Failure class reproduced locally: an unwritable path in the tree yields the identical unlink-EACCES; normalising the tree makes the reset apply cleanly. * fix(projects): create element_id indexes after migration, not in SCHEMA (boot-brick) (#1853) The canvas and task stores put their element_id index in SCHEMA: CREATE INDEX ... ON project_canvas_elements(project_id, element_id) CREATE INDEX ... ON project_tasks(project_id, element_id) BaseStore runs SCHEMA (executescript) BEFORE _post_init, so on an existing pre-element_id database the index creation raised 'no such column: element_id' before the _post_init ALTER could add the column, crashing controller boot after an upgrade. Reproduced live: the Pi bricked on startup right after pulling this code (fresh installs were fine because SCHEMA creates the table WITH element_id, so CI never exercised the migration path). Move both indexes out of SCHEMA into _post_init, created after the ALTER. Same class of bug and fix as the registry active-handle index (#1841). Regression test seeds a pre-element_id DB and asserts both stores boot, migrate the column, and build the index. * fix(hailo): install from hailo_model_zoo_genai via cmake, not a nonexistent repo (#1851) The installer defaulted HAILO_OLLAMA_REPO to hailo-ai/hailo-ollama, which does not exist (git ls-remote: Repository not found), so every Hailo-10H install failed at clone. The Hailo-Ollama server is not a standalone repo: it ships inside hailo-ai/hailo_model_zoo_genai and is built from source there (its README: an Ollama-compatible API written in C++ on top of HailoRT). - Point the repo at hailo_model_zoo_genai, pinned to a real commit. - Replace the Python venv/pip build with the repo's actual cmake flow (configure -> build -> install), adding the C++ toolchain + OpenSSL deps. cmake --install lands the hailo-ollama binary in /usr/local/bin and manifests under /usr/local/share/hailo-ollama. - Resolve the binary from PATH (system install), not a venv. - Start the server bare (it has no serve subcommand or --port flag) and set the listen port via OLLAMA_HOST (bind 127.0.0.1 on the managed port 7836, off the banned default 8000), per the repo's docs/USAGE.rst. Not hardware-tested locally (no Hailo-10H here); validated by bash -n and the upstream build docs. doc62fr (#1771) tests live on real hardware and reports logs. * chore(deps): bump mcp in the uv group across 1 directory (#2010) Bumps the uv group with 1 update in the / directory: [mcp](https://github.com/modelcontextprotocol/python-sdk). Updates `mcp` from 1.27.2 to 1.28.1 - [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases) - [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md) - [Commits](https://github.com/modelcontextprotocol/python-sdk/compare/v1.27.2...v1.28.1) --- updated-dependencies: - dependency-name: mcp dependency-version: 1.28.1 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump immutable (#2090) Bumps the npm_and_yarn group with 1 update in the /desktop directory: [immutable](https://github.com/immutable-js/immutable-js). Updates `immutable` from 4.3.8 to 4.3.9 - [Release notes](https://github.com/immutable-js/immutable-js/releases) - [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/immutable-js/immutable-js/compare/v4.3.8...v4.3.9) --- updated-dependencies: - dependency-name: immutable dependency-version: 4.3.9 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * release: promote dev to master for v1.0.0-beta.44 (#2122) * fix(desktop): dialogs render above windows, and mint shows the URL and PIN (#2092) Two bugs on the same screen, both reported from live use. Window z-index was an unbounded counter: every open, focus, restore and recenter incremented nextZIndex forever, while portal overlays sit at a fixed z-[10001]. After enough focus switches in one session, windows rendered on top of modal dialogs. The stack is now renumbered to 1..N on each change, so window z stays far below the overlay layer regardless of session length, and relative order is preserved by sorting on the existing values first. This affected every portal overlay, not only the invite dialog. ProjectMembers closed the invite dialog in its onMinted handler, unmounting it before the result rendered. The invite URL and PIN are shown exactly once and cannot be recovered, so a successful mint looked like a silent failure. The parent now refreshes its member list only and the user closes the dialog once they have copied the credentials. * feat(agents): project_tasks_create scope so an external agent can author cards (#2098) An agent holding project_tasks could claim, close and comment on existing cards but never open one, so an approved grant bought nothing on that route. Rather than widen project_tasks, which is documented and tested as read plus lifecycle plus comments (Invariant 2 + 5) and would retroactively grant authoring to every agent already approved for it, authoring gets its own narrower scope that an owner opts into per agent. create_task now authorises through the same _authorize_task_actor as the other task routes, parameterised on scope, so existence-hiding 404s behave identically. The middleware allowlist admits POST .../tasks, which lets the token reach a handler that then verifies JWT, project binding and the narrower scope; project_tasks alone is still refused. Tests keep the original invariant (project_tasks alone cannot create) and add the halves that make it meaningful: the new scope DOES allow authoring, it is project-bound so a grant on A cannot create on B, and it does not widen member management. Authorship is attributed to the agent, not the project owner. * feat(library): item card component with thumbnail, status, artifacts, collection link (#2097) Add LibraryItemCard component per docs/design/library-app.md sections 2-4. Card shows thumbnail (or placeholder), title, kind badge, media duration, pipeline status per stage (jobs shape), artifact list (text, transcript, description, ocr) with preview, link-to-collection action, and a disabled Download stub until P3. Failure states are always visible -- no silent empties for missing thumbnails, pipeline stages, artifacts, or errors. Includes lib/library.ts with types and API client for the library store (items, artifacts, jobs) and 25 component tests covering pending, processing, ready, and error states. * feat(agents): enforce files_read/files_write so member agents can access project Files (#2100) * feat(agents): enforce files_read/files_write scopes so member agents can access project Files Project-files routes (/api/projects/{slug}/files*, mkdir, trash, stats) had no membership or scope gate and were absent from the agent middleware allowlist, so an agent token could not reach them at all while the files_read/files_write scopes existed but were never enforced. This wires them up, mirroring the canvas pattern: - _authorize_files_actor resolves slug -> project and authorizes a session owner/admin (unchanged) OR an agent holding files_read (reads) / files_write (writes) grant bound to that project. A token bound to another project, or an unknown slug, collapses into an existence-hiding 404; a missing scope is 403. - _AGENT_FILES_ROUTES added to auth_middleware so agent JWTs pass through to the routes, which verify the grant. - InviteAgentDialog offers files_read (default on) and files_write, so an owner can grant file access at invite time. Agents that are project members can now read the project's Files and add files via the API. Grant creation already grants these scopes generically, and membership is added via the always-on project_tasks scope. Adds tests/test_routes_project_files_agent_scope.py (10 cases covering read/write allow, missing-scope 403, cross-project 404, unknown-slug 404, session owner unchanged). * feat(agents): surface Files in the invite bundle + fix agent API-surface docs - build_connection_bundle now advertises the project Files endpoints and adds a Files capability section to the join guide when files_read/files_write are granted, plus task_create when project_tasks_create is granted, so a joining agent is told the Files API exists and how to reach it (slug-keyed paths). - docs/agent-coordination.md: drop the non-existent project_doc_review scope, add files_read/files_write, project_tasks_create, and decisions_write to the agent API-surface list, and correct the doc-gate note (it fires only on file add/delete, so it does not catch allowlist edits). - README: replace the understated read-only agent-surface sentence with the real scoped surface (tasks, canvas, files, decisions, a2a). Verified against VALID_SCOPES / _ALLOWED_SCOPES and the auth_middleware allowlist. Invite tests pass (36). * feat(providers): add Nous Portal as a cloud model provider (#2102) Nous Portal (Nous Research) is an OpenAI-compatible inference API serving the Hermes 4 family and frontier models. Wire it up as a first-class cloud provider so it can be added from the Providers app instead of a hand-configured openai-compatible endpoint. - providers/__init__.py: add 'nous' to ALL_TYPES + CLOUD_TYPES, and map it to the OpenAI LiteLLM prefix (api_base set explicitly, like kilocode). - routes/providers.py: default base URL https://inference-api.nousresearch.com/v1 and a seed model list (flagship Hermes models) for the case where /v1/models cannot be listed without a working credential. - backend_adapters.py: 'nous' uses the CloudAPIAdapter probe. - Frontend: add 'nous' to the cloud provider type lists and the Providers app metadata (label 'Nous Portal', default URL, description, key placeholder). Base URL and OpenAI-compatibility verified against Nous Portal docs. Backend provider suite passes (68); frontend tsc clean. * feat(desktop): Assistant Studio - a workspace for a personal-assistant agent (#2103) A new studio app where the user picks a registered agent to be their PA and works out of one hub. Left rail: Overview, Journal, Calendar/time, Tasks, Comms, Canvas, and a Deliverables (files/reports) area. The PA picker defaults to Hermes when present and persists the choice. Journal, Tasks, Calendar events and Deliverables persist locally per PA so switching PA swaps the whole workspace; Comms opens the live agent chat and Canvas points at the project canvas. MVP scope: self-contained, no new backend (localStorage-backed), so it is additive and safe. Accessible (labels, aria-current, keyboard add). Registered as an optional studio app. Backend wiring (real calendar, PA-scoped board/files) is a follow-up. tsc clean; frontend build passes. * feat(agents): request additional scopes for an existing agent identity (#1921) * feat(agents): request additional scopes for an existing agent identity Add a scope-request flow so an already-registered agent can gain more scope grants on its SAME canonical_id, instead of the auth-request flow which mints a new identity on approval (and 409s on an active-handle collision). Endpoints (in routes/agent_auth_requests.py): - POST /api/agents/registry/{cid}/scope-requests (create) - POST /api/agents/registry/{cid}/scope-requests/{id}/approve - POST /api/agents/registry/{cid}/scope-requests/{id}/deny Auth (security-critical): creation is gated to the agent's OWN registry bearer token (sub == canonical_id) OR the owning user / an admin, because the agent already holds credentials; an anonymous caller can never escalate an existing identity. The middleware allowlist exposes only the create path to a registry JWT; approve/deny are owner/admin only. Approval writes add_grant(cid, scope, project_id) per granted scope (idempotent via the UNIQUE key), never registers a second identity, and lets the admin narrow but not widen the requested scopes. decisions_read/decisions_write are grantable globally or per-project; project_tasks and canvas scopes still require an explicit project_id. Adds AgentScopeRequestsStore, a scope-agnostic check_agent_identity helper, and full route tests. VALID_SCOPES stays in sync with _ALLOWED_SCOPES. Fixes #1920 * fix(agents): fold scope-request approval security findings (#1921) Addresses the Kilo + CodeRabbit findings on the approve/create scope-request paths: - Major (project binding): approve_scope_request bound global-capable scopes (decisions_*) to effective_project, which fell back to the agent-named req.project_id when the operator gave no explicit project_id. Since an agent can self-request, that let a global scope bind to any project the operator never validated (cross-project escalation). Now grants bind ONLY to the operator's explicit body.project_id (None = global); the agent-named value is never a binding. - Atomicity + races: approve_scope_request wrote grants + membership before the set_decision flip with no lock, so concurrent approvals could double-grant. Wrapped the whole approval in the same per-request _get_approve_lock the consent path uses, with a pending re-check inside the lock (grant-before-flip is safe under the lock + idempotent add_grant). - Info leak: create_scope_request now authorizes BEFORE scope-vocabulary validation, so an unauthorized caller cannot probe whether a scope name is valid. Adds tests: global scope ignores an agent-supplied project_id (binds global), and create authorizes before vocab (403 not a 400 vocab leak). 16 tests pass. * fix(agents): take the per-request lock in deny_scope_request too (#1921) Kilo review: approve_scope_request now serializes concurrent approvals under _get_approve_lock; deny lacked the same lock, so a concurrent approve+deny of the same request was unserialized. Wrap the deny body in the same per-req lock with a pending re-check, matching approve and the sibling consent path. * fix(apps): register assistant-studio as an installable optional app (#2104) Assistant Studio (#2103) shipped in the frontend registry as optional but was not in the server-side optional-app catalog, so getLaunchableApps hid it (it is only shown once installed) and POST /api/apps/optional/assistant-studio/install returned 'not an optional app'. Add it to OPTIONAL_FRONTEND_APPS + the version / trust / provenance dicts, matching the other Creative Studios. * fix(shortcuts): resolve the container's real incus project (and start it) for terminal shortcuts (#2105) The agent terminal/TUI shortcuts opened an incus PTY with no --project flag, so incus used the client's default project (a per-user one like user-999). An agent whose container lives in a different project (e.g. a legacy container in 'default') failed with 'Failed to fetch instance taos-agent-<name> in project user-999: Instance not found', even though the container exists. Every other container op already resolves the real project via _resolve_container_project (--all-projects); the PTY path was the lone exception. _open_incus_pty now resolves the container's actual project and passes --project, and starts the container if it is stopped (incus exec fails on a non-running instance), so a dev-access shortcut works regardless of project or run state. Adds a sync _resolve_project_and_state_sync sibling to the async resolver. Tests: exec targets the resolved project + stopped container is started; a running container is not restarted. * test(scope-requests): add grant-enforcement E2E, tighten approve tolerance, add deny negative test (#2108) Three test gaps filled following PR #1921 merge: 1. E2E grant-enforcement test (test_approved_scope_grant_unlocks_route_e2e): agent self-requests decisions_write → admin approves → agent uses token on POST /api/decisions → assert 200. Proves the full grant-enforcement chain is wired end to end. Regression guard for issue #2095. 2. Tighten test_agent_cannot_approve_its_own_request: replace (401, 403) tolerance with exact 401. The middleware does not pass a registry JWT through to the approve handler (only the create path is allowlisted), so it falls to the session gate → 401 exactly. 3. Deny negative test (test_agent_cannot_deny_its_own_request): same auth model as approve — middleware does not allowlist the deny endpoint for registry JWTs, so an agent token on the deny endpoint falls through to the session gate → 401. Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com> * chore(deps): bump actions/setup-python from 6 to 7 (#2109) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump the python-deps group with 2 updates (#2110) Updates the requirements on [matrix-nio](https://github.com/matrix-nio/matrix-nio) and [litellm[proxy]](https://github.com/BerriAI/litellm) to permit the latest version. Updates `matrix-nio` from 0.25.2 to 0.26.0 - [Changelog](https://github.com/matrix-nio/matrix-nio/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-nio/matrix-nio/compare/0.25.2...0.26.0) Updates `litellm[proxy]` to 1.93.0 - [Release notes](https://github.com/BerriAI/litellm/releases) - [Commits](https://github.com/BerriAI/litellm/compare/v1.92.0...v1.93.0) --- updated-dependencies: - dependency-name: matrix-nio dependency-version: 0.26.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-deps - dependency-name: litellm[proxy] dependency-version: 1.93.0 dependency-type: direct:production dependency-group: python-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(release): v1.0.0-beta.44 version bump + changelog (#2121) * chore(release): v1.0.0-beta.44 version bump + changelog * docs(changelog): complete the beta.44 entry (dialog/mint fix, task-create scope) * test(desktop): add unit tests for AssistantStudioApp (#2116) * test(desktop): add unit tests for AssistantStudioApp * test(desktop): await the mount-time agents fetch in the first two Assistant Studio tests Qodo review: the first two tests rendered the component without awaiting its mount-time /api/agents fetch, so the resulting setState could land outside React Testing Library's act() and flake in stricter environments. The later tests in the same file already waited, so this was an inconsistency as much as a latent flake. Both now drain the fetch before asserting. * fix(library): wire up the unused source ingest option (#2117) * fix(library): wire up the unused source ingest option * fix(library): remove the unused source ingest option instead of sending it Review found that serializing source only moved the silent drop server-side: /api/library/ingest accepts file, url and title only, and LibraryStore has no source column (its source_url is already derived from the url). So the field was discarded either way, while now looking wired. The card offered wire-it-in or remove-it and I recommended wire-it-in without checking the backend, which was wrong. Removing it is the honest fix: no caller passes source, so nothing breaks, and a dead option no longer implies a feature that does not exist. Capturing real source metadata is a backend feature, not a client nit. * chore(deps): bump the spa-deps group in /desktop with 17 updates (#2111) Bumps the spa-deps group in /desktop with 17 updates: | Package | From | To | | --- | --- | --- | | [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.19` | `1.1.23` | | [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.20` | `2.1.24` | | [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.11` | `2.1.15` | | [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.3` | `2.3.7` | | [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.3.0` | `1.3.3` | | [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.3` | `1.3.7` | | [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.17` | `1.1.21` | | [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.12` | `1.2.16` | | [@tiptap/extension-link](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-link) | `3.28.0` | `3.29.0` | | [@tiptap/extension-underline](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-underline) | `3.28.0` | `3.29.0` | | [@tiptap/pm](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/pm) | `3.28.0` | `3.29.0` | | [@tiptap/react](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/react) | `3.28.0` | `3.29.0` | | [@tiptap/starter-kit](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/starter-kit) | `3.28.0` | `3.29.0` | | [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.7` | `19.2.8` | | [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.7` | `19.2.8` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.61.1` | `1.62.0` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.3` | `6.0.4` | Updates `@radix-ui/react-dialog` from 1.1.19 to 1.1.23 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog) Updates `@radix-ui/react-dropdown-menu` from 2.1.20 to 2.1.24 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu) Updates `@radix-ui/react-label` from 2.1.11 to 2.1.15 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label) Updates `@radix-ui/react-select` from 2.3.3 to 2.3.7 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select) Updates `@radix-ui/react-slot` from 1.3.0 to 1.3.3 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot) Updates `@radix-ui/react-switch` from 1.3.3 to 1.3.7 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch) Updates `@radix-ui/react-tabs` from 1.1.17 to 1.1.21 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs) Updates `@radix-ui/react-tooltip` from 1.2.12 to 1.2.16 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip) Updates `@tiptap/extension-link` from 3.28.0 to 3.29.0 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-link/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.0/packages/extension-link) Updates `@tiptap/extension-underline` from 3.28.0 to 3.29.0 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-underline/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.0/packages/extension-underline) Updates `@tiptap/pm` from 3.28.0 to 3.29.0 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/pm/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.0/packages/pm) Updates `@tiptap/react` from 3.28.0 to 3.29.0 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/react/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.0/packages/react) Updates `@tiptap/starter-kit` from 3.28.0 to 3.29.0 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/starter-kit/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.0/packages/starter-kit) Updates `react` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react) Updates `react-dom` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom) Updates `@playwright/test` from 1.61.1 to 1.62.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.1...v1.62.0) Updates `@vitejs/plugin-react` from 6.0.3 to 6.0.4 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.4/packages/plugin-react) --- updated-dependencies: - dependency-name: "@radix-ui/react-dialog" dependency-version: 1.1.23 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-dropdown-menu" dependency-version: 2.1.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-label" dependency-version: 2.1.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-select" dependency-version: 2.3.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-slot" dependency-version: 1.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-switch" dependency-version: 1.3.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-tabs" dependency-version: 1.1.21 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@radix-ui/react-tooltip" dependency-version: 1.2.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@tiptap/extension-link" dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: "@tiptap/extension-underline" dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: "@tiptap/pm" dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: "@tiptap/react" dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: "@tiptap/starter-kit" dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: react dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: react-dom dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: spa-deps - dependency-name: "@playwright/test" dependency-version: 1.62.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: spa-deps - dependency-name: "@vitejs/plugin-react" dependency-version: 6.0.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: spa-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(agents): bind project_tasks_create and files scopes to a project on approval (#2127) * fix(agents): bind project_tasks_create and files scopes to a project on approval Review of the beta.44 promotion found that _SCOPE_PROJECT_SCOPES listed only project_tasks and the canvas scopes, so project_tasks_create, files_read and files_write could be approved with no project_id. The grant was then written global (project_id=None), and check_agent_scope_for_project only matches a grant bound to the project, so the operator believed they had granted access while the agent silently had none. Fails closed, but silently wrong is its own bug. Also replaces the em dashes in docs/agent-coordination.md with commas and colons per the house style, and records in project_files.py why the session path deliberately allows an unknown slug (lazily-created, slug-addressed files tree, documented by test_list_unknown_slug_returns_empty) while the agent path stays strict. 42 tests pass (project files, files agent scope, scope requests). * fix(projects): setting an agent as project lead now sets lead_member_id (#2113) add_agent_to_project wrote role='lead' on the member row but never called set_lead, which is the only writer of projects.lead_member_id. The member row is just a label; the pointer column is the actual lead. So the agent read as lead in the UI while every lead-gated check refused them. This is what happened to Hermes on taOSrabbit: role='lead', is_lead=0, lead_member_id NULL. Best-effort like the rest of that block, since the membership and grant already stand on their own. * fix(agents): one definition of which scopes require a project binding Qodo caught that the auth-request approval path still granted files_* and project_tasks_create globally. The project-scope set existed as three parallel copies (two function-local, one module-level), and the earlier fix only corrected the module-level one -- leaving the path an invite actually takes still writing those grants with project_id=None. check_agent_scope_for_project only matches a grant bound to that exact project, so a global grant never matches. The approval returns 200, the operator believes access was granted, and the agent silently has none. Now defined once and referenced everywhere; _SCOPE_* are plain aliases rather than rebuilt literals, since re-listing the members is how the copies drifted. Adds the regression test that was missing: nothing pinned this set, which is why three copies could disagree unnoticed. Checks alias identity (not equality) so a re-introduced copy fails even while it still happens to agree, asserts a single assignment per name in the module source, and asserts every project-bound scope is in VALID_SCOPES -- a typo there fails open, granting globally. * ci: raise the test timeout above the actual suite runtime (#2134) The cap was 45 min with a comment claiming 3.12/3.13 finish in ~16. Measured over the last 12 job records that is no longer true: 3.13 takes 31-41 min and 3.12 takes 38-41, so the cap sat roughly 4 minutes above the slowest normal run. Two of those 12 were killed mid-suite with nothing actually wrong, including the one gating #2127. A cap that close to the median does not catch hangs, it manufactures red PRs, and a timeout kill is indistinguishable from a real failure until you check the clock against timeout-minutes. That is the worst property a merge gate can have. 75 keeps a bound on a genuinely hung job while leaving real headroom. The suite growing from ~16 to ~40 min is its own problem and is filed separately; this stops it corrupting merge decisions in the meantime. * feat(feedback): add per-user 24h submission cap (#2131) * feat(feedback): add per-user 24h submission cap * fix(feedback): make the 24h cap atomic (Qodo) The cap did a SELECT COUNT then an INSERT as two awaited calls. Every sequential test passes and the limit still does not hold: concurrent requests all read the same count, all see room, and all insert. Verified against the pre-fix code, 8 racers with one slot left all got 201 and took a cap of 20 to 27. A user with a few tabs open trips this without trying, and a scripted client defeats it outright. Moved enforcement into the store as a single INSERT ... SELECT ... WHERE (SELECT COUNT(*) ...) < ?, so SQLite's write lock makes the check and the insert indivisible, and rowcount says which way it went. Adds the concurrency test that was missing, plus one asserting a rejected submission leaves no row behind: a partially-written reject would tighten the cap on every retry. * refactor(feedback): drop count_recent, orphaned by the atomic cap This PR added count_recent for the route to call before inserting. Moving the cap into create_within_cap left it with no callers anywhere in the tree, so it is dead on arrival rather than pre-existing code worth keeping. Removing it also removes the tempting wrong path: a future caller reaching for count_recent would reintroduce exactly the check-then-insert race the atomic statement exists to close. * test: replace always-true assert with issubclass check in test_installer_class_available (#1979) * fix(install): route LXC port allocation through centralized allocator Replace the standalone _find_free_port() in lxc_installer.py with allocate_host_port(app_id) from port_allocator.py so the centralized allocator is the single source of truth for all app host-port assignments. - Remove _find_free_port(), socket, and closing imports from lxc_installer - Import allocate_host_port instead of RESERVED_PORTS - Accumulate failed ports in exclude set across TOCTOU retry loop - Fix stale _docker_published_port docstring (DockerInstaller now maps {allocated_host_port}:{container_port}, not {p}:{p}) - Add test class verifying _find_free_port is gone and allocate_host_port is the only import Refs: #695 * test: strengthen allocator import assertion per Kilo suggestion Add assert not hasattr(mod, 'RESERVED_PORTS') to verify the old import is truly removed, not just that allocate_host_port is present. * test: replace always-true assert with issubclass check in test_installer_class_available --------- Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com> * feat(wallpaper): add Wallhaven proxy route + sectioned picker integration (#1902) * feat(wallpaper): add Wallhaven proxy route + browse-online picker section - Add wallhaven_api_key config field (env-only, never in repo) - Create GET /api/wallhaven/search proxy route to wallhaven.cc API - Keyless by default; optional X-API-Key header when WALLHAVEN_API_KEY set - Handle rate limiting (429), timeouts (504), and Wallhaven errors (502) - New WallhavenBrowser component: debounced search, thumbnail grid, pagination - Integrate WallhavenBrowser into WallpaperPicker as collapsible section - WallpaperPicker: "Browse online" toggle expands search UI, selecting a Wallhaven image applies it as a remote wallpaper - Backend tests (test_wallhaven.py, 12 tests) with respx mocking - Frontend tests (WallhavenBrowser.test.tsx, 8 tests; WallpaperPicker 14 tests) Fixes #864 * fix(wallpaper): escape CSS url, persist wallpaperIdByTheme, guard JSONResponse, validate categories/purity, move os import - Escape single-quotes and backslashes in remote wallpaper URLs to prevent CSS injection and render breakage (WallpaperPicker.tsx onSelect). - Route through a state updater that sets wallpaperIdByTheme, and include light/mobile/fallback variants so theme switch preserves remote wallpapers. - Use the received label as wallpaperOverlayText instead of discarding it. - Guard resp.json() with try/except ValueError and cap response at 1 MB (wallhaven.py:71). - Validate categories/purity with ^[01]{3}$ before forwarding to Wallhaven. - Move import os as _os from inside load_config to module top (config.py). * fix(wallpaper): harden CSS url() escaping — escape parens, strip control chars, validate http(s) scheme Kilo WARNING: CSS url…
Pins the behavior a non-Gitea LXC manifest (service_name, ports, ui_port, ui_path, state_paths) must get once the generic install path lands: systemd unit named from service_name, no dl.gitea.com binary download, no /etc/gitea/app.ini, declared ports published, state_paths created in-container, and admin_password not required when no admin user is declared. Each test is xfail(strict=True, reason=...) so they stay green today by failing as expected, and become XPASS failures the instant the implementation ships -- forcing the marker to be removed.
Acceptance test: generic (non-Gitea) LXC service install, xfail-strict until implemented
- scripts/check_store_wiring.py detects PRs that add a new BaseStore subclass without wiring it into tinyagentos/app.py - Uses name-level check (class name appears in app.py) - Only flags newly added classes; pre-existing orphans are skipped - Store-Unwired-Intentionally: <ClassName>, <why> trailer waives and logs - .github/workflows/store-wiring-gate.yml runs the check on PRs - tests/test_check_store_wiring.py proves FAIL, PASS, existing orphan not flagged, trailer waiver, and transitive subclass detection
…se, honest docs - hold SAFE_POINT state across cancel window, queue arriving messages, remove destructive _message_queue.clear() in handle_message - use asyncio.wait instead of asyncio.wait_for in await_subagent and await_all_subagents so a timeout never cancels the subagent task - reuse _create_supervised_task for progress and runner tasks, cancel_and_wait for bounded cancellation, prune settled subagents and delivered entries, switch UI timestamps to time.time() - update changelog to describe library as not-yet-wired infrastructure, add not-yet-integrated banner to design doc
Fix-forward #2335: safe-point race + wait_for cancellation + task_utils reuse + wiring design note
…models (per design doc)
Add the design law to 01-rules.md with a worked example anonymised as 'an agent', link it from index.md, and rebuild the compiled manual. Trim verbose prose in the image-prompting guide to stay within the compiled manual 18000-char budget. Update CHANGELOG.
Agent guides: add the mechanical-simple-auditable design law
…faces /auth/status and /auth/me validated sessions without the User-Agent the API middleware checks, so a session whose UA hash stopped matching (a browser auto-update rotates the UA string) read authenticated on status while every /api/* call returned 401. The SPA's LoginGate treats that contradiction as session-expired, re-checks status, gets authenticated, remounts the shell, and loops - the PWA refresh loop observed on the beta.46 deployment. The chat, canvas, terminal and web-chat WebSocket handlers had the inverse hole: they accepted a cookie the APIs reject. All six call sites now pass the request's User-Agent, so the stolen- cookie binding check gives one answer everywhere. Sessions created without a UA hash continue to validate regardless, unchanged.
fix(auth): apply session User-Agent binding uniformly across auth surfaces
…er gate on PR edits
…nstall The five Hailo-10H HEF manifests declared requires.backends without a targets list, so hardware_to_targets() never emitted a matching target and resolve() rejected every device, including a real Pi 5 + Hailo-10H. - hardware_to_targets(): add a hailo10h NPU branch emitting "hailo", mirroring the existing rockchip branch. - Each HEF manifest's hailo-ollama backend entry now declares targets: [hailo], matching the rkllm manifests' targets: [rockchip] convention. - Add tests/catalog/test_resolver_hailo.py: loads a real manifest from app-catalog and resolves it against a simulated Pi 5 + Hailo-10H profile (expects ResolveOk/hailo-ollama) and a CPU-only x86 profile (expects ResolveErr).
…pages sha256 was empty on all five HEF manifests, which silently skips integrity verification on download. Pulled the pinned per-model hef_h10h digests from hailo-ai/hailo_model_zoo_genai at tag v5.1.1 (the same release the download URLs point at) and spot-verified one (Qwen2-1.5B-Instruct.hef) by streaming the file and comparing sha256sum — exact match. size_mb was also stale on every entry; recomputed from the actual Content-Length of each dev-public.hailo.ai URL (bytes / 1048576, rounded), which corrected two entries significantly: qwen2.5-1.5b-instruct-hef 1679 -> 2250 and deepseek-r1-distill-qwen-1.5b-hef 2427 -> 2261. Updated the two matching "GB" labels for consistency. The other three shifted by only a few MB. llama3.2-3b-instruct-hef and qwen2.5-coder-1.5b-instruct-hef pointed their homepage at the base (non-Instruct) HuggingFace model card; both HEF variants are instruction-tuned, so point at the *-Instruct card instead.
…ntion Every other Llama 3.2 entry in the catalog uses a dashed llama-3.2-<size> id (llama-3.2-1b, llama-3.2-3b). The new HEF manifest dropped the dash before the version, so rename the directory and the manifest's id field to match: llama3.2-3b-instruct-hef -> llama-3.2-3b-instruct-hef.
CI check: a new BaseStore must be wired into the app lifespan (no orphan stores)
_MODEL_FILE_SUFFIXES drove both the downloaded-models listing and the orphan-file scan in routes/models.py, but didn't know about .hef — so files pulled by the new Hailo-10H HEF manifests would silently never show up as local files or be considered for orphan cleanup.
…ine route #2333 added StrikeStore and ProjectTaskStore's strikes= param but never constructed the store or passed it in, so quarantine/close-time strike clearing was dead code and the strikes= param was inert. - Construct StrikeStore in create_app(), init/close it in the lifespan alongside the other project-scoped stores, attach it as app.state.task_strikes, and pass it into ProjectTaskStore(strikes=...). - Fix tinyagentos/projects/ids.py: StrikeStore.record_strike calls new_id("str") but "str" was never registered in ID_PREFIXES, so every strike record raised ValueError. Without this the feature never worked at all, wiring or not. - Surface strike_count + latest_strike on GET task detail. - Add a LEAD-only POST .../tasks/{id}/unquarantine route (mirrors the claimable curation gate), wired into the agent-token allowlist and docs/agent-coordination.md. - tests/conftest.py's client fixture bypasses the lifespan and manually inits every store it touches; task_strikes needed the same treatment or GET task detail 500s under that fixture. - Add tests/projects/test_strike_wiring.py exercising the real app lifespan (app.router.lifespan_context) rather than the bypass fixture, since the bypass would pass even with the wiring missing.
README's catalog counts were stale at 113 in four places (dev had already moved to 115 before this PR, and this PR's five HEF manifests bring it to 120 — verified by counting app-catalog/models/*/manifest.yaml directories in-tree). Also "verified against HuggingFace" is no longer true now that the Hailo HEF manifests point at dev-public.hailo.ai, so reword to "its upstream host", and call out the new Hailo-10H HEF variants alongside the existing RK3588 NPU mention.
routes/models.py now recognizes .hef files, which is a user-visible behaviour change and trips the doc-gate's user-visible-changelog rule (on_modify on tinyagentos/routes/*.py). Add the changelog.d fragment per docs/changelog-fragments.md instead of editing CHANGELOG.md directly.
AgentChatRouter drives OpenClaw ACP turns through one AgentLoop per agent
(replacing the per-agent asyncio.Lock): the turn-holder drives its turn,
then iteratively drives every message queued at the safe point, each with
its own trace id. reach_safe_point runs in a finally so a raising turn can
never wedge the loop in WORKING.
The desktop taOS agent chat endpoint serializes on app.state.taos_agent_loop,
fixing a race where two concurrent POSTs shared the opencode session with no
serialization. A concurrent request gets a queued-notice NDJSON frame; the
turn-holder surfaces queued message contents into its stream tail before the
final done frame (redrive out of scope).
New GET /api/taos-agent/status returns the desktop loop's status scoped to
state / current_turn_id / queued_count / subagents [{id, task, state,
started_at}] with subagent result/error stripped.
Refs: tsk-icpt4i
AppManifest declared no context_window field and from_dict never read the YAML value, so every catalog manifest loaded as 0. All consumers read it via getattr(manifest, "context_window", 0), so the chat context-window budget code (routes/store.py, routes/store_install.py, routes/taosmd.py, agent_chat_router.py, routes/agents.py, chat/reactions.py) always hit the 4000-token "unknown window" fallback. Add context_window: int = 0 to AppManifest (0 == unknown, preserving the existing getattr fallback semantics) and wire it into from_dict. Real windows now flow into history_token_budget, which floors tiny windows (e.g. rkllm 4096 -> 512) instead of defaulting to 4000. Tests (red-first): a manifest declaring context_window=4096 loads onto AppManifest; a manifest without it defaults to 0. Plus a build_context_window budget test for a known small 4096-token window, asserting oldest-first trimming under the resulting 512-token budget. #2338, #1740
Make 3-strike QUARANTINE loud: store + lifespan wiring + surfacing + tests (FULL slice, replaces tsk-glxi4e)
store-wiring gate v2: AST-level wiring check + rename-status files + waiver cleanup
context_window is inert repo-wide: AppManifest has no such field, every manifest value is silently dropped
…ack tests, changelog fragment - Remove wallpaperFitToClass: exported but used only by its own test; the real fit mapping lives in tokens.css attribute selectors. - Desktop reads wallpaperFit from the theme store like its other theme fields; the letterboxBg prop always equaled the fallback Desktop already computes, so the prop threading added nothing. - Drop the two background-color: var(--wallpaper-fallback) rules: that custom property is defined nowhere, and the inline fallback background on every .taos-wallpaper element wins over the sheet anyway. - Remove the duplicate device-id mint in App.tsx (getDeviceId mints lazily at store init, before the effect could run). - Make the default/invalid-value tests call loadWallpaperFit instead of asserting state they had just set; drop two tests that only exercised local string concatenation. Invalid-value test proven red against a validation-skipping mutation. - Restore the square-display comment in tokens.css; add the changelog fragment doc-gate asks for.
…e routes Sweep of the same class the scope-request routes fixed: PATCH, DELETE, rotate-tokens, and org PUT all returned 404-if-missing then 403-if-not-owner, disclosing id existence to any authenticated non-owner. All four now return the not-found response for non-owners, using the exact idiom of the GET route this file already documents as the reference implementation. Lifecycle routes (_transition) and the consent approve/deny routes check admin BEFORE any lookup, so they respond uniformly already and are unchanged. Byte-identical tests (status + body, non-owner vs nonexistent) for all four routes with a real non-admin user; PATCH test proven red against the old require_owner_or_admin behaviour. The import that check left orphaned is removed. Changelog fragment added for doc-gate.
Wallpaper fit options (fill/fit/stretch/center/tile), persisted PER DEVICE
…04, document contract The class sweep changed four write routes but missed four pre-existing tests asserting the old 403 in OTHER files (caught by CI shards): two rotate-tokens tests, the lifecycle PATCH non-owner test, and the org PUT non-owner test. All now assert the not-found 404 with docstrings explaining why. docs/agent-coordination.md gains the existence-hiding contract for the whole owner-gated registry surface (doc-gate agent-manual rule), including the warning that a 404 no longer proves nonexistence.
…es, route logs Folds the Kilo + CodeRabbit findings on the identical-response tests: .json() compares normalized objects so it cannot back the byte-identical claim; all seven tests now compare resp.content. The three scope-request tests also send their nonexistent probe from the SAME non-owner client instead of the admin fixture, since the contract under test is what one unprivileged caller can distinguish. The four registry write routes gain the same unknown-vs-not-owner server-side logs the scope-request routes already emit.
…reate check_agent_identity raises 401 (malformed token) or 403 (inactive agent) in-route, but create_scope_request 404s on an unknown target BEFORE auth runs, so a caller holding a bad token could distinguish existing targets (401/403) from nonexistent ones (404). The authorize helper now converts those raises into the uniform 404, logging the true cause server-side. Regression test: a suspended agent's validly signed token gets byte-identical 404s for an existing and a nonexistent target; proven red against the unguarded call. Also renames the create/approve/deny log labels from 403-not-owner to 404-not-owner to match what the routes actually return (CodeRabbit findings, both folded).
Existence-hiding 404 across ALL agent-registry routes (issue #2106, reported by hognek)
…k-u23vjy) Re-scoped fix-forward of closed duplicate PR #2183. Of the card's four defects, two are already resolved or impossible on dev: position-0 collision was fixed by #2265's atomic in-INSERT allocation (existing concurrency test guards it), and NULL list_id rows cannot exist (schema NOT NULL). The two real ones land here: - get_entry read cur.description outside the cursor context; moved inside. - reorder_entries left already-issued UPDATEs pending when one raised, so the next unrelated commit() flushed a half-applied reorder; now rolls back and re-raises, with a test proving the pending write neither survives immediately nor resurfaces via a later unrelated commit (proven red against the unguarded store).
…it() CodeRabbit's Major on #2361, folded: asyncio.CancelledError does not inherit Exception, so task cancellation mid-reorder left the issued UPDATEs pending exactly like the original hazard, and commit() sat outside the guard. The commit moves inside the try and the handler catches BaseException, rolling back before re-raising. Regression tests for both paths; the cancellation test proven red against the except-Exception guard.
fix(lists): get_entry cursor scope + reorder rollback on failure (tsk-u23vjy)
…(tsk-hfs6zv) routes/agent_model_api.py enforces its own consent-key auth (never resolves a model without a valid key, OpenAI-shaped 401 otherwise) but the middleware had no passthrough for /v1 paths, so every external OpenAI-compatible caller was rejected by the session gate before the handler ran - the surface was dead code from outside (found by hermes's live probe, bus 2380). Exempts exactly GET /v1/models and POST /v1/chat/completions, method-sensitive; /v1/anything-else and wrong-method requests stay session-gated, with tests for both directions. External-caller tests (no session cookie) proven red against the unmodified middleware, including a minted-key 200 end to end. The chat route's 501-until-seam behavior is untouched and now documented in agent-coordination.md.
fix(auth): consent-key passthrough for the /v1 agent-model surface (tsk-hfs6zv)
…r has NO protection (#2362) * chore(security): gate .gitignore secret ignores on master/dev/release/* Add scripts/check_secret_ignores.py, which asserts that the committed .gitignore (a) still contains every required secret-protection rule as an active line and (b) ignores a canonical set of secret-shaped paths (data/hub/identity.json, foo.key, creds.json, x.p8, y_credentials.json, ...) via git check-ignore. Covered by tests/test_check_secret_ignores.py, including a parametrized test that drops each required pattern from a copy of the real .gitignore and proves the guard goes red, plus a real-tree regression asserting this branch is green. .github/workflows/secret-ignores-gate.yml runs the guard on push and PR to master, dev, and release/*, so a dropped pattern fails the branch it lands on rather than being assumed during dev->master promotion. Docs: Secret-ignores gate section in the contributor skill and a post-promotion verification step in docs/RELEASING.md. Changelog fragment added. Refs tsk-laezfg. * fix(security): ignore data/.litellm_master_key and add it to the gate canon The LiteLLM proxy master key has a bare _key suffix, so neither *.key nor data/*.key matches it: the file has been live, untracked, and one git add -A away from staging on dev boxes (long-standing queue item). This PR is the reviewed route for exactly that class of rule, so the rule lands here with both gate signals covering it; removal proven red: SECRET-IGNORE FAIL: .gitignore is missing required protection patterns: - data/.litellm_master_key * docs(releasing): tag the check_secret_ignores fence as bash (MD040)
Collate 15 changelog.d fragments into the 1.0.0-beta.48 section (merged with the direct [Unreleased] entries under single headings), bump the four version files including uv.lock's normalized 1.0.0b48. Docs-Reviewed: release version bump; no CI, packaging or contribution rule change
chore(release): v1.0.0-beta.48 version bump + changelog
…ommits landed directly on master)
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
|
👋 Thanks for the PR! This one targets See CONTRIBUTING.md for the branch model. |
📝 WalkthroughWalkthroughThis release updates agent turn handling, task quarantine, Hailo model support, desktop wallpaper settings, authorization behavior, CI safeguards, storage consistency, documentation, and regression coverage. ChangesPlatform and release updates
Desktop and agent behavior
Task and route behavior
Sequence Diagram(s)sequenceDiagram
participant Client
participant taos_agent
participant AgentLoop
participant ACP
Client->>taos_agent: Submit chat request
taos_agent->>AgentLoop: Accept turn or queue message
AgentLoop->>ACP: Drive active turn
ACP-->>AgentLoop: Stream response and completion
AgentLoop->>AgentLoop: Reach safe point and drain queue
AgentLoop-->>taos_agent: Return frames and status
taos_agent-->>Client: Stream response frames
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review SummaryStatus: 9 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (13 files)
Fix these issues in Kilo Cloud Reviewed by step-3.7-flash · Input: 75.1K · Output: 16.5K · Cached: 379.4K |
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (17)
tests/conftest.py-359-362 (1)
359-362: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winClose
task_strikesduring fixture teardown.The fixture initializes this SQLite connection, but teardown never closes it. Repeated
clientfixtures leak database worker connections.Add
await task_strikes.close()afterawait project_task_store.close().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/conftest.py` around lines 359 - 362, Update the client fixture teardown to close task_strikes after project_task_store.close() by awaiting task_strikes.close(), ensuring the initialized SQLite connection is released before teardown completes.tinyagentos/projects/strike_store.py-21-23 (1)
21-23: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSet
zip()strictness explicitly.Line 23 triggers Ruff B905. Use
strict=Truebecause_row()requires exactly the six selected columns.Proposed fix
- return dict(zip(keys, r)) + return dict(zip(keys, r, strict=True))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/projects/strike_store.py` around lines 21 - 23, Update the _row function’s dict(zip(...)) call to pass strict=True, ensuring the six expected keys must match exactly six selected row values.Source: Linters/SAST tools
tests/test_routes_mcp.py-43-44 (1)
43-44: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the Ruff RUF059 warnings.
appis unused at Line 44 and Line 92. Rename it to_app.Proposed fix
- client, app = app_client + client, _app = app_client ... - client, app = app_client + client, _app = app_clientAlso applies to: 91-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_routes_mcp.py` around lines 43 - 44, Rename the unused app variable to _app in both test_list_servers_empty and the corresponding test around lines 91–92, while preserving the existing app_client unpacking and test behavior.Source: Linters/SAST tools
desktop/src/theme/tokens.css-206-223 (1)
206-223: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReset repetition for every non-tile fit.
After a user selects
tile, its attribute selector keepsbackground-repeat: repeat. Selectingfitorcenterdoes not restoreno-repeat, so the image can repeat in letterbox space. Setbackground-repeat: no-repeatfor all non-tile fit selectors.Proposed fix
.taos-wallpaper[data-wallpaper-fit="fill"] { background-size: cover; + background-repeat: no-repeat; } .taos-wallpaper[data-wallpaper-fit="fit"] { background-size: contain; + background-repeat: no-repeat; } .taos-wallpaper[data-wallpaper-fit="stretch"] { background-size: 100% 100%; + background-repeat: no-repeat; } .taos-wallpaper[data-wallpaper-fit="center"] { background-size: auto; background-position: center; + background-repeat: no-repeat; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/theme/tokens.css` around lines 206 - 223, Update the non-tile selectors in the wallpaper fit rules—fill, fit, stretch, and center—to explicitly set background-repeat to no-repeat, while preserving repeat only for the tile selector.desktop/src/stores/__tests__/wallpaper-fit.test.ts-62-69 (1)
62-69: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTest the Zustand store initializer.
This test calls
loadWallpaperFit()directly. It does not verify that a freshtheme-storemodule initializeswallpaperFitfromlocalStorage. PrepopulatelocalStorage, callvi.resetModules(), dynamically importtheme-store, and assertuseThemeStore.getState().wallpaperFit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/stores/__tests__/wallpaper-fit.test.ts` around lines 62 - 69, Update the “loads the persisted value as the initial store state” test to verify the Zustand initializer rather than calling loadWallpaperFit directly: prepopulate localStorage, reset modules with vi.resetModules(), dynamically import theme-store, and assert useThemeStore.getState().wallpaperFit equals the persisted value.desktop/src/stores/theme-store.ts-196-203 (1)
196-203: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse
randomId()ingetDeviceId(). Plain HTTP deployments do not providecrypto.randomUUID(). The caught exception makes the preference default to"fill"and prevents persistence. Add a regression test for the missing-randomUUIDpath.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@desktop/src/stores/theme-store.ts` around lines 196 - 203, Update getDeviceId() to generate new identifiers with the existing randomId() helper instead of crypto.randomUUID(), preserving localStorage persistence and returning the stored identifier. Add a regression test covering environments where crypto.randomUUID is unavailable..claude/skills/taos-development-skill/SKILL.md-451-453 (1)
451-453: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd a language identifier to the fenced block.
Line 451 opens a fenced code block without a language identifier. This violates markdownlint rule MD040.
Proposed fix
-``` +```text Store-Unwired-Intentionally: <ClassName>, <why>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/skills/taos-development-skill/SKILL.md around lines 451 - 453, Update the fenced block containing the Store-Unwired-Intentionally example to specify the text language identifier, using the existing block content unchanged.Source: Linters/SAST tools
tests/test_chat_exporter.py-227-239 (1)
227-239: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe UPDATE does not reliably target the message just inserted.
The subquery orders by
created_at DESC LIMIT 1. After the first iteration, rows already sharets, so SQLite can select any of the tied rows. The loop can then rewrite the same row repeatedly and leave a later message with its original timestamp. The test still passes, because it only asserts determinism and a count of 3, but it does not create the equal-timestamp scenario it targets. Update by the returned message id instead.💚 Proposed test change
for i in range(3): - await store.send_message( + msg = await store.send_message( channel_id="ch1", author_id="user1", author_type="user", content=f"msg{i}", content_blocks=[{"type": "paragraph", "text": f"msg{i}"}], ) await store._db.execute( - "UPDATE chat_messages SET created_at = ? WHERE id = (SELECT id FROM chat_messages ORDER BY created_at DESC LIMIT 1)", - (ts,), + "UPDATE chat_messages SET created_at = ? WHERE id = ?", + (ts, msg["id"]), ) await store._db.commit()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_chat_exporter.py` around lines 227 - 239, Update the test loop around store.send_message to capture the returned message identifier, then use that id in the chat_messages UPDATE instead of selecting the latest row by created_at. Keep assigning ts and committing each inserted message so the test reliably creates the intended equal-timestamp scenario.tinyagentos/chat/chat_exporter.py-188-191 (1)
188-191: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winA whitespace-only text block aborts the whole channel export.
flatten_bodyreturns the whitespace for a block such as{"type": "paragraph", "text": " "}, sobodyis truthy and thecontentfallback is skipped. The guard then raisesChatExportErrorandexport_channelfails for the entire channel. This contradicts the stated design that a block must never brick the export. Consider falling back tocontent, or to a placeholder, before raising.🛠️ Proposed fix
- if not body and msg.get("content"): + if not body.strip() and msg.get("content"): body = str(msg["content"]) if content_blocks and not body.strip(): - raise ChatExportError( - f"message {msg.get('id')!r} has content_blocks but empty body" - ) + body = f"[{len(content_blocks)} block(s), no text]"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/chat/chat_exporter.py` around lines 188 - 191, Update the message-body handling around flatten_body so whitespace-only flattened text does not trigger ChatExportError or abort export_channel. Treat body as unusable when it contains only whitespace, then fall back to the existing content value or an appropriate placeholder before the content_blocks empty-body guard; preserve the error only when no usable body or fallback exists.tinyagentos/agent_loop.py-436-436 (1)
436-436: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUnused
doneresult fromasyncio.waitin two methods. Both call sites unpackdone, pendingbut read onlypending, which Ruff reports as RUF059.
tinyagentos/agent_loop.py#L436-L436: renamedoneto_doneinawait_subagent.tinyagentos/agent_loop.py#L481-L481: renamedoneto_doneinawait_all_subagents.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/agent_loop.py` at line 436, Rename the unused done result to _done in both await_subagent and await_all_subagents when unpacking asyncio.wait results. Apply the change at tinyagentos/agent_loop.py lines 436-436 and 481-481; leave pending handling unchanged.Source: Linters/SAST tools
CHANGELOG.md-118-122 (1)
118-122: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDescribe the old Node 20 state in past tense.
Line 54 states that
spa-buildruns on Node 22. Line 122 states that CI pins Node 20. Change Line 122 to describe the Dependabot failure before this release changed CI.Proposed fix
- spa-build (jsdom 30 requires Node >=22.13; CI pins Node 20). + spa-build (jsdom 30 requires Node >=22.13; CI then pinned Node 20).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CHANGELOG.md` around lines 118 - 122, Update the changelog entry describing the grouped jsdom 30 Dependabot failure to use past tense for the former Node 20 CI configuration, while preserving the details about jsdom’s Node 22.13 requirement and the spa-build context.docs/design/radio-studio.md-325-328 (1)
325-328: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove Phase 2 commands from the Phase 1 chat contract.
Phase 1 excludes targeted sweeps and audio capture, but the chat contract advertises
/stareand/capture. The UI and agent would promise operations that do not ship. Remove these commands or mark them unavailable until the corresponding skills and routes exist.This finding uses the supplied Phase 1 cut and chat command list.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/radio-studio.md` around lines 325 - 328, Update the “Agent Chat” command list in the Phase 1 contract to remove or explicitly mark `/stare` and `/capture` as unavailable, while retaining the supported `/survey` and `/alert` commands.docs/design/agent-loop-subagents.md-64-64 (1)
64-64: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd language identifiers to all reported Markdown fences.
markdownlintMD040 flags each opening fence because it has no language identifier.
docs/design/agent-loop-subagents.md#L64-L64: use atextfence and remove the standalonetextmarker from the block body.docs/design/radio-studio.md#L100-L100: use abashfence.docs/design/radio-studio.md#L106-L106: use abashfence.docs/design/radio-studio.md#L265-L265: use atextfence.docs/design/radio-studio.md#L281-L281: use atextfence.docs/design/radio-studio.md#L334-L334: use atextfence.docs/design/radio-studio.md#L423-L423: use atextfence.docs/design/radio-studio.md#L451-L451: use atextfence.This finding is based on the supplied markdownlint results.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/agent-loop-subagents.md` at line 64, Update all listed Markdown fences to include the required language identifiers: use a text fence at docs/design/agent-loop-subagents.md:64-64 and remove the standalone text marker from its block body; use bash fences at docs/design/radio-studio.md:100-100 and 106-106; and use text fences at docs/design/radio-studio.md:265-265, 281-281, 334-334, 423-423, and 451-451.Source: Linters/SAST tools
docs/taos-agent-manual.md-45-46 (1)
45-46: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse ATX headings or make the separators unambiguous.
The
markdownlintresult reports setext headings in these changed sections. Convert intended headings to##or###, or insert a blank line when---is only a horizontal rule. This removes MD003 and keeps the manual structure stable.The finding is based on the supplied markdownlint result.
Also applies to: 265-268
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/taos-agent-manual.md` around lines 45 - 46, Update the affected sections in taos-agent-manual.md to replace intended setext headings with explicit ATX headings using the appropriate ## or ### level; where --- is intended only as a horizontal rule, separate it from surrounding content with a blank line. Preserve the manual’s existing heading hierarchy and content.Source: Linters/SAST tools
tests/test_model_manifest_integrity.py-200-205 (1)
200-205: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winParse
download_urlas a URL.
startswith("https://")accepts malformed values such ashttps://and does not validate a hostname. Parse the value and require an HTTPS scheme plus a non-empty hostname.This finding uses the supplied URL validation rule.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_model_manifest_integrity.py` around lines 200 - 205, Update the download_url validation rule in the manifest integrity test to parse the value as a URL rather than using startswith("https://"). Require both an HTTPS scheme and a non-empty hostname, while preserving the existing error reporting for invalid URLs.tests/test_model_manifest_integrity.py-206-210 (1)
206-210: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReject booleans as
size_mb.In Python,
boolis a subclass ofint. A manifest value ofsize_mb: truetherefore passes the current check and is treated as1. Usetype(size_mb) is int, or explicitly exclude booleans before checking that the value is positive.This finding uses the supplied size validation expression.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_model_manifest_integrity.py` around lines 206 - 210, Update the size_mb validation in the manifest integrity check to reject boolean values explicitly, since bool is an int subclass in Python. Use an exact-int check or exclude booleans before validating that size_mb is positive, while preserving the existing error reporting.tinyagentos/registry.py-50-50 (1)
50-50: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winValidate
context_windowat the manifest boundary.The
intannotation does not validate YAML data. A manifest can providenull, a string, a negative integer, or a boolean. Such a value violates the0 = unknowncontract and can break context budgeting. Reject or normalize non-negative integers, excluding booleans, before constructingAppManifest.This finding uses the supplied field declaration and
from_dict()assignment.Also applies to: 79-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/registry.py` at line 50, Validate context_window in from_dict() before constructing AppManifest: accept only non-boolean integers greater than or equal to zero, and reject or normalize null, strings, booleans, and negative values according to the manifest’s established validation behavior. Ensure both context_window declarations preserve the 0 = unknown contract.
🧹 Nitpick comments (9)
scripts/check_secret_ignores.py (3)
187-197: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueValidate
--repo-rootbefore running Git.If the caller passes a path that does not exist,
_read_gitignorereturns an empty string, and the first_run_gitcall raisesFileNotFoundErrororNotADirectoryErrorfromcwd. The user sees a traceback instead of a clear message.🛡️ Proposed fix
args = parser.parse_args(argv) repo_root = Path(args.repo_root) + if not repo_root.is_dir(): + print(f"secret-ignores-guard: --repo-root is not a directory: {repo_root}") + return 2 violations = check_secret_ignores(repo_root)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_secret_ignores.py` around lines 187 - 197, Validate the parsed repo_root in main before calling check_secret_ignores or any Git operation: require it to exist and be a directory, and report an argparse-style clear error for invalid paths instead of allowing cwd failures. Preserve the existing behavior for valid repository roots.
109-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStatic analysis flags both Git helpers identically, and both hits are false positives. Each script runs Git through a fixed argument list with no shell and no request-derived input, so
S603,S607, and the ast-grepsubprocess-from-requestrule do not describe a real risk. Add scoped suppressions only if CI enforces these rules.
scripts/check_secret_ignores.py#L109-L115: add# noqa: S603, S607to thesubprocess.runcall in_run_git, or configure a per-file ignore forscripts/.scripts/check_store_wiring.py#L47-L48: apply the same suppression to thesubprocess.runcall in_run_git, and to thegit showcall at lines 72-75.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_secret_ignores.py` around lines 109 - 115, Add scoped static-analysis suppressions for the fixed, non-shell Git invocations: annotate the subprocess.run call in scripts/check_secret_ignores.py (lines 109-115) and the _run_git subprocess.run call plus git show invocation in scripts/check_store_wiring.py (lines 47-48 and 72-75) with the required S603 and S607 ignores, or configure an equivalent per-file ignore for scripts/.Source: Linters/SAST tools
151-154: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueAdd the
--separator before the path argument.
git check-ignore --quiet <path>treats an argument that starts with-as an option. The currentSECRET_PATHSentries are all safe, but a future entry such as-config.keywould make Git report an unknown-option error. The script then returns exit code 129, andis_path_ignoredreportsFalse. The gate fails with a misleading message instead of a clear error.🛡️ Proposed fix
def is_path_ignored(path: str, repo_root: Path) -> bool: """True if `git check-ignore` reports `path` as ignored on `repo_root`.""" - result = _run_git(["check-ignore", "--quiet", path], repo_root) + result = _run_git(["check-ignore", "--quiet", "--", path], repo_root) return result.returncode == 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_secret_ignores.py` around lines 151 - 154, Update is_path_ignored to pass the Git option terminator "--" immediately before path in the check-ignore arguments, ensuring paths beginning with "-" are treated as operands while preserving the existing return-code handling.docs/RELEASING.md (1)
37-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider listing the pattern families instead of a partial pattern list.
The prose lists a subset of
REQUIRED_PATTERNS. The script also requires*.token,*.cred,data/.secrets_key,data/.seeded-agent-tokens.json,data/secrets.db*,data/*.key,data/*.token, and the newdata/.litellm_master_key. A partial list drifts as patterns are added.📝 Proposed wording
-The `secret-ignores-gate` runs on the `master` push (and on the PR merge result) -and confirms the promoted `.gitignore` still ignores every secret-shaped path it -did on `dev` -- `identity.json`, `*.key`, `*.p8`, `*credentials.json`, `*creds*.json` -and the `*_private.*` key shapes, plus the `secrets/` and `data/hub/` rules. Re-run -it by hand if a conflict resolution touched `.gitignore`: +The `secret-ignores-gate` runs on the `master` push (and on the PR merge result) +and confirms the promoted `.gitignore` still ignores every secret-shaped path it +did on `dev`. The authoritative list is `REQUIRED_PATTERNS` in +`scripts/check_secret_ignores.py`; it covers key material (`*.key`, `*.p8`, +`*_private.*`), credential files (`*credentials.json`, `*creds*.json`, +`identity.json`, `*.token`, `*.cred`) and the `secrets/`, `data/hub/` and +`data/` rules. Re-run it by hand if a conflict resolution touched `.gitignore`:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/RELEASING.md` around lines 37 - 45, Update the secret-ignores-gate documentation to describe the required pattern families comprehensively rather than enumerating a partial, hardcoded list. Reference the REQUIRED_PATTERNS categories, including secret file extensions, credential/key/token paths, private-key shapes, and secrets/data directory rules, while preserving the command for rerunning scripts/check_secret_ignores.py.scripts/check_store_wiring.py (2)
154-162: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winThe name-level fallback lets an unwired store pass the gate.
_is_wired_in_app_pyreturnsTruewhen the class name appears anywhere intinyagentos/app.py. A barefrom tinyagentos.projects.strike_store import StrikeStoresatisfies the regex. The store is then never assigned toapp.state, and the gate reports it as wired. This is the exact failure mode the guard exists to catch.Two further weaknesses:
re.sub(r"#.*", "", app_py_content)also strips#characters inside string literals, so the comment stripping is not reliable.- A class name mentioned only in a type annotation or a docstring also passes.
Consider reporting the fallback as a warning instead of a pass, or restricting the fallback to lines that also contain
app.state.♻️ Proposed tightening
def _is_wired_in_app_py(app_py_content: str, class_name: str) -> tuple[bool, str]: ast_ok = _is_wired_ast(app_py_content, class_name) if ast_ok: return True, "AST" code_only = re.sub(r"#.*", "", app_py_content) - name_ok = bool(re.search(rf"\b{re.escape(class_name)}\b", code_only)) + # Require the name to co-occur with an app.state assignment on the same + # line; a bare import must not satisfy the guard. + name_ok = bool(re.search( + rf"^.*app\.state\b.*\b{re.escape(class_name)}\b.*$", + code_only, + re.MULTILINE, + )) if name_ok: return True, "name-level-fallback" return False, "unwired"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_store_wiring.py` around lines 154 - 162, Tighten _is_wired_in_app_py so a bare class-name match cannot report a store as wired. Restrict any fallback to reliable, non-comment source that also demonstrates assignment to app.state, preferably using AST parsing rather than regex comment stripping; otherwise return unwired and preserve the existing AST success path.
165-184: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
build_class_hierarchykeys classes by bare name across the whole package.
classes[node.name] = basescollapses same-named classes from different modules. It also resolves a base class by bare name only. A localclass Base(...)in one module can make an unrelatedclass Foo(Base)in another module look like aBaseStoresubclass, or hide a real one. The result is a false positive or a false negative in the gate.For a repository guard this is likely acceptable. If collisions appear, key the map by
module:ClassNameand resolve bases through the file's imports.Also applies to: 205-221
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check_store_wiring.py` around lines 165 - 184, Update build_class_hierarchy to identify classes by their module-qualified names rather than bare class names, preserving separate entries for same-named classes in different files. Resolve each class’s base references within its module and through that module’s imports, so local or unrelated classes cannot be mistaken for the intended store hierarchy; apply the same qualified-name handling to the downstream logic around the additionally affected range.tinyagentos/projects/lists_store.py (1)
177-179: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
strict=tozip()as Ruff B905 requires.
cur.descriptionandrowalways have equal length here, sostrict=Trueis safe and silences the lint warning.♻️ Proposed change
- return dict(zip(keys, row)) + return dict(zip(keys, row, strict=True))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/projects/lists_store.py` around lines 177 - 179, Update the dict construction in the cursor row conversion to call zip with strict=True, preserving the existing keys and row pairing while satisfying Ruff B905.Source: Linters/SAST tools
tests/test_chat_context_window.py (1)
47-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe suffix assertion is vacuous because all messages are identical.
Every message has the same content, so line 55 passes for any kept subset, including a non-contiguous or oldest-first one. Give each message a distinct marker to prove the kept set is the newest contiguous suffix.
💚 Proposed test change
- message = "x" * 400 # 100 tokens per message - msgs = [_msg("user", message) for _ in range(20)] # 2000 tokens total + # Distinct prefix per message, same token cost (100 tokens each). + contents = [f"{i:02d}" + "x" * 398 for i in range(20)] # 2000 tokens total + msgs = [_msg("user", c) for c in contents] ctx = build_context_window(msgs, limit=20, max_tokens=budget) total = sum(estimate_tokens(m["content"]) for m in ctx) assert total <= budget assert len(ctx) < 20 # Oldest dropped first -> the kept set is a contiguous suffix. - assert [m["content"] for m in ctx] == [message] * len(ctx) + assert [m["content"] for m in ctx] == contents[-len(ctx):]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_chat_context_window.py` around lines 47 - 55, Update the test data in the context-window test around build_context_window so each message has a distinct, position-dependent marker while retaining the intended token budget. Change the final assertion to compare the retained messages against the expected newest contiguous suffix, ensuring the test detects non-contiguous or oldest-first selection.tests/test_model_manifest_integrity.py (1)
18-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExpose catalog targets as data instead of parsing source text.
Changing
hardware_to_targets()from literaltargets.append(...)calls to a constant, helper, orextend()call can make this test fail or omit valid targets. Expose a shared target enum or constant fromtinyagentos/cluster/capabilities.pyand import it here. Testhardware_to_targets()behavior separately.This finding uses the supplied target extraction and capability implementation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_model_manifest_integrity.py` around lines 18 - 37, Replace the source-text regex extraction used to build KNOWN_TARGETS with an imported shared target enum or constant exposed by capabilities.py. Update hardware_to_targets() to derive its accepted targets from that shared definition, and keep this test focused on manifest integrity while covering hardware_to_targets() behavior separately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/design/agent-loop-subagents.md`:
- Around line 48-53: Revise the cancellation design around cancel_subagents()
and reach_safe_point so subagents do not resume the main loop while inside
side-effecting tool or file operations. Define cooperative cancellation or
rollback at those boundaries, and specify the bounded-timeout outcome, including
how unfinished work is handled, before transitioning the loop to IDLE.
- Around line 88-105: Update the desktop endpoint flow around AgentLoop and
reach_safe_point() so every queued message is either redriven as its own agent
turn before the stream closes or explicitly returned as a retry result; do not
leave queued requests handled only by the turn-holder’s stream. Revise the
“Still not integrated” documentation to describe the implemented behavior
accurately and remove the claim that queued messages are merely surfaced without
redrive.
In `@docs/design/radio-studio.md`:
- Around line 64-72: Add normalized vendorid and productid fields to the SdrInfo
dataclass, populated by SDR detection and preserved through the LXC and Docker
passthrough paths. Ensure both backends consume these fields when constructing
USB device descriptors, including the documented empty or unknown-value
behavior.
- Around line 184-202: Update the hackrf_spectrum_survey tool_schema and its
route contract to validate device-derived frequency bounds, require start_hz <
stop_hz, constrain gain and bin_width_hz, and cap the frequency span or
resulting bin count. Ensure the route applies a timeout and propagates
cancellation before forwarding requests, preserving only bounded, valid survey
operations.
- Around line 95-113: Update the container passthrough design around
create_container() and usb_devices so raw USB access cannot bypass receive-only
restrictions. Route SDR access through a host-side receive-only broker or
allowlisted device API, or enforce TX disable in hardware, and document the
selected mechanism. Add a container-level test that attempts a transmit
operation and verifies it is denied.
In `@docs/mirror-policy.md`:
- Around line 25-38: Update the Hailo `.hef` exception documentation to state
that installation is unavailable if the vendor CDN disappears unless a legally
approved fallback is established. Keep the existing SHA-256 integrity checks,
but remove claims that a pinned digest proves licensing or provenance, and avoid
promising future mirroring without an approved source.
In `@tests/test_lxc_installer.py`:
- Around line 674-721: Update the _run_generic_install test helper to patch
containers.destroy_container alongside the other container operations, using an
async mock that prevents real Incus calls during install rollback while
preserving the helper’s existing result and proxy assertions.
In `@tests/test_model_manifest_integrity.py`:
- Line 46: Harden the checksum exemption around _SHA256_ALLOWLIST so newly added
manifest IDs cannot silently bypass sha256 validation. Compare the allowlist
against a reviewed baseline checked by CI, or add an explicit approval mechanism
that fails when the set grows; preserve exemptions only for pre-approved
existing IDs in the checksum validation loop.
In `@tinyagentos/chat/chat_exporter.py`:
- Around line 209-246: Update the oversize-envelope truncation logic in the
exporter method containing the shown block so the budget accounts for JSON
escaping in the final envelope. After constructing the reference note and
candidate body, repeatedly measure _serialized_size(envelope) with the actual
body inserted and shrink the UTF-8 body until the serialized envelope is at or
below self._max_message_bytes, preserving valid UTF-8 and the truncation suffix.
In `@tinyagentos/projects/task_store.py`:
- Around line 475-482: Update the unquarantine flow around the task state commit
and _strikes.clear_strikes so strikes recorded after the task becomes open are
preserved: either clear only strikes created before the transition or perform
the reset atomically before committing/exposing the task to the ready pool. Add
a regression test that interleaves a new verification failure between the state
transition and strike reset and verifies the new strike remains.
- Around line 470-473: Update the unquarantine SQL transition in the
project-task restoration logic to set claimed_by and claimed_at to NULL
alongside status = 'open'. Add a regression test covering the claimed →
quarantined → unquarantined → claimed flow and verify the task can be claimed
again.
In `@tinyagentos/routes/auth.py`:
- Around line 663-664: Reject requests with a missing User-Agent when validating
sessions bound to a user-agent hash: update tinyagentos/routes/auth.py lines
663-664 to enforce this before accepting the session, and apply the same
rejection before accepting the WebSocket in tinyagentos/routes/canvas.py lines
82-88. Add no-User-Agent coverage for /auth/status and /auth/me in
tests/test_auth.py lines 1225-1263.
In `@tinyagentos/routes/channel_hub.py`:
- Around line 268-274: Update validate_session in tinyagentos/auth.py to reject
missing user_agent whenever user_agent_hash exists, while preserving access for
legacy sessions without a stored hash. In tinyagentos/routes/channel_hub.py
lines 268-274 and tinyagentos/routes/chat.py lines 114-120, pass
websocket.headers.get("user-agent") directly without an empty-string fallback so
missing headers remain detectable.
In `@tinyagentos/routes/models.py`:
- Line 44: Update delete_model() to use the same recursive model-discovery and
ownership logic as get_downloaded_models(), including nested .hef files, instead
of a top-level models_dir glob. Ensure successful deletion of a nested .hef file
calls mark_uninstalled(). Add a regression test covering deletion from the
documented nested layout.
In `@tinyagentos/routes/terminal.py`:
- Around line 26-30: Update the WebSocket authentication flow around
auth_mgr.validate_session and the corresponding API middleware to reject
requests with a missing User-Agent when the session is User-Agent-bound, while
continuing to accept legacy sessions without user_agent_hash. Preserve
validate_session behavior for session_user() callers unless they are updated to
pass the request User-Agent, and add regression tests covering bound-session
rejection and legacy-session acceptance.
---
Minor comments:
In @.claude/skills/taos-development-skill/SKILL.md:
- Around line 451-453: Update the fenced block containing the
Store-Unwired-Intentionally example to specify the text language identifier,
using the existing block content unchanged.
In `@CHANGELOG.md`:
- Around line 118-122: Update the changelog entry describing the grouped jsdom
30 Dependabot failure to use past tense for the former Node 20 CI configuration,
while preserving the details about jsdom’s Node 22.13 requirement and the
spa-build context.
In `@desktop/src/stores/__tests__/wallpaper-fit.test.ts`:
- Around line 62-69: Update the “loads the persisted value as the initial store
state” test to verify the Zustand initializer rather than calling
loadWallpaperFit directly: prepopulate localStorage, reset modules with
vi.resetModules(), dynamically import theme-store, and assert
useThemeStore.getState().wallpaperFit equals the persisted value.
In `@desktop/src/stores/theme-store.ts`:
- Around line 196-203: Update getDeviceId() to generate new identifiers with the
existing randomId() helper instead of crypto.randomUUID(), preserving
localStorage persistence and returning the stored identifier. Add a regression
test covering environments where crypto.randomUUID is unavailable.
In `@desktop/src/theme/tokens.css`:
- Around line 206-223: Update the non-tile selectors in the wallpaper fit
rules—fill, fit, stretch, and center—to explicitly set background-repeat to
no-repeat, while preserving repeat only for the tile selector.
In `@docs/design/agent-loop-subagents.md`:
- Line 64: Update all listed Markdown fences to include the required language
identifiers: use a text fence at docs/design/agent-loop-subagents.md:64-64 and
remove the standalone text marker from its block body; use bash fences at
docs/design/radio-studio.md:100-100 and 106-106; and use text fences at
docs/design/radio-studio.md:265-265, 281-281, 334-334, 423-423, and 451-451.
In `@docs/design/radio-studio.md`:
- Around line 325-328: Update the “Agent Chat” command list in the Phase 1
contract to remove or explicitly mark `/stare` and `/capture` as unavailable,
while retaining the supported `/survey` and `/alert` commands.
In `@docs/taos-agent-manual.md`:
- Around line 45-46: Update the affected sections in taos-agent-manual.md to
replace intended setext headings with explicit ATX headings using the
appropriate ## or ### level; where --- is intended only as a horizontal rule,
separate it from surrounding content with a blank line. Preserve the manual’s
existing heading hierarchy and content.
In `@tests/conftest.py`:
- Around line 359-362: Update the client fixture teardown to close task_strikes
after project_task_store.close() by awaiting task_strikes.close(), ensuring the
initialized SQLite connection is released before teardown completes.
In `@tests/test_chat_exporter.py`:
- Around line 227-239: Update the test loop around store.send_message to capture
the returned message identifier, then use that id in the chat_messages UPDATE
instead of selecting the latest row by created_at. Keep assigning ts and
committing each inserted message so the test reliably creates the intended
equal-timestamp scenario.
In `@tests/test_model_manifest_integrity.py`:
- Around line 200-205: Update the download_url validation rule in the manifest
integrity test to parse the value as a URL rather than using
startswith("https://"). Require both an HTTPS scheme and a non-empty hostname,
while preserving the existing error reporting for invalid URLs.
- Around line 206-210: Update the size_mb validation in the manifest integrity
check to reject boolean values explicitly, since bool is an int subclass in
Python. Use an exact-int check or exclude booleans before validating that
size_mb is positive, while preserving the existing error reporting.
In `@tests/test_routes_mcp.py`:
- Around line 43-44: Rename the unused app variable to _app in both
test_list_servers_empty and the corresponding test around lines 91–92, while
preserving the existing app_client unpacking and test behavior.
In `@tinyagentos/agent_loop.py`:
- Line 436: Rename the unused done result to _done in both await_subagent and
await_all_subagents when unpacking asyncio.wait results. Apply the change at
tinyagentos/agent_loop.py lines 436-436 and 481-481; leave pending handling
unchanged.
In `@tinyagentos/chat/chat_exporter.py`:
- Around line 188-191: Update the message-body handling around flatten_body so
whitespace-only flattened text does not trigger ChatExportError or abort
export_channel. Treat body as unusable when it contains only whitespace, then
fall back to the existing content value or an appropriate placeholder before the
content_blocks empty-body guard; preserve the error only when no usable body or
fallback exists.
In `@tinyagentos/projects/strike_store.py`:
- Around line 21-23: Update the _row function’s dict(zip(...)) call to pass
strict=True, ensuring the six expected keys must match exactly six selected row
values.
In `@tinyagentos/registry.py`:
- Line 50: Validate context_window in from_dict() before constructing
AppManifest: accept only non-boolean integers greater than or equal to zero, and
reject or normalize null, strings, booleans, and negative values according to
the manifest’s established validation behavior. Ensure both context_window
declarations preserve the 0 = unknown contract.
---
Nitpick comments:
In `@docs/RELEASING.md`:
- Around line 37-45: Update the secret-ignores-gate documentation to describe
the required pattern families comprehensively rather than enumerating a partial,
hardcoded list. Reference the REQUIRED_PATTERNS categories, including secret
file extensions, credential/key/token paths, private-key shapes, and
secrets/data directory rules, while preserving the command for rerunning
scripts/check_secret_ignores.py.
In `@scripts/check_secret_ignores.py`:
- Around line 187-197: Validate the parsed repo_root in main before calling
check_secret_ignores or any Git operation: require it to exist and be a
directory, and report an argparse-style clear error for invalid paths instead of
allowing cwd failures. Preserve the existing behavior for valid repository
roots.
- Around line 109-115: Add scoped static-analysis suppressions for the fixed,
non-shell Git invocations: annotate the subprocess.run call in
scripts/check_secret_ignores.py (lines 109-115) and the _run_git subprocess.run
call plus git show invocation in scripts/check_store_wiring.py (lines 47-48 and
72-75) with the required S603 and S607 ignores, or configure an equivalent
per-file ignore for scripts/.
- Around line 151-154: Update is_path_ignored to pass the Git option terminator
"--" immediately before path in the check-ignore arguments, ensuring paths
beginning with "-" are treated as operands while preserving the existing
return-code handling.
In `@scripts/check_store_wiring.py`:
- Around line 154-162: Tighten _is_wired_in_app_py so a bare class-name match
cannot report a store as wired. Restrict any fallback to reliable, non-comment
source that also demonstrates assignment to app.state, preferably using AST
parsing rather than regex comment stripping; otherwise return unwired and
preserve the existing AST success path.
- Around line 165-184: Update build_class_hierarchy to identify classes by their
module-qualified names rather than bare class names, preserving separate entries
for same-named classes in different files. Resolve each class’s base references
within its module and through that module’s imports, so local or unrelated
classes cannot be mistaken for the intended store hierarchy; apply the same
qualified-name handling to the downstream logic around the additionally affected
range.
In `@tests/test_chat_context_window.py`:
- Around line 47-55: Update the test data in the context-window test around
build_context_window so each message has a distinct, position-dependent marker
while retaining the intended token budget. Change the final assertion to compare
the retained messages against the expected newest contiguous suffix, ensuring
the test detects non-contiguous or oldest-first selection.
In `@tests/test_model_manifest_integrity.py`:
- Around line 18-37: Replace the source-text regex extraction used to build
KNOWN_TARGETS with an imported shared target enum or constant exposed by
capabilities.py. Update hardware_to_targets() to derive its accepted targets
from that shared definition, and keep this test focused on manifest integrity
while covering hardware_to_targets() behavior separately.
In `@tinyagentos/projects/lists_store.py`:
- Around line 177-179: Update the dict construction in the cursor row conversion
to call zip with strict=True, preserving the existing keys and row pairing while
satisfying Ruff B905.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 116378a0-c105-4f8c-a440-d4b2c397ab95
⛔ Files ignored due to path filters (2)
desktop/package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.jsonuv.lockis excluded by!**/*.lock,!**/uv.lock
📒 Files selected for processing (82)
.claude/skills/taos-development-skill/SKILL.md.github/workflows/ci.yml.github/workflows/secret-ignores-gate.yml.github/workflows/store-wiring-gate.yml.gitignoreCHANGELOG.mdREADME.mdapp-catalog/models/deepseek-r1-distill-qwen-1.5b-hef/manifest.yamlapp-catalog/models/llama-3.2-3b-instruct-hef/manifest.yamlapp-catalog/models/qwen2-1.5b-instruct-hef/manifest.yamlapp-catalog/models/qwen2.5-1.5b-instruct-hef/manifest.yamlapp-catalog/models/qwen2.5-coder-1.5b-instruct-hef/manifest.yamldesktop/package.jsondesktop/src/App.tsxdesktop/src/apps/GuidesApp.test.tsxdesktop/src/apps/SettingsApp.tsxdesktop/src/apps/agents/RegistryPanel.test.tsxdesktop/src/apps/agents/RegistryPanel.tsxdesktop/src/components/Desktop.tsxdesktop/src/stores/__tests__/wallpaper-fit.test.tsdesktop/src/stores/theme-store.tsdesktop/src/theme/tokens.cssdocs/RELEASING.mddocs/agent-coordination.mddocs/agent-manual/01-rules.mddocs/agent-manual/10-image-prompting.mddocs/agent-manual/index.mddocs/design/agent-loop-subagents.mddocs/design/radio-studio.mddocs/mirror-policy.mddocs/taos-agent-manual.mdpyproject.tomlscripts/check_secret_ignores.pyscripts/check_store_wiring.pytests/catalog/test_resolver_hailo.pytests/conftest.pytests/projects/test_lists_store.pytests/projects/test_strike_wiring.pytests/test_agent_chat_router.pytests/test_agent_loop.pytests/test_agent_registry.pytests/test_agent_scope_requests.pytests/test_auth.pytests/test_chat_context_window.pytests/test_chat_exporter.pytests/test_check_secret_ignores.pytests/test_check_store_wiring.pytests/test_lxc_installer.pytests/test_model_manifest_integrity.pytests/test_registry.pytests/test_registry_governance_lifecycle.pytests/test_routes_agent_model_api.pytests/test_routes_agent_org.pytests/test_routes_mcp.pytests/test_routes_memory.pytests/test_routes_project_invites.pytests/test_taos_agent_chat.pytests/test_task_store.pytests/test_token_rotation.pytinyagentos/__init__.pytinyagentos/agent_chat_router.pytinyagentos/agent_loop.pytinyagentos/app.pytinyagentos/auth_middleware.pytinyagentos/chat/chat_exporter.pytinyagentos/cluster/capabilities.pytinyagentos/projects/ids.pytinyagentos/projects/lists_store.pytinyagentos/projects/strike_store.pytinyagentos/projects/task_store.pytinyagentos/registry.pytinyagentos/routes/agent_auth_requests.pytinyagentos/routes/agent_registry.pytinyagentos/routes/auth.pytinyagentos/routes/canvas.pytinyagentos/routes/channel_hub.pytinyagentos/routes/chat.pytinyagentos/routes/memory.pytinyagentos/routes/models.pytinyagentos/routes/projects.pytinyagentos/routes/taos_agent.pytinyagentos/routes/terminal.py
|
Adjudication of the 20 bot threads before resolving them (this promotion PR re-reviews the cumulative diff of 27 already-merged dev PRs; each finding was judged against the original change's review, not dismissed on venue): Real and carded — tsk-y6x6s4 (lead-held, red-first): the Deliberate design, already settled on the original PRs:
Spec commentary on design docs (radio-studio.md, agent-loop-subagents.md, mirror-policy.md): these documents are pre-build specs with their own cards; the concrete points (survey input bounds, SdrInfo vendor/product ids, subagent cancellation safe-points, queued-message redrive) are captured as build-time acceptance criteria on those tracks, not code defects in this tree. Test-quality notes (lxc_installer patch-set comparison, sha256 allowlist baseline, chat_exporter escaping budget): noted; the exporter truncation-vs-escaping point is plausible but bounded (envelope overshoot by escape bytes), queued for the next test-debt pass rather than blocking a version promotion. Resolving all threads on this basis; the release content itself was reviewed PR-by-PR on dev. |
Replaces #2365 (dev->master direct hit the strict up-to-date rule: master carries 3 Dependabot merges dev never got - h2 4.4.1, aiohttp 3.14.3, npm group). Same pattern as sync/dev-to-master-beta45 and release/promote-beta46: branch from dev + merge master, so the promoted tree is dev's content plus master's newer lockfile entries. Version lines verified intact after the lock merge (uv.lock 1.0.0b48, package.json 1.0.0-beta.48; test_version_lock_sync passes).
After this merges: back-merge master into dev to restore tree identity (diff dev master must be empty), then tag v1.0.0-beta.48 and cut the GitHub Release.
Closes out the tsk-laezfg / #2362 release-time verification via the secret-ignores-gate run on this PR and the master push.
Summary by CodeRabbit