The path-escape guard that uninstall applies to recorded paths now also
covers update's obsolete-file deletion through a shared check, so a
tampered store cannot turn either delete site into an arbitrary file
removal. Updates gain a working non-interactive path: --yes now applies
to --update-bundle (locally modified files, obsolete files, and modified
mcp entries are all kept; everything else refreshes), owned mcp entries
whose recorded hash still matches the local entry take the remote side
without prompting, and the non-TTY conflict bails name the flag that
actually works per surface. An update records its new commit and version
only after files and mcp entries land, so an aborted update cannot claim
content it never wrote. The store gains a version field and rejects
stores from newer builds, the corrupt-store error no longer advises the
removal that would forfeit ownership tracking, and duplicate records
tracking one source abort a rename instead of overwriting a record.
Reinstalling from a source URL reclassifies owned unmodified files as
silent refreshes just like updates. git runs with GIT_TERMINAL_PROMPT=0
and a null stdin so private or mistyped URLs fail instead of hanging.
File comparison fills buffers fully before comparing, deleting an
obsolete file prunes emptied directories, mcp.json backfill uses the
fsynced atomic writer, --list-bundles no longer triggers builtin
backfill, bundle-name completion logs store errors instead of swallowing
them and offers --yes, and REPL .uninstall rejects unknown flags.
The user-origin marker on replaced mcp.json entries is now sticky:
re-records and cross-bundle transfers only upgrade replaced to
transferred when the prior record proves bundle origin, so updating a
bundle can no longer make uninstall delete a key the user had before the
bundle replaced it. Canonical source URLs lowercase only the host, since
self-hosted forges treat repository paths as case-sensitive and
collapsing distinct repos misdirects updates and uninstalls. git clone
invocations pass '--' before the URL so a crafted source cannot be
parsed as a git flag. Lifecycle flags (--install, --install-builtins,
--update-bundle, --uninstall) and their companions now conflict
explicitly instead of first-match dispatch silently dropping actions.
--install-from returns as a hidden tombstone that errors with the
replacement instead of feeding the flag to the LLM as prompt text.
--list-bundles dispatches before config load so a pure read no longer
boots MCP servers. write_file_atomic fsyncs before the rename so a crash
cannot persist a truncated store. REPL: .uninstall accepts --yes,
.install rejects trailing tokens after a category, and .install remote
gets a migration hint. Plus polish: host validation rejects '#' and '?',
renamed_to no longer serializes null, derived names get a debug assert
against the validator, completions share DEFAULT_GIT_HOST, README
mentions skills.
- Hang fix: 120s timeout on compression LLM call + raw_stream
channel-close handling (None => break instead of spinning)
- Supervisor effective active count: stop counting finished
JoinHandles as occupying capacity slots
- Universal tool result size cap: truncate_if_needed() on
ToolResult, applied in eval_tool_calls() after escalation block;
configurable via max_tool_result_chars in AppConfig/AgentConfig
- Windowed compression: compression_keep_last config param keeps
the N most recent messages visible after compression
- Fix pre-existing flaky test: add #[serial] to
handle_list_available_unrestricted_when_no_whitelist so it does
not race with TestConfigDirGuard-based tests that temporarily
populate the agents data dir