Skip to content

feat(chat): /new creates a new session instead of resetting the current one#3071

Merged
houko merged 6 commits into
librefang:mainfrom
neo-wanderer:feat/new-command-creates-session
Apr 25, 2026
Merged

feat(chat): /new creates a new session instead of resetting the current one#3071
houko merged 6 commits into
librefang:mainfrom
neo-wanderer:feat/new-command-creates-session

Conversation

@neo-wanderer

@neo-wanderer neo-wanderer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Splits /new (fresh session, new UUID, navigate) from /reset (wipe in place, same UUID). Also adds two related fixes for URL-pinned sessions discovered while testing.

Behaviour changes

  1. /new creates a new session instead of clearing in place. Channels/scripts that relied on the old behaviour should switch to /reset.
  2. GET /api/agents/{id}/session now accepts ?session_id=<uuid> to load a specific session. Cross-agent reads return 404; malformed UUIDs 400.
  3. Landing on a hand-agent URL flips showHandAgents to "1" in localStorage so the picker keeps showing hand agents on refresh.

Changes

  • ws.rs — split new/reset; /new now surfaces an explicit error if the kernel returns no session_id instead of swallowing it.
  • routes/agents.rs — typed GetAgentSessionQuery { session_id: Option<Uuid> }; cross-agent guard now also fires in the Ok(None) branch (an explicit session_id that doesn't match this agent's canonical id 404s rather than falling back to the canonical empty session).
  • ChatPage.tsxuseChatMessages honors the URL-pinned sessionId; auto-flips showHandAgents for hand-agent URLs; surfaces session-load errors via the existing onClearError toast channel.
  • zh.jsoncmd_new now reads "新建会话(生成新会话 ID)".
  • SDKs (go, js, python, rust): get_agent_session gains an optional session_id query param. Helper functions in all four SDKs already drop None/undefined/empty values, so default callers are unaffected.

Tests

  • cargo test -p librefang-api: 42 passed (includes new test_get_agent_session_rejects_cross_agent_session_id covering the cross-agent 404 + malformed-UUID 400 + same-agent round-trip).
  • cargo clippy --workspace --all-targets -- -D warnings: clean.
  • cargo test --workspace: 1 pre-existing failure (librefang-kernel::config::tests::test_load_config_defaults) — host-env dependent (asserts default log_level == "info" but reads from ~/.librefang/config.toml), identical on upstream/main unmodified.

Test plan

  • /new flips URL ?sessionId to a fresh UUID, dropdown shows the new row, history empty.
  • /reset still clears history in the current session (same session_id).
  • Cross-agent GET /api/agents/{A}/session?session_id=<B's session> → 404.
  • Malformed ?session_id=foo → 400.
  • Refresh on ?agentId=<hand-agent> no longer dumps to default assistant.

@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review size/M 50-249 lines changed area/runtime Agent loop, LLM drivers, WASM sandbox area/kernel Core kernel (scheduling, RBAC, workflows) and removed size/M 50-249 lines changed labels Apr 24, 2026
@github-actions
github-actions Bot requested a review from houko April 24, 2026 19:19
@github-actions github-actions Bot added size/M 50-249 lines changed size/L 250-999 lines changed area/sdk JavaScript and Python SDKs and removed size/M 50-249 lines changed size/L 250-999 lines changed labels Apr 24, 2026
@neo-wanderer

Copy link
Copy Markdown
Contributor Author

Added a closely-related fix on the same branch (commit 0a8a311):

GET /api/agents/{id}/session now honors ?session_id= query param.

