From cae78b3f918d4d6c26cdd9c26c4e534d723176a4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 27 Dec 2025 12:10:44 +0000 Subject: [PATCH] fix: treat /model status as model list --- CHANGELOG.md | 1 + src/auto-reply/reply.directive.test.ts | 26 ++++++++++++++++++++++++++ src/auto-reply/reply.ts | 17 +++++++++++------ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acc3f2665..f3de67e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - LM Studio responses API: tools payloads no longer include `strict: null`, and LM Studio no longer gets forced `/` tags. - Identity emoji no longer auto-prefixes replies (set `messages.responsePrefix` explicitly if desired). - Model switches now enqueue a system event so the next run knows the active model. +- `/model status` now lists available models (same as `/model`). - `process log` pagination is now line-based (omit `offset` to grab the last N lines). - macOS WebChat: assistant bubbles now update correctly when toggling light/dark mode. - macOS: avoid spawning a duplicate gateway process when an external listener already exists. diff --git a/src/auto-reply/reply.directive.test.ts b/src/auto-reply/reply.directive.test.ts index 3cef1bb1c..98f65c91a 100644 --- a/src/auto-reply/reply.directive.test.ts +++ b/src/auto-reply/reply.directive.test.ts @@ -366,6 +366,32 @@ describe("directive parsing", () => { }); }); + it("lists allowlisted models on /model status", async () => { + await withTempHome(async (home) => { + vi.mocked(runEmbeddedPiAgent).mockReset(); + const storePath = path.join(home, "sessions.json"); + + const res = await getReplyFromConfig( + { Body: "/model status", From: "+1222", To: "+1222" }, + {}, + { + agent: { + model: "anthropic/claude-opus-4-5", + workspace: path.join(home, "clawd"), + allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"], + }, + session: { store: storePath }, + }, + ); + + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("anthropic/claude-opus-4-5"); + expect(text).toContain("openai/gpt-4.1-mini"); + expect(text).not.toContain("claude-sonnet-4-1"); + expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); + }); + }); + it("sets model override on /model directive", async () => { await withTempHome(async (home) => { vi.mocked(runEmbeddedPiAgent).mockReset(); diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index c612ddc1e..6d14eaa89 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -554,6 +554,11 @@ export async function getReplyFromConfig( alias ? `Model switched to ${alias} (${label}).` : `Model switched to ${label}.`; + const isModelListAlias = + hasModelDirective && rawModelDirective?.trim().toLowerCase() === "status"; + const effectiveModelDirective = isModelListAlias + ? undefined + : rawModelDirective; const directiveOnly = (() => { if ( @@ -569,7 +574,7 @@ export async function getReplyFromConfig( })(); if (directiveOnly) { - if (hasModelDirective && !rawModelDirective) { + if (hasModelDirective && (!rawModelDirective || isModelListAlias)) { if (allowedModelCatalog.length === 0) { cleanupTyping(); return { text: "No models available." }; @@ -620,16 +625,16 @@ export async function getReplyFromConfig( let modelSelection: | { provider: string; model: string; isDefault: boolean; alias?: string } | undefined; - if (hasModelDirective && rawModelDirective) { + if (hasModelDirective && effectiveModelDirective) { const resolved = resolveModelRefFromString({ - raw: rawModelDirective, + raw: effectiveModelDirective, defaultProvider, aliasIndex, }); if (!resolved) { cleanupTyping(); return { - text: `Unrecognized model "${rawModelDirective}". Use /model to list available models.`, + text: `Unrecognized model "${effectiveModelDirective}". Use /model to list available models.`, }; } const key = modelKey(resolved.ref.provider, resolved.ref.model); @@ -742,9 +747,9 @@ export async function getReplyFromConfig( } updated = true; } - if (hasModelDirective && rawModelDirective) { + if (hasModelDirective && effectiveModelDirective) { const resolved = resolveModelRefFromString({ - raw: rawModelDirective, + raw: effectiveModelDirective, defaultProvider, aliasIndex, });