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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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] : []);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,12 @@
"title": "Exec",
"detailKeys": ["command"]
},
"bash": {
"emoji": "🛠️",
"title": "Exec",
"label": "exec",
"detailKeys": ["command"]
},
"process": {
"emoji": "🧰",
"title": "Process",

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

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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