refactor: extend media understanding
This commit is contained in:
@@ -68,6 +68,7 @@ export const handleStatusCommand: CommandHandler = async (params, allowTextComma
|
||||
resolveDefaultThinkingLevel: params.resolveDefaultThinkingLevel,
|
||||
isGroup: params.isGroup,
|
||||
defaultGroupActivation: params.defaultGroupActivation,
|
||||
mediaDecisions: params.ctx.MediaUnderstandingDecisions,
|
||||
});
|
||||
return { shouldContinue: false, reply };
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "..
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import type { CommandContext } from "./commands-types.js";
|
||||
import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js";
|
||||
import type { MediaUnderstandingDecision } from "../../media-understanding/types.js";
|
||||
|
||||
function formatApiKeySnippet(apiKey: string): string {
|
||||
const compact = apiKey.replace(/\s+/g, "");
|
||||
@@ -105,6 +106,7 @@ export async function buildStatusReply(params: {
|
||||
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
|
||||
isGroup: boolean;
|
||||
defaultGroupActivation: () => "always" | "mention";
|
||||
mediaDecisions?: MediaUnderstandingDecision[];
|
||||
}): Promise<ReplyPayload | undefined> {
|
||||
const {
|
||||
cfg,
|
||||
@@ -200,6 +202,7 @@ export async function buildStatusReply(params: {
|
||||
dropPolicy: queueSettings.dropPolicy,
|
||||
showDetails: queueOverrides,
|
||||
},
|
||||
mediaDecisions: params.mediaDecisions,
|
||||
includeTranscriptUsage: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||
resolveDefaultThinkingLevel: async () => resolvedDefaultThinkLevel,
|
||||
isGroup,
|
||||
defaultGroupActivation: defaultActivation,
|
||||
mediaDecisions: ctx.MediaUnderstandingDecisions,
|
||||
});
|
||||
}
|
||||
typing.cleanup();
|
||||
|
||||
@@ -185,6 +185,7 @@ export async function handleInlineActions(params: {
|
||||
resolveDefaultThinkingLevel,
|
||||
isGroup,
|
||||
defaultGroupActivation: defaultActivation,
|
||||
mediaDecisions: ctx.MediaUnderstandingDecisions,
|
||||
});
|
||||
await sendInlineReply(inlineStatusReply);
|
||||
directives = { ...directives, hasStatusDirective: false };
|
||||
|
||||
@@ -90,6 +90,59 @@ describe("buildStatusMessage", () => {
|
||||
expect(text).toContain("elevated");
|
||||
});
|
||||
|
||||
it("includes media understanding decisions when present", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: { model: "anthropic/claude-opus-4-5" },
|
||||
sessionEntry: { sessionId: "media", updatedAt: 0 },
|
||||
sessionKey: "agent:main:main",
|
||||
queue: { mode: "none" },
|
||||
mediaDecisions: [
|
||||
{
|
||||
capability: "image",
|
||||
outcome: "success",
|
||||
attachments: [
|
||||
{
|
||||
attachmentIndex: 0,
|
||||
attempts: [
|
||||
{
|
||||
type: "provider",
|
||||
outcome: "success",
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
},
|
||||
],
|
||||
chosen: {
|
||||
type: "provider",
|
||||
outcome: "success",
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
capability: "audio",
|
||||
outcome: "skipped",
|
||||
attachments: [
|
||||
{
|
||||
attachmentIndex: 1,
|
||||
attempts: [
|
||||
{
|
||||
type: "provider",
|
||||
outcome: "skipped",
|
||||
reason: "maxBytes: too large",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const normalized = normalizeTestText(text);
|
||||
expect(normalized).toContain("Media: image ok (openai/gpt-5.2) · audio skipped (maxBytes)");
|
||||
});
|
||||
|
||||
it("does not show elevated label when session explicitly disables it", () => {
|
||||
const text = buildStatusMessage({
|
||||
agent: { model: "anthropic/claude-opus-4-5", elevatedDefault: "on" },
|
||||
|
||||
@@ -24,6 +24,7 @@ import { VERSION } from "../version.js";
|
||||
import { listChatCommands, listChatCommandsForConfig } from "./commands-registry.js";
|
||||
import type { SkillCommandSpec } from "../agents/skills.js";
|
||||
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js";
|
||||
import type { MediaUnderstandingDecision } from "../media-understanding/types.js";
|
||||
|
||||
type AgentConfig = Partial<NonNullable<NonNullable<ClawdbotConfig["agents"]>["defaults"]>>;
|
||||
|
||||
@@ -52,6 +53,7 @@ type StatusArgs = {
|
||||
modelAuth?: string;
|
||||
usageLine?: string;
|
||||
queue?: QueueStatus;
|
||||
mediaDecisions?: MediaUnderstandingDecision[];
|
||||
includeTranscriptUsage?: boolean;
|
||||
now?: number;
|
||||
};
|
||||
@@ -167,6 +169,42 @@ const formatUsagePair = (input?: number | null, output?: number | null) => {
|
||||
return `🧮 Tokens: ${inputLabel} in / ${outputLabel} out`;
|
||||
};
|
||||
|
||||
const formatMediaUnderstandingLine = (decisions?: MediaUnderstandingDecision[]) => {
|
||||
if (!decisions || decisions.length === 0) return null;
|
||||
const parts = decisions
|
||||
.map((decision) => {
|
||||
const count = decision.attachments.length;
|
||||
const countLabel = count > 1 ? ` x${count}` : "";
|
||||
if (decision.outcome === "success") {
|
||||
const chosen = decision.attachments.find((entry) => entry.chosen)?.chosen;
|
||||
const provider = chosen?.provider?.trim();
|
||||
const model = chosen?.model?.trim();
|
||||
const modelLabel = provider ? (model ? `${provider}/${model}` : provider) : null;
|
||||
return `${decision.capability}${countLabel} ok${modelLabel ? ` (${modelLabel})` : ""}`;
|
||||
}
|
||||
if (decision.outcome === "no-attachment") {
|
||||
return `${decision.capability} none`;
|
||||
}
|
||||
if (decision.outcome === "disabled") {
|
||||
return `${decision.capability} off`;
|
||||
}
|
||||
if (decision.outcome === "scope-deny") {
|
||||
return `${decision.capability} denied`;
|
||||
}
|
||||
if (decision.outcome === "skipped") {
|
||||
const reason = decision.attachments
|
||||
.flatMap((entry) => entry.attempts.map((attempt) => attempt.reason).filter(Boolean))
|
||||
.find(Boolean);
|
||||
const shortReason = reason ? reason.split(":")[0]?.trim() : undefined;
|
||||
return `${decision.capability} skipped${shortReason ? ` (${shortReason})` : ""}`;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (parts.length === 0) return null;
|
||||
return `📎 Media: ${parts.join(" · ")}`;
|
||||
};
|
||||
|
||||
export function buildStatusMessage(args: StatusArgs): string {
|
||||
const now = args.now ?? Date.now();
|
||||
const entry = args.sessionEntry;
|
||||
@@ -320,12 +358,14 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
const costLine = costLabel ? `💵 Cost: ${costLabel}` : null;
|
||||
const usageCostLine =
|
||||
usagePair && costLine ? `${usagePair} · ${costLine}` : (usagePair ?? costLine);
|
||||
const mediaLine = formatMediaUnderstandingLine(args.mediaDecisions);
|
||||
|
||||
return [
|
||||
versionLine,
|
||||
modelLine,
|
||||
usageCostLine,
|
||||
`📚 ${contextLine}`,
|
||||
mediaLine,
|
||||
args.usageLine,
|
||||
`🧵 ${sessionLine}`,
|
||||
`⚙️ ${optionsLine}`,
|
||||
|
||||
Reference in New Issue
Block a user