From cd90130877f1817e2e76459370ff643861b098c9 Mon Sep 17 00:00:00 2001 From: Sid Uppal Date: Mon, 23 Mar 2026 22:03:39 -0700 Subject: [PATCH] msteams: implement Teams AI agent UX best practices (#51808) Migrates the Teams extension from @microsoft/agents-hosting to the official Teams SDK (@microsoft/teams.apps + @microsoft/teams.api) and implements Microsoft's AI UX best practices for Teams agents. - AI-generated label on all bot messages (Teams native badge + thumbs up/down) - Streaming responses in 1:1 chats via Teams streaminfo protocol - Welcome card with configurable prompt starters on bot install - Feedback with reflective learning (negative feedback triggers background reflection) - Typing indicators for personal + group chats (disabled for channels) - Informative status updates (progress bar while LLM processes) - JWT validation via Teams SDK createServiceTokenValidator - User-Agent: teams.ts[apps]/ OpenClaw/ on outbound requests - Fix copy-pasted image downloads (smba.trafficmanager.net auth allowlist) - Pre-parse auth gate (reject unauthenticated requests before body parsing) - Reflection dispatcher lifecycle fix (prevent leaked dispatchers) - Colon-safe session filenames (Windows compatibility) - Cooldown cache eviction (prevent unbounded memory growth) Closes #51806 --- docs/.generated/config-baseline.json | 2 +- docs/.generated/config-baseline.jsonl | 2 +- extensions/msteams/package.json | 10 +- extensions/msteams/src/ai-entity.ts | 7 + extensions/msteams/src/attachments/shared.ts | 3 + .../msteams/src/conversation-store-fs.test.ts | 50 + .../msteams/src/conversation-store-fs.ts | 4 + extensions/msteams/src/conversation-store.ts | 2 + .../msteams/src/feedback-reflection.test.ts | 138 ++ extensions/msteams/src/feedback-reflection.ts | 353 +++++ extensions/msteams/src/file-consent.ts | 3 + extensions/msteams/src/graph-upload.ts | 135 +- extensions/msteams/src/graph.test.ts | 52 +- extensions/msteams/src/graph.ts | 12 +- .../msteams/src/graph.user-agent.test.ts | 52 + extensions/msteams/src/messenger.test.ts | 96 +- extensions/msteams/src/messenger.ts | 21 +- extensions/msteams/src/monitor-handler.ts | 216 ++- .../src/monitor-handler/message-handler.ts | 25 +- .../msteams/src/monitor.lifecycle.test.ts | 6 + extensions/msteams/src/monitor.ts | 120 +- extensions/msteams/src/probe.test.ts | 19 +- extensions/msteams/src/probe.ts | 16 +- extensions/msteams/src/reply-dispatcher.ts | 129 +- extensions/msteams/src/sdk-types.ts | 44 +- extensions/msteams/src/sdk.ts | 376 ++++- extensions/msteams/src/send-context.ts | 10 +- .../msteams/src/streaming-message.test.ts | 206 +++ extensions/msteams/src/streaming-message.ts | 267 ++++ extensions/msteams/src/user-agent.test.ts | 45 + extensions/msteams/src/user-agent.ts | 45 + extensions/msteams/src/welcome-card.test.ts | 57 + extensions/msteams/src/welcome-card.ts | 57 + pnpm-lock.yaml | 1385 +++++++++++------ scripts/tsdown-build.mjs | 2 +- .../reply/dispatch-from-config.test.ts | 52 + src/auto-reply/reply/dispatch-from-config.ts | 4 +- src/config/types.msteams.ts | 12 + .../bundled-plugin-metadata.generated.ts | 4 +- 39 files changed, 3349 insertions(+), 690 deletions(-) create mode 100644 extensions/msteams/src/ai-entity.ts create mode 100644 extensions/msteams/src/feedback-reflection.test.ts create mode 100644 extensions/msteams/src/feedback-reflection.ts create mode 100644 extensions/msteams/src/graph.user-agent.test.ts create mode 100644 extensions/msteams/src/streaming-message.test.ts create mode 100644 extensions/msteams/src/streaming-message.ts create mode 100644 extensions/msteams/src/user-agent.test.ts create mode 100644 extensions/msteams/src/user-agent.ts create mode 100644 extensions/msteams/src/welcome-card.test.ts create mode 100644 extensions/msteams/src/welcome-card.ts diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index b1ed24f9fd8..53f29e6db11 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -24324,7 +24324,7 @@ "network" ], "label": "Microsoft Teams", - "help": "Bot Framework; enterprise support.", + "help": "Teams SDK; enterprise support.", "hasChildren": true }, { diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index c8dd49163d9..eff3191af3e 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -2181,7 +2181,7 @@ {"recordType":"path","path":"channels.mattermost.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Require Mention","help":"Require @mention in channels before responding (default: true).","hasChildren":false} {"recordType":"path","path":"channels.mattermost.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.mattermost.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} -{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Microsoft Teams","help":"Bot Framework; enterprise support.","hasChildren":true} +{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Microsoft Teams","help":"Teams SDK; enterprise support.","hasChildren":true} {"recordType":"path","path":"channels.msteams.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.msteams.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.msteams.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 9c8cfc6c00a..acd7e5b8b4e 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -4,9 +4,9 @@ "description": "OpenClaw Microsoft Teams channel plugin", "type": "module", "dependencies": { - "@microsoft/agents-hosting": "^1.4.1", - "express": "^5.2.1", - "uuid": "^13.0.0" + "@microsoft/teams.api": "2.0.5", + "@microsoft/teams.apps": "2.0.5", + "express": "^5.2.1" }, "devDependencies": { "openclaw": "workspace:*" @@ -27,10 +27,10 @@ "channel": { "id": "msteams", "label": "Microsoft Teams", - "selectionLabel": "Microsoft Teams (Bot Framework)", + "selectionLabel": "Microsoft Teams (Teams SDK)", "docsPath": "/channels/msteams", "docsLabel": "msteams", - "blurb": "Bot Framework; enterprise support.", + "blurb": "Teams SDK; enterprise support.", "aliases": [ "teams" ], diff --git a/extensions/msteams/src/ai-entity.ts b/extensions/msteams/src/ai-entity.ts new file mode 100644 index 00000000000..dba7ddd9bab --- /dev/null +++ b/extensions/msteams/src/ai-entity.ts @@ -0,0 +1,7 @@ +/** AI-generated content entity added to every outbound AI message. */ +export const AI_GENERATED_ENTITY = { + type: "https://schema.org/Message", + "@type": "Message", + "@id": "", + additionalType: ["AIGeneratedContent"], +}; diff --git a/extensions/msteams/src/attachments/shared.ts b/extensions/msteams/src/attachments/shared.ts index 33af8077cd0..56c04e725ec 100644 --- a/extensions/msteams/src/attachments/shared.ts +++ b/extensions/msteams/src/attachments/shared.ts @@ -59,6 +59,9 @@ export const DEFAULT_MEDIA_HOST_ALLOWLIST = [ export const DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST = [ "api.botframework.com", "botframework.com", + // Bot Framework Service URL (smba.trafficmanager.net) used for outbound + // replies and inbound attachment downloads (clipboard-pasted images). + "smba.trafficmanager.net", "graph.microsoft.com", "graph.microsoft.us", "graph.microsoft.de", diff --git a/extensions/msteams/src/conversation-store-fs.test.ts b/extensions/msteams/src/conversation-store-fs.test.ts index 79253a51e3c..47a595606a0 100644 --- a/extensions/msteams/src/conversation-store-fs.test.ts +++ b/extensions/msteams/src/conversation-store-fs.test.ts @@ -72,4 +72,54 @@ describe("msteams conversation store (fs)", () => { "19:new@thread.tacv2", ]); }); + + it("stores and retrieves timezone from conversation reference", async () => { + const stateDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-store-")); + const store = createMSTeamsConversationStoreFs({ + env: { ...process.env, OPENCLAW_STATE_DIR: stateDir }, + ttlMs: 60_000, + }); + + const ref: StoredConversationReference = { + conversation: { id: "19:tz-test@thread.tacv2" }, + channelId: "msteams", + serviceUrl: "https://service.example.com", + user: { id: "u1", aadObjectId: "aad1" }, + timezone: "America/Los_Angeles", + }; + + await store.upsert("19:tz-test@thread.tacv2", ref); + + const retrieved = await store.get("19:tz-test@thread.tacv2"); + expect(retrieved).not.toBeNull(); + expect(retrieved!.timezone).toBe("America/Los_Angeles"); + }); + + it("preserves existing timezone when upsert omits timezone", async () => { + const stateDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-store-")); + const store = createMSTeamsConversationStoreFs({ + env: { ...process.env, OPENCLAW_STATE_DIR: stateDir }, + ttlMs: 60_000, + }); + + await store.upsert("19:tz-keep@thread.tacv2", { + conversation: { id: "19:tz-keep@thread.tacv2" }, + channelId: "msteams", + serviceUrl: "https://service.example.com", + user: { id: "u1" }, + timezone: "Europe/London", + }); + + // Second upsert without timezone field + await store.upsert("19:tz-keep@thread.tacv2", { + conversation: { id: "19:tz-keep@thread.tacv2" }, + channelId: "msteams", + serviceUrl: "https://service.example.com", + user: { id: "u1" }, + }); + + const retrieved = await store.get("19:tz-keep@thread.tacv2"); + expect(retrieved).not.toBeNull(); + expect(retrieved!.timezone).toBe("Europe/London"); + }); }); diff --git a/extensions/msteams/src/conversation-store-fs.ts b/extensions/msteams/src/conversation-store-fs.ts index 8257114fc89..9f49d8eea6a 100644 --- a/extensions/msteams/src/conversation-store-fs.ts +++ b/extensions/msteams/src/conversation-store-fs.ts @@ -137,7 +137,11 @@ export function createMSTeamsConversationStoreFs(params?: { const normalizedId = normalizeConversationId(conversationId); await withFileLock(filePath, empty, async () => { const store = await readStore(); + const existing = store.conversations[normalizedId]; store.conversations[normalizedId] = { + // Preserve fields from previous entry that may not be present on every activity + // (e.g. timezone is only sent when clientInfo entity is available). + ...(existing?.timezone && !reference.timezone ? { timezone: existing.timezone } : {}), ...reference, lastSeenAt: new Date().toISOString(), }; diff --git a/extensions/msteams/src/conversation-store.ts b/extensions/msteams/src/conversation-store.ts index a32bb717aff..8c49e92aed4 100644 --- a/extensions/msteams/src/conversation-store.ts +++ b/extensions/msteams/src/conversation-store.ts @@ -32,6 +32,8 @@ export type StoredConversationReference = { * Graph-native chat ID so we don't need to re-query the API on every send. */ graphChatId?: string; + /** IANA timezone from Teams clientInfo entity (e.g. "America/New_York") */ + timezone?: string; }; export type MSTeamsConversationStoreEntry = { diff --git a/extensions/msteams/src/feedback-reflection.test.ts b/extensions/msteams/src/feedback-reflection.test.ts new file mode 100644 index 00000000000..599b5e5079e --- /dev/null +++ b/extensions/msteams/src/feedback-reflection.test.ts @@ -0,0 +1,138 @@ +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + buildFeedbackEvent, + buildReflectionPrompt, + clearReflectionCooldowns, + isReflectionAllowed, + loadSessionLearnings, + recordReflectionTime, +} from "./feedback-reflection.js"; + +describe("buildFeedbackEvent", () => { + it("builds a well-formed custom event", () => { + const event = buildFeedbackEvent({ + messageId: "msg-123", + value: "negative", + comment: "too verbose", + sessionKey: "msteams:user1", + agentId: "default", + conversationId: "19:abc", + }); + + expect(event.type).toBe("custom"); + expect(event.event).toBe("feedback"); + expect(event.value).toBe("negative"); + expect(event.comment).toBe("too verbose"); + expect(event.messageId).toBe("msg-123"); + expect(event.ts).toBeGreaterThan(0); + }); + + it("omits comment when not provided", () => { + const event = buildFeedbackEvent({ + messageId: "msg-123", + value: "positive", + sessionKey: "msteams:user1", + agentId: "default", + conversationId: "19:abc", + }); + + expect(event.comment).toBeUndefined(); + expect(event.value).toBe("positive"); + }); +}); + +describe("buildReflectionPrompt", () => { + it("includes the thumbed-down response", () => { + const prompt = buildReflectionPrompt({ + thumbedDownResponse: "Here is a long explanation...", + }); + + expect(prompt).toContain("previous response wasn't helpful"); + expect(prompt).toContain("Here is a long explanation..."); + expect(prompt).toContain("reflect"); + }); + + it("truncates long responses", () => { + const longResponse = "x".repeat(600); + const prompt = buildReflectionPrompt({ + thumbedDownResponse: longResponse, + }); + + expect(prompt).toContain("..."); + expect(prompt.length).toBeLessThan(longResponse.length + 500); + }); + + it("includes user comment when provided", () => { + const prompt = buildReflectionPrompt({ + thumbedDownResponse: "Some response", + userComment: "Too wordy", + }); + + expect(prompt).toContain('User\'s comment: "Too wordy"'); + }); + + it("works without optional params", () => { + const prompt = buildReflectionPrompt({}); + expect(prompt).toContain("previous response wasn't helpful"); + expect(prompt).toContain("reflect"); + }); +}); + +describe("reflection cooldown", () => { + afterEach(() => { + clearReflectionCooldowns(); + }); + + it("allows first reflection", () => { + expect(isReflectionAllowed("session-1")).toBe(true); + }); + + it("blocks reflection within cooldown", () => { + recordReflectionTime("session-1"); + expect(isReflectionAllowed("session-1", 60_000)).toBe(false); + }); + + it("allows reflection after cooldown expires", () => { + // Manually set a past timestamp + recordReflectionTime("session-1"); + // Override the map entry to simulate time passing + clearReflectionCooldowns(); + expect(isReflectionAllowed("session-1", 1)).toBe(true); + }); + + it("tracks sessions independently", () => { + recordReflectionTime("session-1"); + expect(isReflectionAllowed("session-1", 60_000)).toBe(false); + expect(isReflectionAllowed("session-2", 60_000)).toBe(true); + }); +}); + +describe("loadSessionLearnings", () => { + let tmpDir: string; + + afterEach(async () => { + if (tmpDir) { + await rm(tmpDir, { recursive: true, force: true }); + } + }); + + it("returns empty array when file doesn't exist", async () => { + tmpDir = await mkdtemp(path.join(os.tmpdir(), "learnings-test-")); + const learnings = await loadSessionLearnings(tmpDir, "nonexistent"); + expect(learnings).toEqual([]); + }); + + it("reads existing learnings", async () => { + tmpDir = await mkdtemp(path.join(os.tmpdir(), "learnings-test-")); + // Colons are sanitized to underscores in filenames (Windows compat) + const safeKey = "msteams_user1"; + const filePath = path.join(tmpDir, `${safeKey}.learnings.json`); + await writeFile(filePath, JSON.stringify(["Be concise", "Use examples"]), "utf-8"); + + const learnings = await loadSessionLearnings(tmpDir, "msteams:user1"); + expect(learnings).toEqual(["Be concise", "Use examples"]); + }); +}); diff --git a/extensions/msteams/src/feedback-reflection.ts b/extensions/msteams/src/feedback-reflection.ts new file mode 100644 index 00000000000..ccfee9e36f8 --- /dev/null +++ b/extensions/msteams/src/feedback-reflection.ts @@ -0,0 +1,353 @@ +/** + * Background reflection triggered by negative user feedback (thumbs-down). + * + * Flow: + * 1. User thumbs-down → invoke handler acks immediately + * 2. This module runs in the background (fire-and-forget) + * 3. Reads recent session context + * 4. Sends a synthetic reflection prompt to the agent + * 5. Stores the derived learning in session + * 6. Optionally sends a proactive follow-up to the user + */ + +import { dispatchReplyFromConfigWithSettledDispatcher } from "openclaw/plugin-sdk/msteams"; +import type { OpenClawConfig } from "../runtime-api.js"; +import type { StoredConversationReference } from "./conversation-store.js"; +import type { MSTeamsAdapter } from "./messenger.js"; +import { buildConversationReference, sendMSTeamsMessages } from "./messenger.js"; +import type { MSTeamsMonitorLogger } from "./monitor-types.js"; +import { getMSTeamsRuntime } from "./runtime.js"; + +/** Default cooldown between reflections per session (5 minutes). */ +const DEFAULT_COOLDOWN_MS = 300_000; + +/** Max chars of the thumbed-down response to include in the reflection prompt. */ +const MAX_RESPONSE_CHARS = 500; + +/** Tracks last reflection time per session to enforce cooldown. */ +const lastReflectionBySession = new Map(); + +/** Maximum cooldown entries before pruning expired ones. */ +const MAX_COOLDOWN_ENTRIES = 500; + +/** Prune expired cooldown entries to prevent unbounded memory growth. */ +function pruneExpiredCooldowns(cooldownMs: number): void { + if (lastReflectionBySession.size <= MAX_COOLDOWN_ENTRIES) { + return; + } + const now = Date.now(); + for (const [key, time] of lastReflectionBySession) { + if (now - time >= cooldownMs) { + lastReflectionBySession.delete(key); + } + } +} + +export type FeedbackEvent = { + type: "custom"; + event: "feedback"; + ts: number; + messageId: string; + value: "positive" | "negative"; + comment?: string; + sessionKey: string; + agentId: string; + conversationId: string; + reflectionLearning?: string; +}; + +export function buildFeedbackEvent(params: { + messageId: string; + value: "positive" | "negative"; + comment?: string; + sessionKey: string; + agentId: string; + conversationId: string; +}): FeedbackEvent { + return { + type: "custom", + event: "feedback", + ts: Date.now(), + messageId: params.messageId, + value: params.value, + comment: params.comment, + sessionKey: params.sessionKey, + agentId: params.agentId, + conversationId: params.conversationId, + }; +} + +export function buildReflectionPrompt(params: { + thumbedDownResponse?: string; + userComment?: string; +}): string { + const parts: string[] = ["A user indicated your previous response wasn't helpful."]; + + if (params.thumbedDownResponse) { + const truncated = + params.thumbedDownResponse.length > MAX_RESPONSE_CHARS + ? `${params.thumbedDownResponse.slice(0, MAX_RESPONSE_CHARS)}...` + : params.thumbedDownResponse; + parts.push(`\nYour response was:\n> ${truncated}`); + } + + if (params.userComment) { + parts.push(`\nUser's comment: "${params.userComment}"`); + } + + parts.push( + "\nBriefly reflect: what could you improve? Consider tone, length, " + + "accuracy, relevance, and specificity. Reply with:\n" + + "1. A short adjustment note (1-2 sentences) for your future behavior " + + "in this conversation.\n" + + "2. Whether you should follow up with the user (yes if the adjustment " + + "is non-obvious or you have a clarifying question; no if minor).\n" + + "3. If following up, draft a brief message to the user.", + ); + + return parts.join("\n"); +} + +/** + * Check if a reflection is allowed (cooldown not active). + */ +export function isReflectionAllowed(sessionKey: string, cooldownMs?: number): boolean { + const cooldown = cooldownMs ?? DEFAULT_COOLDOWN_MS; + const lastTime = lastReflectionBySession.get(sessionKey); + if (lastTime == null) { + return true; + } + return Date.now() - lastTime >= cooldown; +} + +/** + * Record that a reflection was run for a session. + */ +export function recordReflectionTime(sessionKey: string): void { + lastReflectionBySession.set(sessionKey, Date.now()); + pruneExpiredCooldowns(DEFAULT_COOLDOWN_MS); +} + +/** + * Clear reflection cooldown tracking (for tests). + */ +export function clearReflectionCooldowns(): void { + lastReflectionBySession.clear(); +} + +export type RunFeedbackReflectionParams = { + cfg: OpenClawConfig; + adapter: MSTeamsAdapter; + appId: string; + conversationRef: StoredConversationReference; + sessionKey: string; + agentId: string; + conversationId: string; + feedbackMessageId: string; + thumbedDownResponse?: string; + userComment?: string; + log: MSTeamsMonitorLogger; +}; + +/** + * Run a background reflection after negative feedback. + * This is designed to be called fire-and-forget (don't await in the invoke handler). + */ +export async function runFeedbackReflection(params: RunFeedbackReflectionParams): Promise { + const { cfg, log, sessionKey } = params; + const msteamsCfg = cfg.channels?.msteams; + + // Check cooldown + const cooldownMs = msteamsCfg?.feedbackReflectionCooldownMs ?? DEFAULT_COOLDOWN_MS; + if (!isReflectionAllowed(sessionKey, cooldownMs)) { + log.debug?.("skipping reflection (cooldown active)", { sessionKey }); + return; + } + + // Record cooldown after successful dispatch (not before) so transient + // failures don't suppress future reflection attempts. + + const core = getMSTeamsRuntime(); + const reflectionPrompt = buildReflectionPrompt({ + thumbedDownResponse: params.thumbedDownResponse, + userComment: params.userComment, + }); + + // Use the agentId from the feedback handler (already resolved with correct routing) + // instead of re-resolving, which could yield a different agent in peer-specific setups. + const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { + agentId: params.agentId, + }); + + const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg); + const body = core.channel.reply.formatAgentEnvelope({ + channel: "Teams", + from: "system", + body: reflectionPrompt, + envelope: envelopeOptions, + }); + + const ctxPayload = core.channel.reply.finalizeInboundContext({ + Body: body, + BodyForAgent: reflectionPrompt, + RawBody: reflectionPrompt, + CommandBody: reflectionPrompt, + From: `msteams:system:${params.conversationId}`, + To: `conversation:${params.conversationId}`, + SessionKey: params.sessionKey, + ChatType: "direct" as const, + SenderName: "system", + SenderId: "system", + Provider: "msteams" as const, + Surface: "msteams" as const, + Timestamp: Date.now(), + WasMentioned: true, + CommandAuthorized: false, + OriginatingChannel: "msteams" as const, + OriginatingTo: `conversation:${params.conversationId}`, + }); + + // Capture the reflection response instead of sending it directly. + // We only want to proactively message if the agent decides to follow up. + let reflectionResponse = ""; + + const noopTypingCallbacks = { + onReplyStart: async () => {}, + onIdle: () => {}, + onCleanup: () => {}, + }; + + const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({ + deliver: async (payload) => { + if (payload.text) { + reflectionResponse += (reflectionResponse ? "\n" : "") + payload.text; + } + }, + typingCallbacks: noopTypingCallbacks, + humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, params.agentId), + onError: (err) => { + log.debug?.("reflection reply error", { error: String(err) }); + }, + }); + + try { + await dispatchReplyFromConfigWithSettledDispatcher({ + ctxPayload, + cfg, + dispatcher, + onSettled: () => {}, + replyOptions, + }); + } catch (err) { + log.error("reflection dispatch failed", { error: String(err) }); + // Don't record cooldown — allow retry on next feedback + return; + } + + if (!reflectionResponse.trim()) { + log.debug?.("reflection produced no output"); + return; + } + + // Reflection succeeded — record cooldown now + recordReflectionTime(sessionKey); + + log.info("reflection complete", { + sessionKey, + responseLength: reflectionResponse.length, + }); + + // Store the learning in the session + try { + await storeSessionLearning({ + storePath, + sessionKey: params.sessionKey, + learning: reflectionResponse.trim(), + }); + } catch (err) { + log.debug?.("failed to store reflection learning", { error: String(err) }); + } + + // Send proactive follow-up if the reflection suggests one. + // Simple heuristic: if the response contains "follow up: yes" or similar, + // or if it's reasonably short (a direct message to the user). + // For now, always send the reflection as a follow-up — the prompt asks + // the agent to decide, and it will draft a user-facing message if appropriate. + const shouldNotify = + reflectionResponse.toLowerCase().includes("follow up") || reflectionResponse.length < 300; + + if (shouldNotify) { + try { + const baseRef = buildConversationReference(params.conversationRef); + const proactiveRef = { ...baseRef, activityId: undefined }; + + await params.adapter.continueConversation(params.appId, proactiveRef, async (ctx) => { + await ctx.sendActivity({ + type: "message", + text: reflectionResponse.trim(), + }); + }); + log.info("sent reflection follow-up", { sessionKey }); + } catch (err) { + log.debug?.("failed to send reflection follow-up", { error: String(err) }); + } + } +} + +/** + * Store a learning derived from feedback reflection in a session companion file. + */ +async function storeSessionLearning(params: { + storePath: string; + sessionKey: string; + learning: string; +}): Promise { + const fs = await import("node:fs/promises"); + const path = await import("node:path"); + + const safeKey = params.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_"); + const learningsFile = path.join(params.storePath, `${safeKey}.learnings.json`); + + let learnings: string[] = []; + try { + const existing = await fs.readFile(learningsFile, "utf-8"); + const parsed = JSON.parse(existing); + if (Array.isArray(parsed)) { + learnings = parsed; + } + } catch { + // File doesn't exist yet — start fresh. + } + + learnings.push(params.learning); + + // Cap at 10 learnings to avoid unbounded growth + if (learnings.length > 10) { + learnings = learnings.slice(-10); + } + + await fs.mkdir(path.dirname(learningsFile), { recursive: true }); + await fs.writeFile(learningsFile, JSON.stringify(learnings, null, 2), "utf-8"); +} + +/** + * Load session learnings for injection into extraSystemPrompt. + */ +export async function loadSessionLearnings( + storePath: string, + sessionKey: string, +): Promise { + const fs = await import("node:fs/promises"); + const path = await import("node:path"); + + const safeKey = sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_"); + const learningsFile = path.join(storePath, `${safeKey}.learnings.json`); + + try { + const content = await fs.readFile(learningsFile, "utf-8"); + const parsed = JSON.parse(content); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} diff --git a/extensions/msteams/src/file-consent.ts b/extensions/msteams/src/file-consent.ts index 268e82fff64..8f79595f599 100644 --- a/extensions/msteams/src/file-consent.ts +++ b/extensions/msteams/src/file-consent.ts @@ -8,6 +8,8 @@ * - Parsing fileConsent/invoke activities */ +import { buildUserAgent } from "./user-agent.js"; + export interface FileConsentCardParams { filename: string; description?: string; @@ -114,6 +116,7 @@ export async function uploadToConsentUrl(params: { const res = await fetchFn(params.url, { method: "PUT", headers: { + "User-Agent": buildUserAgent(), "Content-Type": params.contentType ?? "application/octet-stream", "Content-Range": `bytes 0-${params.buffer.length - 1}/${params.buffer.length}`, }, diff --git a/extensions/msteams/src/graph-upload.ts b/extensions/msteams/src/graph-upload.ts index 61303cf877b..7e8be062325 100644 --- a/extensions/msteams/src/graph-upload.ts +++ b/extensions/msteams/src/graph-upload.ts @@ -10,6 +10,7 @@ */ import type { MSTeamsAccessTokenProvider } from "./attachments/types.js"; +import { buildUserAgent } from "./user-agent.js"; const GRAPH_ROOT = "https://graph.microsoft.com/v1.0"; const GRAPH_BETA = "https://graph.microsoft.com/beta"; @@ -21,53 +22,6 @@ export interface OneDriveUploadResult { name: string; } -function parseUploadedDriveItem( - data: { id?: string; webUrl?: string; name?: string }, - label: "OneDrive" | "SharePoint", -): OneDriveUploadResult { - if (!data.id || !data.webUrl || !data.name) { - throw new Error(`${label} upload response missing required fields`); - } - - return { - id: data.id, - webUrl: data.webUrl, - name: data.name, - }; -} - -async function uploadDriveItem(params: { - buffer: Buffer; - filename: string; - contentType?: string; - tokenProvider: MSTeamsAccessTokenProvider; - fetchFn?: typeof fetch; - url: string; - label: "OneDrive" | "SharePoint"; -}): Promise { - const fetchFn = params.fetchFn ?? fetch; - const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); - - const res = await fetchFn(params.url, { - method: "PUT", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": params.contentType ?? "application/octet-stream", - }, - body: new Uint8Array(params.buffer), - }); - - if (!res.ok) { - const body = await res.text().catch(() => ""); - throw new Error(`${params.label} upload failed: ${res.status} ${res.statusText} - ${body}`); - } - - return parseUploadedDriveItem( - (await res.json()) as { id?: string; webUrl?: string; name?: string }, - params.label, - ); -} - /** * Upload a file to the user's OneDrive root folder. * For larger files, this uses the simple upload endpoint (up to 4MB). @@ -79,13 +33,42 @@ export async function uploadToOneDrive(params: { tokenProvider: MSTeamsAccessTokenProvider; fetchFn?: typeof fetch; }): Promise { + const fetchFn = params.fetchFn ?? fetch; + const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); + // Use "OpenClawShared" folder to organize bot-uploaded files const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`; - return await uploadDriveItem({ - ...params, - url: `${GRAPH_ROOT}/me/drive/root:${uploadPath}:/content`, - label: "OneDrive", + + const res = await fetchFn(`${GRAPH_ROOT}/me/drive/root:${uploadPath}:/content`, { + method: "PUT", + headers: { + "User-Agent": buildUserAgent(), + Authorization: `Bearer ${token}`, + "Content-Type": params.contentType ?? "application/octet-stream", + }, + body: new Uint8Array(params.buffer), }); + + if (!res.ok) { + const body = await res.text().catch(() => ""); + throw new Error(`OneDrive upload failed: ${res.status} ${res.statusText} - ${body}`); + } + + const data = (await res.json()) as { + id?: string; + webUrl?: string; + name?: string; + }; + + if (!data.id || !data.webUrl || !data.name) { + throw new Error("OneDrive upload response missing required fields"); + } + + return { + id: data.id, + webUrl: data.webUrl, + name: data.name, + }; } export interface OneDriveSharingLink { @@ -109,6 +92,7 @@ export async function createSharingLink(params: { const res = await fetchFn(`${GRAPH_ROOT}/me/drive/items/${params.itemId}/createLink`, { method: "POST", headers: { + "User-Agent": buildUserAgent(), Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, @@ -194,13 +178,45 @@ export async function uploadToSharePoint(params: { siteId: string; fetchFn?: typeof fetch; }): Promise { + const fetchFn = params.fetchFn ?? fetch; + const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); + // Use "OpenClawShared" folder to organize bot-uploaded files const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`; - return await uploadDriveItem({ - ...params, - url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`, - label: "SharePoint", - }); + + const res = await fetchFn( + `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`, + { + method: "PUT", + headers: { + "User-Agent": buildUserAgent(), + Authorization: `Bearer ${token}`, + "Content-Type": params.contentType ?? "application/octet-stream", + }, + body: new Uint8Array(params.buffer), + }, + ); + + if (!res.ok) { + const body = await res.text().catch(() => ""); + throw new Error(`SharePoint upload failed: ${res.status} ${res.statusText} - ${body}`); + } + + const data = (await res.json()) as { + id?: string; + webUrl?: string; + name?: string; + }; + + if (!data.id || !data.webUrl || !data.name) { + throw new Error("SharePoint upload response missing required fields"); + } + + return { + id: data.id, + webUrl: data.webUrl, + name: data.name, + }; } export interface ChatMember { @@ -239,7 +255,7 @@ export async function getDriveItemProperties(params: { const res = await fetchFn( `${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, - { headers: { Authorization: `Bearer ${token}` } }, + { headers: { "User-Agent": buildUserAgent(), Authorization: `Bearer ${token}` } }, ); if (!res.ok) { @@ -273,8 +289,6 @@ export async function getDriveItemProperties(params: { * * This function looks up the matching Graph chat by querying the bot's chats filtered * by the target user's AAD object ID. - * - * Returns the Graph chat ID if found, or null if resolution fails. */ export async function resolveGraphChatId(params: { /** Bot Framework conversation ID (may be in non-Graph format for personal DMs) */ @@ -353,7 +367,7 @@ export async function getChatMembers(params: { const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); const res = await fetchFn(`${GRAPH_ROOT}/chats/${params.chatId}/members`, { - headers: { Authorization: `Bearer ${token}` }, + headers: { "User-Agent": buildUserAgent(), Authorization: `Bearer ${token}` }, }); if (!res.ok) { @@ -413,6 +427,7 @@ export async function createSharePointSharingLink(params: { { method: "POST", headers: { + "User-Agent": buildUserAgent(), Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, diff --git a/extensions/msteams/src/graph.test.ts b/extensions/msteams/src/graph.test.ts index 878ad5dc5b3..a027070d1db 100644 --- a/extensions/msteams/src/graph.test.ts +++ b/extensions/msteams/src/graph.test.ts @@ -1,16 +1,22 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -const { loadMSTeamsSdkWithAuthMock, readAccessTokenMock, resolveMSTeamsCredentialsMock } = - vi.hoisted(() => { - return { - loadMSTeamsSdkWithAuthMock: vi.fn(), - readAccessTokenMock: vi.fn(), - resolveMSTeamsCredentialsMock: vi.fn(), - }; - }); +const { + loadMSTeamsSdkWithAuthMock, + createMSTeamsTokenProviderMock, + readAccessTokenMock, + resolveMSTeamsCredentialsMock, +} = vi.hoisted(() => { + return { + loadMSTeamsSdkWithAuthMock: vi.fn(), + createMSTeamsTokenProviderMock: vi.fn(), + readAccessTokenMock: vi.fn(), + resolveMSTeamsCredentialsMock: vi.fn(), + }; +}); vi.mock("./sdk.js", () => ({ loadMSTeamsSdkWithAuth: loadMSTeamsSdkWithAuthMock, + createMSTeamsTokenProvider: createMSTeamsTokenProviderMock, })); vi.mock("./token-response.js", () => ({ @@ -66,10 +72,10 @@ describe("msteams graph helpers", () => { expect(globalThis.fetch).toHaveBeenCalledWith( "https://graph.microsoft.com/v1.0/groups?$select=id", { - headers: { + headers: expect.objectContaining({ Authorization: "Bearer graph-token", ConsistencyLevel: "eventual", - }, + }), }, ); @@ -86,25 +92,21 @@ describe("msteams graph helpers", () => { }); it("resolves Graph tokens through the SDK auth provider", async () => { - const getAccessToken = vi.fn(async () => ({ accessToken: "sdk-token" })); - const MsalTokenProvider = vi.fn(function MockMsalTokenProvider() { - return { getAccessToken }; - }); + const getAccessToken = vi.fn(async () => "raw-graph-token"); + const mockApp = { id: "mock-app" }; resolveMSTeamsCredentialsMock.mockReturnValue({ appId: "app-id", appPassword: "app-password", tenantId: "tenant-id", }); - loadMSTeamsSdkWithAuthMock.mockResolvedValue({ - sdk: { MsalTokenProvider }, - authConfig: { clientId: "app-id" }, - }); + loadMSTeamsSdkWithAuthMock.mockResolvedValue({ app: mockApp }); + createMSTeamsTokenProviderMock.mockReturnValue({ getAccessToken }); readAccessTokenMock.mockReturnValue("resolved-token"); await expect(resolveGraphToken({ channels: { msteams: {} } })).resolves.toBe("resolved-token"); - expect(MsalTokenProvider).toHaveBeenCalledWith({ clientId: "app-id" }); + expect(createMSTeamsTokenProviderMock).toHaveBeenCalledWith(mockApp); expect(getAccessToken).toHaveBeenCalledWith("https://graph.microsoft.com"); }); @@ -114,15 +116,9 @@ describe("msteams graph helpers", () => { "MS Teams credentials missing", ); - const getAccessToken = vi.fn(async () => ({ token: null })); - loadMSTeamsSdkWithAuthMock.mockResolvedValue({ - sdk: { - MsalTokenProvider: vi.fn(function MockMsalTokenProvider() { - return { getAccessToken }; - }), - }, - authConfig: { clientId: "app-id" }, - }); + const getAccessToken = vi.fn(async () => null); + loadMSTeamsSdkWithAuthMock.mockResolvedValue({ app: { id: "mock-app" } }); + createMSTeamsTokenProviderMock.mockReturnValue({ getAccessToken }); resolveMSTeamsCredentialsMock.mockReturnValue({ appId: "app-id", appPassword: "app-password", diff --git a/extensions/msteams/src/graph.ts b/extensions/msteams/src/graph.ts index 6100b197c03..62b1e998ab3 100644 --- a/extensions/msteams/src/graph.ts +++ b/extensions/msteams/src/graph.ts @@ -1,8 +1,9 @@ import type { MSTeamsConfig } from "../runtime-api.js"; import { GRAPH_ROOT } from "./attachments/shared.js"; -import { loadMSTeamsSdkWithAuth } from "./sdk.js"; +import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; import { readAccessToken } from "./token-response.js"; import { resolveMSTeamsCredentials } from "./token.js"; +import { buildUserAgent } from "./user-agent.js"; export type GraphUser = { id?: string; @@ -38,6 +39,7 @@ export async function fetchGraphJson(params: { }): Promise { const res = await fetch(`${GRAPH_ROOT}${params.path}`, { headers: { + "User-Agent": buildUserAgent(), Authorization: `Bearer ${params.token}`, ...params.headers, }, @@ -56,10 +58,10 @@ export async function resolveGraphToken(cfg: unknown): Promise { if (!creds) { throw new Error("MS Teams credentials missing"); } - const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds); - const tokenProvider = new sdk.MsalTokenProvider(authConfig); - const token = await tokenProvider.getAccessToken("https://graph.microsoft.com"); - const accessToken = readAccessToken(token); + const { app } = await loadMSTeamsSdkWithAuth(creds); + const tokenProvider = createMSTeamsTokenProvider(app); + const graphTokenValue = await tokenProvider.getAccessToken("https://graph.microsoft.com"); + const accessToken = readAccessToken(graphTokenValue); if (!accessToken) { throw new Error("MS Teams graph token unavailable"); } diff --git a/extensions/msteams/src/graph.user-agent.test.ts b/extensions/msteams/src/graph.user-agent.test.ts new file mode 100644 index 00000000000..bc1503d8b8b --- /dev/null +++ b/extensions/msteams/src/graph.user-agent.test.ts @@ -0,0 +1,52 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("./runtime.js", () => ({ + getMSTeamsRuntime: vi.fn(() => ({ version: "2026.3.19" })), +})); + +import { fetchGraphJson } from "./graph.js"; +import { resetUserAgentCache } from "./user-agent.js"; + +describe("fetchGraphJson User-Agent", () => { + afterEach(() => { + resetUserAgentCache(); + vi.restoreAllMocks(); + }); + + it("sends User-Agent header with OpenClaw version", async () => { + const mockFetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ value: [] }), + }); + vi.stubGlobal("fetch", mockFetch); + + await fetchGraphJson({ token: "test-token", path: "/groups" }); + + expect(mockFetch).toHaveBeenCalledOnce(); + const [, init] = mockFetch.mock.calls[0]; + expect(init.headers["User-Agent"]).toMatch(/^teams\.ts\[apps\]\/.+ OpenClaw\/2026\.3\.19$/); + expect(init.headers).toHaveProperty("Authorization", "Bearer test-token"); + + vi.unstubAllGlobals(); + }); + + it("allows caller headers to override User-Agent", async () => { + const mockFetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ value: [] }), + }); + vi.stubGlobal("fetch", mockFetch); + + await fetchGraphJson({ + token: "test-token", + path: "/groups", + headers: { "User-Agent": "custom-agent/1.0" }, + }); + + const [, init] = mockFetch.mock.calls[0]; + // Caller headers spread after, so they override + expect(init.headers["User-Agent"]).toBe("custom-agent/1.0"); + + vi.unstubAllGlobals(); + }); +}); diff --git a/extensions/msteams/src/messenger.test.ts b/extensions/msteams/src/messenger.test.ts index 890e2e6321f..2fa822aae94 100644 --- a/extensions/msteams/src/messenger.test.ts +++ b/extensions/msteams/src/messenger.test.ts @@ -21,6 +21,7 @@ import { resolvePreferredOpenClawTmpDir } from "../../../src/infra/tmp-openclaw- import { type MSTeamsAdapter, type MSTeamsRenderedMessage, + buildActivity, renderReplyPayloadsToMessages, sendMSTeamsMessages, } from "./messenger.js"; @@ -293,16 +294,21 @@ describe("msteams messenger", () => { expect(firstSent.text).toContain( "📎 [upload.txt](https://onedrive.example.com/share/item123)", ); - expect(firstSent.entities).toEqual([ - { - type: "mention", - text: "John", - mentioned: { - id: "29:08q2j2o3jc09au90eucae", - name: "John", + expect(sent[0]?.entities).toEqual( + expect.arrayContaining([ + { + type: "mention", + text: "John", + mentioned: { + id: "29:08q2j2o3jc09au90eucae", + name: "John", + }, }, - }, - ]); + expect.objectContaining({ + additionalType: ["AIGeneratedContent"], + }), + ]), + ); } finally { await rm(tmpDir, { recursive: true, force: true }); } @@ -477,4 +483,76 @@ describe("msteams messenger", () => { ]); }); }); + + describe("buildActivity AI metadata", () => { + const baseRef: StoredConversationReference = { + activityId: "activity123", + user: { id: "user123", name: "User" }, + agent: { id: "bot123", name: "Bot" }, + conversation: { id: "conv123", conversationType: "personal" }, + channelId: "msteams", + serviceUrl: "https://service.example.com", + }; + + it("adds AI-generated entity to text messages", async () => { + const activity = await buildActivity({ text: "hello" }, baseRef); + const entities = activity.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "https://schema.org/Message", + "@type": "Message", + additionalType: ["AIGeneratedContent"], + }), + ]), + ); + }); + + it("adds AI-generated entity to media-only messages", async () => { + const activity = await buildActivity({ mediaUrl: "https://example.com/img.png" }, baseRef); + const entities = activity.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + additionalType: ["AIGeneratedContent"], + }), + ]), + ); + }); + + it("preserves mention entities alongside AI entity", async () => { + const activity = await buildActivity({ text: "hi @User" }, baseRef); + const entities = activity.entities as Array>; + // Should have at least the AI entity + expect(entities.length).toBeGreaterThanOrEqual(1); + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + additionalType: ["AIGeneratedContent"], + }), + ]), + ); + }); + + it("sets feedbackLoopEnabled in channelData when enabled", async () => { + const activity = await buildActivity( + { text: "hello" }, + baseRef, + undefined, + undefined, + undefined, + { + feedbackLoopEnabled: true, + }, + ); + const channelData = activity.channelData as Record; + expect(channelData.feedbackLoopEnabled).toBe(true); + }); + + it("defaults feedbackLoopEnabled to false", async () => { + const activity = await buildActivity({ text: "hello" }, baseRef); + const channelData = activity.channelData as Record; + expect(channelData.feedbackLoopEnabled).toBe(false); + }); + }); }); diff --git a/extensions/msteams/src/messenger.ts b/extensions/msteams/src/messenger.ts index e9540e3dfea..06af303d744 100644 --- a/extensions/msteams/src/messenger.ts +++ b/extensions/msteams/src/messenger.ts @@ -264,24 +264,32 @@ export function renderReplyPayloadsToMessages( return out; } -async function buildActivity( +import { AI_GENERATED_ENTITY } from "./ai-entity.js"; + +export async function buildActivity( msg: MSTeamsRenderedMessage, conversationRef: StoredConversationReference, tokenProvider?: MSTeamsAccessTokenProvider, sharePointSiteId?: string, mediaMaxBytes?: number, + options?: { feedbackLoopEnabled?: boolean }, ): Promise> { const activity: Record = { type: "message" }; + // Mark as AI-generated so Teams renders the "AI generated" badge. + activity.channelData = { + feedbackLoopEnabled: options?.feedbackLoopEnabled ?? false, + }; + if (msg.text) { // Parse mentions from text (format: @[Name](id)) const { text: formattedText, entities } = parseMentions(msg.text); activity.text = formattedText; - // Add mention entities if any mentions were found - if (entities.length > 0) { - activity.entities = entities; - } + // Start with mention entities (if any) + AI-generated entity + activity.entities = [...(entities.length > 0 ? entities : []), AI_GENERATED_ENTITY]; + } else { + activity.entities = [AI_GENERATED_ENTITY]; } if (msg.mediaUrl) { @@ -401,6 +409,8 @@ export async function sendMSTeamsMessages(params: { sharePointSiteId?: string; /** Max media size in bytes. Default: 100MB. */ mediaMaxBytes?: number; + /** Enable the Teams feedback loop (thumbs up/down) on sent messages. */ + feedbackLoopEnabled?: boolean; }): Promise { const messages = params.messages.filter( (m) => (m.text && m.text.trim().length > 0) || m.mediaUrl, @@ -461,6 +471,7 @@ export async function sendMSTeamsMessages(params: { params.tokenProvider, params.sharePointSiteId, params.mediaMaxBytes, + { feedbackLoopEnabled: params.feedbackLoopEnabled }, ), ), { messageIndex, messageCount: messages.length }, diff --git a/extensions/msteams/src/monitor-handler.ts b/extensions/msteams/src/monitor-handler.ts index 4cda545bd02..1d2561072ef 100644 --- a/extensions/msteams/src/monitor-handler.ts +++ b/extensions/msteams/src/monitor-handler.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig, RuntimeEnv } from "../runtime-api.js"; import type { MSTeamsConversationStore } from "./conversation-store.js"; +import { buildFeedbackEvent, runFeedbackReflection } from "./feedback-reflection.js"; import { buildFileInfoCard, parseFileConsentInvoke, uploadToConsentUrl } from "./file-consent.js"; import { normalizeMSTeamsConversationId } from "./inbound.js"; import type { MSTeamsAdapter } from "./messenger.js"; @@ -8,7 +9,9 @@ import type { MSTeamsMonitorLogger } from "./monitor-types.js"; import { getPendingUpload, removePendingUpload } from "./pending-uploads.js"; import type { MSTeamsPollStore } from "./polls.js"; import { withRevokedProxyFallback } from "./revoked-context.js"; +import { getMSTeamsRuntime } from "./runtime.js"; import type { MSTeamsTurnContext } from "./sdk-types.js"; +import { buildGroupWelcomeText, buildWelcomeCard } from "./welcome-card.js"; export type MSTeamsAccessTokenProvider = { getAccessToken: (scope: string) => Promise; @@ -137,6 +140,161 @@ async function handleFileConsentInvoke( return true; } +/** + * Parse and handle feedback invoke activities (thumbs up/down). + * Returns true if the activity was a feedback invoke, false otherwise. + */ +async function handleFeedbackInvoke( + context: MSTeamsTurnContext, + deps: MSTeamsMessageHandlerDeps, +): Promise { + const activity = context.activity; + const value = activity.value as + | { + actionName?: string; + actionValue?: { reaction?: string; feedback?: string }; + replyToId?: string; + } + | undefined; + + if (!value) { + return false; + } + + // Teams feedback invoke format: actionName="feedback", actionValue.reaction="like"|"dislike" + if (value.actionName !== "feedback") { + return false; + } + + const reaction = value.actionValue?.reaction; + if (reaction !== "like" && reaction !== "dislike") { + deps.log.debug?.("ignoring feedback with unknown reaction", { reaction }); + return false; + } + + const msteamsCfg = deps.cfg.channels?.msteams; + if (msteamsCfg?.feedbackEnabled === false) { + deps.log.debug?.("feedback handling disabled"); + return true; // Still consume the invoke + } + + // Extract user comment from the nested JSON string + let userComment: string | undefined; + if (value.actionValue?.feedback) { + try { + const parsed = JSON.parse(value.actionValue.feedback) as { feedbackText?: string }; + userComment = parsed.feedbackText || undefined; + } catch { + // Best effort — feedback text is optional + } + } + + // Strip ;messageid=... suffix to match the normalized ID used by the message handler. + const conversationId = normalizeMSTeamsConversationId(activity.conversation?.id ?? "unknown"); + const senderId = activity.from?.aadObjectId ?? activity.from?.id ?? "unknown"; + const messageId = value.replyToId ?? activity.replyToId ?? "unknown"; + const isNegative = reaction === "dislike"; + + // Route feedback using the same chat-type logic as normal messages + // so session keys, agent IDs, and transcript paths match. + const convType = activity.conversation?.conversationType?.toLowerCase(); + const isDirectMessage = convType === "personal" || (!convType && !activity.conversation?.isGroup); + const isChannel = convType === "channel"; + + const core = getMSTeamsRuntime(); + const route = core.channel.routing.resolveAgentRoute({ + cfg: deps.cfg, + channel: "msteams", + peer: { + kind: isDirectMessage ? "direct" : isChannel ? "channel" : "group", + id: isDirectMessage ? senderId : conversationId, + }, + }); + + // Log feedback event to session JSONL + const feedbackEvent = buildFeedbackEvent({ + messageId, + value: isNegative ? "negative" : "positive", + comment: userComment, + sessionKey: route.sessionKey, + agentId: route.agentId, + conversationId, + }); + + deps.log.info("received feedback", { + value: feedbackEvent.value, + messageId, + conversationId, + hasComment: Boolean(userComment), + }); + + // Write feedback event to session transcript + try { + const storePath = core.channel.session.resolveStorePath(deps.cfg.session?.store, { + agentId: route.agentId, + }); + const fs = await import("node:fs/promises"); + const pathMod = await import("node:path"); + const safeKey = route.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_"); + const transcriptFile = pathMod.join(storePath, `${safeKey}.jsonl`); + await fs.appendFile(transcriptFile, JSON.stringify(feedbackEvent) + "\n", "utf-8").catch(() => { + // Best effort — transcript dir may not exist yet + }); + } catch { + // Best effort + } + + // Build conversation reference for proactive messages (ack + reflection follow-up) + const conversationRef = { + activityId: activity.id, + user: { + id: activity.from?.id, + name: activity.from?.name, + aadObjectId: activity.from?.aadObjectId, + }, + agent: activity.recipient + ? { id: activity.recipient.id, name: activity.recipient.name } + : undefined, + bot: activity.recipient + ? { id: activity.recipient.id, name: activity.recipient.name } + : undefined, + conversation: { + id: conversationId, + conversationType: activity.conversation?.conversationType, + tenantId: activity.conversation?.tenantId, + }, + channelId: activity.channelId ?? "msteams", + serviceUrl: activity.serviceUrl, + locale: activity.locale, + }; + + // For negative feedback, trigger background reflection (fire-and-forget). + // No ack message — the reflection follow-up serves as the acknowledgement. + // Sending anything during the invoke handler causes "unable to reach app" errors. + if (isNegative && msteamsCfg?.feedbackReflection !== false) { + // Note: thumbedDownResponse is not populated here because we don't cache + // sent message text. The agent still has full session context for reflection + // since the reflection runs in the same session. The user comment (if any) + // provides additional signal. + runFeedbackReflection({ + cfg: deps.cfg, + adapter: deps.adapter, + appId: deps.appId, + conversationRef, + sessionKey: route.sessionKey, + agentId: route.agentId, + conversationId, + feedbackMessageId: messageId, + userComment, + log: deps.log, + }).catch((err) => { + deps.log.error("feedback reflection failed", { error: String(err) }); + }); + } + + return true; +} + export function registerMSTeamsHandlers( handler: T, deps: MSTeamsMessageHandlerDeps, @@ -168,6 +326,18 @@ export function registerMSTeamsHandlers( } return; } + + // Handle feedback invokes (thumbs up/down on AI-generated messages). + // Just return after handling — the process() handler sends HTTP 200 automatically. + // Do NOT call sendActivity with invokeResponse; our custom adapter would POST + // a new activity to Bot Framework instead of responding to the HTTP request. + if (ctx.activity?.type === "invoke" && ctx.activity?.name === "message/submitAction") { + const handled = await handleFeedbackInvoke(ctx, deps); + if (handled) { + return; + } + } + return originalRun.call(handler, context); }; } @@ -182,11 +352,51 @@ export function registerMSTeamsHandlers( }); handler.onMembersAdded(async (context, next) => { - const membersAdded = (context as MSTeamsTurnContext).activity?.membersAdded ?? []; + const ctx = context as MSTeamsTurnContext; + const membersAdded = ctx.activity?.membersAdded ?? []; + const botId = ctx.activity?.recipient?.id; + const msteamsCfg = deps.cfg.channels?.msteams; + for (const member of membersAdded) { - if (member.id !== (context as MSTeamsTurnContext).activity?.recipient?.id) { + if (member.id === botId) { + // Bot was added to a conversation — send welcome card if configured. + const conversationType = + ctx.activity?.conversation?.conversationType?.toLowerCase() ?? "personal"; + const isPersonal = conversationType === "personal"; + + if (isPersonal && msteamsCfg?.welcomeCard !== false) { + const botName = ctx.activity?.recipient?.name ?? undefined; + const card = buildWelcomeCard({ + botName, + promptStarters: msteamsCfg?.promptStarters, + }); + try { + await ctx.sendActivity({ + type: "message", + attachments: [ + { + contentType: "application/vnd.microsoft.card.adaptive", + content: card, + }, + ], + }); + deps.log.info("sent welcome card"); + } catch (err) { + deps.log.debug?.("failed to send welcome card", { error: String(err) }); + } + } else if (!isPersonal && msteamsCfg?.groupWelcomeCard === true) { + const botName = ctx.activity?.recipient?.name ?? undefined; + try { + await ctx.sendActivity(buildGroupWelcomeText(botName)); + deps.log.info("sent group welcome message"); + } catch (err) { + deps.log.debug?.("failed to send group welcome", { error: String(err) }); + } + } else { + deps.log.debug?.("skipping welcome (disabled by config or conversation type)"); + } + } else { deps.log.debug?.("member added", { member: member.id }); - // Don't send welcome message - let the user initiate conversation. } } await next(); diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index 5466243a9fb..cb1bfc9de5e 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -324,6 +324,11 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { return; } + // Extract clientInfo entity (Teams sends this on every activity with timezone, locale, etc.) + const clientInfo = activity.entities?.find((e) => e.type === "clientInfo") as + | { timezone?: string; locale?: string; country?: string; platform?: string } + | undefined; + // Build conversation reference for proactive replies. const agent = activity.recipient; const conversationRef: StoredConversationReference = { @@ -340,6 +345,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { channelId: activity.channelId, serviceUrl: activity.serviceUrl, locale: activity.locale, + // Only set timezone if present (preserve previously stored value on next upsert) + ...(clientInfo?.timezone ? { timezone: clientInfo.timezone } : {}), }; conversationStore.upsert(conversationId, conversationRef).catch((err) => { log.debug?.("failed to save conversation reference", { @@ -575,10 +582,26 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { sharePointSiteId, }); + // Use Teams clientInfo timezone if no explicit userTimezone is configured. + // This ensures the agent knows the sender's timezone for time-aware responses + // and proactive sends within the same session. + // Apply Teams clientInfo timezone if no explicit userTimezone is configured. + const senderTimezone = clientInfo?.timezone || conversationRef.timezone; + const effectiveCfg = + senderTimezone && !cfg.agents?.defaults?.userTimezone + ? { + ...cfg, + agents: { + ...cfg.agents, + defaults: { ...cfg.agents?.defaults, userTimezone: senderTimezone }, + }, + } + : cfg; + log.info("dispatching to agent", { sessionKey: route.sessionKey }); try { const { queuedFinal, counts } = await dispatchReplyFromConfigWithSettledDispatcher({ - cfg, + cfg: effectiveCfg, ctxPayload, dispatcher, onSettled: () => markDispatchIdle(), diff --git a/extensions/msteams/src/monitor.lifecycle.test.ts b/extensions/msteams/src/monitor.lifecycle.test.ts index b1ca885c0c2..d61ca057ed6 100644 --- a/extensions/msteams/src/monitor.lifecycle.test.ts +++ b/extensions/msteams/src/monitor.lifecycle.test.ts @@ -113,6 +113,12 @@ vi.mock("./resolve-allowlist.js", () => ({ vi.mock("./sdk.js", () => ({ createMSTeamsAdapter: () => createMSTeamsAdapter(), loadMSTeamsSdkWithAuth: () => loadMSTeamsSdkWithAuth(), + createMSTeamsTokenProvider: () => ({ + getAccessToken: vi.fn().mockResolvedValue("mock-token"), + }), + createBotFrameworkJwtValidator: vi.fn().mockResolvedValue({ + validate: vi.fn().mockResolvedValue(true), + }), })); vi.mock("./runtime.js", () => ({ diff --git a/extensions/msteams/src/monitor.ts b/extensions/msteams/src/monitor.ts index 0f3f2905763..1ea4323c4b1 100644 --- a/extensions/msteams/src/monitor.ts +++ b/extensions/msteams/src/monitor.ts @@ -18,7 +18,12 @@ import { resolveMSTeamsUserAllowlist, } from "./resolve-allowlist.js"; import { getMSTeamsRuntime } from "./runtime.js"; -import { createMSTeamsAdapter, loadMSTeamsSdkWithAuth } from "./sdk.js"; +import { + createBotFrameworkJwtValidator, + createMSTeamsAdapter, + createMSTeamsTokenProvider, + loadMSTeamsSdkWithAuth, +} from "./sdk.js"; import { resolveMSTeamsCredentials } from "./token.js"; import { applyMSTeamsWebhookTimeouts, @@ -224,14 +229,16 @@ export async function monitorMSTeamsProvider( // Dynamic import to avoid loading SDK when provider is disabled const express = await import("express"); - const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds); - const { ActivityHandler, MsalTokenProvider, authorizeJWT } = sdk; + const { sdk, app } = await loadMSTeamsSdkWithAuth(creds); - // Auth configuration - create early so adapter is available for deliverReplies - const tokenProvider = new MsalTokenProvider(authConfig); - const adapter = createMSTeamsAdapter(authConfig, sdk); + // Build a token provider adapter for Graph API operations + const tokenProvider = createMSTeamsTokenProvider(app); - const handler = registerMSTeamsHandlers(new ActivityHandler() as MSTeamsActivityHandler, { + const adapter = createMSTeamsAdapter(app, sdk); + + // Build a simple ActivityHandler-compatible object + const handler = buildActivityHandler(); + registerMSTeamsHandlers(handler, { cfg, runtime, appId, @@ -246,7 +253,19 @@ export async function monitorMSTeamsProvider( // Create Express server const expressApp = express.default(); - expressApp.use(authorizeJWT(authConfig)); + + // Cheap pre-parse auth gate: reject requests without a Bearer token before + // spending CPU/memory on JSON body parsing. This prevents unauthenticated + // request floods from forcing body parsing on internet-exposed webhooks. + expressApp.use((req: Request, res: Response, next: (err?: unknown) => void) => { + const auth = req.headers.authorization; + if (!auth || !auth.startsWith("Bearer ")) { + res.status(401).json({ error: "Unauthorized" }); + return; + } + next(); + }); + expressApp.use(express.json({ limit: MSTEAMS_WEBHOOK_MAX_BODY_BYTES })); expressApp.use((err: unknown, _req: Request, res: Response, next: (err?: unknown) => void) => { if (err && typeof err === "object" && "status" in err && err.status === 413) { @@ -256,6 +275,29 @@ export async function monitorMSTeamsProvider( next(err); }); + // JWT validation — verify Bot Framework tokens using the Teams SDK's + // JwtValidator (validates signature via JWKS, audience, issuer, expiration). + const jwtValidator = await createBotFrameworkJwtValidator(creds); + expressApp.use((req: Request, res: Response, next: (err?: unknown) => void) => { + // Authorization header is guaranteed by the pre-parse auth gate above. + const authHeader = req.headers.authorization!; + const serviceUrl = (req.body as Record)?.serviceUrl as string | undefined; + jwtValidator + .validate(authHeader, serviceUrl) + .then((valid) => { + if (!valid) { + log.debug?.("JWT validation failed"); + res.status(401).json({ error: "Unauthorized" }); + return; + } + next(); + }) + .catch((err) => { + log.debug?.(`JWT validation error: ${String(err)}`); + res.status(401).json({ error: "Unauthorized" }); + }); + }); + // Set up the messages endpoint - use configured path and /api/messages as fallback const configuredPath = msteamsCfg.webhook?.path ?? "/api/messages"; const messageHandler = (req: Request, res: Response) => { @@ -320,3 +362,65 @@ export async function monitorMSTeamsProvider( return { app: expressApp, shutdown }; } + +/** + * Build a minimal ActivityHandler-compatible object that supports + * onMessage / onMembersAdded registration and a run() method. + */ +function buildActivityHandler(): MSTeamsActivityHandler { + type Handler = (context: unknown, next: () => Promise) => Promise; + const messageHandlers: Handler[] = []; + const membersAddedHandlers: Handler[] = []; + const reactionsAddedHandlers: Handler[] = []; + const reactionsRemovedHandlers: Handler[] = []; + + const handler: MSTeamsActivityHandler = { + onMessage(cb) { + messageHandlers.push(cb); + return handler; + }, + onMembersAdded(cb) { + membersAddedHandlers.push(cb); + return handler; + }, + onReactionsAdded(cb) { + reactionsAddedHandlers.push(cb); + return handler; + }, + onReactionsRemoved(cb) { + reactionsRemovedHandlers.push(cb); + return handler; + }, + async run(context: unknown) { + const ctx = context as { activity?: { type?: string } }; + const activityType = ctx?.activity?.type; + const noop = async () => {}; + + if (activityType === "message") { + for (const h of messageHandlers) { + await h(context, noop); + } + } else if (activityType === "conversationUpdate") { + for (const h of membersAddedHandlers) { + await h(context, noop); + } + } else if (activityType === "messageReaction") { + const activity = ( + ctx as { activity?: { reactionsAdded?: unknown[]; reactionsRemoved?: unknown[] } } + )?.activity; + if (activity?.reactionsAdded?.length) { + for (const h of reactionsAddedHandlers) { + await h(context, noop); + } + } + if (activity?.reactionsRemoved?.length) { + for (const h of reactionsRemovedHandlers) { + await h(context, noop); + } + } + } + }, + }; + + return handler; +} diff --git a/extensions/msteams/src/probe.test.ts b/extensions/msteams/src/probe.test.ts index 26914abf9de..68c8b950a24 100644 --- a/extensions/msteams/src/probe.test.ts +++ b/extensions/msteams/src/probe.test.ts @@ -5,18 +5,27 @@ const hostMockState = vi.hoisted(() => ({ tokenError: null as Error | null, })); -vi.mock("@microsoft/agents-hosting", () => ({ - getAuthConfigWithDefaults: (cfg: unknown) => cfg, - MsalTokenProvider: class { - async getAccessToken() { +vi.mock("@microsoft/teams.apps", () => ({ + App: class { + protected async getBotToken() { if (hostMockState.tokenError) { throw hostMockState.tokenError; } - return "token"; + return { value: "token" }; + } + protected async getAppGraphToken() { + if (hostMockState.tokenError) { + throw hostMockState.tokenError; + } + return { value: "token" }; } }, })); +vi.mock("@microsoft/teams.api", () => ({ + Client: class {}, +})); + import { probeMSTeams } from "./probe.js"; describe("msteams probe", () => { diff --git a/extensions/msteams/src/probe.ts b/extensions/msteams/src/probe.ts index ce5f244c3dd..960b62f1595 100644 --- a/extensions/msteams/src/probe.ts +++ b/extensions/msteams/src/probe.ts @@ -4,7 +4,7 @@ import { type MSTeamsConfig, } from "../runtime-api.js"; import { formatUnknownError } from "./errors.js"; -import { loadMSTeamsSdkWithAuth } from "./sdk.js"; +import { createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; import { readAccessToken } from "./token-response.js"; import { resolveMSTeamsCredentials } from "./token.js"; @@ -64,9 +64,13 @@ export async function probeMSTeams(cfg?: MSTeamsConfig): Promise { - await withRevokedProxyFallback({ - run: async () => { - await params.context.sendActivity({ type: "typing" }); - }, - onRevoked: async () => { - const baseRef = buildConversationReference(params.conversationRef); - await params.adapter.continueConversation( - params.appId, - { ...baseRef, activityId: undefined }, - async (ctx) => { - await ctx.sendActivity({ type: "typing" }); - }, - ); - }, - onRevokedLog: () => { - params.log.debug?.("turn context revoked, sending typing via proactive messaging"); - }, - }); - }; + const sendTypingIndicator = + isPersonal || isGroupChat + ? async () => { + await withRevokedProxyFallback({ + run: async () => { + await params.context.sendActivity({ type: "typing" }); + }, + onRevoked: async () => { + const baseRef = buildConversationReference(params.conversationRef); + await params.adapter.continueConversation( + params.appId, + { ...baseRef, activityId: undefined }, + async (ctx) => { + await ctx.sendActivity({ type: "typing" }); + }, + ); + }, + onRevokedLog: () => { + params.log.debug?.("turn context revoked, sending typing via proactive messaging"); + }, + }); + } + : async () => { + // No-op for channels (not supported) + }; const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelReplyPipeline({ cfg: params.cfg, @@ -99,12 +113,26 @@ export function createMSTeamsReplyDispatcher(params: { cfg: params.cfg, resolveChannelLimitMb: ({ cfg }) => cfg.channels?.msteams?.mediaMaxMb, }); + const feedbackLoopEnabled = params.cfg.channels?.msteams?.feedbackEnabled !== false; + + // Streaming for personal (1:1) chats using the Teams streaminfo protocol. + let stream: TeamsHttpStream | undefined; + // Track whether onPartialReply was ever called — if so, the stream + // owns the text delivery and deliver should skip text payloads. + let streamReceivedTokens = false; + + if (isPersonal) { + stream = new TeamsHttpStream({ + sendActivity: (activity) => params.context.sendActivity(activity), + feedbackLoopEnabled, + onError: (err) => { + params.log.debug?.(`stream error: ${err instanceof Error ? err.message : String(err)}`); + }, + }); + } // Accumulate rendered messages from all deliver() calls so the entire turn's - // reply is sent in a single sendMSTeamsMessages() call. This avoids Teams - // silently dropping blocks 2+ when each deliver() opened its own independent - // continueConversation() call — only the first proactive send per turn context - // window succeeds. (#29379) + // reply is sent in a single sendMSTeamsMessages() call. (#29379) const pendingMessages: MSTeamsRenderedMessage[] = []; const sendMessages = async (messages: MSTeamsRenderedMessage[]): Promise => { @@ -115,7 +143,6 @@ export function createMSTeamsReplyDispatcher(params: { conversationRef: params.conversationRef, context: params.context, messages, - // Enable default retry/backoff for throttling/transient failures. retry: {}, onRetry: (event) => { params.log.debug?.("retrying send", { @@ -126,6 +153,7 @@ export function createMSTeamsReplyDispatcher(params: { tokenProvider: params.tokenProvider, sharePointSiteId: params.sharePointSiteId, mediaMaxBytes, + feedbackLoopEnabled, }); }; @@ -133,16 +161,12 @@ export function createMSTeamsReplyDispatcher(params: { if (pendingMessages.length === 0) { return; } - // Copy the buffer before draining so we have a reference for per-message - // retry if the batch send fails. const toSend = pendingMessages.splice(0); const total = toSend.length; let ids: string[]; try { ids = await sendMessages(toSend); } catch { - // Batch send failed (e.g. bad attachment on one message); retry each - // message individually so trailing blocks are not silently lost. ids = []; let failed = 0; for (const msg of toSend) { @@ -175,6 +199,18 @@ export function createMSTeamsReplyDispatcher(params: { humanDelay: core.channel.reply.resolveHumanDelayConfig(params.cfg, params.agentId), typingCallbacks, deliver: async (payload) => { + // When streaming received tokens AND hasn't failed, skip text delivery — + // finalize() handles the final message. If streaming failed (>4000 chars), + // fall through so deliver sends the complete response. + // For payloads with media, strip the text and send media only. + if (stream && streamReceivedTokens && stream.hasContent) { + const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length); + if (!hasMedia) { + return; + } + payload = { ...payload, text: undefined }; + } + // Render the payload to messages and accumulate them. All messages from // this turn are flushed together in markDispatchIdle() so they go out // in a single continueConversation() call. @@ -203,8 +239,7 @@ export function createMSTeamsReplyDispatcher(params: { }, }); - // Wrap markDispatchIdle to flush all accumulated messages before signalling idle. - // Returns a promise so callers (e.g. onSettled) can await completion. + // Wrap markDispatchIdle to flush accumulated messages and finalize stream. const markDispatchIdle = (): Promise => { return flushPendingMessages() .catch((err) => { @@ -218,14 +253,36 @@ export function createMSTeamsReplyDispatcher(params: { hint, }); }) + .then(() => { + if (stream) { + return stream.finalize().catch((err) => { + params.log.debug?.("stream finalize failed", { error: String(err) }); + }); + } + }) .finally(() => { baseMarkDispatchIdle(); }); }; + // Build reply options with onPartialReply for streaming. + // Send the informative update on the first token (not eagerly at stream creation) + // so it only appears when the LLM is actually generating text — not when the + // agent uses a tool (e.g. sends an adaptive card) without streaming. + const streamingReplyOptions = stream + ? { + onPartialReply: (payload: { text?: string }) => { + if (payload.text) { + streamReceivedTokens = true; + stream!.update(payload.text); + } + }, + } + : {}; + return { dispatcher, - replyOptions: { ...replyOptions, onModelSelected }, + replyOptions: { ...replyOptions, ...streamingReplyOptions, onModelSelected }, markDispatchIdle, }; } diff --git a/extensions/msteams/src/sdk-types.ts b/extensions/msteams/src/sdk-types.ts index 3a7e00a7b05..22654b6fdaa 100644 --- a/extensions/msteams/src/sdk-types.ts +++ b/extensions/msteams/src/sdk-types.ts @@ -1,5 +1,3 @@ -import type { TurnContext } from "@microsoft/agents-hosting"; - /** * Minimal public surface we depend on from the Microsoft SDK types. * @@ -8,7 +6,47 @@ import type { TurnContext } from "@microsoft/agents-hosting"; * stricter than what the runtime accepts (e.g. it allows plain activity-like * objects), so we model the minimal structural shape we rely on. */ -export type MSTeamsActivity = TurnContext["activity"]; + +export type MSTeamsActivity = { + type: string; + id?: string; + timestamp?: string; + localTimestamp?: string; + channelId?: string; + from?: { id?: string; name?: string; aadObjectId?: string; role?: string }; + conversation?: { + id?: string; + conversationType?: string; + tenantId?: string; + name?: string; + isGroup?: boolean; + }; + recipient?: { id?: string; name?: string }; + text?: string; + textFormat?: string; + locale?: string; + serviceUrl?: string; + channelData?: { + team?: { id?: string; name?: string }; + channel?: { id?: string; name?: string }; + tenant?: { id?: string }; + [key: string]: unknown; + }; + attachments?: Array<{ + contentType?: string; + contentUrl?: string; + content?: unknown; + name?: string; + thumbnailUrl?: string; + }>; + entities?: Array>; + value?: unknown; + name?: string; + membersAdded?: Array<{ id?: string; name?: string }>; + membersRemoved?: Array<{ id?: string; name?: string }>; + replyToId?: string; + [key: string]: unknown; +}; export type MSTeamsTurnContext = { activity: MSTeamsActivity; diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index ce0b2583060..996a08233a3 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -1,33 +1,377 @@ import type { MSTeamsAdapter } from "./messenger.js"; import type { MSTeamsCredentials } from "./token.js"; +import { buildUserAgent } from "./user-agent.js"; -export type MSTeamsSdk = typeof import("@microsoft/agents-hosting"); -export type MSTeamsAuthConfig = ReturnType; +/** + * Resolved Teams SDK modules loaded lazily to avoid importing when the + * provider is disabled. + */ +export type MSTeamsTeamsSdk = { + App: typeof import("@microsoft/teams.apps").App; + Client: typeof import("@microsoft/teams.api").Client; +}; -export async function loadMSTeamsSdk(): Promise { - return await import("@microsoft/agents-hosting"); +/** + * A Teams SDK App instance used for token management and proactive messaging. + */ +export type MSTeamsApp = InstanceType; + +/** + * Token provider compatible with the existing codebase, wrapping the Teams + * SDK App's token methods. + */ +export type MSTeamsTokenProvider = { + getAccessToken: (scope: string) => Promise; +}; + +export async function loadMSTeamsSdk(): Promise { + const [appsModule, apiModule] = await Promise.all([ + import("@microsoft/teams.apps"), + import("@microsoft/teams.api"), + ]); + return { + App: appsModule.App, + Client: apiModule.Client, + }; } -export function buildMSTeamsAuthConfig( - creds: MSTeamsCredentials, - sdk: MSTeamsSdk, -): MSTeamsAuthConfig { - return sdk.getAuthConfigWithDefaults({ +/** + * Create a Teams SDK App instance from credentials. The App manages token + * acquisition, JWT validation, and the HTTP server lifecycle. + * + * This replaces the previous CloudAdapter + MsalTokenProvider + authorizeJWT + * from @microsoft/agents-hosting. + */ +export function createMSTeamsApp(creds: MSTeamsCredentials, sdk: MSTeamsTeamsSdk): MSTeamsApp { + return new sdk.App({ clientId: creds.appId, clientSecret: creds.appPassword, tenantId: creds.tenantId, }); } -export function createMSTeamsAdapter( - authConfig: MSTeamsAuthConfig, - sdk: MSTeamsSdk, -): MSTeamsAdapter { - return new sdk.CloudAdapter(authConfig) as unknown as MSTeamsAdapter; +/** + * Build a token provider that uses the Teams SDK App for token acquisition. + */ +export function createMSTeamsTokenProvider(app: MSTeamsApp): MSTeamsTokenProvider { + return { + async getAccessToken(scope: string): Promise { + if (scope.includes("graph.microsoft.com")) { + const token = await ( + app as unknown as { getAppGraphToken(): Promise<{ toString(): string } | null> } + ).getAppGraphToken(); + return token ? String(token) : ""; + } + const token = await ( + app as unknown as { getBotToken(): Promise<{ toString(): string } | null> } + ).getBotToken(); + return token ? String(token) : ""; + }, + }; +} + +/** + * Update an existing activity via the Bot Framework REST API. + * PUT /v3/conversations/{conversationId}/activities/{activityId} + */ +async function updateActivityViaRest(params: { + serviceUrl: string; + conversationId: string; + activityId: string; + activity: Record; + token?: string; +}): Promise { + const { serviceUrl, conversationId, activityId, activity, token } = params; + const baseUrl = serviceUrl.replace(/\/+$/, ""); + const url = `${baseUrl}/v3/conversations/${encodeURIComponent(conversationId)}/activities/${encodeURIComponent(activityId)}`; + + const headers: Record = { + "Content-Type": "application/json", + "User-Agent": buildUserAgent(), + }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + const response = await fetch(url, { + method: "PUT", + headers, + body: JSON.stringify({ + type: "message", + ...activity, + id: activityId, + }), + }); + + if (!response.ok) { + const body = await response.text().catch(() => ""); + throw Object.assign(new Error(`updateActivity failed: HTTP ${response.status} ${body}`), { + statusCode: response.status, + }); + } + + return await response.json().catch(() => ({ id: activityId })); +} + +/** + * Build a CloudAdapter-compatible adapter using the Teams SDK REST client. + * + * This replaces the previous CloudAdapter from @microsoft/agents-hosting. + * For incoming requests: the App's HttpPlugin handles JWT validation. + * For proactive sends: uses the Bot Framework REST API via + * @microsoft/teams.api Client. + */ +export function createMSTeamsAdapter(app: MSTeamsApp, sdk: MSTeamsTeamsSdk): MSTeamsAdapter { + return { + async continueConversation(_appId, reference, logic) { + const serviceUrl = reference.serviceUrl; + if (!serviceUrl) { + throw new Error("Missing serviceUrl in conversation reference"); + } + + const conversationId = reference.conversation?.id; + if (!conversationId) { + throw new Error("Missing conversation.id in conversation reference"); + } + + // Fetch a fresh token for each call via a token factory. + // The SDK's App manages token caching/refresh internally. + const getToken = async () => { + const token = await ( + app as unknown as { getBotToken(): Promise<{ toString(): string } | null> } + ).getBotToken(); + return token ? String(token) : undefined; + }; + + // Build a send context that uses the Bot Framework REST API. + // Pass a token factory (not a cached value) so each request gets a fresh token. + const apiClient = new sdk.Client(serviceUrl, { + token: async () => (await getToken()) || undefined, + headers: { "User-Agent": buildUserAgent() }, + } as Record); + + const sendContext = { + async sendActivity(textOrActivity: string | object): Promise { + const activity = + typeof textOrActivity === "string" + ? ({ type: "message", text: textOrActivity } as Record) + : (textOrActivity as Record); + + const response = await apiClient.conversations.activities(conversationId).create({ + type: "message", + ...activity, + from: reference.agent + ? { id: reference.agent.id, name: reference.agent.name ?? "", role: "bot" } + : undefined, + conversation: { + id: conversationId, + conversationType: reference.conversation?.conversationType ?? "personal", + }, + } as Parameters< + typeof apiClient.conversations.activities extends (id: string) => { + create: (a: infer T) => unknown; + } + ? never + : never + >[0]); + + return response; + }, + async updateActivity( + activityUpdate: { id: string } & Record, + ): Promise { + const activityId = activityUpdate.id; + if (!activityId) { + throw new Error("updateActivity requires an activity id"); + } + // Bot Framework REST API: PUT /v3/conversations/{conversationId}/activities/{activityId} + return await updateActivityViaRest({ + serviceUrl, + conversationId, + activityId, + activity: activityUpdate, + token: await getToken(), + }); + }, + }; + + await logic(sendContext); + }, + + async process(req, res, logic) { + const request = req as { body?: Record }; + const response = res as { + status: (code: number) => { send: (body?: unknown) => void }; + }; + + const activity = request.body; + const isInvoke = (activity as Record)?.type === "invoke"; + + try { + const serviceUrl = activity?.serviceUrl as string | undefined; + + // Token factory — fetches a fresh token for each API call. + const getToken = async () => { + const token = await ( + app as unknown as { getBotToken(): Promise<{ toString(): string } | null> } + ).getBotToken(); + return token ? String(token) : undefined; + }; + + const context = { + activity, + async sendActivity(textOrActivity: string | object): Promise { + const msg = + typeof textOrActivity === "string" + ? ({ type: "message", text: textOrActivity } as Record) + : (textOrActivity as Record); + + // invokeResponse is handled by the HTTP response from process(), + // not by posting a new activity to Bot Framework. + if (msg.type === "invokeResponse") { + return { id: "invokeResponse" }; + } + + if (!serviceUrl) { + return { id: "unknown" }; + } + + const convId = (activity?.conversation as Record)?.id as + | string + | undefined; + if (!convId) { + return { id: "unknown" }; + } + + const apiClient = new sdk.Client(serviceUrl, { + token: async () => (await getToken()) || undefined, + headers: { "User-Agent": buildUserAgent() }, + } as Record); + + const botId = (activity?.recipient as Record)?.id as + | string + | undefined; + const botName = (activity?.recipient as Record)?.name as + | string + | undefined; + const convType = (activity?.conversation as Record) + ?.conversationType as string | undefined; + + // Preserve replyToId for threaded replies (replyStyle: "thread") + const inboundActivityId = (activity as Record)?.id as + | string + | undefined; + + return await apiClient.conversations.activities(convId).create({ + type: "message", + ...msg, + from: botId ? { id: botId, name: botName ?? "", role: "bot" } : undefined, + conversation: { id: convId, conversationType: convType ?? "personal" }, + ...(inboundActivityId && !msg.replyToId ? { replyToId: inboundActivityId } : {}), + } as Parameters< + typeof apiClient.conversations.activities extends (id: string) => { + create: (a: infer T) => unknown; + } + ? never + : never + >[0]); + }, + async sendActivities( + activities: Array<{ type: string } & Record>, + ): Promise { + const results = []; + for (const act of activities) { + results.push(await context.sendActivity(act)); + } + return results; + }, + async updateActivity( + activityUpdate: { id: string } & Record, + ): Promise { + const activityId = activityUpdate.id; + if (!activityId || !serviceUrl) { + return { id: "unknown" }; + } + const convId = (activity?.conversation as Record)?.id as + | string + | undefined; + if (!convId) { + return { id: "unknown" }; + } + return await updateActivityViaRest({ + serviceUrl, + conversationId: convId, + activityId, + activity: activityUpdate, + token: await getToken(), + }); + }, + }; + + // For invoke activities, send HTTP 200 immediately before running + // handler logic so slow operations (file uploads, reflections) don't + // hit Teams invoke timeouts ("unable to reach app"). + if (isInvoke) { + response.status(200).send(); + } + + await logic(context); + + if (!isInvoke) { + response.status(200).send(); + } + } catch (err) { + if (!isInvoke) { + response.status(500).send({ error: String(err) }); + } + } + }, + + async updateActivity(_context, activity) { + // No-op: updateActivity is handled via REST in streaming-message.ts + }, + + async deleteActivity(_context, _reference) { + // No-op: deleteActivity not yet implemented for Teams SDK adapter + }, + }; } export async function loadMSTeamsSdkWithAuth(creds: MSTeamsCredentials) { const sdk = await loadMSTeamsSdk(); - const authConfig = buildMSTeamsAuthConfig(creds, sdk); - return { sdk, authConfig }; + const app = createMSTeamsApp(creds, sdk); + return { sdk, app }; +} + +/** + * Create a Bot Framework JWT validator using the Teams SDK's built-in + * JwtValidator pre-configured for Bot Framework signing keys. + * + * Validates: signature (JWKS), audience (appId), issuer (api.botframework.com), + * and expiration (5-minute clock tolerance). + */ +export async function createBotFrameworkJwtValidator(creds: MSTeamsCredentials): Promise<{ + validate: (authHeader: string, serviceUrl?: string) => Promise; +}> { + const { createServiceTokenValidator } = + await import("@microsoft/teams.apps/dist/middleware/auth/jwt-validator.js"); + const validator = createServiceTokenValidator(creds.appId, creds.tenantId); + + return { + async validate(authHeader: string, serviceUrl?: string): Promise { + const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader; + if (!token) { + return false; + } + try { + const result = await validator.validateAccessToken( + token, + serviceUrl ? { validateServiceUrl: { expectedServiceUrl: serviceUrl } } : undefined, + ); + return result != null; + } catch { + return false; + } + }, + }; } diff --git a/extensions/msteams/src/send-context.ts b/extensions/msteams/src/send-context.ts index 2dd3102ed24..a6c92f7b0c8 100644 --- a/extensions/msteams/src/send-context.ts +++ b/extensions/msteams/src/send-context.ts @@ -12,7 +12,7 @@ import type { import { resolveGraphChatId } from "./graph-upload.js"; import type { MSTeamsAdapter } from "./messenger.js"; import { getMSTeamsRuntime } from "./runtime.js"; -import { createMSTeamsAdapter, loadMSTeamsSdkWithAuth } from "./sdk.js"; +import { createMSTeamsAdapter, createMSTeamsTokenProvider, loadMSTeamsSdkWithAuth } from "./sdk.js"; import { resolveMSTeamsCredentials } from "./token.js"; export type MSTeamsConversationType = "personal" | "groupChat" | "channel"; @@ -131,11 +131,11 @@ export async function resolveMSTeamsSendContext(params: { const core = getMSTeamsRuntime(); const log = core.logging.getChildLogger({ name: "msteams:send" }); - const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds); - const adapter = createMSTeamsAdapter(authConfig, sdk); + const { sdk, app } = await loadMSTeamsSdkWithAuth(creds); + const adapter = createMSTeamsAdapter(app, sdk); - // Create token provider for Graph API / OneDrive operations - const tokenProvider = new sdk.MsalTokenProvider(authConfig) as MSTeamsAccessTokenProvider; + // Create token provider adapter for Graph API / OneDrive operations + const tokenProvider: MSTeamsAccessTokenProvider = createMSTeamsTokenProvider(app); // Determine conversation type from stored reference const storedConversationType = ref.conversation?.conversationType?.toLowerCase() ?? ""; diff --git a/extensions/msteams/src/streaming-message.test.ts b/extensions/msteams/src/streaming-message.test.ts new file mode 100644 index 00000000000..0d17d7c764e --- /dev/null +++ b/extensions/msteams/src/streaming-message.test.ts @@ -0,0 +1,206 @@ +import { describe, expect, it, vi } from "vitest"; +import { TeamsHttpStream } from "./streaming-message.js"; + +describe("TeamsHttpStream", () => { + it("sends first chunk as typing activity with streaminfo", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "stream-1" }; + }), + }); + + // Enough text to pass MIN_INITIAL_CHARS threshold + stream.update("Hello, this is a test response that is long enough."); + + // Wait for throttle to flush + await new Promise((r) => setTimeout(r, 700)); + + expect(sent.length).toBeGreaterThanOrEqual(1); + const firstActivity = sent[0] as Record; + expect(firstActivity.type).toBe("typing"); + expect(typeof firstActivity.text).toBe("string"); + expect(firstActivity.text as string).toContain("Hello"); + // Should have streaminfo entity + const entities = firstActivity.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "streaminfo", streamType: "streaming" }), + ]), + ); + }); + + it("sends final message activity on finalize", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "stream-1" }; + }), + }); + + stream.update("Hello, this is a complete response for finalization testing."); + await new Promise((r) => setTimeout(r, 700)); + + await stream.finalize(); + + // Find the final message activity + const finalActivity = sent.find((a) => (a as Record).type === "message") as + | Record + | undefined; + + expect(finalActivity).toBeDefined(); + expect(finalActivity!.text).toBe( + "Hello, this is a complete response for finalization testing.", + ); + // No cursor in final + expect(finalActivity!.text as string).not.toContain("\u258D"); + + // Should have AI-generated entity + const entities = finalActivity!.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([expect.objectContaining({ additionalType: ["AIGeneratedContent"] })]), + ); + + // Should have streaminfo with final type + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "streaminfo", streamType: "final" }), + ]), + ); + }); + + it("does not send below MIN_INITIAL_CHARS", async () => { + const sendActivity = vi.fn(async () => ({ id: "x" })); + const stream = new TeamsHttpStream({ sendActivity }); + + stream.update("Hi"); + await new Promise((r) => setTimeout(r, 700)); + + expect(sendActivity).not.toHaveBeenCalled(); + }); + + it("finalize with no content does nothing", async () => { + const sendActivity = vi.fn(async () => ({ id: "x" })); + const stream = new TeamsHttpStream({ sendActivity }); + + await stream.finalize(); + expect(sendActivity).not.toHaveBeenCalled(); + }); + + it("finalize sends content even if no chunks were streamed", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "msg-1" }; + }), + }); + + // Short text — below MIN_INITIAL_CHARS, so no streaming chunk sent + stream.update("Short"); + await stream.finalize(); + + // Should send final message even though no chunks were streamed + expect(sent.length).toBe(1); + const activity = sent[0] as Record; + expect(activity.type).toBe("message"); + expect(activity.text).toBe("Short"); + }); + + it("sets feedbackLoopEnabled on final message", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "stream-1" }; + }), + feedbackLoopEnabled: true, + }); + + stream.update("A response long enough to pass the minimum character threshold for streaming."); + await new Promise((r) => setTimeout(r, 700)); + await stream.finalize(); + + const finalActivity = sent.find( + (a) => (a as Record).type === "message", + ) as Record; + + const channelData = finalActivity.channelData as Record; + expect(channelData.feedbackLoopEnabled).toBe(true); + }); + + it("sends informative update with streamType informative", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "stream-1" }; + }), + }); + + await stream.sendInformativeUpdate("Thinking..."); + + expect(sent.length).toBe(1); + const activity = sent[0] as Record; + expect(activity.type).toBe("typing"); + expect(activity.text).toBe("Thinking..."); + const entities = activity.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "streaminfo", + streamType: "informative", + streamSequence: 1, + }), + ]), + ); + }); + + it("informative update establishes streamId for subsequent chunks", async () => { + const sent: unknown[] = []; + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async (activity) => { + sent.push(activity); + return { id: "stream-1" }; + }), + }); + + await stream.sendInformativeUpdate("Working..."); + stream.update("Hello, this is a long enough response for streaming to begin."); + await new Promise((r) => setTimeout(r, 1600)); + + // Second activity (streaming chunk) should have the streamId from the informative update + expect(sent.length).toBeGreaterThanOrEqual(2); + const chunk = sent[1] as Record; + const entities = chunk.entities as Array>; + expect(entities).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "streaminfo", streamId: "stream-1" }), + ]), + ); + }); + + it("hasContent is true after update", () => { + const stream = new TeamsHttpStream({ + sendActivity: vi.fn(async () => ({ id: "x" })), + }); + + expect(stream.hasContent).toBe(false); + stream.update("some text"); + expect(stream.hasContent).toBe(true); + }); + + it("double finalize is a no-op", async () => { + const sendActivity = vi.fn(async () => ({ id: "x" })); + const stream = new TeamsHttpStream({ sendActivity }); + + stream.update("A response long enough to pass the minimum character threshold."); + await stream.finalize(); + const callCount = sendActivity.mock.calls.length; + + await stream.finalize(); + expect(sendActivity.mock.calls.length).toBe(callCount); + }); +}); diff --git a/extensions/msteams/src/streaming-message.ts b/extensions/msteams/src/streaming-message.ts new file mode 100644 index 00000000000..61b5e180cf9 --- /dev/null +++ b/extensions/msteams/src/streaming-message.ts @@ -0,0 +1,267 @@ +/** + * Teams streaming message using the streaminfo entity protocol. + * + * Follows the official Teams SDK pattern: + * 1. First chunk → POST a typing activity with streaminfo entity (streamType: "streaming") + * 2. Subsequent chunks → POST typing activities with streaminfo + incrementing streamSequence + * 3. Finalize → POST a message activity with streaminfo (streamType: "final") + * + * Uses the shared draft-stream-loop for throttling (avoids rate limits). + */ + +import { createDraftStreamLoop, type DraftStreamLoop } from "openclaw/plugin-sdk/channel-lifecycle"; + +/** Default throttle interval between stream updates (ms). + * Teams docs recommend buffering tokens for 1.5-2s; limit is 1 req/s. */ +const DEFAULT_THROTTLE_MS = 1500; + +/** Minimum chars before sending the first streaming message. */ +const MIN_INITIAL_CHARS = 20; + +/** Teams message text limit. */ +const TEAMS_MAX_CHARS = 4000; + +type StreamSendFn = (activity: Record) => Promise<{ id?: string } | unknown>; + +export type TeamsStreamOptions = { + /** Function to send an activity (POST to Bot Framework). */ + sendActivity: StreamSendFn; + /** Whether to enable feedback loop on the final message. */ + feedbackLoopEnabled?: boolean; + /** Throttle interval in ms. Default: 600. */ + throttleMs?: number; + /** Called on errors during streaming. */ + onError?: (err: unknown) => void; +}; + +import { AI_GENERATED_ENTITY } from "./ai-entity.js"; + +function extractId(response: unknown): string | undefined { + if (response && typeof response === "object" && "id" in response) { + const id = (response as { id?: unknown }).id; + return typeof id === "string" ? id : undefined; + } + return undefined; +} + +function buildStreamInfoEntity( + streamId: string | undefined, + streamType: "informative" | "streaming" | "final", + streamSequence?: number, +): Record { + const entity: Record = { + type: "streaminfo", + streamType, + }; + // streamId is only present after the first chunk (returned by the service) + if (streamId) { + entity.streamId = streamId; + } + // streamSequence must be present for start/continue, but NOT for final + if (streamSequence != null) { + entity.streamSequence = streamSequence; + } + return entity; +} + +export class TeamsHttpStream { + private sendActivity: StreamSendFn; + private feedbackLoopEnabled: boolean; + private onError?: (err: unknown) => void; + + private accumulatedText = ""; + private streamId: string | undefined = undefined; + private sequenceNumber = 0; + private stopped = false; + private finalized = false; + private streamFailed = false; + private lastStreamedText = ""; + private loop: DraftStreamLoop; + + constructor(options: TeamsStreamOptions) { + this.sendActivity = options.sendActivity; + this.feedbackLoopEnabled = options.feedbackLoopEnabled ?? false; + this.onError = options.onError; + + this.loop = createDraftStreamLoop({ + throttleMs: options.throttleMs ?? DEFAULT_THROTTLE_MS, + isStopped: () => this.stopped, + sendOrEditStreamMessage: (text) => this.pushStreamChunk(text), + }); + } + + /** + * Send an informative status update (blue progress bar in Teams). + * Call this immediately when a message is received, before LLM starts generating. + * Establishes the stream so subsequent chunks continue from this stream ID. + */ + async sendInformativeUpdate(text: string): Promise { + if (this.stopped || this.finalized) { + return; + } + + this.sequenceNumber++; + + const activity: Record = { + type: "typing", + text, + entities: [buildStreamInfoEntity(this.streamId, "informative", this.sequenceNumber)], + }; + + try { + const response = await this.sendActivity(activity); + if (!this.streamId) { + this.streamId = extractId(response); + } + } catch (err) { + this.onError?.(err); + } + } + + /** + * Ingest partial text from the LLM token stream. + * Called by onPartialReply — accumulates text and throttles updates. + */ + update(text: string): void { + if (this.stopped || this.finalized) { + return; + } + this.accumulatedText = text; + + // Wait for minimum chars before first send (avoids push notification flicker) + if (!this.streamId && this.accumulatedText.length < MIN_INITIAL_CHARS) { + return; + } + + // Text exceeded Teams limit — finalize immediately with what we have + // so the user isn't left waiting while the LLM keeps generating. + if (this.accumulatedText.length > TEAMS_MAX_CHARS) { + this.streamFailed = true; + void this.finalize(); + return; + } + + // Don't append cursor — Teams requires each chunk to be a prefix of subsequent chunks. + // The cursor character would cause "content should contain previously streamed content" errors. + this.loop.update(this.accumulatedText); + } + + /** + * Finalize the stream — send the final message activity. + */ + async finalize(): Promise { + if (this.finalized) { + return; + } + this.finalized = true; + this.stopped = true; + this.loop.stop(); + await this.loop.waitForInFlight(); + + // If no text was streamed (e.g. agent sent a card via tool instead of + // streaming text), just return. Teams auto-clears the informative progress + // bar after its streaming timeout. Sending an empty final message fails + // with 403. + if (!this.accumulatedText.trim()) { + return; + } + + // If streaming failed (>4000 chars or POST errors), close the stream + // with the last successfully streamed text so Teams removes the "Stop" + // button and replaces the partial chunks. deliver() handles the complete + // response since hasContent returns false when streamFailed is true. + if (this.streamFailed) { + if (this.streamId) { + try { + await this.sendActivity({ + type: "message", + text: this.lastStreamedText || "", + channelData: { feedbackLoopEnabled: this.feedbackLoopEnabled }, + entities: [AI_GENERATED_ENTITY, buildStreamInfoEntity(this.streamId, "final")], + }); + } catch { + // Best effort — stream will auto-close after Teams timeout + } + } + return; + } + + // Send final message activity. + // Per the spec: type=message, streamType=final, NO streamSequence. + try { + const entities: Array> = [AI_GENERATED_ENTITY]; + if (this.streamId) { + entities.push(buildStreamInfoEntity(this.streamId, "final")); + } + + const finalActivity: Record = { + type: "message", + text: this.accumulatedText, + channelData: { + feedbackLoopEnabled: this.feedbackLoopEnabled, + }, + entities, + }; + + await this.sendActivity(finalActivity); + } catch (err) { + this.onError?.(err); + } + } + + /** Whether streaming successfully delivered content (at least one chunk sent, not failed). */ + get hasContent(): boolean { + return this.accumulatedText.length > 0 && !this.streamFailed; + } + + /** Whether the stream has been finalized. */ + get isFinalized(): boolean { + return this.finalized; + } + + /** Whether streaming fell back (not used in this implementation). */ + get isFallback(): boolean { + return false; + } + + /** + * Send a single streaming chunk as a typing activity with streaminfo. + * Per the Teams REST API spec: + * - First chunk: no streamId, streamSequence=1 → returns 201 with { id: streamId } + * - Subsequent chunks: include streamId, increment streamSequence → returns 202 + */ + private async pushStreamChunk(text: string): Promise { + if (this.stopped && !this.finalized) { + return false; + } + + this.sequenceNumber++; + + const activity: Record = { + type: "typing", + text, + entities: [buildStreamInfoEntity(this.streamId, "streaming", this.sequenceNumber)], + }; + + try { + const response = await this.sendActivity(activity); + if (!this.streamId) { + this.streamId = extractId(response); + } + this.lastStreamedText = text; + return true; + } catch (err) { + const axiosData = (err as { response?: { data?: unknown; status?: number } })?.response; + const statusCode = axiosData?.status ?? (err as { statusCode?: number })?.statusCode; + const responseBody = axiosData?.data ? JSON.stringify(axiosData.data).slice(0, 300) : ""; + const msg = err instanceof Error ? err.message : String(err); + this.onError?.( + new Error( + `stream POST failed (HTTP ${statusCode ?? "?"}): ${msg}${responseBody ? ` body=${responseBody}` : ""}`, + ), + ); + this.streamFailed = true; + return false; + } + } +} diff --git a/extensions/msteams/src/user-agent.test.ts b/extensions/msteams/src/user-agent.test.ts new file mode 100644 index 00000000000..2bfb60feebb --- /dev/null +++ b/extensions/msteams/src/user-agent.test.ts @@ -0,0 +1,45 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +// Mock the runtime before importing buildUserAgent +const mockRuntime = { + version: "2026.3.19", +}; + +vi.mock("./runtime.js", () => ({ + getMSTeamsRuntime: vi.fn(() => mockRuntime), +})); + +import { getMSTeamsRuntime } from "./runtime.js"; +import { buildUserAgent, resetUserAgentCache } from "./user-agent.js"; + +describe("buildUserAgent", () => { + beforeEach(() => { + resetUserAgentCache(); + vi.mocked(getMSTeamsRuntime).mockReturnValue(mockRuntime as never); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns teams.ts[apps]/ OpenClaw/ format", () => { + const ua = buildUserAgent(); + expect(ua).toMatch(/^teams\.ts\[apps\]\/.+ OpenClaw\/2026\.3\.19$/); + }); + + it("reflects the runtime version", () => { + vi.mocked(getMSTeamsRuntime).mockReturnValue({ version: "1.2.3" } as never); + const ua = buildUserAgent(); + expect(ua).toMatch(/OpenClaw\/1\.2\.3$/); + }); + + it("returns OpenClaw/unknown when runtime is not initialized", () => { + vi.mocked(getMSTeamsRuntime).mockImplementation(() => { + throw new Error("MSTeams runtime not initialized"); + }); + const ua = buildUserAgent(); + expect(ua).toMatch(/OpenClaw\/unknown$/); + // SDK version should still be present + expect(ua).toMatch(/^teams\.ts\[apps\]\//); + }); +}); diff --git a/extensions/msteams/src/user-agent.ts b/extensions/msteams/src/user-agent.ts new file mode 100644 index 00000000000..05c8ac196b9 --- /dev/null +++ b/extensions/msteams/src/user-agent.ts @@ -0,0 +1,45 @@ +import { createRequire } from "node:module"; +import { getMSTeamsRuntime } from "./runtime.js"; + +let cachedUserAgent: string | undefined; + +function resolveTeamsSdkVersion(): string { + try { + const require = createRequire(import.meta.url); + const pkg = require("@microsoft/teams.apps/package.json") as { version?: string }; + return pkg.version ?? "unknown"; + } catch { + return "unknown"; + } +} + +function resolveOpenClawVersion(): string { + try { + return getMSTeamsRuntime().version; + } catch { + return "unknown"; + } +} + +/** + * Build a combined User-Agent string that preserves the Teams SDK identity + * and appends the OpenClaw version. + * + * Format: "teams.ts[apps]/ OpenClaw/" + * Example: "teams.ts[apps]/2.0.5 OpenClaw/2026.3.22" + * + * This lets the Teams backend track SDK usage while also identifying the + * host application. + */ +/** Reset the cached User-Agent (for testing). */ +export function resetUserAgentCache(): void { + cachedUserAgent = undefined; +} + +export function buildUserAgent(): string { + if (cachedUserAgent) { + return cachedUserAgent; + } + cachedUserAgent = `teams.ts[apps]/${resolveTeamsSdkVersion()} OpenClaw/${resolveOpenClawVersion()}`; + return cachedUserAgent; +} diff --git a/extensions/msteams/src/welcome-card.test.ts b/extensions/msteams/src/welcome-card.test.ts new file mode 100644 index 00000000000..d4e53259269 --- /dev/null +++ b/extensions/msteams/src/welcome-card.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { buildGroupWelcomeText, buildWelcomeCard } from "./welcome-card.js"; + +describe("buildWelcomeCard", () => { + it("builds card with default prompt starters", () => { + const card = buildWelcomeCard(); + expect(card.type).toBe("AdaptiveCard"); + expect(card.version).toBe("1.5"); + + const body = card.body as Array<{ text: string }>; + expect(body[0]?.text).toContain("OpenClaw"); + + const actions = card.actions as Array<{ title: string; data: unknown }>; + expect(actions.length).toBe(3); + expect(actions[0]?.title).toBe("What can you do?"); + }); + + it("uses custom bot name", () => { + const card = buildWelcomeCard({ botName: "TestBot" }); + const body = card.body as Array<{ text: string }>; + expect(body[0]?.text).toContain("TestBot"); + }); + + it("uses custom prompt starters", () => { + const card = buildWelcomeCard({ + promptStarters: ["Do X", "Do Y"], + }); + const actions = card.actions as Array<{ title: string; data: unknown }>; + expect(actions.length).toBe(2); + expect(actions[0]?.title).toBe("Do X"); + expect(actions[1]?.title).toBe("Do Y"); + + // Verify imBack data + const data = actions[0]?.data as { msteams: { type: string; value: string } }; + expect(data.msteams.type).toBe("imBack"); + expect(data.msteams.value).toBe("Do X"); + }); + + it("falls back to defaults when promptStarters is empty", () => { + const card = buildWelcomeCard({ promptStarters: [] }); + const actions = card.actions as Array<{ title: string }>; + expect(actions.length).toBe(3); + }); +}); + +describe("buildGroupWelcomeText", () => { + it("includes bot name", () => { + const text = buildGroupWelcomeText("MyBot"); + expect(text).toContain("MyBot"); + expect(text).toContain("@MyBot"); + }); + + it("defaults to OpenClaw", () => { + const text = buildGroupWelcomeText(); + expect(text).toContain("OpenClaw"); + }); +}); diff --git a/extensions/msteams/src/welcome-card.ts b/extensions/msteams/src/welcome-card.ts new file mode 100644 index 00000000000..07c5cf98cb6 --- /dev/null +++ b/extensions/msteams/src/welcome-card.ts @@ -0,0 +1,57 @@ +/** + * Builds an Adaptive Card for welcoming users when the bot is added to a conversation. + */ + +const DEFAULT_PROMPT_STARTERS = [ + "What can you do?", + "Summarize my last meeting", + "Help me draft an email", +]; + +export type WelcomeCardOptions = { + /** Bot display name. Falls back to "OpenClaw". */ + botName?: string; + /** Custom prompt starters. Falls back to defaults. */ + promptStarters?: string[]; +}; + +/** + * Build a welcome Adaptive Card for 1:1 personal chats. + */ +export function buildWelcomeCard(options?: WelcomeCardOptions): Record { + const botName = options?.botName || "OpenClaw"; + const starters = options?.promptStarters?.length + ? options.promptStarters + : DEFAULT_PROMPT_STARTERS; + + return { + type: "AdaptiveCard", + version: "1.5", + body: [ + { + type: "TextBlock", + text: `Hi! I'm ${botName}.`, + weight: "bolder", + size: "medium", + }, + { + type: "TextBlock", + text: "I can help you with questions, tasks, and more. Here are some things to try:", + wrap: true, + }, + ], + actions: starters.map((label) => ({ + type: "Action.Submit", + title: label, + data: { msteams: { type: "imBack", value: label } }, + })), + }; +} + +/** + * Build a brief welcome message for group chats (when the bot is @mentioned). + */ +export function buildGroupWelcomeText(botName?: string): string { + const name = botName || "OpenClaw"; + return `Hi! I'm ${name}. Mention me with @${name} to get started.`; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ef2de073ab..18eb29c2f23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: 0.6.0 '@napi-rs/canvas': specifier: ^0.1.89 - version: 0.1.95 + version: 0.1.92 '@sinclair/typebox': specifier: 0.34.48 version: 0.34.48 @@ -439,7 +439,7 @@ importers: version: 41.2.0-rc.0 music-metadata: specifier: ^11.11.2 - version: 11.12.3 + version: 11.12.1 zod: specifier: ^4.3.6 version: 4.3.6 @@ -498,15 +498,15 @@ importers: extensions/msteams: dependencies: - '@microsoft/agents-hosting': - specifier: ^1.4.1 - version: 1.4.1 + '@microsoft/teams.api': + specifier: 2.0.5 + version: 2.0.5(@microsoft/teams.cards@2.0.5)(@microsoft/teams.common@2.0.5) + '@microsoft/teams.apps': + specifier: 2.0.5 + version: 2.0.5(@microsoft/teams.api@2.0.5(@microsoft/teams.cards@2.0.5)(@microsoft/teams.common@2.0.5))(@microsoft/teams.common@2.0.5)(@microsoft/teams.graph@2.0.5) express: specifier: ^5.2.1 version: 5.2.1 - uuid: - specifier: ^13.0.0 - version: 13.0.0 devDependencies: openclaw: specifier: workspace:* @@ -805,8 +805,8 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-bedrock-runtime@3.1014.0': - resolution: {integrity: sha512-K0TmX1D6dIh4J2QtqUuEXxbyMmtHD+kwHvUg1JwDXaLXC7zJJlR0p1692YBh/eze9tHbuKqP/VWzUy6XX9IPGw==} + '@aws-sdk/client-bedrock-runtime@3.997.0': + resolution: {integrity: sha512-yEgCc/HvI7dLeXQLCuc4cnbzwE/NbNpKX8NmSSWTy3jnjiMZwrNKdHMBgPoNvaEb0klHhnTyO+JCHVVCPI/eYw==} engines: {node: '>=20.0.0'} '@aws-sdk/client-bedrock@3.1014.0': @@ -857,16 +857,16 @@ packages: resolution: {integrity: sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag==} engines: {node: '>=20.0.0'} - '@aws-sdk/eventstream-handler-node@3.972.11': - resolution: {integrity: sha512-2IrLrOruRr1NhTK0vguBL1gCWv1pu4bf4KaqpsA+/vCJpFEbvXFawn71GvCzk1wyjnDUsemtKypqoKGv4cSGbA==} + '@aws-sdk/eventstream-handler-node@3.972.7': + resolution: {integrity: sha512-p8k2ZWKJVrR3KIcBbI+/+FcWXdwe3LLgGnixsA7w8lDwWjzSVDHFp6uPeSqBt5PQpRxzak9EheJ1xTmOnHGf4g==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.972.8': resolution: {integrity: sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-eventstream@3.972.8': - resolution: {integrity: sha512-r+oP+tbCxgqXVC3pu3MUVePgSY0ILMjA+aEwOosS77m3/DRbtvHrHwqvMcw+cjANMeGzJ+i0ar+n77KXpRA8RQ==} + '@aws-sdk/middleware-eventstream@3.972.4': + resolution: {integrity: sha512-0t+2Dn46cRE9iu5ynUXINBtR0wNHi/Jz3FbrqS5k3dGot2O7Ln1xCqXbJUAtGM5ZAqN77SbnpETAgVWC84DeoA==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-expect-continue@3.972.8': @@ -905,8 +905,8 @@ packages: resolution: {integrity: sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-websocket@3.972.13': - resolution: {integrity: sha512-Gp6EWIqHX5wmsOR5ZxWyyzEU8P0xBdSxkm6VHEwXwBqScKZ7QWRoj6ZmHpr+S44EYb5tuzGya4ottsogSu2W3A==} + '@aws-sdk/middleware-websocket@3.972.8': + resolution: {integrity: sha512-KPUXz8lRw73Rh12/QkELxiryC9Wi9Ah1xNzFe2Vtbz2/81c2ZA0yM8er+u0iCF/SRMMhDQshLcmRNgn/ueA+gA==} engines: {node: '>= 14.0.0'} '@aws-sdk/nested-clients@3.996.13': @@ -929,6 +929,10 @@ packages: resolution: {integrity: sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.997.0': + resolution: {integrity: sha512-UdG36F7lU9aTqGFRieEyuRUJlgEJBqKeKKekC0esH21DbUSKhPR1kZBah214kYasIaWe1hLJLaqUigoTa5hZAQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.6': resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} engines: {node: '>=20.0.0'} @@ -945,8 +949,8 @@ packages: resolution: {integrity: sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-locate-window@3.965.5': - resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + '@aws-sdk/util-locate-window@3.965.4': + resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==} engines: {node: '>=20.0.0'} '@aws-sdk/util-user-agent-browser@3.972.8': @@ -965,29 +969,17 @@ packages: resolution: {integrity: sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA==} engines: {node: '>=20.0.0'} - '@aws/lambda-invoke-store@0.2.4': - resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + '@aws/lambda-invoke-store@0.2.3': + resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} engines: {node: '>=18.0.0'} - '@azure/abort-controller@2.1.2': - resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} - engines: {node: '>=18.0.0'} - - '@azure/core-auth@1.10.1': - resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==} - engines: {node: '>=20.0.0'} - - '@azure/core-util@1.13.1': - resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==} - engines: {node: '>=20.0.0'} - - '@azure/msal-common@16.4.0': - resolution: {integrity: sha512-twXt09PYtj1PffNNIAzQlrBd0DS91cdA6i1gAfzJ6BnPM4xNk5k9q/5xna7jLIjU3Jnp0slKYtucshGM8OGNAw==} + '@azure/msal-common@15.17.0': + resolution: {integrity: sha512-VQ5/gTLFADkwue+FohVuCqlzFPUq4xSrX8jeZe+iwZuY6moliNC8xt86qPVNYdtbQfELDf2Nu6LI+demFPHGgw==} engines: {node: '>=0.8.0'} - '@azure/msal-node@5.1.1': - resolution: {integrity: sha512-71grXU6+5hl+3CL3joOxlj/AW6rmhthuTlG0fRqsTrhPArQBpZuUFzCIlKOGdcafLUa/i1hBdV78ZxJdlvRA+g==} - engines: {node: '>=20'} + '@azure/msal-node@3.8.10': + resolution: {integrity: sha512-0Hz7Kx4hs70KZWep/Rd7aw/qOLUF92wUOhn7ZsOuB5xNR/06NL1E2RAI9+UKH1FtvN8nD6mFjH7UKSjv6vOWvQ==} + engines: {node: '>=16'} '@babel/generator@8.0.0-rc.2': resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} @@ -997,8 +989,8 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0-rc.3': - resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} + '@babel/helper-string-parser@8.0.0-rc.2': + resolution: {integrity: sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==} engines: {node: ^20.19.0 || >=22.12.0} '@babel/helper-validator-identifier@7.28.5': @@ -1019,8 +1011,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - '@babel/runtime@7.29.2': - resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} '@babel/types@7.29.0': @@ -1038,8 +1030,8 @@ packages: '@blazediff/core@1.9.1': resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} - '@borewit/text-codec@0.2.2': - resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} @@ -1048,15 +1040,15 @@ packages: '@buape/carbon@0.0.0-beta-20260317045421': resolution: {integrity: sha512-yM+r5iSxA/iG8CZ2VhK+EkcBQV+y45WLgF7kuczt2Ul1yixjXSCCcM80GppsklfUv7pqM4Dui+7w1WB3f5p7Kg==} - '@cacheable/memory@2.0.8': - resolution: {integrity: sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==} + '@cacheable/memory@2.0.7': + resolution: {integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==} '@cacheable/node-cache@1.7.6': resolution: {integrity: sha512-6Omk2SgNnjtxB5f/E6bTIWIt5xhdpx39fGNRQgU9lojvRxU68v+qY+SXXLsp3ZGukqoPjsK21wZ6XABFr/Ge3A==} engines: {node: '>=18'} - '@cacheable/utils@2.4.0': - resolution: {integrity: sha512-PeMMsqjVq+bF0WBsxFBxr/WozBJiZKY0rUojuaCoIaKnEl3Ju1wfEwS+SV1DU/cSe8fqHIPiYJFif8T3MVt4cQ==} + '@cacheable/utils@2.3.4': + resolution: {integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==} '@clack/core@1.1.0': resolution: {integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==} @@ -1156,17 +1148,14 @@ packages: resolution: {integrity: sha512-3yJ255e4ag3wfZu/DSxeOZK1UtnqNxnspmLaQetGT0pDkThNZoHs+Zg6dgZZ19JEVomXygvfHn9lNpICZuYtEA==} engines: {node: '>=22.12.0'} - '@emnapi/core@1.9.1': - resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emnapi/runtime@1.9.1': - resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} - - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} @@ -1336,8 +1325,8 @@ packages: '@noble/hashes': optional: true - '@google/genai@1.46.0': - resolution: {integrity: sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==} + '@google/genai@1.42.0': + resolution: {integrity: sha512-+3nlMTcrQufbQ8IumGkOphxD5Pd5kKyJOzLcnY0/1IuE8upJk5aLmoexZ2BJhBp1zAjRJMEB4a2CJwKI9e2EYw==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -1385,8 +1374,8 @@ packages: peerDependencies: hono: 4.12.8 - '@huggingface/jinja@0.5.6': - resolution: {integrity: sha512-MyMWyLnjqo+KRJYSH7oWNbsOn5onuIvfXYPcc0WOGxU0eHUV7oAYUoQTl2BMdu7ml+ea/bu11UM+EshbeHwtIA==} + '@huggingface/jinja@0.5.5': + resolution: {integrity: sha512-xRlzazC+QZwr6z4ixEqYHo9fgwhTZ3xNSdljlKfUFGZSdlvt166DljRELFUfFytlYOYvo3vTisA/AFOuOAzFQQ==} engines: {node: '>=18'} '@img/colour@1.0.0': @@ -1883,13 +1872,32 @@ packages: resolution: {integrity: sha512-88+n+dvxLI1cjS10UIlKXVYK7TGWbpAnnaDC9fow7ch/hCvdu3dFhJ3tS3/13N9s9+1QFXB4FFuommj+tHJPhQ==} engines: {node: '>= 18'} - '@microsoft/agents-activity@1.4.1': - resolution: {integrity: sha512-iye9tZrYSIxm7CQ746B+KW+SRrOcjNTDVXzqsqavtqOfjKJoiDI9dc4Y9Yh6HEX9RKsM0L/clbzfaWf0wcUCIQ==} - engines: {node: '>=20.0.0'} + '@microsoft/teams.api@2.0.5': + resolution: {integrity: sha512-GZ70z038pLjycTDTy/r/mQUdRiuwAUGbyLHPszq+hxpo2HutzuIVfmCUOeGrSDhDPZLaw03PaML3SdqE9R1zZw==} + engines: {node: '>=20'} + peerDependencies: + '@microsoft/teams.cards': 2.0.5 + '@microsoft/teams.common': 2.0.5 - '@microsoft/agents-hosting@1.4.1': - resolution: {integrity: sha512-lmZUkPDnQemdSdxPIwBLR4iOxq9FqOyZmOyU5YTllqqnrNk5RTyBe2IVjA+yZ0RELeMycGFtzY/f6mxu9zdEwQ==} - engines: {node: '>=20.0.0'} + '@microsoft/teams.apps@2.0.5': + resolution: {integrity: sha512-z5m8gV6tbrOrbR1mU0RqyRkT4A+ilStlwXrafAfvkALrTW3ge9VwEgvDg0qEhkM2Bnomx1Y9rmqLc87LD2PLOg==} + engines: {node: '>=20'} + peerDependencies: + '@microsoft/teams.api': 2.0.5 + '@microsoft/teams.common': 2.0.5 + '@microsoft/teams.graph': 2.0.5 + + '@microsoft/teams.cards@2.0.5': + resolution: {integrity: sha512-6BI3WKyjrDwJ1OyMo7t6mjfCIwZUeHN+B4p9Sewjk54+9FfTl3bp83OW3zT/rs1YXKhiKPEJ1WrXnNiiw7V9vA==} + engines: {node: '>=20'} + + '@microsoft/teams.common@2.0.5': + resolution: {integrity: sha512-S2F27BGd2YT6CZnuN/NrtWF2CUO9FMB9Y5oe04YYJSFZNpGbv/KkvUWE5FPqi8P1ag+wj2gvQr3HLZK+ApuXtg==} + engines: {node: '>=20'} + + '@microsoft/teams.graph@2.0.5': + resolution: {integrity: sha512-qJhs9eaJPsIf45EaACHrMy5yF8OuwXZLCopbzQx09sXCB89NqoCGTSa2rwtsI0nPmi79MFiioeSQYZAkpBSHDQ==} + engines: {node: '>=20'} '@mistralai/mistralai@1.14.1': resolution: {integrity: sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==} @@ -1908,74 +1916,144 @@ packages: resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==} engines: {node: '>=14.0.0'} - '@napi-rs/canvas-android-arm64@0.1.95': - resolution: {integrity: sha512-SqTh0wsYbetckMXEvHqmR7HKRJujVf1sYv1xdlhkifg6TlCSysz1opa49LlS3+xWuazcQcfRfmhA07HxxxGsAA==} + '@napi-rs/canvas-android-arm64@0.1.92': + resolution: {integrity: sha512-rDOtq53ujfOuevD5taxAuIFALuf1QsQWZe1yS/N4MtT+tNiDBEdjufvQRPWZ11FubL2uwgP8ApYU3YOaNu1ZsQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.95': - resolution: {integrity: sha512-F7jT0Syu+B9DGBUBcMk3qCRIxAWiDXmvEjamwbYfbZl7asI1pmXZUnCOoIu49Wt0RNooToYfRDxU9omD6t5Xuw==} + '@napi-rs/canvas-android-arm64@0.1.97': + resolution: {integrity: sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.92': + resolution: {integrity: sha512-4PT6GRGCr7yMRehp42x0LJb1V0IEy1cDZDDayv7eKbFUIGbPFkV7CRC9Bee5MPkjg1EB4ZPXXUyy3gjQm7mR8Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.95': - resolution: {integrity: sha512-54eb2Ho15RDjYGXO/harjRznBrAvu+j5nQ85Z4Qd6Qg3slR8/Ja+Yvvy9G4yo7rdX6NR9GPkZeSTf2UcKXwaXw==} + '@napi-rs/canvas-darwin-arm64@0.1.97': + resolution: {integrity: sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.92': + resolution: {integrity: sha512-5e/3ZapP7CqPtDcZPtmowCsjoyQwuNMMD7c0GKPtZQ8pgQhLkeq/3fmk0HqNSD1i227FyJN/9pDrhw/UMTkaWA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.95': - resolution: {integrity: sha512-hYaLCSLx5bmbnclzQc3ado3PgZ66blJWzjXp0wJmdwpr/kH+Mwhj6vuytJIomgksyJoCdIqIa4N6aiqBGJtJ5Q==} + '@napi-rs/canvas-darwin-x64@0.1.97': + resolution: {integrity: sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.92': + resolution: {integrity: sha512-j6KaLL9iir68lwpzzY+aBGag1PZp3+gJE2mQ3ar4VJVmyLRVOh+1qsdNK1gfWoAVy5w6U7OEYFrLzN2vOFUSng==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.95': - resolution: {integrity: sha512-J7VipONahKsmScPZsipHVQBqpbZx4favaD8/enWzzlGcjiwycOoymL7f4tNeqdjK0su19bDOUt6mjp9gsPWYlw==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.97': + resolution: {integrity: sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.92': + resolution: {integrity: sha512-s3NlnJMHOSotUYVoTCoC1OcomaChFdKmZg0VsHFeIkeHbwX0uPHP4eCX1irjSfMykyvsGHTQDfBAtGYuqxCxhQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-arm64-musl@0.1.95': - resolution: {integrity: sha512-PXy0UT1J/8MPG8UAkWp6Fd51ZtIZINFzIjGH909JjQrtCuJf3X6nanHYdz1A+Wq9o4aoPAw1YEUpFS1lelsVlg==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.97': + resolution: {integrity: sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.95': - resolution: {integrity: sha512-2IzCkW2RHRdcgF9W5/plHvYFpc6uikyjMb5SxjqmNxfyDFz9/HB89yhi8YQo0SNqrGRI7yBVDec7Pt+uMyRWsg==} + '@napi-rs/canvas-linux-arm64-musl@0.1.92': + resolution: {integrity: sha512-xV0GQnukYq5qY+ebkAwHjnP2OrSGBxS3vSi1zQNQj0bkXU6Ou+Tw7JjCM7pZcQ28MUyEBS1yKfo7rc7ip2IPFQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-arm64-musl@0.1.97': + resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.92': + resolution: {integrity: sha512-+GKvIFbQ74eB/TopEdH6XIXcvOGcuKvCITLGXy7WLJAyNp3Kdn1ncjxg91ihatBaPR+t63QOE99yHuIWn3UQ9w==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@napi-rs/canvas-linux-x64-gnu@0.1.95': - resolution: {integrity: sha512-OV/ol/OtcUr4qDhQg8G7SdViZX8XyQeKpPsVv/j3+7U178FGoU4M+yIocdVo1ih/A8GQ63+LjF4jDoEjaVU8Pw==} + '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': + resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/canvas-linux-x64-gnu@0.1.92': + resolution: {integrity: sha512-tFd6MwbEhZ1g64iVY2asV+dOJC+GT3Yd6UH4G3Hp0/VHQ6qikB+nvXEULskFYZ0+wFqlGPtXjG1Jmv7sJy+3Ww==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-linux-x64-musl@0.1.95': - resolution: {integrity: sha512-Z5KzqBK/XzPz5+SFHKz7yKqClEQ8pOiEDdgk5SlphBLVNb8JFIJkxhtJKSvnJyHh2rjVgiFmvtJzMF0gNwwKyQ==} + '@napi-rs/canvas-linux-x64-gnu@0.1.97': + resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-win32-arm64-msvc@0.1.95': - resolution: {integrity: sha512-aj0YbRpe8qVJ4OzMsK7NfNQePgcf9zkGFzNZ9mSuaxXzhpLHmlF2GivNdCdNOg8WzA/NxV6IU4c5XkXadUMLeA==} + '@napi-rs/canvas-linux-x64-musl@0.1.92': + resolution: {integrity: sha512-uSuqeSveB/ZGd72VfNbHCSXO9sArpZTvznMVsb42nqPP7gBGEH6NJQ0+hmF+w24unEmxBhPYakP/Wiosm16KkA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-linux-x64-musl@0.1.97': + resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-win32-arm64-msvc@0.1.92': + resolution: {integrity: sha512-20SK5AU/OUNz9ZuoAPj5ekWai45EIBDh/XsdrVZ8le/pJVlhjFU3olbumSQUXRFn7lBRS+qwM8kA//uLaDx6iQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/canvas-win32-x64-msvc@0.1.95': - resolution: {integrity: sha512-GA8leTTCfdjuHi8reICTIxU0081PhXvl3lzIniLUjeLACx9GubUiyzkwFb+oyeKLS5IAGZFLKnzAf4wm2epRlA==} + '@napi-rs/canvas-win32-arm64-msvc@0.1.97': + resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@0.1.92': + resolution: {integrity: sha512-KEhyZLzq1MXCNlXybz4k25MJmHFp+uK1SIb8yJB0xfrQjz5aogAMhyseSzewo+XxAq3OAOdyKvfHGNzT3w1RPg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.95': - resolution: {integrity: sha512-lkg23ge+rgyhgUwXmlbkPEhuhHq/hUi/gXKH+4I7vO+lJrbNfEYcQdJLIGjKyXLQzgFiiyDAwh5vAe/tITAE+w==} + '@napi-rs/canvas-win32-x64-msvc@0.1.97': + resolution: {integrity: sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.92': + resolution: {integrity: sha512-q7ZaUCJkEU5BeOdE7fBx1XWRd2T5Ady65nxq4brMf5L4cE1VV/ACq5w9Z5b/IVJs8CwSSIwc30nlthH0gFo4Ig==} + engines: {node: '>= 10'} + + '@napi-rs/canvas@0.1.97': + resolution: {integrity: sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==} engines: {node: '>= 10'} '@napi-rs/wasm-runtime@1.1.1': @@ -2122,8 +2200,8 @@ packages: resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} engines: {node: '>= 20'} '@octokit/graphql@9.0.3': @@ -2166,8 +2244,8 @@ packages: peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-retry@8.1.0': - resolution: {integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==} + '@octokit/plugin-retry@8.0.3': + resolution: {integrity: sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=7' @@ -2182,8 +2260,8 @@ packages: resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} - '@octokit/request@10.0.8': - resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==} + '@octokit/request@10.0.7': + resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} engines: {node: '>= 20'} '@octokit/types@16.0.0': @@ -2959,18 +3037,26 @@ packages: peerDependencies: '@types/express': ^5.0.0 + '@slack/logger@4.0.0': + resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + '@slack/logger@4.0.1': resolution: {integrity: sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==} engines: {node: '>= 18', npm: '>= 8.6.0'} - '@slack/oauth@3.0.5': - resolution: {integrity: sha512-exqFQySKhNDptWYSWhvRUJ4/+ndu2gayIy7vg/JfmJq3wGtGdHk531P96fAZyBm5c1Le3yaPYqv92rL4COlU3A==} + '@slack/oauth@3.0.4': + resolution: {integrity: sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg==} engines: {node: '>=18', npm: '>=8.6.0'} - '@slack/socket-mode@2.0.6': - resolution: {integrity: sha512-Aj5RO3MoYVJ+b2tUjHUXuA3tiIaCUMOf1Ss5tPiz29XYVUi6qNac2A8ulcU1pUPERpXVHTmT1XW6HzQIO74daQ==} + '@slack/socket-mode@2.0.5': + resolution: {integrity: sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ==} engines: {node: '>= 18', npm: '>= 8.6.0'} + '@slack/types@2.20.0': + resolution: {integrity: sha512-PVF6P6nxzDMrzPC8fSCsnwaI+kF8YfEpxf3MqXmdyjyWTYsZQURpkK7WWUWvP5QpH55pB7zyYL9Qem/xSgc5VA==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + '@slack/types@2.20.1': resolution: {integrity: sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==} engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} @@ -3003,22 +3089,42 @@ packages: resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.2.10': + resolution: {integrity: sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.2.12': resolution: {integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.2.10': + resolution: {integrity: sha512-0xupsu9yj9oDVuQ50YCTS9nuSYhGlrwqdaKQel9y2Fz7LU9fNErVlw9N0o4pm4qqvWEGbSTI4HKc6XJfB30MVw==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.2.12': resolution: {integrity: sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-config-resolver@4.3.10': + resolution: {integrity: sha512-8kn6sinrduk0yaYHMJDsNuiFpXwQwibR7n/4CDUqn4UgaG+SeBHu5jHGFdU9BLFAM7Q4/gvr9RYxBHz9/jKrhA==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-config-resolver@4.3.12': resolution: {integrity: sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-node@4.2.10': + resolution: {integrity: sha512-uUrxPGgIffnYfvIOUmBM5i+USdEBRTdh7mLPttjphgtooxQ8CtdO1p6K5+Q4BBAZvKlvtJ9jWyrWpBJYzBKsyQ==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-node@4.2.12': resolution: {integrity: sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-universal@4.2.10': + resolution: {integrity: sha512-aArqzOEvcs2dK+xQVCgLbpJQGfZihw8SD4ymhkwNTtwKbnrzdhJsFDKuMQnam2kF69WzgJYOU5eJlCx+CA32bw==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-universal@4.2.12': resolution: {integrity: sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==} engines: {node: '>=18.0.0'} @@ -3195,91 +3301,91 @@ packages: resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} engines: {node: '>=18.0.0'} - '@snazzah/davey-android-arm-eabi@0.1.10': - resolution: {integrity: sha512-7bwHxSNEI2wVXOT6xnmpnO9SHb2xwAnf9oEdL45dlfVHTgU1Okg5rwGwRvZ2aLVFFbTyecfC8EVZyhpyTkjLSw==} + '@snazzah/davey-android-arm-eabi@0.1.9': + resolution: {integrity: sha512-Dq0WyeVGBw+uQbisV/6PeCQV2ndJozfhZqiNIfQxu6ehIdXB7iHILv+oY+AQN2n+qxiFmLh/MOX9RF+pIWdPbA==} engines: {node: '>= 10'} cpu: [arm] os: [android] - '@snazzah/davey-android-arm64@0.1.10': - resolution: {integrity: sha512-68WUf2LQwQTP9MgPcCqTWwJztJSIk0keGfF2Y/b+MihSDh29fYJl7C0rbz69aUrVCvCC2lYkB/46P8X1kBz7yg==} + '@snazzah/davey-android-arm64@0.1.9': + resolution: {integrity: sha512-OE16OZjv7F/JrD7Mzw5eL2gY2vXRPC8S7ZrmkcMyz/sHHJsGHlT+L7X5s56Bec1YDTVmzAsH4UBuvVBoXuIWEQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@snazzah/davey-darwin-arm64@0.1.10': - resolution: {integrity: sha512-nYC+DWCGUC1jUGEenCNQE/jJpL/02m0ebY/NvTCQbul5ktI/ShVzgA3kzssEhZvhf6jbH048Rs39wDhp/b24Jg==} + '@snazzah/davey-darwin-arm64@0.1.9': + resolution: {integrity: sha512-z7oORvAPExikFkH6tvHhbUdZd77MYZp9VqbCpKEiI+sisWFVXgHde7F7iH3G4Bz6gUYJfgvKhWXiDRc+0SC4dg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@snazzah/davey-darwin-x64@0.1.10': - resolution: {integrity: sha512-0q5Rrcs+O9sSSnPX+A3R3djEQs2nTAtMe5N3lApO6lZas/QNMl6wkEWCvTbDc2cfAYBMSk2jgc1awlRXi4LX3Q==} + '@snazzah/davey-darwin-x64@0.1.9': + resolution: {integrity: sha512-f1LzGyRGlM414KpXml3OgWVSd7CgylcdYaFj/zDBb8bvWjxyvsI9iMeuPfe/cduloxRj8dELde/yCDZtFR6PdQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@snazzah/davey-freebsd-x64@0.1.10': - resolution: {integrity: sha512-/Gq5YDD6Oz8iBqVJLswUnetCv9JCRo1quYX5ujzpAG8zPCNItZo4g4h5p9C+h4Yoay2quWBYhoaVqQKT96bm8g==} + '@snazzah/davey-freebsd-x64@0.1.9': + resolution: {integrity: sha512-k6p3JY2b8rD6j0V9Ql7kBUMR4eJdcpriNwiHltLzmtGuz/nK5RGQdkEP68gTLc+Uj3xs5Cy0jRKmv2xJQBR4sA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@snazzah/davey-linux-arm-gnueabihf@0.1.10': - resolution: {integrity: sha512-0Z7Vrt0WIbgxws9CeHB9qlueYJlvltI44rUuZmysdi70UcHGxlr7nE3MnzYCr9nRWRegohn8EQPWHMKMDJH2GA==} + '@snazzah/davey-linux-arm-gnueabihf@0.1.9': + resolution: {integrity: sha512-xDaAFUC/1+n/YayNwKsqKOBMuW0KI6F0SjgWU+krYTQTVmAKNjOM80IjemrVoqTpBOxBsT80zEtct2wj11CE3Q==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@snazzah/davey-linux-arm64-gnu@0.1.10': - resolution: {integrity: sha512-xhZQycn4QB+qXhqm/QmZ+kb9MHMXcbjjoPfvcIL4WMQXFG/zUWHW8EiBk7ZTEGMOpeab3F9D1+MlgumglYByUQ==} + '@snazzah/davey-linux-arm64-gnu@0.1.9': + resolution: {integrity: sha512-t1VxFBzWExPNpsNY/9oStdAAuHqFvwZvIO2YPYyVNstxfi2KmAbHMweHUW7xb2ppXuhVQZ4VGmmeXiXcXqhPBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@snazzah/davey-linux-arm64-musl@0.1.10': - resolution: {integrity: sha512-pudzQCP9rZItwW4qHHvciMwtNd9kWH4l73g6Id1LRpe6sc8jiFBV7W+YXITj2PZbI0by6XPfkRP6Dk5IkGOuAw==} + '@snazzah/davey-linux-arm64-musl@0.1.9': + resolution: {integrity: sha512-Xvlr+nBPzuFV4PXHufddlt08JsEyu0p8mX2DpqdPxdpysYIH4I8V86yJiS4tk04a6pLBDd8IxTbBwvXJKqd/LQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@snazzah/davey-linux-x64-gnu@0.1.10': - resolution: {integrity: sha512-DC8qRmk+xJEFNqjxKB46cETKeDQqgUqE5p39KXS2k6Vl/XTi8pw8pXOxrPfYte5neoqlWAVQzbxuLnwpyRJVEQ==} + '@snazzah/davey-linux-x64-gnu@0.1.9': + resolution: {integrity: sha512-6Uunc/NxiEkg1reroAKZAGfOtjl1CGa7hfTTVClb2f+DiA8ZRQWBh+3lgkq/0IeL262B4F14X8QRv5Bsv128qw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@snazzah/davey-linux-x64-musl@0.1.10': - resolution: {integrity: sha512-wPR5/2QmsF7sR0WUaCwbk4XI3TLcxK9PVK8mhgcAYyuRpbhcVgNGWXs8ulcyMSXve5pFRJAFAuMTGCEb014peg==} + '@snazzah/davey-linux-x64-musl@0.1.9': + resolution: {integrity: sha512-fFQ/n3aWt1lXhxSdy+Ge3gi5bR3VETMVsWhH0gwBALUKrbo3ZzgSktm4lNrXE9i0ncMz/CDpZ5i0wt/N3XphEQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@snazzah/davey-wasm32-wasi@0.1.10': - resolution: {integrity: sha512-SfQavU+eKTDbRmPeLRodrVSfsWq25PYTmH1nIZW3B27L6IkijzjXZZuxiU1ZG1gdI5fB7mwXrOTtx34t+vAG7Q==} + '@snazzah/davey-wasm32-wasi@0.1.9': + resolution: {integrity: sha512-xWvzej8YCVlUvzlpmqJMIf0XmLlHqulKZ2e7WNe2TxQmsK+o0zTZqiQYs2MwaEbrNXBhYlHDkdpuwoXkJdscNQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@snazzah/davey-win32-arm64-msvc@0.1.10': - resolution: {integrity: sha512-Raafk53smYs67wZCY9bQXHXzbaiRMS5QCdjTdin3D9fF5A06T/0Zv1z7/YnaN+O3GSL/Ou3RvynF7SziToYiFQ==} + '@snazzah/davey-win32-arm64-msvc@0.1.9': + resolution: {integrity: sha512-sTqry/DfltX2OdW1CTLKa3dFYN5FloAEb2yhGsY1i5+Bms6OhwByXfALvyMHYVo61Th2+sD+9BJpQffHFKDA3w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@snazzah/davey-win32-ia32-msvc@0.1.10': - resolution: {integrity: sha512-pAs43l/DiZ+icqBwxIwNePzuYxFM1ZblVuf7t6vwwSLxvova7vnREnU7qDVjbc5/YTUHOsqYy3S6TpZMzDo2lw==} + '@snazzah/davey-win32-ia32-msvc@0.1.9': + resolution: {integrity: sha512-twD3LwlkGnSwphsCtpGb5ztpBIWEvGdc0iujoVkdzZ6nJiq5p8iaLjJMO4hBm9h3s28fc+1Qd7AMVnagiOasnA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@snazzah/davey-win32-x64-msvc@0.1.10': - resolution: {integrity: sha512-kr6148VVBoUT4CtD+5hYshTFRny7R/xQZxXFhFc0fYjtmdMVM8Px9M91olg1JFNxuNzdfMfTufR58Q3wfBocug==} + '@snazzah/davey-win32-x64-msvc@0.1.9': + resolution: {integrity: sha512-eMnXbv4GoTngWYY538i/qHz2BS+RgSXFsvKltPzKqnqzPzhQZIY7TemEJn3D5yWGfW4qHve9u23rz93FQqnQMA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@snazzah/davey@0.1.10': - resolution: {integrity: sha512-J5f7vV5/tnj0xGnqufFRd6qiWn3FcR3iXjpjpEmO2Ok+Io0AASkMaZ3I39TsL45as0Qo5bq9wWuamFQ77PjJ+g==} + '@snazzah/davey@0.1.9': + resolution: {integrity: sha512-vNZk5y+IsxjwzTAXikvzz5pqMLb35YytC64nVF2MAFVhjpXu9ITOKUriZ0JG/llwzCAi56jb5x0cXDRIyE2A2A==} engines: {node: '>= 10'} '@standard-schema/spec@1.1.0': @@ -3291,12 +3397,12 @@ packages: '@telegraf/types@7.1.0': resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==} - '@thi.ng/bitstream@2.4.44': - resolution: {integrity: sha512-SN5GtdycUC8J9kRn4+GAcAJ93F9vKtaiI/SjpOyLl911bWNlF8gANFFCdgBkzbF2DHcpKflHxrVVfrYB6aCdsw==} + '@thi.ng/bitstream@2.4.41': + resolution: {integrity: sha512-treRzw3+7I1YCuilFtznwT3SGtceS9spUXhyBqeuKNTm4nIfMuvg4fNqx4GgpuS6cGPQNPMUJm0OyzKnSe2Emw==} engines: {node: '>=18'} - '@thi.ng/errors@2.6.6': - resolution: {integrity: sha512-da0slIGmueCf4T0b+xT4TMVUQjHGFTJpul5TAVvPUl7Xn5Noe13Uq6Ag4D5ZCcDwwe2iJ8iHaTuYSJaFZ9f8Mg==} + '@thi.ng/errors@2.6.3': + resolution: {integrity: sha512-owkOOKHf7MrAPN2jNpKWDdY/vjtPFiJf6oxZ3jkkhV6ICTu2iY1fXIR2wQ7kVEeybdtb0w24k2PtrU43OYCWdg==} engines: {node: '>=18'} '@tinyhttp/content-disposition@2.2.4': @@ -3363,8 +3469,8 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/aws-lambda@8.10.161': - resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} + '@types/aws-lambda@8.10.160': + resolution: {integrity: sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==} '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -3438,11 +3544,11 @@ packages: '@types/node@16.9.1': resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} - '@types/node@20.19.37': - resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/node@20.19.33': + resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} - '@types/node@24.12.0': - resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + '@types/node@24.10.13': + resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==} '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} @@ -3519,10 +3625,6 @@ packages: resolution: {integrity: sha512-CmzQTKvesYHmz3g92G+XPDis25ocvHqa/gK8m98w+bML99KJLEWQKVlvkLrYA85JiJEK+XBIiz+6lCgUqRkWXA==} hasBin: true - '@typespec/ts-http-runtime@0.3.4': - resolution: {integrity: sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==} - engines: {node: '>=20.0.0'} - '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -3618,6 +3720,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -3717,10 +3823,13 @@ packages: resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} engines: {node: '>=6'} - array-back@6.2.3: - resolution: {integrity: sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==} + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} engines: {node: '>=12.17'} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3761,16 +3870,16 @@ packages: audio-decode@2.2.3: resolution: {integrity: sha512-Z0lHvMayR/Pad9+O9ddzaBJE0DrhZkQlStrC1RwcAHF3AhQAsdwKHeLGK8fYKyp2DDU6xHxzGb4CLMui12yVrg==} - audio-type@2.4.0: - resolution: {integrity: sha512-ugYMgxLpH6gyWUhFWFl2HCJboFL5z/GoqSdonx8ZycfNP8JDHBhRNzYWzrCRa/6htOWfvJAq7qpRloxvx06sRA==} + audio-type@2.2.1: + resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==} engines: {node: '>=14'} await-to-js@3.0.0: resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} engines: {node: '>=6.0.0'} - axios@1.13.6: - resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} b4a@1.8.0: resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} @@ -3858,6 +3967,10 @@ packages: bmp-ts@1.0.9: resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -3871,8 +3984,8 @@ packages: bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - brace-expansion@5.0.4: - resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -3911,8 +4024,8 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} - cacheable@2.3.4: - resolution: {integrity: sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==} + cacheable@2.3.2: + resolution: {integrity: sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -4030,8 +4143,8 @@ packages: resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} engines: {node: '>=4.0.0'} - command-line-usage@7.0.4: - resolution: {integrity: sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==} + command-line-usage@7.0.3: + resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} engines: {node: '>=12.20.0'} commander@10.0.1: @@ -4052,6 +4165,10 @@ packages: constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -4063,6 +4180,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -4118,6 +4238,14 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -4156,6 +4284,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -4350,6 +4482,10 @@ packages: peerDependencies: express: '>= 4.11' + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} @@ -4421,6 +4557,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + finalhandler@2.1.1: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} @@ -4454,12 +4594,16 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - fs-extra@11.3.4: - resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} fs.realpath@1.0.0: @@ -4595,8 +4739,8 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hashery@1.5.1: - resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + hashery@1.5.0: + resolution: {integrity: sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==} engines: {node: '>=20'} hasown@2.0.2: @@ -4622,9 +4766,6 @@ packages: hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} - hookified@2.1.0: - resolution: {integrity: sha512-ootKng4eaxNxa7rx6FJv2YKef3DuhqbEj3l70oGXwddPQEEnISm50TEZQclqiLTAtilT2nu7TErtCO523hHkyg==} - hosted-git-info@9.0.2: resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -4669,6 +4810,10 @@ packages: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -4813,8 +4958,8 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.2.1: - resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==} + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} @@ -4866,9 +5011,6 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-with-bigint@3.5.7: - resolution: {integrity: sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4890,9 +5032,9 @@ packages: jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - jwks-rsa@4.0.1: - resolution: {integrity: sha512-poXwUA8S4cP9P5N8tZS3xnUDJH8WmwSGfKK9gIaRPdjLHyJtd9iX/cngX9CUIe0Caof5JhK2EbN7N5lnnaf9NA==} - engines: {node: ^20.19.0 || ^22.12.0 || >= 23.0.0} + jwks-rsa@3.2.2: + resolution: {integrity: sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==} + engines: {node: '>=14'} jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} @@ -4908,8 +5050,8 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} - koffi@2.15.2: - resolution: {integrity: sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g==} + koffi@2.15.1: + resolution: {integrity: sha512-mnc0C0crx/xMSljb5s9QbnLrlFHprioFO1hkXyuSuO/QtbpLDa0l/uM21944UfQunMKmp3/r789DTDxVyyH6aA==} lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -5071,16 +5213,24 @@ packages: resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==} engines: {node: '>=18'} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lru-memoizer@3.0.0: - resolution: {integrity: sha512-m83w/cYXLdUIboKSPxzPAGfYnk+vqeDYXuoSrQRw1q+yVEd8IXhvMufN8Q5TIPe7e2jyX4SRNrDJI2Skw1yznQ==} + lru-memoizer@2.3.0: + resolution: {integrity: sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==} lru_map@0.4.1: resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} @@ -5139,10 +5289,17 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -5154,6 +5311,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} @@ -5189,6 +5350,11 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -5231,11 +5397,14 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - music-metadata@11.12.3: - resolution: {integrity: sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==} + music-metadata@11.12.1: + resolution: {integrity: sha512-j++ltLxHDb5VCXET9FzQ8bnueiLHwQKgCO7vcbkRH/3F7fRjPkv6qncGEJ47yFhmemcYtgvsOAlcQ1dRBTkDjg==} engines: {node: '>=18'} mz@2.7.0: @@ -5246,11 +5415,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.7: - resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} engines: {node: ^18 || >=20} hasBin: true + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -5259,8 +5432,8 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - node-addon-api@8.6.0: - resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==} + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} engines: {node: ^18 || ^20 || >= 21} node-api-headers@1.8.0: @@ -5344,10 +5517,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-path@0.11.8: - resolution: {integrity: sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==} - engines: {node: '>= 10.12.0'} - obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -5553,6 +5722,9 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -5686,8 +5858,8 @@ packages: pug-attrs@3.0.0: resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} - pug-code-gen@3.0.3: - resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==} + pug-code-gen@3.0.4: + resolution: {integrity: sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==} pug-error@2.1.0: resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} @@ -5716,8 +5888,8 @@ packages: pug-walk@2.0.0: resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} - pug@3.0.3: - resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} + pug@3.0.4: + resolution: {integrity: sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==} pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -5730,8 +5902,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qified@0.9.0: - resolution: {integrity: sha512-4q61YgkHbY6gmwkqm0BsxyLDO3UYdrdiJTJ7JiaZb3xpW1duxn135SB7KqUEkCiuu5O4W+TtwEWP2VjmSRanvA==} + qified@0.6.0: + resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} engines: {node: '>=20'} qoa-format@1.0.1: @@ -5761,6 +5933,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + raw-body@3.0.2: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} @@ -5936,10 +6112,18 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} @@ -6002,8 +6186,8 @@ packages: peerDependencies: signal-polyfill: ^0.2.0 - simple-git@3.33.0: - resolution: {integrity: sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==} + simple-git@3.31.1: + resolution: {integrity: sha512-oiWP4Q9+kO8q9hHqkX35uuHmxiEbZNTrZ5IPxgMGrJwN76pzjm/jabkZO0ItEcqxAincqGAzL3QHSaHt4+knBg==} simple-xml-to-json@1.2.4: resolution: {integrity: sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg==} @@ -6152,8 +6336,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-final-newline@2.0.0: @@ -6164,17 +6348,13 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strnum@2.2.1: - resolution: {integrity: sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==} + strnum@2.2.2: + resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} - strtok3@10.3.5: - resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -6224,6 +6404,10 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyexec@1.0.4: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} @@ -6236,8 +6420,8 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -6328,6 +6512,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -6429,9 +6617,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} uuid@13.0.0: resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} @@ -6649,6 +6837,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -6691,9 +6882,6 @@ packages: peerDependencies: zod: ^3.25 || ^4 - zod@3.25.75: - resolution: {integrity: sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -6763,7 +6951,7 @@ snapshots: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.973.6 - '@aws-sdk/util-locate-window': 3.965.5 + '@aws-sdk/util-locate-window': 3.965.4 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -6773,7 +6961,7 @@ snapshots: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.973.6 - '@aws-sdk/util-locate-window': 3.965.5 + '@aws-sdk/util-locate-window': 3.965.4 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -6793,30 +6981,30 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1014.0': + '@aws-sdk/client-bedrock-runtime@3.997.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/core': 3.973.23 '@aws-sdk/credential-provider-node': 3.972.24 - '@aws-sdk/eventstream-handler-node': 3.972.11 - '@aws-sdk/middleware-eventstream': 3.972.8 + '@aws-sdk/eventstream-handler-node': 3.972.7 + '@aws-sdk/middleware-eventstream': 3.972.4 '@aws-sdk/middleware-host-header': 3.972.8 '@aws-sdk/middleware-logger': 3.972.8 '@aws-sdk/middleware-recursion-detection': 3.972.8 '@aws-sdk/middleware-user-agent': 3.972.24 - '@aws-sdk/middleware-websocket': 3.972.13 + '@aws-sdk/middleware-websocket': 3.972.8 '@aws-sdk/region-config-resolver': 3.972.9 - '@aws-sdk/token-providers': 3.1014.0 + '@aws-sdk/token-providers': 3.997.0 '@aws-sdk/types': 3.973.6 '@aws-sdk/util-endpoints': 3.996.5 '@aws-sdk/util-user-agent-browser': 3.972.8 '@aws-sdk/util-user-agent-node': 3.973.10 '@smithy/config-resolver': 4.4.13 '@smithy/core': 3.23.12 - '@smithy/eventstream-serde-browser': 4.2.12 - '@smithy/eventstream-serde-config-resolver': 4.3.12 - '@smithy/eventstream-serde-node': 4.2.12 + '@smithy/eventstream-serde-browser': 4.2.10 + '@smithy/eventstream-serde-config-resolver': 4.3.10 + '@smithy/eventstream-serde-node': 4.2.10 '@smithy/fetch-http-handler': 5.3.15 '@smithy/hash-node': 4.2.12 '@smithy/invalid-dependency': 4.2.12 @@ -7075,10 +7263,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/eventstream-handler-node@3.972.11': + '@aws-sdk/eventstream-handler-node@3.972.7': dependencies: '@aws-sdk/types': 3.973.6 - '@smithy/eventstream-codec': 4.2.12 + '@smithy/eventstream-codec': 4.2.10 '@smithy/types': 4.13.1 tslib: 2.8.1 @@ -7092,7 +7280,7 @@ snapshots: '@smithy/util-config-provider': 4.2.2 tslib: 2.8.1 - '@aws-sdk/middleware-eventstream@3.972.8': + '@aws-sdk/middleware-eventstream@3.972.4': dependencies: '@aws-sdk/types': 3.973.6 '@smithy/protocol-http': 5.3.12 @@ -7145,7 +7333,7 @@ snapshots: '@aws-sdk/middleware-recursion-detection@3.972.8': dependencies: '@aws-sdk/types': 3.973.6 - '@aws/lambda-invoke-store': 0.2.4 + '@aws/lambda-invoke-store': 0.2.3 '@smithy/protocol-http': 5.3.12 '@smithy/types': 4.13.1 tslib: 2.8.1 @@ -7184,12 +7372,12 @@ snapshots: '@smithy/util-retry': 4.2.12 tslib: 2.8.1 - '@aws-sdk/middleware-websocket@3.972.13': + '@aws-sdk/middleware-websocket@3.972.8': dependencies: '@aws-sdk/types': 3.973.6 '@aws-sdk/util-format-url': 3.972.8 - '@smithy/eventstream-codec': 4.2.12 - '@smithy/eventstream-serde-browser': 4.2.12 + '@smithy/eventstream-codec': 4.2.10 + '@smithy/eventstream-serde-browser': 4.2.10 '@smithy/fetch-http-handler': 5.3.15 '@smithy/protocol-http': 5.3.12 '@smithy/signature-v4': 5.3.12 @@ -7282,6 +7470,18 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/token-providers@3.997.0': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/types@3.973.6': dependencies: '@smithy/types': 4.13.1 @@ -7306,7 +7506,7 @@ snapshots: '@smithy/types': 4.13.1 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.965.5': + '@aws-sdk/util-locate-window@3.965.4': dependencies: tslib: 2.8.1 @@ -7332,33 +7532,13 @@ snapshots: fast-xml-parser: 5.5.7 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.2.4': {} + '@aws/lambda-invoke-store@0.2.3': {} - '@azure/abort-controller@2.1.2': + '@azure/msal-common@15.17.0': {} + + '@azure/msal-node@3.8.10': dependencies: - tslib: 2.8.1 - - '@azure/core-auth@1.10.1': - dependencies: - '@azure/abort-controller': 2.1.2 - '@azure/core-util': 1.13.1 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - - '@azure/core-util@1.13.1': - dependencies: - '@azure/abort-controller': 2.1.2 - '@typespec/ts-http-runtime': 0.3.4 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - - '@azure/msal-common@16.4.0': {} - - '@azure/msal-node@5.1.1': - dependencies: - '@azure/msal-common': 16.4.0 + '@azure/msal-common': 15.17.0 jsonwebtoken: 9.0.3 uuid: 8.3.2 @@ -7373,7 +7553,7 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@8.0.0-rc.3': {} + '@babel/helper-string-parser@8.0.0-rc.2': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -7387,7 +7567,7 @@ snapshots: dependencies: '@babel/types': 8.0.0-rc.2 - '@babel/runtime@7.29.2': {} + '@babel/runtime@7.28.6': {} '@babel/types@7.29.0': dependencies: @@ -7396,14 +7576,14 @@ snapshots: '@babel/types@8.0.0-rc.2': dependencies: - '@babel/helper-string-parser': 8.0.0-rc.3 + '@babel/helper-string-parser': 8.0.0-rc.2 '@babel/helper-validator-identifier': 8.0.0-rc.2 '@bcoe/v8-coverage@1.0.2': {} '@blazediff/core@1.9.1': {} - '@borewit/text-codec@0.2.2': {} + '@borewit/text-codec@0.2.1': {} '@bramus/specificity@2.4.2': dependencies: @@ -7429,22 +7609,22 @@ snapshots: - opusscript - utf-8-validate - '@cacheable/memory@2.0.8': + '@cacheable/memory@2.0.7': dependencies: - '@cacheable/utils': 2.4.0 + '@cacheable/utils': 2.3.4 '@keyv/bigmap': 1.3.1(keyv@5.6.0) hookified: 1.15.1 keyv: 5.6.0 '@cacheable/node-cache@1.7.6': dependencies: - cacheable: 2.3.4 + cacheable: 2.3.2 hookified: 1.15.1 keyv: 5.6.0 - '@cacheable/utils@2.4.0': + '@cacheable/utils@2.3.4': dependencies: - hashery: 1.5.1 + hashery: 1.5.0 keyv: 5.6.0 '@clack/core@1.1.0': @@ -7553,7 +7733,7 @@ snapshots: '@discordjs/opus@0.10.0': dependencies: '@discordjs/node-pre-gyp': 0.4.5 - node-addon-api: 8.6.0 + node-addon-api: 8.5.0 transitivePeerDependencies: - encoding - supports-color @@ -7561,23 +7741,6 @@ snapshots: '@discordjs/voice@0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)': dependencies: - '@types/ws': 8.18.1 - discord-api-types: 0.38.42 - prism-media: 1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1) - tslib: 2.8.1 - ws: 8.19.0 - transitivePeerDependencies: - - '@discordjs/opus' - - bufferutil - - ffmpeg-static - - node-opus - - opusscript - - utf-8-validate - optional: true - - '@discordjs/voice@0.19.2(@discordjs/opus@0.10.0)(opusscript@0.1.1)': - dependencies: - '@snazzah/davey': 0.1.10 '@types/ws': 8.18.1 discord-api-types: 0.38.42 prism-media: 1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1) @@ -7590,10 +7753,27 @@ snapshots: - node-opus - opusscript - utf-8-validate + optional: true - '@emnapi/core@1.9.1': + '@discordjs/voice@0.19.2(@discordjs/opus@0.10.0)(opusscript@0.1.1)': dependencies: - '@emnapi/wasi-threads': 1.2.0 + '@snazzah/davey': 0.1.9 + '@types/ws': 8.18.1 + discord-api-types: 0.38.42 + prism-media: 1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1) + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true @@ -7602,12 +7782,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.0': + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 optional: true @@ -7697,7 +7872,7 @@ snapshots: optionalDependencies: '@noble/hashes': 2.0.1 - '@google/genai@1.46.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))': + '@google/genai@1.42.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))': dependencies: google-auth-library: 10.6.2 p-retry: 4.6.2 @@ -7753,7 +7928,7 @@ snapshots: dependencies: hono: 4.12.8 - '@huggingface/jinja@0.5.6': {} + '@huggingface/jinja@0.5.5': {} '@img/colour@1.0.0': {} @@ -8098,7 +8273,7 @@ snapshots: dependencies: badgen: 3.2.3 colors: 1.4.0 - fs-extra: 11.3.4 + fs-extra: 11.3.3 '@jscpd/core@4.0.4': dependencies: @@ -8113,15 +8288,15 @@ snapshots: cli-table3: 0.6.5 colors: 1.4.0 fast-glob: 3.3.3 - fs-extra: 11.3.4 + fs-extra: 11.3.3 markdown-table: 2.0.0 - pug: 3.0.3 + pug: 3.0.4 '@jscpd/html-reporter@4.0.4': dependencies: colors: 1.4.0 - fs-extra: 11.3.4 - pug: 3.0.3 + fs-extra: 11.3.3 + pug: 3.0.4 '@jscpd/tokenizer@4.0.4': dependencies: @@ -8131,7 +8306,7 @@ snapshots: '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: - hashery: 1.5.1 + hashery: 1.5.0 hookified: 1.15.1 keyv: 5.6.0 @@ -8181,7 +8356,7 @@ snapshots: '@larksuiteoapi/node-sdk@1.59.0': dependencies: - axios: 1.13.6 + axios: 1.13.5 lodash.identity: 3.0.0 lodash.merge: 4.6.2 lodash.pickby: 4.6.0 @@ -8195,9 +8370,9 @@ snapshots: '@line/bot-sdk@10.6.0': dependencies: - '@types/node': 24.12.0 + '@types/node': 24.10.13 optionalDependencies: - axios: 1.13.6 + axios: 1.13.5 transitivePeerDependencies: - debug @@ -8307,8 +8482,8 @@ snapshots: '@mariozechner/pi-ai@0.61.1(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.73.0(zod@4.3.6) - '@aws-sdk/client-bedrock-runtime': 3.1014.0 - '@google/genai': 1.46.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)) + '@aws-sdk/client-bedrock-runtime': 3.997.0 + '@google/genai': 1.42.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)) '@mistralai/mistralai': 1.14.1 '@sinclair/typebox': 0.34.48 ajv: 8.18.0 @@ -8346,7 +8521,7 @@ snapshots: marked: 15.0.12 minimatch: 10.2.4 proper-lockfile: 4.1.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 undici: 7.24.5 yaml: 2.8.3 optionalDependencies: @@ -8368,7 +8543,7 @@ snapshots: marked: 15.0.12 mime-types: 3.0.2 optionalDependencies: - koffi: 2.15.2 + koffi: 2.15.1 '@matrix-org/matrix-sdk-crypto-nodejs@0.4.0': dependencies: @@ -8379,28 +8554,44 @@ snapshots: '@matrix-org/matrix-sdk-crypto-wasm@18.0.0': {} - '@microsoft/agents-activity@1.4.1': + '@microsoft/teams.api@2.0.5(@microsoft/teams.cards@2.0.5)(@microsoft/teams.common@2.0.5)': dependencies: - debug: 4.4.3 - uuid: 11.1.0 - zod: 3.25.75 - transitivePeerDependencies: - - supports-color + '@microsoft/teams.cards': 2.0.5 + '@microsoft/teams.common': 2.0.5 + jwt-decode: 4.0.0 + qs: 6.14.2 - '@microsoft/agents-hosting@1.4.1': + '@microsoft/teams.apps@2.0.5(@microsoft/teams.api@2.0.5(@microsoft/teams.cards@2.0.5)(@microsoft/teams.common@2.0.5))(@microsoft/teams.common@2.0.5)(@microsoft/teams.graph@2.0.5)': dependencies: - '@azure/core-auth': 1.10.1 - '@azure/msal-node': 5.1.1 - '@microsoft/agents-activity': 1.4.1 - axios: 1.13.6 + '@azure/msal-node': 3.8.10 + '@microsoft/teams.api': 2.0.5(@microsoft/teams.cards@2.0.5)(@microsoft/teams.common@2.0.5) + '@microsoft/teams.common': 2.0.5 + '@microsoft/teams.graph': 2.0.5 + axios: 1.13.5 + cors: 2.8.6 + express: 4.22.1 jsonwebtoken: 9.0.3 - jwks-rsa: 4.0.1 - object-path: 0.11.8 - zod: 3.25.75 + jwks-rsa: 3.2.2 + reflect-metadata: 0.2.2 transitivePeerDependencies: - debug - supports-color + '@microsoft/teams.cards@2.0.5': {} + + '@microsoft/teams.common@2.0.5': + dependencies: + axios: 1.13.5 + transitivePeerDependencies: + - debug + + '@microsoft/teams.graph@2.0.5': + dependencies: + '@microsoft/teams.common': 2.0.5 + qs: 6.14.2 + transitivePeerDependencies: + - debug + '@mistralai/mistralai@1.14.1': dependencies: ws: 8.20.0 @@ -8423,7 +8614,7 @@ snapshots: express: 5.2.1 express-rate-limit: 8.3.1(express@5.2.1) hono: 4.12.8 - jose: 6.2.1 + jose: 6.2.2 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 @@ -8434,57 +8625,105 @@ snapshots: '@mozilla/readability@0.6.0': {} - '@napi-rs/canvas-android-arm64@0.1.95': + '@napi-rs/canvas-android-arm64@0.1.92': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.95': + '@napi-rs/canvas-android-arm64@0.1.97': optional: true - '@napi-rs/canvas-darwin-x64@0.1.95': + '@napi-rs/canvas-darwin-arm64@0.1.92': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.95': + '@napi-rs/canvas-darwin-arm64@0.1.97': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.95': + '@napi-rs/canvas-darwin-x64@0.1.92': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.95': + '@napi-rs/canvas-darwin-x64@0.1.97': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.95': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.92': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.95': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.97': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.95': + '@napi-rs/canvas-linux-arm64-gnu@0.1.92': optional: true - '@napi-rs/canvas-win32-arm64-msvc@0.1.95': + '@napi-rs/canvas-linux-arm64-gnu@0.1.97': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.95': + '@napi-rs/canvas-linux-arm64-musl@0.1.92': optional: true - '@napi-rs/canvas@0.1.95': + '@napi-rs/canvas-linux-arm64-musl@0.1.97': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.92': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.92': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.97': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.92': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.97': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@0.1.92': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@0.1.97': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.92': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.97': + optional: true + + '@napi-rs/canvas@0.1.92': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.95 - '@napi-rs/canvas-darwin-arm64': 0.1.95 - '@napi-rs/canvas-darwin-x64': 0.1.95 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.95 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.95 - '@napi-rs/canvas-linux-arm64-musl': 0.1.95 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.95 - '@napi-rs/canvas-linux-x64-gnu': 0.1.95 - '@napi-rs/canvas-linux-x64-musl': 0.1.95 - '@napi-rs/canvas-win32-arm64-msvc': 0.1.95 - '@napi-rs/canvas-win32-x64-msvc': 0.1.95 + '@napi-rs/canvas-android-arm64': 0.1.92 + '@napi-rs/canvas-darwin-arm64': 0.1.92 + '@napi-rs/canvas-darwin-x64': 0.1.92 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.92 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.92 + '@napi-rs/canvas-linux-arm64-musl': 0.1.92 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.92 + '@napi-rs/canvas-linux-x64-gnu': 0.1.92 + '@napi-rs/canvas-linux-x64-musl': 0.1.92 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.92 + '@napi-rs/canvas-win32-x64-msvc': 0.1.92 + + '@napi-rs/canvas@0.1.97': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.97 + '@napi-rs/canvas-darwin-arm64': 0.1.97 + '@napi-rs/canvas-darwin-x64': 0.1.97 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.97 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.97 + '@napi-rs/canvas-linux-arm64-musl': 0.1.97 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.97 + '@napi-rs/canvas-linux-x64-gnu': 0.1.97 + '@napi-rs/canvas-linux-x64-musl': 0.1.97 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.97 + '@napi-rs/canvas-win32-x64-msvc': 0.1.97 + optional: true '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -8565,7 +8804,7 @@ snapshots: dependencies: '@octokit/auth-oauth-app': 9.0.3 '@octokit/auth-oauth-user': 6.0.2 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 toad-cache: 3.7.0 @@ -8576,14 +8815,14 @@ snapshots: dependencies: '@octokit/auth-oauth-device': 8.0.3 '@octokit/auth-oauth-user': 6.0.2 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/auth-oauth-device@8.0.3': dependencies: '@octokit/oauth-methods': 6.0.2 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -8591,7 +8830,7 @@ snapshots: dependencies: '@octokit/auth-oauth-device': 8.0.3 '@octokit/oauth-methods': 6.0.2 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -8606,20 +8845,20 @@ snapshots: dependencies: '@octokit/auth-token': 6.0.0 '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.3': + '@octokit/endpoint@11.0.2': dependencies: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/graphql@9.0.3': dependencies: - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -8631,7 +8870,7 @@ snapshots: '@octokit/core': 7.0.6 '@octokit/oauth-authorization-url': 8.0.0 '@octokit/oauth-methods': 6.0.2 - '@types/aws-lambda': 8.10.161 + '@types/aws-lambda': 8.10.160 universal-user-agent: 7.0.3 '@octokit/oauth-authorization-url@8.0.0': {} @@ -8639,7 +8878,7 @@ snapshots: '@octokit/oauth-methods@6.0.2': dependencies: '@octokit/oauth-authorization-url': 8.0.0 - '@octokit/request': 10.0.8 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 @@ -8661,7 +8900,7 @@ snapshots: '@octokit/core': 7.0.6 '@octokit/types': 16.0.0 - '@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)': + '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 '@octokit/request-error': 7.1.0 @@ -8678,13 +8917,12 @@ snapshots: dependencies: '@octokit/types': 16.0.0 - '@octokit/request@10.0.8': + '@octokit/request@10.0.7': dependencies: - '@octokit/endpoint': 11.0.3 + '@octokit/endpoint': 11.0.2 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 fast-content-type-parse: 3.0.0 - json-with-bigint: 3.5.7 universal-user-agent: 7.0.3 '@octokit/types@16.0.0': @@ -9307,13 +9545,13 @@ snapshots: '@slack/bolt@4.6.0(@types/express@5.0.6)': dependencies: - '@slack/logger': 4.0.1 - '@slack/oauth': 3.0.5 - '@slack/socket-mode': 2.0.6 - '@slack/types': 2.20.1 + '@slack/logger': 4.0.0 + '@slack/oauth': 3.0.4 + '@slack/socket-mode': 2.0.5 + '@slack/types': 2.20.0 '@slack/web-api': 7.15.0 '@types/express': 5.0.6 - axios: 1.13.6 + axios: 1.13.5 express: 5.2.1 path-to-regexp: 8.3.0 raw-body: 3.0.2 @@ -9324,13 +9562,17 @@ snapshots: - supports-color - utf-8-validate + '@slack/logger@4.0.0': + dependencies: + '@types/node': 25.5.0 + '@slack/logger@4.0.1': dependencies: '@types/node': 25.5.0 - '@slack/oauth@3.0.5': + '@slack/oauth@3.0.4': dependencies: - '@slack/logger': 4.0.1 + '@slack/logger': 4.0.0 '@slack/web-api': 7.15.0 '@types/jsonwebtoken': 9.0.10 '@types/node': 25.5.0 @@ -9338,9 +9580,9 @@ snapshots: transitivePeerDependencies: - debug - '@slack/socket-mode@2.0.6': + '@slack/socket-mode@2.0.5': dependencies: - '@slack/logger': 4.0.1 + '@slack/logger': 4.0.0 '@slack/web-api': 7.15.0 '@types/node': 25.5.0 '@types/ws': 8.18.1 @@ -9351,6 +9593,8 @@ snapshots: - debug - utf-8-validate + '@slack/types@2.20.0': {} + '@slack/types@2.20.1': {} '@slack/web-api@7.15.0': @@ -9359,7 +9603,7 @@ snapshots: '@slack/types': 2.20.1 '@types/node': 25.5.0 '@types/retry': 0.12.0 - axios: 1.13.6 + axios: 1.13.5 eventemitter3: 5.0.4 form-data: 2.5.4 is-electron: 2.2.2 @@ -9414,6 +9658,13 @@ snapshots: '@smithy/url-parser': 4.2.12 tslib: 2.8.1 + '@smithy/eventstream-codec@4.2.10': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + '@smithy/eventstream-codec@4.2.12': dependencies: '@aws-crypto/crc32': 5.2.0 @@ -9421,23 +9672,46 @@ snapshots: '@smithy/util-hex-encoding': 4.2.2 tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.2.10': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.10 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.2.12': dependencies: '@smithy/eventstream-serde-universal': 4.2.12 '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/eventstream-serde-config-resolver@4.3.10': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-config-resolver@4.3.12': dependencies: '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/eventstream-serde-node@4.2.10': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.10 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-node@4.2.12': dependencies: '@smithy/eventstream-serde-universal': 4.2.12 '@smithy/types': 4.13.1 tslib: 2.8.1 + '@smithy/eventstream-serde-universal@4.2.10': + dependencies: + '@smithy/eventstream-codec': 4.2.10 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-universal@4.2.12': dependencies: '@smithy/eventstream-codec': 4.2.12 @@ -9709,66 +9983,66 @@ snapshots: dependencies: tslib: 2.8.1 - '@snazzah/davey-android-arm-eabi@0.1.10': + '@snazzah/davey-android-arm-eabi@0.1.9': optional: true - '@snazzah/davey-android-arm64@0.1.10': + '@snazzah/davey-android-arm64@0.1.9': optional: true - '@snazzah/davey-darwin-arm64@0.1.10': + '@snazzah/davey-darwin-arm64@0.1.9': optional: true - '@snazzah/davey-darwin-x64@0.1.10': + '@snazzah/davey-darwin-x64@0.1.9': optional: true - '@snazzah/davey-freebsd-x64@0.1.10': + '@snazzah/davey-freebsd-x64@0.1.9': optional: true - '@snazzah/davey-linux-arm-gnueabihf@0.1.10': + '@snazzah/davey-linux-arm-gnueabihf@0.1.9': optional: true - '@snazzah/davey-linux-arm64-gnu@0.1.10': + '@snazzah/davey-linux-arm64-gnu@0.1.9': optional: true - '@snazzah/davey-linux-arm64-musl@0.1.10': + '@snazzah/davey-linux-arm64-musl@0.1.9': optional: true - '@snazzah/davey-linux-x64-gnu@0.1.10': + '@snazzah/davey-linux-x64-gnu@0.1.9': optional: true - '@snazzah/davey-linux-x64-musl@0.1.10': + '@snazzah/davey-linux-x64-musl@0.1.9': optional: true - '@snazzah/davey-wasm32-wasi@0.1.10': + '@snazzah/davey-wasm32-wasi@0.1.9': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@snazzah/davey-win32-arm64-msvc@0.1.10': + '@snazzah/davey-win32-arm64-msvc@0.1.9': optional: true - '@snazzah/davey-win32-ia32-msvc@0.1.10': + '@snazzah/davey-win32-ia32-msvc@0.1.9': optional: true - '@snazzah/davey-win32-x64-msvc@0.1.10': + '@snazzah/davey-win32-x64-msvc@0.1.9': optional: true - '@snazzah/davey@0.1.10': + '@snazzah/davey@0.1.9': optionalDependencies: - '@snazzah/davey-android-arm-eabi': 0.1.10 - '@snazzah/davey-android-arm64': 0.1.10 - '@snazzah/davey-darwin-arm64': 0.1.10 - '@snazzah/davey-darwin-x64': 0.1.10 - '@snazzah/davey-freebsd-x64': 0.1.10 - '@snazzah/davey-linux-arm-gnueabihf': 0.1.10 - '@snazzah/davey-linux-arm64-gnu': 0.1.10 - '@snazzah/davey-linux-arm64-musl': 0.1.10 - '@snazzah/davey-linux-x64-gnu': 0.1.10 - '@snazzah/davey-linux-x64-musl': 0.1.10 - '@snazzah/davey-wasm32-wasi': 0.1.10 - '@snazzah/davey-win32-arm64-msvc': 0.1.10 - '@snazzah/davey-win32-ia32-msvc': 0.1.10 - '@snazzah/davey-win32-x64-msvc': 0.1.10 + '@snazzah/davey-android-arm-eabi': 0.1.9 + '@snazzah/davey-android-arm64': 0.1.9 + '@snazzah/davey-darwin-arm64': 0.1.9 + '@snazzah/davey-darwin-x64': 0.1.9 + '@snazzah/davey-freebsd-x64': 0.1.9 + '@snazzah/davey-linux-arm-gnueabihf': 0.1.9 + '@snazzah/davey-linux-arm64-gnu': 0.1.9 + '@snazzah/davey-linux-arm64-musl': 0.1.9 + '@snazzah/davey-linux-x64-gnu': 0.1.9 + '@snazzah/davey-linux-x64-musl': 0.1.9 + '@snazzah/davey-wasm32-wasi': 0.1.9 + '@snazzah/davey-win32-arm64-msvc': 0.1.9 + '@snazzah/davey-win32-ia32-msvc': 0.1.9 + '@snazzah/davey-win32-x64-msvc': 0.1.9 '@standard-schema/spec@1.1.0': {} @@ -9779,12 +10053,12 @@ snapshots: '@telegraf/types@7.1.0': optional: true - '@thi.ng/bitstream@2.4.44': + '@thi.ng/bitstream@2.4.41': dependencies: - '@thi.ng/errors': 2.6.6 + '@thi.ng/errors': 2.6.3 optional: true - '@thi.ng/errors@2.6.6': + '@thi.ng/errors@2.6.3': optional: true '@tinyhttp/content-disposition@2.2.4': {} @@ -9875,7 +10149,7 @@ snapshots: tslib: 2.8.1 optional: true - '@types/aws-lambda@8.10.161': {} + '@types/aws-lambda@8.10.160': {} '@types/body-parser@1.19.6': dependencies: @@ -9955,11 +10229,11 @@ snapshots: '@types/node@16.9.1': {} - '@types/node@20.19.37': + '@types/node@20.19.33': dependencies: undici-types: 6.21.0 - '@types/node@24.12.0': + '@types/node@24.10.13': dependencies: undici-types: 7.16.0 @@ -10030,14 +10304,6 @@ snapshots: '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260322.1 '@typescript/native-preview-win32-x64': 7.0.0-dev.20260322.1 - '@typespec/ts-http-runtime@0.3.4': - dependencies: - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - '@ungap/structured-clone@1.3.0': {} '@urbit/aura@3.0.0': {} @@ -10047,7 +10313,7 @@ snapshots: '@vitest/browser': 4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0) '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) playwright: 1.58.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil @@ -10063,7 +10329,7 @@ snapshots: magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0 transitivePeerDependencies: @@ -10083,7 +10349,7 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 std-env: 4.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(jsdom@29.0.1(@noble/hashes@2.0.1))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@vitest/browser': 4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.0) @@ -10095,7 +10361,7 @@ snapshots: '@vitest/spy': 4.1.0 '@vitest/utils': 4.1.0 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: @@ -10107,7 +10373,7 @@ snapshots: '@vitest/pretty-format@4.1.0': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 '@vitest/runner@4.1.0': dependencies: @@ -10127,7 +10393,7 @@ snapshots: dependencies: '@vitest/pretty-format': 4.1.0 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 '@wasm-audio-decoders/common@9.0.7': dependencies: @@ -10158,8 +10424,8 @@ snapshots: '@hapi/boom': 9.1.4 async-mutex: 0.5.0 libsignal: '@whiskeysockets/libsignal-node@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67' - lru-cache: 11.2.7 - music-metadata: 11.12.3 + lru-cache: 11.2.6 + music-metadata: 11.12.1 p-queue: 9.1.0 pino: 9.14.0 protobufjs: 7.5.4 @@ -10185,6 +10451,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -10256,9 +10527,9 @@ snapshots: '@swc/helpers': 0.5.19 '@types/command-line-args': 5.2.3 '@types/command-line-usage': 5.0.4 - '@types/node': 20.19.37 + '@types/node': 20.19.33 command-line-args: 5.2.1 - command-line-usage: 7.0.4 + command-line-usage: 7.0.3 flatbuffers: 24.12.23 json-bignum: 0.0.3 tslib: 2.8.1 @@ -10276,7 +10547,9 @@ snapshots: array-back@3.1.0: {} - array-back@6.2.3: {} + array-back@6.2.2: {} + + array-flatten@1.1.1: {} asap@2.0.6: {} @@ -10320,19 +10593,19 @@ snapshots: '@wasm-audio-decoders/flac': 0.2.10 '@wasm-audio-decoders/ogg-vorbis': 0.1.20 audio-buffer: 5.0.0 - audio-type: 2.4.0 + audio-type: 2.2.1 mpg123-decoder: 1.0.3 node-wav: 0.0.2 ogg-opus-decoder: 1.7.3 qoa-format: 1.0.1 optional: true - audio-type@2.4.0: + audio-type@2.2.1: optional: true await-to-js@3.0.0: {} - axios@1.13.6: + axios@1.13.5: dependencies: follow-redirects: 1.15.11 form-data: 2.5.4 @@ -10406,6 +10679,23 @@ snapshots: bmp-ts@1.0.9: {} + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -10426,7 +10716,7 @@ snapshots: bowser@2.14.1: {} - brace-expansion@5.0.4: + brace-expansion@5.0.3: dependencies: balanced-match: 4.0.4 @@ -10465,13 +10755,13 @@ snapshots: cac@7.0.0: {} - cacheable@2.3.4: + cacheable@2.3.2: dependencies: - '@cacheable/memory': 2.0.8 - '@cacheable/utils': 2.4.0 + '@cacheable/memory': 2.0.7 + '@cacheable/utils': 2.3.4 hookified: 1.15.1 keyv: 5.6.0 - qified: 0.9.0 + qified: 0.6.0 call-bind-apply-helpers@1.0.2: dependencies: @@ -10556,7 +10846,7 @@ snapshots: cmake-js@8.0.0: dependencies: debug: 4.4.3 - fs-extra: 11.3.4 + fs-extra: 11.3.3 node-api-headers: 1.8.0 rc: 1.2.8 semver: 7.7.4 @@ -10594,9 +10884,9 @@ snapshots: lodash.camelcase: 4.3.0 typical: 4.0.0 - command-line-usage@7.0.4: + command-line-usage@7.0.3: dependencies: - array-back: 6.2.3 + array-back: 6.2.2 chalk-template: 0.4.0 table-layout: 4.1.1 typical: 7.3.0 @@ -10615,12 +10905,18 @@ snapshots: '@babel/parser': 7.29.0 '@babel/types': 7.29.0 + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + content-disposition@1.0.1: {} content-type@1.0.5: {} convert-source-map@2.0.0: {} + cookie-signature@1.0.7: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -10672,6 +10968,10 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -10697,6 +10997,8 @@ snapshots: dequal@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} devlop@1.1.0: @@ -10883,6 +11185,42 @@ snapshots: express: 5.2.1 ip-address: 10.1.0 + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + express@5.2.1: dependencies: accepts: 2.0.0 @@ -10954,7 +11292,7 @@ snapshots: dependencies: fast-xml-builder: 1.1.4 path-expression-matcher: 1.2.0 - strnum: 2.2.1 + strnum: 2.2.2 fastq@1.20.1: dependencies: @@ -10972,7 +11310,7 @@ snapshots: file-type@21.3.4: dependencies: '@tokenizer/inflate': 0.4.1 - strtok3: 10.3.5 + strtok3: 10.3.4 token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: @@ -10988,6 +11326,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -11022,9 +11372,11 @@ snapshots: forwarded@0.2.0: {} + fresh@0.5.2: {} + fresh@2.0.0: {} - fs-extra@11.3.4: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 @@ -11217,7 +11569,7 @@ snapshots: has-unicode@2.0.1: optional: true - hashery@1.5.1: + hashery@1.5.0: dependencies: hookified: 1.15.1 @@ -11251,8 +11603,6 @@ snapshots: hookified@1.15.1: {} - hookified@2.1.0: {} - hosted-git-info@9.0.2: dependencies: lru-cache: 11.2.7 @@ -11315,6 +11665,10 @@ snapshots: human-signals@1.1.1: {} + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -11364,7 +11718,7 @@ snapshots: commander: 10.0.1 eventemitter3: 5.0.4 filenamify: 6.0.0 - fs-extra: 11.3.4 + fs-extra: 11.3.3 is-unicode-supported: 2.1.0 lifecycle-utils: 2.1.0 lodash.debounce: 4.0.8 @@ -11374,7 +11728,7 @@ snapshots: sleep-promise: 9.1.0 slice-ansi: 7.1.2 stdout-update: 4.0.1 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 optionalDependencies: '@reflink/reflink': 0.1.19 @@ -11490,7 +11844,7 @@ snapshots: jiti@2.6.1: {} - jose@6.2.1: {} + jose@4.15.9: {} jose@6.2.2: {} @@ -11503,7 +11857,7 @@ snapshots: jscpd-sarif-reporter@4.0.6: dependencies: colors: 1.4.0 - fs-extra: 11.3.4 + fs-extra: 11.3.3 node-sarif-builder: 3.4.0 jscpd@4.0.8: @@ -11515,7 +11869,7 @@ snapshots: '@jscpd/tokenizer': 4.0.4 colors: 1.4.0 commander: 5.1.0 - fs-extra: 11.3.4 + fs-extra: 11.3.3 gitignore-to-glob: 0.3.0 jscpd-sarif-reporter: 4.0.6 @@ -11555,15 +11909,13 @@ snapshots: json-schema-to-ts@3.1.1: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.28.6 ts-algebra: 2.0.0 json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} - json-with-bigint@3.5.7: {} - json5@2.2.3: {} jsonfile@6.2.0: @@ -11603,13 +11955,13 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jwks-rsa@4.0.1: + jwks-rsa@3.2.2: dependencies: '@types/jsonwebtoken': 9.0.10 debug: 4.4.3 - jose: 6.2.2 + jose: 4.15.9 limiter: 1.1.5 - lru-memoizer: 3.0.0 + lru-memoizer: 2.3.0 transitivePeerDependencies: - supports-color @@ -11626,7 +11978,7 @@ snapshots: klona@2.0.6: {} - koffi@2.15.2: + koffi@2.15.1: optional: true lie@3.3.0: @@ -11757,14 +12109,20 @@ snapshots: dependencies: steno: 4.0.2 + lru-cache@11.2.6: {} + lru-cache@11.2.7: {} + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lru-cache@7.18.3: {} - lru-memoizer@3.0.0: + lru-memoizer@2.3.0: dependencies: lodash.clonedeep: 4.5.0 - lru-cache: 11.2.7 + lru-cache: 6.0.0 lru_map@0.4.1: {} @@ -11810,7 +12168,7 @@ snapshots: matrix-js-sdk@41.2.0-rc.0: dependencies: - '@babel/runtime': 7.29.2 + '@babel/runtime': 7.28.6 '@matrix-org/matrix-sdk-crypto-wasm': 18.0.0 another-json: 0.2.0 bs58: 6.0.0 @@ -11846,14 +12204,20 @@ snapshots: mdurl@2.0.0: {} + media-typer@0.3.0: {} + media-typer@1.1.0: {} + merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} merge2@1.4.1: {} + methods@1.1.2: {} + micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 @@ -11888,6 +12252,8 @@ snapshots: dependencies: mime-db: 1.54.0 + mime@1.6.0: {} + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -11896,7 +12262,7 @@ snapshots: minimatch@10.2.4: dependencies: - brace-expansion: 5.0.4 + brace-expansion: 5.0.3 minimist@1.2.8: {} @@ -11918,11 +12284,13 @@ snapshots: mrmime@2.0.1: {} + ms@2.0.0: {} + ms@2.1.3: {} - music-metadata@11.12.3: + music-metadata@11.12.1: dependencies: - '@borewit/text-codec': 0.2.2 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 content-type: 1.0.5 debug: 4.4.3 @@ -11943,13 +12311,15 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.7: {} + nanoid@5.1.6: {} + + negotiator@0.6.3: {} negotiator@1.0.0: {} netmask@2.0.2: {} - node-addon-api@8.6.0: {} + node-addon-api@8.5.0: {} node-api-headers@1.8.0: {} @@ -11977,7 +12347,7 @@ snapshots: node-llama-cpp@3.16.2(typescript@5.9.3): dependencies: - '@huggingface/jinja': 0.5.6 + '@huggingface/jinja': 0.5.5 async-retry: 1.3.3 bytes: 3.1.2 chalk: 5.6.2 @@ -11986,23 +12356,23 @@ snapshots: cross-spawn: 7.0.6 env-var: 7.5.0 filenamify: 6.0.0 - fs-extra: 11.3.4 + fs-extra: 11.3.3 ignore: 7.0.5 ipull: 3.9.5 is-unicode-supported: 2.1.0 lifecycle-utils: 3.1.1 log-symbols: 7.0.1 - nanoid: 5.1.7 - node-addon-api: 8.6.0 + nanoid: 5.1.6 + node-addon-api: 8.5.0 octokit: 5.0.5 ora: 9.3.0 pretty-ms: 9.3.0 proper-lockfile: 4.1.2 semver: 7.7.4 - simple-git: 3.33.0 + simple-git: 3.31.1 slice-ansi: 8.0.0 stdout-update: 4.0.1 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 validate-npm-package-name: 7.0.2 which: 6.0.1 yargs: 17.7.2 @@ -12030,7 +12400,7 @@ snapshots: node-sarif-builder@3.4.0: dependencies: '@types/sarif': 2.1.7 - fs-extra: 11.3.4 + fs-extra: 11.3.3 node-wav@0.0.2: optional: true @@ -12074,8 +12444,6 @@ snapshots: object-inspect@1.13.4: {} - object-path@0.11.8: {} - obug@2.1.1: {} octokit@5.0.5: @@ -12086,7 +12454,7 @@ snapshots: '@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.6) '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) - '@octokit/plugin-retry': 8.1.0(@octokit/core@7.0.6) + '@octokit/plugin-retry': 8.0.3(@octokit/core@7.0.6) '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 @@ -12322,13 +12690,15 @@ snapshots: lru-cache: 11.2.7 minipass: 7.1.3 + path-to-regexp@0.1.12: {} + path-to-regexp@8.3.0: {} pathe@2.0.3: {} pdfjs-dist@5.5.207: optionalDependencies: - '@napi-rs/canvas': 0.1.95 + '@napi-rs/canvas': 0.1.97 node-readable-to-web-readable-stream: 0.4.2 pend@1.2.0: {} @@ -12475,7 +12845,7 @@ snapshots: js-stringify: 1.0.2 pug-runtime: 3.0.1 - pug-code-gen@3.0.3: + pug-code-gen@3.0.4: dependencies: constantinople: 4.0.1 doctypes: 1.1.0 @@ -12525,9 +12895,9 @@ snapshots: pug-walk@2.0.0: {} - pug@3.0.3: + pug@3.0.4: dependencies: - pug-code-gen: 3.0.3 + pug-code-gen: 3.0.4 pug-filters: 4.0.0 pug-lexer: 5.0.1 pug-linker: 4.0.0 @@ -12545,13 +12915,13 @@ snapshots: punycode@2.3.1: {} - qified@0.9.0: + qified@0.6.0: dependencies: - hookified: 2.1.0 + hookified: 1.15.1 qoa-format@1.0.1: dependencies: - '@thi.ng/bitstream': 2.4.44 + '@thi.ng/bitstream': 2.4.41 optional: true qrcode-terminal@0.12.0: {} @@ -12570,6 +12940,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + raw-body@3.0.2: dependencies: bytes: 3.1.2 @@ -12770,6 +13147,24 @@ snapshots: semver@7.7.4: {} + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + send@1.2.1: dependencies: debug: 4.4.3 @@ -12786,6 +13181,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 @@ -12890,7 +13294,7 @@ snapshots: dependencies: signal-polyfill: 0.2.2 - simple-git@3.33.0: + simple-git@3.31.1: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 @@ -13004,7 +13408,7 @@ snapshots: ansi-escapes: 6.2.1 ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 steno@4.0.2: {} @@ -13027,12 +13431,12 @@ snapshots: dependencies: emoji-regex: 10.6.0 get-east-asian-width: 1.5.0 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 string-width@8.2.0: dependencies: get-east-asian-width: 1.5.0 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 string_decoder@1.1.1: dependencies: @@ -13052,7 +13456,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.2.0: + strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 @@ -13060,16 +13464,12 @@ snapshots: strip-json-comments@2.0.1: {} - strnum@2.2.1: {} + strnum@2.2.2: {} strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 - strtok3@10.3.5: - dependencies: - '@tokenizer/token': 0.3.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -13080,7 +13480,7 @@ snapshots: table-layout@4.1.1: dependencies: - array-back: 6.2.3 + array-back: 6.2.2 wordwrapjs: 5.1.1 tar-stream@3.1.8: @@ -13146,6 +13546,8 @@ snapshots: tinycolor2@1.6.0: {} + tinyexec@1.0.2: {} + tinyexec@1.0.4: {} tinyglobby@0.2.15: @@ -13155,7 +13557,7 @@ snapshots: tinypool@2.1.0: {} - tinyrainbow@3.1.0: {} + tinyrainbow@3.0.3: {} to-regex-range@5.0.1: dependencies: @@ -13169,7 +13571,7 @@ snapshots: token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.2.2 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 @@ -13234,6 +13636,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -13317,7 +13724,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.1.0: {} + utils-merge@1.0.1: {} uuid@13.0.0: {} @@ -13371,9 +13778,9 @@ snapshots: picomatch: 4.0.3 std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 1.0.4 + tinyexec: 1.0.2 tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 + tinyrainbow: 3.0.3 vite: 8.0.1(@types/node@25.5.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: @@ -13467,6 +13874,8 @@ snapshots: y18n@5.0.8: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yaml@2.8.3: {} @@ -13520,8 +13929,6 @@ snapshots: dependencies: zod: 4.3.6 - zod@3.25.75: {} - zod@3.25.76: {} zod@4.3.6: {} diff --git a/scripts/tsdown-build.mjs b/scripts/tsdown-build.mjs index 4d31d06a693..a86a1a01e8c 100644 --- a/scripts/tsdown-build.mjs +++ b/scripts/tsdown-build.mjs @@ -49,7 +49,7 @@ function findFatalUnresolvedImport(lines) { } const normalizedLine = line.replace(ANSI_ESCAPE_RE, ""); - if (!normalizedLine.includes("extensions/")) { + if (!normalizedLine.includes("extensions/") && !normalizedLine.includes("node_modules/")) { return normalizedLine; } } diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index d00956672be..f85389d8460 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -2628,6 +2628,58 @@ describe("dispatchReplyFromConfig", () => { ); }); + it("passes configOverride to replyResolver when provided", async () => { + setNoAbort(); + const cfg = emptyConfig; + const dispatcher = createDispatcher(); + const ctx = buildTestCtx({ Provider: "msteams", Surface: "msteams" }); + + const overrideCfg = { + agents: { defaults: { userTimezone: "America/New_York" } }, + } as OpenClawConfig; + + let receivedCfg: OpenClawConfig | undefined; + const replyResolver = async ( + _ctx: MsgContext, + _opts?: GetReplyOptions, + cfgArg?: OpenClawConfig, + ) => { + receivedCfg = cfgArg; + return { text: "hi" } satisfies ReplyPayload; + }; + + await dispatchReplyFromConfig({ + ctx, + cfg, + dispatcher, + replyResolver, + configOverride: overrideCfg, + }); + + expect(receivedCfg).toBe(overrideCfg); + }); + + it("passes base cfg to replyResolver when configOverride is not provided", async () => { + setNoAbort(); + const cfg = { agents: { defaults: { userTimezone: "UTC" } } } as OpenClawConfig; + const dispatcher = createDispatcher(); + const ctx = buildTestCtx({ Provider: "telegram", Surface: "telegram" }); + + let receivedCfg: OpenClawConfig | undefined; + const replyResolver = async ( + _ctx: MsgContext, + _opts?: GetReplyOptions, + cfgArg?: OpenClawConfig, + ) => { + receivedCfg = cfgArg; + return { text: "hi" } satisfies ReplyPayload; + }; + + await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver }); + + expect(receivedCfg).toBe(cfg); + }); + it("suppresses isReasoning payloads from final replies (WhatsApp channel)", async () => { setNoAbort(); const dispatcher = createDispatcher(); diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index 5b18b417a35..579ae922fce 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -152,6 +152,8 @@ export async function dispatchReplyFromConfig(params: { dispatcher: ReplyDispatcher; replyOptions?: Omit; replyResolver?: typeof import("./get-reply-from-config.runtime.js").getReplyFromConfig; + /** Optional config override passed to getReplyFromConfig (e.g. per-sender timezone). */ + configOverride?: OpenClawConfig; }): Promise { const { ctx, cfg, dispatcher } = params; const diagnosticsEnabled = isDiagnosticsEnabled(cfg); @@ -641,7 +643,7 @@ export async function dispatchReplyFromConfig(params: { return run(); }, }, - cfg, + params.configOverride ?? cfg, ); if (ctx.AcpDispatchTailAfterReset === true) { diff --git a/src/config/types.msteams.ts b/src/config/types.msteams.ts index 83195f03a40..4d9211d64d6 100644 --- a/src/config/types.msteams.ts +++ b/src/config/types.msteams.ts @@ -121,4 +121,16 @@ export type MSTeamsConfig = { healthMonitor?: ChannelHealthMonitorConfig; /** Outbound response prefix override for this channel/account. */ responsePrefix?: string; + /** Show a welcome Adaptive Card when the bot is added to a 1:1 chat. Default: true. */ + welcomeCard?: boolean; + /** Custom prompt starter labels shown on the welcome card. */ + promptStarters?: string[]; + /** Show a welcome message when the bot is added to a group chat. Default: false. */ + groupWelcomeCard?: boolean; + /** Enable the Teams feedback loop (thumbs up/down) on AI-generated messages. Default: true. */ + feedbackEnabled?: boolean; + /** Enable background reflection when a user gives negative feedback. Default: true. */ + feedbackReflection?: boolean; + /** Minimum interval (ms) between reflections per session. Default: 300000 (5 min). */ + feedbackReflectionCooldownMs?: number; }; diff --git a/src/plugins/bundled-plugin-metadata.generated.ts b/src/plugins/bundled-plugin-metadata.generated.ts index 75c91505582..1f03f323f4d 100644 --- a/src/plugins/bundled-plugin-metadata.generated.ts +++ b/src/plugins/bundled-plugin-metadata.generated.ts @@ -2091,10 +2091,10 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ channel: { id: "msteams", label: "Microsoft Teams", - selectionLabel: "Microsoft Teams (Bot Framework)", + selectionLabel: "Microsoft Teams (Teams SDK)", docsPath: "/channels/msteams", docsLabel: "msteams", - blurb: "Bot Framework; enterprise support.", + blurb: "Teams SDK; enterprise support.", aliases: ["teams"], order: 60, },