← Back to PRs

#17666: feat(plugins): expose steerSession and isSessionStreaming on plugin API

by mconcat open 2026-02-16 02:19 View on GitHub →
stale size: S
--- ## Summary Add two new methods to `OpenClawPluginApi` for agent-to-agent steering: - `steerSession(sessionKey, text)`: inject a message into an active streaming session between tool calls. Returns `true` if queued. - `isSessionStreaming(sessionKey)`: check if a session is currently streaming. These wrap the internal `queueEmbeddedPiMessage`/`isEmbeddedPiRunStreaming` functions with sessionKey→sessionId resolution, so plugins don't need to know internal session IDs. Use case: multi-agent plugins can steer agents that are actively streaming when another agent posts to a shared channel, solving the stale-context problem without workarounds. - **Problem:** Plugins have no way to inject messages into an actively streaming agent session. The internal `queueEmbeddedPiMessage()` exists but is not exposed through the plugin API. Multi-agent plugins (e.g. agent swarms where agents communicate through shared channels) cannot notify a streaming agent about new messages from other agents, causing stale-context issues where the agent completes its response without seeing the latest input. - **Why it matters:** Without steer access, the only workarounds are: (1) waiting for the agent to finish and then sending a follow-up (high latency), or (2) building a full A2A transport layer that duplicates internal OpenClaw functionality. Direct steer access enables real-time agent-to-agent coordination with zero additional infrastructure. - **What changed:** Two new methods added to `OpenClawPluginApi` type and implemented in `createPluginRegistry`. A `resolveSessionId()` helper bridges the gap between canonical session keys (what plugins know) and internal session IDs (what the embedded runner uses). No existing methods, types, or behavior were modified. - **What did NOT change (scope boundary):** No changes to the embedded runner itself, session store format, queue modes, or any existing plugin API methods. The `queueEmbeddedPiMessage` semantics are preserved exactly — steer only works when the session is actively streaming and not compacting. ## Change Type (select all) - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Docs - [ ] Security hardening - [ ] Chore/infra ## Scope (select all touched areas) - [ ] Gateway / orchestration - [ ] Skills / tool execution - [ ] Auth / tokens - [ ] Memory / storage - [ ] Integrations - [x] API / contracts - [ ] UI / DX - [ ] CI/CD / infra ## Linked Issue/PR - Related: N/A (no existing issue; motivated by flock multi-agent plugin development) ## User-visible / Behavior Changes None — these are plugin API additions only. No existing behavior, defaults, or config are affected. Plugins that don't call the new methods see zero difference. ## Security Impact (required) - New permissions/capabilities? **Yes** — plugins can now inject text into other agents' streaming sessions. - Secrets/tokens handling changed? **No** - New/changed network calls? **No** — purely in-process; no network involved. - Command/tool execution surface changed? **No** — steered text goes through the same `activeSession.steer()` path as existing inbound messages. - Data access scope changed? **No** — plugins already have access to `config` which contains agent IDs and session keys. - **Risk + mitigation:** A malicious plugin could steer arbitrary sessions. Mitigation: (1) plugin loading is already trust-gated (only admin-configured plugins load), (2) `queueEmbeddedPiMessage` internally validates the session is streaming and not compacting before accepting, (3) the steered text follows the same processing path as any other inbound message — no privilege escalation. ## Repro + Verification ### Environment - OS: Linux 6.17.0-14 (x64) - Runtime/container: Node v22.22.0 - Model/provider: N/A (API surface change, no model interaction) - Integration/channel: N/A - Relevant config: Any OpenClaw config with plugins enabled ### Steps 1. Create a plugin that calls `api.steerSession("agent:coder:discord:channel:123", "new message from reviewer")` 2. Have the target agent actively streaming (processing a prompt) 3. Observe the steered text is injected between tool calls ### Expected - `steerSession` returns `true`, agent sees injected message in its next tool-call boundary - `isSessionStreaming` returns `true` while agent is streaming, `false` otherwise - Both return `false` gracefully when session doesn't exist or isn't streaming ### Actual - Not yet verified with live plugin integration (see Human Verification) ## Evidence - [x] Trace/log snippets `queueEmbeddedPiMessage` emits diagnostic logs on success/failure: ``` queue message failed: sessionId=xxx reason=no_active_run queue message failed: sessionId=xxx reason=not_streaming queue message failed: sessionId=xxx reason=compacting ``` `resolveSessionId` returns `undefined` when session key not found → both methods return `false` without side effects. ## Human Verification (required) - **Verified scenarios:** Import resolution correct (all imported symbols exist in their source modules). Type compatibility confirmed — `steerSession` and `isSessionStreaming` signatures match `OpenClawPluginApi` type. `loadSessionStore` returns `Record<string, SessionEntry>` where `SessionEntry.sessionId` is the key used by `ACTIVE_EMBEDDED_RUNS`. - **Edge cases checked:** Missing session key → `resolveSessionId` returns `undefined` → methods return `false`. Session exists but not streaming → `queueEmbeddedPiMessage` returns `false`. Session compacting → `queueEmbeddedPiMessage` returns `false`. - **What you did NOT verify:** Full build (pnpm install + tsc) — deps not installed in local checkout. Live end-to-end test with an actual plugin calling the API during streaming. ## Compatibility / Migration - Backward compatible? **Yes** — additive API only; no existing signatures changed. - Config/env changes? **No** - Migration needed? **No** ## Failure Recovery (if this breaks) - **How to disable/revert quickly:** Revert the single commit. Or: plugins simply don't call the new methods — they are opt-in. - **Files/config to restore:** `src/plugins/types.ts`, `src/plugins/registry.ts` - **Known bad symptoms:** If `loadSessionStore` or `resolveDefaultSessionStorePath` throws for an unexpected agent ID, `steerSession`/`isSessionStreaming` would throw up to the calling plugin. Low risk — these functions are battle-tested in the existing codebase. ## Risks and Mitigations - **Risk:** `loadSessionStore` reads from disk on every call (with TTL cache). High-frequency `steerSession` calls could add I/O pressure. - **Mitigation:** `loadSessionStore` already has mtime-based caching with `structuredClone`. In practice, steer calls happen at most once per tool-call boundary per channel post — well within cache TTL. - **Risk:** Session store key format mismatch — if a plugin passes a non-canonical key, `resolveSessionId` silently returns `undefined`. - **Mitigation:** This is the correct fail-safe behavior (returns `false`). Documented in JSDoc that the key must be canonical. <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds two new methods to the `OpenClawPluginApi` for agent-to-agent session steering: - `steerSession(sessionKey, text)` — injects a message into an active streaming session between tool calls, returning `true` if successfully queued - `isSessionStreaming(sessionKey)` — checks if a session is currently streaming Both methods use a new private `resolveSessionId` helper that bridges canonical session keys (what plugins know) to internal session IDs (what the embedded runner uses) via `resolveAgentIdFromSessionKey` → `resolveDefaultSessionStorePath` → `loadSessionStore`. - The implementation correctly handles all failure paths: missing session key, non-existent session, session not streaming, and session currently compacting — all return `false` without side effects - `loadSessionStore` performs synchronous disk I/O but has TTL-based mtime caching (default 45s), appropriate for the expected call frequency (once per tool-call boundary) - No existing methods, types, or behavior were modified — this is a purely additive change - No tests were added for the new functionality <h3>Confidence Score: 4/5</h3> - This PR is safe to merge — additive API surface only, with correct fail-safe behavior on all error paths. - Score of 4 reflects that the implementation is clean, type-safe, and follows existing codebase patterns. The resolution chain (session key → agent ID → store path → session store → sessionId) correctly handles all edge cases. All imported functions have been verified to not throw unexpected exceptions. The only reason this isn't a 5 is the absence of unit tests for the new methods, and the fact that end-to-end verification with an actual plugin has not been performed. - No files require special attention. Both changes are straightforward and well-contained. <sub>Last reviewed commit: 34f4f02</sub> <!-- greptile_other_comments_section --> <sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs