feat: add /reasoning reasoning visibility
This commit is contained in:
@@ -79,6 +79,13 @@ const CHAT_COMMANDS: ChatCommandDefinition[] = [
|
||||
textAliases: ["/verbose", "/v"],
|
||||
acceptsArgs: true,
|
||||
},
|
||||
{
|
||||
key: "reasoning",
|
||||
nativeName: "reasoning",
|
||||
description: "Toggle reasoning visibility.",
|
||||
textAliases: ["/reasoning", "/reason"],
|
||||
acceptsArgs: true,
|
||||
},
|
||||
{
|
||||
key: "elevated",
|
||||
nativeName: "elevated",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { drainSystemEvents } from "../infra/system-events.js";
|
||||
import {
|
||||
extractElevatedDirective,
|
||||
extractQueueDirective,
|
||||
extractReasoningDirective,
|
||||
extractReplyToTag,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
@@ -99,6 +100,12 @@ describe("directive parsing", () => {
|
||||
expect(res.verboseLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches reasoning directive", () => {
|
||||
const res = extractReasoningDirective("/reasoning on please");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.reasoningLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches elevated with leading space", () => {
|
||||
const res = extractElevatedDirective(" please /elevated on now");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
|
||||
@@ -66,6 +66,7 @@ import type { MsgContext, TemplateContext } from "./templating.js";
|
||||
import {
|
||||
type ElevatedLevel,
|
||||
normalizeThinkLevel,
|
||||
type ReasoningLevel,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "./thinking.js";
|
||||
@@ -75,6 +76,7 @@ import type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||
|
||||
export {
|
||||
extractElevatedDirective,
|
||||
extractReasoningDirective,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
} from "./reply/directives.js";
|
||||
@@ -288,6 +290,9 @@ export async function getReplyFromConfig(
|
||||
hasVerboseDirective: false,
|
||||
verboseLevel: undefined,
|
||||
rawVerboseLevel: undefined,
|
||||
hasReasoningDirective: false,
|
||||
reasoningLevel: undefined,
|
||||
rawReasoningLevel: undefined,
|
||||
hasElevatedDirective: false,
|
||||
elevatedLevel: undefined,
|
||||
rawElevatedLevel: undefined,
|
||||
@@ -310,6 +315,7 @@ export async function getReplyFromConfig(
|
||||
const hasDirective =
|
||||
parsedDirectives.hasThinkDirective ||
|
||||
parsedDirectives.hasVerboseDirective ||
|
||||
parsedDirectives.hasReasoningDirective ||
|
||||
parsedDirectives.hasElevatedDirective ||
|
||||
parsedDirectives.hasStatusDirective ||
|
||||
parsedDirectives.hasModelDirective ||
|
||||
@@ -327,6 +333,7 @@ export async function getReplyFromConfig(
|
||||
...parsedDirectives,
|
||||
hasThinkDirective: false,
|
||||
hasVerboseDirective: false,
|
||||
hasReasoningDirective: false,
|
||||
hasStatusDirective: false,
|
||||
hasModelDirective: false,
|
||||
hasQueueDirective: false,
|
||||
@@ -377,6 +384,10 @@ export async function getReplyFromConfig(
|
||||
(directives.verboseLevel as VerboseLevel | undefined) ??
|
||||
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||
(agentCfg?.verboseDefault as VerboseLevel | undefined);
|
||||
const resolvedReasoningLevel: ReasoningLevel =
|
||||
(directives.reasoningLevel as ReasoningLevel | undefined) ??
|
||||
(sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ??
|
||||
"off";
|
||||
const resolvedElevatedLevel = elevatedAllowed
|
||||
? ((directives.elevatedLevel as ElevatedLevel | undefined) ??
|
||||
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
|
||||
@@ -542,6 +553,7 @@ export async function getReplyFromConfig(
|
||||
defaultGroupActivation: () => defaultActivation,
|
||||
resolvedThinkLevel,
|
||||
resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
|
||||
resolvedReasoningLevel,
|
||||
resolvedElevatedLevel,
|
||||
resolveDefaultThinkingLevel: modelState.resolveDefaultThinkingLevel,
|
||||
provider,
|
||||
@@ -734,6 +746,7 @@ export async function getReplyFromConfig(
|
||||
authProfileId,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
reasoningLevel: resolvedReasoningLevel,
|
||||
elevatedLevel: resolvedElevatedLevel,
|
||||
bashElevated: {
|
||||
enabled: elevatedEnabled,
|
||||
|
||||
@@ -221,6 +221,7 @@ export async function runReplyAgent(params: {
|
||||
authProfileId: followupRun.run.authProfileId,
|
||||
thinkLevel: followupRun.run.thinkLevel,
|
||||
verboseLevel: followupRun.run.verboseLevel,
|
||||
reasoningLevel: followupRun.run.reasoningLevel,
|
||||
bashElevated: followupRun.run.bashElevated,
|
||||
timeoutMs: followupRun.run.timeoutMs,
|
||||
runId,
|
||||
|
||||
@@ -41,7 +41,12 @@ import {
|
||||
formatTokenCount,
|
||||
} from "../status.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
|
||||
import type {
|
||||
ElevatedLevel,
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
VerboseLevel,
|
||||
} from "../thinking.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { isAbortTrigger, setAbortMemory } from "./abort.js";
|
||||
import type { InlineDirectives } from "./directive-handling.js";
|
||||
@@ -202,6 +207,7 @@ export async function handleCommands(params: {
|
||||
defaultGroupActivation: () => "always" | "mention";
|
||||
resolvedThinkLevel?: ThinkLevel;
|
||||
resolvedVerboseLevel: VerboseLevel;
|
||||
resolvedReasoningLevel: ReasoningLevel;
|
||||
resolvedElevatedLevel?: ElevatedLevel;
|
||||
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
|
||||
provider: string;
|
||||
@@ -226,6 +232,7 @@ export async function handleCommands(params: {
|
||||
defaultGroupActivation,
|
||||
resolvedThinkLevel,
|
||||
resolvedVerboseLevel,
|
||||
resolvedReasoningLevel,
|
||||
resolvedElevatedLevel,
|
||||
resolveDefaultThinkingLevel,
|
||||
provider,
|
||||
@@ -405,6 +412,7 @@ export async function handleCommands(params: {
|
||||
resolvedThink:
|
||||
resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
|
||||
resolvedVerbose: resolvedVerboseLevel,
|
||||
resolvedReasoning: resolvedReasoningLevel,
|
||||
resolvedElevated: resolvedElevatedLevel,
|
||||
modelAuth: resolveModelAuthLabel(provider, cfg),
|
||||
webLinked,
|
||||
|
||||
@@ -32,9 +32,11 @@ import type { ReplyPayload } from "../types.js";
|
||||
import {
|
||||
type ElevatedLevel,
|
||||
extractElevatedDirective,
|
||||
extractReasoningDirective,
|
||||
extractStatusDirective,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
type ReasoningLevel,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "./directives.js";
|
||||
@@ -155,6 +157,9 @@ export type InlineDirectives = {
|
||||
hasVerboseDirective: boolean;
|
||||
verboseLevel?: VerboseLevel;
|
||||
rawVerboseLevel?: string;
|
||||
hasReasoningDirective: boolean;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
rawReasoningLevel?: string;
|
||||
hasElevatedDirective: boolean;
|
||||
elevatedLevel?: ElevatedLevel;
|
||||
rawElevatedLevel?: string;
|
||||
@@ -188,12 +193,18 @@ export function parseInlineDirectives(body: string): InlineDirectives {
|
||||
rawLevel: rawVerboseLevel,
|
||||
hasDirective: hasVerboseDirective,
|
||||
} = extractVerboseDirective(thinkCleaned);
|
||||
const {
|
||||
cleaned: reasoningCleaned,
|
||||
reasoningLevel,
|
||||
rawLevel: rawReasoningLevel,
|
||||
hasDirective: hasReasoningDirective,
|
||||
} = extractReasoningDirective(verboseCleaned);
|
||||
const {
|
||||
cleaned: elevatedCleaned,
|
||||
elevatedLevel,
|
||||
rawLevel: rawElevatedLevel,
|
||||
hasDirective: hasElevatedDirective,
|
||||
} = extractElevatedDirective(verboseCleaned);
|
||||
} = extractElevatedDirective(reasoningCleaned);
|
||||
const { cleaned: statusCleaned, hasDirective: hasStatusDirective } =
|
||||
extractStatusDirective(elevatedCleaned);
|
||||
const {
|
||||
@@ -225,6 +236,9 @@ export function parseInlineDirectives(body: string): InlineDirectives {
|
||||
hasVerboseDirective,
|
||||
verboseLevel,
|
||||
rawVerboseLevel,
|
||||
hasReasoningDirective,
|
||||
reasoningLevel,
|
||||
rawReasoningLevel,
|
||||
hasElevatedDirective,
|
||||
elevatedLevel,
|
||||
rawElevatedLevel,
|
||||
@@ -257,6 +271,7 @@ export function isDirectiveOnly(params: {
|
||||
if (
|
||||
!directives.hasThinkDirective &&
|
||||
!directives.hasVerboseDirective &&
|
||||
!directives.hasReasoningDirective &&
|
||||
!directives.hasElevatedDirective &&
|
||||
!directives.hasModelDirective &&
|
||||
!directives.hasQueueDirective
|
||||
@@ -367,6 +382,11 @@ export async function handleDirectiveOnly(params: {
|
||||
text: `Unrecognized verbose level "${directives.rawVerboseLevel ?? ""}". Valid levels: off, on.`,
|
||||
};
|
||||
}
|
||||
if (directives.hasReasoningDirective && !directives.reasoningLevel) {
|
||||
return {
|
||||
text: `Unrecognized reasoning level "${directives.rawReasoningLevel ?? ""}". Valid levels: on, off.`,
|
||||
};
|
||||
}
|
||||
if (directives.hasElevatedDirective && !directives.elevatedLevel) {
|
||||
return {
|
||||
text: `Unrecognized elevated level "${directives.rawElevatedLevel ?? ""}". Valid levels: off, on.`,
|
||||
@@ -476,6 +496,11 @@ export async function handleDirectiveOnly(params: {
|
||||
if (directives.verboseLevel === "off") delete sessionEntry.verboseLevel;
|
||||
else sessionEntry.verboseLevel = directives.verboseLevel;
|
||||
}
|
||||
if (directives.hasReasoningDirective && directives.reasoningLevel) {
|
||||
if (directives.reasoningLevel === "off")
|
||||
delete sessionEntry.reasoningLevel;
|
||||
else sessionEntry.reasoningLevel = directives.reasoningLevel;
|
||||
}
|
||||
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
||||
if (directives.elevatedLevel === "off") delete sessionEntry.elevatedLevel;
|
||||
else sessionEntry.elevatedLevel = directives.elevatedLevel;
|
||||
@@ -533,6 +558,13 @@ export async function handleDirectiveOnly(params: {
|
||||
: `${SYSTEM_MARK} Verbose logging enabled.`,
|
||||
);
|
||||
}
|
||||
if (directives.hasReasoningDirective && directives.reasoningLevel) {
|
||||
parts.push(
|
||||
directives.reasoningLevel === "off"
|
||||
? `${SYSTEM_MARK} Reasoning visibility disabled.`
|
||||
: `${SYSTEM_MARK} Reasoning visibility enabled.`,
|
||||
);
|
||||
}
|
||||
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
||||
parts.push(
|
||||
directives.elevatedLevel === "off"
|
||||
@@ -634,6 +666,14 @@ export async function persistInlineDirectives(params: {
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
if (directives.hasReasoningDirective && directives.reasoningLevel) {
|
||||
if (directives.reasoningLevel === "off") {
|
||||
delete sessionEntry.reasoningLevel;
|
||||
} else {
|
||||
sessionEntry.reasoningLevel = directives.reasoningLevel;
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
if (
|
||||
directives.hasElevatedDirective &&
|
||||
directives.elevatedLevel &&
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { ReasoningLevel } from "../thinking.js";
|
||||
import {
|
||||
type ElevatedLevel,
|
||||
normalizeElevatedLevel,
|
||||
normalizeReasoningLevel,
|
||||
normalizeThinkLevel,
|
||||
normalizeVerboseLevel,
|
||||
type ThinkLevel,
|
||||
@@ -74,6 +76,28 @@ export function extractElevatedDirective(body?: string): {
|
||||
};
|
||||
}
|
||||
|
||||
export function extractReasoningDirective(body?: string): {
|
||||
cleaned: string;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
rawLevel?: string;
|
||||
hasDirective: boolean;
|
||||
} {
|
||||
if (!body) return { cleaned: "", hasDirective: false };
|
||||
const match = body.match(
|
||||
/(?:^|\s)\/(?:reasoning|reason)(?=$|\s|:)\s*:?\s*([a-zA-Z-]+)\b/i,
|
||||
);
|
||||
const reasoningLevel = normalizeReasoningLevel(match?.[1]);
|
||||
const cleaned = match
|
||||
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
|
||||
: body.trim();
|
||||
return {
|
||||
cleaned,
|
||||
reasoningLevel,
|
||||
rawLevel: match?.[1],
|
||||
hasDirective: !!match,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractStatusDirective(body?: string): {
|
||||
cleaned: string;
|
||||
hasDirective: boolean;
|
||||
@@ -89,4 +113,4 @@ export function extractStatusDirective(body?: string): {
|
||||
};
|
||||
}
|
||||
|
||||
export type { ElevatedLevel, ThinkLevel, VerboseLevel };
|
||||
export type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel };
|
||||
|
||||
@@ -131,6 +131,7 @@ export function createFollowupRunner(params: {
|
||||
authProfileId: queued.run.authProfileId,
|
||||
thinkLevel: queued.run.thinkLevel,
|
||||
verboseLevel: queued.run.verboseLevel,
|
||||
reasoningLevel: queued.run.reasoningLevel,
|
||||
bashElevated: queued.run.bashElevated,
|
||||
timeoutMs: queued.run.timeoutMs,
|
||||
runId,
|
||||
|
||||
@@ -4,7 +4,12 @@ import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { OriginatingChannelType } from "../templating.js";
|
||||
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "./directives.js";
|
||||
import type {
|
||||
ElevatedLevel,
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
VerboseLevel,
|
||||
} from "./directives.js";
|
||||
import { isRoutableChannel } from "./route-reply.js";
|
||||
export type QueueMode =
|
||||
| "steer"
|
||||
@@ -54,6 +59,7 @@ export type FollowupRun = {
|
||||
authProfileId?: string;
|
||||
thinkLevel?: ThinkLevel;
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
elevatedLevel?: ElevatedLevel;
|
||||
bashElevated?: {
|
||||
enabled: boolean;
|
||||
|
||||
@@ -20,7 +20,12 @@ import {
|
||||
type SessionScope,
|
||||
} from "../config/sessions.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "./thinking.js";
|
||||
import type {
|
||||
ElevatedLevel,
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
VerboseLevel,
|
||||
} from "./thinking.js";
|
||||
|
||||
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
|
||||
|
||||
@@ -34,6 +39,7 @@ type StatusArgs = {
|
||||
groupActivation?: "mention" | "always";
|
||||
resolvedThink?: ThinkLevel;
|
||||
resolvedVerbose?: VerboseLevel;
|
||||
resolvedReasoning?: ReasoningLevel;
|
||||
resolvedElevated?: ElevatedLevel;
|
||||
modelAuth?: string;
|
||||
now?: number;
|
||||
@@ -173,6 +179,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
const thinkLevel = args.resolvedThink ?? args.agent?.thinkingDefault ?? "off";
|
||||
const verboseLevel =
|
||||
args.resolvedVerbose ?? args.agent?.verboseDefault ?? "off";
|
||||
const reasoningLevel = args.resolvedReasoning ?? "off";
|
||||
const elevatedLevel =
|
||||
args.resolvedElevated ??
|
||||
args.sessionEntry?.elevatedLevel ??
|
||||
@@ -241,8 +248,8 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
)}${entry?.abortedLastRun ? " • last run aborted" : ""}`;
|
||||
|
||||
const optionsLine = runtime.sandboxed
|
||||
? `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | elevated=${elevatedLevel}`
|
||||
: `Options: thinking=${thinkLevel} | verbose=${verboseLevel}`;
|
||||
? `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | reasoning=${reasoningLevel} | elevated=${elevatedLevel}`
|
||||
: `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | reasoning=${reasoningLevel}`;
|
||||
|
||||
const modelLabel = model ? `${provider}/${model}` : "unknown";
|
||||
|
||||
@@ -273,6 +280,6 @@ export function buildHelpMessage(): string {
|
||||
return [
|
||||
"ℹ️ Help",
|
||||
"Shortcuts: /new reset | /compact [instructions] | /restart relink",
|
||||
"Options: /think <level> | /verbose on|off | /elevated on|off | /model <id>",
|
||||
"Options: /think <level> | /verbose on|off | /reasoning on|off | /elevated on|off | /model <id>",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeThinkLevel } from "./thinking.js";
|
||||
import { normalizeReasoningLevel, normalizeThinkLevel } from "./thinking.js";
|
||||
|
||||
describe("normalizeThinkLevel", () => {
|
||||
it("accepts mid as medium", () => {
|
||||
expect(normalizeThinkLevel("mid")).toBe("medium");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeReasoningLevel", () => {
|
||||
it("accepts on/off", () => {
|
||||
expect(normalizeReasoningLevel("on")).toBe("on");
|
||||
expect(normalizeReasoningLevel("off")).toBe("off");
|
||||
});
|
||||
|
||||
it("accepts show/hide", () => {
|
||||
expect(normalizeReasoningLevel("show")).toBe("on");
|
||||
expect(normalizeReasoningLevel("hide")).toBe("off");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high";
|
||||
export type VerboseLevel = "off" | "on";
|
||||
export type ElevatedLevel = "off" | "on";
|
||||
export type ReasoningLevel = "off" | "on";
|
||||
|
||||
// Normalize user-provided thinking level strings to the canonical enum.
|
||||
export function normalizeThinkLevel(
|
||||
@@ -55,3 +56,31 @@ export function normalizeElevatedLevel(
|
||||
if (["on", "true", "yes", "1"].includes(key)) return "on";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Normalize reasoning visibility flags used to toggle reasoning exposure.
|
||||
export function normalizeReasoningLevel(
|
||||
raw?: string | null,
|
||||
): ReasoningLevel | undefined {
|
||||
if (!raw) return undefined;
|
||||
const key = raw.toLowerCase();
|
||||
if (
|
||||
[
|
||||
"off",
|
||||
"false",
|
||||
"no",
|
||||
"0",
|
||||
"hide",
|
||||
"hidden",
|
||||
"disable",
|
||||
"disabled",
|
||||
].includes(key)
|
||||
)
|
||||
return "off";
|
||||
if (
|
||||
["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(
|
||||
key,
|
||||
)
|
||||
)
|
||||
return "on";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user