fix: omit live deliveryContext when targeting a remote gateway

resolveGatewayWriteMeta() was forwarding the local agent run's
deliveryContext to config.apply/config.patch/update.run even when
gatewayUrl pointed to a remote gateway. Server handlers now prefer
params.deliveryContext over extractDeliveryInfo(sessionKey), so a
remote restart sentinel would be written with the local chat's channel/
to, causing post-restart wake messages to be delivered to the caller's
chat instead of the session that lives on the remote gateway.

Fix: gate deliveryContext forwarding on isRemoteGateway (truthy
gatewayOpts.gatewayUrl). When targeting a remote gateway, omit
deliveryContext so the remote server's extractDeliveryInfo(sessionKey)
remains authoritative for the routing of that session. See #18612.
This commit is contained in:
Bryan Marty 2026-03-09 13:42:52 +00:00
parent 80538c607d
commit 33c24858ff
No known key found for this signature in database
2 changed files with 34 additions and 1 deletions

View File

@ -168,6 +168,31 @@ describe("createGatewayTool live delivery context guard", () => {
expect(sentinelPayload?.deliveryContext?.to).toBe("123456789");
});
it("does not forward live RPC delivery context when gatewayUrl targets a remote gateway", async () => {
// A remote gateway has its own extractDeliveryInfo(sessionKey) — forwarding
// the local agent's deliveryContext would write a sentinel with the wrong
// destination on the remote host.
mocks.callGatewayTool.mockClear();
mocks.readGatewayCallOptions.mockReturnValueOnce({ gatewayUrl: "wss://remote-gw.example.com" });
const tool = createGatewayTool({
agentSessionKey: "agent:main:main",
agentChannel: "discord",
agentTo: "123456789",
});
await execTool(tool, {
action: "config.patch",
raw: '{"key":"value"}',
baseHash: "abc123",
sessionKey: "agent:main:main",
note: "remote patch",
gatewayUrl: "wss://remote-gw.example.com",
});
const forwardedParams = getCallArg<Record<string, unknown>>(mocks.callGatewayTool, 0, 2);
expect(forwardedParams?.deliveryContext).toBeUndefined();
});
it("does not forward live RPC delivery context when a non-default agent passes sessionKey='main'", async () => {
// "main" resolves to "agent:main:main" (default agent), which differs from the
// current session "agent:shopping-claw:main". Live context must NOT be forwarded.

View File

@ -268,7 +268,15 @@ export function createGatewayTool(opts?: {
explicitSessionKey != null &&
rpcCanonicalizeTarget(explicitSessionKey) !==
(rpcOwnKey ? rpcCanonicalizeOwn(rpcOwnKey) : undefined);
const deliveryContext = isTargetingOtherSession ? undefined : liveDeliveryContextForRpc;
// Also omit when the call targets a remote gateway. The remote server's
// extractDeliveryInfo(sessionKey) is the authoritative source for that
// session's delivery route. Forwarding the local agent run's deliveryContext
// would write a sentinel with the wrong chat destination on the remote host,
// causing post-restart wake messages to be sent to the caller's chat instead
// of the session on the remote gateway. See #18612.
const isRemoteGateway = Boolean(gatewayOpts.gatewayUrl?.trim());
const deliveryContext =
isTargetingOtherSession || isRemoteGateway ? undefined : liveDeliveryContextForRpc;
return { sessionKey, note, restartDelayMs, deliveryContext };
};