


























@@ -35,22 +35,37 @@ vi.mock("../infra/outbound/message.js", () => ({
3535}));
36363737vi.mock("../utils/message-channel.js", () => {
38+const INTERNAL_MESSAGE_CHANNEL = "webchat";
39+const NATIVE_APPROVAL_CHANNELS = new Set([
40+"webchat",
41+"discord",
42+"googlechat",
43+"imessage",
44+"matrix",
45+"qqbot",
46+"signal",
47+"slack",
48+"telegram",
49+"whatsapp",
50+]);
3851const normalizeMessageChannel = (raw?: string | null) => {
3952const normalized = raw?.trim().toLowerCase();
4053if (!normalized) {
4154return undefined;
4255}
43-if (normalized === "web" || normalized === "webchat") {
44-return "internal";
56+if (normalized === "web") {
57+return INTERNAL_MESSAGE_CHANNEL;
4558}
4659return normalized;
4760};
4861const isGatewayMessageChannel = (value: string) => Boolean(normalizeMessageChannel(value));
4962return {
50-INTERNAL_MESSAGE_CHANNEL: "internal",
63+INTERNAL_MESSAGE_CHANNEL,
64+isNativeApprovalChannel: (value?: string | null) =>
65+typeof value === "string" && NATIVE_APPROVAL_CHANNELS.has(value),
5166isDeliverableMessageChannel: (value: string) => {
5267const channel = normalizeMessageChannel(value);
53-return Boolean(channel && channel !== "internal" && channel !== "tui");
68+return Boolean(channel && channel !== INTERNAL_MESSAGE_CHANNEL && channel !== "tui");
5469},
5570 isGatewayMessageChannel,
5671 normalizeMessageChannel,
@@ -98,12 +113,12 @@ vi.mock("../infra/exec-approval-surface.js", () => ({
98113kind: "enabled",
99114 channel,
100115channelLabel:
101-channel === "tui" ? "terminal UI" : channel === "internal" ? "Web UI" : "this platform",
116+channel === "tui" ? "terminal UI" : channel === "webchat" ? "Web UI" : "this platform",
102117accountId: params.accountId ?? undefined,
103118};
104119},
105120supportsNativeExecApprovalClient: (channel?: string | null) =>
106-!channel || channel === "internal" || channel === "tui",
121+!channel || channel === "webchat" || channel === "tui",
107122}));
108123109124vi.mock("../infra/shell-env.js", () => ({
@@ -962,7 +977,7 @@ describe("exec approvals", () => {
962977);
963978});
964979965-it("continues the original agent session after approved gateway exec completes with an external route", async () => {
980+it("continues the original agent session after approved gateway exec completes with a non-native external route", async () => {
966981const agentCalls: Array<Record<string, unknown>> = [];
967982968983mockAcceptedApprovalFlow({
@@ -975,26 +990,26 @@ describe("exec approvals", () => {
975990host: "gateway",
976991ask: "always",
977992approvalRunningNoticeMs: 0,
978-sessionKey: "agent:main:discord:channel:123",
993+sessionKey: "agent:main:feishu:channel:123",
979994elevated: { enabled: true, allowed: true, defaultLevel: "ask" },
980-messageProvider: "discord",
995+messageProvider: "feishu",
981996currentChannelId: "123",
982997accountId: "default",
983998currentThreadTs: "456",
984999});
9851000986-const result = await tool.execute("call-gw-followup-discord", {
1001+const result = await tool.execute("call-gw-followup-feishu", {
9871002command: "echo ok",
9881003workdir: process.cwd(),
9891004});
99010059911006expect(result.details.status).toBe("approval-pending");
9921007await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
9931008expectRecordFields(agentCalls[0], {
994-sessionKey: "agent:main:discord:channel:123",
1009+sessionKey: "agent:main:feishu:channel:123",
9951010deliver: true,
9961011bestEffortDeliver: true,
997-channel: "discord",
1012+channel: "feishu",
9981013to: "123",
9991014accountId: "default",
10001015threadId: "456",
@@ -1007,7 +1022,7 @@ describe("exec approvals", () => {
10071022expect(sendMessage).not.toHaveBeenCalled();
10081023});
100910241010-it("auto-continues the same Discord session after approval resolves without a second user turn", async () => {
1025+it("waits inline for native Discord approval and resumes the same session without a second user turn", async () => {
10111026const agentCalls: Array<Record<string, unknown>> = [];
10121027let resolveDecision: ((value: { decision: string }) => void) | undefined;
10131028const decisionPromise = new Promise<{ decision: string }>((resolve) => {
@@ -1040,31 +1055,26 @@ describe("exec approvals", () => {
10401055currentThreadTs: "456",
10411056});
104210571043-const result = await tool.execute("call-gw-followup-discord-delayed", {
1058+let settled = false;
1059+const resultPromise = tool.execute("call-gw-followup-discord-delayed", {
10441060command: "printf delayed-ok",
10451061workdir: process.cwd(),
10461062});
1063+void resultPromise.then(() => {
1064+settled = true;
1065+});
104710661048-expect(result.details.status).toBe("approval-pending");
1067+await Promise.resolve();
1068+expect(settled).toBe(false);
10491069expect(agentCalls).toHaveLength(0);
1050107010511071resolveDecision?.({ decision: "allow-once" });
105210721053-await expect.poll(() => agentCalls.length, { timeout: 3000, interval: 1 }).toBe(1);
1054-expectRecordFields(agentCalls[0], {
1055-sessionKey: "agent:main:discord:channel:123",
1056-deliver: true,
1057-bestEffortDeliver: true,
1058-channel: "discord",
1059-to: "123",
1060-accountId: "default",
1061-threadId: "456",
1062-});
1063-expect(typeof agentCalls[0]?.message).toBe("string");
1064-expect(agentCalls[0]?.message).toContain(
1065-"If the task requires more steps, continue from this result before replying to the user.",
1066-);
1067-expect(agentCalls[0]?.message).toContain("delayed-ok");
1073+const result = await resultPromise;
1074+1075+expect(result.details.status).toBe("completed");
1076+expect(getResultText(result)).toContain("delayed-ok");
1077+expect(agentCalls).toHaveLength(0);
10681078expect(sendMessage).not.toHaveBeenCalled();
10691079});
10701080此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。