fix(webchat): set command flag on slash command responses (#2030)
Webchat slash commands (/status, /help, /new) were not working because command responses were missing the message.command flag. This caused the Control UI to not recognize them as command responses. - Always set message.command=true when agent wasn't started - Fix missing agentCommand import in E2E test - Add webchat commands unit tests
This commit is contained in:
parent
bc7ba73a34
commit
cd0aea0f8c
@ -506,12 +506,13 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
})
|
||||
.then(() => {
|
||||
if (!agentRunStarted) {
|
||||
// No agent was started, meaning this was handled as a command
|
||||
const combinedReply = finalReplyParts
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
.trim();
|
||||
let message: Record<string, unknown> | undefined;
|
||||
let message: Record<string, unknown> = { command: true };
|
||||
if (combinedReply) {
|
||||
const { storePath: latestStorePath, entry: latestEntry } = loadSessionEntry(
|
||||
p.sessionKey,
|
||||
@ -525,7 +526,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
createIfMissing: true,
|
||||
});
|
||||
if (appended.ok) {
|
||||
message = appended.message;
|
||||
message = { ...appended.message, command: true };
|
||||
} else {
|
||||
context.logGateway.warn(
|
||||
`webchat transcript append failed: ${appended.error ?? "unknown error"}`,
|
||||
@ -537,6 +538,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
timestamp: now,
|
||||
stopReason: "injected",
|
||||
usage: { input: 0, output: 0, totalTokens: 0 },
|
||||
command: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { WebSocket } from "ws";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { emitAgentEvent, registerAgentRunContext } from "../infra/agent-events.js";
|
||||
import {
|
||||
agentCommand,
|
||||
connectOk,
|
||||
getReplyFromConfig,
|
||||
installGatewayTestHooks,
|
||||
|
||||
123
src/gateway/server.chat.webchat-commands.test.ts
Normal file
123
src/gateway/server.chat.webchat-commands.test.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
import type { MsgContext } from "../auto-reply/templating.js";
|
||||
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
|
||||
import { buildCommandContext, handleCommands } from "../auto-reply/reply/commands.js";
|
||||
import { parseInlineDirectives } from "../auto-reply/reply/directive-handling.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
|
||||
// Test that webchat commands work with CommandAuthorized: true
|
||||
describe("webchat slash commands", () => {
|
||||
const workspaceDir = "/tmp/clawdbot-test";
|
||||
|
||||
function buildWebchatParams(commandBody: string, cfg: ClawdbotConfig) {
|
||||
const ctx = {
|
||||
Body: commandBody,
|
||||
BodyForAgent: commandBody,
|
||||
BodyForCommands: commandBody,
|
||||
RawBody: commandBody,
|
||||
CommandBody: commandBody,
|
||||
SessionKey: "agent:main:webchat:test",
|
||||
Provider: INTERNAL_MESSAGE_CHANNEL,
|
||||
Surface: INTERNAL_MESSAGE_CHANNEL,
|
||||
OriginatingChannel: INTERNAL_MESSAGE_CHANNEL,
|
||||
ChatType: "direct",
|
||||
CommandAuthorized: true,
|
||||
CommandSource: undefined,
|
||||
} as MsgContext;
|
||||
|
||||
const command = buildCommandContext({
|
||||
ctx,
|
||||
cfg,
|
||||
isGroup: false,
|
||||
triggerBodyNormalized: commandBody.trim(),
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
return {
|
||||
ctx,
|
||||
cfg,
|
||||
command,
|
||||
directives: parseInlineDirectives(commandBody),
|
||||
elevated: { enabled: true, allowed: true, failures: [] },
|
||||
sessionKey: "agent:main:webchat:test",
|
||||
workspaceDir,
|
||||
defaultGroupActivation: () => "mention" as const,
|
||||
resolvedVerboseLevel: "off" as const,
|
||||
resolvedReasoningLevel: "off" as const,
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
provider: "webchat",
|
||||
model: "test-model",
|
||||
contextTokens: 0,
|
||||
isGroup: false,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("/status returns a reply and does not continue to agent", async () => {
|
||||
const cfg = {} as ClawdbotConfig;
|
||||
const params = buildWebchatParams("/status", cfg);
|
||||
|
||||
console.log("Command context:", {
|
||||
commandBodyNormalized: params.command.commandBodyNormalized,
|
||||
isAuthorizedSender: params.command.isAuthorizedSender,
|
||||
surface: params.command.surface,
|
||||
});
|
||||
|
||||
const result = await handleCommands(params);
|
||||
|
||||
console.log("Result:", {
|
||||
shouldContinue: result.shouldContinue,
|
||||
hasReply: Boolean(result.reply),
|
||||
replyPreview: result.reply?.text?.slice(0, 100),
|
||||
});
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply).toBeDefined();
|
||||
expect(result.reply?.text).toContain("Clawdbot");
|
||||
});
|
||||
|
||||
it("/help returns a reply and does not continue to agent", async () => {
|
||||
const cfg = {} as ClawdbotConfig;
|
||||
const params = buildWebchatParams("/help", cfg);
|
||||
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply).toBeDefined();
|
||||
expect(result.reply?.text).toContain("Help");
|
||||
});
|
||||
|
||||
it("/new continues to agent (session reset)", async () => {
|
||||
const cfg = {} as ClawdbotConfig;
|
||||
const params = buildWebchatParams("/new", cfg);
|
||||
|
||||
const result = await handleCommands(params);
|
||||
|
||||
// /new triggers session reset but continues to agent for greeting
|
||||
expect(result.shouldContinue).toBe(true);
|
||||
});
|
||||
|
||||
it("commands work with commands.text: false (webchat is not native)", async () => {
|
||||
const cfg = { commands: { text: false } } as ClawdbotConfig;
|
||||
const params = buildWebchatParams("/status", cfg);
|
||||
|
||||
const result = await handleCommands(params);
|
||||
|
||||
// Even with commands.text: false, webchat should still handle commands
|
||||
// because webchat doesn't have native command support
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply).toBeDefined();
|
||||
});
|
||||
|
||||
it("verifies isAuthorizedSender is true for webchat", async () => {
|
||||
const cfg = {} as ClawdbotConfig;
|
||||
const params = buildWebchatParams("/status", cfg);
|
||||
|
||||
expect(params.command.isAuthorizedSender).toBe(true);
|
||||
expect(params.command.surface).toBe(INTERNAL_MESSAGE_CHANNEL);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user