config.yaml gains an optional update section (remote / remote_url / branch). The
resolver keeps its default - whatever remote this tree already tracks - and the
explicit choice wins when set, which is also what lets a tarball installation
that never saw git get on the update rails at all.
- RemoteURL is fetched straight into a ref namespace of its own, so a URL source
cannot be shadowed by a same-named remote's stale refs. ValidRemoteURL allowlists
https/http/ssh/git/file and local absolute paths, and rejects git's ext::
transport (which executes commands) before anything reaches argv.
- Adopt connects a plain directory to the source. Preview is side-effect free (the
fetch happens in a throwaway bare repository) and lists every local file whose
content the target would replace, plus the operator content that is put aside.
The confirmed run inits, adds origin, stashes operator content, keeps every
replaced file under .update-backup/<ts>/overwritten/ so "the update ate my edit"
has an address, lands the target content, sets upstream, rebuilds the binary.
A failure removes only the .git it created itself; once the content has landed
the tree stays adopted even if the build fails.
- Console/API: the status envelope carries the configured source;
POST /system/update/source writes it through the same config path every other
setting uses (validated, audited); POST /system/update/adopt is preview-then-job,
with the job's kind recorded. OpenAPI updated; RBAC rides the existing
update:apply prefix and the route inventory stays green.
Tests: URL-mode check/apply against real repositories (including the address
beating a remote name), URL validation table, and the full adoption story -
preview touches nothing, operator content survives, the replaced edit is in the
backup, and the adopted tree updates and rolls back like any other installation.
Runtime artifacts (agent workspaces, tool-output spill, C2 payloads, chat
uploads, workflow checkpoints, diagnostic logs) previously accumulated
without bound: most were only removed when a conversation or project was
deleted, and tmp/c2 plus workflow checkpoints were never removed at all.
Add a storage cleaner with named per-category tasks (Gitea-style), a
settings page tab, and a background sweep that is off by default so
upgrading never deletes existing data.
Safety properties, since mis-deleting live task data costs far more than
the disk saved:
- dry-run is the default; a real cleanup requires dry_run=false together
with confirm=true at the API layer, not just a frontend dialog
- sessions active within active_grace_hours are always skipped, and a
failed activity lookup skips conservatively (fail closed)
- directories whose conversation/project no longer exists are reclaimed
as orphans after orphan_grace_days
- scanners never follow symlinks and every candidate path is confined to
its category root; deletion renames to a .tmp-for-deletion marker first
so a crash leaves recoverable residue instead of a half-deleted dir
- storage:* permissions are admin-only; without the grantSystemRolePermissions
skip the default branch would have given operators an irreversible
file-deletion right
Also fix two confirmed leaks: DeleteConversation left chat_uploads files
on disk (their rows already vanished via ON DELETE CASCADE), and workflow
checkpoints had no deletion path at all.
Co-authored-by: Parallels <parallels@kali-linux-2025-2.localdomain>
Let audit_agent approve or reject with one System One call instead of chat JSON, while keeping the OpenAI-compatible backend as an option.
Co-authored-by: Cursor <cursoragent@cursor.com>