feat: extend verbose tool feedback
This commit is contained in:
@@ -459,7 +459,7 @@ Options:
|
||||
- `--to <dest>` (for session key and optional delivery)
|
||||
- `--session-id <id>`
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>` (GPT-5.2 + Codex models only)
|
||||
- `--verbose <on|off>`
|
||||
- `--verbose <on|full|off>`
|
||||
- `--channel <whatsapp|telegram|discord|slack|signal|imessage>`
|
||||
- `--local`
|
||||
- `--deliver`
|
||||
|
||||
@@ -39,6 +39,6 @@ clawdbot agent --to +15555550123 --message "Summon reply" --deliver
|
||||
- `--deliver`: send the reply to the chosen channel (requires `--to`)
|
||||
- `--channel`: `whatsapp|telegram|discord|slack|signal|imessage` (default: `whatsapp`)
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>`: persist thinking level (GPT-5.2 + Codex models only)
|
||||
- `--verbose <on|off>`: persist verbose level
|
||||
- `--verbose <on|full|off>`: persist verbose level
|
||||
- `--timeout <seconds>`: override agent timeout
|
||||
- `--json`: output structured JSON
|
||||
|
||||
@@ -74,7 +74,7 @@ Text + native (when enabled):
|
||||
- `/send on|off|inherit` (owner-only)
|
||||
- `/reset` or `/new`
|
||||
- `/think <off|minimal|low|medium|high|xhigh>` (dynamic choices by model/provider; aliases: `/thinking`, `/t`)
|
||||
- `/verbose on|off` (alias: `/v`)
|
||||
- `/verbose on|full|off` (alias: `/v`)
|
||||
- `/reasoning on|off|stream` (alias: `/reason`; when on, sends a separate message prefixed `Reasoning:`; `stream` = Telegram draft only)
|
||||
- `/elevated on|off` (alias: `/elev`)
|
||||
- `/model <name>` (alias: `/models`; or `/<alias>` from `agents.defaults.models.*.alias`)
|
||||
|
||||
@@ -33,12 +33,13 @@ read_when:
|
||||
- **Embedded Pi**: the resolved level is passed to the in-process Pi agent runtime.
|
||||
|
||||
## Verbose directives (/verbose or /v)
|
||||
- Levels: `on|full` or `off` (default).
|
||||
- Levels: `on` (minimal) | `full` | `off` (default).
|
||||
- Directive-only message toggles session verbose and replies `Verbose logging enabled.` / `Verbose logging disabled.`; invalid levels return a hint without changing state.
|
||||
- `/verbose off` stores an explicit session override; clear it via the Sessions UI by choosing `inherit`.
|
||||
- Inline directive affects only that message; session/global defaults apply otherwise.
|
||||
- Send `/verbose` (or `/verbose:`) with no argument to see the current verbose level.
|
||||
- When verbose is on, agents that emit structured tool results (Pi, other JSON agents) send each tool result back as its own metadata-only message, prefixed with `<emoji> <tool-name>: <arg>` when available (path/command); the tool output itself is not forwarded. These tool summaries are sent as soon as each tool finishes (separate bubbles), not as streaming deltas. If you toggle `/verbose on|off` while a run is in-flight, subsequent tool bubbles honor the new setting.
|
||||
- When verbose is on, agents that emit structured tool results (Pi, other JSON agents) send each tool call back as its own metadata-only message, prefixed with `<emoji> <tool-name>: <arg>` when available (path/command). These tool summaries are sent as soon as each tool starts (separate bubbles), not as streaming deltas.
|
||||
- When verbose is `full`, exec/process tool outputs are also forwarded after completion (separate bubble, truncated to a safe length). If you toggle `/verbose on|full|off` while a run is in-flight, subsequent tool bubbles honor the new setting.
|
||||
|
||||
## Reasoning visibility (/reasoning)
|
||||
- Levels: `on|off|stream`.
|
||||
|
||||
@@ -75,7 +75,7 @@ Core:
|
||||
|
||||
Session controls:
|
||||
- `/think <off|minimal|low|medium|high>`
|
||||
- `/verbose <on|off>`
|
||||
- `/verbose <on|full|off>`
|
||||
- `/reasoning <on|off|stream>`
|
||||
- `/cost <on|off>`
|
||||
- `/elevated <on|off>` (alias: `/elev`)
|
||||
|
||||
@@ -213,6 +213,7 @@ export async function runEmbeddedPiAgent(
|
||||
runId: params.runId,
|
||||
abortSignal: params.abortSignal,
|
||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||
onPartialReply: params.onPartialReply,
|
||||
onAssistantMessageStart: params.onAssistantMessageStart,
|
||||
onBlockReply: params.onBlockReply,
|
||||
|
||||
@@ -366,6 +366,7 @@ export async function runEmbeddedAttempt(
|
||||
verboseLevel: params.verboseLevel,
|
||||
reasoningMode: params.reasoningLevel ?? "off",
|
||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||
onToolResult: params.onToolResult,
|
||||
onReasoningStream: params.onReasoningStream,
|
||||
onBlockReply: params.onBlockReply,
|
||||
|
||||
@@ -38,6 +38,7 @@ export type RunEmbeddedPiAgentParams = {
|
||||
runId: string;
|
||||
abortSignal?: AbortSignal;
|
||||
shouldEmitToolResult?: () => boolean;
|
||||
shouldEmitToolOutput?: () => boolean;
|
||||
onPartialReply?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||
onAssistantMessageStart?: () => void | Promise<void>;
|
||||
onBlockReply?: (payload: {
|
||||
|
||||
@@ -68,7 +68,9 @@ export function buildEmbeddedRunPayloads(params: {
|
||||
if (errorText) replyItems.push({ text: errorText, isError: true });
|
||||
|
||||
const inlineToolResults =
|
||||
params.inlineToolResultsAllowed && params.verboseLevel === "on" && params.toolMetas.length > 0;
|
||||
params.inlineToolResultsAllowed &&
|
||||
params.verboseLevel !== "off" &&
|
||||
params.toolMetas.length > 0;
|
||||
if (inlineToolResults) {
|
||||
for (const { toolName, meta } of params.toolMetas) {
|
||||
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
|
||||
|
||||
@@ -43,6 +43,7 @@ export type EmbeddedRunAttemptParams = {
|
||||
runId: string;
|
||||
abortSignal?: AbortSignal;
|
||||
shouldEmitToolResult?: () => boolean;
|
||||
shouldEmitToolOutput?: () => boolean;
|
||||
onPartialReply?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||
onAssistantMessageStart?: () => void | Promise<void>;
|
||||
onBlockReply?: (payload: {
|
||||
|
||||
@@ -5,12 +5,28 @@ import { normalizeTextForComparison } from "./pi-embedded-helpers.js";
|
||||
import { isMessagingTool, isMessagingToolSendAction } from "./pi-embedded-messaging.js";
|
||||
import type { EmbeddedPiSubscribeContext } from "./pi-embedded-subscribe.handlers.types.js";
|
||||
import {
|
||||
extractToolResultText,
|
||||
extractMessagingToolSend,
|
||||
isToolResultError,
|
||||
sanitizeToolResult,
|
||||
} from "./pi-embedded-subscribe.tools.js";
|
||||
import { inferToolMetaFromArgs } from "./pi-embedded-utils.js";
|
||||
|
||||
const TOOL_OUTPUT_ALLOWLIST = new Set(["exec", "bash", "process"]);
|
||||
|
||||
function extendExecMeta(toolName: string, args: unknown, meta?: string): string | undefined {
|
||||
const normalized = toolName.trim().toLowerCase();
|
||||
if (normalized !== "exec" && normalized !== "bash") return meta;
|
||||
if (!args || typeof args !== "object") return meta;
|
||||
const record = args as Record<string, unknown>;
|
||||
const flags: string[] = [];
|
||||
if (record.pty === true) flags.push("pty");
|
||||
if (record.elevated === true) flags.push("elevated");
|
||||
if (flags.length === 0) return meta;
|
||||
const suffix = flags.join(" · ");
|
||||
return meta ? `${meta} · ${suffix}` : suffix;
|
||||
}
|
||||
|
||||
export async function handleToolExecutionStart(
|
||||
ctx: EmbeddedPiSubscribeContext,
|
||||
evt: AgentEvent & { toolName: string; toolCallId: string; args: unknown },
|
||||
@@ -36,7 +52,7 @@ export async function handleToolExecutionStart(
|
||||
}
|
||||
}
|
||||
|
||||
const meta = inferToolMetaFromArgs(toolName, args);
|
||||
const meta = extendExecMeta(toolName, args, inferToolMetaFromArgs(toolName, args));
|
||||
ctx.state.toolMetaById.set(toolCallId, meta);
|
||||
ctx.log.debug(
|
||||
`embedded run tool start: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`,
|
||||
@@ -53,8 +69,8 @@ export async function handleToolExecutionStart(
|
||||
args: args as Record<string, unknown>,
|
||||
},
|
||||
});
|
||||
// Await onAgentEvent to ensure typing indicator starts before tool summaries are emitted.
|
||||
await ctx.params.onAgentEvent?.({
|
||||
// Best-effort typing signal; do not block tool summaries on slow emitters.
|
||||
void ctx.params.onAgentEvent?.({
|
||||
stream: "tool",
|
||||
data: { phase: "start", name: toolName, toolCallId },
|
||||
});
|
||||
@@ -185,4 +201,15 @@ export function handleToolExecutionEnd(
|
||||
ctx.log.debug(
|
||||
`embedded run tool end: runId=${ctx.params.runId} tool=${toolName} toolCallId=${toolCallId}`,
|
||||
);
|
||||
|
||||
if (
|
||||
ctx.params.onToolResult &&
|
||||
ctx.shouldEmitToolOutput() &&
|
||||
TOOL_OUTPUT_ALLOWLIST.has(toolName.trim().toLowerCase())
|
||||
) {
|
||||
const outputText = extractToolResultText(sanitizedResult);
|
||||
if (outputText) {
|
||||
ctx.emitToolOutput(toolName, meta, outputText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function createEmbeddedPiSessionEventHandler(ctx: EmbeddedPiSubscribeCont
|
||||
handleMessageEnd(ctx, evt as never);
|
||||
return;
|
||||
case "tool_execution_start":
|
||||
// Async handler - awaits typing indicator before emitting tool summaries.
|
||||
// Async handler - best-effort typing indicator, avoids blocking tool summaries.
|
||||
// Catch rejections to avoid unhandled promise rejection crashes.
|
||||
handleToolExecutionStart(ctx, evt as never).catch((err) => {
|
||||
ctx.log.debug(`tool_execution_start handler failed: ${String(err)}`);
|
||||
|
||||
@@ -56,7 +56,9 @@ export type EmbeddedPiSubscribeContext = {
|
||||
blockChunker: EmbeddedBlockChunker | null;
|
||||
|
||||
shouldEmitToolResult: () => boolean;
|
||||
shouldEmitToolOutput: () => boolean;
|
||||
emitToolSummary: (toolName?: string, meta?: string) => void;
|
||||
emitToolOutput: (toolName?: string, meta?: string, output?: string) => void;
|
||||
stripBlockTags: (
|
||||
text: string,
|
||||
state: { thinking: boolean; final: boolean; inlineCode?: InlineCodeState },
|
||||
|
||||
@@ -131,4 +131,52 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
expect(payload.text).toContain("snapshot");
|
||||
expect(payload.text).toContain("https://example.com");
|
||||
});
|
||||
|
||||
it("emits exec output in full verbose mode and includes PTY indicator", async () => {
|
||||
let handler: ((evt: unknown) => void) | undefined;
|
||||
const session: StubSession = {
|
||||
subscribe: (fn) => {
|
||||
handler = fn;
|
||||
return () => {};
|
||||
},
|
||||
};
|
||||
|
||||
const onToolResult = vi.fn();
|
||||
|
||||
subscribeEmbeddedPiSession({
|
||||
session: session as unknown as Parameters<typeof subscribeEmbeddedPiSession>[0]["session"],
|
||||
runId: "run-exec-full",
|
||||
verboseLevel: "full",
|
||||
onToolResult,
|
||||
});
|
||||
|
||||
handler?.({
|
||||
type: "tool_execution_start",
|
||||
toolName: "exec",
|
||||
toolCallId: "tool-exec-1",
|
||||
args: { command: "claude", pty: true },
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onToolResult).toHaveBeenCalledTimes(1);
|
||||
const summary = onToolResult.mock.calls[0][0];
|
||||
expect(summary.text).toContain("exec");
|
||||
expect(summary.text).toContain("pty");
|
||||
|
||||
handler?.({
|
||||
type: "tool_execution_end",
|
||||
toolName: "exec",
|
||||
toolCallId: "tool-exec-1",
|
||||
isError: false,
|
||||
result: { content: [{ type: "text", text: "hello\nworld" }] },
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onToolResult).toHaveBeenCalledTimes(2);
|
||||
const output = onToolResult.mock.calls[1][0];
|
||||
expect(output.text).toContain("hello");
|
||||
expect(output.text).toContain("```txt");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,24 @@ export function sanitizeToolResult(result: unknown): unknown {
|
||||
return { ...record, content: sanitized };
|
||||
}
|
||||
|
||||
export function extractToolResultText(result: unknown): string | undefined {
|
||||
if (!result || typeof result !== "object") return undefined;
|
||||
const record = result as Record<string, unknown>;
|
||||
const content = Array.isArray(record.content) ? record.content : null;
|
||||
if (!content) return undefined;
|
||||
const texts = content
|
||||
.map((item) => {
|
||||
if (!item || typeof item !== "object") return undefined;
|
||||
const entry = item as Record<string, unknown>;
|
||||
if (entry.type !== "text" || typeof entry.text !== "string") return undefined;
|
||||
const trimmed = entry.text.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
})
|
||||
.filter((value): value is string => Boolean(value));
|
||||
if (texts.length === 0) return undefined;
|
||||
return texts.join("\n");
|
||||
}
|
||||
|
||||
export function isToolResultError(result: unknown): boolean {
|
||||
if (!result || typeof result !== "object") return false;
|
||||
const record = result as { details?: unknown };
|
||||
|
||||
@@ -172,7 +172,16 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
||||
const shouldEmitToolResult = () =>
|
||||
typeof params.shouldEmitToolResult === "function"
|
||||
? params.shouldEmitToolResult()
|
||||
: params.verboseLevel === "on";
|
||||
: params.verboseLevel === "on" || params.verboseLevel === "full";
|
||||
const shouldEmitToolOutput = () =>
|
||||
typeof params.shouldEmitToolOutput === "function"
|
||||
? params.shouldEmitToolOutput()
|
||||
: params.verboseLevel === "full";
|
||||
const formatToolOutputBlock = (text: string) => {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return "(no output)";
|
||||
return `\`\`\`txt\n${trimmed}\n\`\`\``;
|
||||
};
|
||||
const emitToolSummary = (toolName?: string, meta?: string) => {
|
||||
if (!params.onToolResult) return;
|
||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined);
|
||||
@@ -187,6 +196,21 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
||||
// ignore tool result delivery failures
|
||||
}
|
||||
};
|
||||
const emitToolOutput = (toolName?: string, meta?: string, output?: string) => {
|
||||
if (!params.onToolResult || !output) return;
|
||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined);
|
||||
const message = `${agg}\n${formatToolOutputBlock(output)}`;
|
||||
const { text: cleanedText, mediaUrls } = parseReplyDirectives(message);
|
||||
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return;
|
||||
try {
|
||||
void params.onToolResult({
|
||||
text: cleanedText,
|
||||
mediaUrls: mediaUrls?.length ? mediaUrls : undefined,
|
||||
});
|
||||
} catch {
|
||||
// ignore tool result delivery failures
|
||||
}
|
||||
};
|
||||
|
||||
const stripBlockTags = (
|
||||
text: string,
|
||||
@@ -363,7 +387,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
||||
blockChunking,
|
||||
blockChunker,
|
||||
shouldEmitToolResult,
|
||||
shouldEmitToolOutput,
|
||||
emitToolSummary,
|
||||
emitToolOutput,
|
||||
stripBlockTags,
|
||||
emitBlockChunk,
|
||||
flushBlockReplyBuffer,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
import type { ReasoningLevel } from "../auto-reply/thinking.js";
|
||||
import type { ReasoningLevel, VerboseLevel } from "../auto-reply/thinking.js";
|
||||
import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
||||
|
||||
export type SubscribeEmbeddedPiSessionParams = {
|
||||
session: AgentSession;
|
||||
runId: string;
|
||||
verboseLevel?: "off" | "on";
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningMode?: ReasoningLevel;
|
||||
shouldEmitToolResult?: () => boolean;
|
||||
shouldEmitToolOutput?: () => boolean;
|
||||
onToolResult?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||
onReasoningStream?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||
onBlockReply?: (payload: {
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
"title": "Exec",
|
||||
"detailKeys": ["command"]
|
||||
},
|
||||
"bash": {
|
||||
"emoji": "🛠️",
|
||||
"title": "Exec",
|
||||
"label": "exec",
|
||||
"detailKeys": ["command"]
|
||||
},
|
||||
"process": {
|
||||
"emoji": "🧰",
|
||||
"title": "Process",
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("directive behavior", () => {
|
||||
|
||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||
expect(text).toContain("Current verbose level: on");
|
||||
expect(text).toContain("Options: on, off.");
|
||||
expect(text).toContain("Options: on, full, off.");
|
||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,6 +61,7 @@ export async function runAgentTurnWithFallback(params: {
|
||||
resolvedBlockStreamingBreak: "text_end" | "message_end";
|
||||
applyReplyToMode: (payload: ReplyPayload) => ReplyPayload;
|
||||
shouldEmitToolResult: () => boolean;
|
||||
shouldEmitToolOutput: () => boolean;
|
||||
pendingToolTasks: Set<Promise<void>>;
|
||||
resetSessionAfterCompactionFailure: (reason: string) => Promise<boolean>;
|
||||
resetSessionAfterRoleOrderingConflict: (reason: string) => Promise<boolean>;
|
||||
@@ -335,6 +336,7 @@ export async function runAgentTurnWithFallback(params: {
|
||||
}
|
||||
: undefined,
|
||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||
onToolResult: onToolResult
|
||||
? (payload) => {
|
||||
// `subscribeEmbeddedPiSession` may invoke tool callbacks without awaiting them.
|
||||
|
||||
@@ -18,17 +18,38 @@ export const createShouldEmitToolResult = (params: {
|
||||
}): (() => boolean) => {
|
||||
return () => {
|
||||
if (!params.sessionKey || !params.storePath) {
|
||||
return params.resolvedVerboseLevel === "on";
|
||||
return params.resolvedVerboseLevel !== "off";
|
||||
}
|
||||
try {
|
||||
const store = loadSessionStore(params.storePath);
|
||||
const entry = store[params.sessionKey];
|
||||
const current = normalizeVerboseLevel(entry?.verboseLevel);
|
||||
if (current) return current === "on";
|
||||
if (current) return current !== "off";
|
||||
} catch {
|
||||
// ignore store read failures
|
||||
}
|
||||
return params.resolvedVerboseLevel === "on";
|
||||
return params.resolvedVerboseLevel !== "off";
|
||||
};
|
||||
};
|
||||
|
||||
export const createShouldEmitToolOutput = (params: {
|
||||
sessionKey?: string;
|
||||
storePath?: string;
|
||||
resolvedVerboseLevel: VerboseLevel;
|
||||
}): (() => boolean) => {
|
||||
return () => {
|
||||
if (!params.sessionKey || !params.storePath) {
|
||||
return params.resolvedVerboseLevel === "full";
|
||||
}
|
||||
try {
|
||||
const store = loadSessionStore(params.storePath);
|
||||
const entry = store[params.sessionKey];
|
||||
const current = normalizeVerboseLevel(entry?.verboseLevel);
|
||||
if (current) return current === "full";
|
||||
} catch {
|
||||
// ignore store read failures
|
||||
}
|
||||
return params.resolvedVerboseLevel === "full";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { VerboseLevel } from "../thinking.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||
import { runAgentTurnWithFallback } from "./agent-runner-execution.js";
|
||||
import {
|
||||
createShouldEmitToolOutput,
|
||||
createShouldEmitToolResult,
|
||||
finalizeWithFollowup,
|
||||
isAudioPayload,
|
||||
@@ -116,6 +117,11 @@ export async function runReplyAgent(params: {
|
||||
storePath,
|
||||
resolvedVerboseLevel,
|
||||
});
|
||||
const shouldEmitToolOutput = createShouldEmitToolOutput({
|
||||
sessionKey,
|
||||
storePath,
|
||||
resolvedVerboseLevel,
|
||||
});
|
||||
|
||||
const pendingToolTasks = new Set<Promise<void>>();
|
||||
const blockReplyTimeoutMs = opts?.blockReplyTimeoutMs ?? BLOCK_REPLY_SEND_TIMEOUT_MS;
|
||||
@@ -296,6 +302,7 @@ export async function runReplyAgent(params: {
|
||||
resolvedBlockStreamingBreak,
|
||||
applyReplyToMode,
|
||||
shouldEmitToolResult,
|
||||
shouldEmitToolOutput,
|
||||
pendingToolTasks,
|
||||
resetSessionAfterCompactionFailure,
|
||||
resetSessionAfterRoleOrderingConflict,
|
||||
@@ -473,6 +480,7 @@ export async function runReplyAgent(params: {
|
||||
|
||||
// If verbose is enabled and this is a new session, prepend a session hint.
|
||||
let finalPayloads = replyPayloads;
|
||||
const verboseEnabled = resolvedVerboseLevel !== "off";
|
||||
if (autoCompactionCompleted) {
|
||||
const count = await incrementCompactionCount({
|
||||
sessionEntry: activeSessionEntry,
|
||||
@@ -480,12 +488,12 @@ export async function runReplyAgent(params: {
|
||||
sessionKey,
|
||||
storePath,
|
||||
});
|
||||
if (resolvedVerboseLevel === "on") {
|
||||
if (verboseEnabled) {
|
||||
const suffix = typeof count === "number" ? ` (count ${count})` : "";
|
||||
finalPayloads = [{ text: `🧹 Auto-compaction complete${suffix}.` }, ...finalPayloads];
|
||||
}
|
||||
}
|
||||
if (resolvedVerboseLevel === "on" && activeIsNewSession) {
|
||||
if (verboseEnabled && activeIsNewSession) {
|
||||
finalPayloads = [{ text: `🧭 New session: ${followupRun.run.sessionId}` }, ...finalPayloads];
|
||||
}
|
||||
if (responseUsageLine) {
|
||||
|
||||
@@ -137,11 +137,11 @@ export async function handleDirectiveOnly(params: {
|
||||
if (!directives.rawVerboseLevel) {
|
||||
const level = currentVerboseLevel ?? "off";
|
||||
return {
|
||||
text: withOptions(`Current verbose level: ${level}.`, "on, off"),
|
||||
text: withOptions(`Current verbose level: ${level}.`, "on, full, off"),
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: `Unrecognized verbose level "${directives.rawVerboseLevel}". Valid levels: off, on.`,
|
||||
text: `Unrecognized verbose level "${directives.rawVerboseLevel}". Valid levels: off, on, full.`,
|
||||
};
|
||||
}
|
||||
if (directives.hasReasoningDirective && !directives.reasoningLevel) {
|
||||
@@ -333,7 +333,9 @@ export async function handleDirectiveOnly(params: {
|
||||
parts.push(
|
||||
directives.verboseLevel === "off"
|
||||
? formatDirectiveAck("Verbose logging disabled.")
|
||||
: formatDirectiveAck("Verbose logging enabled."),
|
||||
: directives.verboseLevel === "full"
|
||||
? formatDirectiveAck("Verbose logging set to full.")
|
||||
: formatDirectiveAck("Verbose logging enabled."),
|
||||
);
|
||||
}
|
||||
if (directives.hasReasoningDirective && directives.reasoningLevel) {
|
||||
|
||||
@@ -227,7 +227,7 @@ export function createFollowupRunner(params: {
|
||||
sessionKey,
|
||||
storePath,
|
||||
});
|
||||
if (queued.run.verboseLevel === "on") {
|
||||
if (queued.run.verboseLevel && queued.run.verboseLevel !== "off") {
|
||||
const suffix = typeof count === "number" ? ` (count ${count})` : "";
|
||||
finalPayloads.unshift({
|
||||
text: `🧹 Auto-compaction complete${suffix}.`,
|
||||
|
||||
@@ -271,7 +271,8 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
|
||||
const queueMode = args.queue?.mode ?? "unknown";
|
||||
const queueDetails = formatQueueDetails(args.queue);
|
||||
const verboseLabel = verboseLevel === "on" ? "verbose" : null;
|
||||
const verboseLabel =
|
||||
verboseLevel === "full" ? "verbose:full" : verboseLevel === "on" ? "verbose" : null;
|
||||
const elevatedLabel = elevatedLevel === "on" ? "elevated" : null;
|
||||
const optionParts = [
|
||||
`Runtime: ${runtime.label}`,
|
||||
@@ -338,7 +339,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
export function buildHelpMessage(cfg?: ClawdbotConfig): string {
|
||||
const options = [
|
||||
"/think <level>",
|
||||
"/verbose on|off",
|
||||
"/verbose on|full|off",
|
||||
"/reasoning on|off",
|
||||
"/elevated on|off",
|
||||
"/model <id>",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
export type VerboseLevel = "off" | "on";
|
||||
export type VerboseLevel = "off" | "on" | "full";
|
||||
export type ElevatedLevel = "off" | "on";
|
||||
export type ReasoningLevel = "off" | "on" | "stream";
|
||||
export type UsageDisplayLevel = "off" | "on";
|
||||
@@ -87,7 +87,8 @@ export function normalizeVerboseLevel(raw?: string | null): VerboseLevel | undef
|
||||
if (!raw) return undefined;
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) return "off";
|
||||
if (["on", "full", "true", "yes", "1"].includes(key)) return "on";
|
||||
if (["full", "all", "everything"].includes(key)) return "full";
|
||||
if (["on", "minimal", "true", "yes", "1"].includes(key)) return "on";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function agentCommand(
|
||||
|
||||
const verboseOverride = normalizeVerboseLevel(opts.verbose);
|
||||
if (opts.verbose && !verboseOverride) {
|
||||
throw new Error('Invalid verbose level. Use "on" or "off".');
|
||||
throw new Error('Invalid verbose level. Use "on", "full", or "off".');
|
||||
}
|
||||
|
||||
const timeoutSecondsRaw =
|
||||
|
||||
@@ -118,7 +118,7 @@ export type AgentDefaultsConfig = {
|
||||
/** Default thinking level when no /think directive is present. */
|
||||
thinkingDefault?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
/** Default verbose level when no /verbose directive is present. */
|
||||
verboseDefault?: "off" | "on";
|
||||
verboseDefault?: "off" | "on" | "full";
|
||||
/** Default elevated level when no /elevated directive is present. */
|
||||
elevatedDefault?: "off" | "on";
|
||||
/** Default block streaming level when no override is present. */
|
||||
|
||||
@@ -99,7 +99,7 @@ export const AgentDefaultsSchema = z
|
||||
z.literal("xhigh"),
|
||||
])
|
||||
.optional(),
|
||||
verboseDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
|
||||
verboseDefault: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
|
||||
elevatedDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
|
||||
blockStreamingDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
|
||||
blockStreamingBreak: z.union([z.literal("text_end"), z.literal("message_end")]).optional(),
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ensureAgentWorkspace } from "../../agents/workspace.js";
|
||||
import {
|
||||
formatXHighModelHint,
|
||||
normalizeThinkLevel,
|
||||
normalizeVerboseLevel,
|
||||
supportsXHighThinking,
|
||||
} from "../../auto-reply/thinking.js";
|
||||
import { createOutboundSendDeps, type CliDeps } from "../../cli/outbound-send-deps.js";
|
||||
@@ -245,8 +246,9 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
try {
|
||||
const sessionFile = resolveSessionTranscriptPath(cronSession.sessionEntry.sessionId, agentId);
|
||||
const resolvedVerboseLevel =
|
||||
(cronSession.sessionEntry.verboseLevel as "on" | "off" | undefined) ??
|
||||
(agentCfg?.verboseDefault as "on" | "off" | undefined);
|
||||
normalizeVerboseLevel(cronSession.sessionEntry.verboseLevel) ??
|
||||
normalizeVerboseLevel(agentCfg?.verboseDefault) ??
|
||||
"off";
|
||||
registerAgentRunContext(cronSession.sessionEntry.sessionId, {
|
||||
sessionKey: agentSessionKey,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { VerboseLevel } from "../auto-reply/thinking.js";
|
||||
|
||||
export type AgentEventStream = "lifecycle" | "tool" | "assistant" | "error" | (string & {});
|
||||
|
||||
export type AgentEventPayload = {
|
||||
@@ -11,7 +13,7 @@ export type AgentEventPayload = {
|
||||
|
||||
export type AgentRunContext = {
|
||||
sessionKey?: string;
|
||||
verboseLevel?: "off" | "on";
|
||||
verboseLevel?: VerboseLevel;
|
||||
};
|
||||
|
||||
// Keep per-run counters so streams stay strictly monotonic per runId.
|
||||
|
||||
Reference in New Issue
Block a user