fix(plugin-sdk): preserve runtime contexts in test mocks (#107059)

* test(plugin-sdk): make runtime context mock stateful

* docs(agents): record Testbox invocation contracts
This commit is contained in:
Peter Steinberger
2026-07-13 21:21:09 -07:00
committed by GitHub
parent 66e6923929
commit be5558cb4e
3 changed files with 38 additions and 8 deletions

View File

@@ -172,6 +172,8 @@ Skills own workflows; root owns hard policy and routing.
- PR refs: `gh pr view/diff` or `gh api`, not web search. Prefer `gitcrawl` for maintainer discovery; missing/stale `gitcrawl` falls through to live `gh`, not contributor setup. Verify live with `gh` before mutation.
- `gh pr view` takes the branch positionally; no `--head` flag.
- zsh: quote `gh api` endpoints containing `?` or brackets; otherwise glob expansion corrupts the invocation.
- Blacksmith Testbox status/stop: `--id <tbx_id>`; no status JSON flag.
- Crabbox final timing JSON = proof complete; if portal sync hangs after it, interrupt wrapper only.
- GitHub Actions: resolve workflow files from `.github/workflows` or API; never infer filenames from display names.
- zsh: quote command globs; unmatched patterns abort before the tool runs.
- zsh: don't use `path` as a variable; it rewrites `$PATH`.

View File

@@ -49,6 +49,32 @@ describe("createPluginRuntimeMock", () => {
expect(vi.isMockFunction(debouncer.cancelKey)).toBe(true);
});
it("tracks channel runtime contexts through the mock registry", () => {
const runtime = createPluginRuntimeMock();
const key = {
channelId: "whatsapp",
accountId: "default",
capability: "connection-controller",
};
const context = { getActiveListener: vi.fn() };
const onEvent = vi.fn();
const unsubscribe = runtime.channel.runtimeContexts.watch({ ...key, onEvent });
const lease = runtime.channel.runtimeContexts.register({ ...key, context });
expect(runtime.channel.runtimeContexts.get(key)).toBe(context);
expect(onEvent).toHaveBeenCalledWith({ type: "registered", key, context });
expect(vi.isMockFunction(runtime.channel.runtimeContexts.register)).toBe(true);
expect(vi.isMockFunction(runtime.channel.runtimeContexts.get)).toBe(true);
expect(vi.isMockFunction(runtime.channel.runtimeContexts.watch)).toBe(true);
lease.dispose();
expect(runtime.channel.runtimeContexts.get(key)).toBeUndefined();
expect(onEvent).toHaveBeenLastCalledWith({ type: "unregistered", key });
unsubscribe();
});
it("exposes channel inbound helpers without the removed turn aliases", async () => {
const runtime = createPluginRuntimeMock();
const channel = "test";

View File

@@ -5,6 +5,7 @@ import {
sanitizeInboundSystemTags,
} from "../../auto-reply/reply/inbound-text.js";
import { resolveSessionEntryResetFreshness } from "../../config/sessions/entry-freshness.js";
import { createChannelRuntimeContextRegistry } from "../../plugins/runtime/channel-runtime-contexts.js";
import {
implicitMentionKindWhen,
resolveInboundMentionDecision,
@@ -142,6 +143,7 @@ export function createPluginRuntimeMediaMock(
}
export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> = {}): PluginRuntime {
const runtimeContexts = createChannelRuntimeContextRegistry();
const runEmbeddedAgentMock = vi.fn().mockResolvedValue({
payloads: [],
meta: {},
@@ -773,14 +775,14 @@ export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> =
vi.fn() as unknown as PluginRuntime["channel"]["threadBindings"]["setMaxAgeBySessionKey"],
},
runtimeContexts: {
register: vi.fn(({ abortSignal }: { abortSignal?: AbortSignal }) => {
const lease = { dispose: vi.fn() };
abortSignal?.addEventListener("abort", lease.dispose, { once: true });
return lease;
}) as unknown as PluginRuntime["channel"]["runtimeContexts"]["register"],
get: vi.fn() as unknown as PluginRuntime["channel"]["runtimeContexts"]["get"],
watch: vi.fn(() =>
vi.fn(),
register: vi.fn(
runtimeContexts.register,
) as unknown as PluginRuntime["channel"]["runtimeContexts"]["register"],
get: vi.fn(
runtimeContexts.get,
) as unknown as PluginRuntime["channel"]["runtimeContexts"]["get"],
watch: vi.fn(
runtimeContexts.watch,
) as unknown as PluginRuntime["channel"]["runtimeContexts"]["watch"],
},
activity: {} as PluginRuntime["channel"]["activity"],