Skip to content

feat(types,kernel,api): per-agent channel allowlist#4961

Closed
DaBlitzStein wants to merge 2 commits into
librefang:mainfrom
DaBlitzStein:feat/per-agent-channels-backend
Closed

feat(types,kernel,api): per-agent channel allowlist#4961
DaBlitzStein wants to merge 2 commits into
librefang:mainfrom
DaBlitzStein:feat/per-agent-channels-backend

Conversation

@DaBlitzStein

@DaBlitzStein DaBlitzStein commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4926

Adds a channels field to AgentManifest that restricts which
configured channels an agent can use. Empty list means all channels
(backward-compatible default).

  • AgentManifest.channels: Vec<String> with serde(default) + vec_lenient
  • AgentRegistry::update_channels() for in-memory mutation
  • LibreFangKernel::set_agent_channels() with rollback on DB persist failure
  • KernelApi trait method + impl delegation
  • GET /api/agents/{id}/channels returns assigned, available, and mode
  • PUT /api/agents/{id}/channels updates the allowlist and persists to disk
  • configured_channel_names() helper in channels routes
  • OpenAPI spec registration
  • Wizard default: channels: vec![]

Test plan

  • cargo check -p librefang-api --lib — compiles clean
  • cargo clippy -p librefang-api -p librefang-kernel -p librefang-types --lib -- -D warnings — zero warnings
  • cargo test -p librefang-api — integration tests pass
  • GET returns {"assigned":[],"available":["telegram",...],"mode":"all"} for fresh agent
  • PUT with {"channels":["telegram"]} persists and survives restart

@github-actions github-actions Bot added size/L 250-999 lines changed area/kernel Core kernel (scheduling, RBAC, workflows) labels May 12, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from fdaba63 to 1a85a20 Compare May 12, 2026 11:08
@github-actions github-actions Bot added size/M 50-249 lines changed and removed size/L 250-999 lines changed labels May 12, 2026
@DaBlitzStein DaBlitzStein changed the title feat(api,kernel): per-agent channel allowlist with GET/PUT endpoints feat(types,kernel,api): per-agent channel allowlist May 12, 2026
@DaBlitzStein

Copy link
Copy Markdown
Contributor Author

Pausing this PR — currently under testing in our integration branch. Will resume once validation completes.

@DaBlitzStein
DaBlitzStein marked this pull request as draft May 13, 2026 06:39
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 3b2c58b to bfc22e6 Compare May 13, 2026 18:10
@github-actions github-actions Bot added size/L 250-999 lines changed and removed size/M 50-249 lines changed labels May 13, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch 2 times, most recently from 4a0b78f to 2d4d796 Compare May 14, 2026 11:56
@github-actions github-actions Bot added size/M 50-249 lines changed and removed size/L 250-999 lines changed labels May 14, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 6c1f0cc to 8b25885 Compare May 14, 2026 14:43
@github-actions github-actions Bot added size/L 250-999 lines changed and removed size/M 50-249 lines changed labels May 14, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 8b25885 to a28fc7e Compare May 14, 2026 15:52
@github-actions github-actions Bot added size/M 50-249 lines changed has-conflicts PR has merge conflicts that need resolution and removed size/L 250-999 lines changed labels May 14, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 90316a1 to effd0fe Compare May 16, 2026 23:43
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review size/L 250-999 lines changed and removed has-conflicts PR has merge conflicts that need resolution size/M 50-249 lines changed ready-for-review PR is ready for maintainer review size/L 250-999 lines changed labels May 16, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 367a865 to ebc6a60 Compare May 21, 2026 16:31
@DaBlitzStein

Copy link
Copy Markdown
Contributor Author

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.

@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 21, 2026

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

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.)

@github-actions github-actions Bot added needs-changes Changes requested by reviewer and removed ready-for-review PR is ready for maintainer review labels May 22, 2026
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from 18621c4 to fc5cfba Compare May 22, 2026 20:14
@DaBlitzStein

Copy link
Copy Markdown
Contributor Author

All 4 review points addressed. Branch rebased onto current origin/main (was ~49 commits behind) and squashed to a single clean commit (fc5cfba52).

1. Namespace mismatch fix (correctness bug)

get_agent_channels previously built the available list from sc.name (sidecar display names). Bridge enforcement in resolve_or_fallback calls channel_type_str which resolves to sc.channel_type (or sc.name when channel_type is None — mirroring ChannelType::Custom(s) => s.as_str()). A sidecar with name="my-tg-bot" channel_type="telegram" would surface "my-tg-bot" in available; the operator stores that; enforcement compares against "telegram" → never matches → every inbound message silently filtered out.

Fixed by emitting sc.channel_type.as_deref().unwrap_or(sc.name.as_str()) in get_agent_channels, with dedup via HashSet. Evidence chain: bridge.rs:2079 channel_type_str for Custom(s) returns s.as_str(); types.rs:2170 SidecarChannelConfig::channel_type: Option<String> defaults to None (falls back to name when unset). The expression mirrors that fallback exactly.

A new integration test test_get_agent_channels_available_uses_channel_type_namespace boots a kernel with name="my-tg-bot" channel_type="telegram" and name="discord" channel_type=None, then asserts available contains "telegram" (not "my-tg-bot") and "discord".

2. Scope creep removed

The manifest.workspace = None block in resolve_manifest (lines 329–334) is gone. Not part of this PR. If it's a real fix it should land separately.

3. OpenAPI + SDKs regenerated

openapi.json regenerated via cargo test -p librefang-api --test openapi_spec_test (319 paths written). All 4 SDKs regenerated via scripts/codegen-sdks.py: Python (1336 lines), JS (1710 lines), Go (1798 lines), Rust (2005 lines). xtask/baselines/openapi.sha256 auto-updated by the pre-commit hook.

4. Rebase

Rebased onto origin/main. The conflict in routes/channels.rs was resolved by taking upstream's side — the in-process channel scaffolding (CHANNEL_REGISTRY, FieldType, configured_channel_names, etc.) deleted in upstream was kept deleted.

Test results

  • cargo check --workspace --lib --exclude librefang-desktop: clean
  • cargo clippy --workspace --all-targets --exclude librefang-desktop -- -D warnings: clean
  • cargo test -p librefang-channels: 3 passed
  • cargo test -p librefang-api --test agent_channels_routes_test: 7 passed (6 original + 1 new namespace test)
  • cargo test -p librefang-api --test openapi_spec_test: 1 passed

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

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).

@github-actions github-actions Bot added the area/sdk JavaScript and Python SDKs label May 22, 2026
DaBlitzStein added a commit to DaBlitzStein/librefang that referenced this pull request May 22, 2026
…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.
@github-actions github-actions Bot added the has-conflicts PR has merge conflicts that need resolution label May 23, 2026
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
@DaBlitzStein
DaBlitzStein force-pushed the feat/per-agent-channels-backend branch from f3901c1 to 2339349 Compare May 23, 2026 22:11
DaBlitzStein added a commit to DaBlitzStein/librefang that referenced this pull request May 23, 2026
…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.
@github-actions github-actions Bot removed the has-conflicts PR has merge conflicts that need resolution label May 23, 2026
@DaBlitzStein

Copy link
Copy Markdown
Contributor Author

Rebased onto current origin/main (debb31501). All 4 review points from the re-review were already addressed in the prior push (namespace mismatch fixed with sc.channel_type.as_deref().unwrap_or(sc.name.as_str()), scope-creep manifest.workspace=None removed, openapi+SDKs regenerated, enforcement-site integration test added). The stale HEAD was caused by GitHub's auto-merge-from-main; this push restores the clean single-commit rebase.

DaBlitzStein added a commit to DaBlitzStein/librefang that referenced this pull request May 23, 2026
@github-actions github-actions Bot added the has-conflicts PR has merge conflicts that need resolution label May 24, 2026
DaBlitzStein added a commit to DaBlitzStein/librefang that referenced this pull request May 25, 2026
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
@DaBlitzStein

Copy link
Copy Markdown
Contributor Author

Superseded by clean rewrite on feat/per-agent-channels-v3 — fixes namespace mismatch, removes scope creep, adds enforcement-site tests.

houko pushed a commit that referenced this pull request May 28, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/channels Messaging channel adapters area/kernel Core kernel (scheduling, RBAC, workflows) area/sdk JavaScript and Python SDKs has-conflicts PR has merge conflicts that need resolution needs-changes Changes requested by reviewer size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(channels): N:M agent-channel assignment — replace single default_agent with multi-agent routing

2 participants