Compare commits
2 Commits
vincentkoc
...
fix/cron-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f5a1569f5 | ||
|
|
da440003e5 |
@@ -184,6 +184,71 @@ describe("runCronIsolatedAgentTurn", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses cron origin session for unpinned announce fallback and implicit delivery resolution", async () => {
|
||||
await withTempCronHome(async (home) => {
|
||||
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
|
||||
const originSessionKey = "agent:main:bluebubbles:direct:+19257864429";
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
"agent:main:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "telegram",
|
||||
lastTo: "999",
|
||||
},
|
||||
[originSessionKey]: {
|
||||
sessionId: "origin-session",
|
||||
updatedAt: Date.now(),
|
||||
lastChannel: "telegram",
|
||||
lastTo: "123",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
const deps = createCliDeps();
|
||||
mockAgentPayloads([{ text: "Final weather summary" }]);
|
||||
|
||||
const res = await runCronIsolatedAgentTurn({
|
||||
cfg: makeCfg(home, storePath, {
|
||||
session: {
|
||||
store: storePath,
|
||||
mainKey: "main",
|
||||
dmScope: "per-channel-peer",
|
||||
},
|
||||
channels: {
|
||||
telegram: { botToken: "t-1" },
|
||||
},
|
||||
}),
|
||||
deps,
|
||||
job: {
|
||||
...makeJob({ kind: "agentTurn", message: "do it" }),
|
||||
sessionKey: originSessionKey,
|
||||
delivery: { mode: "announce" },
|
||||
},
|
||||
message: "do it",
|
||||
sessionKey: "cron:job-1",
|
||||
lane: "cron",
|
||||
});
|
||||
|
||||
expect(res.status).toBe("ok");
|
||||
expect(runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
|
||||
const announceArgs = vi.mocked(runSubagentAnnounceFlow).mock.calls[0]?.[0] as
|
||||
| {
|
||||
requesterSessionKey?: string;
|
||||
requesterOrigin?: { channel?: string; to?: string };
|
||||
}
|
||||
| undefined;
|
||||
expect(announceArgs?.requesterSessionKey).toBe(originSessionKey);
|
||||
expect(announceArgs?.requesterOrigin?.channel).toBe("telegram");
|
||||
expect(announceArgs?.requesterOrigin?.to).toBe("123");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes resolved threadId into shared subagent announce flow", async () => {
|
||||
await withTempCronHome(async (home) => {
|
||||
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
|
||||
|
||||
@@ -128,4 +128,31 @@ describe("resolveDeliveryTarget", () => {
|
||||
|
||||
expect(result.accountId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("prefers origin session context for implicit last-target resolution", async () => {
|
||||
vi.mocked(loadSessionStore).mockReturnValue({
|
||||
"agent:test:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: 1000,
|
||||
lastChannel: "telegram",
|
||||
lastTo: "main-target",
|
||||
lastAccountId: "main-account",
|
||||
},
|
||||
"agent:test:bluebubbles:direct:+19257864429": {
|
||||
sessionId: "origin-session",
|
||||
updatedAt: 1001,
|
||||
lastChannel: "telegram",
|
||||
lastTo: "origin-target",
|
||||
lastAccountId: "origin-account",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await resolveDeliveryTarget(makeCfg(), "agent-b", {
|
||||
channel: "last",
|
||||
sessionKey: "agent:test:bluebubbles:direct:+19257864429",
|
||||
});
|
||||
|
||||
expect(result.channel).toBe("telegram");
|
||||
expect(result.accountId).toBe("origin-account");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ export async function resolveDeliveryTarget(
|
||||
jobPayload: {
|
||||
channel?: "last" | ChannelId;
|
||||
to?: string;
|
||||
sessionKey?: string;
|
||||
},
|
||||
): Promise<{
|
||||
channel: Exclude<OutboundChannel, "none">;
|
||||
@@ -32,6 +33,8 @@ export async function resolveDeliveryTarget(
|
||||
}> {
|
||||
const requestedChannel = typeof jobPayload.channel === "string" ? jobPayload.channel : "last";
|
||||
const explicitTo = typeof jobPayload.to === "string" ? jobPayload.to : undefined;
|
||||
const originSessionKey =
|
||||
typeof jobPayload.sessionKey === "string" ? jobPayload.sessionKey.trim() : "";
|
||||
const allowMismatchedLastTo = requestedChannel === "last";
|
||||
|
||||
const sessionCfg = cfg.session;
|
||||
@@ -39,14 +42,32 @@ export async function resolveDeliveryTarget(
|
||||
const storePath = resolveStorePath(sessionCfg?.store, { agentId });
|
||||
const store = loadSessionStore(storePath);
|
||||
const main = store[mainSessionKey];
|
||||
const origin = originSessionKey ? store[originSessionKey] : undefined;
|
||||
|
||||
const preliminary = resolveSessionDeliveryTarget({
|
||||
const preliminaryFromOrigin = origin
|
||||
? resolveSessionDeliveryTarget({
|
||||
entry: origin,
|
||||
requestedChannel,
|
||||
explicitTo,
|
||||
allowMismatchedLastTo,
|
||||
})
|
||||
: undefined;
|
||||
const preliminaryFromMain = resolveSessionDeliveryTarget({
|
||||
entry: main,
|
||||
requestedChannel,
|
||||
explicitTo,
|
||||
allowMismatchedLastTo,
|
||||
});
|
||||
|
||||
const hasResolvedTarget = (value?: { channel?: string; to?: string }) =>
|
||||
Boolean(value?.channel && value?.to);
|
||||
const useMainContext =
|
||||
hasResolvedTarget(preliminaryFromMain) && !hasResolvedTarget(preliminaryFromOrigin);
|
||||
const contextEntry = useMainContext ? main : (origin ?? main);
|
||||
const preliminary = useMainContext
|
||||
? preliminaryFromMain
|
||||
: (preliminaryFromOrigin ?? preliminaryFromMain);
|
||||
|
||||
let fallbackChannel: Exclude<OutboundChannel, "none"> | undefined;
|
||||
if (!preliminary.channel) {
|
||||
try {
|
||||
@@ -59,7 +80,7 @@ export async function resolveDeliveryTarget(
|
||||
|
||||
const resolved = fallbackChannel
|
||||
? resolveSessionDeliveryTarget({
|
||||
entry: main,
|
||||
entry: contextEntry,
|
||||
requestedChannel,
|
||||
explicitTo,
|
||||
fallbackChannel,
|
||||
|
||||
@@ -101,6 +101,15 @@ function resolveCronDeliveryBestEffort(job: CronJob): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasPinnedCronAnnounceTarget(plan: { channel?: string; to?: string }): boolean {
|
||||
const to = typeof plan.to === "string" ? plan.to.trim() : "";
|
||||
if (to) {
|
||||
return true;
|
||||
}
|
||||
const channel = typeof plan.channel === "string" ? plan.channel.trim().toLowerCase() : "";
|
||||
return Boolean(channel && channel !== "last");
|
||||
}
|
||||
|
||||
async function resolveCronAnnounceSessionKey(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
@@ -359,10 +368,17 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
const agentPayload = params.job.payload.kind === "agentTurn" ? params.job.payload : null;
|
||||
const deliveryPlan = resolveCronDeliveryPlan(params.job);
|
||||
const deliveryRequested = deliveryPlan.requested;
|
||||
const cronOriginSessionKey =
|
||||
params.job.sessionKey?.trim() ||
|
||||
resolveAgentMainSessionKey({
|
||||
cfg: params.cfg,
|
||||
agentId,
|
||||
});
|
||||
|
||||
const resolvedDelivery = await resolveDeliveryTarget(cfgWithAgentDefaults, agentId, {
|
||||
channel: deliveryPlan.channel ?? "last",
|
||||
to: deliveryPlan.to,
|
||||
sessionKey: cronOriginSessionKey,
|
||||
});
|
||||
|
||||
const { formattedTime, timeLine } = resolveCronStyleNow(params.cfg, now);
|
||||
@@ -658,21 +674,23 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
}
|
||||
}
|
||||
} else if (synthesizedText) {
|
||||
const announceMainSessionKey = resolveAgentMainSessionKey({
|
||||
cfg: params.cfg,
|
||||
agentId,
|
||||
});
|
||||
const announceSessionKey = await resolveCronAnnounceSessionKey({
|
||||
cfg: cfgWithAgentDefaults,
|
||||
agentId,
|
||||
fallbackSessionKey: announceMainSessionKey,
|
||||
delivery: {
|
||||
channel: resolvedDelivery.channel,
|
||||
to: resolvedDelivery.to,
|
||||
accountId: resolvedDelivery.accountId,
|
||||
threadId: resolvedDelivery.threadId,
|
||||
},
|
||||
const announceUsesPinnedTarget = hasPinnedCronAnnounceTarget({
|
||||
channel: deliveryPlan.channel,
|
||||
to: deliveryPlan.to,
|
||||
});
|
||||
const announceSessionKey = announceUsesPinnedTarget
|
||||
? await resolveCronAnnounceSessionKey({
|
||||
cfg: cfgWithAgentDefaults,
|
||||
agentId,
|
||||
fallbackSessionKey: cronOriginSessionKey,
|
||||
delivery: {
|
||||
channel: resolvedDelivery.channel,
|
||||
to: resolvedDelivery.to,
|
||||
accountId: resolvedDelivery.accountId,
|
||||
threadId: resolvedDelivery.threadId,
|
||||
},
|
||||
})
|
||||
: cronOriginSessionKey;
|
||||
const taskLabel =
|
||||
typeof params.job.name === "string" && params.job.name.trim()
|
||||
? params.job.name.trim()
|
||||
|
||||
Reference in New Issue
Block a user