feat: add elevated bash mode
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
} from "../config/sessions.js";
|
||||
import { drainSystemEvents } from "../infra/system-events.js";
|
||||
import {
|
||||
extractElevatedDirective,
|
||||
extractQueueDirective,
|
||||
extractReplyToTag,
|
||||
extractThinkDirective,
|
||||
@@ -85,6 +86,12 @@ describe("directive parsing", () => {
|
||||
expect(res.verboseLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches elevated with leading space", () => {
|
||||
const res = extractElevatedDirective(" please /elevated on now");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
expect(res.elevatedLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("matches think at start of line", () => {
|
||||
const res = extractThinkDirective("/think:high run slow");
|
||||
expect(res.hasDirective).toBe(true);
|
||||
|
||||
@@ -143,6 +143,85 @@ describe("trigger handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows approved sender to toggle elevated mode", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const cfg = {
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
elevated: {
|
||||
allowFrom: { whatsapp: ["+1000"] },
|
||||
},
|
||||
},
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/elevated on",
|
||||
From: "+1000",
|
||||
To: "+2000",
|
||||
Surface: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("Elevated mode enabled");
|
||||
|
||||
const storeRaw = await fs.readFile(cfg.session.store, "utf-8");
|
||||
const store = JSON.parse(storeRaw) as Record<
|
||||
string,
|
||||
{ elevatedLevel?: string }
|
||||
>;
|
||||
expect(store.main?.elevatedLevel).toBe("on");
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects elevated toggles when disabled", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const cfg = {
|
||||
agent: {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
workspace: join(home, "clawd"),
|
||||
elevated: {
|
||||
enabled: false,
|
||||
allowFrom: { whatsapp: ["+1000"] },
|
||||
},
|
||||
},
|
||||
whatsapp: {
|
||||
allowFrom: ["+1000"],
|
||||
},
|
||||
session: { store: join(home, "sessions.json") },
|
||||
};
|
||||
|
||||
const res = await getReplyFromConfig(
|
||||
{
|
||||
Body: "/elevated on",
|
||||
From: "+1000",
|
||||
To: "+2000",
|
||||
Surface: "whatsapp",
|
||||
SenderE164: "+1000",
|
||||
},
|
||||
{},
|
||||
cfg,
|
||||
);
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toBe("elevated is not available right now.");
|
||||
|
||||
const storeRaw = await fs.readFile(cfg.session.store, "utf-8");
|
||||
const store = JSON.parse(storeRaw) as Record<
|
||||
string,
|
||||
{ elevatedLevel?: string }
|
||||
>;
|
||||
expect(store.main?.elevatedLevel).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a context overflow fallback when the embedded agent throws", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockRejectedValue(
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
DEFAULT_AGENT_WORKSPACE_DIR,
|
||||
ensureAgentWorkspace,
|
||||
} from "../agents/workspace.js";
|
||||
import { type ClawdisConfig, loadConfig } from "../config/config.js";
|
||||
import {
|
||||
type AgentElevatedAllowFromConfig,
|
||||
type ClawdisConfig,
|
||||
loadConfig,
|
||||
} from "../config/config.js";
|
||||
import { resolveSessionTranscriptPath } from "../config/sessions.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
|
||||
@@ -47,6 +51,7 @@ import { createTypingController } from "./reply/typing.js";
|
||||
import type { MsgContext } from "./templating.js";
|
||||
import {
|
||||
normalizeThinkLevel,
|
||||
type ElevatedLevel,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "./thinking.js";
|
||||
@@ -55,6 +60,7 @@ import { isAudio, transcribeInboundAudio } from "./transcription.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||
|
||||
export {
|
||||
extractElevatedDirective,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
} from "./reply/directives.js";
|
||||
@@ -65,6 +71,99 @@ export type { GetReplyOptions, ReplyPayload } from "./types.js";
|
||||
const BARE_SESSION_RESET_PROMPT =
|
||||
"A new session was started via /new or /reset. Say hi briefly (1-2 sentences) and ask what the user wants to do next. Do not mention internal steps, files, tools, or reasoning.";
|
||||
|
||||
function normalizeAllowToken(value?: string) {
|
||||
if (!value) return "";
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function slugAllowToken(value?: string) {
|
||||
if (!value) return "";
|
||||
let text = value.trim().toLowerCase();
|
||||
if (!text) return "";
|
||||
text = text.replace(/^[@#]+/, "");
|
||||
text = text.replace(/[\s_]+/g, "-");
|
||||
text = text.replace(/[^a-z0-9-]+/g, "-");
|
||||
return text.replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
function stripSenderPrefix(value?: string) {
|
||||
if (!value) return "";
|
||||
const trimmed = value.trim();
|
||||
return trimmed.replace(
|
||||
/^(whatsapp|telegram|discord|signal|imessage|webchat|user|group|channel):/i,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
function resolveElevatedAllowList(
|
||||
allowFrom: AgentElevatedAllowFromConfig | undefined,
|
||||
surface: string,
|
||||
): Array<string | number> | undefined {
|
||||
switch (surface) {
|
||||
case "whatsapp":
|
||||
return allowFrom?.whatsapp;
|
||||
case "telegram":
|
||||
return allowFrom?.telegram;
|
||||
case "discord":
|
||||
return allowFrom?.discord;
|
||||
case "signal":
|
||||
return allowFrom?.signal;
|
||||
case "imessage":
|
||||
return allowFrom?.imessage;
|
||||
case "webchat":
|
||||
return allowFrom?.webchat;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function isApprovedElevatedSender(params: {
|
||||
surface: string;
|
||||
ctx: MsgContext;
|
||||
allowFrom?: AgentElevatedAllowFromConfig;
|
||||
}): boolean {
|
||||
const rawAllow = resolveElevatedAllowList(params.allowFrom, params.surface);
|
||||
if (!rawAllow || rawAllow.length === 0) return false;
|
||||
|
||||
const allowTokens = rawAllow
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean);
|
||||
if (allowTokens.length === 0) return false;
|
||||
if (allowTokens.some((entry) => entry === "*")) return true;
|
||||
|
||||
const tokens = new Set<string>();
|
||||
const addToken = (value?: string) => {
|
||||
if (!value) return;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return;
|
||||
tokens.add(trimmed);
|
||||
const normalized = normalizeAllowToken(trimmed);
|
||||
if (normalized) tokens.add(normalized);
|
||||
const slugged = slugAllowToken(trimmed);
|
||||
if (slugged) tokens.add(slugged);
|
||||
};
|
||||
|
||||
addToken(params.ctx.SenderName);
|
||||
addToken(params.ctx.SenderE164);
|
||||
addToken(params.ctx.From);
|
||||
addToken(stripSenderPrefix(params.ctx.From));
|
||||
addToken(params.ctx.To);
|
||||
addToken(stripSenderPrefix(params.ctx.To));
|
||||
|
||||
for (const rawEntry of allowTokens) {
|
||||
const entry = rawEntry.trim();
|
||||
if (!entry) continue;
|
||||
const stripped = stripSenderPrefix(entry);
|
||||
if (tokens.has(entry) || tokens.has(stripped)) return true;
|
||||
const normalized = normalizeAllowToken(stripped);
|
||||
if (normalized && tokens.has(normalized)) return true;
|
||||
const slugged = slugAllowToken(stripped);
|
||||
if (slugged && tokens.has(slugged)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function getReplyFromConfig(
|
||||
ctx: MsgContext,
|
||||
opts?: GetReplyOptions,
|
||||
@@ -146,6 +245,27 @@ export async function getReplyFromConfig(
|
||||
sessionCtx.Body = directives.cleaned;
|
||||
sessionCtx.BodyStripped = directives.cleaned;
|
||||
|
||||
const surfaceKey =
|
||||
sessionCtx.Surface?.trim().toLowerCase() ??
|
||||
ctx.Surface?.trim().toLowerCase() ??
|
||||
"";
|
||||
const elevatedConfig = agentCfg?.elevated;
|
||||
const elevatedEnabled = elevatedConfig?.enabled !== false;
|
||||
const elevatedAllowed =
|
||||
elevatedEnabled &&
|
||||
Boolean(
|
||||
surfaceKey &&
|
||||
isApprovedElevatedSender({
|
||||
surface: surfaceKey,
|
||||
ctx,
|
||||
allowFrom: elevatedConfig?.allowFrom,
|
||||
}),
|
||||
);
|
||||
if (directives.hasElevatedDirective && (!elevatedEnabled || !elevatedAllowed)) {
|
||||
typing.cleanup();
|
||||
return { text: "elevated is not available right now." };
|
||||
}
|
||||
|
||||
const requireMention = resolveGroupRequireMention({
|
||||
cfg,
|
||||
ctx: sessionCtx,
|
||||
@@ -161,6 +281,12 @@ export async function getReplyFromConfig(
|
||||
(directives.verboseLevel as VerboseLevel | undefined) ??
|
||||
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||
(agentCfg?.verboseDefault as VerboseLevel | undefined);
|
||||
const resolvedElevatedLevel = elevatedAllowed
|
||||
? ((directives.elevatedLevel as ElevatedLevel | undefined) ??
|
||||
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
|
||||
(agentCfg?.elevatedDefault as ElevatedLevel | undefined) ??
|
||||
"off")
|
||||
: "off";
|
||||
const resolvedBlockStreaming =
|
||||
agentCfg?.blockStreamingDefault === "off" ? "off" : "on";
|
||||
const resolvedBlockStreamingBreak =
|
||||
@@ -220,6 +346,8 @@ export async function getReplyFromConfig(
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
@@ -242,6 +370,8 @@ export async function getReplyFromConfig(
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
@@ -466,6 +596,12 @@ export async function getReplyFromConfig(
|
||||
model,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
elevatedLevel: resolvedElevatedLevel,
|
||||
bashElevated: {
|
||||
enabled: elevatedEnabled,
|
||||
allowed: elevatedAllowed,
|
||||
defaultLevel: resolvedElevatedLevel ?? "off",
|
||||
},
|
||||
timeoutMs,
|
||||
blockReplyBreak: resolvedBlockStreamingBreak,
|
||||
ownerNumbers:
|
||||
|
||||
@@ -187,6 +187,7 @@ export async function runReplyAgent(params: {
|
||||
model: followupRun.run.model,
|
||||
thinkLevel: followupRun.run.thinkLevel,
|
||||
verboseLevel: followupRun.run.verboseLevel,
|
||||
bashElevated: followupRun.run.bashElevated,
|
||||
timeoutMs: followupRun.run.timeoutMs,
|
||||
runId,
|
||||
blockReplyBreak: resolvedBlockStreamingBreak,
|
||||
|
||||
@@ -18,8 +18,10 @@ import { extractModelDirective } from "../model.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import {
|
||||
extractElevatedDirective,
|
||||
extractThinkDirective,
|
||||
extractVerboseDirective,
|
||||
type ElevatedLevel,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "./directives.js";
|
||||
@@ -44,6 +46,9 @@ export type InlineDirectives = {
|
||||
hasVerboseDirective: boolean;
|
||||
verboseLevel?: VerboseLevel;
|
||||
rawVerboseLevel?: string;
|
||||
hasElevatedDirective: boolean;
|
||||
elevatedLevel?: ElevatedLevel;
|
||||
rawElevatedLevel?: string;
|
||||
hasModelDirective: boolean;
|
||||
rawModelDirective?: string;
|
||||
hasQueueDirective: boolean;
|
||||
@@ -72,11 +77,17 @@ export function parseInlineDirectives(body: string): InlineDirectives {
|
||||
rawLevel: rawVerboseLevel,
|
||||
hasDirective: hasVerboseDirective,
|
||||
} = extractVerboseDirective(thinkCleaned);
|
||||
const {
|
||||
cleaned: elevatedCleaned,
|
||||
elevatedLevel,
|
||||
rawLevel: rawElevatedLevel,
|
||||
hasDirective: hasElevatedDirective,
|
||||
} = extractElevatedDirective(verboseCleaned);
|
||||
const {
|
||||
cleaned: modelCleaned,
|
||||
rawModel,
|
||||
hasDirective: hasModelDirective,
|
||||
} = extractModelDirective(verboseCleaned);
|
||||
} = extractModelDirective(elevatedCleaned);
|
||||
const {
|
||||
cleaned: queueCleaned,
|
||||
queueMode,
|
||||
@@ -100,6 +111,9 @@ export function parseInlineDirectives(body: string): InlineDirectives {
|
||||
hasVerboseDirective,
|
||||
verboseLevel,
|
||||
rawVerboseLevel,
|
||||
hasElevatedDirective,
|
||||
elevatedLevel,
|
||||
rawElevatedLevel,
|
||||
hasModelDirective,
|
||||
rawModelDirective: rawModel,
|
||||
hasQueueDirective,
|
||||
@@ -127,6 +141,7 @@ export function isDirectiveOnly(params: {
|
||||
if (
|
||||
!directives.hasThinkDirective &&
|
||||
!directives.hasVerboseDirective &&
|
||||
!directives.hasElevatedDirective &&
|
||||
!directives.hasModelDirective &&
|
||||
!directives.hasQueueDirective
|
||||
)
|
||||
@@ -142,6 +157,8 @@ export async function handleDirectiveOnly(params: {
|
||||
sessionStore?: Record<string, SessionEntry>;
|
||||
sessionKey?: string;
|
||||
storePath?: string;
|
||||
elevatedEnabled: boolean;
|
||||
elevatedAllowed: boolean;
|
||||
defaultProvider: string;
|
||||
defaultModel: string;
|
||||
aliasIndex: ModelAliasIndex;
|
||||
@@ -161,6 +178,8 @@ export async function handleDirectiveOnly(params: {
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
@@ -213,6 +232,17 @@ export async function handleDirectiveOnly(params: {
|
||||
text: `Unrecognized verbose level "${directives.rawVerboseLevel ?? ""}". Valid levels: off, on.`,
|
||||
};
|
||||
}
|
||||
if (directives.hasElevatedDirective && !directives.elevatedLevel) {
|
||||
return {
|
||||
text: `Unrecognized elevated level "${directives.rawElevatedLevel ?? ""}". Valid levels: off, on.`,
|
||||
};
|
||||
}
|
||||
if (
|
||||
directives.hasElevatedDirective &&
|
||||
(!elevatedEnabled || !elevatedAllowed)
|
||||
) {
|
||||
return { text: "elevated is not available right now." };
|
||||
}
|
||||
|
||||
const queueModeInvalid =
|
||||
directives.hasQueueDirective &&
|
||||
@@ -296,6 +326,10 @@ export async function handleDirectiveOnly(params: {
|
||||
if (directives.verboseLevel === "off") delete sessionEntry.verboseLevel;
|
||||
else sessionEntry.verboseLevel = directives.verboseLevel;
|
||||
}
|
||||
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
||||
if (directives.elevatedLevel === "off") delete sessionEntry.elevatedLevel;
|
||||
else sessionEntry.elevatedLevel = directives.elevatedLevel;
|
||||
}
|
||||
if (modelSelection) {
|
||||
if (modelSelection.isDefault) {
|
||||
delete sessionEntry.providerOverride;
|
||||
@@ -344,6 +378,13 @@ export async function handleDirectiveOnly(params: {
|
||||
: `${SYSTEM_MARK} Verbose logging enabled.`,
|
||||
);
|
||||
}
|
||||
if (directives.hasElevatedDirective && directives.elevatedLevel) {
|
||||
parts.push(
|
||||
directives.elevatedLevel === "off"
|
||||
? `${SYSTEM_MARK} Elevated mode disabled.`
|
||||
: `${SYSTEM_MARK} Elevated mode enabled.`,
|
||||
);
|
||||
}
|
||||
if (modelSelection) {
|
||||
const label = `${modelSelection.provider}/${modelSelection.model}`;
|
||||
const labelWithAlias = modelSelection.alias
|
||||
@@ -385,6 +426,8 @@ export async function persistInlineDirectives(params: {
|
||||
sessionStore?: Record<string, SessionEntry>;
|
||||
sessionKey?: string;
|
||||
storePath?: string;
|
||||
elevatedEnabled: boolean;
|
||||
elevatedAllowed: boolean;
|
||||
defaultProvider: string;
|
||||
defaultModel: string;
|
||||
aliasIndex: ModelAliasIndex;
|
||||
@@ -401,6 +444,8 @@ export async function persistInlineDirectives(params: {
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
@@ -429,6 +474,19 @@ export async function persistInlineDirectives(params: {
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
if (
|
||||
directives.hasElevatedDirective &&
|
||||
directives.elevatedLevel &&
|
||||
elevatedEnabled &&
|
||||
elevatedAllowed
|
||||
) {
|
||||
if (directives.elevatedLevel === "off") {
|
||||
delete sessionEntry.elevatedLevel;
|
||||
} else {
|
||||
sessionEntry.elevatedLevel = directives.elevatedLevel;
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
const modelDirective =
|
||||
directives.hasModelDirective && params.effectiveModelDirective
|
||||
? params.effectiveModelDirective
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
normalizeElevatedLevel,
|
||||
normalizeThinkLevel,
|
||||
normalizeVerboseLevel,
|
||||
type ElevatedLevel,
|
||||
type ThinkLevel,
|
||||
type VerboseLevel,
|
||||
} from "../thinking.js";
|
||||
@@ -50,4 +52,26 @@ export function extractVerboseDirective(body?: string): {
|
||||
};
|
||||
}
|
||||
|
||||
export type { ThinkLevel, VerboseLevel };
|
||||
export function extractElevatedDirective(body?: string): {
|
||||
cleaned: string;
|
||||
elevatedLevel?: ElevatedLevel;
|
||||
rawLevel?: string;
|
||||
hasDirective: boolean;
|
||||
} {
|
||||
if (!body) return { cleaned: "", hasDirective: false };
|
||||
const match = body.match(
|
||||
/(?:^|\s)\/(?:elevated|elev)(?=$|\s|:)\s*:?\s*([a-zA-Z-]+)\b/i,
|
||||
);
|
||||
const elevatedLevel = normalizeElevatedLevel(match?.[1]);
|
||||
const cleaned = match
|
||||
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
|
||||
: body.trim();
|
||||
return {
|
||||
cleaned,
|
||||
elevatedLevel,
|
||||
rawLevel: match?.[1],
|
||||
hasDirective: !!match,
|
||||
};
|
||||
}
|
||||
|
||||
export type { ElevatedLevel, ThinkLevel, VerboseLevel };
|
||||
|
||||
@@ -78,6 +78,7 @@ export function createFollowupRunner(params: {
|
||||
model: queued.run.model,
|
||||
thinkLevel: queued.run.thinkLevel,
|
||||
verboseLevel: queued.run.verboseLevel,
|
||||
bashElevated: queued.run.bashElevated,
|
||||
timeoutMs: queued.run.timeoutMs,
|
||||
runId,
|
||||
blockReplyBreak: queued.run.blockReplyBreak,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||
import type { ClawdisConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { ThinkLevel, VerboseLevel } from "./directives.js";
|
||||
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "./directives.js";
|
||||
export type QueueMode =
|
||||
| "steer"
|
||||
| "followup"
|
||||
@@ -34,6 +34,12 @@ export type FollowupRun = {
|
||||
model: string;
|
||||
thinkLevel?: ThinkLevel;
|
||||
verboseLevel?: VerboseLevel;
|
||||
elevatedLevel?: ElevatedLevel;
|
||||
bashElevated?: {
|
||||
enabled: boolean;
|
||||
allowed: boolean;
|
||||
defaultLevel: ElevatedLevel;
|
||||
};
|
||||
timeoutMs: number;
|
||||
blockReplyBreak: "text_end" | "message_end";
|
||||
ownerNumbers?: string[];
|
||||
|
||||
@@ -161,6 +161,8 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
const thinkLevel = args.resolvedThink ?? args.agent?.thinkingDefault ?? "off";
|
||||
const verboseLevel =
|
||||
args.resolvedVerbose ?? args.agent?.verboseDefault ?? "off";
|
||||
const elevatedLevel =
|
||||
args.entry?.elevatedLevel ?? args.agent?.elevatedDefault ?? "off";
|
||||
|
||||
const webLine = (() => {
|
||||
if (args.webLinked === false) {
|
||||
@@ -200,7 +202,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
contextTokens ?? null,
|
||||
)}${entry?.abortedLastRun ? " • last run aborted" : ""}`;
|
||||
|
||||
const optionsLine = `Options: thinking=${thinkLevel} | verbose=${verboseLevel} (set with /think <level>, /verbose on|off, /model <id>)`;
|
||||
const optionsLine = `Options: thinking=${thinkLevel} | verbose=${verboseLevel} | elevated=${elevatedLevel} (set with /think <level>, /verbose on|off, /elevated on|off, /model <id>)`;
|
||||
|
||||
const modelLabel = model ? `${resolved.provider}/${model}` : "unknown";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high";
|
||||
export type VerboseLevel = "off" | "on";
|
||||
export type ElevatedLevel = "off" | "on";
|
||||
|
||||
// Normalize user-provided thinking level strings to the canonical enum.
|
||||
export function normalizeThinkLevel(
|
||||
@@ -39,3 +40,14 @@ export function normalizeVerboseLevel(
|
||||
if (["on", "full", "true", "yes", "1"].includes(key)) return "on";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Normalize elevated flags used to toggle elevated bash permissions.
|
||||
export function normalizeElevatedLevel(
|
||||
raw?: string | null,
|
||||
): ElevatedLevel | undefined {
|
||||
if (!raw) return undefined;
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) return "off";
|
||||
if (["on", "true", "yes", "1"].includes(key)) return "on";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user