




















@@ -525,6 +525,84 @@ describe("buildGatewayCronService", () => {
525525}
526526});
527527528+it("derives agentId symmetrically for enqueue and wake when only an agent-prefixed sessionKey is supplied", () => {
529+// Multi-agent setup where the configured default ("primary") is NOT the
530+// agent referenced in the sessionKey ("ops"). Pre-PR, enqueue went through
531+// resolveCronSessionKey which treated a non-default agent's key as foreign
532+// and rerouted to primary's main session, while requestHeartbeat correctly
533+// derived agentId from the key — so wake hit ops while the event landed in
534+// primary's queue. Both adapter call sites now derive agentId from the
535+// session key the same way.
536+const cfg = {
537+session: { mainKey: "main" },
538+cron: { store: path.join(os.tmpdir(), `server-cron-symmetric-${Date.now()}`, "cron.json") },
539+agents: {
540+list: [
541+{ id: "primary", default: true, model: "test/primary" },
542+{ id: "ops", model: "test/ops" },
543+],
544+},
545+} as unknown as OpenClawConfig;
546+loadConfigMock.mockReturnValue(cfg);
547+548+const state = buildGatewayCronService({
549+ cfg,
550+deps: {} as CliDeps,
551+broadcast: () => {},
552+});
553+try {
554+const cronDeps = (
555+state.cron as unknown as {
556+state?: {
557+deps?: {
558+enqueueSystemEvent?: (
559+text: string,
560+opts?: { agentId?: string; sessionKey?: string; contextKey?: string },
561+) => void;
562+requestHeartbeat?: (opts?: {
563+agentId?: string;
564+sessionKey?: string | null;
565+source?: string;
566+intent?: string;
567+reason?: string;
568+}) => void;
569+};
570+};
571+}
572+).state?.deps;
573+574+const foreignKey = "agent:ops:cron:nightly:run:abc-123";
575+576+cronDeps?.enqueueSystemEvent?.("hello", {
577+sessionKey: foreignKey,
578+contextKey: "cron:test",
579+});
580+cronDeps?.requestHeartbeat?.({
581+source: "cron",
582+intent: "event",
583+reason: "cron:test",
584+sessionKey: foreignKey,
585+});
586+587+// Both must derive agentId="ops" from the key, NOT fall back to the
588+// configured default "primary". The exact resolved sessionKey is
589+// delegated to resolveCronSessionKey (already covered by other tests);
590+// here we only assert the agent target is consistent across both sides.
591+const enqueueCall = enqueueSystemEventMock.mock.calls.at(-1);
592+const wakeCall = requestHeartbeatMock.mock.calls.at(-1);
593+const enqueueSessionKey = (enqueueCall?.[1] as { sessionKey?: string } | undefined)
594+?.sessionKey;
595+const wakeOpts = wakeCall?.[0] as { agentId?: string; sessionKey?: string } | undefined;
596+597+expect(enqueueSessionKey).toBeDefined();
598+expect(enqueueSessionKey).toMatch(/^agent:ops:/);
599+expect(wakeOpts?.agentId).toBe("ops");
600+expect(wakeOpts?.sessionKey).toMatch(/^agent:ops:/);
601+} finally {
602+state.cron.stop();
603+}
604+});
605+528606it("preserves trust downgrades when cron enqueues system events", () => {
529607const cfg = createCronConfig("server-cron-untrusted");
530608loadConfigMock.mockReturnValue(cfg);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。