fix: format verbose tool output by channel
This commit is contained in:
@@ -54,6 +54,7 @@ Docs: https://docs.clawd.bot
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
||||||
|
- Verbose: wrap tool summaries/output in markdown only for markdown-capable channels.
|
||||||
- Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z.
|
- Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z.
|
||||||
- Telegram: split long captions into follow-up messages.
|
- Telegram: split long captions into follow-up messages.
|
||||||
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
|
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|||||||
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||||
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
||||||
import { resolveUserPath } from "../../utils.js";
|
import { resolveUserPath } from "../../utils.js";
|
||||||
|
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
|
||||||
import { resolveClawdbotAgentDir } from "../agent-paths.js";
|
import { resolveClawdbotAgentDir } from "../agent-paths.js";
|
||||||
import {
|
import {
|
||||||
markAuthProfileFailure,
|
markAuthProfileFailure,
|
||||||
@@ -58,6 +59,14 @@ export async function runEmbeddedPiAgent(
|
|||||||
const globalLane = resolveGlobalLane(params.lane);
|
const globalLane = resolveGlobalLane(params.lane);
|
||||||
const enqueueGlobal =
|
const enqueueGlobal =
|
||||||
params.enqueue ?? ((task, opts) => enqueueCommandInLane(globalLane, task, opts));
|
params.enqueue ?? ((task, opts) => enqueueCommandInLane(globalLane, task, opts));
|
||||||
|
const channelHint = params.messageChannel ?? params.messageProvider;
|
||||||
|
const resolvedToolResultFormat =
|
||||||
|
params.toolResultFormat ??
|
||||||
|
(channelHint
|
||||||
|
? isMarkdownCapableMessageChannel(channelHint)
|
||||||
|
? "markdown"
|
||||||
|
: "plain"
|
||||||
|
: "markdown");
|
||||||
|
|
||||||
return enqueueCommandInLane(sessionLane, () =>
|
return enqueueCommandInLane(sessionLane, () =>
|
||||||
enqueueGlobal(async () => {
|
enqueueGlobal(async () => {
|
||||||
@@ -208,6 +217,7 @@ export async function runEmbeddedPiAgent(
|
|||||||
thinkLevel,
|
thinkLevel,
|
||||||
verboseLevel: params.verboseLevel,
|
verboseLevel: params.verboseLevel,
|
||||||
reasoningLevel: params.reasoningLevel,
|
reasoningLevel: params.reasoningLevel,
|
||||||
|
toolResultFormat: resolvedToolResultFormat,
|
||||||
bashElevated: params.bashElevated,
|
bashElevated: params.bashElevated,
|
||||||
timeoutMs: params.timeoutMs,
|
timeoutMs: params.timeoutMs,
|
||||||
runId: params.runId,
|
runId: params.runId,
|
||||||
@@ -408,6 +418,7 @@ export async function runEmbeddedPiAgent(
|
|||||||
sessionKey: params.sessionKey ?? params.sessionId,
|
sessionKey: params.sessionKey ?? params.sessionId,
|
||||||
verboseLevel: params.verboseLevel,
|
verboseLevel: params.verboseLevel,
|
||||||
reasoningLevel: params.reasoningLevel,
|
reasoningLevel: params.reasoningLevel,
|
||||||
|
toolResultFormat: resolvedToolResultFormat,
|
||||||
inlineToolResultsAllowed: !params.onPartialReply && !params.onToolResult,
|
inlineToolResultsAllowed: !params.onPartialReply && !params.onToolResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ export async function runEmbeddedAttempt(
|
|||||||
runId: params.runId,
|
runId: params.runId,
|
||||||
verboseLevel: params.verboseLevel,
|
verboseLevel: params.verboseLevel,
|
||||||
reasoningMode: params.reasoningLevel ?? "off",
|
reasoningMode: params.reasoningLevel ?? "off",
|
||||||
|
toolResultFormat: params.toolResultFormat,
|
||||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||||
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
shouldEmitToolOutput: params.shouldEmitToolOutput,
|
||||||
onToolResult: params.onToolResult,
|
onToolResult: params.onToolResult,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ReasoningLevel, ThinkLevel, VerboseLevel } from "../../../auto-rep
|
|||||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||||
import type { enqueueCommand } from "../../../process/command-queue.js";
|
import type { enqueueCommand } from "../../../process/command-queue.js";
|
||||||
import type { ExecElevatedDefaults } from "../../bash-tools.js";
|
import type { ExecElevatedDefaults } from "../../bash-tools.js";
|
||||||
import type { BlockReplyChunking } from "../../pi-embedded-subscribe.js";
|
import type { BlockReplyChunking, ToolResultFormat } from "../../pi-embedded-subscribe.js";
|
||||||
import type { SkillSnapshot } from "../../skills.js";
|
import type { SkillSnapshot } from "../../skills.js";
|
||||||
|
|
||||||
export type RunEmbeddedPiAgentParams = {
|
export type RunEmbeddedPiAgentParams = {
|
||||||
@@ -33,6 +33,7 @@ export type RunEmbeddedPiAgentParams = {
|
|||||||
thinkLevel?: ThinkLevel;
|
thinkLevel?: ThinkLevel;
|
||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningLevel?: ReasoningLevel;
|
reasoningLevel?: ReasoningLevel;
|
||||||
|
toolResultFormat?: ToolResultFormat;
|
||||||
bashElevated?: ExecElevatedDefaults;
|
bashElevated?: ExecElevatedDefaults;
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
runId: string;
|
runId: string;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
extractAssistantThinking,
|
extractAssistantThinking,
|
||||||
formatReasoningMessage,
|
formatReasoningMessage,
|
||||||
} from "../../pi-embedded-utils.js";
|
} from "../../pi-embedded-utils.js";
|
||||||
|
import type { ToolResultFormat } from "../../pi-embedded-subscribe.js";
|
||||||
|
|
||||||
type ToolMetaEntry = { toolName: string; meta?: string };
|
type ToolMetaEntry = { toolName: string; meta?: string };
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export function buildEmbeddedRunPayloads(params: {
|
|||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningLevel?: ReasoningLevel;
|
reasoningLevel?: ReasoningLevel;
|
||||||
|
toolResultFormat?: ToolResultFormat;
|
||||||
inlineToolResultsAllowed: boolean;
|
inlineToolResultsAllowed: boolean;
|
||||||
}): Array<{
|
}): Array<{
|
||||||
text?: string;
|
text?: string;
|
||||||
@@ -47,6 +49,7 @@ export function buildEmbeddedRunPayloads(params: {
|
|||||||
replyToCurrent?: boolean;
|
replyToCurrent?: boolean;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
|
const useMarkdown = params.toolResultFormat === "markdown";
|
||||||
const lastAssistantErrored = params.lastAssistant?.stopReason === "error";
|
const lastAssistantErrored = params.lastAssistant?.stopReason === "error";
|
||||||
const errorText = params.lastAssistant
|
const errorText = params.lastAssistant
|
||||||
? formatAssistantErrorText(params.lastAssistant, {
|
? formatAssistantErrorText(params.lastAssistant, {
|
||||||
@@ -71,7 +74,9 @@ export function buildEmbeddedRunPayloads(params: {
|
|||||||
params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
|
params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
|
||||||
if (inlineToolResults) {
|
if (inlineToolResults) {
|
||||||
for (const { toolName, meta } of params.toolMetas) {
|
for (const { toolName, meta } of params.toolMetas) {
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
|
const agg = formatToolAggregate(toolName, meta ? [meta] : [], {
|
||||||
|
markdown: useMarkdown,
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
text: cleanedText,
|
text: cleanedText,
|
||||||
mediaUrls,
|
mediaUrls,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { ReasoningLevel, ThinkLevel, VerboseLevel } from "../../../auto-rep
|
|||||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||||
import type { ExecElevatedDefaults } from "../../bash-tools.js";
|
import type { ExecElevatedDefaults } from "../../bash-tools.js";
|
||||||
import type { MessagingToolSend } from "../../pi-embedded-messaging.js";
|
import type { MessagingToolSend } from "../../pi-embedded-messaging.js";
|
||||||
import type { BlockReplyChunking } from "../../pi-embedded-subscribe.js";
|
import type { BlockReplyChunking, ToolResultFormat } from "../../pi-embedded-subscribe.js";
|
||||||
import type { SkillSnapshot } from "../../skills.js";
|
import type { SkillSnapshot } from "../../skills.js";
|
||||||
import type { SessionSystemPromptReport } from "../../../config/sessions/types.js";
|
import type { SessionSystemPromptReport } from "../../../config/sessions/types.js";
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ export type EmbeddedRunAttemptParams = {
|
|||||||
thinkLevel: ThinkLevel;
|
thinkLevel: ThinkLevel;
|
||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningLevel?: ReasoningLevel;
|
reasoningLevel?: ReasoningLevel;
|
||||||
|
toolResultFormat?: ToolResultFormat;
|
||||||
bashElevated?: ExecElevatedDefaults;
|
bashElevated?: ExecElevatedDefaults;
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
runId: string;
|
runId: string;
|
||||||
|
|||||||
@@ -23,10 +23,13 @@ const log = createSubsystemLogger("agent/embedded");
|
|||||||
export type {
|
export type {
|
||||||
BlockReplyChunking,
|
BlockReplyChunking,
|
||||||
SubscribeEmbeddedPiSessionParams,
|
SubscribeEmbeddedPiSessionParams,
|
||||||
|
ToolResultFormat,
|
||||||
} from "./pi-embedded-subscribe.types.js";
|
} from "./pi-embedded-subscribe.types.js";
|
||||||
|
|
||||||
export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionParams) {
|
export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionParams) {
|
||||||
const reasoningMode = params.reasoningMode ?? "off";
|
const reasoningMode = params.reasoningMode ?? "off";
|
||||||
|
const toolResultFormat = params.toolResultFormat ?? "markdown";
|
||||||
|
const useMarkdown = toolResultFormat === "markdown";
|
||||||
const state: EmbeddedPiSubscribeState = {
|
const state: EmbeddedPiSubscribeState = {
|
||||||
assistantTexts: [],
|
assistantTexts: [],
|
||||||
toolMetas: [],
|
toolMetas: [],
|
||||||
@@ -180,11 +183,14 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
const formatToolOutputBlock = (text: string) => {
|
const formatToolOutputBlock = (text: string) => {
|
||||||
const trimmed = text.trim();
|
const trimmed = text.trim();
|
||||||
if (!trimmed) return "(no output)";
|
if (!trimmed) return "(no output)";
|
||||||
|
if (!useMarkdown) return trimmed;
|
||||||
return `\`\`\`txt\n${trimmed}\n\`\`\``;
|
return `\`\`\`txt\n${trimmed}\n\`\`\``;
|
||||||
};
|
};
|
||||||
const emitToolSummary = (toolName?: string, meta?: string) => {
|
const emitToolSummary = (toolName?: string, meta?: string) => {
|
||||||
if (!params.onToolResult) return;
|
if (!params.onToolResult) return;
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined);
|
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
||||||
|
markdown: useMarkdown,
|
||||||
|
});
|
||||||
const { text: cleanedText, mediaUrls } = parseReplyDirectives(agg);
|
const { text: cleanedText, mediaUrls } = parseReplyDirectives(agg);
|
||||||
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return;
|
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return;
|
||||||
try {
|
try {
|
||||||
@@ -198,7 +204,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
};
|
};
|
||||||
const emitToolOutput = (toolName?: string, meta?: string, output?: string) => {
|
const emitToolOutput = (toolName?: string, meta?: string, output?: string) => {
|
||||||
if (!params.onToolResult || !output) return;
|
if (!params.onToolResult || !output) return;
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined);
|
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
||||||
|
markdown: useMarkdown,
|
||||||
|
});
|
||||||
const message = `${agg}\n${formatToolOutputBlock(output)}`;
|
const message = `${agg}\n${formatToolOutputBlock(output)}`;
|
||||||
const { text: cleanedText, mediaUrls } = parseReplyDirectives(message);
|
const { text: cleanedText, mediaUrls } = parseReplyDirectives(message);
|
||||||
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return;
|
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return;
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
|||||||
import type { ReasoningLevel, VerboseLevel } from "../auto-reply/thinking.js";
|
import type { ReasoningLevel, VerboseLevel } from "../auto-reply/thinking.js";
|
||||||
import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
||||||
|
|
||||||
|
export type ToolResultFormat = "markdown" | "plain";
|
||||||
|
|
||||||
export type SubscribeEmbeddedPiSessionParams = {
|
export type SubscribeEmbeddedPiSessionParams = {
|
||||||
session: AgentSession;
|
session: AgentSession;
|
||||||
runId: string;
|
runId: string;
|
||||||
verboseLevel?: VerboseLevel;
|
verboseLevel?: VerboseLevel;
|
||||||
reasoningMode?: ReasoningLevel;
|
reasoningMode?: ReasoningLevel;
|
||||||
|
toolResultFormat?: ToolResultFormat;
|
||||||
shouldEmitToolResult?: () => boolean;
|
shouldEmitToolResult?: () => boolean;
|
||||||
shouldEmitToolOutput?: () => boolean;
|
shouldEmitToolOutput?: () => boolean;
|
||||||
onToolResult?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
onToolResult?: (payload: { text?: string; mediaUrls?: string[] }) => void | Promise<void>;
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import {
|
|||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
||||||
import { defaultRuntime } from "../../runtime.js";
|
import { defaultRuntime } from "../../runtime.js";
|
||||||
|
import {
|
||||||
|
isMarkdownCapableMessageChannel,
|
||||||
|
resolveMessageChannel,
|
||||||
|
} from "../../utils/message-channel.js";
|
||||||
import { stripHeartbeatToken } from "../heartbeat.js";
|
import { stripHeartbeatToken } from "../heartbeat.js";
|
||||||
import type { TemplateContext } from "../templating.js";
|
import type { TemplateContext } from "../templating.js";
|
||||||
import type { VerboseLevel } from "../thinking.js";
|
import type { VerboseLevel } from "../thinking.js";
|
||||||
@@ -222,6 +226,14 @@ export async function runAgentTurnWithFallback(params: {
|
|||||||
thinkLevel: params.followupRun.run.thinkLevel,
|
thinkLevel: params.followupRun.run.thinkLevel,
|
||||||
verboseLevel: params.followupRun.run.verboseLevel,
|
verboseLevel: params.followupRun.run.verboseLevel,
|
||||||
reasoningLevel: params.followupRun.run.reasoningLevel,
|
reasoningLevel: params.followupRun.run.reasoningLevel,
|
||||||
|
toolResultFormat: (() => {
|
||||||
|
const channel = resolveMessageChannel(
|
||||||
|
params.sessionCtx.Surface,
|
||||||
|
params.sessionCtx.Provider,
|
||||||
|
);
|
||||||
|
if (!channel) return "markdown";
|
||||||
|
return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain";
|
||||||
|
})(),
|
||||||
bashElevated: params.followupRun.run.bashElevated,
|
bashElevated: params.followupRun.run.bashElevated,
|
||||||
timeoutMs: params.followupRun.run.timeoutMs,
|
timeoutMs: params.followupRun.run.timeoutMs,
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ describe("tool meta formatting", () => {
|
|||||||
expect(out).toContain("a→b");
|
expect(out).toContain("a→b");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("wraps aggregate meta in backticks when markdown is enabled", () => {
|
||||||
|
vi.stubEnv("HOME", "/Users/test");
|
||||||
|
const out = formatToolAggregate("fs", ["/Users/test/dir/a.txt"], { markdown: true });
|
||||||
|
expect(out).toContain("`~/dir/a.txt`");
|
||||||
|
});
|
||||||
|
|
||||||
it("formats prefixes with default labels", () => {
|
it("formats prefixes with default labels", () => {
|
||||||
vi.stubEnv("HOME", "/Users/test");
|
vi.stubEnv("HOME", "/Users/test");
|
||||||
expect(formatToolPrefix(undefined, undefined)).toBe("🧩 tool");
|
expect(formatToolPrefix(undefined, undefined)).toBe("🧩 tool");
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { formatToolSummary, resolveToolDisplay } from "../agents/tool-display.js";
|
import { formatToolSummary, resolveToolDisplay } from "../agents/tool-display.js";
|
||||||
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
||||||
|
|
||||||
|
type ToolAggregateOptions = {
|
||||||
|
markdown?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function shortenPath(p: string): string {
|
export function shortenPath(p: string): string {
|
||||||
return shortenHomePath(p);
|
return shortenHomePath(p);
|
||||||
}
|
}
|
||||||
@@ -14,7 +18,11 @@ export function shortenMeta(meta: string): string {
|
|||||||
return `${shortenHomeInString(base)}${rest}`;
|
return `${shortenHomeInString(base)}${rest}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatToolAggregate(toolName?: string, metas?: string[]): string {
|
export function formatToolAggregate(
|
||||||
|
toolName?: string,
|
||||||
|
metas?: string[],
|
||||||
|
options?: ToolAggregateOptions,
|
||||||
|
): string {
|
||||||
const filtered = (metas ?? []).filter(Boolean).map(shortenMeta);
|
const filtered = (metas ?? []).filter(Boolean).map(shortenMeta);
|
||||||
const display = resolveToolDisplay({ name: toolName });
|
const display = resolveToolDisplay({ name: toolName });
|
||||||
const prefix = `${display.emoji} ${display.label}`;
|
const prefix = `${display.emoji} ${display.label}`;
|
||||||
@@ -51,7 +59,8 @@ export function formatToolAggregate(toolName?: string, metas?: string[]): string
|
|||||||
});
|
});
|
||||||
|
|
||||||
const allSegments = [...rawSegments, ...segments];
|
const allSegments = [...rawSegments, ...segments];
|
||||||
return `${prefix}: ${allSegments.join("; ")}`;
|
const meta = allSegments.join("; ");
|
||||||
|
return `${prefix}: ${maybeWrapMarkdown(meta, options?.markdown)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatToolPrefix(toolName?: string, meta?: string) {
|
export function formatToolPrefix(toolName?: string, meta?: string) {
|
||||||
@@ -68,3 +77,9 @@ function isPathLike(value: string): boolean {
|
|||||||
if (value.includes("&&") || value.includes("||")) return false;
|
if (value.includes("&&") || value.includes("||")) return false;
|
||||||
return /^~?(\/[^\s]+)+$/.test(value);
|
return /^~?(\/[^\s]+)+$/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function maybeWrapMarkdown(value: string, markdown?: boolean): string {
|
||||||
|
if (!markdown) return value;
|
||||||
|
if (value.includes("`")) return value;
|
||||||
|
return `\`${value}\``;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ import { getActivePluginRegistry } from "../plugins/runtime.js";
|
|||||||
export const INTERNAL_MESSAGE_CHANNEL = "webchat" as const;
|
export const INTERNAL_MESSAGE_CHANNEL = "webchat" as const;
|
||||||
export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL;
|
export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL;
|
||||||
|
|
||||||
|
const MARKDOWN_CAPABLE_CHANNELS = new Set<string>([
|
||||||
|
"slack",
|
||||||
|
"telegram",
|
||||||
|
"signal",
|
||||||
|
"discord",
|
||||||
|
"tui",
|
||||||
|
INTERNAL_MESSAGE_CHANNEL,
|
||||||
|
]);
|
||||||
|
|
||||||
export { GATEWAY_CLIENT_NAMES, GATEWAY_CLIENT_MODES };
|
export { GATEWAY_CLIENT_NAMES, GATEWAY_CLIENT_MODES };
|
||||||
export type { GatewayClientName, GatewayClientMode };
|
export type { GatewayClientName, GatewayClientMode };
|
||||||
export { normalizeGatewayClientName, normalizeGatewayClientMode };
|
export { normalizeGatewayClientName, normalizeGatewayClientMode };
|
||||||
@@ -112,3 +121,9 @@ export function resolveMessageChannel(
|
|||||||
): string | undefined {
|
): string | undefined {
|
||||||
return normalizeMessageChannel(primary) ?? normalizeMessageChannel(fallback);
|
return normalizeMessageChannel(primary) ?? normalizeMessageChannel(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMarkdownCapableMessageChannel(raw?: string | null): boolean {
|
||||||
|
const channel = normalizeMessageChannel(raw);
|
||||||
|
if (!channel) return false;
|
||||||
|
return MARKDOWN_CAPABLE_CHANNELS.has(channel);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user