feat: Runtime API memory endpoints — bounded inspection and lifecycle controls#5131
Conversation
… rehydration Three coordinated session-layer fixes: - Persisted session records now carry a creation-time instance marker (mirroring the #405 subagent session_boot_id pattern); the work surface excludes restored prior-instance rows in terminal failed/stale states from the default listing, keeping them reachable in the inspector as 'prior session' (#4416). - /cost decomposes the headline into parent-turn, sub-agent, and monotonic display-floor components that provably sum to the headline, itemizes routes from TurnCostAudit with honest ring coverage, and currency totals are projections of one accumulator with an invariant test (#4939). - Legacy pre-v0.8.54 sessions persisted the turn_meta envelope as the leading block; the rehydration filter now hides a complete machine envelope at any index while keeping user-authored literals visible (#4681). Closes #4416. Closes #4939. Closes #4681. Agent-assisted: implemented and tested with Claude Code. https://claude.ai/code/session_01AAEuFJrqcMqF1oztnjZeMy
Windows delivers AltGr as Ctrl+Alt, so AltGr+Q on ABNT2 (which types '/') satisfied the bare CONTROL check in is_help_shortcut and opened help instead of inserting the character. Ctrl-based help matches now reject AltGr chords via the existing key_hint::is_altgr primitive; plain Ctrl+/ and the legacy Ctrl+7 / Ctrl+_ decodings keep working, as does deliberate Ctrl+Alt on non-Windows platforms. Thanks @nicolassmotta for the report and @yyyCode for the earlier PR exploring this (PR #4977). Closes #4723. Agent-assisted: implemented and tested with Claude Code. https://claude.ai/code/session_01AAEuFJrqcMqF1oztnjZeMy
The collapsed workflow panel no longer pins a row above the composer; its lifecycle/progress summary renders as a compact chip in the top status bar after the agents chip. The expanded interactive panel (drill-in, phase rows) keeps its place above the composer while running. Closes #5040. Agent-assisted: implemented and tested with Claude Code. https://claude.ai/code/session_01AAEuFJrqcMqF1oztnjZeMy
…batch Aggregate owned Rust rises 644756 -> 645924 (+1168 net lines: seven fixes with regression tests across client/anthropic, sandbox/seatbelt, tools/workflow, config scoping, session layer, key routing, and the header workflow chip). Largest-module ratchet tightens 19017 -> 18966. No-Issue: CI budget bookkeeping for #5063. Agent-assisted: Claude Code. https://claude.ai/code/session_01AAEuFJrqcMqF1oztnjZeMy
Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
…-train-20260802 Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> # Conflicts: # crates/config/src/lib.rs # crates/tui/src/config.rs
Removes the retired v0868-stopship fleet roster, the v0867 setup QA script, and the three v0867 constitution evidence docs, none of which anything on the train references anymore (rg-verified; the only inbound link was docs/CONFIGURATION.md, updated here). Trims the dead v0868-stopship alias from the stopship role-validation guard in crates/cli/src/lib.rs, drops the retired /slop and /canzha aliases from the command-dispatch architecture doc, and removes the deleted crates/tui/src/slop_ledger.rs path from source-structure-budget.json. The v0868_ name-guard regression test stays by design. Verified: cargo fmt clean, cargo test -p codewhale-cli --lib (204 passed), source-structure-budget.json still valid JSON. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… agent-tool schema A run whose parallel() slots all failed VM-level validation reported 'status: Completed' with a [null, ...] result, because task() options that fail parsing throw before the driver ever sees a dispatch — the #5035 guard only counted driver-side rejections, and its ledger stayed empty. The VM now emits ProgressEvent::TaskRejected for every pre-spawn refusal (malformed options, bad responseSchema, lifetime cap, exhausted budget) with a best-effort label/phase from the raw options; the TUI driver folds those into the same dispatch-failure ledger as spawn rejections. Run completion classifies against that ledger plus per-task terminal records: all slots dropped with no child is Failed, and a run that returned output while any slot failed or was rejected is the new Degraded status — output preserved, error naming the dropped slots, never a plain Completed. The declarative partial-failure test that asserted 'completed' now asserts the honest contract. task() also stops rejecting schemas that the sibling agent tool accepts: workspace_policy ('shared' | 'worktree') folds into worktree isolation, and every multi-word option takes its snake_case spelling as an alias. Unknown fields still fail loudly. Verified: cargo fmt clean; codewhale-workflow-js vm_tests 51 passed; codewhale-tui tools::workflow 95 passed (incl. new vm-rejection and degraded regressions); full tui bin suite 9567 passed with only the two pre-existing credential_scope environment failures, reproduced unchanged on the clean base commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prose observation records any token that looks like a path, so the per-turn <turn_meta> Active-paths list could assert entries like 'Hmbown/CodeWhale (file)' or '120x40 (file)' that never existed on disk — teaching the model workspace facts it then spends paid turns disproving (morning-report issue #5, captains-log turn_meta fabrication). summary_block now re-stats each candidate against the workspace at render time: entries that fail the stat never render, files deleted mid-session fall out on the next turn, and the (file|dir) kind comes from the fresh metadata instead of the sticky observation-time flag. Bytes only change when the filesystem genuinely changed, which is the same exception the #280 byte-stability contract already makes for newly observed paths; the /preview-request parity path shares this code. The turn-meta-diet worktree needs no reconciliation — its content already landed on the train as bddceb0. Verified: cargo fmt clean; working_set suite 46 passed (incl. the new stat-verify regression), turn_metadata 22 passed, preview parity 165 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two coupled ambient-motion defects (captains-log #16, owner report): Idle never settled — the empty-water home screen qualified for ambient animation frames unconditionally, so a fully idle session repainted the ocean forever (33 frame diffs in 14s at the home screen). The animation wake block now tracks an idle anchor: when nothing is live (no turn, no running sub-agents, no queued/running/waiting durable tasks, completion exhale finished, not loading, not browsing history) the aquarium gets six seconds of gentle motion and then stops requesting frames entirely — a genuinely still scene until real activity clears the anchor. Creatures teleported at high token throughput — positions are pure functions of an elapsed-ms clock, but that clock was raw wall time sampled whenever a draw happened to land. Fast streams produce sparse, irregular draws, so consecutive frames sampled the scene seconds apart and fish jumped across the gap. Draws now advance a dedicated ambient clock by real elapsed time clamped to 160ms per frame: bursty schedules slow the scene down smoothly instead of teleporting it, and waking from the settled state resumes from the exact pose it stilled in. The reduced-motion freeze test drives the sampled clock directly so it still proves a 9-second clock jump renders identical frames under low_motion. Verified: cargo fmt clean; new clamp + idle-settle unit tests pass; tui::widgets 284, tui::ui 690, underwater 34, ocean 26 — all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… goal visible Two operate-loop truths from the morning report and captains-log #12: Continuation after internal events — sub-agent completions already wake the idle engine, but background shell completion is pull-only: nothing re-entered the turn loop when a job finished, so an active goal waiting on background work sat inert until the user re-prompted. The idle receive now arms a coarse 750ms poll only while a goal is active and an unclaimed background completion may exist (reusing the manager's read-only pending signal), and on readiness queues a normal goal continuation. The continuation dispatch path already re-reads live goal state — pause/budget/clear all still win — and handle_send_message's boundary drain claims the evidence exactly as a user turn would. Without an active goal nothing changes: completions keep waiting for the next user-initiated turn, proven by the disarmed-timeout regression. Goal visibility — the sidebar goal banner dies with a hidden sidebar, so a set goal was invisible chrome-wide. The footer now carries an unconditional goal chip beside the shell chip: truncated objective plus the continuation pass while hunting, and an explicit 'goal paused' state so a stalled loop is never silent. Terminal verdicts clear the chip. Verified: cargo fmt clean; new wake regression passes (including the no-goal disarmed case); goal 89, footer 105, shell 270 suites green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…routes
Subscription routes (Kimi for Coding, Codex OAuth, Z.ai plans) have no
dollar truth: the metering audit correctly refuses to price them, but the
footer then dropped the Allowance chip entirely, so plan users saw either
nothing or — with stale unaudited session estimates — misleading dollars
elsewhere in the session. The cost slot now renders the existing
plan-aware usage line ('usage: Kimi Code quota · 12%') for Allowance
chips; metered routes keep their audited dollars and the cache-savings
suffix. The stale-dollar guard test now asserts the honest contract:
no '$' on a plan route, plan label visible.
Verified: footer 105 and route_billing 48 suites green; cargo fmt clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
config.toml carried a plaintext OAuth-shaped token while a sibling entry was properly [redacted] — mixed hygiene the doctor never mentioned. The configuration section now scans the raw file for bearer-shaped values (known credential prefixes, or long random strings under token/secret/ key-named entries) and warns with the key names only; values are never echoed. Models, URLs, hex ids, and redacted entries stay quiet, proven by unit tests. Verified: doctor suite green; cargo fmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pping, honest model width, goal-first Operate placeholder Four first-run papercuts from the morning report: Trust dialog keys read 1/3/2. The number keys now mirror the footer's reading order — 1 trusts, 2 continues without trusting, 3 quits (Y/U/N/ Esc unchanged) — with the footer, the Enter hint, and all fourteen translated locales updated together so copy and handling cannot drift. Deep workspace paths hard-split mid-component because ratatui's Wrap breaks only at whitespace. The trust prompt now wraps its location line at '/' boundaries with indented continuations, using the panel's real inner width. The header's proportional width split let a long workspace squeeze the model identity to a meaningless 'deep…' at 120 cols. The model — a trust fact — now keeps up to 14 columns before the workspace takes the rest. The Operate composer greeted first-run users with 'Coordinate parallel tasks...'; it now says what to type: 'Describe the goal — Codewhale keeps working until it's done'. Verified: onboarding 52, trust 69, localization 31, widgets 284, header 127 suites green; cargo fmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…quests A scout died at 141 seconds because a DeepSeek response stream failed to decode — the transient classifier's needle list covered headers, timeouts, resets, and 5xx/429 wording but not body/decode failures, so the child was classified Fatal on attempt one with zero retries and the parent had to re-plan from a runtime event (morning-report issue #7). Body-decode and truncated-response wording now classifies as transient: 'error decoding response body', the Chat API read/parse contexts, and unexpected-EOF/incomplete-message phrasing. The existing bounded policy applies unchanged — at most two same-prompt retries with 250ms backoff — and auth-class failures stay fatal, pinned by the new classifier test. Verified: subagent suite green; cargo fmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One unscoped 'agent status' poll returned 203KB because every agent — including live ones being polled repeatedly — carried its full session projection: launch manifest with the complete child prompt, the worker event ring, and any checkpointed message history (morning-report issue #4). Supervision needs the top-level facts, not the archive. Running children in the unscoped list now drop the snapshot, worker_record, and checkpoint fields and carry compact: true plus a note naming the two full-detail paths; identity, status, usage, follow-up, takeover, artifacts, and verification all stay. Terminal agents keep the full projection — fetching results is the point of a terminal row — and verbose: true restores the old shape everywhere. Single-agent status is unchanged, including its running-unchanged throttle. Verified: subagent suite 516 green including the new compaction regression (compact running row, full terminal row, verbose restore); cargo fmt clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DGF-01 (dogfood 2026-08-02): doctor's API Connectivity section and `codewhale config list` presented the configured route as if it were what a live session serves. A running session keeps the route it resolved at launch, so after a config edit (or an override at launch) the two can legitimately disagree — and the surfaces gave the reader no way to know which truth they were looking at. The dogfood session burned real time believing doctor's moonshot/k3 answer described a session that was serving deepseek-v4-flash. - doctor_api_target now resolves the model through the same resolver the live client uses at session launch (route_runtime:: resolve_runtime_route), so saved provider models, alias normalization, and Codex roster preference are reflected instead of a re-derived config default; on resolution failure it falls back to the raw configured model and says so (no error details printed — same redaction stance as the JSON route report). - human output gains an explicit scope line (configured route, applies at launch; a running session keeps its launch-time route) and the model line is suffixed "(resolved)" or "(configured; route resolution unavailable)". - doctor JSON gains route_scope=configured_at_launch and model_resolution=resolved|configured_unresolved next to the existing default_text_model field (old keys untouched). - `config list` prints two #-comment header lines naming the file and the configured-vs-live distinction; # keeps 'key = value' parsers safe. Verification: - cargo fmt --check -p codewhale-tui -p codewhale-cli: clean - cargo test -p codewhale-tui --bin codewhale-tui doctor: 99 passed; 0 failed (includes new doctor_api_target_falls_back_to_configured_model_when_resolution_fails and resolution asserts on the two endpoint tests) - cargo test -p codewhale-cli config: 3 passed; 0 failed
DGF-02 (dogfood 2026-08-02): a user-approved write was blocked by the read-only execution sandbox, and the model — never told its own posture — burned turns debugging the failure blind. Approval and sandbox are separate authorities, and no surface said so. Three surfaces, one resolver (core::authority::sandbox_policy_for_turn, the same one tool execution uses, so the words and the enforcement cannot disagree): - <turn_meta> gains 'Current sandbox posture: <label>' next to the permission posture line. Stable per session, so ordinary turns stay byte-identical (preview parity covered by the existing snapshot tests). SandboxPolicy::posture_label renders the deterministic label; the read-only label states outright that tool approval cannot lift the sandbox. - shell results: when the sandbox denies a command (and the Plan-mode network hint didn't already explain it), the result opens with a hint naming the effective posture and saying approval does not lift it — sandbox_denied_hint also lands in metadata. Both the foreground and the delta/wait paths carry it. - the approval gate: prompting for a shell-family tool while the batch posture is read-only appends '…approving runs the command without write access (approval cannot lift the sandbox)' to the approval description, so the human sees the ceiling before granting. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- sandbox_denied_hint posture_label turn_metadata_names sandbox: covered by the combined run below (6 new tests all green) - cargo test -p codewhale-tui --bin codewhale-tui -- turn_meta preview: 197 passed; 0 failed (byte-stability + preview parity intact)
DGF-03 (dogfood 2026-08-02): Esc reports 'Turn interrupted' while detached background shells keep running — files land on disk after the UI said stopped, which read as a lie. Killing those jobs would be wrong: background continuation is deliberate (614ef72 wakes an idle goal exactly when such a job finishes). The fix is honesty, not slaughter. The two paths that can classify a turn Interrupted (the model turn loop's completion and the composer-bang shell turn) now emit, right before TurnComplete, a status naming each still-running background job — id plus a bounded command preview — and stating the jobs continue and may still write files, with /jobs as the follow-up. Silent when nothing survives, so ordinary interrupts are unchanged. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- interrupted_turn_names turn_metadata: 24 passed; 0 failed — includes the new interrupted_turn_names_surviving_background_shell_jobs, which starts a real background sleep-then-write job, fires the interrupt path, and asserts the status names the surviving task id while the marker file has not yet been written.
W6 rest (morning-report issue #4): every agent start returned ~12KB — the full child prompt via launch_manifest inside worker_record, a duplicated snapshot, plus takeover/artifacts/verification structs that are meaningless seconds after spawn. A spawn receipt's one job is 'child launched, here is how to check on it'. action=start results now flow through compact_spawn_receipt: the archive keys (snapshot, worker_record, checkpoint) and the status-retrievable supervision detail (artifacts, takeover, transcript_handle, verification) drop; identity, status, context_mode, prefix_cache, follow_up, and the honest usage-at-spawn note stay, with compact: true and a note naming both full-detail paths. verbose: true on start restores the old shape byte-for-byte. Unscoped status compaction (9fa5e04) is intentionally less aggressive — its rows describe children with history. Measured in the regression fixture: compact receipt 783B (<1KB target met) vs 2448B for the same projection uncompacted — and the fixture carries no launch manifest; with one, the delta is the ~12KB observed in dogfood. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- subagent::tests: 362 passed; 0 failed (includes spawn_receipt_compacts_and_verbose_restores_the_archive)
R4 (finish-operator 2026-08-02): the Fatal arm of the child request path returned bare Err — no checkpoint, no transcript handle — so a fatal provider error (auth revoked, invalid request) after N completed steps stranded all of them. Dogfood lost a 141s scout this way. The Interrupted arm one line below already knew how to preserve work; only the classification differed. Fatal failures now flow through the same checkpoint-and-park path as transient exhaustion: continuable checkpoint with the full child conversation, transcript handle, waiting_for_user projection, and a reason prefixed 'fatal provider error:'. The one exception is a fatal on the very first request (steps <= 1 — the counter increments before the attempt): zero completed work still fails plainly, preserving the existing terminal-sink fan-in contract. No automatic retry is added — fatal stays fatal (3e1eb52 kept auth non-retryable); the change is that the work the error interrupts survives for re-dispatch via the continuation handle. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- subagent::tests: 363 passed; 0 failed — includes the new fatal_provider_failure_mid_run_parks_a_continuable_checkpoint (banks a real tool-call step against a mock provider, hits a 400 on the next request, asserts the park + checkpoint, then re-dispatches from the checkpoint against a healthy route and asserts completion) and the unchanged non_retryable_provider_failure_fans_in_to_every_terminal_sink
…_meta R6 (finish-operator 2026-08-02): the only envelope number anyone quoted was a stale fixture comment (workflows/stopship.workflow.js:1-5, 17,457–17,550 tokens from an old live run). Nothing failed when the assembled payloads grew. Three regressions now build — never send — the real payloads and pin them at measured-current +10%: - read_only_child_envelope_stays_within_measured_ceiling: scout system prompt + seed messages + child tool catalog = 80,856 bytes measured (~20k tokens — the old 17.5k figure was already low); ceiling 89,000. - parent_agent_surface_stays_within_measured_ceiling: BASE_PROMPT + full parent tool catalog = 72,679 bytes measured; ceiling 80,000. - turn_meta_block_stays_within_measured_ceiling: per-turn <turn_meta> block via the production snapshot path = 254 bytes measured (includes the new sandbox-posture line); ceiling 280. Each failure message says how to raise the ceiling honestly (re-measure in the same commit). The stopship fixture comment now points at the pinned test instead of the stale token figure. Proof the ceilings bite: a scratch diff padding BASE_PROMPT +15,000B, the child system prompt +17,000B, and turn_meta +1 line (~+20% each) failed all three tests; reverted, all green. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- turn_meta_block_stays read_only_child_envelope parent_agent_surface: 4 passed; 0 failed
R7 (finish-operator 2026-08-02): the morning report caught — by hand — a child claiming file edits that git status had never seen. The verification summary riding the worker record said only self_report_only; nothing machine-checked the claim. At terminal delivery of a Completed child, claimed_diff_taint now compares the result summary's claimed changed-files against the child workspace's live git state: the dirty set from git status --porcelain (rename-aware) plus files changed by commits made after the worker started (created_at_ms via git log --since — a child that commits its work is honest, not invisible). Extraction is deliberately conservative for high-signal taint: only path-shaped tokens on a line that also carries a change verb count as claims. Any claim git cannot see flips the record's verification to claim_mismatch with the offending paths named; the parent keeps the result — labeled, not trusted. No git, no claims, or all-visible claims leave the summary untouched. Known blur, chosen deliberately: git --since has one-second granularity, so a claim delivered within a second of a baseline commit can escape taint — the check errs toward not-tainting rather than accusing honest children. Verification: - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- subagent::tests: 367 passed; 0 failed — includes the new completed_claim_of_untouched_file_taints_verification (claim without a touch → claim_mismatch naming src/lib.rs) and completed_claim_matching_workspace_state_stays_untainted (dirty claim and committed claim both stay self_report_only)
RELEASE_CHECKLIST.md §3 requires `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`. It had never been run this session and was failing with four errors — three pre-existing, one mine. - crates/tui/src/tui/widgets/mod.rs: drop the unused `Duration` import. - crates/tui/src/tui/widgets/header.rs: `model_width.min(14).max(4)` → `clamp(4, 14)` (manual_clamp). Same 4..=14 bound the honest-model-width work in bf04783 intended; no behavior change. - crates/tui/src/config/tests.rs: build ProvidersConfig in one initializer instead of default-then-reassign (field_reassign_with_default). - crates/tui/src/tools/subagent/tests.rs: `.map(|m| text_of(m))` → `.map(&text_of)` (redundant_closure), and drop the three `eprintln!` measurement prints I added with the R6 size tests (print_stderr). The measured values live in the ceiling comments and commit bodies; the assertion messages already report them on failure. Verification: - cargo clippy --workspace --all-targets --all-features --locked -- -D warnings: exit 0, zero errors (was 4) - cargo fmt --check -p codewhale-tui: clean - cargo test -p codewhale-tui --bin codewhale-tui -- subagent::tests config::tests widgets: 1090 passed; 0 failed
…#5164) * Initial plan * WIP: harden execpolicy deny matching --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…dyEn (#5179) * Initial plan * fix: zh admin pre-fills and publishes bodyZh not bodyEn startEdit always set editBody to draft.bodyEn regardless of locale. Change to isZh ? draft.bodyZh : draft.bodyEn so the /zh admin textarea pre-fills the Chinese body. Also fix the "Posted" preview row which displayed bodyEn unconditionally — now respects isZh in the same way. Add a test asserting that POST /api/admin/post with lang=zh and no editedBody sends the zh body to GitHub, not the English one. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
… of silent no-op (#5180) * Initial plan * fix(web): digest post returns real GitHub Issue URL instead of false ok:true The admin digest "post" action was returning `{ ok: true, action: "digest-skipped" }` without posting anything or marking the draft as posted. This caused the draft to reappear in Pending forever — a false success receipt. Fix: digest drafts are now posted as new GitHub Issues (using the existing MAINTAINER_GITHUB_PAT + REST API). The response includes the real issue `number` and `url`; `draft.posted` is set to true and the draft is stored back. GitHub API failures propagate as 502 errors, never as ok:true. Tests: two new source-contract tests in public-api-security.test.ts pin both paths — happy path (real url/number returned) and error path (502, not ok:true). Closes #5178 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan * WIP: invalidate context meter after compaction * Add compaction token meter regression test --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…5206) * Initial plan * WIP: persist interrupted assistant output --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan * WIP: add mechanical stop-word turn cancellation --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
….9.4) (#5137) * Initial plan * feat(config): add multiple named operator-scoped Fleet configurations (#5039) Adds support for multiple named durable Fleet configurations in the config TOML, each scoped to an operator identity. The existing [fleet] table remains the backward-compatible default. New public types: - `NamedFleetConfigToml`: a [fleets.<name>] entry with a required `operator` field plus independent trust/role/profile/exec settings. Exposes `resolve_role()` and `as_fleet_config()` for unified usage. - `FleetResolutionError`: typed, actionable errors (UnknownFleet, UnknownOperator, AmbiguousOperator) with human-readable Display messages that list available options rather than failing silently. New methods on `ConfigToml`: - `resolve_fleet(name)`: returns the named fleet or `UnknownFleet` error with available names listed. - `resolve_fleet_for_operator(operator)`: returns the unique fleet owned by an operator; `UnknownOperator` if none matches, `AmbiguousOperator` if more than one matches (caller must name a fleet explicitly). New `fleets` field on `ConfigToml`: `BTreeMap<String, NamedFleetConfigToml>`, serialized under [fleets.*] keys. Skipped when empty so legacy configs are byte-for-byte unchanged. config.example.toml updated with full named-fleet documentation and examples, including the selection-precedence comment. 14 new tests added to crates/config/src/tests.rs covering: legacy-only, mixed (legacy + named), multiple named fleets, resolve_fleet/operator success and error paths, error message content, as_fleet_config view, and round-trip serialization. All 489 tests pass. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…ion tool (#5138) * Initial plan * feat(tools): add send_later one-shot delayed continuation tool Implements the model-callable `send_later` tool requested in issue #4190. - `DelayedTriggerRecord` / `DelayedTriggerStatus` / `CreateDelayedTriggerRequest` added to `automation_manager.rs`; triggers are stored under `~/.codewhale/automations/triggers/` and survive process restart. - `AutomationManager` gains `create_trigger`, `get_trigger`, `save_trigger`, `list_triggers`, `cancel_trigger`, and `collect_due_triggers` methods. - `fire_due_triggers_shared` function added; called on every scheduler tick so pending triggers are fired without an extra background thread. - New `crates/tui/src/tools/send_later.rs` implements `ToolSpec` for `send_later` with actions: schedule, list, read, cancel. - `schedule` accepts mutually-exclusive `delay_minutes` or `fire_at` (ISO 8601 UTC) plus a `message`; returns `trigger_id` + resolved `fire_at`. - Re-arm lineage tracked via optional `parent_trigger_id`. - Workspace defaults to the active context workspace. - Registered in `ToolRegistryBuilder::with_runtime_task_tools()` (full surface) and `with_runtime_read_only_task_tools()` (plan-mode, read-only variant). - 12 unit tests cover scheduling, persistence, cancellation, malformed times, status filtering, lineage, restart recovery, and due-trigger collection. - Updated `read_only_task_surface_contains_no_per_action_aliases` registry test to expect 4 canonical tools instead of 3. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…3982) (#5139) * Initial plan * feat(advisor): add optional background advisor watcher (issue #3982) Implements the opt-in advisor/watcher mode requested in issue #3982. The advisor fires fire-and-forget after each successfully-completed turn that contains tool calls, reads a bounded slice of recent tool-call/result pairs from the session transcript, makes a concise LLM call, and emits an advisory note into the transcript status area. Key design properties (Shape B — reuses existing subagent primitives): - Off by default (enabled = false); toggle via /advisor on|off or [advisor] enabled = true in config.toml - Bounded input: max 8 tool-call/result pairs by default (max 32) - Rate-limited: one emission per 60 s by default (configurable) - Deduplicated: identical notes within 300 s window are suppressed; advisor responses of exactly "ok" are silently dropped - Child-failure isolated: advisor errors are logged, never surfaced as parent turn failures (spawn_supervised wraps the task) - Policy-bounded: read-only reviewer prompt, no tool access, 256 tokens New files: - crates/tui/src/tools/subagent/advisor.rs — AdvisorConfig, EmissionGuard, ToolCallPair, extract_tool_call_pairs(), build_advisor_prompt(), run_advisor_for_turn(); 10 unit tests covering all acceptance criteria - crates/tui/src/commands/groups/core/advisor.rs — /advisor command Changed files: - crates/config/src/lib.rs: AdvisorConfigToml TOML schema struct - crates/tui/src/core/events.rs: Event::AdvisoryNote variant - crates/tui/src/core/ops.rs: Op::SetAdvisorEnabled op - crates/tui/src/tools/subagent/mod.rs: pub mod advisor + re-exports - crates/tui/src/core/engine.rs: advisor_config in EngineConfig + Engine struct field + op handler + post-TurnComplete spawn hook - crates/tui/src/tui/app/types.rs: AppAction::SetAdvisorEnabled - crates/tui/src/tui/ui.rs: AppAction handler + Event::AdvisoryNote display + advisor_config wired in build_engine_config - crates/tui/src/config.rs: advisor field in Config struct + merge - crates/tui/src/main.rs: advisor_config in headless EngineConfig - crates/tui/src/runtime_threads.rs: advisor_config in runtime EngineConfig - crates/tui/src/commands/groups/core/mod.rs: register /advisor command - crates/tui/src/localization.rs: CmdAdvisorDescription message ID - crates/tui/locales/*.json (en + 13 complete locales): translation key - config.example.toml: [advisor] section documentation --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…lemetry, persistence size assertion (#5140) * Initial plan * feat: fleet memory hardening follow-up (#3885) Four items from the fleet memory hardening follow-up: Item 2 — Bounded Fleet/sub-agent default step budgets: - Add FLEET_DEFAULT_MAX_TURNS = 500 constant to codewhale-config - Change default_fleet_max_turns() from u32::MAX to 500 so sessions have a finite step budget out of the box - Change fleet_task_to_worker_spec_with_profiles to default max_steps to WorkerRuntimeProfile::default_max_steps(role) when no budget is set, instead of u32::MAX - Remove u32::MAX special-case from apply_exec_hardening and build_worker_exec_command_from_prompt (0 = no cap; any positive value = bounded) - Update unbounded_max_turns_is_not_passed test → two tests: one for 0 (no cap) and one verifying the new finite default IS forwarded to the subprocess Item 1 — Handle eviction when agent records are retired: - Add HandleStore::evict_session() to remove all handles for a session_id - Add SubAgentManager::pending_handle_evictions queue populated by cleanup() when an agent is fully retired (absent from both agents and worker_records) - Add SubAgentManager::drain_pending_handle_evictions() to drain the queue - Wire async eviction in inspect_agent_from_input (both the single-agent and list paths) after the manager write-lock is released Item 3 — Memory telemetry in Fleet smoke runs: - Add rss_kb() helper reading VmRSS from /proc/self/status (Linux only) - Wire baseline and post-run RSS logging (via eprintln) in the fleet smoke test so memory regressions produce numbers, not user reports Item 4 — Aggregate byte budget assertion for persisted sub-agent state: - Add persisted_subagent_state_has_bounded_serialized_size test that writes 10 worker records and asserts the serialized file is under 64 MiB, verifying the transitively-bounded budget holds and the output is valid JSON --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…#5141) * Initial plan * feat: add SidebarFocus::Sessions variant for dedicated sessions sidebar panel (#2934) Add a `Sessions` variant to `SidebarFocus` so users can pin the sidebar to the sessions history panel (the persistent rail) directly, without needing the `sessions_rail` opt-in setting. Changes: - `tui/src/tui/app.rs`: Add `SidebarFocus::Sessions` variant; update `from_setting` (aliases: sessions, sessions_rail, session_history) and `as_setting` (persists as "sessions") - `tui/src/tui/sidebar.rs`: Route `SidebarFocus::Sessions` to `render_sidebar_sessions` in both the fixed-focus and full-panel code paths; add two new tests - `tui/src/tui/ui.rs`: Add keybindings — Alt+Ctrl+5 and Alt+^ (Shift+6) — for the Sessions panel, following the existing Alt+1–4 and Alt+!#$% pattern - `tui/src/commands/groups/config/config.rs`: Extend `/sidebar` command to accept `sessions`, `session_history`, and `sessions_rail` values - `tui/src/settings.rs`: Update `normalize_sidebar_focus` and the `sidebar_focus` setter to accept and persist `sessions`; add test - `tui/src/config_ui.rs`: Add `Sessions` variant to `SidebarFocusValue`, its `as_setting` impl, and the `From<&str>` conversion - `tui/src/tui/app/tests.rs`: Extend existing `SidebarFocus` round-trip test with `Sessions` cases The `SidebarFocus::Sessions` panel renders the workspace-scoped sessions rail as the sole sidebar content (plus a compact Work strip when active work is present), regardless of whether the `sessions_rail` setting is enabled. `sidebar_auto_idle` already returns false for every explicit focus, so the sessions panel is never collapsed away when idle. All 178 sidebar tests, 42 sessions tests, and the 7 sidebar_focus tests pass. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…son parity (#5143) * Initial plan * feat(i18n): promote zh-Hant to full shipped locale with complete en parity Expand zh-Hant.json from 502 keys to 1252 keys (full parity with en.json). All 750 missing translations generated from zh-Hans using Traditional Chinese vocabulary and character mappings. Promote zh-Hant from partial pack to shipped-complete locale: - Add ZhHant to shipped_complete() list - Change is_partial_pack() to return false unconditionally - Replace zh_hant_is_scoped_as_partial_pack test with zh_hant_has_reached_en_parity_and_is_complete - Update partial_pack_status_tracks_the_shipped_locale_registry test All 30 localization tests pass. cargo fmt --all -- --check passes. Closes #790 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Initial plan * Changes before error encountered Agent-Logs-Url: https://github.com/Hmbown/CodeWhale/sessions/3a6b0bb2-7714-4b43-b421-7724e5a5adb9 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* fix(tui): complete stale call sites left at the train tip The v0.9.4 train tip (1454723, [WIP] #5110) does not compile: the owner_session_id, cancel_token, stop_words, and steer-config changes landed without updating every call site. Fill in the missing NewTaskRequest.owner_session_id, the parallel-batch cancel_token, the merge_config stop_words field, and the &Config argument in the six steer test call sites so the crate builds and the gates can run. * feat(tui): workflow status chip in the ocean header (#5040) The train wired WorkflowPanel::top_bar_chip into the classic header only, so a collapsed workflow run was invisible on ocean-shell sessions. Render the same chip in the ocean top bar, following the goal chip's pattern (#5222): the route label yields width first, the chip truncates cleanly without mid-word clipping, drops entirely when even a minimal chip cannot fit, and renders nothing when no workflow is active. Salvages the width-budgeting idea from the superseded #5113 review while reusing the train's top_bar_chip machinery.
fim_edit writes the target workspace file atomically, so advertising ReadOnly alongside WritesFiles is contradictory. Keep the write and approval capabilities and pin the resulting classification with a focused test. Adapted from Pinvou#5 by @asto18089. Signed-off-by: hexin <372726039@qq.com> Co-authored-by: asto18089 <44870036+asto18089@users.noreply.github.com>
rlm_eval injects the loaded source as content, but its schema and missing-code example told callers to use an undefined SOURCE name. Align every alias description and pin both the schema and error example. Adapted from Pinvou#4 by @asto18089. Signed-off-by: hexin <372726039@qq.com> Co-authored-by: asto18089 <44870036+asto18089@users.noreply.github.com>
grep_files uses a fixed set of common non-code directory exclusions and does not parse .gitignore. Replace the false claim with the behavior implemented by the walker and add a regression assertion. Adapted from Pinvou#4 by @asto18089. Signed-off-by: hexin <372726039@qq.com> Co-authored-by: asto18089 <44870036+asto18089@users.noreply.github.com>
Update bundled integration skills to recommend the codewhale CLI, .codewhale configuration and skill roots, and the built-in PDF reader. Keep the .deepseek skill root only as an explicitly labeled legacy fallback and add a bundled-content regression test. Adapted from Pinvou#4 by @asto18089. Signed-off-by: hexin <372726039@qq.com> Co-authored-by: asto18089 <44870036+asto18089@users.noreply.github.com>
…rnings, budget (#5227) * fix(i18n): complete zh-Hant pack for the keys added after #5143 The /automation surface plus a few keybinding/advisor strings landed on the train after b02a05b promoted zh-Hant to a complete pack, leaving it 47 keys short of en.json (1252 vs 1299) and breaking three parity tests (shipped_complete_packs_have_raw_key_parity_with_english, automation_complete_packs_have_raw_key_and_placeholder_parity, zh_hant_has_reached_en_parity_and_is_complete). Translate the 47 missing keys into Traditional Chinese per crates/tui/locales/AGENTS.md: placeholders stay literal, commands and key names untranslated, product terms English. Parity claim now holds, so no shipped pack is partial anymore. Also update partial_locale_badge_survives_minimum_terminal_layout (renamed to complete_locale_shows_no_partial_badge_at_minimum_terminal_layout): it still asserted zh-Hant renders the '(partial)' badge, which was only true before #5143. The badge path itself is unchanged for future partial packs; the test now asserts a complete pack shows no badge at minimum layout. * fix(tui): finish the #5110 stale failed-agent isolation The #5110 squash landed WIP ("Changes before error encountered") with two self-inconsistent pieces: - select_work_sidebar_tasks documented that a terminal task missing ended_at is dropped, but the owner-match arm kept it anyway, so a ghost receipt could still surface on the current session's Work sidebar. Terminal tasks without ended_at are now dropped before the ownership checks, matching the documented behavior and work_sidebar_hides_other_session_terminals_but_keeps_current_and_active. - task_list_shows_owner_session_when_present expected a 12-char + ellipsis truncation ("session-1234…"), which would overflow the 12-wide Session column the format strings are built to keep aligned. The formatter truncates to 11 chars + ellipsis ("session-123…") so the value fits the column; correct the test expectation to the alignment-preserving behavior. No assertion was weakened: the test still pins the exact truncated rendering. * chore(tui): repair fmt drift Train-side pre-existing drift in settings.rs, tools/web_search.rs, and tui/app/tests.rs (buildkite failed on this earlier tonight). Pure cargo fmt --all output, no semantic changes. * chore(tui): clear unused_mut and dead_code warnings - native_memory.rs with_read_lock: fd-lock 4's RwLock::read takes &self, so the binding no longer needs mut (write still does). - SubAgentToolRegistry.accept_verification was never read: since #5186 the bounded verification surface is delegated to every shell-capable child instead of being keyed off this bit. Drop the dead field; the SubAgentRuntime bit it was copied from stays (it is read at spawn). * refactor(tui): split test modules out of two thousand-line files native_memory.rs (1091 lines) and tui/views/fleet_roster.rs (1044 lines) crossed the 1000-line production-module ceiling in scripts/source-structure-budget.json during the v0.9.4 train. Move each file's #[cfg(test)] module verbatim into a Rust-2018 sibling submodule (native_memory/tests.rs, fleet_roster/tests.rs); test files are excluded from the budget's production-source accounting by definition. The only non-verbatim change is the include! path in the fleet roster tests, which now resolves one directory up. Public APIs are untouched; both files are back under the ceiling (773 and 638 lines). * chore(scripts): bump source-structure aggregate ceiling with dated TODO The v0.9.4 train's 30-PR stack grew owned production Rust from 649350 to 659633 lines (+10283). Splits are line-neutral by design, so they cannot pay this down; only deletion can, and a delete-and-dedup pass is out of scope for a hygiene lane. Raise max_total_owned_rust_lines to the measured 659633 with a dated TODO to pay it back down in v0.9.5. Per-file ceilings were NOT raised: the two new thousand-line modules from the stack were split back under 1000 in the parent commit.
agents/message only queues mail for a running child, while agents/followup delivers through a live input channel and cannot resume an interrupted child. Remove the natural/idle resume promises and pin the descriptions to those implemented states. Adapted from Pinvou#4 by @asto18089. Signed-off-by: hexin <372726039@qq.com> Co-authored-by: asto18089 <44870036+asto18089@users.noreply.github.com>
…time API
Implements GET/v1/memory (list with scope/search/limit), GET /v1/memory/{id}
(inspect), POST /v1/memory (create, auth-gated), and DELETE /v1/memory (clear
by scope) backed by the existing NativeMemoryStore.
Key design decisions:
- Raw file-system paths are never exposed; entries carry scope ("global" /
"workspace") and workspace_id (SHA-256 digest of origin URL, not a path)
- Summaries are bounded to 300 chars to prevent private data exfiltration
- Workspace scope lookups are silently empty when no git origin is configured
(same behavior as the existing get_for_workspace boundary)
- DELETE /v1/memory requires explicit scope= param, rejecting absent/empty values
- memory: true is advertised in GET /v1/runtime/info capabilities
Also adds NativeMemoryStore::list_all() for ordered listing without FTS, and
updates the RuntimeCapabilities struct + test in the protocol crate.
Closes #5072
b377472 to
3130b49
Compare
a241772 to
72a7323
Compare
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 35663593 | Triggered | Generic High Entropy Secret | 23e2790 | crates/tui/src/doctor/tests.rs | View secret |
| 35663594 | Triggered | Generic High Entropy Secret | 23e2790 | crates/tui/src/doctor/tests.rs | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
|
Closing this stale draft as superseded. The bounded memory inspection and lifecycle surface landed on I verified that the landed commit is contained in current |
The v0.9.4 Runtime API route table had no memory resource, leaving managed clients unable to inspect active memory, understand its scope/provenance, or apply lifecycle controls without a second memory store.
New endpoints (
/v1/memory)All routes are gated behind the existing
require_runtime_tokenmiddleware.GET/v1/memoryscope=global|workspace|all,q=<fts>,limit=1–200GET/v1/memory/{id}POST/v1/memoryscope: "global"|"workspace")DELETE/v1/memoryscope=paramExample response shape:
{ "entries": [{ "id": 42, "scope": "global", "workspace_id": null, "summary": "prefer snake_case for identifiers", "stale": false, "line_start": 3, "line_end": 3, "status": "active" }], "total": 1 }Redaction / safety
scope+workspace_id(SHA-256 digest of the git origin URL, not a local path)GET /v1/memory/{id}applies the sameget_for_workspaceboundary as existing read surfaces — numeric IDs from another repo resolve to 404Supporting changes
NativeMemoryStore::list_all— new method for ordered full-scope listing without an FTS query (used whenq=is absent)RuntimeCapabilities.memory: true— advertised inGET /v1/runtime/info; field is#[serde(default)]so older deserialization paths seefalserather than an error