refactor: tidy directive parsing + queue status
This commit is contained in:
@@ -12,15 +12,7 @@ import {
|
||||
saveSessionStore,
|
||||
} from "../config/sessions.js";
|
||||
import { drainSystemEvents } from "../infra/system-events.js";
|
||||
import {
|
||||
extractElevatedDirective,
|
||||
extractQueueDirective,
|
||||
extractReasoningDirective,
|
||||
extractReplyToTag,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
getReplyFromConfig,
|
||||
} from "./reply.js";
|
||||
import { getReplyFromConfig } from "./reply.js";
|
||||
|
||||
vi.mock("../agents/pi-embedded.js", () => ({
|
||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||
@@ -60,7 +52,7 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
}
|
||||
}
|
||||
|
||||
describe("directive parsing", () => {
|
||||
describe("directive behavior", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
vi.mocked(loadModelCatalog).mockResolvedValue([
|
||||
@@ -74,127 +66,6 @@ describe("directive parsing", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("ignores verbose directive inside URL", () => {
|
||||
const body = "https://x.com/verioussmith/status/1997066835133669687";
|
||||
const res = extractVerboseDirective(body);
|
||||
expect(res.hasDirective).toBe(false);
|
||||
expect(res.cleaned).toBe(body);
|
||||
});
|
||||
|
||||
it("ignores typoed /verioussmith", () => {
|
||||
const body = "/verioussmith";
|
||||
const res = extractVerboseDirective(body);
|
||||
expect(res.hasDirective).toBe(false);
|
||||
expect(res.cleaned).toBe(body.trim());
|
||||
});
|
||||
|
||||
it("ignores think directive inside URL", () => {
|
||||
const body = "see https://example.com/path/thinkstuff";
|
||||
const res = extractThinkDirective(body);
|
||||
expect(res.hasDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("matches verbose with leading space", () => {
|
||||
const res = extractVerboseDirective(" please /verbose on now");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.verboseLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches reasoning directive", () => {
|
||||
const res = extractReasoningDirective("/reasoning on please");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.reasoningLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches reasoning stream directive", () => {
|
||||
const res = extractReasoningDirective("/reasoning stream please");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.reasoningLevel).toBe("stream");
|
||||
});
|
||||
|
||||
it("matches elevated with leading space", () => {
|
||||
const res = extractElevatedDirective(" please /elevated on now");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.elevatedLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches think at start of line", () => {
|
||||
const res = extractThinkDirective("/think:high run slow");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.thinkLevel).toBe("high");
|
||||
});
|
||||
|
||||
it("does not match /think followed by extra letters", () => {
|
||||
// e.g. someone typing "/think" + extra letter "hink"
|
||||
const res = extractThinkDirective("/thinkstuff");
|
||||
expect(res.hasDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("matches /think with no argument", () => {
|
||||
const res = extractThinkDirective("/think");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.thinkLevel).toBeUndefined();
|
||||
expect(res.rawLevel).toBeUndefined();
|
||||
});
|
||||
|
||||
it("matches /t with no argument", () => {
|
||||
const res = extractThinkDirective("/t");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.thinkLevel).toBeUndefined();
|
||||
});
|
||||
|
||||
it("matches think with no argument and consumes colon", () => {
|
||||
const res = extractThinkDirective("/think:");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.thinkLevel).toBeUndefined();
|
||||
expect(res.rawLevel).toBeUndefined();
|
||||
expect(res.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("matches verbose with no argument", () => {
|
||||
const res = extractVerboseDirective("/verbose:");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.verboseLevel).toBeUndefined();
|
||||
expect(res.rawLevel).toBeUndefined();
|
||||
expect(res.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("matches reasoning with no argument", () => {
|
||||
const res = extractReasoningDirective("/reasoning:");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.reasoningLevel).toBeUndefined();
|
||||
expect(res.rawLevel).toBeUndefined();
|
||||
expect(res.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("matches elevated with no argument", () => {
|
||||
const res = extractElevatedDirective("/elevated:");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.elevatedLevel).toBeUndefined();
|
||||
expect(res.rawLevel).toBeUndefined();
|
||||
expect(res.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("matches queue directive", () => {
|
||||
const res = extractQueueDirective("please /queue interrupt now");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.queueMode).toBe("interrupt");
|
||||
expect(res.queueReset).toBe(false);
|
||||
expect(res.cleaned).toBe("please now");
|
||||
});
|
||||
|
||||
it("parses queue options and modes", () => {
|
||||
const res = extractQueueDirective(
|
||||
"please /queue steer+backlog debounce:2s cap:5 drop:summarize now",
|
||||
);
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.queueMode).toBe("steer-backlog");
|
||||
expect(res.debounceMs).toBe(2000);
|
||||
expect(res.cap).toBe(5);
|
||||
expect(res.dropPolicy).toBe("summarize");
|
||||
expect(res.cleaned).toBe("please now");
|
||||
});
|
||||
|
||||
it("keeps reserved command aliases from matching after trimming", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
@@ -254,6 +125,44 @@ describe("directive parsing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows current queue settings when /queue has no arguments", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/queue",
|
||||
From: "+1222",
|
||||
To: "+1222",
|
||||
Provider: "whatsapp",
|
||||
},
|
||||
{},
|
||||
{
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: path.join(home, "clawd"),
|
||||
},
|
||||
routing: {
|
||||
queue: {
|
||||
mode: "collect",
|
||||
debounceMs: 1500,
|
||||
cap: 9,
|
||||
drop: "summarize",
|
||||
},
|
||||
},
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: path.join(home, "sessions.json") },
|
||||
},
|
||||
);
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain(
|
||||
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize.",
|
||||
);
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows current think level when /think has no argument", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||
@@ -299,27 +208,6 @@ describe("directive parsing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("extracts reply_to_current tag", () => {
|
||||
const res = extractReplyToTag("ok [[reply_to_current]]", "msg-1");
|
||||
expect(res.replyToId).toBe("msg-1");
|
||||
expect(res.cleaned).toBe("ok");
|
||||
});
|
||||
|
||||
it("extracts reply_to id tag", () => {
|
||||
const res = extractReplyToTag("see [[reply_to:12345]] now", "msg-1");
|
||||
expect(res.replyToId).toBe("12345");
|
||||
expect(res.cleaned).toBe("see now");
|
||||
});
|
||||
|
||||
it("preserves newlines when stripping reply tags", () => {
|
||||
const res = extractReplyToTag(
|
||||
"line 1\nline 2 [[reply_to_current]]\n\nline 3",
|
||||
"msg-2",
|
||||
);
|
||||
expect(res.replyToId).toBe("msg-2");
|
||||
expect(res.cleaned).toBe("line 1\nline 2\n\nline 3");
|
||||
});
|
||||
|
||||
it("strips reply tags and maps reply_to_current to MessageSid", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user