Auto-reply: add /verbose directives and tool result replies
This commit is contained in:
@@ -34,6 +34,7 @@ type CommandReplyParams = {
|
||||
commandRunner: typeof runCommandWithTimeout;
|
||||
enqueue?: EnqueueRunner;
|
||||
thinkLevel?: ThinkLevel;
|
||||
verboseLevel?: "off" | "on";
|
||||
};
|
||||
|
||||
export type CommandReplyMeta = {
|
||||
@@ -141,6 +142,7 @@ export async function runCommandReply(
|
||||
commandRunner,
|
||||
enqueue = enqueueCommand,
|
||||
thinkLevel,
|
||||
verboseLevel,
|
||||
} = params;
|
||||
|
||||
if (!reply.command?.length) {
|
||||
@@ -301,6 +303,8 @@ export async function runCommandReply(
|
||||
// Collect one message per assistant text from parseOutput (tau RPC can emit many).
|
||||
const parsedTexts =
|
||||
parsed?.texts?.map((t) => t.trim()).filter(Boolean) ?? [];
|
||||
const parsedToolResults =
|
||||
parsed?.toolResults?.map((t) => t.trim()).filter(Boolean) ?? [];
|
||||
|
||||
type ReplyItem = { text: string; media?: string[] };
|
||||
const replyItems: ReplyItem[] = [];
|
||||
@@ -314,6 +318,18 @@ export async function runCommandReply(
|
||||
});
|
||||
}
|
||||
|
||||
if (verboseLevel === "on") {
|
||||
for (const tr of parsedToolResults) {
|
||||
const prefixed = `🛠️ ${tr}`;
|
||||
const { text: cleanedText, mediaUrls: mediaFound } =
|
||||
splitMediaFromOutput(prefixed);
|
||||
replyItems.push({
|
||||
text: cleanedText,
|
||||
media: mediaFound?.length ? mediaFound : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If parser gave nothing, fall back to raw stdout as a single message.
|
||||
if (replyItems.length === 0 && trimmed && !parserProvided) {
|
||||
const { text: cleanedText, mediaUrls: mediaFound } =
|
||||
|
||||
@@ -34,6 +34,7 @@ const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit"]);
|
||||
const ABORT_MEMORY = new Map<string, boolean>();
|
||||
|
||||
type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high";
|
||||
type VerboseLevel = "off" | "on";
|
||||
|
||||
function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined {
|
||||
if (!raw) return undefined;
|
||||
@@ -50,6 +51,14 @@ function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeVerboseLevel(raw?: string | null): VerboseLevel | undefined {
|
||||
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";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractThinkDirective(body?: string): {
|
||||
cleaned: string;
|
||||
thinkLevel?: ThinkLevel;
|
||||
@@ -73,6 +82,26 @@ function extractThinkDirective(body?: string): {
|
||||
};
|
||||
}
|
||||
|
||||
function extractVerboseDirective(body?: string): {
|
||||
cleaned: string;
|
||||
verboseLevel?: VerboseLevel;
|
||||
rawLevel?: string;
|
||||
hasDirective: boolean;
|
||||
} {
|
||||
if (!body) return { cleaned: "", hasDirective: false };
|
||||
const match = body.match(/\/(?:verbose|v)\s*:?\s*([a-zA-Z-]+)\b/i);
|
||||
const verboseLevel = normalizeVerboseLevel(match?.[1]);
|
||||
const cleaned = match
|
||||
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
|
||||
: body.trim();
|
||||
return {
|
||||
cleaned,
|
||||
verboseLevel,
|
||||
rawLevel: match?.[1],
|
||||
hasDirective: !!match,
|
||||
};
|
||||
}
|
||||
|
||||
function isAbortTrigger(text?: string): boolean {
|
||||
if (!text) return false;
|
||||
const normalized = text.trim().toLowerCase();
|
||||
@@ -156,6 +185,7 @@ export async function getReplyFromConfig(
|
||||
let abortedLastRun = false;
|
||||
|
||||
let persistedThinking: string | undefined;
|
||||
let persistedVerbose: string | undefined;
|
||||
|
||||
if (sessionCfg) {
|
||||
const trimmedBody = (ctx.Body ?? "").trim();
|
||||
@@ -185,6 +215,7 @@ export async function getReplyFromConfig(
|
||||
systemSent = entry.systemSent ?? false;
|
||||
abortedLastRun = entry.abortedLastRun ?? false;
|
||||
persistedThinking = entry.thinkingLevel;
|
||||
persistedVerbose = entry.verboseLevel;
|
||||
} else {
|
||||
sessionId = crypto.randomUUID();
|
||||
isNewSession = true;
|
||||
@@ -198,6 +229,7 @@ export async function getReplyFromConfig(
|
||||
systemSent,
|
||||
abortedLastRun,
|
||||
thinkingLevel: persistedThinking,
|
||||
verboseLevel: persistedVerbose,
|
||||
};
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
@@ -216,14 +248,25 @@ export async function getReplyFromConfig(
|
||||
rawLevel: rawThinkLevel,
|
||||
hasDirective: hasThinkDirective,
|
||||
} = extractThinkDirective(sessionCtx.BodyStripped ?? sessionCtx.Body ?? "");
|
||||
sessionCtx.Body = thinkCleaned;
|
||||
sessionCtx.BodyStripped = thinkCleaned;
|
||||
const {
|
||||
cleaned: verboseCleaned,
|
||||
verboseLevel: inlineVerbose,
|
||||
rawLevel: rawVerboseLevel,
|
||||
hasDirective: hasVerboseDirective,
|
||||
} = extractVerboseDirective(thinkCleaned);
|
||||
sessionCtx.Body = verboseCleaned;
|
||||
sessionCtx.BodyStripped = verboseCleaned;
|
||||
|
||||
let resolvedThinkLevel =
|
||||
inlineThink ??
|
||||
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
||||
(reply?.thinkingDefault as ThinkLevel | undefined);
|
||||
|
||||
let resolvedVerboseLevel =
|
||||
inlineVerbose ??
|
||||
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||
(reply?.verboseDefault as VerboseLevel | undefined);
|
||||
|
||||
const directiveOnly = (() => {
|
||||
if (!hasThinkDirective) return false;
|
||||
if (!thinkCleaned) return true;
|
||||
@@ -258,6 +301,38 @@ export async function getReplyFromConfig(
|
||||
return { text: ack };
|
||||
}
|
||||
|
||||
const verboseDirectiveOnly = (() => {
|
||||
if (!hasVerboseDirective) return false;
|
||||
if (!verboseCleaned) return true;
|
||||
const stripped = verboseCleaned.replace(/\[[^\]]+\]\s*/g, "").trim();
|
||||
return stripped.length === 0;
|
||||
})();
|
||||
|
||||
if (verboseDirectiveOnly) {
|
||||
if (!inlineVerbose) {
|
||||
cleanupTyping();
|
||||
return {
|
||||
text: `Unrecognized verbose level "${rawVerboseLevel ?? ""}". Valid levels: off, on.`,
|
||||
};
|
||||
}
|
||||
if (sessionEntry && sessionStore && sessionKey) {
|
||||
if (inlineVerbose === "off") {
|
||||
delete sessionEntry.verboseLevel;
|
||||
} else {
|
||||
sessionEntry.verboseLevel = inlineVerbose;
|
||||
}
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
}
|
||||
const ack =
|
||||
inlineVerbose === "off"
|
||||
? "Verbose logging disabled."
|
||||
: "Verbose logging enabled.";
|
||||
cleanupTyping();
|
||||
return { text: ack };
|
||||
}
|
||||
|
||||
// Optional allowlist by origin number (E.164 without whatsapp: prefix)
|
||||
const allowFrom = cfg.inbound?.allowFrom;
|
||||
const from = (ctx.From ?? "").replace(/^whatsapp:/, "");
|
||||
@@ -445,6 +520,7 @@ export async function getReplyFromConfig(
|
||||
timeoutSeconds,
|
||||
commandRunner,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
});
|
||||
const payloadArray = runResult.payloads ?? [];
|
||||
const meta = runResult.meta;
|
||||
|
||||
Reference in New Issue
Block a user