Yes to both. Here's the full picture with code from the actual test harness:
hijackRun via RPC
hijackRun is a real RPC method — run:admin scope — that creates an elevated operator handoff session:
// packages/gateway/src/rpc/index.ts:123-132 type HijackRunRequest = { runId: string; options?: Record<string, unknown> }; type HijackRunResponse = { runId: string; status: "hijack-ready"; sessionId: string };
Over the wire (WebSocket): { "type": "req", "id": "h1", "method": "hijackRun", "params": { "runId": "run-abc" } }
It returns a sessionId but doesn't itself steer the run — it's the handshake step.
Steering/driving a paused or running workflow
Three RPC methods do this. All go over the same WebSocket framing:
- submitSignal — inject a prompt/payload into a waiting run
The workflow must be suspended at a waitForSignal / durable-deferred point. This is how you "prompt" it.
// Scope: "signal:submit" { runId: "run-abc", correlationKey: "user:prompt:turn-2", // must match what the workflow is waiting on signalName: "user.message", payload: { text: "continue with option B" } }
From e2e/faults/case14-gateway-rpc-roundtrip.test.ts:712-735: const sent = await operator.request("submitSignal", { runId, correlationKey: "deploy:abc", signalName: "deploy.approved", payload: { branch: "main" }, }); // → { runId, signalName, correlationId, seq }
- submitApproval — unblock a human-in-the-loop gate
// Scope: "approval:submit" const approved = await operator.request("submitApproval", { runId, nodeId: "wait-deploy", iteration: 0, decision: { approved: true, note: "looks good" }, }); // Flips node state: waiting-approval → pending, run: waiting-approval → running
- resumeRun — kick a stale/interrupted run
// Scope: "run:write" const r = await operator.request("resumeRun", { runId: staleRunId }); // → { status: "resume_requested" } or { status: "already_terminal" }
"Ended"/terminal runs
If status is finished, failed, or cancelled, resumeRun returns "already_terminal" and submitSignal/submitApproval will reject with RunNotFound or state errors. For those you need a new launchRun. Only paused states (waiting-event, waiting-approval, stale) are steerable in-place.
WebSocket wire protocol
All of the above share the same framing (e2e/faults/case14-gateway-rpc-roundtrip.test.ts:487-538):
// 1. open ws // 2. connect handshake ws.send(JSON.stringify({ type: "req", id: "c1", method: "connect", params: { auth: { token: "your-token" } } })); // 3. then any method ws.send(JSON.stringify({ type: "req", id: "s1", method: "submitSignal", params: { runId, correlationKey, signalName, payload } })); // response: { type: "res", id: "s1", ok: true, payload: { ... } }