





















@@ -11,108 +11,129 @@ afterEach(() => {
1111resetSlashCommandsForTest();
1212});
131314+function isRecord(value: unknown): value is Record<string, unknown> {
15+return typeof value === "object" && value !== null && !Array.isArray(value);
16+}
17+18+function requireRecord(value: unknown, label: string): Record<string, unknown> {
19+if (!isRecord(value)) {
20+throw new Error(`expected ${label} to be an object`);
21+}
22+return value;
23+}
24+25+function requireArray(value: unknown, label: string): unknown[] {
26+if (!Array.isArray(value)) {
27+throw new Error(`expected ${label} to be an array`);
28+}
29+return value;
30+}
31+32+function expectRecordFields(value: unknown, label: string, expected: Record<string, unknown>) {
33+const record = requireRecord(value, label);
34+for (const [key, expectedValue] of Object.entries(expected)) {
35+expect(record[key]).toEqual(expectedValue);
36+}
37+}
38+39+function requireCommandByName(name: string): Record<string, unknown> {
40+return requireRecord(
41+SLASH_COMMANDS.find((entry) => entry.name === name),
42+`slash command ${name}`,
43+);
44+}
45+46+function requireCommandByKey(key: string): Record<string, unknown> {
47+return requireRecord(
48+SLASH_COMMANDS.find((entry) => entry.key === key),
49+`slash command ${key}`,
50+);
51+}
52+53+function expectParsedSlash(input: string, commandFields: Record<string, unknown>, args: string) {
54+const parsed = requireRecord(parseSlashCommand(input), `parsed ${input}`);
55+expectRecordFields(parsed.command, `parsed ${input} command`, commandFields);
56+expect(parsed.args).toBe(args);
57+}
58+1459describe("parseSlashCommand", () => {
1560it("parses commands with an optional colon separator", () => {
16-expect(parseSlashCommand("/think: high")).toMatchObject({
17-command: { name: "think" },
18-args: "high",
19-});
20-expect(parseSlashCommand("/think:high")).toMatchObject({
21-command: { name: "think" },
22-args: "high",
23-});
24-expect(parseSlashCommand("/help:")).toMatchObject({
25-command: { name: "help" },
26-args: "",
27-});
61+expectParsedSlash("/think: high", { name: "think" }, "high");
62+expectParsedSlash("/think:high", { name: "think" }, "high");
63+expectParsedSlash("/help:", { name: "help" }, "");
2864});
29653066it("still parses space-delimited commands", () => {
31-expect(parseSlashCommand("/verbose full")).toMatchObject({
32-command: { name: "verbose" },
33-args: "full",
34-});
67+expectParsedSlash("/verbose full", { name: "verbose" }, "full");
3568});
36693770it("parses fast commands", () => {
38-expect(parseSlashCommand("/fast:on")).toMatchObject({
39-command: { name: "fast" },
40-args: "on",
41-});
71+expectParsedSlash("/fast:on", { name: "fast" }, "on");
4272});
43734474it("keeps /status on the agent path", () => {
4575const status = SLASH_COMMANDS.find((entry) => entry.name === "status");
4676expect(status?.executeLocal).not.toBe(true);
47-expect(parseSlashCommand("/status")).toMatchObject({
48-command: { name: "status" },
49-args: "",
50-});
77+expectParsedSlash("/status", { name: "status" }, "");
5178});
52795380it("includes shared /tools with shared arg hints", () => {
54-const tools = SLASH_COMMANDS.find((entry) => entry.name === "tools");
55-expect(tools).toMatchObject({
81+const tools = requireCommandByName("tools");
82+expectRecordFields(tools, "tools command", {
5683key: "tools",
5784description: "List available runtime tools.",
5885argOptions: ["compact", "verbose"],
5986executeLocal: false,
6087});
61-expect(parseSlashCommand("/tools verbose")).toMatchObject({
62-command: { name: "tools" },
63-args: "verbose",
64-});
88+expectParsedSlash("/tools verbose", { name: "tools" }, "verbose");
6589});
66906791it("parses slash aliases through the shared registry", () => {
68-const exportCommand = SLASH_COMMANDS.find((entry) => entry.key === "export-session");
69-expect(exportCommand).toMatchObject({
92+const exportCommand = requireCommandByKey("export-session");
93+expectRecordFields(exportCommand, "export-session command", {
7094name: "export-session",
7195aliases: ["export"],
7296executeLocal: true,
7397});
74-expect(parseSlashCommand("/export")).toMatchObject({
75-command: { key: "export-session" },
76-args: "",
77-});
78-expect(parseSlashCommand("/export-session")).toMatchObject({
79-command: { key: "export-session" },
80-args: "",
81-});
82-expect(parseSlashCommand("/side what changed?")).toMatchObject({
83-command: { key: "btw", name: "btw", aliases: expect.arrayContaining(["side"]) },
84-args: "what changed?",
85-});
98+expectParsedSlash("/export", { key: "export-session" }, "");
99+expectParsedSlash("/export-session", { key: "export-session" }, "");
100+const side = requireRecord(parseSlashCommand("/side what changed?"), "parsed /side");
101+expectRecordFields(side.command, "side command", { key: "btw", name: "btw" });
102+expect(
103+requireArray(requireRecord(side.command, "side command").aliases, "side aliases"),
104+).toContain("side");
105+expect(side.args).toBe("what changed?");
86106});
8710788108it("keeps canonical long-form slash names as the primary menu command", () => {
89-expect(SLASH_COMMANDS.find((entry) => entry.key === "verbose")).toMatchObject({
109+expectRecordFields(requireCommandByKey("verbose"), "verbose command", {
90110name: "verbose",
91111aliases: ["v"],
92112});
93-expect(SLASH_COMMANDS.find((entry) => entry.key === "think")).toMatchObject({
113+const think = requireCommandByKey("think");
114+expectRecordFields(think, "think command", {
94115name: "think",
95-aliases: expect.arrayContaining(["thinking", "t"]),
96116});
117+const aliases = requireArray(think.aliases, "think aliases");
118+expect(aliases).toContain("thinking");
119+expect(aliases).toContain("t");
97120});
9812199122it("keeps a single local /steer entry with the control-ui metadata", () => {
100123const steerEntries = SLASH_COMMANDS.filter((entry) => entry.name === "steer");
101124expect(steerEntries).toHaveLength(1);
102-expect(steerEntries[0]).toMatchObject({
125+const steer = requireRecord(steerEntries[0], "steer command");
126+expectRecordFields(steer, "steer command", {
103127key: "steer",
104128description: "Inject a message into the active run",
105129args: "[id] <message>",
106-aliases: expect.arrayContaining(["tell"]),
107130executeLocal: true,
108131});
132+expect(requireArray(steer.aliases, "steer aliases")).toContain("tell");
109133});
110134111135it("keeps focus as a local slash command", () => {
112-expect(parseSlashCommand("/focus")).toMatchObject({
113-command: { key: "focus", executeLocal: true },
114-args: "",
115-});
136+expectParsedSlash("/focus", { key: "focus", executeLocal: true }, "");
116137});
117138118139it("refreshes runtime commands from commands.list so docks, plugins, and direct skills appear", async () => {
@@ -154,23 +175,20 @@ describe("parseSlashCommand", () => {
154175agentId: "main",
155176});
156177157-expect(SLASH_COMMANDS.find((entry) => entry.name === "dock-discord")).toMatchObject({
178+expectRecordFields(requireCommandByName("dock-discord"), "dock-discord command", {
158179aliases: ["dock_discord"],
159180category: "tools",
160181executeLocal: false,
161182});
162-expect(SLASH_COMMANDS.find((entry) => entry.name === "dreaming")).toMatchObject({
183+expectRecordFields(requireCommandByName("dreaming"), "dreaming command", {
163184key: "dreaming",
164185executeLocal: false,
165186});
166-expect(SLASH_COMMANDS.find((entry) => entry.name === "prose")).toMatchObject({
187+expectRecordFields(requireCommandByName("prose"), "prose command", {
167188key: "prose",
168189executeLocal: false,
169190});
170-expect(parseSlashCommand("/dock_discord")).toMatchObject({
171-command: { name: "dock-discord" },
172-args: "",
173-});
191+expectParsedSlash("/dock_discord", { name: "dock-discord" }, "");
174192});
175193176194it("does not let remote commands collide with reserved local commands", async () => {
@@ -200,12 +218,12 @@ describe("parseSlashCommand", () => {
200218agentId: "main",
201219});
202220203-expect(SLASH_COMMANDS.find((entry) => entry.name === "redirect")).toMatchObject({
221+expectRecordFields(requireCommandByName("redirect"), "redirect command", {
204222key: "redirect",
205223executeLocal: true,
206224description: "Abort and restart with a new message",
207225});
208-expect(SLASH_COMMANDS.find((entry) => entry.name === "kill")).toMatchObject({
226+expectRecordFields(requireCommandByName("kill"), "kill command", {
209227key: "kill",
210228executeLocal: true,
211229description: "Kill a running subagent (or all).",
@@ -239,14 +257,12 @@ describe("parseSlashCommand", () => {
239257agentId: "main",
240258});
241259242-expect(SLASH_COMMANDS.find((entry) => entry.name === "safe-name")).toMatchObject({
260+expectRecordFields(requireCommandByName("safe-name"), "safe-name command", {
243261name: "safe-name",
244262});
245263expect(SLASH_COMMANDS.find((entry) => entry.name === "prose now")).toBeUndefined();
246264expect(SLASH_COMMANDS.find((entry) => entry.name === "bad:alias")).toBeUndefined();
247-expect(parseSlashCommand("/safe-name")).toMatchObject({
248-command: { name: "safe-name" },
249-});
265+expectParsedSlash("/safe-name", { name: "safe-name" }, "");
250266});
251267252268it("caps remote command payload size and long metadata before it reaches UI state", async () => {
@@ -364,11 +380,11 @@ describe("parseSlashCommand", () => {
364380client: { request } as never,
365381agentId: "main",
366382});
367-expect(SLASH_COMMANDS.find((entry) => entry.name === "valid")).toMatchObject({
383+expectRecordFields(requireCommandByName("valid"), "valid command", {
368384name: "valid",
369385description: "",
370386});
371-expect(SLASH_COMMANDS.find((entry) => entry.name === "pair")).toMatchObject({
387+expectRecordFields(requireCommandByName("pair"), "pair command", {
372388name: "pair",
373389});
374390});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。