refactor(extensions): reuse shared helper primitives

This commit is contained in:
Peter Steinberger
2026-03-07 10:40:57 +00:00
parent 3c71e2bd48
commit 1aa77e4603
58 changed files with 1567 additions and 2195 deletions

View File

@@ -4,7 +4,8 @@ import type {
OpenClawConfig,
} from "openclaw/plugin-sdk/msteams";
import {
buildBaseChannelStatusSummary,
buildProbeChannelStatusSummary,
buildRuntimeAccountStatusSnapshot,
buildChannelConfigSchema,
createDefaultChannelRuntimeState,
DEFAULT_ACCOUNT_ID,
@@ -250,11 +251,43 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
name: undefined as string | undefined,
note: undefined as string | undefined,
}));
type ResolveTargetResultEntry = (typeof results)[number];
type PendingTargetEntry = { input: string; query: string; index: number };
const stripPrefix = (value: string) => normalizeMSTeamsUserInput(value);
const markPendingLookupFailed = (pending: PendingTargetEntry[]) => {
pending.forEach(({ index }) => {
const entry = results[index];
if (entry) {
entry.note = "lookup failed";
}
});
};
const resolvePending = async <T>(
pending: PendingTargetEntry[],
resolveEntries: (entries: string[]) => Promise<T[]>,
applyResolvedEntry: (target: ResolveTargetResultEntry, entry: T) => void,
) => {
if (pending.length === 0) {
return;
}
try {
const resolved = await resolveEntries(pending.map((entry) => entry.query));
resolved.forEach((entry, idx) => {
const target = results[pending[idx]?.index ?? -1];
if (!target) {
return;
}
applyResolvedEntry(target, entry);
});
} catch (err) {
runtime.error?.(`msteams resolve failed: ${String(err)}`);
markPendingLookupFailed(pending);
}
};
if (kind === "user") {
const pending: Array<{ input: string; query: string; index: number }> = [];
const pending: PendingTargetEntry[] = [];
results.forEach((entry, index) => {
const trimmed = entry.input.trim();
if (!trimmed) {
@@ -270,37 +303,21 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
pending.push({ input: entry.input, query: cleaned, index });
});
if (pending.length > 0) {
try {
const resolved = await resolveMSTeamsUserAllowlist({
cfg,
entries: pending.map((entry) => entry.query),
});
resolved.forEach((entry, idx) => {
const target = results[pending[idx]?.index ?? -1];
if (!target) {
return;
}
target.resolved = entry.resolved;
target.id = entry.id;
target.name = entry.name;
target.note = entry.note;
});
} catch (err) {
runtime.error?.(`msteams resolve failed: ${String(err)}`);
pending.forEach(({ index }) => {
const entry = results[index];
if (entry) {
entry.note = "lookup failed";
}
});
}
}
await resolvePending(
pending,
(entries) => resolveMSTeamsUserAllowlist({ cfg, entries }),
(target, entry) => {
target.resolved = entry.resolved;
target.id = entry.id;
target.name = entry.name;
target.note = entry.note;
},
);
return results;
}
const pending: Array<{ input: string; query: string; index: number }> = [];
const pending: PendingTargetEntry[] = [];
results.forEach((entry, index) => {
const trimmed = entry.input.trim();
if (!trimmed) {
@@ -323,48 +340,32 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
pending.push({ input: entry.input, query, index });
});
if (pending.length > 0) {
try {
const resolved = await resolveMSTeamsChannelAllowlist({
cfg,
entries: pending.map((entry) => entry.query),
});
resolved.forEach((entry, idx) => {
const target = results[pending[idx]?.index ?? -1];
if (!target) {
return;
}
if (!entry.resolved || !entry.teamId) {
target.resolved = false;
target.note = entry.note;
return;
}
target.resolved = true;
if (entry.channelId) {
target.id = `${entry.teamId}/${entry.channelId}`;
target.name =
entry.channelName && entry.teamName
? `${entry.teamName}/${entry.channelName}`
: (entry.channelName ?? entry.teamName);
} else {
target.id = entry.teamId;
target.name = entry.teamName;
target.note = "team id";
}
if (entry.note) {
target.note = entry.note;
}
});
} catch (err) {
runtime.error?.(`msteams resolve failed: ${String(err)}`);
pending.forEach(({ index }) => {
const entry = results[index];
if (entry) {
entry.note = "lookup failed";
}
});
}
}
await resolvePending(
pending,
(entries) => resolveMSTeamsChannelAllowlist({ cfg, entries }),
(target, entry) => {
if (!entry.resolved || !entry.teamId) {
target.resolved = false;
target.note = entry.note;
return;
}
target.resolved = true;
if (entry.channelId) {
target.id = `${entry.teamId}/${entry.channelId}`;
target.name =
entry.channelName && entry.teamName
? `${entry.teamName}/${entry.channelName}`
: (entry.channelName ?? entry.teamName);
} else {
target.id = entry.teamId;
target.name = entry.teamName;
target.note = "team id";
}
if (entry.note) {
target.note = entry.note;
}
},
);
return results;
},
@@ -429,23 +430,17 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
outbound: msteamsOutbound,
status: {
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
buildChannelSummary: ({ snapshot }) => ({
...buildBaseChannelStatusSummary(snapshot),
port: snapshot.port ?? null,
probe: snapshot.probe,
lastProbeAt: snapshot.lastProbeAt ?? null,
}),
buildChannelSummary: ({ snapshot }) =>
buildProbeChannelStatusSummary(snapshot, {
port: snapshot.port ?? null,
}),
probeAccount: async ({ cfg }) => await probeMSTeams(cfg.channels?.msteams),
buildAccountSnapshot: ({ account, runtime, probe }) => ({
accountId: account.accountId,
enabled: account.enabled,
configured: account.configured,
running: runtime?.running ?? false,
lastStartAt: runtime?.lastStartAt ?? null,
lastStopAt: runtime?.lastStopAt ?? null,
lastError: runtime?.lastError ?? null,
...buildRuntimeAccountStatusSnapshot({ runtime, probe }),
port: runtime?.port ?? null,
probe,
}),
},
gateway: {

View File

@@ -72,6 +72,17 @@ const createRecordedSendActivity = (
};
};
const REVOCATION_ERROR = "Cannot perform 'set' on a proxy that has been revoked";
const createFallbackAdapter = (proactiveSent: string[]): MSTeamsAdapter => ({
continueConversation: async (_appId, _reference, logic) => {
await logic({
sendActivity: createRecordedSendActivity(proactiveSent),
});
},
process: async () => {},
});
describe("msteams messenger", () => {
beforeEach(() => {
setMSTeamsRuntime(runtimeStub);
@@ -297,18 +308,11 @@ describe("msteams messenger", () => {
const ctx = {
sendActivity: async () => {
throw new TypeError("Cannot perform 'set' on a proxy that has been revoked");
throw new TypeError(REVOCATION_ERROR);
},
};
const adapter: MSTeamsAdapter = {
continueConversation: async (_appId, _reference, logic) => {
await logic({
sendActivity: createRecordedSendActivity(proactiveSent),
});
},
process: async () => {},
};
const adapter = createFallbackAdapter(proactiveSent);
const ids = await sendMSTeamsMessages({
replyStyle: "thread",
@@ -338,18 +342,11 @@ describe("msteams messenger", () => {
threadSent.push(content);
return { id: `id:${content}` };
}
throw new TypeError("Cannot perform 'set' on a proxy that has been revoked");
throw new TypeError(REVOCATION_ERROR);
},
};
const adapter: MSTeamsAdapter = {
continueConversation: async (_appId, _reference, logic) => {
await logic({
sendActivity: createRecordedSendActivity(proactiveSent),
});
},
process: async () => {},
};
const adapter = createFallbackAdapter(proactiveSent);
const ids = await sendMSTeamsMessages({
replyStyle: "thread",

View File

@@ -2,6 +2,7 @@ import {
DEFAULT_ACCOUNT_ID,
buildPendingHistoryContextFromMap,
clearHistoryEntriesIfEnabled,
dispatchReplyFromConfigWithSettledDispatcher,
DEFAULT_GROUP_HISTORY_LIMIT,
createScopedPairingAccess,
logInboundDrop,
@@ -11,6 +12,7 @@ import {
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
resolveMentionGating,
resolveInboundSessionEnvelopeContext,
formatAllowlistMatchMeta,
resolveEffectiveAllowFromLists,
resolveDmGroupAccessWithLists,
@@ -451,12 +453,9 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
const mediaPayload = buildMSTeamsMediaPayload(mediaList);
const envelopeFrom = isDirectMessage ? senderName : conversationType;
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
cfg,
agentId: route.agentId,
});
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
const previousTimestamp = core.channel.session.readSessionUpdatedAt({
storePath,
sessionKey: route.sessionKey,
});
const body = core.channel.reply.formatAgentEnvelope({
@@ -559,18 +558,14 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
log.info("dispatching to agent", { sessionKey: route.sessionKey });
try {
const { queuedFinal, counts } = await core.channel.reply.withReplyDispatcher({
const { queuedFinal, counts } = await dispatchReplyFromConfigWithSettledDispatcher({
cfg,
ctxPayload,
dispatcher,
onSettled: () => {
markDispatchIdle();
},
run: () =>
core.channel.reply.dispatchReplyFromConfig({
ctx: ctxPayload,
cfg,
dispatcher,
replyOptions,
}),
replyOptions,
});
log.info("dispatch complete", { queuedFinal, counts });

View File

@@ -157,24 +157,13 @@ export async function sendMessageMSTeams(
log.debug?.("sending file consent card", { uploadId, fileName, size: media.buffer.length });
const baseRef = buildConversationReference(ref);
const proactiveRef = { ...baseRef, activityId: undefined };
let messageId = "unknown";
try {
await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
const response = await turnCtx.sendActivity(activity);
messageId = extractMessageId(response) ?? "unknown";
});
} catch (err) {
const classification = classifyMSTeamsSendError(err);
const hint = formatMSTeamsSendErrorHint(classification);
const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
throw new Error(
`msteams consent card send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
{ cause: err },
);
}
const messageId = await sendProactiveActivity({
adapter,
appId,
ref,
activity,
errorPrefix: "msteams consent card send",
});
log.info("sent file consent card", { conversationId, messageId, uploadId });
@@ -245,14 +234,11 @@ export async function sendMessageMSTeams(
text: messageText || undefined,
attachments: [fileCardAttachment],
};
const baseRef = buildConversationReference(ref);
const proactiveRef = { ...baseRef, activityId: undefined };
let messageId = "unknown";
await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
const response = await turnCtx.sendActivity(activity);
messageId = extractMessageId(response) ?? "unknown";
const messageId = await sendProactiveActivityRaw({
adapter,
appId,
ref,
activity,
});
log.info("sent native file card", {
@@ -288,14 +274,11 @@ export async function sendMessageMSTeams(
type: "message",
text: messageText ? `${messageText}\n\n${fileLink}` : fileLink,
};
const baseRef = buildConversationReference(ref);
const proactiveRef = { ...baseRef, activityId: undefined };
let messageId = "unknown";
await adapter.continueConversation(appId, proactiveRef, async (turnCtx) => {
const response = await turnCtx.sendActivity(activity);
messageId = extractMessageId(response) ?? "unknown";
const messageId = await sendProactiveActivityRaw({
adapter,
appId,
ref,
activity,
});
log.info("sent message with OneDrive file link", {
@@ -382,13 +365,14 @@ type ProactiveActivityParams = {
errorPrefix: string;
};
async function sendProactiveActivity({
type ProactiveActivityRawParams = Omit<ProactiveActivityParams, "errorPrefix">;
async function sendProactiveActivityRaw({
adapter,
appId,
ref,
activity,
errorPrefix,
}: ProactiveActivityParams): Promise<string> {
}: ProactiveActivityRawParams): Promise<string> {
const baseRef = buildConversationReference(ref);
const proactiveRef = {
...baseRef,
@@ -396,12 +380,27 @@ async function sendProactiveActivity({
};
let messageId = "unknown";
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
const response = await ctx.sendActivity(activity);
messageId = extractMessageId(response) ?? "unknown";
});
return messageId;
}
async function sendProactiveActivity({
adapter,
appId,
ref,
activity,
errorPrefix,
}: ProactiveActivityParams): Promise<string> {
try {
await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
const response = await ctx.sendActivity(activity);
messageId = extractMessageId(response) ?? "unknown";
return await sendProactiveActivityRaw({
adapter,
appId,
ref,
activity,
});
return messageId;
} catch (err) {
const classification = classifyMSTeamsSendError(err);
const hint = formatMSTeamsSendErrorHint(classification);