





















@@ -529,6 +529,88 @@ describe("Discord native plugin command dispatch", () => {
529529expect(interaction.reply).not.toHaveBeenCalled();
530530});
531531532+it("returns an explicit warning instead of success when dispatch produces zero visible replies", async () => {
533+const cfg = createConfig();
534+const interaction = createInteraction();
535+runtimeModuleMocks.matchPluginCommand.mockReturnValue(null);
536+runtimeModuleMocks.dispatchReplyWithDispatcher.mockResolvedValue({
537+counts: { final: 0, block: 0, tool: 0 },
538+queuedFinal: false,
539+} as never);
540+const command = await createNativeCommand(cfg, {
541+name: "new",
542+description: "Start a new session.",
543+acceptsArgs: true,
544+});
545+546+await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
547+548+expect(interaction.followUp).toHaveBeenCalledWith(
549+expect.objectContaining({
550+content: "⚠️ Command produced no visible reply.",
551+ephemeral: true,
552+}),
553+);
554+expect(interaction.reply).not.toHaveBeenCalled();
555+});
556+557+it("does not warn when dispatch reports a queued final without visible counts", async () => {
558+const cfg = createConfig();
559+const interaction = createInteraction();
560+runtimeModuleMocks.matchPluginCommand.mockReturnValue(null);
561+runtimeModuleMocks.dispatchReplyWithDispatcher.mockResolvedValue({
562+counts: { final: 0, block: 0, tool: 0 },
563+queuedFinal: true,
564+} as never);
565+const command = await createNativeCommand(cfg, {
566+name: "new",
567+description: "Start a new session.",
568+acceptsArgs: true,
569+});
570+571+await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
572+573+expect(interaction.followUp).not.toHaveBeenCalledWith(
574+expect.objectContaining({ content: "⚠️ Command produced no visible reply." }),
575+);
576+expect(interaction.reply).not.toHaveBeenCalled();
577+});
578+579+it("returns an explicit warning when a direct plugin command has no visible reply", async () => {
580+const cfg = createConfig();
581+const commandSpec: NativeCommandSpec = {
582+name: "cron_jobs",
583+description: "List cron jobs",
584+acceptsArgs: false,
585+};
586+const interaction = createInteraction();
587+const pluginMatch = {
588+command: {
589+name: "cron_jobs",
590+description: "List cron jobs",
591+pluginId: "cron-jobs",
592+acceptsArgs: false,
593+handler: vi.fn().mockResolvedValue({ text: "" }),
594+},
595+args: undefined,
596+};
597+598+runtimeModuleMocks.matchPluginCommand.mockReturnValue(pluginMatch as never);
599+runtimeModuleMocks.executePluginCommand.mockResolvedValue({});
600+const dispatchSpy = runtimeModuleMocks.dispatchReplyWithDispatcher.mockResolvedValue(
601+{} as never,
602+);
603+const command = await createNativeCommand(cfg, commandSpec);
604+605+await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
606+607+expect(dispatchSpy).not.toHaveBeenCalled();
608+expect(interaction.followUp).toHaveBeenCalledWith(
609+expect.objectContaining({ content: "⚠️ Command produced no visible reply." }),
610+);
611+expect(interaction.reply).not.toHaveBeenCalled();
612+});
613+532614it("forwards Discord thread metadata into direct plugin command execution", async () => {
533615const cfg = {
534616commands: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。