import { spawn } from "node:child_process"; import {
createTopLevelChannelAllowFromSetter,
createTopLevelChannelDmPolicy,
createTopLevelChannelGroupPolicySetter,
mergeAllowFromEntries,
splitSetupEntries,
type ChannelSetupDmPolicy,
type ChannelSetupWizard,
type OpenClawConfig,
type WizardPrompter,
} from "openclaw/plugin-sdk/setup"; import type { MSTeamsTeamConfig } from "../runtime-api.js"; import { formatUnknownError } from "./errors.js"; import {
parseMSTeamsTeamEntry,
resolveMSTeamsChannelAllowlist,
resolveMSTeamsUserAllowlist,
} from "./resolve-allowlist.js"; import { createMSTeamsSetupWizardBase, msteamsSetupAdapter } from "./setup-core.js"; import { resolveMSTeamsCredentials, saveDelegatedTokens } from "./token.js";
function looksLikeGuid(value: string): boolean { return /^[0-9a-fA-F-]{16,}$/.test(value);
}
async function promptMSTeamsAllowFrom(params: {
cfg: OpenClawConfig;
prompter: WizardPrompter;
}): Promise<OpenClawConfig> { const existing = params.cfg.channels?.msteams?.allowFrom ?? [];
await params.prompter.note(
[ "Allowlist MS Teams DMs by display name, UPN/email, or user id.", "We resolve names to user IDs via Microsoft Graph when credentials allow.", "Examples:", "- alex@example.com", "- Alex Johnson", "- 00000000-0000-0000-0000-000000000000",
].join("\n"), "MS Teams allowlist",
);
while (true) { const entry = await params.prompter.text({
message: "MS Teams allowFrom (usernames or ids)",
placeholder: "alex@example.com, Alex Johnson",
initialValue: existing[0] ? existing[0] : undefined,
validate: (value) => (value.trim() ? undefined : "Required"),
}); const parts = splitSetupEntries(entry); if (parts.length === 0) {
await params.prompter.note("Enter at least one user.", "MS Teams allowlist"); continue;
}
export const msteamsSetupWizard: ChannelSetupWizard = {
...msteamsSetupWizardBase, // Override finalize to layer on the optional delegated-auth bootstrap after // the base wizard collects app credentials. This preserves main's shared // setup-core flow while keeping the delegated OAuth step from this PR.
finalize: async (params) => { // setup-core always provides a finalize; the type is optional only because // ChannelSetupWizard.finalize is generally optional. Fall back to the // incoming cfg if the base ever returns void for forward-compat. const baseFinalize = msteamsSetupWizardBase.finalize; const baseResult = baseFinalize ? await baseFinalize(params) : undefined;
let next = baseResult?.cfg ?? params.cfg; const finalCreds = resolveMSTeamsCredentials(next.channels?.msteams); if (finalCreds?.type === "secret") { const enableDelegated = await params.prompter.confirm({
message: "Enable delegated auth? (required for reactions and write operations)",
initialValue: false,
}); if (enableDelegated) {
next = {
...next,
channels: {
...next.channels,
msteams: {
...next.channels?.msteams,
delegatedAuth: { enabled: true },
},
},
}; try { const { loginMSTeamsDelegated } = await import("./oauth.js"); const { shouldUseManualOAuthFlow } = await import("./oauth.flow.js"); const isRemote = Boolean(process.env.SSH_TTY || process.env.SSH_CONNECTION); const progress = params.prompter.progress("MSTeams Delegated OAuth"); const tokens = await loginMSTeamsDelegated(
{
isRemote: shouldUseManualOAuthFlow(isRemote),
openUrl: openDelegatedOAuthUrl,
log: (msg) => params.prompter.note(msg),
note: (msg, title) => params.prompter.note(msg, title),
prompt: (msg) => params.prompter.text({ message: msg }),
progress,
},
{
tenantId: finalCreds.tenantId,
clientId: finalCreds.appId,
clientSecret: finalCreds.appPassword,
},
);
saveDelegatedTokens(tokens);
progress.stop("Delegated auth configured");
} catch (err) {
await params.prompter.note(
`Delegated auth setup failed: ${formatUnknownError(err)}\n` + "You can retry later via the setup wizard.", "MS Teams delegated auth",
);
}
}
} return { ...baseResult, cfg: next };
},
dmPolicy: msteamsDmPolicy,
groupAccess: msteamsGroupAccess,
disable: (cfg) => ({
...cfg,
channels: {
...cfg.channels,
msteams: { ...cfg.channels?.msteams, enabled: false },
},
}),
};
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.1Bemerkung:
(vorverarbeitet am 2026-09-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.