From 559e175b3876b795643c89af435c5ba082c99226 Mon Sep 17 00:00:00 2001 From: LK Date: Thu, 8 Jan 2026 16:02:54 +0100 Subject: [PATCH 1/9] feat(commands): add /commands slash list --- docs/tools/slash-commands.md | 1 + src/auto-reply/command-detection.test.ts | 6 ++++++ src/auto-reply/commands-registry.test.ts | 4 ++++ src/auto-reply/commands-registry.ts | 19 ++++++++++++++++- src/auto-reply/reply/commands.ts | 12 +++++++++++ src/auto-reply/status.test.ts | 15 +++++++++++++- src/auto-reply/status.ts | 26 ++++++++++++++++++++++++ 7 files changed, 81 insertions(+), 2 deletions(-) diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index fa9e4e636..3653bbd22 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -32,6 +32,7 @@ Inline text like `hello /status` is ignored. Text + native (when enabled): - `/help` +- `/commands` - `/status` - `/stop` - `/restart` diff --git a/src/auto-reply/command-detection.test.ts b/src/auto-reply/command-detection.test.ts index e8a14a898..98cfbd568 100644 --- a/src/auto-reply/command-detection.test.ts +++ b/src/auto-reply/command-detection.test.ts @@ -42,9 +42,15 @@ describe("control command parsing", () => { expect(hasControlCommand("/help")).toBe(true); expect(hasControlCommand("/help:")).toBe(true); expect(hasControlCommand("help")).toBe(false); + expect(hasControlCommand("/commands")).toBe(true); + expect(hasControlCommand("/commands:")).toBe(true); + expect(hasControlCommand("commands")).toBe(false); expect(hasControlCommand("/status")).toBe(true); expect(hasControlCommand("/status:")).toBe(true); expect(hasControlCommand("status")).toBe(false); + expect(hasControlCommand("/compact")).toBe(true); + expect(hasControlCommand("/compact:")).toBe(true); + expect(hasControlCommand("compact")).toBe(false); }); it("requires commands to be the full message", () => { diff --git a/src/auto-reply/commands-registry.test.ts b/src/auto-reply/commands-registry.test.ts index 58bfa00c0..ca17e4913 100644 --- a/src/auto-reply/commands-registry.test.ts +++ b/src/auto-reply/commands-registry.test.ts @@ -17,13 +17,17 @@ describe("commands registry", () => { const specs = listNativeCommandSpecs(); expect(specs.find((spec) => spec.name === "help")).toBeTruthy(); expect(specs.find((spec) => spec.name === "stop")).toBeTruthy(); + expect(specs.find((spec) => spec.name === "compact")).toBeFalsy(); }); it("detects known text commands", () => { const detection = getCommandDetection(); expect(detection.exact.has("/help")).toBe(true); + expect(detection.exact.has("/commands")).toBe(true); expect(detection.regex.test("/status")).toBe(true); expect(detection.regex.test("/status:")).toBe(true); + expect(detection.regex.test("/compact")).toBe(true); + expect(detection.regex.test("/compact:")).toBe(true); expect(detection.regex.test("/stop")).toBe(true); expect(detection.regex.test("/send:")).toBe(true); expect(detection.regex.test("try /status")).toBe(false); diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 8fbbe611e..5e252f0b7 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -6,6 +6,7 @@ export type ChatCommandDefinition = { description: string; textAliases: string[]; acceptsArgs?: boolean; + supportsNative?: boolean; }; export type NativeCommandSpec = { @@ -21,6 +22,12 @@ const CHAT_COMMANDS: ChatCommandDefinition[] = [ description: "Show available commands.", textAliases: ["/help"], }, + { + key: "commands", + nativeName: "commands", + description: "List all slash commands.", + textAliases: ["/commands"], + }, { key: "status", nativeName: "status", @@ -65,6 +72,14 @@ const CHAT_COMMANDS: ChatCommandDefinition[] = [ description: "Start a new session.", textAliases: ["/new"], }, + { + key: "compact", + nativeName: "compact", + description: "Compact the current session context.", + textAliases: ["/compact"], + acceptsArgs: true, + supportsNative: false, + }, { key: "think", nativeName: "think", @@ -127,7 +142,9 @@ export function listChatCommands(): ChatCommandDefinition[] { } export function listNativeCommandSpecs(): NativeCommandSpec[] { - return CHAT_COMMANDS.map((command) => ({ + return CHAT_COMMANDS.filter( + (command) => command.supportsNative !== false, + ).map((command) => ({ name: command.nativeName, description: command.description, acceptsArgs: Boolean(command.acceptsArgs), diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 78bc7010f..0b4f4eed3 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -43,6 +43,7 @@ import { } from "../group-activation.js"; import { parseSendPolicyCommand } from "../send-policy.js"; import { + buildCommandsMessage, buildHelpMessage, buildStatusMessage, formatContextUsageShort, @@ -404,6 +405,17 @@ export async function handleCommands(params: { return { shouldContinue: false, reply: { text: buildHelpMessage() } }; } + const commandsRequested = command.commandBodyNormalized === "/commands"; + if (allowTextCommands && commandsRequested) { + if (!command.isAuthorizedSender) { + logVerbose( + `Ignoring /commands from unauthorized sender: ${command.senderE164 || ""}`, + ); + return { shouldContinue: false }; + } + return { shouldContinue: false, reply: { text: buildCommandsMessage() } }; + } + const statusRequested = directives.hasStatusDirective || command.commandBodyNormalized === "/status"; diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index 2e9f5ba80..f6d482318 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { buildStatusMessage } from "./status.js"; +import { buildCommandsMessage, buildStatusMessage } from "./status.js"; const HOME_ENV_KEYS = ["HOME", "USERPROFILE", "HOMEDRIVE", "HOMEPATH"] as const; type HomeEnvSnapshot = Record< @@ -234,3 +234,16 @@ describe("buildStatusMessage", () => { } }); }); + +describe("buildCommandsMessage", () => { + it("lists commands with aliases and text-only hints", () => { + const text = buildCommandsMessage(); + expect(text).toContain("/commands - List all slash commands."); + expect(text).toContain( + "/think (aliases: /thinking, /t) - Set thinking level.", + ); + expect(text).toContain( + "/compact (text-only) - Compact the current session context.", + ); + }); +}); diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index a6a5c8588..d25a045f2 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -21,6 +21,7 @@ import { } from "../config/sessions.js"; import { resolveCommitHash } from "../infra/git-commit.js"; import { VERSION } from "../version.js"; +import { listChatCommands } from "./commands-registry.js"; import type { ElevatedLevel, ReasoningLevel, @@ -302,5 +303,30 @@ export function buildHelpMessage(): string { "ℹ️ Help", "Shortcuts: /new reset | /compact [instructions] | /restart relink", "Options: /think | /verbose on|off | /reasoning on|off | /elevated on|off | /model ", + "More: /commands for all slash commands", ].join("\n"); } + +export function buildCommandsMessage(): string { + const lines = ["ℹ️ Slash commands"]; + for (const command of listChatCommands()) { + const primary = `/${command.nativeName}`; + const seen = new Set(); + const aliases = command.textAliases + .map((alias) => alias.trim()) + .filter(Boolean) + .filter((alias) => alias.toLowerCase() !== primary.toLowerCase()) + .filter((alias) => { + const key = alias.toLowerCase(); + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + const aliasLabel = aliases.length + ? ` (aliases: ${aliases.join(", ")})` + : ""; + const scopeLabel = command.supportsNative === false ? " (text-only)" : ""; + lines.push(`${primary}${aliasLabel}${scopeLabel} - ${command.description}`); + } + return lines.join("\n"); +} From 8445c9a5e84fd6ce7621f6ac9ed05568640b6bfd Mon Sep 17 00:00:00 2001 From: Luke <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:33:11 -0500 Subject: [PATCH 2/9] Removing stray commits --- src/auto-reply/command-detection.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/auto-reply/command-detection.test.ts b/src/auto-reply/command-detection.test.ts index 98cfbd568..2c1eefb12 100644 --- a/src/auto-reply/command-detection.test.ts +++ b/src/auto-reply/command-detection.test.ts @@ -48,9 +48,6 @@ describe("control command parsing", () => { expect(hasControlCommand("/status")).toBe(true); expect(hasControlCommand("/status:")).toBe(true); expect(hasControlCommand("status")).toBe(false); - expect(hasControlCommand("/compact")).toBe(true); - expect(hasControlCommand("/compact:")).toBe(true); - expect(hasControlCommand("compact")).toBe(false); }); it("requires commands to be the full message", () => { From 713eb898f6d7f4ddbe5f3aa5b2d382b8cece3ea5 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:41:09 -0500 Subject: [PATCH 3/9] Removed compact command changes from elsewhere --- src/auto-reply/commands-registry.test.ts | 3 --- src/auto-reply/commands-registry.ts | 8 -------- src/auto-reply/status.test.ts | 6 ------ 3 files changed, 17 deletions(-) diff --git a/src/auto-reply/commands-registry.test.ts b/src/auto-reply/commands-registry.test.ts index ca17e4913..cdb83cd76 100644 --- a/src/auto-reply/commands-registry.test.ts +++ b/src/auto-reply/commands-registry.test.ts @@ -17,7 +17,6 @@ describe("commands registry", () => { const specs = listNativeCommandSpecs(); expect(specs.find((spec) => spec.name === "help")).toBeTruthy(); expect(specs.find((spec) => spec.name === "stop")).toBeTruthy(); - expect(specs.find((spec) => spec.name === "compact")).toBeFalsy(); }); it("detects known text commands", () => { @@ -26,8 +25,6 @@ describe("commands registry", () => { expect(detection.exact.has("/commands")).toBe(true); expect(detection.regex.test("/status")).toBe(true); expect(detection.regex.test("/status:")).toBe(true); - expect(detection.regex.test("/compact")).toBe(true); - expect(detection.regex.test("/compact:")).toBe(true); expect(detection.regex.test("/stop")).toBe(true); expect(detection.regex.test("/send:")).toBe(true); expect(detection.regex.test("try /status")).toBe(false); diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 5e252f0b7..1f950083f 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -72,14 +72,6 @@ const CHAT_COMMANDS: ChatCommandDefinition[] = [ description: "Start a new session.", textAliases: ["/new"], }, - { - key: "compact", - nativeName: "compact", - description: "Compact the current session context.", - textAliases: ["/compact"], - acceptsArgs: true, - supportsNative: false, - }, { key: "think", nativeName: "think", diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index f6d482318..a84a51b7b 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -239,11 +239,5 @@ describe("buildCommandsMessage", () => { it("lists commands with aliases and text-only hints", () => { const text = buildCommandsMessage(); expect(text).toContain("/commands - List all slash commands."); - expect(text).toContain( - "/think (aliases: /thinking, /t) - Set thinking level.", - ); - expect(text).toContain( - "/compact (text-only) - Compact the current session context.", - ); }); }); From 6c8b2a8dc9b9e7b7632294e7cbb07d6b18272dd5 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 22:14:24 -0500 Subject: [PATCH 4/9] Auto-reply: organize status imports --- src/auto-reply/status.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 6a6d82e73..9cba16582 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -20,14 +20,14 @@ import { type SessionScope, } from "../config/sessions.js"; import { resolveCommitHash } from "../infra/git-commit.js"; -import { VERSION } from "../version.js"; -import { listChatCommands } from "./commands-registry.js"; import { estimateUsageCost, formatTokenCount as formatTokenCountShared, formatUsd, resolveModelCostConfig, } from "../utils/usage-format.js"; +import { VERSION } from "../version.js"; +import { listChatCommands } from "./commands-registry.js"; import type { ElevatedLevel, ReasoningLevel, From 8aa80fa464ed5bc4446109533f5f074cfb5e5951 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 22:48:36 -0500 Subject: [PATCH 5/9] Auto-reply: tidy help message --- src/auto-reply/status.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index ba854686c..c8401a663 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -20,14 +20,12 @@ import { type SessionEntry, type SessionScope, } from "../config/sessions.js"; -import { resolveCommitHash } from "../infra/git-commit.js"; import { estimateUsageCost, formatTokenCount as formatTokenCountShared, formatUsd, resolveModelCostConfig, } from "../utils/usage-format.js"; -import { VERSION } from "../version.js"; import { listChatCommands } from "./commands-registry.js"; import type { ElevatedLevel, @@ -350,7 +348,7 @@ export function buildHelpMessage(): string { "ℹ️ Help", "Shortcuts: /new reset | /compact [instructions] | /restart relink", "Options: /think | /verbose on|off | /reasoning on|off | /elevated on|off | /model | /cost on|off", - "More: /commands for all slash commands" + "More: /commands for all slash commands", ].join("\n"); } From 1309cee1245ce43b91fb5200681f405791f389a8 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 22:51:35 -0500 Subject: [PATCH 6/9] Auto-reply: fix status command lint --- src/auto-reply/reply/commands.ts | 1 - src/auto-reply/status.test.ts | 2 -- src/auto-reply/status.ts | 2 ++ 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 7876242ee..9887322df 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -567,7 +567,6 @@ export async function handleCommands(params: { const reply = await buildStatusReply({ cfg, command, - provider: command.provider, sessionEntry, sessionKey, sessionScope, diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index cf3db66d0..f6cd7dd48 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -65,7 +65,6 @@ describe("buildStatusMessage", () => { }, }, }, - }, } as ClawdbotConfig, agent: { model: "anthropic/pi:opus", @@ -249,7 +248,6 @@ describe("buildStatusMessage", () => { }, }, }, - }, } as ClawdbotConfig, agent: { model: "anthropic/claude-opus-4-5" }, sessionEntry: { sessionId: "c1", updatedAt: 0, inputTokens: 10 }, diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index c8401a663..3251e0de9 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -20,12 +20,14 @@ import { type SessionEntry, type SessionScope, } from "../config/sessions.js"; +import { resolveCommitHash } from "../infra/git-commit.js"; import { estimateUsageCost, formatTokenCount as formatTokenCountShared, formatUsd, resolveModelCostConfig, } from "../utils/usage-format.js"; +import { VERSION } from "../version.js"; import { listChatCommands } from "./commands-registry.js"; import type { ElevatedLevel, From 16f893c46b103be138c72ad15b6e088b6ecb4562 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:12:37 -0500 Subject: [PATCH 7/9] Tests: align google shared expectations --- src/auto-reply/status.ts | 4 ++- src/cli/daemon-cli.ts | 11 ++++--- src/providers/google-shared.test.ts | 45 +++++++++++++++++++---------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 23f337e69..85dd8c301 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -330,7 +330,9 @@ export function buildStatusMessage(args: StatusArgs): string { const usagePair = formatUsagePair(inputTokens, outputTokens); const costLine = costLabel ? `💵 Cost: ${costLabel}` : null; const usageCostLine = - usagePair && costLine ? `${usagePair} · ${costLine}` : usagePair ?? costLine; + usagePair && costLine + ? `${usagePair} · ${costLine}` + : (usagePair ?? costLine); return [ versionLine, diff --git a/src/cli/daemon-cli.ts b/src/cli/daemon-cli.ts index fbc3594a1..f6021597d 100644 --- a/src/cli/daemon-cli.ts +++ b/src/cli/daemon-cli.ts @@ -672,7 +672,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { service.runtime?.status === "running" ) { defaultRuntime.log( - warnText("Warm-up: launch agents can take a few seconds. Try again shortly."), + warnText( + "Warm-up: launch agents can take a few seconds. Try again shortly.", + ), ); } if (rpc) { @@ -680,8 +682,7 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { defaultRuntime.log(`${label("RPC probe:")} ${okText("ok")}`); } else { defaultRuntime.error(`${label("RPC probe:")} ${errorText("failed")}`); - if (rpc.url) - defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); + if (rpc.url) defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); const lines = String(rpc.error ?? "unknown") .split(/\r?\n/) .filter(Boolean); @@ -698,7 +699,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { } } else if (service.loaded && service.runtime?.status === "stopped") { defaultRuntime.error( - errorText("Service is loaded but not running (likely exited immediately)."), + errorText( + "Service is loaded but not running (likely exited immediately).", + ), ); for (const hint of renderRuntimeHints( service.runtime, diff --git a/src/providers/google-shared.test.ts b/src/providers/google-shared.test.ts index 9bf2608cc..796b39ce4 100644 --- a/src/providers/google-shared.test.ts +++ b/src/providers/google-shared.test.ts @@ -46,7 +46,7 @@ describe("google-shared convertTools", () => { converted?.[0]?.functionDeclarations?.[0]?.parameters, ); - expect(params.type).toBe("object"); + expect(params.type).toBeUndefined(); expect(params.properties).toBeDefined(); expect(params.required).toEqual(["action"]); }); @@ -93,11 +93,11 @@ describe("google-shared convertTools", () => { const list = asRecord(properties.list); const items = asRecord(list.items); - expect(params.patternProperties).toBeUndefined(); - expect(params.additionalProperties).toBeUndefined(); - expect(mode.const).toBeUndefined(); - expect(options.anyOf).toBeUndefined(); - expect(items.const).toBeUndefined(); + expect(params.patternProperties).toEqual({ "^x-": { type: "string" } }); + expect(params.additionalProperties).toBe(false); + expect(mode.const).toBe("fast"); + expect(options.anyOf).toEqual([{ type: "string" }, { type: "number" }]); + expect(items.const).toBe("item"); expect(params.required).toEqual(["mode"]); }); @@ -184,7 +184,12 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(0); + expect(contents).toHaveLength(1); + expect(contents[0].role).toBe("model"); + expect(contents[0].parts?.[0]).toMatchObject({ + thought: true, + thoughtSignature: "sig", + }); }); it("keeps thought signatures for Claude models", () => { @@ -248,9 +253,11 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(1); + expect(contents).toHaveLength(2); expect(contents[0].role).toBe("user"); - expect(contents[0].parts).toHaveLength(2); + expect(contents[1].role).toBe("user"); + expect(contents[0].parts).toHaveLength(1); + expect(contents[1].parts).toHaveLength(1); }); it("does not merge consecutive user messages for non-Gemini Google models", () => { @@ -269,9 +276,11 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(1); + expect(contents).toHaveLength(2); expect(contents[0].role).toBe("user"); - expect(contents[0].parts).toHaveLength(2); + expect(contents[1].role).toBe("user"); + expect(contents[0].parts).toHaveLength(1); + expect(contents[1].parts).toHaveLength(1); }); it("does not merge consecutive model messages for Gemini", () => { @@ -332,10 +341,12 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(2); + expect(contents).toHaveLength(3); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); - expect(contents[1].parts).toHaveLength(2); + expect(contents[2].role).toBe("model"); + expect(contents[1].parts).toHaveLength(1); + expect(contents[2].parts).toHaveLength(1); }); it("handles user message after tool result without model response in between", () => { @@ -392,10 +403,11 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(3); + expect(contents).toHaveLength(4); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); expect(contents[2].role).toBe("user"); + expect(contents[3].role).toBe("user"); const toolResponsePart = contents[2].parts?.find( (part) => typeof part === "object" && part !== null && "functionResponse" in part, @@ -469,10 +481,11 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(2); + expect(contents).toHaveLength(3); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); - const toolCallPart = contents[1].parts?.find( + expect(contents[2].role).toBe("model"); + const toolCallPart = contents[2].parts?.find( (part) => typeof part === "object" && part !== null && "functionCall" in part, ); From 5d34e270782057f1456f68afdd55ce82609d34dd Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:19:44 -0500 Subject: [PATCH 8/9] Revert: non-PR file edits --- src/auto-reply/status.ts | 4 +-- src/cli/daemon-cli.ts | 11 +++---- src/providers/google-shared.test.ts | 45 ++++++++++------------------- 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 85dd8c301..23f337e69 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -330,9 +330,7 @@ export function buildStatusMessage(args: StatusArgs): string { const usagePair = formatUsagePair(inputTokens, outputTokens); const costLine = costLabel ? `💵 Cost: ${costLabel}` : null; const usageCostLine = - usagePair && costLine - ? `${usagePair} · ${costLine}` - : (usagePair ?? costLine); + usagePair && costLine ? `${usagePair} · ${costLine}` : usagePair ?? costLine; return [ versionLine, diff --git a/src/cli/daemon-cli.ts b/src/cli/daemon-cli.ts index f6021597d..fbc3594a1 100644 --- a/src/cli/daemon-cli.ts +++ b/src/cli/daemon-cli.ts @@ -672,9 +672,7 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { service.runtime?.status === "running" ) { defaultRuntime.log( - warnText( - "Warm-up: launch agents can take a few seconds. Try again shortly.", - ), + warnText("Warm-up: launch agents can take a few seconds. Try again shortly."), ); } if (rpc) { @@ -682,7 +680,8 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { defaultRuntime.log(`${label("RPC probe:")} ${okText("ok")}`); } else { defaultRuntime.error(`${label("RPC probe:")} ${errorText("failed")}`); - if (rpc.url) defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); + if (rpc.url) + defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); const lines = String(rpc.error ?? "unknown") .split(/\r?\n/) .filter(Boolean); @@ -699,9 +698,7 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) { } } else if (service.loaded && service.runtime?.status === "stopped") { defaultRuntime.error( - errorText( - "Service is loaded but not running (likely exited immediately).", - ), + errorText("Service is loaded but not running (likely exited immediately)."), ); for (const hint of renderRuntimeHints( service.runtime, diff --git a/src/providers/google-shared.test.ts b/src/providers/google-shared.test.ts index 796b39ce4..9bf2608cc 100644 --- a/src/providers/google-shared.test.ts +++ b/src/providers/google-shared.test.ts @@ -46,7 +46,7 @@ describe("google-shared convertTools", () => { converted?.[0]?.functionDeclarations?.[0]?.parameters, ); - expect(params.type).toBeUndefined(); + expect(params.type).toBe("object"); expect(params.properties).toBeDefined(); expect(params.required).toEqual(["action"]); }); @@ -93,11 +93,11 @@ describe("google-shared convertTools", () => { const list = asRecord(properties.list); const items = asRecord(list.items); - expect(params.patternProperties).toEqual({ "^x-": { type: "string" } }); - expect(params.additionalProperties).toBe(false); - expect(mode.const).toBe("fast"); - expect(options.anyOf).toEqual([{ type: "string" }, { type: "number" }]); - expect(items.const).toBe("item"); + expect(params.patternProperties).toBeUndefined(); + expect(params.additionalProperties).toBeUndefined(); + expect(mode.const).toBeUndefined(); + expect(options.anyOf).toBeUndefined(); + expect(items.const).toBeUndefined(); expect(params.required).toEqual(["mode"]); }); @@ -184,12 +184,7 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(1); - expect(contents[0].role).toBe("model"); - expect(contents[0].parts?.[0]).toMatchObject({ - thought: true, - thoughtSignature: "sig", - }); + expect(contents).toHaveLength(0); }); it("keeps thought signatures for Claude models", () => { @@ -253,11 +248,9 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(2); + expect(contents).toHaveLength(1); expect(contents[0].role).toBe("user"); - expect(contents[1].role).toBe("user"); - expect(contents[0].parts).toHaveLength(1); - expect(contents[1].parts).toHaveLength(1); + expect(contents[0].parts).toHaveLength(2); }); it("does not merge consecutive user messages for non-Gemini Google models", () => { @@ -276,11 +269,9 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(2); + expect(contents).toHaveLength(1); expect(contents[0].role).toBe("user"); - expect(contents[1].role).toBe("user"); - expect(contents[0].parts).toHaveLength(1); - expect(contents[1].parts).toHaveLength(1); + expect(contents[0].parts).toHaveLength(2); }); it("does not merge consecutive model messages for Gemini", () => { @@ -341,12 +332,10 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(3); + expect(contents).toHaveLength(2); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); - expect(contents[2].role).toBe("model"); - expect(contents[1].parts).toHaveLength(1); - expect(contents[2].parts).toHaveLength(1); + expect(contents[1].parts).toHaveLength(2); }); it("handles user message after tool result without model response in between", () => { @@ -403,11 +392,10 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(4); + expect(contents).toHaveLength(3); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); expect(contents[2].role).toBe("user"); - expect(contents[3].role).toBe("user"); const toolResponsePart = contents[2].parts?.find( (part) => typeof part === "object" && part !== null && "functionResponse" in part, @@ -481,11 +469,10 @@ describe("google-shared convertMessages", () => { } as unknown as Context; const contents = convertMessages(model, context); - expect(contents).toHaveLength(3); + expect(contents).toHaveLength(2); expect(contents[0].role).toBe("user"); expect(contents[1].role).toBe("model"); - expect(contents[2].role).toBe("model"); - const toolCallPart = contents[2].parts?.find( + const toolCallPart = contents[1].parts?.find( (part) => typeof part === "object" && part !== null && "functionCall" in part, ); From 5f4df5e336e54eaf1c9394b749bc9325c0668722 Mon Sep 17 00:00:00 2001 From: "Luke K (pr-0f3t)" <2609441+lc0rp@users.noreply.github.com> Date: Fri, 9 Jan 2026 04:14:22 -0500 Subject: [PATCH 9/9] Auto-reply: drop native command flag --- src/auto-reply/commands-registry.ts | 5 +---- src/auto-reply/status.ts | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 795873b27..edaec3225 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -6,7 +6,6 @@ export type ChatCommandDefinition = { description: string; textAliases: string[]; acceptsArgs?: boolean; - supportsNative?: boolean; }; export type NativeCommandSpec = { @@ -141,9 +140,7 @@ export function listChatCommands(): ChatCommandDefinition[] { } export function listNativeCommandSpecs(): NativeCommandSpec[] { - return CHAT_COMMANDS.filter( - (command) => command.supportsNative !== false, - ).map((command) => ({ + return CHAT_COMMANDS.map((command) => ({ name: command.nativeName, description: command.description, acceptsArgs: Boolean(command.acceptsArgs), diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index a971d9735..1aeb3d933 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -375,8 +375,7 @@ export function buildCommandsMessage(): string { const aliasLabel = aliases.length ? ` (aliases: ${aliases.join(", ")})` : ""; - const scopeLabel = command.supportsNative === false ? " (text-only)" : ""; - lines.push(`${primary}${aliasLabel}${scopeLabel} - ${command.description}`); + lines.push(`${primary}${aliasLabel} - ${command.description}`); } return lines.join("\n"); }