/** * src/extension/rpc.ts — B7: cross-extension RPC over `pi.events`. * * Lets OTHER Pi extensions drive this one (mirror of the tintinweb * `cross-extension-rpc.ts` pattern, benchmark §4h): * * subagents:rpc — method envelope: {requestId, method, params} * subagents:rpc:ping — direct channel: {requestId} * subagents:rpc:spawn — direct channel: {requestId, agent, task, ...} * subagents:rpc:stop — direct channel: {requestId, run_id} * * Every request replies EXACTLY ONCE on the requestId-scoped channel * `subagents:rpc:reply:` with a standardized envelope: * {success:true, requestId, data} on success * {success:false, requestId, error} on refusal/unknown/failure * A payload without a usable requestId is reported on the fixed * `subagents:rpc:reply:_malformed` channel (no scope to reply into). * * SAFETY: `spawn` delegates to the SAME DispatchEngine preflight as the * delegate tools (six-part contract, write-scope, path policy, model gate) — * there is no bypass. Replies are hash-only/body-free: run metadata and * outputHash, NEVER the child output. `stop` mirrors the FleetView abort * (monitor-level abort of a queued/running run) and emits `subagents:aborted`. * * Zero @earendil-works/* imports (I9); no-op when the host has no `pi.events`. */ import type { EventSink } from "./events.js"; import type { SubagentsEventBus } from "./events.js"; import type { ExtensionAPI } from "./pi-types.js"; import type { GetRuntime } from "./tools.js"; /** Shared method-envelope channel (tintinweb parity). */ export declare const RPC_CHANNEL = "subagents:rpc"; /** Direct per-method channels. */ export declare const RPC_PING_CHANNEL = "subagents:rpc:ping"; export declare const RPC_SPAWN_CHANNEL = "subagents:rpc:spawn"; export declare const RPC_STOP_CHANNEL = "subagents:rpc:stop"; /** Version of this RPC protocol (bump on breaking envelope changes). */ export declare const RPC_PROTOCOL_VERSION = 1; /** Fallback reply channel for requests without a usable requestId. */ export declare const RPC_MALFORMED_REQUEST_ID = "_malformed"; /** Reply channel scoped by requestId: `subagents:rpc:reply:`. */ export declare function rpcReplyChannel(requestId: string): string; /** Standardized success envelope. */ export interface RpcSuccessEnvelope { success: true; requestId: string; data: Record; } /** Standardized error envelope. */ export interface RpcErrorEnvelope { success: false; requestId: string; error: string; } export type RpcEnvelope = RpcSuccessEnvelope | RpcErrorEnvelope; /** RPC methods exposed by this extension. */ export declare const RPC_METHODS: readonly ["ping", "spawn", "stop"]; export type RpcMethod = (typeof RPC_METHODS)[number]; /** Dependencies: reply sink, session runtime accessor, optional lifecycle bus. */ export interface SubagentsRpcDeps { sink: EventSink; getRuntime: GetRuntime; eventBus?: SubagentsEventBus; } /** * Register the RPC handlers on `pi.events`. Subscribes to the shared channel * plus the three direct per-method channels; each request replies exactly * once on its scoped reply channel. No-op when the host exposes no bus. */ export declare function registerSubagentsRpc(pi: ExtensionAPI, deps: Omit): void; /** Envelope before the reply closure stamps the scoped requestId. */ export type RpcPreEnvelope = { success: true; data: Record; } | { success: false; error: string; }; /** Core dispatcher: validate scope, route the method, reply exactly once. */ export declare function handleRpcPayload(deps: SubagentsRpcDeps, payload: unknown, directMethod?: RpcMethod): void;