Skip to content

fix(gateway): persist agent delete config before archiving owned state (#7941) - #8017

Merged
Audacity88 merged 4 commits into
zeroclaw-labs:masterfrom
wangmiao0668000666:fix/issue-7941-delete-agent-persist-order
Jun 26, 2026
Merged

fix(gateway): persist agent delete config before archiving owned state (#7941)#8017
Audacity88 merged 4 commits into
zeroclaw-labs:masterfrom
wangmiao0668000666:fix/issue-7941-delete-agent-persist-order

Conversation

@wangmiao0668000666

Copy link
Copy Markdown
Contributor

Summary

  • Base: master (commit 74a806943)
  • Problem: delete_agent_cascade runs the archive + owned-state cascade before persist_and_swap. A persist failure leaves config naming the agent while its workspace has been archived and its owned stores (memory / cron / acp / session) have been purged — the inverse of the [Bug]: agent rename can move owned state before config persistence #7907 rename split-brain, in the delete direction.
  • Why: the rename path was already reordered in PR fix(gateway): persist agent rename before moving owned state #7940. The delete path is the mirror defect; Nillth scoped it out to keep the p1 tight and filed [Bug]: agent delete can purge owned state before config persistence (mirror of #7907) #7941 for the follow-up. Fix is "mechanically-identical reorder" (his words) plus an additive warnings field on the response.
  • What changed:
    • crates/zeroclaw-gateway/src/api_config.rs::delete_agent_cascade — reorder: delete_with_cascade + mark dirty + persist_and_swap first, then archive + cascade_owned_state from the post-swap config.
    • crates/zeroclaw-gateway/src/api_config.rs::MapKeyResponse — add warnings: Vec<String> (#[serde(default, skip_serializing_if = "Vec::is_empty")]).
    • crates/zeroclaw-gateway/src/api_config.rs::tests — three real-sqlite regression tests:
      1. agent_delete_leaves_owned_state_intact_when_persist_fails (cron probe)
      2. agent_delete_purges_owned_state_after_successful_persist (cron probe + workspace-archive check)
      3. agent_delete_response_carries_partial_failure_warnings (read-only archive root → warnings field in 200 OK)
    • crates/zeroclaw-gateway/src/api.rstest_state becomes pub(crate) and is re-exported at the crate root via pub(crate) use tests::test_state;, mirroring the helper Nillth added for the [Bug]: agent rename can move owned state before config persistence #7907 rename coverage. Without this, the new regression tests in api_config can't reach the AppState builder.
  • Out of scope: the recovery-path blocker on PR fix(gateway): persist agent rename before moving owned state #7940 (the "re-issue to converge" path that doesn't actually run). That belongs to the [Bug]: agent rename can move owned state before config persistence #7907 PR's review thread, not to this delete-side fix.

Label Snapshot

  • Risk: medium
  • Size: S
  • Scope: gateway
  • Module: config / agents
  • Contributor tier: trusted (5+ PRs once this lands)

Change Metadata

  • Type: fix
  • Primary scope: crates/zeroclaw-gateway/src/

Linked Issue

Closes #7941
Related: #7907 (the rename-direction mirror of this defect)

Supersede Attribution

None.

Validation Evidence

$ cargo fmt --all -- --check
(no output — clean)
$ cargo clippy --locked -p zeroclaw-gateway --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 21.89s
$ cargo test --locked -p zeroclaw-gateway --lib
test result: ok. 266 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
$ cargo test --locked -p zeroclaw-gateway --lib api_config::tests::agent_delete
test api_config::tests::agent_delete_leaves_owned_state_intact_when_persist_fails ... ok
test api_config::tests::agent_delete_purges_owned_state_after_successful_persist ... ok
test api_config::tests::agent_delete_response_carries_partial_failure_warnings ... ok
test result: ok. 3 passed; 0 failed

Real behavior proof

  • Behavior addressed: DELETE /api/config/map-key?path=agents&key=<alias> must leave the agent's workspace and owned stores intact on a persist failure, and must surface partial side-effect failures in the response body (not just the server log).
  • Real environment tested: Linux x86_64, Rust 1.94, zeroclaw 0.8.1 / master @ 74a806943.
  • Exact steps run after this patch:
    • cargo test --locked -p zeroclaw-gateway --lib api_config::tests::agent_delete
    • cargo test --locked -p zeroclaw-gateway --lib (full crate lib tests, 266 passed)
    • cargo clippy --locked -p zeroclaw-gateway --all-targets -- -D warnings (clean)
    • cargo fmt --all -- --check (clean)
  • Evidence after fix:
    test api_config::tests::agent_delete_leaves_owned_state_intact_when_persist_fails ... ok
    test api_config::tests::agent_delete_purges_owned_state_after_successful_persist ... ok
    test api_config::tests::agent_delete_response_carries_partial_failure_warnings ... ok
    test result: ok. 3 passed; 0 failed
    
    The three tests use real rusqlite cron probes (not mocks). The persist-fail test makes config_path itself a directory so save_dirty's atomic write cannot complete; the cron row stays under victim and the workspace directory is never created. The happy-path test asserts the cron row is purged, the workspace is gone from its original path, and a victim-<ts> archive directory exists with a workspace/ subdir. The partial-failure test chmods the archive root to 0o555 (unix-only) and asserts the response body has a warnings array with at least one archive mention.
  • Observed result after fix: the response shape changes only by gaining an optional warnings field (omitted when empty). The persist-fail / persist-ok / partial-fail invariants from the issue body all hold.
  • What was not tested: the test suite does not exercise the actual HTTP handler endpoint — it calls the delete_agent_cascade function directly with a constructed AppState. The HTTP layer is a thin axum wrapper; the cascade logic that the issue calls out is fully covered.

Security Impact

  • Permissions: no new permission checks. The handler still requires require_auth upstream (unchanged).
  • Network calls: none added.
  • Secrets: none touched.
  • File access: archive-dir creation, workspace fs::rename, and the per-store purges are the same as before — only the ordering changed. The persist-first reorder means a partial-failure window now shows config already deleted while workspace / owned stores may still be present, so the operator MUST inspect the warnings field. This is documented in the response struct's doc comment.

Privacy and Data Hygiene

  • Archive dir still lives under data_dir/agents/_deleted/<alias>-<ts>/ and continues to receive the owned-state export.
  • No new data is written outside the existing archive path.
  • No telemetry surface changes.

Compatibility / Migration

  • Response shape: MapKeyResponse gains an optional warnings: Vec<String> field. The field is #[serde(default, skip_serializing_if = "Vec::is_empty")], so the wire format is byte-identical for the non-agent MapKey callers (generic create / post-replace). Only the agent-delete path can populate it.
  • API contract: the success path still returns 200 OK with the same path / key / created: false body. A persist failure still returns the same error response (4xx with ConfigApiCode::ReloadFailed). The new behavior is strictly additive on the response shape.
  • Migration: none required for clients. Existing clients ignore the optional warnings field; new clients can act on it.

i18n Follow-Through

No user-visible string changes. The warnings array content is operator-facing English (e.g. "archive dir creation failed (...): <io error>"); it does not pass through Fluent.

Human Verification

  • Ran cargo test --locked -p zeroclaw-gateway --lib locally — 266 passed, 0 failed.
  • Inspected the diff manually: the reorder places persist_and_swap between the delete_with_cascade block and the archive block; the working move into persist_and_swap is the last use of working before the post-swap read-back of state.config for data_dir and the cascade.

Side Effects / Blast Radius

  • The MapKeyResponse schema gains an optional field. The OpenAPI generator (schemars::JsonSchema) re-derives the schema from the struct, so the generated OpenAPI document now lists warnings as an optional array of strings.
  • The pre-push hook in .githooks/ runs rust_quality_gate.sh + cargo test --locked — both pass on the affected crate.

Agent Collaboration Notes

  • The test_state visibility change (fnpub(crate) + pub(crate) use tests::test_state;) is the same pattern Nillth added to api.rs in PR fix(gateway): persist agent rename before moving owned state #7940. If that PR lands first, this PR's visibility change is a no-op (the symbol is already pub(crate)); if it lands second, Nillth's diff will overlap and one of us will need to rebase. The diff is two lines either way; rebase is trivial.
  • The new warnings field on MapKeyResponse is parallel to the existing warnings field on RenameMapKeyResponse (line 1282). If the maintainer prefers a single shared type, that's a follow-up refactor.

Rollback Plan

  • Revert the commit on the merge commit.
  • No feature flag involved; the change is a pure reorder + additive response field.
  • MapKeyResponse::warnings is #[serde(default, skip_serializing_if = "Vec::is_empty")] so rolling back leaves the wire format unchanged for non-agent callers.

Risks and Mitigations

  • Risk: a third caller of MapKeyResponse outside api_config breaks because the new field is mandatory in construction. Mitigation: grep confirmed all three call sites — delete_agent_cascade (this PR), handle_map_key (created + replaced, both updated), and the create-on-empty path. All four are updated. If a downstream consumer constructs MapKeyResponse via struct-literal, they need to add warnings: vec![]. The compiler will catch any miss.
  • Risk: the test_state visibility change in api.rs widens the test-only API surface. Mitigation: #[cfg(test)] on both the pub(crate) use line and the pub(crate) fn — production builds are unaffected.
  • Risk: partial-failure test is unix-only (#[cfg(unix)] chmod); the windows behavior is unverified. Mitigation: the test is #[cfg(unix)]-gated so the CI macOS + Linux runners cover it; the windows runner is not in the project's required-CI matrix. The cascade code itself is platform-agnostic — only the failure-injection mechanism is unix-specific.

@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@Audacity88 ready for review when you have a moment. All CI checks are passing locally (cargo fmt / clippy -D warnings / cargo test --locked), and the diff is scoped to fix #7941's root cause (agent delete config now persists before the owned-state archive). Happy to address any feedback — small, medium, or large — in this same PR if it fits, or split into a follow-up if not. No rush; just letting you know this is active and ready to go.

@Audacity88 Audacity88 added bug Something isn't working risk: high labels Jun 20, 2026
@Audacity88 Audacity88 added this to the v0.8.3 milestone Jun 20, 2026
@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/issue-7941-delete-agent-persist-order branch 2 times, most recently from 9b96735 to bf3403a Compare June 21, 2026 16:04
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

@Audacity88 Rebased onto current master b10d22270 (force-pushed to bf3403a4e). 14 upstream commits picked up, including Nillth's merged #7940 (rename-side split-brain fix).

Re-applied the delete-side persist-first reorder on top of Nillth's rename-side fix — both rename_agent_cascade (4 tests) and delete_agent_cascade (3 tests) now use the persist-first contract. The 4 + 3 regression tests cover the persist-fail-leaves-state-intact, persist-success-runs-side-effects, and partial-failure-aggregates-warnings shapes; both pass locally (cargo test --locked -p zeroclaw-gateway --lib agent_rename agent_delete → 7/7). No production code outside delete_agent_cascade touched.

Local validation:

cargo check --locked -p zeroclaw-gateway --tests    ✓
cargo test --locked -p zeroclaw-gateway --lib        277 passed; 0 failed
cargo clippy -p zeroclaw-gateway --all-targets -D warnings  ✓

Ready for review.

@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/issue-7941-delete-agent-persist-order branch 2 times, most recently from 40058a4 to ebce300 Compare June 22, 2026 01:38
zeroclaw-labs#7941)

Reorder `delete_agent_cascade` to match the rename path's persist-first
contract: `delete_with_cascade` + `persist_and_swap` first, then
`archive workspace` + `cascade_owned_state`. Pre-fix, a persist
failure left config naming the agent while its workspace had been
archived and its owned stores (memory / cron / acp / session) had been
purged — the inverse split-brain of zeroclaw-labs#7907 in the delete direction.

Add `warnings: Vec<String>` to `MapKeyResponse` so partial side-effect
failures (archive dir creation, workspace `fs::rename`, per-store purge
errors) are surfaced in the response body, not just the server log. The
two non-agent callers of `MapKeyResponse` (`handle_map_key` create /
`handle_map_key` post-replace) get an empty `warnings` array — the
field is `#[serde(skip_serializing_if = "Vec::is_empty")]` so the
JSON shape they emit is unchanged.

Three real-sqlite regression tests cover the contract:
  - persist-fail aborts cleanly (agent + workspace + cron all intact)
  - persist-ok purges the agent + archives the workspace
  - partial-failure (read-only archive root) carries `warnings` in 200 OK
`test_state` becomes `pub(crate)` so `api_config` regression tests
can share the AppState builder — mirrors the helper Nillth added for
the zeroclaw-labs#7907 rename coverage.
@wangmiao0668000666
wangmiao0668000666 force-pushed the fix/issue-7941-delete-agent-persist-order branch from ebce300 to 86958ca Compare June 22, 2026 06:06

@singlerider singlerider left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed at head 86958ca. Fixes #7941, the delete-direction mirror of the #7907 rename split-brain. I cross-checked the reorder against the live handler and the regression tests. No prior reviews, no other blocks.

🟢 What looks good — the persist-before-side-effects reorder is correct and complete

delete_with_cascade + mark_dirty over cascade.dirty_paths() + persist_and_swap now run first; archive and cascade_owned_state run afterward from the read-back committed config. A persist failure therefore returns an error with the agent still named in config and its workspace and owned stores untouched, which is exactly the inverse split-brain #7941 describes. agent_delete_leaves_owned_state_intact_when_persist_fails proves it: persist is forced to fail (config_path is a directory), and the seeded cron row, the workspace dir, the absence of an archive dir, and the un-swapped in-memory config are all asserted.

🟢 What looks good — the slice-4 workspace-resolution lesson is preserved

workspace is still resolved via agent_workspace_dir(alias) BEFORE the cascade removes the entry, with the explanatory comment about custom-workspace fallthrough. Only data_dir is read from the post-swap config, which is safe because data_dir is global, not per-agent. That is the right split: per-agent state captured pre-mutation, global state read post-commit.

🟢 What looks good — partial failures are surfaced, not just logged

MapKeyResponse.warnings: Option<Vec<String>> with skip_serializing_if = Option::is_none keeps the generic create/delete map-key responses byte-identical (they set None) while the agent-delete path now combines archive-dir, workspace-rename, and cascade_owned_state per-store failures into the 200 body. agent_delete_response_carries_partial_failure_warnings exercises the read-only-archive-root path end to end. The test_state re-export mirrors the helper from the rename coverage rather than widening any production surface.

Verdict: APPROVED.

@Audacity88 Audacity88 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed current head 86958ca against live PR state, #7941, the related #7940 rename-side fix, the gateway/config source, and the visible CI results. I did not run local Cargo validation in this review-only pass. I agree with @singlerider's approval: the delete-side fix matches the accepted persist-first contract.

🟢 What looks good — persist happens before destructive delete side effects

delete_agent_cascade now resolves the agent workspace while the agent entry still exists, runs delete_with_cascade, marks every dirty cascade path, and calls persist_and_swap before archiving the workspace or purging owned stores. That is the important #7941 safety property. If persistence fails, the handler returns before moving the workspace or touching cron/memory/acp/session state, so config and owned state stay consistent.

🟢 What looks good — post-persist partial failures now reach the caller

The new optional MapKeyResponse.warnings field is only populated on the agent-delete path. Generic map-key create/delete responses keep the old JSON shape when there are no warnings. The delete path combines archive-dir, workspace-rename, and cascade_owned_state warnings into the response body, so a committed config delete with a failed side effect is no longer a clean-looking 200 that only leaves evidence in server logs.

🟢 What looks good — regression coverage hits the right boundary

The tests cover the three load-bearing cases: persist failure leaves the cron row and workspace intact, successful persist removes the config entry and then purges/archives owned state, and a post-persist archive-side failure returns a warnings array. I also agree with @singlerider that preserving the pre-delete workspace resolution is the right carry-over from the rename-side fix, especially for custom workspace paths.

One merge-readiness note, separate from this approval: current master has moved over api.rs, api_config.rs, and adjacent config/cron surfaces since this branch's base. GitHub reports the branch as cleanly mergeable, but this high-risk gateway path should still get a branch refresh and refreshed CI, or equivalent merge-result evidence, before landing.

@Audacity88
Audacity88 merged commit 94ef00f into zeroclaw-labs:master Jun 26, 2026
18 checks passed
@wangmiao0668000666

Copy link
Copy Markdown
Contributor Author

Thanks @Audacity88 for the merge, and thanks to @singlerider and @Audacity88 for the reviews on the delete-side persist-first fix and the partial-failure warnings. 🙏

@wangmiao0668000666
wangmiao0668000666 deleted the fix/issue-7941-delete-agent-persist-order branch June 30, 2026 15:31
rikitrader added a commit to rikitrader/zeroclaw-x0 that referenced this pull request Jul 6, 2026
#38)

* test(zerocode): cover insecure-TLS confirmation flow (#7693) (#8178)

- ff4cece test(zerocode): cover insecure-TLS confirmation flow (#7693)
- 95edace Merge branch 'master' into fix/issue-7693-zerocode-insecure-tls-confi…

* fix(runtime): strip /think directive before per-turn tool filter on process_message (#8054) (#8216)

- 3a350d4 fix(runtime): strip /think directive before per-turn tool filter on p…
- e9e15a3 Merge branch 'master' into fix/issue-8054-surface4-think-directive-to…

* test(gateway): cover auth rate limiter loopback detection and lockout window (#8271)

- 38abc41 test(gateway): cover auth rate limiter loopback detection and lockout…
- 8cc720f Merge branch 'master' into alix/test-gateway-rate-limit

* test(runtime): pin approval-required and control branches of should_execute_tools_in_parallel (#7686) (#8222)

- 2fa7be2 test(runtime): pin approval-required and control branches of should_e…
- 9d47fb0 Merge branch 'master' into fix/issue-7686-approval-tool-batch-ordering

* test(memory): cover future timestamp decay clamp (#8297)

- bfe95e0 test(memory): cover future timestamp decay clamp
- 25359fb Merge branch 'master' into test-memory-decay-future-timestamp

* test(memory): cover namespace quota violation payload (#8298)

- 0e99ebf test(memory): cover namespace quota violation payload
- 46896bf Merge branch 'master' into test-memory-namespace-quota

* docs(providers): fill configuration examples (#7706)

- 5132b2b docs(providers): fill configuration examples
- 542d316 Merge branch 'master' into alix/x11-provider-config-docs

* docs(maintainers): align checked-in label references (#8240)

- 1a67b65 docs(maintainers): align checked-in label references
- 7fa4211 Merge branch 'master' into codex/issue-6808-canonical-label-references

* fix(runtime): cap shell subprocess memory (#7937)

- 17e1956 fix(runtime): cap shell subprocess memory
- 70865fe fix(runtime): compile memory limits across platforms
- 0b39b81 fix(runtime): handle windows process handles
- d73b3fd fix(runtime): split unix rlimit conversion
- 6d91976 fix(runtime): classify language memory errors
- 5d464d1 Merge branch 'master' into codex/issue-6916-shell-memory-limit
- 8ea10d9 Merge branch 'master' into codex/issue-6916-shell-memory-limit
- 274975f docs(skills): clarify shell memory limit scope
- 2a7395e Merge branch 'master' into codex/issue-6916-shell-memory-limit

* fix(gateway): persist agent delete config before archiving owned state (#7941) (#8017)

- 86958ca fix(gateway): persist agent delete config before archiving owned state (#7941)
- 163aa28 Merge branch 'master' into fix/issue-7941-delete-agent-persist-order
- 932d8d5 fix(gateway): include warnings in cascade delete response
- 8b3e591 Merge branch 'master' into fix/issue-7941-delete-agent-persist-order

* test(providers): pin responses-wire option propagation (#7690) (#8037)

- b85ea2b test(providers): pin responses-wire option propagation (#7690)
- c18a643 test(providers): document supported vs unsupported responses-wire options (#7690)
- 0aeeca3 ci: re-trigger Quality Gate on #8037
- fee4df0 docs(providers): enumerate responses-path runtime options that are not yet wired
- fa2a7e9 feat(providers): wire provider_timeout_secs and extra_headers through responses path
- 847a96a Revert "feat(providers): wire provider_timeout_secs and extra_headers through responses path"
- 0a958fb Merge branch 'master' into fix/issue-7690-responses-wire-options

* ci(workflows): add lockfile integrity check and npm dependency review (#8155)

- 8880007 ci(workflows): add lockfile integrity check and npm dependency review
- 2b20d2d fix: correct dependency-review-action SHA pin
- e62fbfb Merge branch 'master' into ci/lockfile-and-npm-review

* fix(config): surface model_routes and embedding_routes via Configurable and #[nested] (#7855)

- eeb60e5 fix(config): surface model_routes and embedding_routes via Configurable and #[nested]
- a213623 ci: re-trigger CI to verify flaky test
- a5de2c1 fix(config): add #[group] to route fields and map_key_sections tests
- 703189c fix(config): remove dead credential_class on nested Vec fields, add schemars x-secret on route api_key
- 5701956 fix(config): add curated sections and serde(default) rationale for route configs
- 4124322 Merge branch 'master' into fix/7701-model-route-configurable
- 38b01bc Merge branch 'master' into fix/7701-model-route-configurable
- 69abec7 Merge branch 'master' into fix/7701-model-route-configurable

* fix(runtime): strip orphaned tool_use on max-iterations exit (#7865)

- 90aaf44 fix(runtime): strip orphaned tool_use on max-iterations exit
- 87f6afa fix(runtime): drop content-null orphan dispatch instead of corrupting it
- b98fd3f Merge branch 'master' into fix/strip-orphaned-tool-use-max-iter
- 3fe6f5b Merge branch 'master' into fix/strip-orphaned-tool-use-max-iter

* docs(zerocode): document session picker workflow (#7860)

- 3e4215a docs(zerocode): document session picker workflow
- 5725d97 docs(zerocode): address session-picker review feedback
- 74f5541 Merge branch 'master' into fix/zerocode-session-docs-7746

* fix(providers): distinguish missing vs expired OpenAI Codex credentials (#8029)


The call-time credential resolver returned the same "auth profile not
found" error whether no Codex profile existed at all, or a profile existed
but its access token was expired and could not be refreshed. The latter
case misattributed a re-authentication problem to a missing profile,
sending users into a login loop pointed at the wrong fix.

Branch the error on whether a profile was actually loaded:
- no profile   -> "No OpenAI Codex credentials found. Run auth login ..."
- stale token  -> "credentials are present but expired or could not be
                   refreshed. Re-run auth login ..."

Message-only change; the auth/resolution control flow is unchanged.

* fix(telegram): bypass mention_only gate for replies to bot messages (#7958)

- 8c15897 fix(telegram): bypass mention_only gate for replies to bot messages
- bb8d0a2 fix(telegram): add tests for reply-to-bot mention_only gate bypass
- f7dd595 fix(telegram): fix reply-to-bot tests and clippy::collapsible_if
- 54a51ad chore: cargo fmt

* test(tools): cover hardware_board_info execute paths (#7867)

- 9ceb36b test(tools): cover hardware_board_info execute paths
- ba808d3 test(tools): add memory-read and probe-fallback execute coverage

* test(config): cover schema_markdown string helpers (#8243)

- 5fdf8e2 test(config): cover schema_markdown string helpers

* test(memory): cover qdrant backend classification and profile keys (#8268)

- 5c62552 test(memory): cover qdrant backend classification and profile keys

* fix(test): make control-plane PID liveness tests deterministic (#8242)

- d6f6fb5 fix(test): make control-plane PID liveness tests deterministic

* fix(runtime): warn when systemd user lingering is disabled (#8249)

- 8c467b1 fix(runtime): warn when systemd user lingering is disabled

* test(discord): cover custom id kind escaping (#8300)

- 23145f1 test(discord): cover custom id kind escaping
- ee08d15 Merge branch 'master' into test-discord-custom-id-escape

* test(channels): cover allowlist wildcard matcher short-circuit (#8299)

- c75119f test(channels): cover allowlist wildcard matcher short-circuit
- 0e52c22 Merge branch 'master' into test-channels-allowlist-wildcard

* ci(workflows): add CycloneDX SBOM generation for Rust and npm (#8158)

- bb835f0 ci(workflows): add CycloneDX SBOM generation
- e13f1b4 Merge branch 'master' into ci/sbom-generation

* fix(observability): CLI one-shot loses telemetry and token totals on exit (#8146)

- ff9fc19 fix(observability): flush telemetry before CLI one-shot process exit
- 30395de fix(observability): populate tokens_used in CLI path AgentEnd event
- 17a6ea9 Merge branch 'master' into fix/flush-guard-cli-telemetry
- d9ad3a6 Merge branch 'master' into fix/flush-guard-cli-telemetry
- 71459ab Merge branch 'master' into fix/flush-guard-cli-telemetry
- 525db3f Merge branch 'master' into fix/flush-guard-cli-telemetry
- 2570d30 Merge branch 'master' into fix/flush-guard-cli-telemetry
- 708c2f7 Merge branch 'master' into fix/flush-guard-cli-telemetry

* fix(zerocode): render only the viewport in long sessions (#8330)

- 6de2def fix(zerocode): render only the viewport in long sessions
- 26c0857 fix(zerocode): bound copy-region rebuild to the viewport

* feat(sop): out-of-band approval plane with fail-closed timeout and PriorityBased gate fix (#8304)

- feat(sop): approval-plane types - principal, decision, ledger (EPIC C, C0)
- feat(sop): approval config - ApprovalMode + ApprovalTimeoutAction (EPIC C, C1)
- feat(sop): resolve_gate chokepoint + clear_waiting_gate extraction (EPIC C, C3)
- fix(sop): fail-closed approval timeout, drop Critical/High auto-approve (EPIC C, C2) [SEC-FLIP]
- fix(sop): gate PriorityBased Critical/High runs (EPIC C, C7) [SEC-FLIP]
- feat(sop): route sop_approve through resolve_gate, honor approval_mode (EPIC C, C4)
- docs(sop): append-only store ledger is the approval audit of record (EPIC C, C5)
- feat(sop): gateway out-of-band approval routes /admin/sop/* (EPIC C, C6)
- feat(sop): CLI sop approve/deny/pending via the running daemon (EPIC C, C8)
- feat(sop): WS kind:sop approval frames resolve gates (EPIC C, C9)
- fix(sop): route new SOP CLI verb output through fluent i18n (EPIC C, C8)
- fix(sop): gate gateway SOP CLI verbs behind agent-runtime; WS let-else + deny reason (EPIC C review)
- refactor(sop): retire legacy Memory approval audit; rebuild metrics from gate ledger (EPIC C)
- refactor(sop): meter approval completion at the resolve_gate chokepoint
- i18n(sop): localize gateway WebSocket SOP approval error frames (EPIC C)
- fix(sop): fail closed when the approval-gate audit row cannot be persisted
- fix(sop): restore deterministic checkpoint resume + harden gate transitions (review)

* feat(acp): add opt-in MCP support for standalone ACP sessions (#8237)

- 014eeda feat(acp): add opt-in MCP support for standalone ACP sessions
- d243b5e refactor(acp): make MCP opt-in a per-agent setting
- 877a967 fix(acp): restore ACP sessions under their owning agent
- 6ee4a30 fix(acp): release sessions lock before agent/MCP startup in session/new
- 93e0741 ci: re-trigger checks (prior Test job hit a runner disk-space error)
- d8b2f71 Merge branch 'master' into feat/acp-enable-mcp

* fix(ci): defer stable-pointer tag check to deploy time (#8344)

- 2441512 fix(ci): defer stable-pointer tag check to deploy time
- 91314ad fix(ci): defer stable-pointer publish until version dir exists
- 7bd7959 Merge branch 'master' into ci/docs-stable-pointer-defer-tag-check

* test(tools): cover email_imap TLS close_notify detection (#8346)

- 0e253b6 test(tools): cover email_imap TLS close_notify detection
- 6cad799 Merge branch 'master' into test/email-imap-tls-close-notify

* test(tools): cover report template substitution safety and html escaping (#8270)

- 014e15a test(tools): cover report template substitution safety and html escaping
- 5cfe2e0 Merge branch 'master' into alix/test-report-templates

* docs(labels): document ACP channel label (#8406)

* fix(agent/loop-detector): do not count failed tool results as "no progress" (#8213)

- cf486de fix(agent/loop-detector): do not count failed tool results as "no progress"
- bfa6557 fix(agent/loop-detector): gate the hash-based identical-output abort on success too
- a077711 Merge branch 'master' into fix/loop-detector-skip-failed-results

* fix(runtime): forward narration emitted after a native tool call (#8329)

- b2d3b26 fix(runtime): forward narration emitted after a native tool call
- 3e34e60 Merge branch 'master' into fix/zerocode-narration-flush

* fix(provider): cool down rate-limited fallback entries (#8317)

- aa877fa fix(provider): cool down rate-limited fallback entries
- 0053fc2 Merge branch 'master' into codex/issue-6074-provider-rate-limit-cooldown

Co-authored-by: Aleksandr Prilipko <31770101+zverozabr@users.noreply.github.com>
Co-authored-by: SimianAstronaut7 <79373020+SimianAstronaut7@users.noreply.github.com>

* docs(mdbook): escape generated CLI placeholders (#8204)

- 8ca08eb docs(mdbook): escape generated CLI placeholders
- 831f70c docs(mdbook): use table for html tag escape check
- 4497d8e Merge branch 'master' into codex/issue-7269-cli-placeholder-escaping

* test(tools): cover pushover notification shape overlap (#8356)

- 382b4db test(tools): cover pushover notification shape overlap
- 42b0ca8 Merge branch 'master' into codex/issue-6165-pushover-notify-proof

* fix(cli): add confirmation feedback after secret prompt input (#7856)

- 04444bd fix(cli): add confirmation feedback after secret prompt input
- ef577ca style: apply cargo fmt to secret prompt feedback lines
- 6bb5bc6 style: add i18n-exempt comments to secret prompt confirmation echoes
- 0a351b9 fix(cli): replace bare eprintln! strings with Fluent i18n keys for secret prompt feedback
- cc42c56 fix(cli): implement pre-submit feedback for secret prompts using crossterm raw mode
- 1068feb fix(cli): harden secret_prompt with RAII raw-mode guard and consistent confirmation guards
- e10b49d fix(cli): return raw buffer from secret_prompt, re-prompt on empty required input

* test(eval): cover trace case parsing and suite loading (#8252)

The trace fixture format and suite loader had no unit tests. Add coverage:

- `TraceResponse` serde tag dispatch (text / tool_calls) with the
  `#[serde(default)]` token fields defaulting to 0;
- `LlmTrace` defaulting `expects` when the key is omitted;
- `LlmTrace::from_file` parsing a fixture from disk;
- `load_suite` filtering to `*.json` and returning entries sorted by path.

Test-only; no behavior change.

* fix(self_test): correct comment numbering in run_full function (#8212)

The comment numbering in run_full was incorrect:
- Line 69: changed from '// 9.' to '// 10.' for gateway health check
- Line 72: changed from '// 10.' to '// 11.' for memory round-trip check
- Line 75: changed from '// 11.' to '// 12.' for WebSocket handshake check

This matches the actual sequence of checks since run_quick already has 9 checks.

* docs(tools): document relationship memory workflows (#8263)

* fix(windows): share cmd shell command construction (#8247)

- 84989b2 fix(windows): share cmd shell command construction
- 8b9d112 fix(windows): name cmd shell protocol tokens

* ci(release): build release artifacts from the canonical feature registry (#8343)

- dadb4fe ci(release): build release artifacts from the canonical feature registry
- 651a012 fix(install): align prebuilt messaging with the dist release feature set
- 3a7f926 fix(install): make --preset full deliver the advertised feature set

* fix(channels): localize core runtime command replies (#7858)

- 5f1d109 fix(channels): localize core runtime command replies
- 97b3e5c fix(channels): pass timeout text as &str to finalize_draft
- 1691b39 chore: merge master and resolve en/cli.ftl conflict
- ea77180 chore: merge master and resolve i18n conflicts
- 7592840 chore: merge master and resolve zh-CN/cli.ftl conflict

* revert(runtime): remove shell subprocess memory cap (#8417)

This reverts the shell/skill subprocess RLIMIT_AS memory ceiling introduced
in #7937. The cap defaulted to 512 MiB and set both rlim_cur and rlim_max,
so it clamped the virtual address space of every shell and skill subprocess
and could not be raised by the child. In practice this made zerocode
unusable: cargo, rustc, and git fail under a 512 MiB RLIMIT_AS because their
threads reserve far more virtual address space than that despite using little
real memory.

* fix(channels/discord): authorize autocomplete in parent-allowlisted threads (#8110)

The type-4 (APPLICATION_COMMAND_AUTOCOMPLETE) arm gated the interaction with
`interaction_gate(..., thread_parent = None)`, while the normal message paths
resolve the thread parent and pass it to `channel_passes_filter`. In a thread
whose parent channel is in `channel_ids` (but the thread id is not), normal
messages pass the filter, yet autocomplete failed closed to an empty choice
set — inconsistent authorization for the same user in the same thread.

Resolve the thread parent from the existing `thread_channels` cache (no REST)
in the autocomplete arm and pass it to the gate. A parent populated by an
earlier message in the thread now authorizes autocomplete exactly as the
message path does (#6829). The lookup is cache-only, so the per-keystroke path
stays side-effect-free; an uncached thread still yields no completions
(fail-closed) rather than issuing an unbounded REST probe on every keystroke.

Closes #8103.

* fix(daemon): fail fast when the gateway address is already in use (#8115)

- 28b4743 fix(daemon): fail fast when the gateway address is already in use
- 29af278 fix(daemon): localize gateway-bind messages and defer non-AddrInUse b…
- 8a3961d Merge branch 'master' into fix/7895-daemon-gateway-reuse

* feat(skills): dashboard write-guard, skipped-audit, and shadowed_by (#7963 follow-ups) (#8082)

- bbeae82 feat(skills): write-guard + skipped-audit + shadowed_by for the dashb…
- aee06c5 feat(web/skills): surface skipped-audit + shadowed_by on the skills page
- 541013f Merge remote-tracking branch 'upstream/master' into feat/dashboard-sk…
- 23541b1 Merge remote-tracking branch 'upstream/master' into feat/dashboard-sk…
- 7fe83f8 refactor(skills): address review nits on dashboard follow-ups
- 8120f28 Merge remote-tracking branch 'upstream/master' into feat/dashboard-sk…
- 018bff7 Merge remote-tracking branch 'upstream/master' into feat/dashboard-sk…
- f9af90c Merge remote-tracking branch 'upstream/master' into feat/dashboard-sk…
- 0057d98 refactor(skills): parameterize cached_load over its store to isolate …
- 475f2a6 Merge branch 'master' into feat/dashboard-skills-followups

* feat(observability): add rotating log-persistence mode with size/date/retention rotation (#8307)

- 1a73cfe feat(observability): add rotating log-persistence mode with size/date…
- 094a4cc Merge branch 'master' into feat/7878-log-rotation
- 48476d5 Merge branch 'master' into feat/7878-log-rotation
- be37095 Merge branch 'master' into feat/7878-log-rotation

* test(tools): cover attribution registry contract for unit-struct tools (#8351)

- 69dd692 test(tools): cover attribution registry contract for unit-struct tools
- 1428d67 Merge branch 'master' into test/attribution-registry-coverage

* feat(tool:browser): allow http:// URLs in browser_open (was https-only) (#8136)

- 56268dd feat(tool:browser): allow http:// URLs in browser_open
- fabd6cc docs(browser): make browser_open http default posture explicit
- 938ed3e docs(browser): clarify allowlist does not enforce HTTPS
- 43a4267 Merge branch 'master' into worktree-allow-http

* fix(channels): transcode Telegram TTS to Opus and wire typed STT provider (#7019)

- 5817b97 fix(channels): send non-opus TTS via sendAudio and wire agent transcr…
- b88de0c fix(channels): resolve typed transcription provider aliases in media …
- a902e2f fix(channels/telegram): wire typed STT providers into channel-interna…
- 24627fd fix(channels/telegram): use sendVoice after synthesize_opus guarantee…
- 431f2fb fix(channels): suppress TTS on error messages
- b8bca9b fix(channels): remove duplicate output_format impls and add missing m…

* fix(tools/cron): accept job name in cron_update and cron_remove (#8007)

Both tools previously required a UUID job_id, forcing a cron_list
lookup before every update or remove. Gemini 2.5 Flash would call
cron_list, get the JSON back, and echo it as a response instead of
chaining to the update call.

Adds resolve_job_id_or_name() to the cron store: tries exact ID match
first, falls back to case-insensitive name search, errors on ambiguity.
cron_update and cron_remove now resolve through this before acting, so
"reschedule morning_briefing to 8am" is a single tool call.

* fix(scoop): register zerocode.exe in manifest (#8276)

The Scoop manifest only registered zeroclaw.exe as a shim, and
zerocode.exe was missing from PATH although present in
~\scoop\apps\zeroclaw\current\zerocode.exe.

Update dist/scoop/zeroclaw.json bin field from a single string
to an array including both zeroclaw.exe and zerocode.exe.

Fixes #8275

* test(config): cover in-root path parent normalization (#8292)

* feat(ci): add arm64 docker target (#5187)

- 0382abb feat(docker): cross-compile arm64 images from the source Dockerfile
- 5ec9a1f ci(docker): publish arm64 for Dockerfile-based image tags
- 26b06f6 Merge branch 'master' into feat/add-arm64-docker-target

* fix(channels/wechat): make wechat channel can handle streaming chat response. (#7437)

- 98763f3 fix(channels/wechat): make wechat channel can handle streaming chat response.

* fix(runtime): read heartbeat tasks from agent workspace (#8402)

Closes #8366

* test(config): cover domain input normalization (#8293)

* fix(channels): serialize per-sender session persistence to prevent race (#7847)

- cf50c17 fix(channels): serialize per-sender session persistence to prevent race (#7753)
- 033a435 fix(channels): bind Arc before lock to fix E0716 temporary drop
- ce3b8b6 fix(channels): add barrier-gated concurrency test for per-sender persist lock serialization
- 2b15379 fix(channels): make concurrency test observe ordering, not just count
- aaab32d fix(channels): apply cargo fmt
- 5778fae fix(channels): add missing thinking_overrides field in persist-lock concurrency test
- a024a46 fix(channels): refresh persist lock test contexts

* fix(channels): keep channel system prompt byte-stable for prefix caching (#6360) (#8174)

Closes #6360.

The channel path rebuilt the system prompt every turn with volatile data
(datetime, reply_target, sender, message_id, cron_add delivery hint),
invalidating provider-side prompt caching on every message. This moves
volatile per-turn context into a `[turn-context]` preamble on the current
outgoing user turn, while keeping cached channel history clean.

Memory recall output now rides in the same outgoing user turn preamble instead
of being appended to the system prompt, matching the CLI shape. The runtime
preamble is also always prepended when `reply_target` is non-empty, so
user-controlled `[turn-context]` text cannot suppress authoritative channel
context.

Related: PR #6630.

* feat(mcp): resource & prompt client surface with policy-gated dispatch tools (#8403)

- 348c01b feat(mcp): add resource protocol types
- e050d49 feat(mcp): add prompt protocol types
- 81926aa feat(mcp): probe and store server capabilities at handshake
- 44fd732 feat(mcp): add capability-gated resource/prompt dispatch on McpServer
- 0d57931 feat(mcp): registry resource/prompt surface + mcp_resources/mcp_prompts tools (policy-gated)
- 31be3c3 test(mcp): lock subagent narrowing of mcp capability tools
- 4fa86a7 fix(mcp): prefix prompt list names, thread list cursor, surface isError envelopes

* fix(channels): initialize persist locks in cache-stability tests (#8434)

* chore(channel-matrix): upgrade Matrix Rust SDK to 0.18.0 (#8421)

Bump the pinned matrix-sdk dependency in zeroclaw-channels from 0.17 to
0.18.0 and re-resolve Cargo.lock. This pulls Ruma 0.16.0 (ruma-client-api
0.24, ruma-common 0.19, ruma-events 0.34, ruma-html 0.8, ruma-macros 0.19)
and the matching matrix-sdk-base/common/crypto/sqlite/store-encryption 0.18
crates.

The 0.18 breaking changes (RumaApiError becoming a UiaaResponse alias and
Pusher::set gaining an append parameter) are not on any path the Matrix
channel uses, so no source changes are required.

This lands the upstream fix matrix-rust-sdk#6594 for the Arc<ClientInner>
reference cycle reported in matrix-rust-sdk#6573, which caused the
per-reload memory leak tracked in #6651.

* docs(agents.md): sync paths and crate list with current workspace layout (#8341)

- 349a88f docs(agents.md): sync paths and crate list with current workspace layout
- 88a2d1c Merge branch 'master' into docs/agents-md-sync-paths

* feat(approval): route tool approvals to a distinct approver channel (#8231)

- a75cbae feat(approval): route tool approvals to a distinct approver channel
- 39f034d Merge remote-tracking branch 'upstream/master' into feat/a2a-approval…
- 7d7e4db Merge remote-tracking branch 'upstream/master' into feat/a2a-approval…
- 9c26bf5 feat(approval): honor approval_route on the non-interactive turn path
- d17cfb8 docs(autonomy): clarify approver_channel is a platform-qualified regi…
- bc64539 Merge branch 'master' into feat/a2a-approval-router
- 133144b Merge branch 'master' into feat/a2a-approval-router

* ci(release): fail early on package token access (#8352)

- 6e10dda ci(release): fail early on package token access
- 5ae71a9 Merge branch 'master' into codex/release-publish-preflight
- f972756 Merge branch 'master' into codex/release-publish-preflight

* perf(web-search): cache strip_tags regex in a LazyLock static (#8350)

- cbb0bfd perf(web-search): cache strip_tags regex in a LazyLock static
- 33dbdfe Merge branch 'master' into fix/strip-tags-lazy-regex

* fix(acp-bridge): strip UTF-8 BOM from config.toml before TOML parsing (#8326)

- 821d95a fix(acp-bridge): strip UTF-8 BOM from config.toml before TOML parsing
- cff90f7 Merge branch 'master' into fix/acp-bridge-bom-tolerance

* docs(runtime): document max_history_messages hard cap alongside whole-turn trim (#8436)

- 66584e7 docs(runtime): document history-msg hard cap alongside whole-turn trim
- 997780c Merge branch 'master' into fix/8369-history-trim-docs-contract

* refactor(log): extract JSONL write pipeline into testable helper (#8437)

- 0f214e2 refactor(log): extract JSONL write pipeline into testable helper
- 7981583 Merge branch 'master' into refactor/log-jsonl-write-helper

* feat(obs): capture prompt/completion content on llm.response spans (#6966)

- 6df195a feat(obs): add LlmMessageSnapshot scaffolding to LlmResponse event
- 209e932 feat(obs): add credential-scrubbing capture_llm_messages helper
- 110c476 feat(obs): capture llm.call messages at both LlmResponse emission sites
- d466595 feat(obs): emit gen_ai.input/output.messages + system_instructions on…
- e6bd51c docs(obs): note gen_ai message attrs + trace cost/privacy profile for…
- faf1a03 style(obs): apply rustfmt to new capture/serializer tests
- 73288d4 Merge remote-tracking branch 'upstream/master' into feat/otel-genai-l…
- 3725b9e docs(obs): replace prose em-dash to satisfy docs-style gate
- 0c2358d Merge remote-tracking branch 'upstream/master' into feat/otel-genai-l…
- 8982324 Merge branch 'master' into feat/otel-genai-llm-messages
- 4ca2c87 Merge branch 'master' into feat/otel-genai-llm-messages
- 52e7698 Merge branch 'master' into feat/otel-genai-llm-messages

* feat(config): add independent delegate targets (#8239)

- 98f1b04 feat(config): add independent delegate targets
- 35d3739 fix(runtime): block always_ask independent delegates
- c246ece fix(runtime): allow agentic delegates without tools
- 092bc45 test(runtime): migrate delegate rename cascade test
- 88a0554 fix(runtime): avoid background delegate reauthorization
- cb27b82 fix(runtime): resolve delegate mode through roster
- ee761ce Merge branch 'master' into feat/independent-delegates
- 51c9a89 docs(delegate): explain delegation invariants
- f9d79ea Merge branch 'master' into feat/independent-delegates

* ci(release): cosign signing, SLSA provenance, and SBOM for release pipeline (#8058) (#8404)

- a79e87d ci: add cosign signing, SBOM generation, and SLSA provenance to relea…
- ddd0b8a fix(ci): move continue-on-error from job level to SLSA generator input
- 330c905 fix(ci): gate SLSA upload behind publish, scope OIDC to signing jobs,…
- 46d2d46 ci(release): add cosign signing to stable release docker job
- 633c125 Merge branch 'master' into ci/release-signing-sbom-provenance

* test(mcp): regression-test mcp_bundles enforcement + warn on no-bundle misconfig (#8370)

- 95f2aa9 test(runtime): cover unscoped-agent zero-MCP path in deferred mode (#7733)
- 771ab9b test(runtime): cover unscoped-agent zero-MCP path in eager mode (#7733)
- c218509 fix(config): warn when mcp.servers configured but no mcp_bundles exist (#7733)
- 53373e6 test(channels): pin resolver contract for orchestrator MCP scoping (#7733)
- 7410e11 test(gateway): cover append_scoped_mcp_tools no-bundle no-op (#7733)
- 90b839d docs(mcp): note bundle-restart semantics + link agent.rs to regression tests (#7733)

* fix(cost): atomic ledger appends + recover concatenated records (#8412)

- f4420a9 fix(cost): atomic ledger appends + recover concatenated records
- 37ff966 fix(cost): route malformed-record warning through typed attrs

* feat(elicitation): ACP multiple-choice for the ACP channel and Zerocode Code tab (#8338)

- 2e4128e feat(api): add ElicitationCapabilities parser for ACP elicitation RFD
- 4568159 feat(api): add ElicitationRequest/Response/Mode wire types
- 3740b61 feat(channels): plumb client elicitation capabilities to AcpChannel
- 42a594c feat(channels): add single_select_schema helper + sensitive-name trip…
- 2c04577 feat(channels): route ACP request_choice through elicitation/create w…
- bf23402 feat(channels): add request_multi_choice + ACP elicitation multi-sele…
- 21e94d4 feat(tools): poll uses ACP elicitation when channel advertises form s…
- fe99b7a docs: repoint elicitation RFD comments at Phase 1 spec
- 1a84c24 docs(changelog): note ACP elicitation Phase 1 (multiple-choice)
- 0acaa0c Merge from origin/master
- 29ad66f feat(zerocode): wire ACP elicitation into the Code tab (Phase 2 modal)
- a4fa4d1 fix(zerocode): elicitation modal claims focus; drop gitignored spec refs
- 71689cb Merge branch 'master' into feat/acp-elicitation
- 9f6453b docs(zerocode,api): drop reviewer handles from comments; refresh stal…

* docs(labels): document retained scoped labels (#8418)

* fix(providers): clean Anthropic tool schemas before native serialization (#7961)

- 86895ad fix(providers): clean Anthropic tool schemas before native serialization
- b7a4878 chore: cargo fmt
- ca237e1 Merge branch 'master' into fix/anthropic-schema-cleaning

* fix(providers): omit tool_choice when the tool list is empty (#7864)

- cf73d01 fix(providers): omit tool_choice when the tool list is empty
- 19c1356 docs(providers): correct tool_choice comment; harden regression test
- fa7be20 Merge branch 'master' into fix/openai-compat-tool-choice-empty
- c061b89 Merge branch 'master' into fix/openai-compat-tool-choice-empty

* fix(provider): prefer chatgpt_account_id claim in Codex JWT account extraction (#8002)

- 753d700 fix(provider): parse https://api.openai.com/auth as nested object for chatgpt_account_id
- ad1ab1f fix(provider): collapse nested if-let to satisfy clippy::collapsible_if
- 95c705a chore: cargo fmt
- 6c63801 Merge branch 'master' into fix/codex-chatgpt-account-id-claim
- 3a3f94b Merge branch 'master' into fix/codex-chatgpt-account-id-claim

* fix(providers): promote tool-result image markers to image_url on native tool calls (#8339)

- 39dad52 fix(providers): promote tool-result image markers to image_url on native tool calls
- 3611d2d Merge branch 'master' into fix/8327-tool-image-parts
- 25ba308 Merge branch 'master' into fix/8327-tool-image-parts

* fix(docs): silence mdbook placeholder warnings (#8435)

* docs(architecture): document runtime state ownership (#8422)

- f2a8640 docs(architecture): document runtime state ownership
- 67590c6 Merge branch 'master' into codex/issue-6808-runtime-state-persistence

* ci(workflows): add monthly cargo outdated dependency scan (#8176)

- 0958d56 ci(workflows): add monthly cargo outdated dependency scan
- 5e79862 fix(ci): propagate outdated scan crashes
- 094c0b8 Merge branch 'master' into ci/cargo-outdated

* fix(runtime): skip NO_REPLY sentinel in cron and heartbeat delivery (#8405)

- 6c9b0fc fix(runtime): skip NO_REPLY sentinel in cron and heartbeat delivery
- ab9b379 fix(runtime): narrow NO_REPLY suppression to quiet forms; cover deliv…
- 15b95f1 Merge remote-tracking branch 'upstream/master' into fix-2128-clean
- 9e24b6e Merge branch 'master' into fix-2128-clean
- f98cb4d Merge branch 'master' into fix-2128-clean

* feat(browser): support allowed_private_hosts for browser tools (#8171)

- cdf3fa7 feat(browser): support allowed_private_hosts for browser tools
- 80d7f91 docs(browser): align allowed_private_hosts scheme doc with code
- 6a5ecdf fix(browser): reject URL userinfo to close SSRF parser-mismatch
- 66c0830 fix(browser): use reqwest::Url to close authority-boundary SSRF bypass
- 3c84937 fix(browser_open): align scheme test and doc with relaxed http/https ...policy
- d7c0921 Merge branch 'master' into worktree-browser-allow-private-hosts
- b139b93 Merge branch 'master' into worktree-browser-allow-private-hosts

* test(config): cover provider alias predicates (#8241)

The China-provider alias predicates and `canonical_china_provider_name`
in `provider_aliases.rs` had no unit coverage. Add a focused suite that
locks down current behavior:

- each family's alias set (GLM / Z.ai / MiniMax / Moonshot / Qwen and the
  standalone Qianfan / Doubao / Bailian families), including the
  global-vs-CN and intl-vs-CN splits;
- that `is_qwen_alias` is the union of its CN/intl/US/OAuth sub-aliases and
  does not fold in the separate Bailian family;
- `canonical_china_provider_name` mapping for every family plus its
  precedence, and a cross-check that the returned canonical name always
  agrees with the matching `is_*_alias` predicate;
- that `family_honors_wire_api` returns true only for the
  bring-your-own-endpoint families (openai / llamacpp / custom).

Test-only; no behavior change.

* test(hardware): cover serial path allowlist helpers (#8245)

The serial-path allowlist in `util.rs` gates which device paths the
hardware serial peripheral may open, but it had no unit tests. Add
coverage for:

- `is_serial_path_allowed`: accepts each known prefix (/dev/ttyACM,
  /dev/ttyUSB, the usbmodem/usbserial families, COM) and rejects paths
  outside it, including a name missing the /dev/ prefix and the empty
  string;
- `serial_path_allowlist_hint`: lists every prefix as a glob;
- `should_open_serial_nonexclusive` / `serial_open_baud`: real device
  paths open exclusively at the configured baud.

Test-only; no behavior change.

* test(api): cover MediaAttachment classification (#8248)

`MediaAttachment::kind` and `from_file` had no unit tests. Add coverage
for the classification rules:

- `kind` prefers a known media MIME type over the file extension, matches
  the MIME prefix case-insensitively, and falls back to the extension when
  the MIME type is uninformative (e.g. application/octet-stream);
- extension-based classification across audio/image/video and the Unknown
  fallback, including case-insensitive and no-extension inputs;
- `from_file` reads the bytes, derives file_name, maps the extension to a
  MIME type, and propagates a read error for a missing path.

Test-only; no behavior change.

* test(api): cover JSON-RPC request/response/error helpers (#8250)

The JSON-RPC envelope types had no unit tests. Add coverage for:

- `JsonRpcRequest::new` (sets the protocol version, wraps the id in
  `Some`) and the `#[serde(default)]` params field (omitted params
  deserialize to `Null`);
- `JsonRpcNotification::new` (version set, no id field serialized);
- `JsonRpcResponse` skipping `result` / `error` when `None`;
- the standard JSON-RPC 2.0 error codes;
- `JsonRpcError` serde round-trip including the optional `data` field.

Test-only; no behavior change.

* test(hardware): cover board registry vid/pid lookup edge cases (#8253)

`lookup_board` had a happy-path test and an all-zero miss. Add the edge
cases that guard the registry contract:

- a match requires both VID and PID — a known VID with an unknown PID, or
  a known PID under the wrong VID, must miss (guards against the lookup
  degrading into a VID-only match);
- the two PIDs registered under arduino-uno both resolve, and arduino-mega
  resolves under the shared Arduino VID via its own PID;
- every (vid, pid) pair in the table is unique;
- every known board resolves to itself via lookup_board.

Test-only; no behavior change.

* test(tools): cover web-search routing defaults and normalization (#8256)

resolve_web_search_provider had tests for each provider alias and the
unknown-provider fallback, but two contracts were uncovered:

- "" and "default" route to DuckDuckGo with used_fallback = false — an
  explicit/empty default must be distinguished from an unknown-provider
  fallback, since the flag drives the warn-on-unknown path;
- resolution trims surrounding whitespace and is case-insensitive.

Test-only; no behavior change.

* test(tools): cover node capability approval gating and group assembly (#8257)

node_capabilities had coverage for names/descriptions/schemas and the
sensitive-capability approval path, but two contracts were uncovered:

- requires_approval matches the "camera."/"screen."/"location." prefix
  (with the trailing dot) and treats unknown / non-sensitive names as not
  requiring approval — a bare "camerafoo" must not match;
- all_standard_capabilities assembles every group (camera, screen,
  location, and the system.notify notification capability).

Test-only; no behavior change.

* test(tools): cover truncate_with_ellipsis (#8259)

util_helpers had tests for floor_char_boundary and clean_verbatim_path
but none for truncate_with_ellipsis. Add coverage:

- short or exactly-at-budget strings are returned unchanged (no ellipsis);
- longer strings are cut to the char budget with "..." appended;
- a trailing space in the kept slice is trimmed before the ellipsis;
- the budget counts Unicode chars, not bytes ("héllo" at 2 -> "hé...").

Test-only; no behavior change.

* test(memory): cover importance scoring weights and category bases (#8260)

importance.rs tested the Core/Conversation base scores, the keyword
boost, and weighted_final_score with symmetric inputs only. Add the
uncovered contracts:

- Daily (0.3) and Custom (0.4) category base scores;
- weighted_final_score weights each signal independently (hybrid 0.7,
  importance 0.2, recency 0.1) — symmetric inputs cannot catch a weight
  swap;
- keyword_boost is case-insensitive and yields no boost without signals.

Test-only; no behavior change.

* feat(sop): daemon SOP maintenance tick (EPIC A1) (#8391)

Adds the periodic daemon tick that drives the SOP engine's maintenance, activating EPIC C's fail-closed approval timeout and B's retention/reaper.

Engine: run_maintenance_tick() runs one pass - fire fail-closed approval timeouts, reap expired concurrency-claim leases, prune terminal runs past max_finished_runs - and returns a MaintenanceSummary for observability. The overdue-gate predicate is extracted into overdue_waiting_run_ids() and shared with check_approval_timeouts so the tick can count escalations. The tick self-approves nothing; timeout handling follows approval_timeout_action (default escalate).

Config: sop.maintenance_interval_secs defaults to 60; 0 disables the tick. The resulting behaviors stay independently gated and fail-closed-safe.

Daemon: spawn_sop_maintenance wires the tick into both daemon paths, behind agent-runtime, before the engine is moved into the registry. The returned JoinHandle is owned by the current foreground daemon/channel run and aborted when that run exits, so reloads do not leave stale maintenance loops on old SOP engine/config/store handles.

Not included: firing cron-scheduled SOP triggers on the same tick (#6686). Executing an AutoApprove-resumed step needs the live SOP executor (EPIC A2); until then the tick logs it.

* feat(sop): wire cron triggers into maintenance tick (#8400)

Wires cached SOP cron triggers into the daemon/channel maintenance tick, giving
configured [[triggers]] type = "cron" SOPs a production caller. Threads the shared
SOP audit logger into the maintenance task and records cron started/skipped/no-match
counts alongside the maintenance summary. Rebuilds the maintenance/cron task per
daemon reload and aborts the old task when the foreground runner exits. Deduplicates
fired cron expressions per check window so SOPs sharing one expression dispatch once.

Fixes #6686. Depends on #8391.

* feat(sop): execute live SOP steps (#8399)

- 6ad3212 feat(sop): execute live SOP steps
- f8d5141 fix(sop): avoid duplicate live executor metrics

* feat(plugins): wasmtime component-model host for tool/channel/memory (#8368)

- 8be579c feat(plugins): wasmtime component-model tool host, retire extism
- cf45797 feat(plugins): channel and memory component-model adapters
- e0e8b2b fix(plugins): wire sandboxed WASI p2 into plugin linkers
- 071ca34 fix(plugins): route pulley backend through the component compile path
- d501f5d fix(plugins): route pulley through deserialize arm, not compile arm
- 0d0b683 fix(plugins): link logging host import for all three plugin worlds
- bf2a757 test(plugins): end-to-end reference plugin load from a throwaway config dir
- c9f2900 test(plugins): refresh reference fixture from refactored published source
- 04f18fe fix(plugins): surface channel poll-message traps instead of silent idle
- b2634d6 docs(plugins): document the wasmtime component-model plugin host
- fa41882 test(plugins): drop committed reference-plugin.wasm binary fixture
- 3ce4ae7 fix(docs): point plugin-protocol config link at tracked reference index

Co-authored-by: bheatwole <5197535+bheatwole@users.noreply.github.com>
Co-authored-by: JordanTheJet <24375129+JordanTheJet@users.noreply.github.com>

* feat(sop): add step contract substrate (#8416)

- 5eb6d0e feat(sop): add step contract substrate
- 230192b docs(sop): document step contract substrate
- b8ea2e2 fix(sop): preserve policy denies for mandatory tools
- 306e82e fix(sop): add ..SopStep::default() to cron regression test fixture

* feat(sop): enforce step schemas at engine boundary (#8420)

- 0e9c5f0 feat(sop): enforce step schemas at engine boundary
- 502427b docs(sop): document step contracts
- 2d48b37 test(sop): satisfy schema enforcement lint

* feat(sop): enforce step routing (#8430)

- ca8ebd6 feat(sop): enforce step routing
- 4aff05b fix(sop): persist routed checkpoint state
- 05c8f6d fix(sop): count retries after initial failure

* test(runtime): cover turn-loop cancellation error types and chain detection (#8456)

- 4e987a9 test(runtime): cover turn-loop cancellation error types and chain det…
- 4e8040c test(runtime): replace disallowed anyhow! macro with anyhow::Error::msg
- 5e6212f Merge branch 'master' into alix/test-turn-outcome

* test(rpc): cover locale parsing and frame schema validation (#8489)

- e3fd312 test(rpc): cover locale parsing and frame schema validation
- 7d5d9db Merge branch 'master' into test/rpc-types-locales-coverage

* fix(tools/calculator): cap values array length to prevent OOM (#8481)

- ab0684f fix(tools/calculator): cap values array length to prevent OOM
- 7df1498 Merge branch 'master' into fix/calculator-values-cap

* feat(cost): cost/org snapshot RPC + cost/query [from,to) window (#8482)

- 80c78c6 feat(cost): cost/org snapshot RPC + cost/query [from,to) window
- 07c3773 fix(cost): cost/org errors on unreadable snapshot, null only for NotF…
- 834aca6 fix(cost): clippy struct-update in test helper; document cost/query a…
- 6bffe0d test(cost): bounded cost/query RPC coverage (valid bounds + INVALID_P…
- 1ba8778 Merge branch 'master' into feat/cost-org-rpc

* feat(channels): add passive WhatsApp group context (#8389)

- 963ac53 feat(channels): add passive WhatsApp group context
- 53a76b0 Merge branch 'master' into feat/8379-passive-group-context
- 86a9b78 test(channels): add new ChannelMessage fields to merged telegram test…
- 15ef06f Merge branch 'master' into feat/8379-passive-group-context
- b9ad1b6 fix(plugins): add new ChannelMessage fields to wasm channel inbound c…
- 8719982 Merge branch 'master' into feat/8379-passive-group-context

* fix(deps): bump anyhow for RUSTSEC-2026-0190 (#8500)

* fix(tool-call-parser): replace Regex::new().unwrap() with expect() (#8388)

Replace 14 instances of .unwrap() on Regex::new() in LazyLock
initializers with .expect() carrying the static variable name,
so a broken regex pattern during refactoring produces an
actionable panic message.

* ci(workflows): add Trivy container scanning for PR and release images (#8168)

- f75d74d ci(workflows): add Trivy container scanning for PR and release images
- de1afa7 fix(ci): split trivy sarif upload
- c806742 Merge branch 'master' into ci/trivy-container-scanning
- 913efc4 Merge branch 'master' into ci/trivy-container-scanning

* feat(runtime): add calendar no-show SOP triggers (#8419)

- af5b68b feat(runtime): add calendar no-show SOP triggers
- 866da50 Merge remote-tracking branch 'origin/master' into codex/issue-6074-ca…
- 2e9c1a3 Merge branch 'master' into codex/issue-6074-calendar-no-show

Co-authored-by: Nim G <theredspoon@users.noreply.github.com>

* feat(cost): offline pricing catalog (consume-only $0 fallback) (#8380)

- 83290ec feat(cost): offline pricing catalog, consume-only, $0 fallback
- 2da8713 build: revert Cargo.lock to master (catalog adds no new dependency)
- d45b8e8 fix(cost): clear global pricing catalog when pricing.json is removed

* fix(channels): preserve image bytes for a configured vision_model_provider (#8468)

The media pipeline only embedded base64 image data when the default model
provider reported vision support. When [multimodal].vision_model_provider routes
images to a dedicated vision provider, the pipeline stripped the bytes to a
filename ref before the vision route ran, so the vision provider never saw the
image. Gate the media-pipeline vision flag on the configured vision route too.

Adds a wiremock regression: a non-vision default provider plus a configured
vision route must deliver the base64 bytes to the vision provider, fails on the
old gate, passes with the fix.

Closes #6841

* feat(sop): enforce step scope and mode events (#8493)

- 6ad3212 feat(sop): execute live SOP steps
- 5eb6d0e feat(sop): add step contract substrate
- 230192b docs(sop): document step contract substrate
- 0e9c5f0 feat(sop): enforce step schemas at engine boundary
- 502427b docs(sop): document step contracts
- 2d48b37 test(sop): satisfy schema enforcement lint
- ca8ebd6 feat(sop): enforce step routing
- 4aff05b fix(sop): persist routed checkpoint state
- b8ea2e2 fix(sop): preserve policy denies for mandatory tools
- 34fc751 fix(sop): avoid duplicate live executor metrics
- 05c8f6d fix(sop): count retries after initial failure
- ba1d50a feat(sop): enforce step tool scope
- d2187a6 feat(sop): emit step mode transition events

* feat(sop): complete payload safety ingress (#8502)

* fix(ci): remove stray zero-to-5 gitlink (#8494)

* fix(tools): avoid manual Windows verbatim prefix slicing (#8497)

* fix(channels): add missing ChannelMessage fields in orchestrator test (#8511)

* docs(labels): document held scoped labels (#8484)

- efb5f17 docs(labels): document held scoped labels
- 3f87fca docs(labels): clarify scoped label pairing

* feat(sop): add filesystem SOP event source (#8461)

- 3f1c2d9 docs(sop): list all eight denied broad roots in connectivity
- d98e27e fix(channel-filesystem): make content cap bite by default, bound debounce map
- f531e12 fix(channels): add new ChannelMessage fields to vision-route test
- cc00cd8 feat(channel-filesystem): carry a 64 KiB content cap, gated off by default
- 1841792 fix(channel-filesystem): extend broad-root deny list to pseudo-filesystems
- 94b4633 fix(channel-filesystem): fail closed on invalid globs and stream file hashing
- a86c233 feat(channel-filesystem): make max_content_bytes optional, default unlimited
- 31ee870 fix(channel-filesystem): reject symlink event paths by default
- 0745dec fix(sop): drive headless deterministic runs to terminal so channels refire
- d7da22b refactor(sop): make filesystem source a Channel impl
- febf82c feat(sop): add filesystem SOP event source

* ci(workflows): add cargo-audit step to PR gate, sync advisory ignores (#8129)

- baca7f9 ci: add cargo-audit to PR gate, refactor ci.yml with YAML anchors
- 9373b87 fix(ci): remove invalid workflow anchors

* test(plugins): cover WasmTool accessors and default schema (#8273)

- be7c73c test(plugins): cover WasmTool accessors and default schema
- be24e61 Merge branch 'master' into alix/test-plugins-wasm-tool

* feat(nix): repair zeroclaw/zerocode nix builds, add nix hash updates to release process (#8336)

- c0f0409 fix(flake): repair zeroclaw, zerocode nix builds
- 34b06ba Merge branch 'master' into flake-updater
- 6127054 Extract CI hash check to script
- 4c14416 Address review comments

* fix(channels): replace Regex::new().unwrap() with expect() in orchestrator and consolidation (#8361)

- orchestrator/mod.rs: TOOL_RESULT_RE regex unwrap -> expect
- consolidation.rs: media-tag regex unwrap -> expect

* fix(zerocode): clear transcript highlight on input click (#8472)

* fix(providers/openai-codex): gate has_tools on non-empty list (#8476)

- 1f4e47b fix(providers/openai-codex): gate has_tools on non-empty list (fixes …
- 8618a67 test(providers/openai-codex): drive regression through the production…

* fix(deps): bump strum versions to deduplicate lockfile (#8225)

- 6a5d82d fix(deps): bump lru and strum versions to deduplicate lockfile
- d557b1f fix(deps): bump strum 0.27→0.28 to deduplicate lockfile
- 72b5c3a fix(deps): drop lru bump

* test(config): cover postgres backend field-visibility exclusions (#8450)

* test(config): cover ThinkingLevel parsing, as_str roundtrip, and budget token thresholds (#8454)

* perf(channels): bound orchestrator notify channel + cap path/url body (#8460)

The `ChannelNotifyObserver` had two latent OOM vectors, both flagged
by the audit at audit-zeroclaw-2026-06-28.md:

1. **Unbounded mpsc** (`crates/zeroclaw-channels/src/orchestrator/mod.rs:4570`)
   used `tokio::sync::mpsc::unbounded_channel::<String>()` for tool-event
   notifications. A slow downstream channel (e.g. a stalled Discord /
   Slack API call at the consumer task at lines 4585-4597) could buffer
   strings indefinitely. A 1000-tool-call turn against a slow channel
   blows up RSS. Switched to `channel::<String>(128)` (matches the
   mid-range of the file's existing production mpsc capacities) and
   the producer now `try_send`s and drops on `Full` / `Disconnected`.

2. **Unbounded per-message body** — the `path` and `url` branches
   in the observer's `record_event` impl (`orchestrator/mod.rs:171-174`)
   used `format!(": {p}")` / `format!(": {u}")` with no cap, while
   the surrounding branches (command / query / generic) already routed
   through `truncate_with_ellipsis`. A user-controlled 10 MB path or
   URL would pass through verbatim. Both branches now route through
   `truncate_with_ellipsis(p, NOTIFY_DETAIL_MAX_CHARS)` with a
   4096-char cap (4 KiB — large enough to cover any realistic absolute
   workspace-prefixed path, small enough to bound the worst case).

`NOTIFY_DETAIL_MAX_CHARS` is a private const at the top of the impl
block so the cap is visible at the call site and easy to bump.

`truncate_with_ellipsis` and `floor_char_boundary` are reused
unchanged from `crates/zeroclaw-channels/src/util.rs:2,13` — no new
helpers, no new deps.

Durability-of-UX tradeoff (documented in the comment on the producer
push): live-typing notifications are best-effort UX. A dropped message
degrades the indicator briefly but does not lose any real state. We
do not log on drop because the observer is in a hot path and a slow
channel could generate thousands of drops per second; the existing
observer-hook call sites in the file already follow the no-log-on-drop
convention.

Two new regression tests pin the contracts:

- `channel_notify_observer_caps_long_path_argument` — feeds a 64 KiB
  `path` argument (16x the cap) and asserts the emitted string is
  bounded by `NOTIFY_DETAIL_MAX_CHARS + prefix + ellipsis`.

- `channel_notify_observer_drops_on_full_channel` — pushes two events
  into a capacity-1 channel without draining; asserts exactly one
  arrives and `tools_used` reflects both events were observed on the
  observer side (the drop is on the notify side, not the observer
  side).

The existing `channel_notify_observer_truncates_utf8_arguments_safely`
test was updated to construct a bounded channel to match the new
field type; no test-logic change.

Public API impact: none. `ChannelNotifyObserver` is module-private
(no `pub`, no re-export); only its internal field type changed.
`zeroclaw-channels` is `Experimental` per AGENTS.md so there is no
stability concern regardless.

Validation:
  cargo fmt --all -- --check                                                    clean
  cargo clippy --locked -p zeroclaw-channels --all-targets -- -D warnings       0
  cargo test --locked -p zeroclaw-channels --lib -- orchestrator::               344 passed (2 new)
  cargo build --locked                                                          clean

* ci(npm): add daily audit sweep (#8480)

* ci(workflows): add SLSA provenance attestation to release pipeline (#8277)

- 5f24e4d ci(workflows): add SLSA provenance attestation to release pipeline
- 219cdbb ci(release): add SLSA provenance attestation
- 4cf978c fix(ci): harden offline attestations

* feat(config): configurable native runtime shell (ref: #5246) (#8311)

- 9138377 feat(config): configurable native runtime shell (#5246)
- 144efc9 fix(config): validate native runtime.shell and prove execution under it
- 4a0ce2a fix(config): reject relative-path runtime.shell and document the rules
- 34be98b test(config): platform-gate native_with_shell_passes_c_flag
- 4bdc9eb Merge remote-tracking branch 'origin/master' into configurable-shell
- ab64ae3 test(config): use target_os cfg form for shell boundary test
- 57dd2fa Merge branch 'master' into configurable-shell

* fix(fill-translations): drop stale translations key in leak repair (#8312) (#8322)

The trailing-\n pre-pass at main.rs:393 seeds translations[msgstr_line]
with the entry's current msgstr text when msgid ends with \n. The
subsequent leak-repair pass rewrites only lines[entry.msgstr_line]
(Recovered translation or blanked Unrecoverable) but never updates
or removes the matching translations entry. write_po prefers
translations over lines, so the pre-seeded leaked text wins and
re-ships the leak to disk even after the lines-side repair cleared it.

For a Recovered leak this is permanent (the entry is never
re-translated); for an Unrecoverable leak whose later re-translation
fails, the stale value also wins.

Drop translations[msgstr_line] in both Recovered and Unrecoverable
arms of the leak-repair loop so write_po falls through to the
correctly-repaired lines value. The pre-pass and write_po are
unchanged.

Orthogonal to #8039 / #8118 (orphaned continuation lines) — that fix
targets lines-side re-parse contamination; this fix targets the
translations-map stale-key path. Both modify the same loop site but
address independent failures.

Regression test repair_leaks_drops_stale_translations_key drives
write_po end-to-end with the trailing-\n pre-seeded key in place
and asserts the recovered translation wins. Mutation-checked:
removing the translations.remove(...) line makes the test fail.

Closes #8312.

# Conflicts:
#	tools/fill-translations/src/main.rs

* refactor(ci): consolidate prebuilt Docker image variants (#8485)

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>

* fix(ci): reconcile cargo-audit ignores with deny.toml to unblock master (#8520)

The `cargo audit` CI step reads the entire Cargo.lock and was failing
master on 22 RustSec advisories absent from `.cargo/audit.toml`, while
`cargo deny check` (graph-aware, reads deny.toml) tolerated most of them.
The two advisory tools had drifted out of sync.

Add all 22 to both files with reasons referencing tracking issue #8519
(milestone v0.8.4):

- 3 wasmtime-wasi CVEs (RUSTSEC-2026-0149/-0182/-0188) pulled via
  zeroclaw-plugins. The real fix is a wasmtime 43 to 45.x stack bump,
  deferred to #8519; ignored here only to unblock the gate. Dependabot
  PR #8490 (bump to 44.0.2) is insufficient: -0182 needs >= 44.0.3 and
  -0188 needs >= 45.0.3.
- 19 unmaintained-crate advisories with no available fix: GTK3 stack via
  zeroclaw-desktop, unic-* unicode tables, proc-macro-error/2, derivative,
  and ttf-parser (via pdf-extract to lopdf), all transitive.

Validated: `cargo audit` exit 0, `cargo deny check` exit 0.

Related #8519

* fix(gateway): propagate pairing DB errors instead of panic (#8466)

- 240fc6d fix(gateway): make pairing atomic across DB errors
- 3b806ac Merge branch 'master' into fix/api-pairing-propagate-db-errors

* test(runtime): cover TrustTracker score updates, decay, regression, and autonomy reduction (#8457)

- cfdc589 test(runtime): cover TrustTracker score updates, decay, regression, a…
- b421e94 test(runtime): make regression preconditions unconditional assertions
- 0e6c7fb Merge branch 'master' into alix/test-trust-types

* test(eval): cover EchoTool metadata, schema, execute output, and default tool registry (#8459)

- d593b6e test(eval): cover EchoTool metadata, schema, execute output, and defa…
- 7fb08c6 Merge branch 'master' into alix/test-eval-tools

* fix(channels): enable model commands on WhatsApp Web (#8414)

- b1d2564 fix(channels): enable model commands on WhatsApp Web
- 3faa907 Merge branch 'master' into codex/fix-whatsapp-runtime-model-command

* test(eval): cover RecordingObserver tool recording and token accumulation (#8458)

- 24e03a7 test(eval): cover RecordingObserver tool recording and token accumula…
- c07b450 Merge branch 'master' into alix/test-eval-observer
- 101dac7 fix(eval): update observer test event shape

* fix(anthropic): propagate serialization error in streaming request builder (#8148)

- fc681f2 fix(anthropic): propagate serialization error in streaming request builder
- c45d387 fix(anthropic): add missing anyhow::Context import and fix StreamError type
- c2c909d fix(providers): apply cargo fmt for CI format gate
- 38cc88f fix(channels): add missing Default::default() to ChannelMessage test initializer
- 4b0e9d1 chore: cargo fmt
- 6e6acd7 Merge branch 'master' into fix/anthropic-streaming-request-expect

* fix(runtime): fire session_end hook on session termination (fixes #7889) (#8003)

- 16a34ac fix(runtime): fire session_end hook on session termination (fixes #7889)
- f064da8 fix(runtime): register configured hook handlers in RPC path, gate session_end on existence
- 26243d6 fix(runtime): extract shared HookRunner::from_config, collapse clippy, add tests
- 0cb0385 fix(runtime): correct command-logger handler name assertion in test
- 1aa5a35 fix(runtime): rebase session_end hook onto latest master, add missing hooks field
- a37207e fix(runtime): apply cargo fmt for CI
- a5f3996 fix(runtime): run cargo fmt --all for CI format gate
- 859d733 test(dispatch): prove missing-session close/delete does not fire session_end
- decb565 test(dispatch): add positive regression for session_end hook on real session close
- 4ba12cb style: cargo fmt
- 663d523 fix(dispatch): add missing hooks field to minimal_with_cost_tracker
- 010eec1 fix(channels): add missing Default::default() to ChannelMessage test initializer
- 143136d Merge branch 'master' into fix/session-end-hook-fire

* feat(plugins): per-call execution limits and FND-001 backend taxonomy (#8491)

- d4a4165 feat(plugins): per-call execution limits and FND-001 backend taxonomy
- 45d8bd3 fix(plugins): validate all four limit fields and refresh plugin docs
- 57f99dc fix(plugins): pass PluginLimits in WasmTool::new unit test

* chore(desktop): remove the zeroclaw-desktop Tauri app and all wiring (#8544)

- 2417999 chore(desktop): remove the zeroclaw-desktop Tauri app and all wiring
- 884e3d8 chore(desktop): clear leftover desktop references from retirement
- 0b1642c chore(desktop): strip remaining desktop release references

* fix(config): warn when sqlite memory requests vector search without an embedder (#8501)

memory.backend defaults to sqlite and memory.search_mode defaults to
Hybrid, but embedding_provider defaults to none. The runtime resolves
none to NoopEmbedding (dimensions 0), so the vector path is silently
skipped and recall collapses to keyword/LIKE-fallback with no warning,
log, or doctor signal.

Add a non-fatal warning in Config::collect_warnings() that fires when:
- the memory backend family is sqlite (derived from memory.backend, not
  resolve_active_storage(), so a bare backend = "sqlite" is covered),
- search_mode is hybrid or embedding, and
- no effective embedder is configured.

A valid hint: embedding route counts as an effective embedder even when
embedding_provider stays none. The check derives entirely from existing
[memory] and [[embedding_routes]] state; nothing new is persisted. The
warning surfaces on stderr via validate(), through the gateway
PropResponse/PatchResponse, and at zeroclaw doctor.

Warning-only, no behavior change. The runtime dimensions() == 0 follow-up
is deferred per maintainer guidance while #8382 reworks zeroclaw-memory.

* feat(web): add select all/deselect all toggle to tool picker groups (#8464)

Add per-group "Select all" / "Deselect all" controls to the dashboard
ToolPicker. Each control acts on its group's currently-displayed
(filtered) entries so it honors an active search and matches the count
shown in the group header, and its label flips between "Select all" and
"Deselect all" based on whether every displayed entry is already
selected. Selection stays order-preserving and de-duplicated.

The controls render in a toolbar above the list, outside the
role="listbox", so they are valid ARIA (a listbox may only contain
option/group descendants) and reachable in the natural Tab order with
native button activation. New en i18n strings (select_all, deselect_all,
and their aria-label prefixes) drive the labels.

* feat(amqp): SOP fan-in dispatch path, fan-in usage docs, and AMQP credential secret fix (#8521)

- bb0286b feat(amqp): add SOP fan-in dispatch path
- 011f4cf docs(sop): schema-driven SOP fan-in pages and AMQP channel docs
- e6ce719 fix(config): mark AMQP credential fields secret
- 88d218f docs(sop): make fan-in pages answer how to actually use each source
- fe9dc8a fix(docs): repoint sop-unwired snippet links and exclude generated re…
- 17f2789 fix(amqp): fail closed on SOP dispatch routing

* docs(labels): document line and memory backend labels (#8523)

* feat(config): add local_small runtime preset (#8531)

- 9dabcab feat(config): add local_small runtime preset
- ad6831d fix(config): enable local_small tool loop
- af268e5 Merge branch 'master' into codex/issue-5287-local-small-preset

* test(tools): cover weather HTTP skill shape (#8433)

- 3726f86 test(tools): cover weather HTTP skill shape
- 9fb0d2a Merge branch 'master' into codex/issue-6165-weather-skill-proof
- 0678bd7 Merge branch 'master' into codex/issue-6165-weather-skill-proof

* fix(ci): allow generated docs reference links (#8533)

- bec9dca fix(ci): allow generated docs reference links
- ed9b0a8 Merge branch 'master' into codex/fix-docs-link-gate-generated-refs

* ci(workflows): gate Windows Clippy for tools changes (#8517)

- a6fc04f ci(workflows): gate Windows Clippy for tools changes
- e83dcb3 ci(workflows): scope targeted Windows Clippy to tools
- f88d922 Merge remote-tracking branch 'origin/master' into codex/ci-targeted-w…
- 2b4c7c2 ci(workflows): keep targeted Windows Clippy dependency-local
- 98f8145 Merge branch 'master' into codex/ci-targeted-windows-clippy

* fix(docs): autolink ACP elicitation RFD (#8498)

- 089995f fix(docs): autolink ACP elicitation RFD
- 61b270b Merge branch 'master' into codex/issue-7269-rustdoc-bare-urls
- 870f447 Merge branch 'master' into codex/issue-7269-rustdoc-bare-urls

* docs(security): clarify tool receipt guarantees (#8407)

- 3f94f54 docs(security): clarify tool receipt guarantees
- 0397b0b Merge branch 'master' into codex/security-receipts-doc-contract

* fix(i18n): reject local path leaks in translations (#8365)

- 8b25b21 fix(i18n): reject local path leaks in translations
- 47ecfa6 fix(i18n): satisfy mdbook path leak lint
- 8f2fb23 Merge remote-tracking branch 'origin/master' into codex/issue-6407-ab…
- 21c3662 Merge branch 'master' into codex/issue-6407-absolute-path-guard-r2

* feat(skills): suggest missing plugins from cached registry (#8428)

- feat(skills): suggest missing plugins from cached registry
- fix(skills): satisfy ci lint for plugin suggestions

* fix(channels): fire message_sent hooks after delivery (#8355)

- fix(channels): fire message_sent hooks after delivery
- fix(channels): refresh message_sent hook test fixtures

* ci(workflows): guard declared repository submodules (#8516)

- ci(workflows): guard declared repository submodules

* fix(agent): refresh system prompt TASK_FRAMING anchor per-turn for vision-routed providers (#8054 Surface 3) (#8503)

- fix(agent): refresh system prompt TASK_FRAMING anchor per-turn for vision-routed providers (#8054 Surface 3)

* fix(gateway): advertise A2A cards on runtime port (#8538)

- fix(gateway): advertise A2A cards on runtime port

* fix(runtime): gate Unix-only shell test helper behind #[cfg(unix)] (#8535)

- fix(runtime): gate Unix-only shell test helper behind #…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working gateway Auto scope: src/gateway/** changed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: agent delete can purge owned state before config persistence (mirror of #7907)

3 participants