While testing /new I noticed the dashboard sessions dropdown appeared to do nothing — clicking any row showed an empty chat pane. Root cause: the frontend fetches history via /api/agents/{id}/session (singular) but never forwarded the URL-pinned sessionId, so the backend always returned whichever session the kernel thinks is canonical-active for that agent. After /new (and in general any time the canonical pointer drifts), that can be a different session than any row in the dropdown — so none of them appear to work.

  • Backend (routes/agents.rs): get_agent_session accepts an optional session_id query param, validates session.agent_id == agent_id before returning (so one agent's id can't read another agent's history), and 400s on malformed UUIDs. Absent param preserves the original canonical-active behaviour.
  • Frontend (api.ts, ChatPage.tsx): loadAgentSession(agentId, sessionId?) forwards the URL id; useChatMessages passes its sessionId argument through.

Verified end-to-end: GET /session?session_id=<X> returns exactly <X>'s messages; no param returns canonical-active as before. This was already implicit in the per-tab multi-session model from #2959 — the WS send path honors session_id, but the initial history load didn't. Now both do.

@neo-wanderer

Copy link
Copy Markdown
Contributor Author

Added a third related fix on the same branch (commit 209a513):

Don't drop URL-pinned hand agents when showHandAgents is off.

Refreshing the dashboard on a hand-agent chat (?agentId=<hand>) would dump the user back to the default assistant. Root cause: agents is built from useAgents({ includeHands: showHandAgents }), and showHandAgents is a localStorage toggle that defaults off. When off, hand-spawned agents aren't in the list, the "clear missing selection" effect fires, and the auto-select effect picks the first running standalone agent.

Fix: before clearing selectedAgentId, flip showHandAgents on and let the refetch include hand agents. If the id is still unresolved after that, then clear. Side effect: landing on any hand-agent URL persists showHandAgents = "1" in localStorage, so hand agents stay visible in the picker — intended UX since the user clearly wanted one.

@github-actions github-actions Bot added size/L 250-999 lines changed and removed size/M 50-249 lines changed labels Apr 24, 2026
@houko

houko commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Review

Overview

Feature design is good: splitting /new (fresh session, new UUID, navigate) from /reset (wipe in place, same UUID), and letting GET /api/agents/{id}/session?session_id= pin a specific session per tab. The dashboard correctly syncs URL + sessions dropdown in one round-trip.

Blocking — scope creep

The diff silently bundles two unrelated changes not mentioned anywhere in the description:

1. Entire PR #3068 kernel-manifest fix is in this branchmanifest_helpers.rs + kernel/mod.rs (parse-order swap, four-name-form matching, name preservation, 7 unit tests). If #3068 merges first this becomes a redundant diff; if it merges after, whichever lands second will fight the other. Please rebase onto #3068's merged state.

2. MAX_HISTORY_MESSAGES: 40 → 400 (agent_loop.rs:79) — same 10× context-budget change I flagged on #3045 and #3068, and the comment above still lies about "40 gives roughly 7–10 turns". Needs its own PR with justification.

Backend

3. Silent empty session_id on /new (ws.rs:~L1130)

let sid = info
    .get("session_id")
    .and_then(|v| v.as_str())
    .unwrap_or_default()
    .to_string();

If create_agent_session ever returns a JSON shape without a session_id string, sid becomes "" and the frontend's truthy check silently drops the navigate — user sees "New session created." but the URL doesn't change. This is exactly the silent-failure class this PR is otherwise fighting. Either emit an explicit error, or tighten create_agent_session's return type to a concrete struct.

4. No test for the new cross-agent leak guard (agents.rs:~L1385)

if session.agent_id != agent_id {
    return (StatusCode::NOT_FOUND, ...);
}

Check is correct — without it, GET /api/agents/A/session?session_id=<B's session> would leak B's history. Please pin it with an integration test; security-sensitive logic shouldn't live without a regression.

5. Untyped query struct (agents.rs:~L1338)

Query(params): Query<HashMap<String, String>> works but a typed struct SessionQuery { session_id: Option<Uuid> } would give automatic UUID validation and cleaner 400s, and matches the rest of the API. Minor.

Dashboard

6. Silent override of showHandAgents preference (ChatPage.tsx:~L2018)

Auto-enabling the hand-agents toggle whenever the URL points at an agent not in the current list is a real UX win (avoids kicking users to the default agent on refresh of a hand-agent URL), but it overrides a user-set localStorage toggle without any signal. Please mention it in the description — it's behaviour change the Summary skips.

7. loadAgentSession 404 path — with session_id now validated server-side (404 for cross-agent, 400 for bad UUID), the .then(session => ...) in useChatMessages doesn't visibly handle those in the diff. Confirm the existing catch surfaces a readable toast rather than leaving setAgentLoading(loadId, true) set.

i18n

8. zh.json still says "重置会话" for cmd_new. Test plan flags this as "follow-up" but an unticketed follow-up tends to stay that way. Either fix here or link an issue.

SDKs

All four get a breaking signature change on get_agent_session. Two concrete worries:

9. Python None-handling

def get_agent_session(self, id: str, session_id: Any = None):
    return self._c._request("GET", f"/api/agents/{id}/session", None, query={"session_id": session_id})

Depending on _request's serialization, None may serialize as "None" or empty — backend's parse::<uuid::Uuid>() will then 400 every default call. Verify _request drops None values, or guard explicitly:

query = {"session_id": session_id} if session_id else None

Same concern for Rust SDK's Option<&str> threaded through &[("session_id", session_id)] — depends on do_req's handling.

10. Go SDK is a hard breakGetAgentSession(id)GetAgentSession(id, query) with no default. If the SDKs are auto-generated from openapi.json, unavoidable, but callers need release-notes warning.

Test plan vs CLAUDE.md

Only manual dashboard clicks are listed. Project instructions require cargo test --workspace + cargo clippy --workspace --all-targets -- -D warnings. Please paste output. The new cross-agent leak guard + session-id parsing is net-new security-sensitive logic.

Requested changes

  • Rebase to drop the fix(kernel): boot-time TOML drift detection now reaches hand agents #3068 manifest diff
  • Drop MAX_HISTORY_MESSAGES: 40 → 400; open separately
  • Explicit error from /new when session_id is missing from kernel response
  • Integration test for cross-agent session-read rejection (404)
  • Verify/fix Python + Rust SDK None-handling for session_id
  • Update Chinese cmd_new string or link a tracking issue
  • Document the showHandAgents auto-flip in the description
  • Paste cargo test + cargo clippy results

Core feature design is good and the backend scoping check is the right move — once the scope is trimmed and the silent-failure cases are tightened, this is mergeable.

@github-actions github-actions Bot added needs-changes Changes requested by reviewer ready-for-review PR is ready for maintainer review and removed ready-for-review PR is ready for maintainer review needs-changes Changes requested by reviewer labels Apr 24, 2026
…nt one

BREAKING CHANGE: The `/new` slash command now creates a brand-new session
(new session_id, empty history, registry switched to it) instead of
resetting the current session in place. The previous behaviour was
identical to `/reset`, leaving both commands functionally duplicated.

- WS backend (ws.rs): `/new` now calls `kernel.create_agent_session` and
  returns the new `session_id` in the `command_result` payload. `/reset`
  continues to call `reset_session` (same id, history cleared).
- Dashboard (ChatPage.tsx): `useChatMessages` surfaces a new
  `onNewSession(session_id)` callback. ChatPage wires it to update the
  URL (`?sessionId=<new>`), bump `sessionVersion`, and invalidate
  `agentKeys.sessions(...)` so the sessions dropdown refetches — URL
  and UI flip to the new session in a single round-trip.
- Command registry (system.rs, en.json): `/new` and `/reset` now have
  distinct descriptions; `/reset` is listed separately in `/api/commands`.

Migration: callers relying on `/new` to clear history without changing
`session_id` should switch to `/reset`.
…nned tabs

When the dashboard sessions dropdown switched rows, the URL flipped to
`?sessionId=<X>` but the history fetch hit `/api/agents/{id}/session`
without forwarding the id — so the backend kept returning whichever
session the kernel had as canonical-active, which can drift away from
every row in the dropdown (e.g. after `/new` creates a fresh session
but the canonical-active pointer moves elsewhere). Result: no matter
which session you clicked, the chat pane showed whatever the kernel
thought was active, often a different session entirely.

- Backend (routes/agents.rs): `get_agent_session` now accepts an
  optional `session_id` query param. If present it loads that session
  via the memory substrate and validates `session.agent_id == agent_id`
  before returning, so one agent's id cannot be used to read another
  agent's history. Bad UUIDs → 400; absent param preserves the
  canonical-active behaviour.
- Frontend (api.ts, ChatPage.tsx): `loadAgentSession(agentId, sessionId?)`
  forwards the URL-pinned session id; `useChatMessages` passes its
  `sessionId` argument through so the fetch matches the tab's URL
  instead of the server's drifting active pointer.
Refreshing the dashboard on a hand-agent chat (`?agentId=<hand>`) would
dump the user back to the default assistant. The `agents` list is
derived from `useAgents({ includeHands: showHandAgents })`, where
`showHandAgents` is a localStorage toggle that defaults off. If it's
off, the server-side query excludes hand-spawned agents, the URL's
agent id isn't in the list, and the "clear missing selection" effect
flips `selectedAgentId` to "" — then the auto-select effect picks
"first running" = the default agent.

Fix: before clearing `selectedAgentId` because the agent isn't in the
list, flip `showHandAgents` on. The refetch pulls the hand agents in,
the URL-pinned id resolves, and the user stays on the chat they meant
to open. If the agent is still missing after hands are loaded, we
clear as before.

Side effect: landing on any hand-agent URL persists
`showHandAgents = "1"` in localStorage, so hand agents stay visible
in the picker going forward. That's the intended UX — the user
clearly wanted to interact with one.
…path

Replace `Query<HashMap<String,String>>` with a typed
`GetAgentSessionQuery { session_id: Option<Uuid> }` so a malformed UUID
is rejected by serde and returns a clean 400 — no hand-rolled parse
branch.

Also tightens the cross-agent leak guard. The original
`session.agent_id != agent_id` check only fired in the `Ok(Some)` arm,
but `get_session` returns `Ok(None)` when the row hasn't been
materialized yet (fresh-spawned agent, no messages). In that branch the
handler used to fall back to the agent's canonical-active session,
which let `GET /api/agents/A/session?session_id=<B's session>` succeed
with A's empty session under the requested id — visually a no-op, but
indistinguishable to a client from a successful read of an unrelated
session. Now: if an explicit session_id is provided in the query and
does not match this agent's canonical id, return 404. Same-agent
canonical id still round-trips (registry already proves ownership).

Adds `test_get_agent_session_rejects_cross_agent_session_id` covering:
- A's id + B's session_id → 404
- malformed UUID → 400
- A's id + A's own session_id → 200
The `/new` slash command swallowed an empty/missing `session_id` field
via `unwrap_or_default()`, returning `command_result` with `session_id:
""`. The dashboard's truthy check then silently dropped the URL
navigate, so the user saw "New session created." but the URL still
pointed at the old session — exactly the silent-failure class this
branch is otherwise fighting.

Now: if `create_agent_session` returns success but the response payload
has no usable `session_id`, surface a typed `error` event instead of a
fake-success `command_result`.
Two small UX cleanups around the new session_id-pinned history fetch:

- `useChatMessages` previously swallowed any `loadAgentSession` error
  with a comment-only catch. With the new server-side validation
  (404 cross-agent, 400 malformed uuid) a stale `?sessionId=` URL would
  render an empty chat with no signal. Forward the error message to
  the existing `onClearError` toast channel.

- `zh.json` `cmd_new` still read "重置会话(清除历史)" — same string as
  `cmd_reset`. Updated to "新建会话(生成新会话 ID)" so the Chinese
  command palette reflects the new vs. reset split.
@neo-wanderer
neo-wanderer force-pushed the feat/new-command-creates-session branch from 2c4ada5 to 68b75a2 Compare April 25, 2026 09:04
@github-actions github-actions Bot removed the area/runtime Agent loop, LLM drivers, WASM sandbox label Apr 25, 2026
@github-actions github-actions Bot removed the area/kernel Core kernel (scheduling, RBAC, workflows) label Apr 25, 2026
@neo-wanderer

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @houko — addressed everything in a force-push (2c4ada5c → 68b75a22):

Blocking

  1. fix(kernel): boot-time TOML drift detection now reaches hand agents #3068 manifest diff dropped. Rebased onto upstream/main directly via cherry-pick of the three feature commits — the manifest_helpers.rs + kernel/mod.rs parse-order changes are no longer in this branch, so fix(kernel): boot-time TOML drift detection now reaches hand agents #3068 can land independently without conflict.
  2. MAX_HISTORY_MESSAGES reverted to 40. Dropped the bump commit; the misleading "7–10 turns" comment is back to its original form. Will open separately with justification + updated tests.

Backend

  1. Silent empty session_id on /new (commit ca9dffad): cmd_new now matches the session_id field explicitly and emits a typed error event if it's missing or empty, instead of returning a fake-success command_result with session_id: "".
  2. Cross-agent guard test (commit 2b78d339): added test_get_agent_session_rejects_cross_agent_session_id. While writing it I discovered the guard had a fallthrough: get_session returns Ok(None) for unmaterialized sessions (fresh-spawned agent, no messages), and the handler used to fall back to the agent's canonical-active session — meaning GET /api/agents/A/session?session_id=<B's-session-uuid> would 200 with A's empty session under the requested id. Same commit tightens that branch: explicit session_id not matching this agent's canonical id → 404. Test covers cross-agent 404, malformed-UUID 400, and same-agent round-trip 200.
  3. Typed query struct (commit 2b78d339): replaced Query<HashMap<String,String>> with GetAgentSessionQuery { session_id: Option<Uuid> }. Serde validates the UUID and returns 400 before the handler runs.

Dashboard

  1. showHandAgents auto-flip documented in the updated PR body under "Behaviour changes".
  2. loadAgentSession 404 path (commit 68b75a22): the .catch was a comment-only swallow; now forwards the error message to the existing onClearError toast channel. The .finally was already clearing setAgentLoading, so the spinner part of the concern wasn't actually present, but the silent empty-chat-on-stale-URL definitely was.

i18n

  1. zh.json cmd_new (commit 68b75a22): now "新建会话(生成新会话 ID)". Fixed in this PR rather than punting.

SDKs

  1. Python + Rust None-handling: verified — both helpers already filter None cleanly. Client._request (Python) line ~69: {k: v for k, v in query.items() if v is not None}. do_req (Rust) lines 46–49: filter_map(|(k, v)| v.map(|vv| (*k, vv))). Same for JS (_withQuery skips undefined/null) and Go (withQuery skips ""). Default None callers don't transmit the param.
  2. Go SDK breaking signature: noted. Since SDKs are auto-generated from openapi.json, callers will see the change on the next regen anyway.

Tests

  • cargo test -p librefang-api: 42 passed, 0 failed. Includes the new cross-agent integration test.
  • cargo clippy --workspace --all-targets -- -D warnings: clean.
  • cargo test --workspace: 1 failure — librefang-kernel::config::tests::test_load_config_defaults — host-env dependent (asserts config.log_level == "info" but load_config(None) falls back to ~/.librefang/config.toml); identical assertion on upstream/main so unrelated to this PR.

PR body updated to call out the two undocumented behaviour changes.

@houko houko 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.

All review concerns addressed and verified — #3068 manifest diff dropped, MAX_HISTORY_MESSAGES revert confirmed, /new emits explicit error on missing session_id, cross-agent guard test covers 404/400/200, typed GetAgentSessionQuery validates UUID, Ok(None) fallthrough fixed, Python/JS/Rust/Go SDK None-handling verified. LGTM.

@houko
houko merged commit 26bcd05 into librefang:main Apr 25, 2026
29 checks passed
@github-actions github-actions Bot removed the ready-for-review PR is ready for maintainer review label Apr 25, 2026
neo-wanderer added a commit to neo-wanderer/librefang that referenced this pull request Apr 25, 2026
Brings in PR librefang#3071 (rebased): /new creates a fresh session, GET
/api/agents/{id}/session honors ?session_id=, hand-agent URLs auto-
flip showHandAgents. Local-only ahead of upstream until librefang#3071 merges.
@neo-wanderer
neo-wanderer deleted the feat/new-command-creates-session branch April 25, 2026 12:39
houko added a commit that referenced this pull request Apr 25, 2026
PR review on #3189 (houko) caught two stale references describing the
pre-#3071 behavior of /new ('wipe the session' / 'clear history'). The
new semantics fork off a fresh session while the previous one stays
resumable on the same channel — phrasing updated in 4 places (en + zh
× 2 files), plus a one-line clarifier in the slash-command roster
section so the cross-reference doesn't silently inherit the old
mental model:

- configuration/channels: 'wipe the session' → 'fork off into a new
  session (the prior conversation stays resumable on the channel)'
- integrations/api/realtime: '(clear history)' → 'forks into a fresh
  session — the prior session is preserved and remains resumable'
- integrations/cli/commands roster paragraph: explicit note that /new
  is a fork, not a wipe.

Same wording mirrored to docs/src/app/zh/.
houko added a commit that referenced this pull request Apr 26, 2026
…pi / observability (#3189)

* docs(providers): add Novita / SearXHG / Bedrock entries

* docs(config): document [parallel_tools] section + max_history_messages

* docs(scheduler): cron multi-delivery + pre_script + silent_marker

* docs: live context.md, chat attachments, CLI slash commands

Salvages three of the six items from the (quota-killed) misc-features
agent run. Each is a focused append, no rewrite of existing content.

- agent/templates: live Live Context section explains the per-turn
  re-read of `context.md` (#3115), the .identity/ vs root path lookup,
  the 32 KB cap with stale-cache fallback, and the cache_context opt-out.
- integrations/api/agents: attachment-resolution table (image / PDF /
  text+code) covering the supported MIME / extension list and the
  200 KB truncation, plus a note about adjacent-text coalescing for
  small chat-tuned local models (#3094).
- integrations/cli/commands: 'Slash Commands in Chat' section listing
  the registry-backed CLI commands (/model /status /help /clear /kill
  /exit) and pointing at channel-side commands for the broader set
  (#3122 #3123).

* docs: tool invoke API, trajectory export, doctor audit, CLI default-pick, SDK codegen

Picks up the items the second misc-features agent didn't get to before
hitting the org quota.

- integrations/api/system: POST /api/tools/{name}/invoke (#3025) — fail-
  closed allowlist, agent_id requirement for approval-gated tools.
- integrations/api/agents: GET /sessions/{id}/trajectory (#3097) —
  json/jsonl formats, redactor pipeline, audit use case.
- operations/troubleshooting: 'Audit checks' subsection enumerating the
  three registered AuditChecks (#3082) — VaultKey, ApiListenAddr,
  ConfigTomlSchema — with what each asserts and how to extend.
- configuration/providers/tools: 'CLI logins as first-class default
  providers' section (#3061) — detection priority claude-code > codex-
  cli > gemini-cli > qwen-code, override via [default_model] in
  config.toml.
- integrations/sdk: 'Auto-generated from openapi.json' section (#3046) —
  scripts/codegen-sdks.py, pre-commit hook, openai-tag skip rule.

* docs(zh): mirror providers / config-core / cron / features-index sections

* docs(zh): mirror agent / api / cli / providers-tools / troubleshooting / sdk additions

Round 2 of the zh translation pass — picks up the seven mid-priority
files that the first commit didn't cover. All sections mirror the EN
content from #3189 but read naturally as Chinese (terms like agent /
session / fallback / NoEntry left in English where they're API
surface, prose translated to 中文).

- agent/templates: Live Context section, .identity/ vs root fallback,
  cache_context = true opt-out.
- api/agents: attachment-resolution table + trajectory export endpoint
  (#3025 / #3094 / #3097).
- api/system: POST /api/tools/{name}/invoke endpoint + fail-closed
  allowlist explanation.
- cli/commands: 'Slash Commands in Chat' section listing CLI registry
  commands and pointing at channel commands.
- providers/tools: 'CLI logins as first-class default providers'
  detection priority and override.
- troubleshooting: 'Audit checks' subsection + table.
- sdk: 'Auto-generated from openapi.json' section.

* docs: refresh /new slash-command semantics (#3071 review fix)

PR review on #3189 (houko) caught two stale references describing the
pre-#3071 behavior of /new ('wipe the session' / 'clear history'). The
new semantics fork off a fresh session while the previous one stays
resumable on the same channel — phrasing updated in 4 places (en + zh
× 2 files), plus a one-line clarifier in the slash-command roster
section so the cross-reference doesn't silently inherit the old
mental model:

- configuration/channels: 'wipe the session' → 'fork off into a new
  session (the prior conversation stays resumable on the channel)'
- integrations/api/realtime: '(clear history)' → 'forks into a fresh
  session — the prior session is preserved and remains resumable'
- integrations/cli/commands roster paragraph: explicit note that /new
  is a fork, not a wipe.

Same wording mirrored to docs/src/app/zh/.

* docs(channels): 9 channel-related Apr 22-25 PRs (en + zh)

Covers the channel + bridge feature wave:
- #3020 per-agent ChannelOverrides — fixes the contradicting 'without
  modifying the agent manifest' phrasing in both en and zh.
- #3077 prefix_agent_name + Signal plain-text default
- #3009 Slack reactions_enabled + #3005 Feishu @mention preservation
  consolidated into a 'Reactions and Processing State' section
- #3008 Signal media attachments
- #3012 WhatsApp send_voice + dm_policy / group_policy
- #3011 Webhook deliver_only mode
- #2972 channel file downloads (file_download_dir / max_bytes config)

* docs(config-core): provider timeout / browser CDP / cron session caps (en + zh)

Three new `config.toml` sections that landed Apr 22-23:
- #3004 `[provider_request_timeout_secs]` map
- #2993 / #2991 `[browser].cdp_endpoint` + `cdp_auth_token_env`
- #2994 `[kernel].cron_session_max_tokens` / `cron_session_max_messages`

* docs(api): GET /attach + Owner Notice envelope (en + zh)

- #3078 SSE attach endpoint for multi-client co-watching — describes
  the per-session SessionStreamHub fan-out, attacher behaviour, and
  the curl example.
- #2965 ReplyEnvelope.owner_notice + notify_owner tool — message body
  field, StreamEvent variant, WhatsApp OWNER_JIDS fan-out.

* docs(observability + ops): Tempo stack, cache_hit_ratio metric, daemon log tee (en + zh)

- #3064 + #3149 — bundled observability stack (otel-collector / Tempo
  / Grafana), bring-up commands, [telemetry] config, business spans,
  cache_hit_ratio metric formula and where it surfaces
- #3022 — `librefang start --foreground` now tees logs to
  ~/.librefang/logs/daemon-YYYY-MM-DD.log; same-day append; 7-day
  rotation pruned on daemon start

* docs: hooks transform / lazy tools / mcp taint / capability detection / providers extras

Final batch of catch-up docs covering the remaining Apr 22-25 user-facing
features:

- agent/hooks: transform_tool_result hook contract (#3003)
- agent/templates: tool_search / tool_load lazy loading + lazy_tools
  opt-out (#3047)
- integrations/mcp-a2a: TaintRuleId enum + per-tool skip_rules policy
  (#2999)
- configuration/features: compaction summaries in user's conversation
  language (#3007)
- configuration/providers/local: embedding auto-detection priority
  list (#3099) + Ollama capability detection (Modality / metadata
  pipeline, #3074 / #3133 / #3134 / #3140)
- configuration/providers/management: custom image-gen provider via
  generic OpenAI-compat driver (#2998) + Moonshot file-upload helper
  via /v1/files (#2966)

All sections mirrored to docs/src/app/zh/.

* docs: address houko fix-PR audit (8 items, en + zh)

PR review caught 1 stale-doc contradiction + 7 undocumented behaviour
changes from the fix-PR cohort.

- #3114 — configuration/core: 'api_key empty = unauthenticated' was
  stale; now documents the loopback-only policy (non-loopback bypass
  was closed)
- #3170 — features/observability: auto_start is opt-in; home_dir-
  scoped Docker labels; RAII cleanup
- #2989 — api/agents POST /message: per-request session_id override
- #3048 — Interrupting a Running Agent Turn: /stop cascades into
  agent_send subagents (recursive)
- #3056 + #3000 — desktop: dedicated Connection screen via custom
  URI scheme with retry / open-in-browser / uninstall
- #2952 — triggers: response routes to agent's home channel
- #2955 — triggers: task_posted assignee_match = 'self' | 'any'
- #3069 — providers/local: 60-second reprobe + manual 'Test
  connection' refresh

All sections mirrored in docs/src/app/zh/.
@houko houko mentioned this pull request Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sdk JavaScript and Python SDKs size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants