Merge branch 'main' into commands-list-clean
This commit is contained in:
@@ -27,6 +27,8 @@ describe("commands registry", () => {
|
||||
expect(detection.regex.test("/status:")).toBe(true);
|
||||
expect(detection.regex.test("/stop")).toBe(true);
|
||||
expect(detection.regex.test("/send:")).toBe(true);
|
||||
expect(detection.regex.test("/models")).toBe(true);
|
||||
expect(detection.regex.test("/models list")).toBe(true);
|
||||
expect(detection.regex.test("try /status")).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ const CHAT_COMMANDS: ChatCommandDefinition[] = [
|
||||
key: "model",
|
||||
nativeName: "model",
|
||||
description: "Show or set the model.",
|
||||
textAliases: ["/model"],
|
||||
textAliases: ["/model", "/models"],
|
||||
acceptsArgs: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,6 +10,13 @@ describe("extractModelDirective", () => {
|
||||
expect(result.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("extracts /models with argument", () => {
|
||||
const result = extractModelDirective("/models gpt-5");
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("gpt-5");
|
||||
expect(result.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("extracts /model with provider/model format", () => {
|
||||
const result = extractModelDirective("/model anthropic/claude-opus-4-5");
|
||||
expect(result.hasDirective).toBe(true);
|
||||
|
||||
@@ -14,7 +14,7 @@ export function extractModelDirective(
|
||||
if (!body) return { cleaned: "", hasDirective: false };
|
||||
|
||||
const modelMatch = body.match(
|
||||
/(?:^|\s)\/model(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)?)?/i,
|
||||
/(?:^|\s)\/models?(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)?)?/i,
|
||||
);
|
||||
|
||||
const aliases = (options?.aliases ?? [])
|
||||
|
||||
@@ -181,7 +181,7 @@ describe("trigger handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("restarts even with prefix/whitespace", async () => {
|
||||
it("rejects /restart by default", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
@@ -193,6 +193,24 @@ describe("trigger handling", () => {
|
||||
makeCfg(home),
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("/restart is disabled");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("restarts when enabled", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const cfg = { ...makeCfg(home), commands: { restart: true } };
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/restart",
|
||||
From: "+1001",
|
||||
To: "+2000",
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(
|
||||
text?.startsWith("⚙️ Restarting") ||
|
||||
text?.startsWith("⚠️ Restart failed"),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import { onAgentEvent } from "../../infra/agent-events.js";
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import type { FollowupRun, QueueSettings } from "./queue.js";
|
||||
import { createMockTypingController } from "./test-helpers.js";
|
||||
|
||||
@@ -105,9 +104,7 @@ function createRun() {
|
||||
|
||||
describe("runReplyAgent claude-cli routing", () => {
|
||||
it("uses claude-cli runner for claude-cli provider", async () => {
|
||||
const randomSpy = vi
|
||||
.spyOn(crypto, "randomUUID")
|
||||
.mockReturnValue("run-1");
|
||||
const randomSpy = vi.spyOn(crypto, "randomUUID").mockReturnValue("run-1");
|
||||
const lifecyclePhases: string[] = [];
|
||||
const unsubscribe = onAgentEvent((evt) => {
|
||||
if (evt.runId !== "run-1") return;
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
} from "../../config/sessions.js";
|
||||
import type { TypingMode } from "../../config/types.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
||||
import {
|
||||
emitAgentEvent,
|
||||
registerAgentRunContext,
|
||||
} from "../../infra/agent-events.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import {
|
||||
estimateUsageCost,
|
||||
@@ -352,7 +355,7 @@ export async function runReplyAgent(params: {
|
||||
runId,
|
||||
extraSystemPrompt: followupRun.run.extraSystemPrompt,
|
||||
ownerNumbers: followupRun.run.ownerNumbers,
|
||||
resumeSessionId:
|
||||
claudeSessionId:
|
||||
sessionEntry?.claudeCliSessionId?.trim() || undefined,
|
||||
})
|
||||
.then((result) => {
|
||||
|
||||
@@ -220,16 +220,13 @@ function resolveModelAuthLabel(
|
||||
const providerKey = normalizeProviderId(resolved);
|
||||
const store = ensureAuthProfileStore();
|
||||
const profileOverride = sessionEntry?.authProfileOverride?.trim();
|
||||
const lastGood = store.lastGood?.[providerKey] ?? store.lastGood?.[resolved];
|
||||
const order = resolveAuthProfileOrder({
|
||||
cfg,
|
||||
store,
|
||||
provider: providerKey,
|
||||
preferredProfile: profileOverride,
|
||||
});
|
||||
const candidates = [profileOverride, lastGood, ...order].filter(
|
||||
Boolean,
|
||||
) as string[];
|
||||
const candidates = [profileOverride, ...order].filter(Boolean) as string[];
|
||||
|
||||
for (const profileId of candidates) {
|
||||
const profile = store.profiles[profileId];
|
||||
@@ -240,6 +237,10 @@ function resolveModelAuthLabel(
|
||||
if (profile.type === "oauth") {
|
||||
return `oauth${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
if (profile.type === "token") {
|
||||
const snippet = formatApiKeySnippet(profile.token);
|
||||
return `token ${snippet}${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
const snippet = formatApiKeySnippet(profile.key);
|
||||
return `api-key ${snippet}${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
@@ -508,6 +509,14 @@ export async function handleCommands(params: {
|
||||
);
|
||||
return { shouldContinue: false };
|
||||
}
|
||||
if (cfg.commands?.restart !== true) {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text: "⚠️ /restart is disabled. Set commands.restart=true to enable.",
|
||||
},
|
||||
};
|
||||
}
|
||||
const hasSigusr1Listener = process.listenerCount("SIGUSR1") > 0;
|
||||
if (hasSigusr1Listener) {
|
||||
scheduleGatewaySigusr1Restart({ reason: "/restart" });
|
||||
|
||||
@@ -88,13 +88,18 @@ const resolveAuthLabel = async (
|
||||
!profile ||
|
||||
(configProfile?.provider &&
|
||||
configProfile.provider !== profile.provider) ||
|
||||
(configProfile?.mode && configProfile.mode !== profile.type)
|
||||
(configProfile?.mode &&
|
||||
configProfile.mode !== profile.type &&
|
||||
!(configProfile.mode === "oauth" && profile.type === "token"))
|
||||
) {
|
||||
return `${profileId}=missing`;
|
||||
}
|
||||
if (profile.type === "api_key") {
|
||||
return `${profileId}=${maskApiKey(profile.key)}`;
|
||||
}
|
||||
if (profile.type === "token") {
|
||||
return `${profileId}=token:${maskApiKey(profile.token)}`;
|
||||
}
|
||||
const display = resolveAuthProfileDisplayLabel({
|
||||
cfg,
|
||||
store,
|
||||
|
||||
@@ -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,
|
||||
@@ -349,7 +351,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
export function buildHelpMessage(): string {
|
||||
return [
|
||||
"ℹ️ Help",
|
||||
"Shortcuts: /new reset | /compact [instructions] | /restart relink",
|
||||
"Shortcuts: /new reset | /compact [instructions] | /restart relink (if enabled)",
|
||||
"Options: /think <level> | /verbose on|off | /reasoning on|off | /elevated on|off | /model <id> | /cost on|off",
|
||||
"More: /commands for all slash commands",
|
||||
].join("\n");
|
||||
|
||||
Reference in New Issue
Block a user