feat: extend verbose tool feedback

This commit is contained in:
Peter Steinberger
2026-01-17 05:33:27 +00:00
parent 4d314db750
commit 99dd428862
31 changed files with 208 additions and 34 deletions

View File

@@ -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();
});
});

View File

@@ -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.

View File

@@ -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";
};
};

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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}.`,

View File

@@ -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>",

View File

@@ -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;
}