* fix(acp): normalize unicode flags and Telegram topic binding * feat(telegram/acp): restore topic-bound ACP and session bindings * fix(acpx): clarify permission-denied guidance * feat(telegram/acp): pin spawn bind notice in topics * docs(telegram): document ACP topic thread binding behavior * refactor(reply): share Telegram conversation-id resolver * fix(telegram/acp): preserve bound session routing semantics * fix(telegram): respect binding persistence and expiry reporting * refactor(telegram): simplify binding lifecycle persistence * fix(telegram): bind acp spawns in direct messages * fix: document telegram ACP topic binding changelog (#36683) (thanks @huntharo) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import { getSessionBindingService } from "../../../infra/outbound/session-binding-service.js";
|
|
import type { CommandHandlerResult } from "../commands-types.js";
|
|
import { formatRunLabel, sortSubagentRuns } from "../subagents-utils.js";
|
|
import {
|
|
type SubagentsCommandContext,
|
|
resolveChannelAccountId,
|
|
resolveCommandSurfaceChannel,
|
|
stopWithText,
|
|
} from "./shared.js";
|
|
|
|
function formatConversationBindingText(params: {
|
|
channel: string;
|
|
conversationId: string;
|
|
}): string {
|
|
if (params.channel === "discord") {
|
|
return `thread:${params.conversationId}`;
|
|
}
|
|
if (params.channel === "telegram") {
|
|
return `conversation:${params.conversationId}`;
|
|
}
|
|
return `binding:${params.conversationId}`;
|
|
}
|
|
|
|
export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): CommandHandlerResult {
|
|
const { params, requesterKey, runs } = ctx;
|
|
const channel = resolveCommandSurfaceChannel(params);
|
|
const accountId = resolveChannelAccountId(params);
|
|
const bindingService = getSessionBindingService();
|
|
const bindingsBySession = new Map<string, ReturnType<typeof bindingService.listBySession>>();
|
|
|
|
const resolveSessionBindings = (sessionKey: string) => {
|
|
const cached = bindingsBySession.get(sessionKey);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const resolved = bindingService
|
|
.listBySession(sessionKey)
|
|
.filter(
|
|
(entry) =>
|
|
entry.status === "active" &&
|
|
entry.conversation.channel === channel &&
|
|
entry.conversation.accountId === accountId,
|
|
);
|
|
bindingsBySession.set(sessionKey, resolved);
|
|
return resolved;
|
|
};
|
|
|
|
const visibleRuns = sortSubagentRuns(runs).filter((entry) => {
|
|
if (!entry.endedAt) {
|
|
return true;
|
|
}
|
|
return resolveSessionBindings(entry.childSessionKey).length > 0;
|
|
});
|
|
|
|
const lines = ["agents:", "-----"];
|
|
if (visibleRuns.length === 0) {
|
|
lines.push("(none)");
|
|
} else {
|
|
let index = 1;
|
|
for (const entry of visibleRuns) {
|
|
const binding = resolveSessionBindings(entry.childSessionKey)[0];
|
|
const bindingText = binding
|
|
? formatConversationBindingText({
|
|
channel,
|
|
conversationId: binding.conversation.conversationId,
|
|
})
|
|
: channel === "discord" || channel === "telegram"
|
|
? "unbound"
|
|
: "bindings available on discord/telegram";
|
|
lines.push(`${index}. ${formatRunLabel(entry)} (${bindingText})`);
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
const requesterBindings = resolveSessionBindings(requesterKey).filter(
|
|
(entry) => entry.targetKind === "session",
|
|
);
|
|
if (requesterBindings.length > 0) {
|
|
lines.push("", "acp/session bindings:", "-----");
|
|
for (const binding of requesterBindings) {
|
|
const label =
|
|
typeof binding.metadata?.label === "string" && binding.metadata.label.trim()
|
|
? binding.metadata.label.trim()
|
|
: binding.targetSessionKey;
|
|
lines.push(
|
|
`- ${label} (${formatConversationBindingText({
|
|
channel,
|
|
conversationId: binding.conversation.conversationId,
|
|
})}, session:${binding.targetSessionKey})`,
|
|
);
|
|
}
|
|
}
|
|
|
|
return stopWithText(lines.join("\n"));
|
|
}
|