diff --git a/src/commands/agent.delivery.test.ts b/src/commands/agent.delivery.test.ts index baa44213ab4..e13cf219966 100644 --- a/src/commands/agent.delivery.test.ts +++ b/src/commands/agent.delivery.test.ts @@ -48,6 +48,7 @@ describe("deliverAgentCommandResult", () => { async function runDelivery(params: { opts: Record; + outboundSession?: { key?: string; agentId?: string }; sessionEntry?: SessionEntry; runtime?: RuntimeEnv; resultText?: string; @@ -62,6 +63,7 @@ describe("deliverAgentCommandResult", () => { deps, runtime, opts: params.opts as never, + outboundSession: params.outboundSession, sessionEntry: params.sessionEntry, result, payloads: result.payloads, @@ -234,6 +236,30 @@ describe("deliverAgentCommandResult", () => { ); }); + it("uses caller-provided outbound session context when opts.sessionKey is absent", async () => { + await runDelivery({ + opts: { + message: "hello", + deliver: true, + channel: "whatsapp", + to: "+15551234567", + }, + outboundSession: { + key: "agent:exec:hook:gmail:thread-1", + agentId: "exec", + }, + }); + + expect(mocks.deliverOutboundPayloads).toHaveBeenCalledWith( + expect.objectContaining({ + session: expect.objectContaining({ + key: "agent:exec:hook:gmail:thread-1", + agentId: "exec", + }), + }), + ); + }); + it("prefixes nested agent outputs with context", async () => { const runtime = createRuntime(); await runDelivery({ diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 3da6935b8ef..038e9651777 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -14,6 +14,7 @@ import { setActivePluginRegistry } from "../plugins/runtime.js"; import type { RuntimeEnv } from "../runtime.js"; import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js"; import { agentCommand } from "./agent.js"; +import * as agentDeliveryModule from "./agent/delivery.js"; vi.mock("../agents/auth-profiles.js", async (importOriginal) => { const actual = await importOriginal(); @@ -49,6 +50,7 @@ const runtime: RuntimeEnv = { const configSpy = vi.spyOn(configModule, "loadConfig"); const runCliAgentSpy = vi.spyOn(cliRunnerModule, "runCliAgent"); +const deliverAgentCommandResultSpy = vi.spyOn(agentDeliveryModule, "deliverAgentCommandResult"); async function withTempHome(fn: (home: string) => Promise): Promise { return withTempHomeBase(fn, { prefix: "openclaw-agent-" }); @@ -230,6 +232,35 @@ describe("agentCommand", () => { }); }); + it("forwards resolved outbound session context when resuming by sessionId", async () => { + await withTempHome(async (home) => { + const storePattern = path.join(home, "sessions", "{agentId}", "sessions.json"); + const execStore = path.join(home, "sessions", "exec", "sessions.json"); + writeSessionStoreSeed(execStore, { + "agent:exec:hook:gmail:thread-1": { + sessionId: "session-exec-hook", + updatedAt: Date.now(), + systemSent: true, + }, + }); + mockConfig(home, storePattern, undefined, undefined, [ + { id: "dev" }, + { id: "exec", default: true }, + ]); + + await agentCommand({ message: "resume me", sessionId: "session-exec-hook" }, runtime); + + const deliverCall = deliverAgentCommandResultSpy.mock.calls.at(-1)?.[0]; + expect(deliverCall?.opts.sessionKey).toBeUndefined(); + expect(deliverCall?.outboundSession).toEqual( + expect.objectContaining({ + key: "agent:exec:hook:gmail:thread-1", + agentId: "exec", + }), + ); + }); + }); + it("resolves resumed session transcript path from custom session store directory", async () => { await withTempHome(async (home) => { const customStoreDir = path.join(home, "custom-state"); diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 63741b559d0..9d869a0f5d1 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -59,6 +59,7 @@ import { emitAgentEvent, registerAgentRunContext, } from "../infra/agent-events.js"; +import { buildOutboundSessionContext } from "../infra/outbound/session-context.js"; import { getRemoteSkillEligibility } from "../infra/skills-remote.js"; import { normalizeAgentId } from "../routing/session-key.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; @@ -316,6 +317,11 @@ export async function agentCommand( sessionKey: sessionKey ?? opts.sessionKey?.trim(), config: cfg, }); + const outboundSession = buildOutboundSessionContext({ + cfg, + agentId: sessionAgentId, + sessionKey, + }); const workspaceDirRaw = resolveAgentWorkspaceDir(cfg, sessionAgentId); const agentDir = resolveAgentDir(cfg, sessionAgentId); const workspace = await ensureAgentWorkspace({ @@ -461,6 +467,7 @@ export async function agentCommand( deps, runtime, opts, + outboundSession, sessionEntry, result, payloads, @@ -809,6 +816,7 @@ export async function agentCommand( deps, runtime, opts, + outboundSession, sessionEntry, result, payloads, diff --git a/src/commands/agent/delivery.ts b/src/commands/agent/delivery.ts index af31d68ca6d..30ac335577a 100644 --- a/src/commands/agent/delivery.ts +++ b/src/commands/agent/delivery.ts @@ -16,7 +16,7 @@ import { normalizeOutboundPayloads, normalizeOutboundPayloadsForJson, } from "../../infra/outbound/payloads.js"; -import { buildOutboundSessionContext } from "../../infra/outbound/session-context.js"; +import type { OutboundSessionContext } from "../../infra/outbound/session-context.js"; import type { RuntimeEnv } from "../../runtime.js"; import { isInternalMessageChannel } from "../../utils/message-channel.js"; import type { AgentCommandOpts } from "./types.js"; @@ -64,11 +64,12 @@ export async function deliverAgentCommandResult(params: { deps: CliDeps; runtime: RuntimeEnv; opts: AgentCommandOpts; + outboundSession: OutboundSessionContext | undefined; sessionEntry: SessionEntry | undefined; result: RunResult; payloads: RunResult["payloads"]; }) { - const { cfg, deps, runtime, opts, sessionEntry, payloads, result } = params; + const { cfg, deps, runtime, opts, outboundSession, sessionEntry, payloads, result } = params; const deliver = opts.deliver === true; const bestEffortDeliver = opts.bestEffortDeliver === true; const turnSourceChannel = opts.runContext?.messageChannel ?? opts.messageChannel; @@ -212,18 +213,13 @@ export async function deliverAgentCommandResult(params: { } if (deliver && deliveryChannel && !isInternalMessageChannel(deliveryChannel)) { if (deliveryTarget) { - const deliverySession = buildOutboundSessionContext({ - cfg, - agentId: opts.agentId, - sessionKey: opts.sessionKey, - }); await deliverOutboundPayloads({ cfg, channel: deliveryChannel, to: deliveryTarget, accountId: resolvedAccountId, payloads: deliveryPayloads, - session: deliverySession, + session: outboundSession, replyToId: resolvedReplyToId ?? null, threadId: resolvedThreadTarget ?? null, bestEffort: bestEffortDeliver,