fix: enqueue system event on model switch
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
- LM Studio/Ollama replies now require <final> tags; streaming ignores content until <final> begins.
|
- LM Studio/Ollama replies now require <final> tags; streaming ignores content until <final> begins.
|
||||||
- LM Studio responses API: tools payloads no longer include `strict: null`, and LM Studio no longer gets forced `<think>/<final>` tags.
|
- LM Studio responses API: tools payloads no longer include `strict: null`, and LM Studio no longer gets forced `<think>/<final>` tags.
|
||||||
- Identity emoji no longer auto-prefixes replies (set `messages.responsePrefix` explicitly if desired).
|
- 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.
|
||||||
- `process log` pagination is now line-based (omit `offset` to grab the last N lines).
|
- `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 WebChat: assistant bubbles now update correctly when toggling light/dark mode.
|
||||||
- macOS: avoid spawning a duplicate gateway process when an external listener already exists.
|
- macOS: avoid spawning a duplicate gateway process when an external listener already exists.
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
extractVerboseDirective,
|
extractVerboseDirective,
|
||||||
getReplyFromConfig,
|
getReplyFromConfig,
|
||||||
} from "./reply.js";
|
} from "./reply.js";
|
||||||
|
import { drainSystemEvents } from "../infra/system-events.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-reply-"));
|
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-reply-"));
|
||||||
@@ -425,6 +426,36 @@ describe("directive parsing", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("queues a system event when switching models", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
drainSystemEvents();
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
|
await getReplyFromConfig(
|
||||||
|
{ Body: "/model Opus", From: "+1222", To: "+1222" },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
model: "openai/gpt-4.1-mini",
|
||||||
|
workspace: path.join(home, "clawd"),
|
||||||
|
allowedModels: ["openai/gpt-4.1-mini", "anthropic/claude-opus-4-5"],
|
||||||
|
modelAliases: {
|
||||||
|
Opus: "anthropic/claude-opus-4-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
session: { store: storePath },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const events = drainSystemEvents();
|
||||||
|
expect(events).toContain(
|
||||||
|
"Model switched to Opus (anthropic/claude-opus-4-5).",
|
||||||
|
);
|
||||||
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("uses model override for inline /model", async () => {
|
it("uses model override for inline /model", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const storePath = path.join(home, "sessions.json");
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
import { logVerbose } from "../globals.js";
|
import { logVerbose } from "../globals.js";
|
||||||
import { buildProviderSummary } from "../infra/provider-summary.js";
|
import { buildProviderSummary } from "../infra/provider-summary.js";
|
||||||
import { triggerClawdisRestart } from "../infra/restart.js";
|
import { triggerClawdisRestart } from "../infra/restart.js";
|
||||||
|
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||||
import { drainSystemEvents } from "../infra/system-events.js";
|
import { drainSystemEvents } from "../infra/system-events.js";
|
||||||
import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
|
import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
@@ -546,6 +547,10 @@ export async function getReplyFromConfig(
|
|||||||
lookupContextTokens(model) ??
|
lookupContextTokens(model) ??
|
||||||
DEFAULT_CONTEXT_TOKENS;
|
DEFAULT_CONTEXT_TOKENS;
|
||||||
|
|
||||||
|
const initialModelLabel = `${provider}/${model}`;
|
||||||
|
const formatModelSwitchEvent = (label: string, alias?: string) =>
|
||||||
|
alias ? `Model switched to ${alias} (${label}).` : `Model switched to ${label}.`;
|
||||||
|
|
||||||
const directiveOnly = (() => {
|
const directiveOnly = (() => {
|
||||||
if (
|
if (
|
||||||
!hasThinkDirective &&
|
!hasThinkDirective &&
|
||||||
@@ -639,6 +644,12 @@ export async function getReplyFromConfig(
|
|||||||
isDefault,
|
isDefault,
|
||||||
alias: resolved.alias,
|
alias: resolved.alias,
|
||||||
};
|
};
|
||||||
|
const nextLabel = `${modelSelection.provider}/${modelSelection.model}`;
|
||||||
|
if (nextLabel !== initialModelLabel) {
|
||||||
|
enqueueSystemEvent(formatModelSwitchEvent(nextLabel, modelSelection.alias), {
|
||||||
|
contextKey: `model:${nextLabel}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sessionEntry && sessionStore && sessionKey) {
|
if (sessionEntry && sessionStore && sessionKey) {
|
||||||
@@ -745,6 +756,13 @@ export async function getReplyFromConfig(
|
|||||||
}
|
}
|
||||||
provider = resolved.ref.provider;
|
provider = resolved.ref.provider;
|
||||||
model = resolved.ref.model;
|
model = resolved.ref.model;
|
||||||
|
const nextLabel = `${provider}/${model}`;
|
||||||
|
if (nextLabel !== initialModelLabel) {
|
||||||
|
enqueueSystemEvent(
|
||||||
|
formatModelSwitchEvent(nextLabel, resolved.alias),
|
||||||
|
{ contextKey: `model:${nextLabel}` },
|
||||||
|
);
|
||||||
|
}
|
||||||
contextTokens =
|
contextTokens =
|
||||||
agentCfg?.contextTokens ??
|
agentCfg?.contextTokens ??
|
||||||
lookupContextTokens(model) ??
|
lookupContextTokens(model) ??
|
||||||
|
|||||||
Reference in New Issue
Block a user