feat(types,kernel,api): per-agent channel allowlist#4961
Conversation
fdaba63 to
1a85a20
Compare
|
Pausing this PR — currently under testing in our integration branch. Will resume once validation completes. |
3b2c58b to
bfc22e6
Compare
4a0b78f to
2d4d796
Compare
6c1f0cc to
8b25885
Compare
8b25885 to
a28fc7e
Compare
90316a1 to
effd0fe
Compare
367a865 to
ebc6a60
Compare
|
Rebased on origin/main (currently @208ca88d8) — branch updated 2026-05-21. Force-pushed, single substantive commit on top of current main, no merge-from-main noise. Ready for review. |
houko
left a comment
There was a problem hiding this comment.
Solid structure and good that you included an integration test for the new routes. A few real problems before this can merge — requesting changes.
1. Allowlist namespace mismatch between API and enforcement (correctness)
The GET endpoint builds available from sidecar display names:
crates/librefang-api/src/routes/agents.rs (get_agent_channels):
let available: Vec<String> = state.kernel.config_ref()
.sidecar_channels.iter().map(|sc| sc.name.clone()).collect();But the bridge enforces the allowlist against the channel type string:
crates/librefang-channels/src/bridge.rs:2810-2814 (resolve_or_fallback):
let ct = channel_type_str(&message.channel); // -> "telegram" / "discord" / Custom(s)
if !allowlist.iter().any(|c| c == ct) { ... return None; }SidecarChannelConfig has a distinct name (display name) and channel_type: Option<String> (defaults to Custom(name)) — see crates/librefang-types/src/config/types.rs:2159 and :2169. So a sidecar named "my-tg-bot" with channel_type="telegram" returns available=["my-tg-bot"], the user stores "my-tg-bot" in the allowlist, and enforcement then compares it against "telegram" — never matches, so every inbound message is filtered out. The namespace the API offers must be the same one enforcement checks. Pick one (channel_type) and use it consistently in both available and the bridge comparison.
2. Unrelated change bundled in — scope creep
agents.rs:329-334 adds, inside resolve_manifest:
if req.template.is_some() {
manifest.workspace = None;
}This is a behavioral change to template spawning (clearing the workspace path) that has nothing to do with the per-agent channel allowlist this PR is about, and it isn't mentioned in the summary or covered by a test. Per the repo's one-PR/one-concern policy, please drop it from this PR (open a separate PR/issue if it's a real fix).
3. Missing OpenAPI + SDK regeneration (required, CI red)
You added routes::get_agent_channels / set_agent_channels to openapi.rs, but openapi.json and the generated SDKs (sdk/) were not regenerated/committed. The "OpenAPI Drift" CI job fails for exactly this reason ("openapi.json, generated SDKs, or schema baselines are out of sync"). Run the codegen (the pre-commit hook regenerates openapi.json; python3 scripts/codegen-sdks.py for SDKs) and commit the result.
4. Branch is 49 commits behind main — please rebase
Merge-base is 208ca88d; origin/main is 49 commits ahead. The "Quality" fmt failures are all in files this PR doesn't touch (librefang-cli/src/main.rs, tui/event.rs, openclaw.rs, config/types.rs) — pre-existing on the stale base, already fixed on main. A rebase clears these and lets CI reflect the real state. (The two Windows shard failures are 45-min runner timeouts, i.e. infra flake, not your code.)
18621c4 to
fc5cfba
Compare
|
All 4 review points addressed. Branch rebased onto current 1. Namespace mismatch fix (correctness bug)
Fixed by emitting A new integration test 2. Scope creep removed The 3. OpenAPI + SDKs regenerated
4. Rebase Rebased onto Test results
|
houko
left a comment
There was a problem hiding this comment.
Re-reviewed the current HEAD. Solid structure and good that you included API-level integration tests. A few real problems before merge.
1. Allowlist namespace mismatch (correctness, blocking). get_agent_channels (crates/librefang-api/src/routes/agents.rs) builds available from sidecar display names (sidecar_channels … sc.name), but enforcement in crates/librefang-channels/src/bridge.rs (resolve_or_fallback) compares against channel_type_str(&message.channel) — the channel type, not the display name. SidecarChannelConfig has distinct name (types.rs:2159) and channel_type (types.rs:2170, defaults to Custom(name)). A sidecar named my-tg-bot with channel_type="telegram" offers ["my-tg-bot"], the user stores it, and enforcement compares against "telegram" — never matches, so every inbound message for that agent is filtered out. Pick one namespace (channel_type) and use it in both available and the bridge comparison.
2. Out-of-scope change (blocking). resolve_manifest adds if req.template.is_some() { manifest.workspace = None; } — a template-spawning behavior change unrelated to this feature, untested, not in the summary. Please drop it (separate PR if it's a real fix).
3. OpenAPI/SDK drift (blocking). You registered the two new routes in openapi.rs but didn't regenerate openapi.json / sdk/. Run codegen and commit, or the drift job stays red.
4. No test at the enforcement site (blocking). tests/agent_channels_routes_test.rs covers only the GET/PUT roundtrip; the new agent_channel_allowlist + the two resolve_or_fallback filter branches (where the namespace bug in #1 lives) are untested. Add a bridge-level test that dispatches an inbound message for an agent with a non-empty allowlist and asserts in/out routing — that pins #1.
5. Rebase onto current main to clear the stale-base fmt failures in Quality.
The kernel/types plumbing is otherwise correct (field + serde + Default + wizard default; rollback on persist failure; trait + impl both updated).
…e with Save buttons Closes librefang#4917, librefang#4918, librefang#4924, librefang#4925. Adds an editable agent-detail panel with four tabs (Skills, MCP servers, Channels, Schedule) over the existing read-only view. Each tab keeps a local draft, an explicit Save button gated on dirty + non-pending state, and dedicated query/mutation hooks under `src/lib/{queries,mutations}/`. The Schedule tab delegates to a dedicated `AgentSchedulePanel` component that calls `patchAgent` with a typed `AgentSchedulePatch` body — the type covers all four schedule variants (`"reactive"`, `periodic`, `proactive`, `continuous`) instead of the narrow string/continuous shape that previously dropped the periodic and proactive cases on the floor. Addressing the review feedback on the previous round: - **Dashboard-only PR now.** The backend half (`set_agent_channels`, `registry.update_channels`, `AgentManifest.channels`, the channel routes, `openapi.json` and `sdk/` regen) is removed from this PR — it duplicates librefang#4961 and ships there. Every Rust file this PR used to touch is back at `origin/main`. - **Reconciled the half-merged Schedule tab.** Removed the duplicate `useMcpServers` import and the duplicate inline `renderScheduleTab` block that referenced helpers (`cronJobsQuery` / `agentTriggersQuery` / `formatPattern` / `CronJobItem`) the refactor renamed/extracted into `AgentSchedulePanel`. Single source of truth is now the component. - **Test cleanup.** `useSetSessionModelOverride` was removed alongside the obsolete `setSessionModelOverride` HTTP client; the matching `misc-mutations.test.tsx` cases that referenced it were dropped. - **Widened `patchAgent` body type** (`api.ts` + `mutations/agents.ts`) so the Schedule tab's "periodic" and "proactive" mutations typecheck. - **Rebased onto `origin/main`** (was 36 commits behind). `pnpm typecheck` — zero errors. `pnpm test src/lib/mutations/misc-mutations.test.tsx --run` — 6/6 pass. Other test failures in the suite (NotificationCenter / ApprovalsPage / ModelsPage / MobilePairingPage — 36 cases) reproduce identically on `origin/main` (verified in a worktree carrying no librefang#4963 changes) and are not introduced by this PR.
Add a `channels: Vec<String>` field to `AgentManifest` that restricts
which configured channels an agent can use. Empty = all channels
(backward-compatible default).
Backend wiring:
- `AgentManifest.channels` with serde default + vec_lenient
- `AgentRegistry::update_channels()` for in-memory mutation
- `LibreFangKernel::set_agent_channels()` with rollback on DB failure
- `KernelApi` trait + impl delegation
- `GET/PUT /api/agents/{id}/channels` endpoints with persist-to-disk
- OpenAPI spec registration + SDK regeneration
Dispatch enforcement:
- `ChannelBridgeHandle::agent_channel_allowlist()` trait method
- `KernelBridgeAdapter` impl via agent registry
- `resolve_or_fallback` enforces allowlist for both primary and fallback
agents — messages whose inbound channel is not in the non-empty
allowlist return None (silently dropped) with a debug! log
Namespace fix (review point 1):
Previously `get_agent_channels` built the `available` list from sidecar
display names (`sc.name`), but bridge enforcement compares against the
channel type string (`channel_type_str` → `sc.channel_type` or
`sc.name` as fallback). A sidecar with `name="my-tg-bot"
channel_type="telegram"` would surface "my-tg-bot" in available, the
operator would store that, and enforcement would compare against
"telegram" — never matching, so every inbound message was filtered out.
Fixed by emitting `sc.channel_type.as_deref().unwrap_or(sc.name)` in
both the GET response and deduplication, mirroring exactly how
`channel_type_str(&ChannelType::Custom(s))` resolves.
Scope creep removed (review point 2):
Dropped the unrelated `manifest.workspace = None` block from
`resolve_manifest` that was incorrectly bundled into this PR.
OpenAPI + SDKs regenerated (review point 3):
`openapi.json` regenerated via `cargo test -p librefang-api --test
openapi_spec_test` (319 paths); Python/JS/Go/Rust SDKs regenerated via
`scripts/codegen-sdks.py`.
Rebased onto current origin/main (review point 4):
Branch was ~49 commits behind; rebased cleanly. Conflict in
`routes/channels.rs` resolved by taking upstream's side (in-process
channel scaffolding deleted in librefang#5461).
Integration tests (7 cases):
- default all-mode, set allowlist, clear back to all-mode
- invalid id → 400, unknown id GET → 404, unknown id PUT → error
- namespace assertion: available uses channel_type not display name
Refs librefang#4926
f3901c1 to
2339349
Compare
…e with Save buttons Closes librefang#4917, librefang#4918, librefang#4924, librefang#4925. Adds an editable agent-detail panel with four tabs (Skills, MCP servers, Channels, Schedule) over the existing read-only view. Each tab keeps a local draft, an explicit Save button gated on dirty + non-pending state, and dedicated query/mutation hooks under `src/lib/{queries,mutations}/`. The Schedule tab delegates to a dedicated `AgentSchedulePanel` component that calls `patchAgent` with a typed `AgentSchedulePatch` body — the type covers all four schedule variants (`"reactive"`, `periodic`, `proactive`, `continuous`) instead of the narrow string/continuous shape that previously dropped the periodic and proactive cases on the floor. Addressing the review feedback on the previous round: - **Dashboard-only PR now.** The backend half (`set_agent_channels`, `registry.update_channels`, `AgentManifest.channels`, the channel routes, `openapi.json` and `sdk/` regen) is removed from this PR — it duplicates librefang#4961 and ships there. Every Rust file this PR used to touch is back at `origin/main`. - **Reconciled the half-merged Schedule tab.** Removed the duplicate `useMcpServers` import and the duplicate inline `renderScheduleTab` block that referenced helpers (`cronJobsQuery` / `agentTriggersQuery` / `formatPattern` / `CronJobItem`) the refactor renamed/extracted into `AgentSchedulePanel`. Single source of truth is now the component. - **Test cleanup.** `useSetSessionModelOverride` was removed alongside the obsolete `setSessionModelOverride` HTTP client; the matching `misc-mutations.test.tsx` cases that referenced it were dropped. - **Widened `patchAgent` body type** (`api.ts` + `mutations/agents.ts`) so the Schedule tab's "periodic" and "proactive" mutations typecheck. - **Rebased onto `origin/main`** (was 36 commits behind). `pnpm typecheck` — zero errors. `pnpm test src/lib/mutations/misc-mutations.test.tsx --run` — 6/6 pass. Other test failures in the suite (NotificationCenter / ApprovalsPage / ModelsPage / MobilePairingPage — 36 cases) reproduce identically on `origin/main` (verified in a worktree carrying no librefang#4963 changes) and are not introduced by this PR.
|
Rebased onto current |
Add channels field to AgentManifest restricting which configured channels an agent can use. Empty = all (backward-compatible default). Uses channel_type namespace consistently in API and enforcement. Refs: librefang#4961
|
Superseded by clean rewrite on feat/per-agent-channels-v3 — fixes namespace mismatch, removes scope creep, adds enforcement-site tests. |
Add channels field to AgentManifest restricting which configured channels an agent can use. Empty = all (backward-compatible default). Uses channel_type namespace consistently in API and enforcement. Refs: #4961
Summary
Fixes #4926
Adds a
channelsfield toAgentManifestthat restricts whichconfigured channels an agent can use. Empty list means all channels
(backward-compatible default).
AgentManifest.channels: Vec<String>withserde(default)+vec_lenientAgentRegistry::update_channels()for in-memory mutationLibreFangKernel::set_agent_channels()with rollback on DB persist failureKernelApitrait method + impl delegationGET /api/agents/{id}/channelsreturns assigned, available, and modePUT /api/agents/{id}/channelsupdates the allowlist and persists to diskconfigured_channel_names()helper in channels routeschannels: vec![]Test plan
cargo check -p librefang-api --lib— compiles cleancargo clippy -p librefang-api -p librefang-kernel -p librefang-types --lib -- -D warnings— zero warningscargo test -p librefang-api— integration tests pass{"assigned":[],"available":["telegram",...],"mode":"all"}for fresh agent{"channels":["telegram"]}persists and survives restart