Skip to content

release: promote dev to master (1.0.0-beta.39)#1776

Merged
jaylfc merged 17 commits into
masterfrom
dev
Jul 10, 2026
Merged

release: promote dev to master (1.0.0-beta.39)#1776
jaylfc merged 17 commits into
masterfrom
dev

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Promotion of dev to master for the 1.0.0-beta.39 release.

Includes the 16 PRs merged since beta.38: Game Studio AI textures/sprites (#1773), taOSgo Headscale mesh-join + persisted service credentials (#1770, #1772), authenticated A2A bus send (#1768), managed node-local backend services + model advertisement (#1756/1758/1760/1762), atomic VRAM check-and-reserve (#1725), audit hotfixes (#1767), rkllama 1.3.0 pin (#1764), catalog install-script fixes (#1694), and the least-privilege project-tasks agent scope (#1774). Full notes in CHANGELOG under [1.0.0-beta.39].

Summary by CodeRabbit

  • New Features

    • Game Studio can generate textures and sprites with prompt-based AI workflows, preview results, and insert assets into projects.
    • Added project-scoped task-board access for external agents and authenticated coordination-bus posting.
    • Added mesh joining/status support and secure credential persistence.
    • Added cluster model advertisements and managed backend service controls.
    • Added VRAM reservation safeguards for model operations.
  • Bug Fixes

    • Binary game assets are preserved during saves.
    • Improved backend installation, health checks, recovery, and manifest validation.
  • Documentation

    • Added guidance for agent installers, asset generation, mesh joining, and backend management.

jaylfc and others added 17 commits July 9, 2026 00:15
…aged systemd service (#1755)

Audit of how backends run on the Pi found the production rkllama was a
hand-started bare process (PPID 1, no unit), never adopted by systemd, so it
had no boot persistence and the Activity 'Restart AI Services' recovery
(routes/system.py) could not restart it. Root cause: the store wrapper
short-circuited with exit 0 the moment any process answered /api/tags on
7833/8080, before delegating to install-rknpu.sh, so a hand-start permanently
blocked the systemd unit from ever being created.

taOS manages backends as systemd services (boot persistence + recovery +
cluster placement/migration all depend on it), so the idempotent short-circuit
now also requires rkllama.service to be enabled. If it is not, we fall through
to install-rknpu.sh, whose install_systemd_unit adopts the running instance
under systemd (ExecStartPre reaps the orphan so the unit can bind the port).

Also corrects the routes/system.py comment: rkllama is a system unit (per
install-rknpu.sh), not a user unit.
* docs(design): cluster-aware backend service management spec

Phase 1 (build now): mandatory managed-service manifest contract + CI lint,
node-local backend service manager in the worker agent (ensure/self-heal/
start/stop/restart/health), #1743 rewired onto it, and a Backend Services
surface in the Cluster app. Phase 2 (spec'd, deferred): controller cross-node
reconciler + adaptive host-backend migration. Grounded in the backend-lifecycle
audit (qmd managed, rkllama hand-started; #1755 store-wrapper trap fix).

* docs(design): correct contract to extend existing lifecycle block (not a new managed block)
…hase 1.1) (#1756)

* feat(catalog): managed-backend-service manifest contract + CI lint

Phase 1, unit 1 of the cluster-aware backend service management design
(docs/design/cluster-backend-service-management.md).

A service manifest whose lifecycle.auto_manage is true must now declare
lifecycle.unit (systemd unit), lifecycle.scope (system|user), and
lifecycle.health (url + expect), so taOS can manage the backend as a systemd
service uniformly across cluster nodes. Enforced by a new
scripts/check_manifests.py managed-lint wired into the doc-gate workflow (the
existing audit-manifests.py is a one-off migration checker, not in CI).

Normalizes rkllama (the only auto_manage:true manifest today) to declare
rkllama.service / system / api-tags health. auto_manage:false backends
(rk-llama-cpp, llama-cpp, mlc-llm, openllm) are grandfathered until a later
slice flips them on with proper fields. 8 lint tests incl. the real catalog.

* fix(manifest-lint): harden auto_manage/YAML checks (fold Kilo review on #1756)

- auto_manage truthy-but-not-bool (quoted "true", 1) is now flagged instead of
  silently bypassing the contract via an identity check.
- an unparseable or non-mapping manifest.yaml is now an error, not a silent
  skip that read as clean.
- validate lifecycle.unit ends in .service and lifecycle.health.expect is a
  string (documented as a substring match on the health body).
4 new tests; 12 total, real catalog clean.
…1758)

Additive module tinyagentos/cluster/backend_services.py for the cluster-aware
backend service management design. Reusable, node-agnostic helpers:
- load_managed_backends(catalog_root): the managed backends declared in
  manifests (lifecycle.auto_manage + unit/scope/health).
- resolve_scope / unit_state: user-vs-system scope + installed/enabled/active.
- service_action(unit, verb): fail-soft systemctl start/stop/restart, promoted
  and generalised from routes/system.py::_restart_ai_unit (kill+reap on
  timeout, 'not installed' when no unit).
- health_probe(url, expect): httpx GET, substring assertion on the 200 body.

No existing behaviour changes; the #1743 recovery endpoint (unit 3) and the
worker-agent backend endpoints (unit 2b) will build on these. 12 tests
(mocked systemctl + httpx).
…ontract (Phase 1.3) (#1760)

Rewire the AI-stack recovery endpoint (POST /api/system/ai-stack/restart)
onto the node-local backend service manager (cluster/backend_services.py,
#1758) instead of the hardcoded AI_STACK_UNITS list and the private
_restart_ai_unit/_systemctl_rc helpers it duplicated.

The restart target set is now derived per node: core-managed units that have
no store manifest (qmd, installed by install-server.sh) unioned with the
store-declared managed backends (lifecycle.auto_manage in their catalog
manifest, e.g. rkllama), then narrowed to units whose systemd file actually
exists on this node. A backend that has migrated to another cluster node is
skipped rather than reported as a spurious failure, and a newly installed
managed backend joins recovery with no code change. Membership uses
systemctl cat (via unit_state) so a dead/crashed unit still counts.

A node with no managed backend installed now returns status "noop" instead
of a misleading "failed". A broken catalog still yields the core units, so
qmd never drops out of recovery.

_restart_ai_unit / _systemctl_rc scope-resolution + kill-reap logic already
moved to backend_services in #1758 and is tested there; the endpoint tests
now stub the derivation + service_action, and a new TestManagedAiUnits covers
the core+manifest union and the installed-on-this-node filter.

Docs-Reviewed: no route added or removed; endpoint path, method, auth gate,
and response shape are unchanged (internal target-derivation refactor only).
… fix, #1706) (#1725)

* feat(models): atomic VRAM check-and-reserve before model load (TOCTOU fix)

Adds VramReservationManager — an asyncio.Lock-protected VRAM reservation
system that atomically checks free VRAM (via nvidia-smi) and reserves the
required amount before a model load begins.  Concurrent callers see the
in-flight reservation and back off, preventing the TOCTOU race where two
model pulls both see enough free VRAM and both try to load, causing OOM.

Integration points:
- POST /api/models/pull  — reserve before ollama pull, release after
- POST /api/models/download (rkllama path) — reserve before installer,
  release in the background task's finally block
- GET  /api/models/vram-reservations — stats endpoint for monitoring

New files:
- tinyagentos/vram_reservation.py — atomic reserve/release manager
- tests/test_vram_reservation.py — 15 tests including concurrent safety

Wired as app.state.vram_reservation in the FastAPI lifespan alongside
the cluster manager and resource scheduler.

Issue: #1706

* docs: add TODO for per-model VRAM estimate replacement

jaylfc review: min_ram_mb heuristic is safe for v1 (over-reserves, fails
closed), but should be replaced with a real per-model VRAM estimate on
CUDA GGUF to avoid false 503s in multi-model setups.

---------

Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com>
…#1762)

* feat(cluster): advertise available models from Skald sidecar manifest

Add skald_manifest.py — parses /etc/taos/skald-models.json (env-configurable)
to enumerate which models a worker CAN load per the GPU catalog.

- WorkerInfo gains available_models list[dict] field (defaults to [])
- ClusterManager.heartbeat() derives available_models from backend
  entries, deduplicating model_ids across backends
- WorkerAgent.detect_backends() enriches each backend with available
  models from the sidecar manifest, tagging each as 'loaded' or
  'available' based on live probe results

Tests: 42 pass — 14 parser tests, 5 field serialization tests,
5 heartbeat derivation tests, 18 existing protocol tests (0 regressions).
Also verified zero regressions against 32 existing worker tests.

Completes end-to-end wiring for Skald PR #361 — the controller's
cluster view now shows both loaded and available models per worker.

* de-skald worker manifest: rename skald_manifest → worker_manifest

- Rename tinyagentos/worker/skald_manifest.py → worker_manifest.py
- Default manifest path: /etc/taos/skald-models.json → /etc/taos/worker-models.json
- Env var: TAOS_SKALD_MANIFEST → TAOS_WORKER_MANIFEST
- Remove all Skald-specific language from docstrings, comments, and field labels
- Update import in agent.py
- Rename test file, update all references, 14/14 pass
- Keep available_models field on WorkerInfo as generic (unchanged)

Core is now fork-agnostic: any external platform can write the manifest.
Skald-specific logic belongs in Skald's codebase.

---------

Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com>
…ches (#1764)

Re-pin TAOS_RKLLAMA_REF from 58038c95 (rknn-llm 1.2.3) to
dadea413 (feat/rknpu-1.3.0: upstream 1.3.0 base + the 6 taOS fork patches +
the rerank-ABI adaptation for the 1.3.0 options element). This ref was
deployed and smoke-verified on the RK3588 production Pi on 2026-07-09, so
fresh installs now get 1.3.0 for latest-model support instead of 1.2.3.

Also add a static patch-presence guard right after checkout (issue #1730):
a past ref bump silently dropped --preload and broke the preloaded
embed/rerank/expand models. The installer now refuses to build against any
ref whose source is missing --preload or the context-overflow payload, and
fails fast before the venv install and service start.

Docs-Reviewed: internal dependency-ref bump plus a defensive guard; no
change to documented install steps, flags, or user-facing behaviour.
…real min_ram_mb key (#1767)

Fixes the urgent findings from the full code audit of the recent merges
(follow-ups #1765/#1766 track the deeper redesign parts):

- worker_manifest.load_manifest: a malformed or non-object manifest file is
  external input and now degrades to the empty manifest with a logged
  warning instead of raising. Previously one typo'd comma in
  /etc/taos/worker-models.json crash-looped the whole worker under systemd.
- worker/agent.py enrichment: skip entries without model_id (logged) and
  wrap the whole enrichment in a fail-soft try/except so no manifest problem
  can take detect_backends down. Heartbeat send failures are now logged
  before being swallowed so a payload-build bug no longer masquerades as
  controller unreachability.
- vram_reservation: the probe now returns None (cannot know) distinct from
  (0, 0) (known-full), and reserve() fails OPEN with bookkeeping-only
  admission when no probe exists, matching cluster claim_lease semantics.
  Previously every reservation was denied 503 on AMD/Apple/Rockchip, i.e.
  on exactly the rkllama hardware the guard was written for. The probe also
  moved to asyncio.to_thread so nvidia-smi no longer blocks the event loop
  while holding the reservation lock. stats() gains probe_available.
- routes/models.py: the rkllama pull gate now reads the real backend-level
  requirement (max of variant.requires.backends[].min_ram_mb, falling back
  to the variant key). Every rkllm variant has variant-level min_ram_mb 0,
  so the previous read made the TOCTOU gate dead code against the entire
  catalog.
- cluster/backend_services.load_managed_backends: skipped manifests are now
  logged so a device-side catalog edit cannot silently drop a backend out
  of #1743 recovery.
- install-rknpu.sh: replace the stale copy-of-truth comment (old port 8080
  and pre-consolidation models path) with an accurate one.

Tests: the two tests that blessed the crash now assert the degrade
behaviour, plus new coverage for non-object manifests, no-probe fail-open,
probe-unavailable stats, and a regression guard that a real probe showing
insufficiency still denies.

Docs-Reviewed: behaviour hardening within existing features; no documented
install step, endpoint path, or response shape changed (installer diff is a
comment correction only).
…o spoofing) (#1768)

* feat(a2a): authenticated bus send proxy so agents post as themselves

The controller's A2A bus proxy was read-only; the only send path was the raw
:7900 bus, which is unauthenticated on the LAN and trusts its `from` field. So
a non-owner agent (e.g. @taOS-website-dev) had to ride the owner's account /SSH
to post, and any caller could spoof any identity.

Add POST /api/a2a/bus/send:
- Authorized senders: an admin session/local token (may set an explicit `from`),
  or an agent registry JWT holding the `a2a_send` scope.
- For an agent caller the bus `from` is DERIVED from the agent's own registry
  handle; a client-supplied `from` is ignored, so one agent cannot post as
  another.
- Fails 502 on bus error (a send must know it did not land), unlike the read
  proxies which degrade to an empty 200.
- Allowlisted in the auth middleware as a write path (own set), preserving the
  no-skeleton-key guard.

Tests: 7 new cases (from-derivation/anti-spoof, scope required, no-auth 401,
admin explicit-from, validation 400, bus-error 502). Docs: agent-coordination.md
gains the authenticated-send path; module docstring updated. doc-gate clean.

* fold Kilo review on #1768: sanitize admin from, trim body, validate reply_to

- Admin-supplied "from" is stripped of non-printable chars and capped at 64
  so it cannot inject newlines/control chars into the bus record or logs.
- The message body is trimmed before forwarding, consistent with thread.
- reply_to, when present, must be a positive message id (rejects 0/negative).
- 4 new tests cover the three folds.

* bound reply_to to the 64-bit id space (Kilo suggestion on #1768)
…1769)

* test(designstudio): add coverage for the untested Design Studio app

The designstudio/ directory shipped with zero tests. Add 105 vitest cases
across 11 files covering the pure logic and the view components, matching
the music/game studio testing conventions (fetch stubbing, controlled-host
rendering, react-konva mocked to inert passthroughs).

- types: hasFill/hasStroke type guards, isValidDesignContent validation, constants
- elementFactory: element creation, centering, zIndex stacking, id uniqueness, duplicate
- designs-api: list/get/create/update/rename/delete fetch wiring and error messages
- useElementHistory: commit/undo/redo, redo-stack clearing, empty-stack no-ops, history cap
- useHtmlImage: load/error lifecycle, crossOrigin, unmount cancellation
- LayersPanel: ordering, labels, select/reorder/visibility/delete, disabled edges
- PropertiesPanel: per-type controls, position display, duplicate/delete, disabled chips
- TemplatesView: grid, filter pills, selection dimensions
- MagicView: prompt/generate/style chips, needs-model and error states, results
- LibraryView: empty/error states, open, backend-confirmed delete, rename
- DesignView: toolbar add/undo, auto-select, brand-swatch gating, zoom controls

* fold Kilo suggestion on #1769: verify the history-cap eviction branch

The undo-cap test only asserted the most-recent snapshot after undo; now it
also walks undo to exhaustion and asserts the original (v0) state was evicted
and the undo depth is bounded by the 100-entry cap, exercising the eviction
branch. (The DesignView 'latest' state-isolation suggestion is already handled
by the existing afterEach reset.)
…in (Slice 1) (#1770)

* docs(design): taOSgo mesh-join foundation + Game Studio asset-generation specs

Two draft design specs for Jay review:
- taOSgo mesh-join foundation: unblocks Web Studio publish (#167) and off-LAN
  remote access. Controller has no mesh-join today; specs the credential store +
  poll-intercept persistence, headless Headscale join (system tailscale vs tsnet
  decision), and the publish caller + static-site server, in 3 shippable slices.
- Game Studio asset-generation (#55): tier-aware textures/audio/(stretch)3D
  reusing the existing image + MusicGen backends, spec -> build -> harden.

* feat(taosgo): persist per-host mesh service credentials at cluster-join (Slice 1)

The controller had no writer for the get_controller_token() seam: it read a
placeholder env var, so the taOSnet passkey fetch (and the future Web Studio
publish caller) had no credential once a host actually joined an account mesh.

Add tinyagentos/taosnet/mesh_credentials.py: a host-local 0600 store for the
per-host service tokens taos.my returns in the cluster-join ready payload
(controller_token = taosnet:passkey, sites_token = sites:publish). The
single-use headscale preauth key is intentionally NOT persisted. get_controller_
token()/get_sites_token() read it, with the TAOS_CONTROLLER_TOKEN/TAOS_SITES_
TOKEN env overrides still winning for dev/tests.

Wire it via a poll-intercept: routes/account_proxy.py::cluster_join_poll now
persists the ready-payload tokens server-side and strips them from the browser-
facing body, so a bearer credential never reaches browser JavaScript. Fail-soft:
non-200/non-JSON/unparseable bodies and save failures leave the response
untouched. passkey_client.get_controller_token() delegates to the new store.

This is Slice 1 of docs/design/taosgo-mesh-join-foundation.md: credentials only,
no mesh-join yet (Slice 2). It already makes the headless taOSnet passkey fetch
real once a host joins. Tests: store round-trip, 0600 perms, preauth-key not
persisted, idempotent re-poll, env override, and the intercept persist+strip +
fail-soft matrix (13). Existing account-proxy/taosnet suites stay green.

* fold Kilo review on #1770: decouple token-strip from save, harden loads

- account_proxy: the service tokens are now ALWAYS stripped from the browser
  body once seen in a 200 JSON response, whether or not the save succeeds. The
  previous fail-soft returned the unstripped body on a save error, leaking the
  bearer token (e.g. a payload with controller_token but no host_id raised in
  save). Parsing no longer depends on Content-Type, so a JSON body served
  without one cannot bypass stripping either.
- mesh_credentials._load(): a corrupt/externally-edited file that parses to a
  non-object (list/string) is treated as absent, so the getters can never raise
  AttributeError and break the headless passkey fetch.
- .gitignore: data/mesh_credentials.json (never commit a bearer-token file).
- 3 regression tests: strip-even-when-save-fails, corrupt-non-dict-file,
  json-without-content-type. 16 pass.
…ts (#1694)

* fix(catalog): fix 6 broken install-script references in agent manifests

Three agents switched from broken script references to pip install
(agent-zero, hermes, openclaw — all available on PyPI). Three agents
(deer-flow, moltis, picoclaw) had missing install scripts created.

Also renamed deer-flow's reference from the generic 'scripts/install.sh'
to 'scripts/install-deer-flow.sh' to avoid namespace collision.

Fixes #888

* docs: document agent framework install scripts (fix doc-drift gate)

Documents the three new agent framework install scripts added in the
catalog-broken-install-scripts fix:
- scripts/install-deer-flow.sh (clone + uv sync, Python 3.12)
- scripts/install-moltis.sh (cargo install from crates.io)
- scripts/install-picoclaw.sh (clone + cmake build)

Also notes that agent-zero, Hermes Agent, and OpenClaw use pip installs
via their catalog manifests, reflecting the manifest changes that switched
them from missing/broken script references to method: pip.

This satisfies the doc-gate 'installers' rule which requires README.md
edits when install scripts are added or removed.

* fix(catalog): revert agent-zero and openclaw to script-based install, fix picoclaw make install, correct deer-flow note

agent-zero and openclaw were switched to method: pip in the prior
catalog fix, but both PyPI names resolve to unrelated packages:

- agent-zero on PyPI = voice-agent framework with VAD/WebSocket routes,
  not frdel/agent-zero (autonomous AI agent)
- openclaw on PyPI = no matching package exists (CMDOP plugin)

Both reverted to clone-and-run via install-agent-zero.sh and
install-openclaw.sh. hermes-agent on PyPI is confirmed correct
(Nous Research — kept as pip).

Also fixed:
- scripts/install-picoclaw.sh: added make install step so the binary
  lands on PATH (was only building, never installing)
- app-catalog/agents/deer-flow/manifest.yaml: removed false claim that
  the script writes config.yaml+systemd (it doesn't)
- README.md: updated agent framework table and description

---------

Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com>
…e 2) (#1772)

* feat(taosgo): headless Headscale mesh-join via system tailscale (Slice 2)

Slice 2 of docs/design/taosgo-mesh-join-foundation.md. Jay chose system
tailscale (real kernel networking, boot-persistent) over userspace tsnet.

- tinyagentos/taosnet/mesh.py: async, fail-soft wrappers over the tailscale CLI:
  mesh_up (tailscale up --login-server hs.taos.my --authkey <preauth> --hostname),
  mesh_status (parse tailscale status --json -> joined/tailnet/node_ip),
  is_joined, mesh_down, is_tailscale_installed. A host without the binary
  degrades to a structured "not available" result; nothing raises into the join
  flow. Login server overridable via TAOS_HEADSCALE_URL.
- routes/account_proxy.py: the cluster-join poll-intercept now consumes the
  single-use headscale_preauth_key server-side to join the mesh (fire-and-forget
  background task, guarded by is_joined() to avoid re-join) and strips the
  preauth key from the browser body too. New GET /api/account/mesh/status for the
  Account pane.
- The strip decision stays decoupled from the join: secrets are always removed
  from the browser body whether or not the join succeeds.

Tests: 15 for mesh.py (up/status/down/installed-guard, arg shape, fail-soft
matrix) + 2 intercept tests (preauth stripped + join intent surfaced); Slice-1
intercept tests updated for the new tuple return. 42 pass across mesh/creds/
account-proxy. doc-gate clean.

FOLLOW-UP (Slice 2b, needs live Pi): install.sh must install tailscale +
tailscaled on supported hosts, and a real join against hs.taos.my must be
verified end to end. Until then this is inert on hosts without tailscale.

* fold Kilo review on #1772: always-strip preauth, single-use guard, task ref

- account_proxy: the intercept now fires on ANY secret in the body (service
  token OR preauth key), so a ready payload carrying only a preauth key is still
  stripped -- the "ALWAYS strip" guarantee no longer hinges on a service token.
  Persist runs only when a controller_token is present.
- Single-use key guard: a preauth key that already had a join attempted is not
  re-fired on a re-delivered ready poll (in-memory _attempted_preauth), since a
  single-use key cannot be retried anyway.
- create_task GC-safety: keep a strong reference to the in-flight join task
  (module set + done-callback discard) so it is not garbage-collected mid-run.
- mesh.py: mesh_status docstring corrected to reflect that detail is only on the
  not-available/error path, not the success dict.
- +1 regression test (preauth-only payload still stripped). 43 pass.
…lice 1) (#1773)

* feat(gamestudio): AI texture/sprite generation via ComfyUI backend (Slice 1)

Add offline texture/sprite generation to the Game Studio, driven by a
ComfyUI backend and gated by hardware tier, mirroring the Images Studio
tier-aware routing.

Backend:
- tinyagentos/asset_gen: async, fail-soft ComfyUI client (submit /prompt,
  poll /history, fetch /view) with a checked-in SDXL text-to-image workflow
  and a parameteriser (prompt/size/seed/checkpoint, tileable prompt hint).
- routes/game_assets.py: POST /api/games/{id}/assets/texture. Resolves the
  tier (catalog ComfyUI backend, else local GPU=SDXL / NPU=SD1.5, else none),
  returns 200 {available:false} when no accelerator, writes the PNG into the
  game's file set, and returns its preview path. Fail-soft on backend errors.
- games.py: preserve binary assets on save so a generated texture is not
  swept by the text-only full-replace.
- backend_catalog: comfyui advertises image-generation.

Frontend:
- AssetsPanel + game-assets-api + asset-reference, wired into EditorView as a
  Chat/Assets tab. Tier-gated "Needs a GPU worker" state, preview, and insert.

Tests: 51 Python (client/workflow/route/games) + 9 desktop (AssetsPanel,
asset-reference) all green. ComfyUI HTTP is fully mocked; no live install.

Docs-Reviewed: new asset route + gamestudio panel; ComfyUI is a store backend, no coordination-doc or README surface changes needed for Slice 1

* fix(gamestudio): fold Kilo review findings on the ComfyUI asset slice

- game_assets: 502 error body dropped the misleading `available:true` (the
  client throws on non-2xx and only reads `.error`; `available` is a 200-only
  tier-gate signal).
- game_assets: cap generated PNGs by per-image size (12MB) and per-game count
  (200) so a runaway request can't fill the disk; refuse at the cap with 409
  before spending a GPU call.
- game_assets: drop the NPU->SD1.5 tier. There is no wired NPU texture backend,
  so it resolved to a local ComfyUI that always 502s; return unavailable
  (needs-a-GPU-worker) instead of an always-failing tier.
- games: preserve generated binary assets on full-replace save by known binary
  extension first (png/jpg/webp/glb/wav/...), then the undecodable-bytes check,
  so a binary payload that happens to decode as UTF-8 is never swept.
- asset-reference: derive the JS const name from the filename so repeated
  inserts into one JS file don't emit a colliding `const assetUrl`.
- workflows: cache load_template (fixed small template set; callers deep-copy
  before mutating) so it isn't re-read+re-parsed from disk on every generation.

Tests updated/added: 502-body omits `available`, asset-count cap 409, distinct
JS const names. 52 python + 10 desktop pass.
…#1774)

* feat(agents): least-privilege project_tasks registry scope for kanban

Add a project_tasks registry-JWT scope so an approved external agent can
drive ONLY its own project's task board with its Ed25519 token, no session
cookie. This unblocks the executor lane (agent claims/updates/closes tasks).

Enforces five security invariants:
1. An agent token reaches task routes only for the project whose id equals
   the token's project_id claim; any other project is an existence-hiding
   404, identical to a non-owner session.
2. project_tasks grants task read + lifecycle + comments only. Task create,
   member management, and project lifecycle stay session owner/admin only.
3. The verified token's canonical_id is the authoritative actor; a lifecycle
   body actor id (claimer_id/releaser_id/closed_by/reopened_by) that differs
   is rejected 403 (an agent may only act as itself).
4. Session owner/admin behavior is unchanged (regression covered).
5. A registry JWT authenticates nothing off the allowlist (no skeleton key);
   the task-path passthrough uses an anchored, segment-strict regex.

Changes:
- agent_token_auth.py: extract a shared verifier and add
  check_agent_scope_for_project; check_agent_scope signature/behavior kept.
- auth_middleware.py: add _is_agent_task_path matcher and OR it into the
  registry-JWT passthrough.
- routes/agent_auth_requests.py: add project_tasks to VALID_SCOPES.
- routes/projects.py: dual-auth the allowed task routes via
  _authorize_task_actor and bind agent actor ids via _resolve_actor.

* fix(agents): bind comment author to the token id (close actor-impersonation)

add_comment gated task comments behind project_tasks but passed the body's
author_id straight to the store, so an agent caller could post a comment
authored as another identity. A comment author is an actor id like the four
lifecycle fields, so run it through _resolve_actor: an agent is pinned to its
canonical_id and a mismatched body author returns 403; a session owner keeps
its provided author_id. Tests: agent comment as another id -> 403; as self ->
stored under the token id.

* fix(agents): bind comment author_id to token identity (invariant 3)

add_comment gated an agent caller but then trusted the request body's
author_id, so an agent could post a comment authored as another identity.
Pin author_id to the verified token canonical_id via _resolve_actor exactly
like the lifecycle handlers: a mismatched body author is rejected 403, and an
omitted author is stored as the agent's own canonical_id.

author_id becomes optional so an agent may omit it (the server pins it); a
session caller that omits it is rejected 400 rather than inserting a NULL
author (the column is NOT NULL).

Audit of every agent-gated task route confirms each body-actor field is now
bound to the token for agent callers: claim (claimer_id), release
(releaser_id), close (closed_by), reopen (reopened_by), comment (author_id).
The read-only routes (list/ready/get/list_comments/context) carry no actor
field. update_task's assignee_id is a prospective task target, not an actor
identity, and the route records no actor, so it is intentionally not pinned.

Tests: agent comment with author_id != canonical_id -> 403; omitted author
succeeds and is stored as the canonical_id.

* fix(agents): tighten project_tasks scope per Kilo review on #1774

Two folds from the Kilo review:

WARNING: PATCH update_task was on the agent allowlist, letting a project_tasks
agent free-mutate title/body/assignee_id/parent (broader than the documented
read + lifecycle + comments, and assignee_id had no actor binding). PATCH is now
session owner/admin only and off the middleware allowlist; agents drive the
board via read + claim/release/close/reopen + comments. Test relocated to assert
an agent token is rejected on PATCH.

SUGGESTION (defense in depth): check_agent_scope_for_project trusted only the
token project_id claim. It now also requires an active grant whose own
project_id matches, so a token claim and grant that diverge cannot authorize.

* fix(agents): add project_tasks to registry _ALLOWED_SCOPES (sync with VALID_SCOPES)

The consent-flow VALID_SCOPES gained project_tasks but the registry grant
allowlist _ALLOWED_SCOPES did not, so test_allowed_scopes_matches_canonical_valid_scopes
failed on CI (the two lists must not drift). Add project_tasks so a grant for it
is accepted by the registry grant path, matching the consent vocabulary.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions

Copy link
Copy Markdown

👋 Thanks for the PR! This one targets master, which is our
stable branch (it's what live installs track). Please retarget it to
dev — click Edit next to the PR title and change the base
branch dropdown from master to dev. Your commits and any review
carry over, nothing is lost.

See CONTRIBUTING.md for the branch model.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Beta.39 adds Game Studio asset generation, taOSgo mesh joining, managed backend service controls, project-scoped agent authorization, atomic VRAM reservations, worker model advertisement, installer updates, release metadata, and broad frontend/backend test coverage.

Changes

Beta.39 platform and feature updates

Layer / File(s) Summary
Release metadata and installer contracts
CHANGELOG.md, README.md, app-catalog/..., scripts/install-*.sh, pyproject.toml, desktop/package.json
Release metadata, agent installation mappings, framework installers, and RKLLama installation checks are updated.
Managed backend manifests and service control
scripts/check_manifests.py, tinyagentos/cluster/backend_services.py, tinyagentos/routes/system.py, app-catalog/services/rkllama/manifest.yaml, .github/workflows/doc-gate.yml
Managed service contracts, linting, systemd discovery/actions, health checks, and catalog-driven restart behavior are added.
Game Studio asset generation
tinyagentos/asset_gen/*, tinyagentos/routes/game_assets.py, desktop/src/apps/gamestudio/*, tinyagentos/routes/games.py
ComfyUI texture and sprite generation is exposed through the API and editor, generated assets can be inserted into source files, and binary assets survive text saves.
Agent authentication and task access
tinyagentos/agent_token_auth.py, tinyagentos/auth_middleware.py, tinyagentos/routes/a2a_bus.py, tinyagentos/routes/projects.py
Shared JWT verification, project-bound task access, actor binding, authenticated A2A sending, and the project_tasks scope are implemented.
taOSgo mesh join
tinyagentos/taosnet/*, tinyagentos/routes/account_proxy.py
Mesh credentials are persisted atomically, removed from browser-facing join responses, and used to trigger fail-soft background Tailscale joins.
VRAM and worker model state
tinyagentos/vram_reservation.py, tinyagentos/routes/models.py, tinyagentos/worker/*, tinyagentos/cluster/*
Model operations reserve VRAM, worker manifests advertise available models, and heartbeats propagate deduplicated model metadata.
Frontend and backend validation
desktop/src/apps/designstudio/*.test.ts*, tests/*.py
New tests cover Design Studio interactions, Game Studio generation, authorization, mesh joining, managed services, VRAM reservations, worker manifests, and route behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EditorView
  participant AssetsPanel
  participant GameAssetsAPI
  participant ComfyUI
  participant GameDirectory
  EditorView->>AssetsPanel: Select Assets tab
  AssetsPanel->>GameAssetsAPI: POST texture generation request
  GameAssetsAPI->>ComfyUI: Submit workflow and poll output
  ComfyUI-->>GameAssetsAPI: Return image bytes
  GameAssetsAPI->>GameDirectory: Save generated PNG
  GameAssetsAPI-->>AssetsPanel: Return preview and filename
  AssetsPanel->>EditorView: Insert asset reference
Loading

Possibly related PRs

  • jaylfc/taOS#1773: Implements the same Game Studio ComfyUI texture/sprite generation flow and related frontend/backend tests.
  • jaylfc/taOS#1770: Overlaps with the mesh credential persistence and join-poll interception changes.
  • jaylfc/taOS#1756: Overlaps with managed backend manifest linting, lifecycle metadata, and CI wiring.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the release promotion to master for beta.39 and matches the PR's overall purpose.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

"""
reservation = self._pending.pop(reservation_id, None)
if reservation is not None:
self._reserved_vram_mb -= reservation.vram_mb

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: release() mutates _reserved_vram_mb without holding self._lock

reserve() holds the lock but await asyncio.to_thread(self._probe_vram) (line 92) yields control; release() runs without the lock and can decrement _reserved_vram_mb during that await. When reserve() resumes it reads the now-smaller reserved total (line 107) and can over-admit — reopening the exact TOCTOU race this feature was built to prevent. Acquire self._lock in release() (and make the read-only accessors lock-safe).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

path should already have been admitted fail-open by :meth:`reserve`
(a deny message never reports these zeros as real capacity).
"""
probe = self._probe_vram()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Blocking nvidia-smi probe runs synchronously on the event loop

available_vram() (and stats() at line 182) call the blocking _probe_vram() subprocess directly on the asyncio loop instead of asyncio.to_thread. available_vram() is reachable on the request hot path via the 503 branch in models.py:666, so every denied model pull blocks the entire event loop (and all concurrent requests) for the probe duration. Wrap these calls in asyncio.to_thread.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

body.required_vram_mb, caller=f"ollama-pull:{model_name}",
)
if reservation is None:
effective_free, _total = vram_mgr.available_vram()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Synchronous blocking VRAM probe on the request hot path

vram_mgr.available_vram() (used in the 503 “insufficient VRAM” branch) calls a blocking nvidia-smi subprocess directly on the event loop, stalling all concurrent requests while denied pulls wait on the probe. Make the accessor use asyncio.to_thread (see the vram_reservation.py finding).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const ref = buildAssetReference(path, filename);
setFiles((prev) => {
const next = { ...prev, [path]: (prev[path] ?? "") + ref };
void flushSave(next);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Side effect (flushSave) inside a setState updater

void flushSave(next) is invoked inside the setFiles((prev) => { ... }) updater. React requires updater functions to be pure; under StrictMode (dev double-invoke) this save runs twice, risking redundant/raced writes. Compute next outside the updater, then call setFiles(next) and flushSave(next) separately.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

json={"priority": 5},
headers=_hdr(token),
)
assert resp.status_code in (401, 403, 404)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Loose status-code assertion can mask a security regression

assert resp.status_code in (401, 403, 404) — a valid pid is always supplied, so the dual-auth gate's only correct denial here is 401 (404 would mean an unauthorized agent reached project lookup). Including 404 lets a regression that does project lookup before the auth gate pass silently. Pin to == 401 (the same looseness appears at lines 309, 320, 329, 336).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return {"unit": unit, "ok": False, "scope": scope, "detail": f"{verb} timed out"}
except Exception as exc: # pragma: no cover - defensive
return {"unit": unit, "ok": False, "scope": scope, "detail": str(exc)[:200]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Inconsistent access: direct subscript backend[\"type\"]

The surrounding loop uses .get(...) for fail-soft access; this direct subscript is inconsistent and would raise KeyError if a backend entry lacked type. Use backend.get(\"type\") for uniformity.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

pass

p = _path()
tmp = p.with_name(p.name + ".tmp")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Stale .tmp file left on error path

If the write fails after mesh_credentials.json.tmp is created, the temp file is never cleaned up and lingers until the next successful save. Add a try/finally to remove the temp file on failure.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

"""
errors: list[str] = []
services_dir = root / "services"
for manifest in sorted(services_dir.glob("*/manifest.yaml")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: glob silently passes when the services dir is missing

If root/services (or a typo'd --root) doesn't exist, glob yields nothing and main prints “clean” (exit 0), silently passing the CI gate. Add an explicit services_dir.is_dir() check that errors out.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

log "installing agent-zero with pip"
cd "$AGENT_ZERO_HOME"
pip install -e . || die "pip install failed in ${AGENT_ZERO_HOME}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Use python3 -m pip instead of bare pip

The prereq check only verifies python3; on hosts where pip is absent or mapped to python2 the install fails. Use python3 -m pip install -e . for consistency.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

log "installing openclaw with pip"
cd "$OPENCLAW_HOME"
pip install -e . || die "pip install failed in ${OPENCLAW_HOME}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Use python3 -m pip instead of bare pip

Same as above — the prereq check verifies python3 but the install uses bare pip. Use python3 -m pip install -e ..


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 14 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 5
SUGGESTION 9
Issue Details (click to expand)

CRITICAL

(none)

WARNING

File Line Issue
tinyagentos/vram_reservation.py 143 release() mutates _reserved_vram_mb without the lock; TOCTOU race vs reserve() reopens the OOM bug this feature fixes
tinyagentos/vram_reservation.py 173 Blocking nvidia-smi probe runs synchronously on the event loop (also stats() line 182)
tinyagentos/routes/models.py 666 Synchronous blocking VRAM probe on the request hot path (503 branch)
desktop/src/apps/gamestudio/EditorView.tsx 279 Side effect (flushSave) inside setState updater — double-runs under StrictMode
tests/test_routes_projects_agent_tasks.py 133 Loose in (401,403,404) assertion can mask a security regression (also 309,320,329,336)

SUGGESTION

File Line Issue
tinyagentos/routes/game_assets.py 124 No prompt length cap / rate limiting on GPU-expensive endpoint
tinyagentos/routes/models.py 702 VRAM reservation released before the VRAM-consuming inference step
desktop/src/apps/gamestudio/asset-reference.ts 14 filename interpolated unsanitized into generated source (injection)
tinyagentos/taosnet/mesh.py 120 Headscale preauth key passed as a visible argv element
tinyagentos/cluster/backend_services.py 182 Direct subscript backend["type"] instead of .get
tinyagentos/taosnet/mesh_credentials.py 82 Stale .tmp file left on error path
scripts/check_manifests.py 45 glob silently passes when the services dir is missing
scripts/install-agent-zero.sh 38 Use python3 -m pip instead of bare pip
scripts/install-openclaw.sh 38 Use python3 -m pip instead of bare pip
Files Reviewed (83 files)
  • tinyagentos/vram_reservation.py - 2 issues (WARNING)
  • tinyagentos/routes/models.py - 2 issues (WARNING, SUGGESTION)
  • tinyagentos/routes/game_assets.py - 1 issue (SUGGESTION)
  • desktop/src/apps/gamestudio/EditorView.tsx - 1 issue (WARNING)
  • desktop/src/apps/gamestudio/asset-reference.ts - 1 issue (SUGGESTION)
  • tests/test_routes_projects_agent_tasks.py - 1 issue (WARNING)
  • tinyagentos/taosnet/mesh.py - 1 issue (SUGGESTION)
  • tinyagentos/cluster/backend_services.py - 1 issue (SUGGESTION)
  • tinyagentos/taosnet/mesh_credentials.py - 1 issue (SUGGESTION)
  • scripts/check_manifests.py - 1 issue (SUGGESTION)
  • scripts/install-agent-zero.sh - 1 issue (SUGGESTION)
  • scripts/install-openclaw.sh - 1 issue (SUGGESTION)

Fix these issues in Kilo Cloud


Reviewed by hy3-20260706:free · Input: 111.7K · Output: 21.3K · Cached: 594.3K

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (12)
scripts/install-openclaw.sh (1)

22-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Shared clone/update logic is duplicated across installer scripts.

The clone-or-update block (check .git, fetch, checkout, pull, or mkdir + chown + clone) is identical in install-agent-zero.sh, install-openclaw.sh, and install-picoclaw.sh. Consider extracting a shared helper (e.g., scripts/install-common.sh) to reduce maintenance burden and ensure consistent behavior.

🤖 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/install-openclaw.sh` around lines 22 - 32, Extract the duplicated
clone-or-update logic from install-agent-zero.sh, install-openclaw.sh, and
install-picoclaw.sh into a shared helper such as install-common.sh, exposing a
function that accepts the home directory, version, and repository (and uses the
existing log behavior). Source the helper from each installer and replace their
inline .git check, fetch/checkout/pull, or mkdir/chown/clone blocks with calls
to the shared function while preserving current arguments and behavior.
scripts/install-moltis.sh (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

MOLTIS_HOME is defined but never used.

The variable is declared but no code references it. Either remove it or use it (e.g., as a clone target if you add a git-based fallback path).

♻️ Proposed fix
 MOLTIS_VERSION="${TAOS_MOLTIS_VERSION:-latest}"
 MOLTIS_REPO="https://github.com/moltis-org/moltis.git"
-MOLTIS_HOME="/opt/moltis"
🤖 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/install-moltis.sh` at line 11, Remove the unused MOLTIS_HOME variable
from scripts/install-moltis.sh, unless the installation flow is updated to use
it as the target directory for a git-based fallback path.

Source: Linters/SAST tools

tests/test_cluster_worker_protocol.py (1)

294-320: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the retained status when deduplicating IDs.

This fixture places available before loaded, but the assertions do not verify which status survives. Assert the intended precedence—likely loaded—so the test catches status loss.

🤖 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_cluster_worker_protocol.py` around lines 294 - 320, Update
test_heartbeat_dedupes_across_backends to assert the deduplicated model retains
the intended higher-priority status, likely "loaded", by checking
w.available_models[0]["status"] in addition to the existing model_id assertion.
desktop/src/apps/gamestudio/EditorView.tsx (2)

272-284: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move flushSave call out of the setFiles updater.

Calling void flushSave(next) inside the setFiles updater introduces a side effect in a function that should be pure. In React Strict Mode, the updater may be called twice, causing duplicate save POST requests. Compute next from filesRef.current outside the updater and call flushSave separately. The same pattern exists in the pre-existing handleApply at line 263.

♻️ Proposed refactor for handleInsertAsset
   const handleInsertAsset = useCallback(
     (filename: string) => {
       const path = activePath;
       if (!path) return;
       const ref = buildAssetReference(path, filename);
-      setFiles((prev) => {
-        const next = { ...prev, [path]: (prev[path] ?? "") + ref };
-        void flushSave(next);
-        return next;
-      });
+      const next = { ...filesRef.current, [path]: (filesRef.current[path] ?? "") + ref };
+      setFiles(next);
+      void flushSave(next);
     },
-    [activePath, flushSave],
+    [activePath, flushSave],
   );
🤖 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/apps/gamestudio/EditorView.tsx` around lines 272 - 284, Move the
flushSave side effect out of the setFiles updater in handleInsertAsset and the
existing handleApply handler. Compute the updated files object from
filesRef.current, call setFiles with the resulting state, then invoke
flushSave(next) separately so the updater remains pure and Strict Mode cannot
duplicate save requests.

417-444: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add role="tabpanel" and associations for the tab panels.

The tablist correctly uses role="tab" and aria-selected, but the content panels lack role="tabpanel" and aria-labelledby/aria-controls associations. Screen reader users navigating by panel role won't find the sidebar content. Adding id to each tab and aria-labelledby on each panel completes the WAI-ARIA tab pattern.

♿ Suggested accessibility improvements
             <button
               type="button"
               role="tab"
+              id="editor-tab-chat"
+              aria-controls="editor-panel-chat"
               aria-selected={rightTab === "chat"}
               onClick={() => setRightTab("chat")}
               ...
             >
               <Sparkles size={14} className="text-accent" />
               Ask the agent
             </button>
             <button
               type="button"
               role="tab"
+              id="editor-tab-assets"
+              aria-controls="editor-panel-assets"
               aria-selected={rightTab === "assets"}
               onClick={() => setRightTab("assets")}
               ...
             >
               <ImagePlus size={14} className="text-accent" />
               Assets
             </button>
           </div>

           {rightTab === "assets" ? (
-            <AssetsPanel gameId={gameId} activePath={activePath} onInsert={handleInsertAsset} />
+            <div role="tabpanel" id="editor-panel-assets" aria-labelledby="editor-tab-assets" className="flex min-h-0 flex-1 flex-col">
+              <AssetsPanel gameId={gameId} activePath={activePath} onInsert={handleInsertAsset} />
+            </div>
           ) : (
-          <>
+          <div role="tabpanel" id="editor-panel-chat" aria-labelledby="editor-tab-chat" className="flex min-h-0 flex-1 flex-col">
           <div className="min-h-0 flex-1 overflow-auto px-3 py-2">
             ...
           </div>
           ...
-          </>
+          </div>
           )}
🤖 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/apps/gamestudio/EditorView.tsx` around lines 417 - 444, Add
complete WAI-ARIA tab associations in the sidebar: give the chat and assets
buttons stable unique IDs and matching aria-controls values, then add
role="tabpanel", corresponding aria-labelledby values, and matching panel IDs to
the rendered content containers for each rightTab view. Update the relevant
rightTab conditional panels while preserving their existing content.
scripts/install-deer-flow.sh (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the default DEERFLOW_VERSION for reproducible installs.

The default main is a moving target — two installs on different days can produce different code. This release includes rkllama 1.3.0 pinning, so pinning deer-flow to a specific commit or tag would be consistent. The TAOS_DEERFLOW_VERSION env var already allows overrides.

♻️ Proposed fix: pin to a specific ref
-DEERFLOW_VERSION="${TAOS_DEERFLOW_VERSION:-main}"
+DEERFLOW_VERSION="${TAOS_DEERFLOW_VERSION:-<specific-tag-or-commit-sha>}"
🤖 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/install-deer-flow.sh` at line 9, Change the default value assigned to
DEERFLOW_VERSION from the moving main branch to a fixed release tag or commit
that includes rkllama 1.3.0, while preserving the TAOS_DEERFLOW_VERSION
environment-variable override.
tinyagentos/app.py (1)

1144-1149: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add an eager None default for app.state.vram_reservation.

Every other lifespan-only attribute in this file (mcp_supervisor, orchestrator, trace_registry, bridge_sessions, vapid_keypair, etc.) gets an explicit None in the eager-init block so attribute-existence checks work before/without the lifespan running. vram_reservation is the exception — it's only ever set inside lifespan(). Current consumers use getattr(..., None) so this isn't a crash today, but it silently deviates from the file's established defensive pattern.

♻️ Proposed fix
     app.state.agent_grants = agent_grants_store
     app.state.app_grants = app_grants_store
     app.state.license_acceptances = license_acceptances_store
     app.state.cluster_pairing = cluster_pairing_store
     app.state.capability_map = capability_map_store
+    # vram_reservation is created by the lifespan; None here keeps
+    # attribute-existence checks consistent during the pre-startup window.
+    app.state.vram_reservation = None

Also applies to: 1484-1602

🤖 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/app.py` around lines 1144 - 1149, Add an explicit None
initialization for app.state.vram_reservation in the eager state-initialization
block, alongside mcp_supervisor, orchestrator, and other lifespan-only
attributes. Keep the lifespan() assignment that replaces it with
VramReservationManager, and ensure all consumers continue using the established
state access pattern.
tinyagentos/agent_token_auth.py (1)

168-189: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant list_grants query on every project-scoped auth check.

_verify_agent_scope (line 115) already fetches grants via list_grants(canonical_id). Then check_agent_scope_for_project (line 181) fetches the same grants again for the defense-in-depth project-binding check. This doubles the DB queries on every project-scoped authorization.

Returning the grants from _verify_agent_scope eliminates the redundant fetch without changing the security semantics.

♻️ Proposed refactor
 async def _verify_agent_scope(
     request: Request, required_scope: str
-) -> Optional[tuple[str, dict]]:
+) -> Optional[tuple[str, dict, list]]:
     """Shared verifier for the agent-token checks.
 
     Return ``(canonical_id, payload)`` for a valid Bearer registry JWT that holds
     an active *required_scope* grant, or ``None`` when no Authorization header is
     present.  Raises the same 401/403 documented on ``check_agent_scope``.
     """
     # ... (unchanged verification logic) ...
 
-    return canonical_id, payload
+    return canonical_id, payload, grants
 
 
 async def check_agent_scope(request: Request, required_scope: str) -> Optional[str]:
     # ...
-    canonical_id, _payload = result
+    canonical_id, _payload, _grants = result
     return canonical_id
 
 
 async def check_agent_scope_for_project(
     request: Request, required_scope: str, project_id: str
 ) -> Optional[str]:
     # ...
-    canonical_id, payload = result
+    canonical_id, payload, grants = result
     token_project = payload.get("project_id")
     if not token_project or token_project != project_id:
         raise HTTPException(status_code=403, detail=PROJECT_SCOPE_MISMATCH_DETAIL)
-    grants_store = _get_grants_store(request)
     now = datetime.now(timezone.utc)
-    grants = await grants_store.list_grants(canonical_id)
     grant_ok = any(
         g["scope"] == required_scope
         and g.get("project_id") == project_id
         and _grant_unexpired(g.get("expires_at"), now)
         for g in grants
     )
🤖 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_token_auth.py` around lines 168 - 189, Update
_verify_agent_scope to return the fetched grants alongside canonical_id and
payload, then adjust its callers to unpack the expanded result. In
check_agent_scope_for_project, reuse those grants for the project_id, scope, and
expiration validation and remove the second _get_grants_store/list_grants call,
preserving the existing authorization behavior.
tinyagentos/routes/account_proxy.py (1)

33-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider tuple unpacking instead of concatenation (RUF005).

_JOIN_SERVICE_TOKENS + ("headscale_preauth_key",) works but *_JOIN_SERVICE_TOKENS, "headscale_preauth_key" is the more idiomatic Python 3 form.

♻️ Proposed fix
-_STRIP_KEYS = _JOIN_SERVICE_TOKENS + ("headscale_preauth_key",)
+_STRIP_KEYS = (*_JOIN_SERVICE_TOKENS, "headscale_preauth_key")
🤖 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/routes/account_proxy.py` around lines 33 - 37, Update the
_STRIP_KEYS definition to use tuple unpacking, replacing tuple concatenation
with *_JOIN_SERVICE_TOKENS followed by "headscale_preauth_key".
tinyagentos/taosnet/mesh_credentials.py (1)

106-125: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider caching _load() result when multiple getters are called in sequence.

Each getter (get_controller_token, get_sites_token, get_host_id) independently calls _load(), re-reading and re-parsing the JSON file from disk. If a caller calls several getters together (e.g., has_mesh_credentials()get_controller_token()_load()), the file is read redundantly. For the current single-call use cases this is fine, but a module-level cache (invalidated on save/clear) would eliminate repeated disk I/O if usage expands.

🤖 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/taosnet/mesh_credentials.py` around lines 106 - 125, Cache the
parsed credentials returned by _load() at module scope and reuse it across
get_controller_token(), get_sites_token(), get_host_id(), and
has_mesh_credentials() calls; invalidate or refresh that cache whenever
credentials are saved or cleared so reads remain current.
tests/test_mesh.py (1)

74-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

_STATUS is a mutable class attribute (RUF012).

If a test ever mutates _STATUS, subsequent tests would see the modified data. Marking it as a class-level constant or using a fixture would be safer.

♻️ Proposed fix
 class TestMeshStatus:
-    _STATUS = {
+    _STATUS: dict = {
         "BackendState": "Running",
         "CurrentTailnet": {"Name": "jason"},
         "Self": {"Online": True, "HostName": "node-1", "TailscaleIPs": ["100.64.0.5"]},
     }

Alternatively, use a @pytest.fixture or copy the dict in each test.

🤖 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_mesh.py` around lines 74 - 78, Update the _STATUS class attribute
in tests/test_mesh.py to avoid mutable shared state: annotate it as a ClassVar
if it is intended to remain class-level, and ensure tests do not mutate it
directly; alternatively, provide the status through a pytest fixture or return a
fresh copy for each test.
tinyagentos/taosnet/mesh.py (1)

57-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider logging the exception in the timeout cleanup path.

The bare except Exception: pass on lines 62-63 silently swallows any error during proc.kill() / proc.wait(). A debug-level log would aid troubleshooting if the cleanup ever fails on an exotic filesystem or zombie process.

♻️ Proposed fix
     except asyncio.TimeoutError:
         if proc is not None:
             try:
                 proc.kill()
                 await proc.wait()
-            except Exception:  # pragma: no cover - defensive
-                pass
+            except Exception:  # pragma: no cover - defensive
+                logger.debug("taosgo: cleanup after timeout failed", exc_info=True)
         return 124, "", f"timed out after {timeout}s"
🤖 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/taosnet/mesh.py` around lines 57 - 64, In the timeout cleanup
path of the process execution function, replace the silent broad exception
handler around proc.kill() and proc.wait() with a debug-level log that records
the cleanup exception and relevant process context, while preserving the
existing timeout return behavior.
🤖 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 `@desktop/src/apps/gamestudio/asset-reference.ts`:
- Around line 12-24: buildAssetReference interpolates filename into HTML, CSS,
JavaScript, and fallback output without validation or escaping. Validate
filename before constructing rel, rejecting unsafe characters such as quotes,
backslashes, angle brackets, and control characters, or escape it appropriately
for each target syntax; ensure assetVarName receives the validated filename and
all generated snippets remain syntactically safe.

In `@scripts/install-agent-zero.sh`:
- Line 37: Create and activate a dedicated virtual environment in
scripts/install-agent-zero.sh before the editable install, then run pip through
that environment for the existing install step. Update the setup flow around the
pip install command to use the virtualenv’s Python/pip and preserve the existing
die failure handling.

In `@scripts/install-moltis.sh`:
- Around line 36-37: Update the install command in the shell script to support
branch-based MOLTIS_VERSION values: attempt the existing --tag flow, then fall
back to --branch or --rev when it fails, while preserving the existing die
behavior if all attempts fail. Ensure the logic uses the MOLTIS_REPO and
MOLTIS_VERSION variables consistently.

In `@scripts/install-openclaw.sh`:
- Line 37: Update the installation command in install-openclaw.sh to avoid PEP
668 failures by installing into a virtual environment or using a user-scoped
installation such as pip install --user -e .; preserve the existing die error
handling and ensure the chosen approach works for the script’s runtime commands.

In `@scripts/install-picoclaw.sh`:
- Line 52: Replace the broad grep-based detection in the install-target check
with a reliable Makefile target check, preferably invoking make’s dry run for
the `install` target or matching an exact `^install:` definition in both
candidate Makefiles. Ensure the subsequent `sudo make install` runs only when a
real install target is present, while preserving the existing fallback behavior.

In `@tests/test_agent_token_auth.py`:
- Around line 68-103: The tests lack coverage for the defense-in-depth
grant/project mismatch in check_agent_scope_for_project. Add a test that creates
a token whose project_id claim is “prj-1” while its project_tasks grant is bound
to “prj-OTHER”, then assert the call raises HTTPException with status 403 and
detail PROJECT_SCOPE_MISMATCH_DETAIL; extend or bypass _mint as needed to create
these divergent values.

In `@tests/test_worker_manifest.py`:
- Around line 171-178: Update test_default_path_fallback to monkeypatch
worker_manifest.DEFAULT_MANIFEST_PATH to a nonexistent path under tmp_path
before calling load_manifest(), ensuring the test does not depend on whether the
host’s /etc/taos/worker-models.json exists; retain the assertions for the empty
manifest and the patched default path.

In `@tinyagentos/cluster/manager.py`:
- Around line 220-231: Update the available-model flattening logic in the
backend aggregation block so duplicate model IDs are merged instead of silently
keeping the first entry. Ensure a later entry with status “loaded” takes
precedence over an earlier “available” entry, while preserving the existing
deduplication behavior and assigning the merged result to
worker.available_models.

In `@tinyagentos/taosnet/mesh.py`:
- Around line 86-92: Update the tailscale invocation in `_run` to write
`preauth_key` to a securely created temporary file and pass it using
`--auth-key=file:<path>` instead of including the secret directly in the command
arguments; ensure the temporary file is cleaned up after execution, including on
errors.

In `@tinyagentos/worker/agent.py`:
- Around line 183-199: Use resident-model data rather than downloaded-model data
when computing status in the worker manifest handling loop. For backends of type
rkllama or ollama, derive probed_names from backend["loaded_models"]; retain
backend["models"] for other backends, then continue using model_id membership to
assign "loaded" versus "available".
- Around line 187-198: In the manifest-processing loop, validate that `model_id`
is a non-empty string before using it in the `probed_names` membership check.
Replace the current truthiness-only guard after `m.get("model_id")` with an
`isinstance(model_id, str)` and non-empty validation, logging and skipping
invalid values so malformed entries cannot abort enrichment for valid models.
- Around line 170-191: Make the manifest mappings in worker_manifest.py
consistent with detect_backends(): either add discovery and capability handling
for the kokoro and whisper backend types so their entries are probed and
forwarded, or remove kokoro/whisper from SOFTWARE_TO_BACKEND_TYPE and the
manifest schema. Ensure detect_backends() no longer silently drops valid
advertised entries.

In `@tinyagentos/worker/worker_manifest.py`:
- Around line 73-79: Update the manifest-loading try block to read the file
explicitly as UTF-8 and include UnicodeDecodeError in the exceptions handled by
the existing malformed-manifest warning path. Preserve the current
logger.warning message and return empty behavior in the manifest parsing
function.

---

Nitpick comments:
In `@desktop/src/apps/gamestudio/EditorView.tsx`:
- Around line 272-284: Move the flushSave side effect out of the setFiles
updater in handleInsertAsset and the existing handleApply handler. Compute the
updated files object from filesRef.current, call setFiles with the resulting
state, then invoke flushSave(next) separately so the updater remains pure and
Strict Mode cannot duplicate save requests.
- Around line 417-444: Add complete WAI-ARIA tab associations in the sidebar:
give the chat and assets buttons stable unique IDs and matching aria-controls
values, then add role="tabpanel", corresponding aria-labelledby values, and
matching panel IDs to the rendered content containers for each rightTab view.
Update the relevant rightTab conditional panels while preserving their existing
content.

In `@scripts/install-deer-flow.sh`:
- Line 9: Change the default value assigned to DEERFLOW_VERSION from the moving
main branch to a fixed release tag or commit that includes rkllama 1.3.0, while
preserving the TAOS_DEERFLOW_VERSION environment-variable override.

In `@scripts/install-moltis.sh`:
- Line 11: Remove the unused MOLTIS_HOME variable from
scripts/install-moltis.sh, unless the installation flow is updated to use it as
the target directory for a git-based fallback path.

In `@scripts/install-openclaw.sh`:
- Around line 22-32: Extract the duplicated clone-or-update logic from
install-agent-zero.sh, install-openclaw.sh, and install-picoclaw.sh into a
shared helper such as install-common.sh, exposing a function that accepts the
home directory, version, and repository (and uses the existing log behavior).
Source the helper from each installer and replace their inline .git check,
fetch/checkout/pull, or mkdir/chown/clone blocks with calls to the shared
function while preserving current arguments and behavior.

In `@tests/test_cluster_worker_protocol.py`:
- Around line 294-320: Update test_heartbeat_dedupes_across_backends to assert
the deduplicated model retains the intended higher-priority status, likely
"loaded", by checking w.available_models[0]["status"] in addition to the
existing model_id assertion.

In `@tests/test_mesh.py`:
- Around line 74-78: Update the _STATUS class attribute in tests/test_mesh.py to
avoid mutable shared state: annotate it as a ClassVar if it is intended to
remain class-level, and ensure tests do not mutate it directly; alternatively,
provide the status through a pytest fixture or return a fresh copy for each
test.

In `@tinyagentos/agent_token_auth.py`:
- Around line 168-189: Update _verify_agent_scope to return the fetched grants
alongside canonical_id and payload, then adjust its callers to unpack the
expanded result. In check_agent_scope_for_project, reuse those grants for the
project_id, scope, and expiration validation and remove the second
_get_grants_store/list_grants call, preserving the existing authorization
behavior.

In `@tinyagentos/app.py`:
- Around line 1144-1149: Add an explicit None initialization for
app.state.vram_reservation in the eager state-initialization block, alongside
mcp_supervisor, orchestrator, and other lifespan-only attributes. Keep the
lifespan() assignment that replaces it with VramReservationManager, and ensure
all consumers continue using the established state access pattern.

In `@tinyagentos/routes/account_proxy.py`:
- Around line 33-37: Update the _STRIP_KEYS definition to use tuple unpacking,
replacing tuple concatenation with *_JOIN_SERVICE_TOKENS followed by
"headscale_preauth_key".

In `@tinyagentos/taosnet/mesh_credentials.py`:
- Around line 106-125: Cache the parsed credentials returned by _load() at
module scope and reuse it across get_controller_token(), get_sites_token(),
get_host_id(), and has_mesh_credentials() calls; invalidate or refresh that
cache whenever credentials are saved or cleared so reads remain current.

In `@tinyagentos/taosnet/mesh.py`:
- Around line 57-64: In the timeout cleanup path of the process execution
function, replace the silent broad exception handler around proc.kill() and
proc.wait() with a debug-level log that records the cleanup exception and
relevant process context, while preserving the existing timeout return behavior.
🪄 Autofix (Beta)

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: 34c0b469-5e47-4e11-b40b-5db37d9c432a

📥 Commits

Reviewing files that changed from the base of the PR and between d0246a5 and b8acce7.

⛔ Files ignored due to path filters (2)
  • desktop/package-lock.json is excluded by !**/package-lock.json
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (83)
  • .github/workflows/doc-gate.yml
  • .gitignore
  • CHANGELOG.md
  • README.md
  • app-catalog/agents/agent-zero/manifest.yaml
  • app-catalog/agents/deer-flow/manifest.yaml
  • app-catalog/agents/hermes/manifest.yaml
  • app-catalog/agents/openclaw/manifest.yaml
  • app-catalog/services/rkllama/manifest.yaml
  • desktop/package.json
  • desktop/src/apps/designstudio/DesignView.test.tsx
  • desktop/src/apps/designstudio/LayersPanel.test.tsx
  • desktop/src/apps/designstudio/LibraryView.test.tsx
  • desktop/src/apps/designstudio/MagicView.test.tsx
  • desktop/src/apps/designstudio/PropertiesPanel.test.tsx
  • desktop/src/apps/designstudio/TemplatesView.test.tsx
  • desktop/src/apps/designstudio/designs-api.test.ts
  • desktop/src/apps/designstudio/elementFactory.test.ts
  • desktop/src/apps/designstudio/types.test.ts
  • desktop/src/apps/designstudio/useElementHistory.test.ts
  • desktop/src/apps/designstudio/useHtmlImage.test.ts
  • desktop/src/apps/gamestudio/AssetsPanel.test.tsx
  • desktop/src/apps/gamestudio/AssetsPanel.tsx
  • desktop/src/apps/gamestudio/EditorView.tsx
  • desktop/src/apps/gamestudio/asset-reference.test.ts
  • desktop/src/apps/gamestudio/asset-reference.ts
  • desktop/src/apps/gamestudio/game-assets-api.ts
  • docs/agent-coordination.md
  • docs/design/cluster-backend-service-management.md
  • docs/design/game-studio-asset-generation.md
  • docs/design/taosgo-mesh-join-foundation.md
  • pyproject.toml
  • scripts/check_manifests.py
  • scripts/install-agent-zero.sh
  • scripts/install-deer-flow.sh
  • scripts/install-moltis.sh
  • scripts/install-openclaw.sh
  • scripts/install-picoclaw.sh
  • scripts/install-rkllama.sh
  • scripts/install-rknpu.sh
  • tests/test_a2a_bus_agent_auth.py
  • tests/test_agent_token_auth.py
  • tests/test_asset_gen_comfyui.py
  • tests/test_backend_services.py
  • tests/test_check_manifests.py
  • tests/test_cluster_worker_protocol.py
  • tests/test_mesh.py
  • tests/test_mesh_credentials.py
  • tests/test_routes_agent_auth_requests.py
  • tests/test_routes_game_assets.py
  • tests/test_routes_games.py
  • tests/test_routes_projects_agent_tasks.py
  • tests/test_routes_system.py
  • tests/test_vram_reservation.py
  • tests/test_worker_manifest.py
  • tinyagentos/__init__.py
  • tinyagentos/agent_token_auth.py
  • tinyagentos/app.py
  • tinyagentos/asset_gen/__init__.py
  • tinyagentos/asset_gen/comfyui_client.py
  • tinyagentos/asset_gen/workflows.py
  • tinyagentos/asset_gen/workflows/texture_sdxl.json
  • tinyagentos/auth_middleware.py
  • tinyagentos/cluster/backend_services.py
  • tinyagentos/cluster/manager.py
  • tinyagentos/cluster/worker_protocol.py
  • tinyagentos/routes/__init__.py
  • tinyagentos/routes/a2a_bus.py
  • tinyagentos/routes/account_proxy.py
  • tinyagentos/routes/agent_auth_requests.py
  • tinyagentos/routes/agent_registry.py
  • tinyagentos/routes/game_assets.py
  • tinyagentos/routes/games.py
  • tinyagentos/routes/models.py
  • tinyagentos/routes/projects.py
  • tinyagentos/routes/system.py
  • tinyagentos/scheduler/backend_catalog.py
  • tinyagentos/taosnet/mesh.py
  • tinyagentos/taosnet/mesh_credentials.py
  • tinyagentos/taosnet/passkey_client.py
  • tinyagentos/vram_reservation.py
  • tinyagentos/worker/agent.py
  • tinyagentos/worker/worker_manifest.py

Comment on lines +12 to +24
export function buildAssetReference(activePath: string, filename: string): string {
const ext = activePath.slice(activePath.lastIndexOf(".") + 1).toLowerCase();
const rel = `./${filename}`;
if (ext === "html" || ext === "htm") {
return `\n<img src="${rel}" alt="" />`;
}
if (ext === "css") {
return `\n/* generated asset */\n.generated-asset { background-image: url("${rel}"); }`;
}
if (ext === "js" || ext === "mjs" || ext === "ts") {
return `\n// generated asset\nconst ${assetVarName(filename)} = "${rel}";`;
}
return `\n/* asset: ${rel} */`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unescaped filename in generated code snippets can break output or enable injection.

buildAssetReference interpolates filename directly into HTML attributes (src="${rel}"), CSS url("${rel}"), and JS string literals ("${rel}") without escaping. A filename containing ", \, or < will produce syntactically broken code, and in the HTML case could introduce XSS if the generated markup is rendered in a preview context.

🛡️ Proposed fix: validate filename before generating reference
 export function buildAssetReference(activePath: string, filename: string): string {
+  // Reject filenames with characters that could break or inject into generated
+  // HTML/CSS/JS snippets. Asset pipelines should produce safe names; this guard
+  // prevents silent corruption if they don't.
+  if (/["'\\<>{}\s]/.test(filename)) {
+    throw new Error(`Unsafe asset filename: ${JSON.stringify(filename)}`);
+  }
   const ext = activePath.slice(activePath.lastIndexOf(".") + 1).toLowerCase();
   const rel = `./${filename}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function buildAssetReference(activePath: string, filename: string): string {
const ext = activePath.slice(activePath.lastIndexOf(".") + 1).toLowerCase();
const rel = `./${filename}`;
if (ext === "html" || ext === "htm") {
return `\n<img src="${rel}" alt="" />`;
}
if (ext === "css") {
return `\n/* generated asset */\n.generated-asset { background-image: url("${rel}"); }`;
}
if (ext === "js" || ext === "mjs" || ext === "ts") {
return `\n// generated asset\nconst ${assetVarName(filename)} = "${rel}";`;
}
return `\n/* asset: ${rel} */`;
export function buildAssetReference(activePath: string, filename: string): string {
// Reject filenames with characters that could break or inject into generated
// HTML/CSS/JS snippets. Asset pipelines should produce safe names; this guard
// prevents silent corruption if they don't.
if (/["'\\<>{}\s]/.test(filename)) {
throw new Error(`Unsafe asset filename: ${JSON.stringify(filename)}`);
}
const ext = activePath.slice(activePath.lastIndexOf(".") + 1).toLowerCase();
const rel = `./${filename}`;
if (ext === "html" || ext === "htm") {
return `\n<img src="${rel}" alt="" />`;
}
if (ext === "css") {
return `\n/* generated asset */\n.generated-asset { background-image: url("${rel}"); }`;
}
if (ext === "js" || ext === "mjs" || ext === "ts") {
return `\n// generated asset\nconst ${assetVarName(filename)} = "${rel}";`;
}
return `\n/* asset: ${rel} */`;
}
🤖 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/apps/gamestudio/asset-reference.ts` around lines 12 - 24,
buildAssetReference interpolates filename into HTML, CSS, JavaScript, and
fallback output without validation or escaping. Validate filename before
constructing rel, rejecting unsafe characters such as quotes, backslashes, angle
brackets, and control characters, or escape it appropriately for each target
syntax; ensure assetVarName receives the validated filename and all generated
snippets remain syntactically safe.

# --- install with pip ------------------------------------------------------
log "installing agent-zero with pip"
cd "$AGENT_ZERO_HOME"
pip install -e . || die "pip install failed in ${AGENT_ZERO_HOME}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## scripts/install-agent-zero.sh (outline)\n'
ast-grep outline scripts/install-agent-zero.sh --view expanded || true

printf '\n## scripts/install-agent-zero.sh (relevant lines)\n'
nl -ba scripts/install-agent-zero.sh | sed -n '1,140p'

printf '\n## Search for virtualenv / pip context\n'
rg -n "venv|virtualenv|pip install|AGENT_ZERO_HOME|break-system-packages|--user" -S scripts README.md . || true

Repository: jaylfc/taOS

Length of output: 328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## scripts/install-agent-zero.sh\n'
sed -n '1,160p' scripts/install-agent-zero.sh

printf '\n## Search for install-agent-zero references\n'
rg -n "install-agent-zero|AGENT_ZERO_HOME|pip install -e \\.|break-system-packages|--user|venv|virtualenv" -S .

Repository: jaylfc/taOS

Length of output: 49896


🌐 Web query:

pip externally managed environment --user editable install PEP 668 pip install -e --user

💡 Result:

PEP 668 introduces a mechanism where operating systems can mark a Python environment as externally managed [1][2][3]. This is intended to prevent users from accidentally installing packages via pip that might conflict with system-level packages managed by the OS package manager (e.g., apt, pacman) [1][2]. When an environment is marked as externally managed—indicated by the presence of an EXTERNALLY-MANAGED file in the standard library directory—pip will refuse to install packages into that environment [1][2]. This restriction applies to both system-wide installations and --user installations, as the latter can also lead to dependency conflicts by overriding packages on the Python sys.path [1][4]. Regarding editable installs (PEP 660) and --user installs, if you are in an externally managed environment, these commands will typically fail with the error: externally-managed-environment [5][6]. To work around this, you have the following options: 1. Use a Virtual Environment (Recommended): Create and activate a virtual environment using python -m venv . This provides an isolated space where pip management is not restricted by the system's PEP 668 marker [1][2][6]. 2. Use Specialized Tools: For installing command-line applications, tools like pipx are recommended as they automatically manage isolated environments for each application [5][6]. 3. Override the restriction: If you are in a disposable environment (such as a container or CI pipeline) where you are certain that breaking system-level package management is acceptable, you can use the --break-system-packages flag [2][6]. pip install --user --break-system-packages Note that this flag is intended for use only when strictly necessary and carries the risk of breaking OS-level tools [2][6]. For editable installs, they should be performed within a virtual environment, which is the standard practice for modern Python development [7][6].

Citations:


Create a virtualenv before the editable install scripts/install-agent-zero.sh:37 runs against the host Python, so pip install -e . can fail on PEP 668-managed systems; --user doesn’t solve that for editable installs.

🤖 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/install-agent-zero.sh` at line 37, Create and activate a dedicated
virtual environment in scripts/install-agent-zero.sh before the editable
install, then run pip through that environment for the existing install step.
Update the setup flow around the pip install command to use the virtualenv’s
Python/pip and preserve the existing die failure handling.

Comment thread scripts/install-moltis.sh
Comment on lines +36 to +37
cargo install --git "$MOLTIS_REPO" --tag "$MOLTIS_VERSION" moltis \
|| die "cargo install moltis from git failed"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

--tag fails for branch names.

cargo install --git --tag "$MOLTIS_VERSION" only accepts git tags. If a user sets TAOS_MOLTIS_VERSION to a branch name (e.g., main), the install will fail. Consider falling back to --rev or --branch when --tag fails, or document that only tag names are supported.

🔧 Suggested fix
 else
     log "installing moltis from git at ${MOLTIS_VERSION}"
-    cargo install --git "$MOLTIS_REPO" --tag "$MOLTIS_VERSION" moltis \
-        || die "cargo install moltis from git failed"
+    cargo install --git "$MOLTIS_REPO" --tag "$MOLTIS_VERSION" moltis \
+        || cargo install --git "$MOLTIS_REPO" --branch "$MOLTIS_VERSION" moltis \
+        || die "cargo install moltis from git failed"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cargo install --git "$MOLTIS_REPO" --tag "$MOLTIS_VERSION" moltis \
|| die "cargo install moltis from git failed"
cargo install --git "$MOLTIS_REPO" --tag "$MOLTIS_VERSION" moltis \
|| cargo install --git "$MOLTIS_REPO" --branch "$MOLTIS_VERSION" moltis \
|| die "cargo install moltis from git failed"
🤖 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/install-moltis.sh` around lines 36 - 37, Update the install command
in the shell script to support branch-based MOLTIS_VERSION values: attempt the
existing --tag flow, then fall back to --branch or --rev when it fails, while
preserving the existing die behavior if all attempts fail. Ensure the logic uses
the MOLTIS_REPO and MOLTIS_VERSION variables consistently.

# --- install with pip ------------------------------------------------------
log "installing openclaw with pip"
cd "$OPENCLAW_HOME"
pip install -e . || die "pip install failed in ${OPENCLAW_HOME}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

pip install -e . may fail on PEP 668 systems.

Same concern as install-agent-zero.sh: on modern Linux distributions with PEP 668 enforcement, system-wide pip install will fail. Consider adding --user or using a virtual environment.

🔧 Suggested fix
- pip install -e . || die "pip install failed in ${OPENCLAW_HOME}"
+ pip install --user -e . || die "pip install failed in ${OPENCLAW_HOME}"
🤖 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/install-openclaw.sh` at line 37, Update the installation command in
install-openclaw.sh to avoid PEP 668 failures by installing into a virtual
environment or using a user-scoped installation such as pip install --user -e .;
preserve the existing die error handling and ensure the chosen approach works
for the script’s runtime commands.

if [[ -d build ]]; then
cd "$PICOCLAW_HOME/build"
fi
if grep -q 'install' Makefile 2>/dev/null || grep -q 'install' ../Makefile 2>/dev/null; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fragile install target detection.

grep -q 'install' Makefile matches any line containing "install" — including comments, variable assignments, or unrelated targets like install-docs. This could attempt sudo make install when no real install target exists, or conversely miss a target named differently. Consider using make -n install (dry-run) or matching the target definition pattern ^install: instead.

🔧 Suggested fix
- if grep -q 'install' Makefile 2>/dev/null || grep -q 'install' ../Makefile 2>/dev/null; then
+ if make -n install >/dev/null 2>&1; then
     sudo make install || log "make install failed — binary may need manual PATH setup"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if grep -q 'install' Makefile 2>/dev/null || grep -q 'install' ../Makefile 2>/dev/null; then
if make -n install >/dev/null 2>&1; then
🤖 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/install-picoclaw.sh` at line 52, Replace the broad grep-based
detection in the install-target check with a reliable Makefile target check,
preferably invoking make’s dry run for the `install` target or matching an exact
`^install:` definition in both candidate Makefiles. Ensure the subsequent `sudo
make install` runs only when a real install target is present, while preserving
the existing fallback behavior.

Comment on lines +86 to +92
rc, _out, err = await _run(
[
"tailscale", "up",
"--login-server", server,
"--authkey", preauth_key,
"--hostname", hostname,
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the tailscale CLI support passing the authkey via stdin, environment variable, or file instead of a command-line argument to avoid exposing it in the process list?

💡 Result:

The Tailscale CLI does not natively support reading the authkey from stdin for the tailscale up command. However, it does provide a secure mechanism to load the authkey from a file by using a specific prefix, which avoids exposing the key in the process list [1]. If you pass the --auth-key flag with a value starting with "file:", the Tailscale CLI treats the remainder of the string as a file path and reads the authkey from that file [1]. Example: tailscale up --auth-key=file:/path/to/authkey This method allows you to store the authkey in a secure location (such as a mounted secret or a restricted-permission file) and reference it without placing the sensitive credential directly on the command line where it could be visible in process monitoring tools [1]. Regarding other methods: - Environment Variables: The tailscale CLI itself does not automatically read an authkey from an environment variable like TS_AUTHKEY for the up command; this is a pattern specifically implemented by the containerboot wrapper used in official Tailscale container images [2][3][4]. - Stdin: The CLI does not support passing the authkey via stdin [1]. For users deploying in container environments, the containerboot wrapper (used in official Docker images) does support the TS_AUTHKEY environment variable, which can also be set to file:/path/to/authkey to achieve a similar security outcome [2][4].

Citations:


Pass the auth key via file: instead of the command line tailscale up supports --auth-key=file:/path/to/key, which avoids exposing the secret in ps or /proc/<pid>/cmdline.

🤖 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/taosnet/mesh.py` around lines 86 - 92, Update the tailscale
invocation in `_run` to write `preauth_key` to a securely created temporary file
and pass it using `--auth-key=file:<path>` instead of including the secret
directly in the command arguments; ensure the temporary file is cleaned up after
execution, including on errors.

Comment on lines +170 to +191
from tinyagentos.worker.worker_manifest import (
load_manifest,
SOFTWARE_TO_BACKEND_TYPE,
)

# The manifest is external input: a malformed file or entry must
# degrade to "no manifest" (logged), never crash detect_backends()
# and take the whole worker down with it.
try:
manifest = load_manifest()
if manifest.get("models"):
for backend in backends:
backend_type = backend["type"]
probed_names = {
m.get("name", "") for m in backend.get("models", [])
}
available = []
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== tinyagentos/worker/agent.py ==\n'
ast-grep outline tinyagentos/worker/agent.py --view expanded || true

printf '\n== tinyagentos/worker/worker_manifest.py ==\n'
ast-grep outline tinyagentos/worker/worker_manifest.py --view expanded || true

printf '\n== search for kokoro/whisper/backend type support ==\n'
rg -n --hidden -S 'kokoro|whisper|SOFTWARE_TO_BACKEND_TYPE|backend_type|type"\s*:\s*"(kokoro|whisper)' tinyagentos || true

printf '\n== detect_backends references ==\n'
rg -n --hidden -S 'detect_backends\(' tinyagentos || true

Repository: jaylfc/taOS

Length of output: 14118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== tinyagentos/worker/agent.py: detect_backends body ==\n'
sed -n '112,210p' tinyagentos/worker/agent.py

printf '\n== tinyagentos/worker/worker_manifest.py ==\n'
sed -n '1,120p' tinyagentos/worker/worker_manifest.py

printf '\n== backend capability definitions and related backend types ==\n'
rg -n --hidden -S 'BACKEND_CAPABILITIES|VALID_BACKEND_TYPES|kokoro|whisper|sd-cpp|llama-cpp|ollama|rkllama' tinyagentos/worker tinyagentos/scheduler tinyagentos/routes tinyagentos/config.py

Repository: jaylfc/taOS

Length of output: 29635


Make Kokoro/Whisper manifest entries reachable.

tinyagentos/worker/worker_manifest.py advertises kokoro and whisper, but detect_backends() never probes those backend types, so those rows are always dropped and never reach the controller. Either add worker discovery/capability support for them or remove them from the manifest mapping/schema.

🤖 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/worker/agent.py` around lines 170 - 191, Make the manifest
mappings in worker_manifest.py consistent with detect_backends(): either add
discovery and capability handling for the kokoro and whisper backend types so
their entries are probed and forwarded, or remove kokoro/whisper from
SOFTWARE_TO_BACKEND_TYPE and the manifest schema. Ensure detect_backends() no
longer silently drops valid advertised entries.

Comment on lines +183 to +199
probed_names = {
m.get("name", "") for m in backend.get("models", [])
}
available = []
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"
available.append({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use resident models to determine loaded status.

For rkllama and ollama, backend["models"] includes downloaded models, not only resident models. The code should use backend["loaded_models"] for those backends; otherwise available capacity is incorrectly reported as already loaded.

Proposed fix
-                    probed_names = {
-                        m.get("name", "") for m in backend.get("models", [])
-                    }
+                    residency_models = (
+                        backend.get("loaded_models", [])
+                        if backend_type in ("rkllama", "ollama")
+                        else backend.get("models", [])
+                    )
+                    probed_names = {
+                        m.get("name", "") for m in residency_models
+                    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
probed_names = {
m.get("name", "") for m in backend.get("models", [])
}
available = []
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"
available.append({
residency_models = (
backend.get("loaded_models", [])
if backend_type in ("rkllama", "ollama")
else backend.get("models", [])
)
probed_names = {
m.get("name", "") for m in residency_models
}
available = []
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"
available.append({
🤖 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/worker/agent.py` around lines 183 - 199, Use resident-model data
rather than downloaded-model data when computing status in the worker manifest
handling loop. For backends of type rkllama or ollama, derive probed_names from
backend["loaded_models"]; retain backend["models"] for other backends, then
continue using model_id membership to assign "loaded" versus "available".

Comment on lines +187 to +198
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate model_id is a non-empty string.

A truthy list or dictionary passes Line 193, then raises TypeError during membership testing at Line 198. The outer catch suppresses the error but drops all enrichment, including valid entries.

-                        if not model_id:
+                        if not isinstance(model_id, str) or not model_id:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"
for m in manifest["models"]:
if not isinstance(m, dict):
continue
if SOFTWARE_TO_BACKEND_TYPE.get(m.get("software", "")) != backend_type:
continue
model_id = m.get("model_id")
if not isinstance(model_id, str) or not model_id:
logger.warning(
"skipping worker-manifest entry without model_id: %r", m
)
continue
status = "loaded" if model_id in probed_names else "available"
🤖 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/worker/agent.py` around lines 187 - 198, In the
manifest-processing loop, validate that `model_id` is a non-empty string before
using it in the `probed_names` membership check. Replace the current
truthiness-only guard after `m.get("model_id")` with an `isinstance(model_id,
str)` and non-empty validation, logging and skipping invalid values so malformed
entries cannot abort enrichment for valid models.

Comment on lines +73 to +79
try:
data = json.loads(manifest_path.read_text())
except (OSError, json.JSONDecodeError) as exc:
logger.warning(
"ignoring malformed worker manifest %s: %s", manifest_path, exc
)
return empty

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- worker_manifest.py ---\n'
nl -ba tinyagentos/worker/worker_manifest.py | sed -n '1,220p'

printf '\n--- search manifest-related uses ---\n'
rg -n "load_manifest|read_text\\(|UnicodeDecodeError|json\\.loads\\(" tinyagentos -S

Repository: jaylfc/taOS

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- tinyagentos/worker/worker_manifest.py ---'
nl -ba tinyagentos/worker/worker_manifest.py | sed -n '1,220p'

echo
echo '--- search manifest-related uses ---'
rg -n "load_manifest|read_text\(|UnicodeDecodeError|json\.loads\(" tinyagentos -S

Repository: jaylfc/taOS

Length of output: 235


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- tinyagentos/worker/worker_manifest.py ---'
cat -n tinyagentos/worker/worker_manifest.py | sed -n '1,220p'

echo
echo '--- search manifest-related uses ---'
rg -n "load_manifest|read_text\(|UnicodeDecodeError|json\.loads\(" tinyagentos -S

Repository: jaylfc/taOS

Length of output: 27133


Handle invalid manifest encodings as malformed input.
manifest_path.read_text() can raise UnicodeDecodeError before json.loads() runs, so malformed external files bypass the fail-soft path. Read as UTF-8 and catch decode failures here too.

🤖 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/worker/worker_manifest.py` around lines 73 - 79, Update the
manifest-loading try block to read the file explicitly as UTF-8 and include
UnicodeDecodeError in the exceptions handled by the existing malformed-manifest
warning path. Preserve the current logger.warning message and return empty
behavior in the manifest parsing function.

@jaylfc jaylfc merged commit 00a686e into master Jul 10, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants