
























@@ -72,6 +72,7 @@ const mocks = vi.hoisted(() => ({
72727373maybeCollectSecretPath(`channels.${scopedChannel}.token`, scopedConfig.token);
7474maybeCollectSecretPath(`channels.${scopedChannel}.botToken`, scopedConfig.botToken);
75+maybeCollectSecretPath(`channels.${scopedChannel}.appPassword`, scopedConfig.appPassword);
7576if (scopedAccountId) {
7677const accountRecord =
7778scopedConfig.accounts &&
@@ -106,6 +107,7 @@ const mocks = vi.hoisted(() => ({
106107type RunMessageActionInput = {
107108agentId?: string;
108109cfg?: unknown;
110+defaultAccountId?: string;
109111params?: Record<string, unknown>;
110112requesterSenderId?: string;
111113sandboxRoot?: string;
@@ -423,6 +425,241 @@ describe("message tool secret scoping", () => {
423425expect(input?.toolContext?.currentChannelProvider).toBe("webchat");
424426});
425427428+it("uses a non-webchat session key when ambient current channel drifted to webchat", async () => {
429+mockSendResult();
430+431+const input = await executeSend({
432+action: { message: "hi" },
433+toolOptions: {
434+config: {
435+channels: {
436+telegram: {
437+botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN" },
438+},
439+},
440+} as never,
441+sourceReplyDeliveryMode: "message_tool_only",
442+currentChannelProvider: "webchat",
443+agentSessionKey: "agent:main:telegram:group:-5150615830",
444+},
445+});
446+447+expect(input?.sourceReplyDeliveryMode).toBe("message_tool_only");
448+expect(input?.toolContext?.currentChannelProvider).toBe("telegram");
449+expect(input?.toolContext?.currentChannelId).toBe("-5150615830");
450+expect(input?.params).toEqual({ action: "send", message: "hi" });
451+452+const secretResolveCall = latestSecretResolveCall();
453+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual(["channels.telegram.botToken"]);
454+});
455+456+it("preserves direct session keys as explicit user targets when ambient channel drifted to webchat", async () => {
457+mockSendResult({ channel: "discord", to: "user:123456789" });
458+459+const input = await executeSend({
460+action: { message: "hi" },
461+toolOptions: {
462+config: {
463+channels: {
464+discord: {
465+token: { source: "env", provider: "default", id: "DISCORD_TOKEN" },
466+},
467+},
468+} as never,
469+sourceReplyDeliveryMode: "message_tool_only",
470+currentChannelProvider: "webchat",
471+agentSessionKey: "agent:main:discord:direct:123456789",
472+},
473+});
474+475+expect(input?.sourceReplyDeliveryMode).toBe("message_tool_only");
476+expect(input?.toolContext?.currentChannelProvider).toBe("discord");
477+expect(input?.toolContext?.currentChannelId).toBe("user:123456789");
478+expect(input?.params).toEqual({ action: "send", message: "hi" });
479+480+const secretResolveCall = latestSecretResolveCall();
481+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual(["channels.discord.token"]);
482+});
483+484+it("preserves MS Teams DM session keys as explicit user targets when ambient channel drifted to webchat", async () => {
485+mockSendResult({ channel: "msteams", to: "user:user-1" });
486+487+const input = await executeSend({
488+action: { message: "hi" },
489+toolOptions: {
490+config: {
491+channels: {
492+msteams: {
493+appPassword: { source: "env", provider: "default", id: "MSTEAMS_APP_PASSWORD" },
494+},
495+},
496+} as never,
497+sourceReplyDeliveryMode: "message_tool_only",
498+currentChannelProvider: "webchat",
499+agentSessionKey: "agent:main:msteams:dm:user-1",
500+},
501+});
502+503+expect(input?.sourceReplyDeliveryMode).toBe("message_tool_only");
504+expect(input?.toolContext?.currentChannelProvider).toBe("msteams");
505+expect(input?.toolContext?.currentChannelId).toBe("user:user-1");
506+expect(input?.params).toEqual({ action: "send", message: "hi" });
507+508+const secretResolveCall = latestSecretResolveCall();
509+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual(["channels.msteams.appPassword"]);
510+});
511+512+it("keeps provider-native direct session targets when ambient channel drifted to webchat", async () => {
513+mockSendResult({ channel: "telegram", to: "123456789" });
514+515+const input = await executeSend({
516+action: { message: "hi" },
517+toolOptions: {
518+config: {
519+channels: {
520+telegram: {
521+botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN" },
522+},
523+},
524+} as never,
525+sourceReplyDeliveryMode: "message_tool_only",
526+currentChannelProvider: "webchat",
527+agentSessionKey: "agent:main:telegram:direct:123456789",
528+},
529+});
530+531+expect(input?.sourceReplyDeliveryMode).toBe("message_tool_only");
532+expect(input?.toolContext?.currentChannelProvider).toBe("telegram");
533+expect(input?.toolContext?.currentChannelId).toBe("123456789");
534+expect(input?.params).toEqual({ action: "send", message: "hi" });
535+536+const secretResolveCall = latestSecretResolveCall();
537+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual(["channels.telegram.botToken"]);
538+});
539+540+it("uses account-scoped session keys for secret and account fallback when ambient channel drifted to webchat", async () => {
541+mockSendResult({ channel: "discord", to: "user:123456789" });
542+543+const input = await executeSend({
544+action: { message: "hi" },
545+toolOptions: {
546+config: {
547+channels: {
548+discord: {
549+token: { source: "env", provider: "default", id: "DISCORD_TOKEN" },
550+accounts: {
551+ops: { token: { source: "env", provider: "default", id: "DISCORD_OPS_TOKEN" } },
552+},
553+},
554+},
555+} as never,
556+sourceReplyDeliveryMode: "message_tool_only",
557+currentChannelProvider: "webchat",
558+agentSessionKey: "agent:main:discord:ops:direct:123456789",
559+},
560+});
561+562+expect(input?.defaultAccountId).toBe("ops");
563+expect(input?.params?.accountId).toBe("ops");
564+expect(input?.toolContext?.currentChannelProvider).toBe("discord");
565+expect(input?.toolContext?.currentChannelId).toBe("user:123456789");
566+567+const secretResolveCall = latestSecretResolveCall();
568+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual([
569+"channels.discord.token",
570+"channels.discord.accounts.ops.token",
571+]);
572+});
573+574+it("keeps account-scoped direct keys when account id matches a peer marker", async () => {
575+mockSendResult({ channel: "discord", to: "user:123456789" });
576+577+const input = await executeSend({
578+action: { message: "hi" },
579+toolOptions: {
580+config: {
581+channels: {
582+discord: {
583+token: { source: "env", provider: "default", id: "DISCORD_TOKEN" },
584+accounts: {
585+direct: {
586+token: { source: "env", provider: "default", id: "DISCORD_DIRECT_TOKEN" },
587+},
588+},
589+},
590+},
591+} as never,
592+sourceReplyDeliveryMode: "message_tool_only",
593+currentChannelProvider: "webchat",
594+agentSessionKey: "agent:main:discord:direct:direct:123456789",
595+},
596+});
597+598+expect(input?.defaultAccountId).toBe("direct");
599+expect(input?.params?.accountId).toBe("direct");
600+expect(input?.toolContext?.currentChannelProvider).toBe("discord");
601+expect(input?.toolContext?.currentChannelId).toBe("user:123456789");
602+603+const secretResolveCall = latestSecretResolveCall();
604+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual([
605+"channels.discord.token",
606+"channels.discord.accounts.direct.token",
607+]);
608+});
609+610+it("handles legacy dm markers when ambient channel drifted to webchat", async () => {
611+mockSendResult({ channel: "slack", to: "user:u123" });
612+613+const input = await executeSend({
614+action: { message: "hi" },
615+toolOptions: {
616+config: {
617+channels: {
618+slack: {
619+botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
620+},
621+},
622+} as never,
623+sourceReplyDeliveryMode: "message_tool_only",
624+currentChannelProvider: "webchat",
625+agentSessionKey: "agent:main:slack:dm:u123:thread:171.222",
626+},
627+});
628+629+expect(input?.toolContext?.currentChannelProvider).toBe("slack");
630+expect(input?.toolContext?.currentChannelId).toBe("user:u123");
631+expect(input?.toolContext?.currentThreadTs).toBe("171.222");
632+expect(input?.toolContext?.replyToMode).toBe("all");
633+634+const secretResolveCall = latestSecretResolveCall();
635+expect(Array.from(secretResolveCall.targetIds ?? [])).toEqual(["channels.slack.botToken"]);
636+});
637+638+it("carries session-key thread suffixes into inferred channel context", async () => {
639+mockSendResult({ channel: "slack", to: "channel:c1" });
640+641+const input = await executeSend({
642+action: { message: "hi" },
643+toolOptions: {
644+config: {
645+channels: {
646+slack: {
647+botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
648+},
649+},
650+} as never,
651+sourceReplyDeliveryMode: "message_tool_only",
652+currentChannelProvider: "webchat",
653+agentSessionKey: "agent:main:slack:channel:c1:thread:1710000000.9999",
654+},
655+});
656+657+expect(input?.toolContext?.currentChannelProvider).toBe("slack");
658+expect(input?.toolContext?.currentChannelId).toBe("c1");
659+expect(input?.toolContext?.currentThreadTs).toBe("1710000000.9999");
660+expect(input?.toolContext?.replyToMode).toBe("all");
661+});
662+426663it("scopes command-time secret resolution to the selected channel/account", async () => {
427664mockSendResult({ channel: "discord", to: "discord:123" });
428665mocks.getRuntimeConfig.mockReturnValue({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。