@openclaw/matrix 2026.7.2-beta.3 → 2026.7.2-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api.js +4 -4
- package/dist/{approval-handler.runtime-BEa98wwG.js → approval-handler.runtime-D1ibJgSk.js} +4 -4
- package/dist/{approval-ids-Dfn7oQAm.js → approval-ids-2OQj32-K.js} +1 -1
- package/dist/{approval-reaction-auth-kXkCJBGi.js → approval-reaction-auth-CCAWWkbu.js} +2 -2
- package/dist/{channel-j331Xd7b.js → channel-Cmi248PA.js} +34 -32
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-BIwlVfQw.js → channel.runtime-BaPXFRgT.js} +34 -40
- package/dist/{cli-0EcKfqFw.js → cli-0CNHkps5.js} +868 -852
- package/dist/{cli-metadata-Xq1Xb15R.js → cli-metadata-DnXxNCoj.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{client-CA0PULls.js → client-BFRg0kd4.js} +2 -2
- package/dist/{client-BtG4quUl.js → client-CitERujx.js} +2 -2
- package/dist/{client-bootstrap-Dk5EAn3n.js → client-bootstrap-CkAnkzB6.js} +1 -1
- package/dist/{config-schema-B5GRV8wL.js → config-schema-BShrVROw.js} +19 -6
- package/dist/contract-api.js +2 -2
- package/dist/{create-client-WQExaby_.js → create-client-f-CkRllE.js} +3 -3
- package/dist/{crypto-runtime-CAff-n15.js → crypto-runtime-D9_6J5tZ.js} +2 -2
- package/dist/{device-health-D4LBxuPq.js → device-health-B4-YXtJR.js} +6 -1
- package/dist/{directory-live-DiC-0XBC.js → directory-live-YRgYDx2w.js} +3 -3
- package/dist/{doctor-DwUVm5Lw.js → doctor-CM8ijNpW.js} +1 -1
- package/dist/doctor-contract-BMSJkkAE.js +167 -0
- package/dist/doctor-contract-api.js +112 -71
- package/dist/{draft-stream-CTLKtJJ0.js → draft-stream-CIjafRr0.js} +1 -1
- package/dist/{file-sync-store-CPkgnSXp.js → file-sync-store-DiiFsayX.js} +1 -1
- package/dist/{http-client-BmrgOZTL.js → http-client-CycYxU9-.js} +2 -2
- package/dist/{idb-persistence-Dyy6ipgU.js → idb-persistence-B6x7dMsF.js} +75 -72
- package/dist/index.js +1 -1
- package/dist/{logging-QH-48Gr6.js → logging-DV-p52N0.js} +8 -1
- package/dist/{media-text-BMXEXb1j.js → media-text-DlQC_k_K.js} +1 -1
- package/dist/{messages-3UBHfZrN.js → messages-Bf_s--M9.js} +3 -3
- package/dist/{monitor-BtUIds8T.js → monitor-s--XqoxF.js} +2041 -1804
- package/dist/plugin-entry.handlers.runtime.js +1 -1
- package/dist/probe.runtime-BJOvCHnZ.js +3 -0
- package/dist/{profile-update-Bh2XtJwN.js → profile-update-z3aM46KS.js} +2 -2
- package/dist/{reaction-events-BwAKtUyQ.js → reaction-events-DrK9Zua7.js} +2 -2
- package/dist/{recovery-key-store-CW6eePfp.js → recovery-key-store-BNnHvXdk.js} +1 -1
- package/dist/{resolve-targets-DVoj-YxB.js → resolve-targets-nNMcrV3W.js} +1 -1
- package/dist/{resolver.runtime-CC0VVsAt.js → resolver.runtime-BvaFlVLg.js} +1 -1
- package/dist/{room-info-B6vKD5DF.js → room-info-CAFZ3Z9J.js} +13 -12
- package/dist/{sdk-CIDZXWpZ.js → sdk-UzPd8tst.js} +227 -181
- package/dist/{send-BJbrdmNm.js → send-C9Ssx-iV.js} +624 -109
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/{setup-bootstrap-DGPOOdz-.js → setup-bootstrap-BBGpPKgy.js} +2 -2
- package/dist/{setup-core-XCdAx1nY.js → setup-core-a542Ernq.js} +135 -45
- package/dist/setup-plugin-api.js +4 -3
- package/dist/{setup-surface-CzUiTe_k.js → setup-surface-BJrCI6KT.js} +34 -30
- package/dist/{shared-DquM1oz6.js → shared-CUH5otj8.js} +5 -12
- package/dist/{startup-verification-CA00kebL.js → startup-verification-wm3_i88s.js} +1 -1
- package/dist/{storage-CzHxeF5E.js → storage-BvAkgzvT.js} +24 -57
- package/dist/{thread-bindings-Cw194yEn.js → thread-bindings-D3RfVgAF.js} +4 -3
- package/dist/{tool-actions.runtime-C0jgWYhX.js → tool-actions.runtime-Dh5ow2EK.js} +8 -8
- package/dist/{verification-DjxyWTNP.js → verification-ZdAXdmuh.js} +1 -1
- package/node_modules/music-metadata/lib/common/Util.d.ts +1 -1
- package/node_modules/music-metadata/lib/common/Util.js +7 -1
- package/node_modules/music-metadata/lib/id3v2/FrameHeader.js +23 -3
- package/node_modules/music-metadata/lib/id3v2/FrameParser.js +2 -2
- package/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +1 -1
- package/node_modules/music-metadata/lib/mp4/AtomToken.js +38 -13
- package/node_modules/music-metadata/lib/mpeg/MpegParser.js +1 -1
- package/node_modules/music-metadata/package.json +6 -6
- package/node_modules/typebox/build/compile/validator.d.mts +1 -4
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +5 -2
- package/node_modules/typebox/build/guard/string.mjs +16 -4
- package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
- package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +29 -32
- package/openclaw.plugin.json +7 -20
- package/package.json +111 -29
- package/dist/doctor-contract-BA2Y6Crx.js +0 -312
- package/dist/probe.runtime-sBV-Govx.js +0 -3
- package/npm-shrinkwrap.json +0 -557
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import { a as resolveMatrixAccountConfig, i as resolveMatrixAccountAllowlistConfig } from "./account-config-BZWN9tGS.js";
|
|
2
2
|
import { t as getMatrixRuntime } from "./runtime-6S3DNFNv.js";
|
|
3
|
-
import {
|
|
4
|
-
import { a as buildAllowlistResolutionSummary, c as createTypingCallbacks, d as logInboundDrop, f as logTypingFailure, h as toLocationContext, i as addAllowlistUserEntriesFromConfigEntry, l as formatLocationText, m as summarizeMapping, n as resolveMatrixStoredSessionMeta, o as canonicalizeAllowlistWithResolvedIds, p as patchAllowlistUsersInConfigEntries, r as resolveMatrixRoomConfig, s as createReplyPrefixOptions, u as getAgentScopedMediaLocalRoots } from "./channel-
|
|
5
|
-
import { a as normalizeMatrixUserId, i as normalizeMatrixAllowList, o as resolveMatrixAllowListMatch } from "./config-schema-
|
|
3
|
+
import { h as resolveConfiguredMatrixBotUserIds } from "./setup-core-a542Ernq.js";
|
|
4
|
+
import { a as buildAllowlistResolutionSummary, c as createTypingCallbacks, d as logInboundDrop, f as logTypingFailure, h as toLocationContext, i as addAllowlistUserEntriesFromConfigEntry, l as formatLocationText, m as summarizeMapping, n as resolveMatrixStoredSessionMeta, o as canonicalizeAllowlistWithResolvedIds, p as patchAllowlistUsersInConfigEntries, r as resolveMatrixRoomConfig, s as createReplyPrefixOptions, u as getAgentScopedMediaLocalRoots } from "./channel-Cmi248PA.js";
|
|
5
|
+
import { a as normalizeMatrixUserId, i as normalizeMatrixAllowList, o as resolveMatrixAllowListMatch } from "./config-schema-BShrVROw.js";
|
|
6
6
|
import { t as isMatrixQualifiedUserId } from "./target-ids-B-5aQxwn.js";
|
|
7
7
|
import { t as formatMatrixErrorMessage } from "./errors-C47hvAF8.js";
|
|
8
|
-
import { D as parsePollStartContent, S as formatPollAsText, T as isPollStartType,
|
|
9
|
-
import { t as createMatrixThreadBindingManager } from "./thread-bindings-
|
|
8
|
+
import { D as parsePollStartContent, S as formatPollAsText, T as isPollStartType, g as MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY, h as readJoinedMatrixMembers, m as isStrictDirectMembership, p as hasDirectMatrixMemberFlag, r as sendMessageMatrix, u as promoteMatrixDirectRoomCandidate, v as chunkMatrixText, w as isPollEventType } from "./send-C9Ssx-iV.js";
|
|
9
|
+
import { t as createMatrixThreadBindingManager } from "./thread-bindings-D3RfVgAF.js";
|
|
10
10
|
import { i as createMatrixInboundEventDeduper } from "./inbound-dedupe-CaBLblef.js";
|
|
11
|
-
import { a as resolveMatrixMessageAttachment, c as fetchMatrixPollSnapshot, i as isLikelyBareFilename, n as formatMatrixMediaUnavailableText, o as resolveMatrixMessageBody, r as formatMatrixMessageText, t as formatMatrixMediaTooLargeText } from "./media-text-
|
|
11
|
+
import { a as resolveMatrixMessageAttachment, c as fetchMatrixPollSnapshot, i as isLikelyBareFilename, n as formatMatrixMediaUnavailableText, o as resolveMatrixMessageBody, r as formatMatrixMessageText, t as formatMatrixMediaTooLargeText } from "./media-text-DlQC_k_K.js";
|
|
12
12
|
import { n as setActiveMatrixClient } from "./active-client-DXvz2gCo.js";
|
|
13
13
|
import { t as isBunRuntime } from "./runtime-BefyhPWv.js";
|
|
14
|
-
import { d as backfillMatrixAuthDeviceIdAfterStartup, f as resolveMatrixAuth, i as resolveSharedMatrixClient, l as isMatrixStartupAbortError, n as releaseSharedClientInstance, p as resolveMatrixAuthContext, u as throwIfMatrixStartupAborted } from "./shared-
|
|
15
|
-
import "./client-
|
|
16
|
-
import { i as isMatrixMediaSizeLimitError, r as MatrixMediaSizeLimitError } from "./http-client-
|
|
14
|
+
import { d as backfillMatrixAuthDeviceIdAfterStartup, f as resolveMatrixAuth, i as resolveSharedMatrixClient, l as isMatrixStartupAbortError, n as releaseSharedClientInstance, p as resolveMatrixAuthContext, u as throwIfMatrixStartupAborted } from "./shared-CUH5otj8.js";
|
|
15
|
+
import "./client-BFRg0kd4.js";
|
|
16
|
+
import { i as isMatrixMediaSizeLimitError, r as MatrixMediaSizeLimitError } from "./http-client-CycYxU9-.js";
|
|
17
17
|
import { d as resolveAllowlistProviderRuntimeGroupPolicy, f as resolveDefaultGroupPolicy, g as warnMissingProviderGroupPolicyFallbackOnce, h as resolveThreadBindingMaxAgeMsForChannel, m as resolveThreadBindingIdleTimeoutMsForChannel, t as GROUP_POLICY_BLOCKED_LABEL } from "./runtime-api-D1xI_cvh.js";
|
|
18
|
-
import { t as resolveMatrixTargets } from "./resolve-targets-
|
|
18
|
+
import { t as resolveMatrixTargets } from "./resolve-targets-nNMcrV3W.js";
|
|
19
19
|
import { t as formatMatrixEncryptedEventDisabledWarning } from "./encryption-guidance-CbEFe38F.js";
|
|
20
20
|
import { n as isMatrixReadySyncState, r as isMatrixTerminalSyncState, t as isMatrixDisconnectedSyncState } from "./sync-state-CWbp0QSY.js";
|
|
21
|
+
import { n as setBoundedMap, t as createMatrixRoomInfoResolver } from "./room-info-CAFZ3Z9J.js";
|
|
21
22
|
import { a as EventType, i as resolveMatrixThreadRouting, n as resolveMatrixReplyToEventId, o as RelationType, r as resolveMatrixThreadRootId, t as resolveMatrixInboundRoute } from "./route-DY6at4qJ.js";
|
|
22
|
-
import { t as createMatrixRoomInfoResolver } from "./room-info-B6vKD5DF.js";
|
|
23
23
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
24
24
|
import { buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, createPreviewMessageReceipt, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftLine, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, waitUntilAbort } from "openclaw/plugin-sdk/channel-outbound";
|
|
25
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringifiedEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
25
|
+
import { normalizeLowercaseStringOrEmpty, normalizeNullableString, normalizeOptionalString, normalizeStringifiedEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
26
26
|
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
|
|
27
27
|
import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
28
28
|
import { buildChannelInboundEventContext, createChannelInboundEnvelopeBuilder, hasFinalInboundReplyDispatch, resolveInboundMentionDecision, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
29
29
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
30
30
|
import { isFutureDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs, resolveOptionalIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
31
|
+
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
31
32
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
32
33
|
import { format } from "node:util";
|
|
33
34
|
import { escapeRegExp, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
@@ -36,13 +37,12 @@ import { resolveAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
|
36
37
|
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
|
|
37
38
|
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
38
39
|
import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
39
|
-
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
40
40
|
import { evaluateSupplementalContextVisibility, resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
41
|
+
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
42
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
41
43
|
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
42
44
|
import { buildInboundHistoryFromEntries } from "openclaw/plugin-sdk/reply-history";
|
|
43
|
-
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
44
45
|
import { decodeHtmlEntities } from "openclaw/plugin-sdk/html-entity-runtime";
|
|
45
|
-
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
46
46
|
import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
47
47
|
//#region extensions/matrix/src/matrix/monitor/auto-join.ts
|
|
48
48
|
function registerMatrixAutoJoin(params) {
|
|
@@ -466,15 +466,8 @@ async function resolveMatrixMonitorConfig(params) {
|
|
|
466
466
|
//#region extensions/matrix/src/matrix/monitor/direct.ts
|
|
467
467
|
const DM_CACHE_TTL_MS = 3e4;
|
|
468
468
|
const RECENT_INVITE_TTL_MS = 3e4;
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
function rememberBounded(map, key, value, maxSize = MAX_TRACKED_DM_ROOMS) {
|
|
472
|
-
map.set(key, value);
|
|
473
|
-
if (map.size > maxSize) {
|
|
474
|
-
const oldest = map.keys().next().value;
|
|
475
|
-
if (typeof oldest === "string") map.delete(oldest);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
469
|
+
const MAX_DM_ROOMS = 1024;
|
|
470
|
+
const MAX_DM_MEMBER_FLAGS = 2048;
|
|
478
471
|
function createDirectRoomTracker(client, opts = {}) {
|
|
479
472
|
const log = opts.log ?? (() => {});
|
|
480
473
|
let lastDmUpdateMs = 0;
|
|
@@ -506,10 +499,10 @@ function createDirectRoomTracker(client, opts = {}) {
|
|
|
506
499
|
try {
|
|
507
500
|
const normalized = await readJoinedMatrixMembers(client, roomId);
|
|
508
501
|
if (!normalized) throw new Error("membership unavailable");
|
|
509
|
-
|
|
502
|
+
setBoundedMap(joinedMembersCache, roomId, {
|
|
510
503
|
members: normalized,
|
|
511
504
|
ts: now
|
|
512
|
-
});
|
|
505
|
+
}, MAX_DM_ROOMS);
|
|
513
506
|
return normalized;
|
|
514
507
|
} catch (err) {
|
|
515
508
|
log(`matrix: dm member lookup failed room=${roomId} (${String(err)})`);
|
|
@@ -524,10 +517,10 @@ function createDirectRoomTracker(client, opts = {}) {
|
|
|
524
517
|
const now = Date.now();
|
|
525
518
|
if (cached && now - cached.ts < DM_CACHE_TTL_MS) return cached.isDirect;
|
|
526
519
|
const isDirect = await hasDirectMatrixMemberFlag(client, roomId, normalizedUserId);
|
|
527
|
-
|
|
520
|
+
setBoundedMap(directMemberFlagCache, cacheKey, {
|
|
528
521
|
isDirect,
|
|
529
522
|
ts: now
|
|
530
|
-
},
|
|
523
|
+
}, MAX_DM_MEMBER_FLAGS);
|
|
531
524
|
return isDirect;
|
|
532
525
|
};
|
|
533
526
|
const hasRecentInviteCandidate = (roomId, remoteUserId) => {
|
|
@@ -581,7 +574,7 @@ function createDirectRoomTracker(client, opts = {}) {
|
|
|
581
574
|
const rememberLocallyPromotedDirectRoom = (roomId, remoteUserId) => {
|
|
582
575
|
const normalizedRemoteUserId = remoteUserId.trim();
|
|
583
576
|
if (!normalizedRemoteUserId) return;
|
|
584
|
-
|
|
577
|
+
setBoundedMap(locallyPromotedDirectRooms, roomId, { remoteUserId: normalizedRemoteUserId }, MAX_DM_ROOMS);
|
|
585
578
|
};
|
|
586
579
|
return {
|
|
587
580
|
invalidateRoom: (roomId) => {
|
|
@@ -593,10 +586,10 @@ function createDirectRoomTracker(client, opts = {}) {
|
|
|
593
586
|
rememberInvite: (roomId, remoteUserId) => {
|
|
594
587
|
const normalizedRemoteUserId = remoteUserId.trim();
|
|
595
588
|
if (!normalizedRemoteUserId) return;
|
|
596
|
-
|
|
589
|
+
setBoundedMap(recentInviteCandidates, roomId, {
|
|
597
590
|
remoteUserId: normalizedRemoteUserId,
|
|
598
591
|
ts: Date.now()
|
|
599
|
-
});
|
|
592
|
+
}, MAX_DM_ROOMS);
|
|
600
593
|
log(`matrix: remembered invite candidate room=${roomId} sender=${normalizedRemoteUserId}`);
|
|
601
594
|
},
|
|
602
595
|
isDirectMessage: async (params) => {
|
|
@@ -793,21 +786,21 @@ const VERIFICATION_NOTICE_PREFIXES = [
|
|
|
793
786
|
"Matrix verification cancelled by ",
|
|
794
787
|
"Matrix verification SAS with "
|
|
795
788
|
];
|
|
796
|
-
function trimMaybeString
|
|
789
|
+
function trimMaybeString(input) {
|
|
797
790
|
return normalizeOptionalString(input) ?? "";
|
|
798
791
|
}
|
|
799
792
|
function isMatrixVerificationEventType(type) {
|
|
800
|
-
return trimMaybeString
|
|
793
|
+
return trimMaybeString(type).startsWith(VERIFICATION_EVENT_PREFIX);
|
|
801
794
|
}
|
|
802
795
|
function isMatrixVerificationRequestMsgType(msgtype) {
|
|
803
|
-
return trimMaybeString
|
|
796
|
+
return trimMaybeString(msgtype) === VERIFICATION_REQUEST_MSGTYPE;
|
|
804
797
|
}
|
|
805
798
|
function isMatrixVerificationNoticeBody(body) {
|
|
806
|
-
const text = trimMaybeString
|
|
799
|
+
const text = trimMaybeString(body);
|
|
807
800
|
return VERIFICATION_NOTICE_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
808
801
|
}
|
|
809
802
|
function isMatrixVerificationRoomMessage(content) {
|
|
810
|
-
return isMatrixVerificationRequestMsgType(content.msgtype) || trimMaybeString
|
|
803
|
+
return isMatrixVerificationRequestMsgType(content.msgtype) || trimMaybeString(content.msgtype) === "m.notice" && isMatrixVerificationNoticeBody(content.body);
|
|
811
804
|
}
|
|
812
805
|
const matrixVerificationConstants = {
|
|
813
806
|
eventPrefix: VERIFICATION_EVENT_PREFIX,
|
|
@@ -818,27 +811,22 @@ const matrixVerificationConstants = {
|
|
|
818
811
|
const MAX_TRACKED_VERIFICATION_EVENTS = 1024;
|
|
819
812
|
const SAS_NOTICE_RETRY_DELAY_MS = 750;
|
|
820
813
|
const VERIFICATION_EVENT_STARTUP_GRACE_MS = 3e4;
|
|
821
|
-
const loadMatrixDirectRoomDeps = createLazyRuntimeModule(() => Promise.all([import("./send-
|
|
814
|
+
const loadMatrixDirectRoomDeps = createLazyRuntimeModule(() => Promise.all([import("./send-C9Ssx-iV.js").then((n) => n.l), import("./send-C9Ssx-iV.js").then((n) => n.f)]).then(([directManagementModule, directRoomModule]) => ({
|
|
822
815
|
inspectMatrixDirectRooms: directManagementModule.inspectMatrixDirectRooms,
|
|
823
816
|
isStrictDirectRoom: directRoomModule.isStrictDirectRoom
|
|
824
817
|
})));
|
|
825
|
-
function trimMaybeString(input) {
|
|
826
|
-
if (typeof input !== "string") return null;
|
|
827
|
-
const trimmed = input.trim();
|
|
828
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
829
|
-
}
|
|
830
818
|
function readVerificationSignal(event) {
|
|
831
|
-
const type =
|
|
819
|
+
const type = normalizeNullableString(event?.type) ?? "";
|
|
832
820
|
const content = event?.content ?? {};
|
|
833
|
-
const msgtype =
|
|
834
|
-
const relatedEventId =
|
|
835
|
-
const transactionId =
|
|
821
|
+
const msgtype = normalizeNullableString(content.msgtype) ?? "";
|
|
822
|
+
const relatedEventId = normalizeNullableString(content["m.relates_to"]?.event_id);
|
|
823
|
+
const transactionId = normalizeNullableString(content.transaction_id);
|
|
836
824
|
if (type === EventType.RoomMessage && isMatrixVerificationRequestMsgType(msgtype)) return {
|
|
837
825
|
stage: "request",
|
|
838
|
-
flowId:
|
|
826
|
+
flowId: normalizeNullableString(event.event_id) ?? transactionId ?? relatedEventId
|
|
839
827
|
};
|
|
840
828
|
if (!isMatrixVerificationEventType(type)) return null;
|
|
841
|
-
const flowId = transactionId ?? relatedEventId ??
|
|
829
|
+
const flowId = transactionId ?? relatedEventId ?? normalizeNullableString(event.event_id);
|
|
842
830
|
if (type === `${matrixVerificationConstants.eventPrefix}request`) return {
|
|
843
831
|
stage: "request",
|
|
844
832
|
flowId
|
|
@@ -873,8 +861,8 @@ function formatVerificationStageNotice(params) {
|
|
|
873
861
|
case "start": return `Matrix verification started with ${senderId}.`;
|
|
874
862
|
case "done": return `Matrix verification completed with ${senderId}.`;
|
|
875
863
|
case "cancel": {
|
|
876
|
-
const code =
|
|
877
|
-
const reason =
|
|
864
|
+
const code = normalizeNullableString(content.code);
|
|
865
|
+
const reason = normalizeNullableString(content.reason);
|
|
878
866
|
if (code && reason) return `Matrix verification cancelled by ${senderId} (${code}: ${reason}).`;
|
|
879
867
|
if (reason) return `Matrix verification cancelled by ${senderId} (${reason}).`;
|
|
880
868
|
return `Matrix verification cancelled by ${senderId}.`;
|
|
@@ -885,7 +873,7 @@ function formatVerificationStageNotice(params) {
|
|
|
885
873
|
function formatVerificationSasNotice(summary) {
|
|
886
874
|
const sas = summary.sas;
|
|
887
875
|
if (!sas) return null;
|
|
888
|
-
const emojiLine = Array.isArray(sas.emoji) && sas.emoji.length > 0 ? `SAS emoji: ${sas.emoji.map(([emoji, name]) => `${
|
|
876
|
+
const emojiLine = Array.isArray(sas.emoji) && sas.emoji.length > 0 ? `SAS emoji: ${sas.emoji.map(([emoji, name]) => `${normalizeNullableString(emoji) ?? "?"} ${normalizeNullableString(name) ?? "?"}`).join(" | ")}` : null;
|
|
889
877
|
const decimalLine = Array.isArray(sas.decimal) && sas.decimal.length === 3 ? `SAS decimal: ${sas.decimal.join(" ")}` : null;
|
|
890
878
|
if (!emojiLine && !decimalLine) return null;
|
|
891
879
|
const lines = [`Matrix verification SAS with ${summary.otherUserId}:`];
|
|
@@ -899,7 +887,7 @@ function resolveVerificationFlowCandidates(params) {
|
|
|
899
887
|
const content = event.content;
|
|
900
888
|
const candidates = /* @__PURE__ */ new Set();
|
|
901
889
|
const add = (value) => {
|
|
902
|
-
const normalized =
|
|
890
|
+
const normalized = normalizeNullableString(value);
|
|
903
891
|
if (normalized) candidates.add(normalized);
|
|
904
892
|
};
|
|
905
893
|
add(flowId);
|
|
@@ -933,7 +921,7 @@ async function resolveVerificationSummaryForSignal(client, params) {
|
|
|
933
921
|
const byTransactionId = list.find((entry) => candidates.some((candidate) => entry.transactionId === candidate));
|
|
934
922
|
if (byTransactionId) return byTransactionId;
|
|
935
923
|
const { inspectMatrixDirectRooms, isStrictDirectRoom } = await loadMatrixDirectRoomDeps();
|
|
936
|
-
const activeRoomId =
|
|
924
|
+
const activeRoomId = normalizeNullableString((await inspectMatrixDirectRooms({
|
|
937
925
|
client,
|
|
938
926
|
remoteUserId: params.senderId
|
|
939
927
|
}).catch(() => null))?.activeRoomId);
|
|
@@ -946,7 +934,7 @@ async function resolveVerificationSummaryForSignal(client, params) {
|
|
|
946
934
|
})) return null;
|
|
947
935
|
const activeByUser = list.filter((entry) => entry.otherUserId === params.senderId && isActiveVerificationSummary(entry)).toSorted((a, b) => resolveSummaryRecency(b) - resolveSummaryRecency(a));
|
|
948
936
|
const activeInRoom = activeByUser.filter((entry) => {
|
|
949
|
-
return
|
|
937
|
+
return normalizeNullableString(entry.roomId) === params.roomId;
|
|
950
938
|
});
|
|
951
939
|
if (activeInRoom.length > 0) return activeInRoom[0] ?? null;
|
|
952
940
|
return activeByUser[0] ?? null;
|
|
@@ -977,7 +965,7 @@ function trackBounded(set, value) {
|
|
|
977
965
|
return true;
|
|
978
966
|
}
|
|
979
967
|
async function sendVerificationNotice(params) {
|
|
980
|
-
const roomId =
|
|
968
|
+
const roomId = normalizeNullableString(params.roomId);
|
|
981
969
|
if (!roomId) return;
|
|
982
970
|
try {
|
|
983
971
|
await params.client.sendMessage(roomId, {
|
|
@@ -1016,7 +1004,7 @@ function createMatrixVerificationEventRouter(params) {
|
|
|
1016
1004
|
const verificationUserRooms = /* @__PURE__ */ new Map();
|
|
1017
1005
|
async function resolveActiveDirectRoomId(remoteUserId) {
|
|
1018
1006
|
const { inspectMatrixDirectRooms } = await loadMatrixDirectRoomDeps();
|
|
1019
|
-
return
|
|
1007
|
+
return normalizeNullableString((await inspectMatrixDirectRooms({
|
|
1020
1008
|
client: params.client,
|
|
1021
1009
|
remoteUserId
|
|
1022
1010
|
}).catch(() => null))?.activeRoomId);
|
|
@@ -1039,8 +1027,8 @@ function createMatrixVerificationEventRouter(params) {
|
|
|
1039
1027
|
}
|
|
1040
1028
|
}
|
|
1041
1029
|
function rememberVerificationUserRoom(remoteUserId, roomId) {
|
|
1042
|
-
const normalizedUserId =
|
|
1043
|
-
const normalizedRoomId =
|
|
1030
|
+
const normalizedUserId = normalizeNullableString(remoteUserId);
|
|
1031
|
+
const normalizedRoomId = normalizeNullableString(roomId);
|
|
1044
1032
|
if (!normalizedUserId || !normalizedRoomId) return;
|
|
1045
1033
|
verificationUserRooms.delete(normalizedUserId);
|
|
1046
1034
|
verificationUserRooms.set(normalizedUserId, normalizedRoomId);
|
|
@@ -1050,11 +1038,11 @@ function createMatrixVerificationEventRouter(params) {
|
|
|
1050
1038
|
}
|
|
1051
1039
|
}
|
|
1052
1040
|
async function resolveSummaryRoomId(summary) {
|
|
1053
|
-
const mappedRoomId =
|
|
1041
|
+
const mappedRoomId = normalizeNullableString(summary.roomId) ?? normalizeNullableString(summary.transactionId ? verificationFlowRooms.get(summary.transactionId) : null) ?? normalizeNullableString(verificationFlowRooms.get(summary.id));
|
|
1054
1042
|
if (mappedRoomId) return mappedRoomId;
|
|
1055
|
-
const remoteUserId =
|
|
1043
|
+
const remoteUserId = normalizeNullableString(summary.otherUserId);
|
|
1056
1044
|
if (!remoteUserId) return null;
|
|
1057
|
-
const recentRoomId =
|
|
1045
|
+
const recentRoomId = normalizeNullableString(verificationUserRooms.get(remoteUserId));
|
|
1058
1046
|
const activeRoomId = await resolveActiveDirectRoomId(remoteUserId);
|
|
1059
1047
|
if (recentRoomId && activeRoomId && recentRoomId === activeRoomId) return recentRoomId;
|
|
1060
1048
|
if (activeRoomId) return activeRoomId;
|
|
@@ -1096,7 +1084,7 @@ function createMatrixVerificationEventRouter(params) {
|
|
|
1096
1084
|
});
|
|
1097
1085
|
}
|
|
1098
1086
|
function routeVerificationEvent(roomId, event) {
|
|
1099
|
-
const senderId =
|
|
1087
|
+
const senderId = normalizeNullableString(event?.sender);
|
|
1100
1088
|
if (!senderId) return false;
|
|
1101
1089
|
const signal = readVerificationSignal(event);
|
|
1102
1090
|
if (!signal) return false;
|
|
@@ -1107,7 +1095,7 @@ function createMatrixVerificationEventRouter(params) {
|
|
|
1107
1095
|
return;
|
|
1108
1096
|
}
|
|
1109
1097
|
const flowId = signal.flowId;
|
|
1110
|
-
const sourceFingerprint =
|
|
1098
|
+
const sourceFingerprint = normalizeNullableString(event?.event_id) ?? `${senderId}:${event.type}:${flowId ?? "none"}`;
|
|
1111
1099
|
if (!await (await loadMatrixDirectRoomDeps()).isStrictDirectRoom({
|
|
1112
1100
|
client: params.client,
|
|
1113
1101
|
roomId,
|
|
@@ -1413,246 +1401,6 @@ function resolveMatrixAckReactionConfig(params) {
|
|
|
1413
1401
|
};
|
|
1414
1402
|
}
|
|
1415
1403
|
//#endregion
|
|
1416
|
-
//#region extensions/matrix/src/matrix/monitor/location.ts
|
|
1417
|
-
function decodeGeoUriParamValue(value) {
|
|
1418
|
-
try {
|
|
1419
|
-
return decodeURIComponent(value);
|
|
1420
|
-
} catch {
|
|
1421
|
-
return value;
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
function parseGeoUri(value) {
|
|
1425
|
-
const trimmed = value.trim();
|
|
1426
|
-
if (!trimmed) return null;
|
|
1427
|
-
if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("geo:")) return null;
|
|
1428
|
-
const [coordsPart, ...paramParts] = trimmed.slice(4).split(";");
|
|
1429
|
-
if (!coordsPart) return null;
|
|
1430
|
-
const coords = coordsPart.split(",");
|
|
1431
|
-
if (coords.length < 2) return null;
|
|
1432
|
-
const latitude = parseStrictFiniteNumber(coords[0] ?? "");
|
|
1433
|
-
const longitude = parseStrictFiniteNumber(coords[1] ?? "");
|
|
1434
|
-
if (latitude === void 0 || longitude === void 0) return null;
|
|
1435
|
-
const params = /* @__PURE__ */ new Map();
|
|
1436
|
-
for (const part of paramParts) {
|
|
1437
|
-
const segment = part.trim();
|
|
1438
|
-
if (!segment) continue;
|
|
1439
|
-
const eqIndex = segment.indexOf("=");
|
|
1440
|
-
const rawKey = eqIndex === -1 ? segment : segment.slice(0, eqIndex);
|
|
1441
|
-
const rawValue = eqIndex === -1 ? "" : segment.slice(eqIndex + 1);
|
|
1442
|
-
const key = normalizeLowercaseStringOrEmpty(rawKey);
|
|
1443
|
-
if (!key) continue;
|
|
1444
|
-
const valuePart = rawValue.trim();
|
|
1445
|
-
params.set(key, valuePart ? decodeGeoUriParamValue(valuePart) : "");
|
|
1446
|
-
}
|
|
1447
|
-
const accuracyRaw = params.get("u");
|
|
1448
|
-
return {
|
|
1449
|
-
latitude,
|
|
1450
|
-
longitude,
|
|
1451
|
-
accuracy: accuracyRaw ? parseStrictFiniteNumber(accuracyRaw) : void 0
|
|
1452
|
-
};
|
|
1453
|
-
}
|
|
1454
|
-
function resolveMatrixLocation(params) {
|
|
1455
|
-
const { eventType, content } = params;
|
|
1456
|
-
if (!(eventType === EventType.Location || eventType === EventType.RoomMessage && content.msgtype === EventType.Location)) return null;
|
|
1457
|
-
const geoUri = normalizeOptionalString(content.geo_uri) ?? "";
|
|
1458
|
-
if (!geoUri) return null;
|
|
1459
|
-
const parsed = parseGeoUri(geoUri);
|
|
1460
|
-
if (!parsed) return null;
|
|
1461
|
-
const caption = normalizeOptionalString(content.body) ?? "";
|
|
1462
|
-
const location = {
|
|
1463
|
-
latitude: parsed.latitude,
|
|
1464
|
-
longitude: parsed.longitude,
|
|
1465
|
-
accuracy: parsed.accuracy,
|
|
1466
|
-
caption: caption || void 0,
|
|
1467
|
-
source: "pin",
|
|
1468
|
-
isLive: false
|
|
1469
|
-
};
|
|
1470
|
-
return {
|
|
1471
|
-
text: formatLocationText(location),
|
|
1472
|
-
context: toLocationContext(location)
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
//#endregion
|
|
1476
|
-
//#region extensions/matrix/src/matrix/monitor/media.ts
|
|
1477
|
-
const MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS = 3e4;
|
|
1478
|
-
async function fetchMatrixMediaBuffer(params) {
|
|
1479
|
-
try {
|
|
1480
|
-
return { buffer: await params.client.downloadContent(params.mxcUrl, {
|
|
1481
|
-
maxBytes: params.maxBytes,
|
|
1482
|
-
readIdleTimeoutMs: MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS
|
|
1483
|
-
}) };
|
|
1484
|
-
} catch (err) {
|
|
1485
|
-
if (isMatrixMediaSizeLimitError(err)) throw err;
|
|
1486
|
-
throw new Error(`Matrix media download failed: ${String(err)}`, { cause: err });
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
/**
|
|
1490
|
-
* Download and decrypt encrypted media from a Matrix room.
|
|
1491
|
-
* Uses the Matrix crypto adapter's decryptMedia helper.
|
|
1492
|
-
*/
|
|
1493
|
-
async function fetchEncryptedMediaBuffer(params) {
|
|
1494
|
-
if (!params.client.crypto) throw new Error("Cannot decrypt media: crypto not enabled");
|
|
1495
|
-
const decrypted = await params.client.crypto.decryptMedia(params.file, {
|
|
1496
|
-
maxBytes: params.maxBytes,
|
|
1497
|
-
readIdleTimeoutMs: MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS
|
|
1498
|
-
});
|
|
1499
|
-
if (decrypted.byteLength > params.maxBytes) throw new MatrixMediaSizeLimitError();
|
|
1500
|
-
return { buffer: decrypted };
|
|
1501
|
-
}
|
|
1502
|
-
async function downloadMatrixMedia(params) {
|
|
1503
|
-
let fetched;
|
|
1504
|
-
if (typeof params.sizeBytes === "number" && params.sizeBytes > params.maxBytes) throw new MatrixMediaSizeLimitError();
|
|
1505
|
-
if (params.file) fetched = await fetchEncryptedMediaBuffer({
|
|
1506
|
-
client: params.client,
|
|
1507
|
-
file: params.file,
|
|
1508
|
-
maxBytes: params.maxBytes
|
|
1509
|
-
});
|
|
1510
|
-
else fetched = await fetchMatrixMediaBuffer({
|
|
1511
|
-
client: params.client,
|
|
1512
|
-
mxcUrl: params.mxcUrl,
|
|
1513
|
-
maxBytes: params.maxBytes
|
|
1514
|
-
});
|
|
1515
|
-
if (!fetched) return null;
|
|
1516
|
-
const headerType = params.contentType ?? void 0;
|
|
1517
|
-
const saved = await getMatrixRuntime().channel.media.saveMediaBuffer(fetched.buffer, headerType, "inbound", params.maxBytes, params.originalFilename);
|
|
1518
|
-
return {
|
|
1519
|
-
path: saved.path,
|
|
1520
|
-
contentType: saved.contentType,
|
|
1521
|
-
placeholder: "[matrix media]"
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
//#endregion
|
|
1525
|
-
//#region extensions/matrix/src/matrix/monitor/mentions.ts
|
|
1526
|
-
const MATRIX_HTML_ENTITY_RE = /&(?:#x?[0-9a-f]+|amp|apos|gt|lt|nbsp|quot);/gi;
|
|
1527
|
-
function decodeVisibleHtmlEntities(value) {
|
|
1528
|
-
return value.replace(MATRIX_HTML_ENTITY_RE, (entity) => {
|
|
1529
|
-
const decoded = decodeHtmlEntities(entity.startsWith("&#") ? entity : entity.toLowerCase());
|
|
1530
|
-
return decoded === "\xA0" ? " " : decoded;
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
function normalizeVisibleMentionText(value) {
|
|
1534
|
-
return normalizeLowercaseStringOrEmpty(decodeVisibleHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")).replace(/\s+/g, " "));
|
|
1535
|
-
}
|
|
1536
|
-
function extractVisibleMentionText(value) {
|
|
1537
|
-
return normalizeVisibleMentionText(value ?? "");
|
|
1538
|
-
}
|
|
1539
|
-
function resolveMatrixUserLocalpart(userId) {
|
|
1540
|
-
const trimmed = userId.trim();
|
|
1541
|
-
if (!trimmed.startsWith("@")) return null;
|
|
1542
|
-
const colonIndex = trimmed.indexOf(":");
|
|
1543
|
-
if (colonIndex <= 1) return null;
|
|
1544
|
-
return trimmed.slice(1, colonIndex).trim() || null;
|
|
1545
|
-
}
|
|
1546
|
-
function resolveMatrixMentionPrefixCandidates(params) {
|
|
1547
|
-
const candidates = [];
|
|
1548
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1549
|
-
const append = (candidate) => {
|
|
1550
|
-
const trimmed = candidate?.trim();
|
|
1551
|
-
if (!trimmed) return;
|
|
1552
|
-
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
|
|
1553
|
-
if (seen.has(normalized)) return;
|
|
1554
|
-
seen.add(normalized);
|
|
1555
|
-
candidates.push(trimmed);
|
|
1556
|
-
};
|
|
1557
|
-
append(params.userId);
|
|
1558
|
-
const localpart = params.userId ? resolveMatrixUserLocalpart(params.userId) : null;
|
|
1559
|
-
append(localpart ? `@${localpart}` : null);
|
|
1560
|
-
append(params.displayName);
|
|
1561
|
-
append(params.displayName ? `@${params.displayName}` : null);
|
|
1562
|
-
append(params.displayName ? `@[${params.displayName}]` : null);
|
|
1563
|
-
return candidates;
|
|
1564
|
-
}
|
|
1565
|
-
function stripMatchedMatrixMentionPrefix(text, pattern) {
|
|
1566
|
-
const match = text.match(pattern);
|
|
1567
|
-
if (!match) return null;
|
|
1568
|
-
return text.slice(match[0].length).trimStart();
|
|
1569
|
-
}
|
|
1570
|
-
function stripNativeMatrixMentionPrefix(text, candidate) {
|
|
1571
|
-
return stripMatchedMatrixMentionPrefix(text, new RegExp(`^\\s*${escapeRegExp(candidate)}(?:\\s*[:,])?(?:\\s+|$)`, "i"));
|
|
1572
|
-
}
|
|
1573
|
-
function stripRegexMatrixMentionPrefix(text, pattern) {
|
|
1574
|
-
const flags = pattern.flags.replace(/[gy]/g, "");
|
|
1575
|
-
return stripMatchedMatrixMentionPrefix(text, new RegExp(`^\\s*(?:${pattern.source})(?:\\s*[:,])?(?:\\s+|$)`, flags));
|
|
1576
|
-
}
|
|
1577
|
-
function stripMatrixMentionPrefix(params) {
|
|
1578
|
-
const text = params.text;
|
|
1579
|
-
if (!text) return text;
|
|
1580
|
-
for (const candidate of resolveMatrixMentionPrefixCandidates(params)) {
|
|
1581
|
-
const stripped = stripNativeMatrixMentionPrefix(text, candidate);
|
|
1582
|
-
if (stripped !== null) return stripped;
|
|
1583
|
-
}
|
|
1584
|
-
for (const pattern of params.mentionRegexes ?? []) {
|
|
1585
|
-
const stripped = stripRegexMatrixMentionPrefix(text, pattern);
|
|
1586
|
-
if (stripped !== null) return stripped;
|
|
1587
|
-
}
|
|
1588
|
-
return text;
|
|
1589
|
-
}
|
|
1590
|
-
function isVisibleMentionLabel(params) {
|
|
1591
|
-
const cleaned = extractVisibleMentionText(params.text);
|
|
1592
|
-
if (!cleaned) return false;
|
|
1593
|
-
if (params.mentionRegexes.some((pattern) => pattern.test(cleaned))) return true;
|
|
1594
|
-
const localpart = resolveMatrixUserLocalpart(params.userId);
|
|
1595
|
-
return [
|
|
1596
|
-
extractVisibleMentionText(params.userId),
|
|
1597
|
-
localpart ? extractVisibleMentionText(localpart) : null,
|
|
1598
|
-
localpart ? extractVisibleMentionText(`@${localpart}`) : null,
|
|
1599
|
-
params.displayName ? extractVisibleMentionText(params.displayName) : null,
|
|
1600
|
-
params.displayName ? extractVisibleMentionText(`@${params.displayName}`) : null,
|
|
1601
|
-
params.displayName ? extractVisibleMentionText(`@[${params.displayName}]`) : null
|
|
1602
|
-
].filter((value) => Boolean(value)).includes(cleaned);
|
|
1603
|
-
}
|
|
1604
|
-
function hasVisibleRoomMention(value) {
|
|
1605
|
-
const cleaned = extractVisibleMentionText(value);
|
|
1606
|
-
return /(^|[^a-z0-9_])@room\b/i.test(cleaned);
|
|
1607
|
-
}
|
|
1608
|
-
/**
|
|
1609
|
-
* Check if formatted_body contains a matrix.to link whose visible label still
|
|
1610
|
-
* looks like a real mention for the given user. Do not trust href alone, since
|
|
1611
|
-
* senders can hide arbitrary matrix.to links behind unrelated link text.
|
|
1612
|
-
* Many Matrix clients (including Element) use HTML links in formatted_body instead of
|
|
1613
|
-
* or in addition to the m.mentions field.
|
|
1614
|
-
*/
|
|
1615
|
-
function checkFormattedBodyMention(params) {
|
|
1616
|
-
if (!params.formattedBody || !params.userId) return false;
|
|
1617
|
-
for (const match of params.formattedBody.matchAll(/<a\b[^>]*href=(["'])(https:\/\/matrix\.to\/#[^"']+)\1[^>]*>(.*?)<\/a>/gis)) {
|
|
1618
|
-
const href = match[2];
|
|
1619
|
-
const visibleLabel = match[3] ?? "";
|
|
1620
|
-
if (!href) continue;
|
|
1621
|
-
try {
|
|
1622
|
-
const parsed = new URL(href);
|
|
1623
|
-
if (decodeURIComponent(parsed.hash.replace(/^#\/?/, "").trim()) !== params.userId.trim()) continue;
|
|
1624
|
-
if (isVisibleMentionLabel({
|
|
1625
|
-
text: visibleLabel,
|
|
1626
|
-
userId: params.userId,
|
|
1627
|
-
mentionRegexes: params.mentionRegexes,
|
|
1628
|
-
displayName: params.displayName
|
|
1629
|
-
})) return true;
|
|
1630
|
-
} catch {
|
|
1631
|
-
continue;
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return false;
|
|
1635
|
-
}
|
|
1636
|
-
function resolveMentions(params) {
|
|
1637
|
-
const mentions = params.content["m.mentions"];
|
|
1638
|
-
const mentionedUsers = Array.isArray(mentions?.user_ids) ? new Set(mentions.user_ids) : /* @__PURE__ */ new Set();
|
|
1639
|
-
const textMentioned = getMatrixRuntime().channel.mentions.matchesMentionPatterns(params.text ?? "", params.mentionRegexes);
|
|
1640
|
-
const visibleRoomMention = hasVisibleRoomMention(params.text) || hasVisibleRoomMention(params.content.formatted_body);
|
|
1641
|
-
const mentionedInFormattedBody = params.userId ? checkFormattedBodyMention({
|
|
1642
|
-
formattedBody: params.content.formatted_body,
|
|
1643
|
-
userId: params.userId,
|
|
1644
|
-
displayName: params.displayName,
|
|
1645
|
-
mentionRegexes: params.mentionRegexes
|
|
1646
|
-
}) : false;
|
|
1647
|
-
const metadataBackedUserMention = Boolean(params.userId && mentionedUsers.has(params.userId) && (mentionedInFormattedBody || textMentioned));
|
|
1648
|
-
const metadataBackedRoomMention = Boolean(mentions?.room) && visibleRoomMention;
|
|
1649
|
-
const explicitMention = mentionedInFormattedBody || metadataBackedUserMention || metadataBackedRoomMention;
|
|
1650
|
-
return {
|
|
1651
|
-
wasMentioned: explicitMention || textMentioned || visibleRoomMention,
|
|
1652
|
-
hasExplicitMention: explicitMention
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
//#endregion
|
|
1656
1404
|
//#region extensions/matrix/src/matrix/monitor/preflight-audio.ts
|
|
1657
1405
|
const MATRIX_DEFAULT_ECHO_TRANSCRIPT_FORMAT = "📝 \"{transcript}\"";
|
|
1658
1406
|
const loadMatrixPreflightAudioRuntime = createLazyRuntimeModule(() => import("./preflight-audio.runtime-BZxxQsxY.js"));
|
|
@@ -1691,8 +1439,10 @@ async function resolveMatrixPreflightAudioTranscript(params) {
|
|
|
1691
1439
|
if (params.abortSignal?.aborted) return;
|
|
1692
1440
|
const transcript = await transcribeFirstAudio({
|
|
1693
1441
|
ctx: {
|
|
1694
|
-
|
|
1695
|
-
|
|
1442
|
+
media: [{
|
|
1443
|
+
path: params.mediaPath,
|
|
1444
|
+
contentType: params.mediaContentType
|
|
1445
|
+
}],
|
|
1696
1446
|
Provider: "matrix",
|
|
1697
1447
|
Surface: "matrix",
|
|
1698
1448
|
OriginatingChannel: "matrix",
|
|
@@ -1732,102 +1482,134 @@ async function sendMatrixPreflightAudioTranscriptEcho(params) {
|
|
|
1732
1482
|
}
|
|
1733
1483
|
}
|
|
1734
1484
|
//#endregion
|
|
1735
|
-
//#region extensions/matrix/src/matrix/monitor/
|
|
1736
|
-
const
|
|
1737
|
-
const
|
|
1738
|
-
function
|
|
1739
|
-
if (
|
|
1740
|
-
|
|
1741
|
-
if (
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
const withoutThinkingBlocks = text.replace(THINKING_BLOCK_RE, "");
|
|
1747
|
-
THINKING_TAG_RE.lastIndex = 0;
|
|
1748
|
-
return !withoutThinkingBlocks.replace(THINKING_TAG_RE, "").trim();
|
|
1485
|
+
//#region extensions/matrix/src/matrix/monitor/handler-helpers.ts
|
|
1486
|
+
const MATRIX_TOOL_PROGRESS_MAX_CHARS = 300;
|
|
1487
|
+
const MAX_TRACKED_SHARED_DM_CONTEXT_NOTICES = 512;
|
|
1488
|
+
function resolveMatrixMentionPrecheckText(params) {
|
|
1489
|
+
if (params.locationText?.trim()) return params.locationText.trim();
|
|
1490
|
+
if (typeof params.content.body === "string" && params.content.body.trim()) return params.content.body.trim();
|
|
1491
|
+
if (isPollStartType(params.eventType)) {
|
|
1492
|
+
const parsed = parsePollStartContent(params.content);
|
|
1493
|
+
if (parsed) return formatPollAsText(parsed);
|
|
1494
|
+
}
|
|
1495
|
+
return "";
|
|
1749
1496
|
}
|
|
1750
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1497
|
+
function hasBundledMatrixReplacementRelation(event) {
|
|
1498
|
+
const relations = event.unsigned?.["m.relations"];
|
|
1499
|
+
if (!relations || typeof relations !== "object") return false;
|
|
1500
|
+
return relations[RelationType.Replace] !== void 0;
|
|
1501
|
+
}
|
|
1502
|
+
function resolveMatrixInboundBodyText(params) {
|
|
1503
|
+
if (params.mediaPlaceholder) return params.rawBody || params.mediaPlaceholder;
|
|
1504
|
+
if (!params.mediaDownloadFailed || !params.hadMediaUrl) return params.rawBody;
|
|
1505
|
+
if (params.mediaSizeLimitExceeded) return formatMatrixMediaTooLargeText({
|
|
1506
|
+
body: params.rawBody,
|
|
1507
|
+
filename: params.filename,
|
|
1508
|
+
msgtype: params.msgtype
|
|
1756
1509
|
});
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const
|
|
1768
|
-
if (
|
|
1769
|
-
if (reply?.audioAsVoice) {
|
|
1770
|
-
logVerbose("matrix reply has audioAsVoice without media/text; skipping");
|
|
1771
|
-
continue;
|
|
1772
|
-
}
|
|
1773
|
-
params.runtime.error?.("matrix reply missing text/media");
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1776
|
-
const replyToIdRaw = (reply.replyToId ?? params.replyToId)?.trim();
|
|
1777
|
-
const replyToId = params.threadId ? replyToIdRaw : params.replyToMode === "off" ? void 0 : replyToIdRaw;
|
|
1778
|
-
const rawText = reply.text ?? "";
|
|
1779
|
-
const mediaList = reply.mediaUrls?.length ? reply.mediaUrls : reply.mediaUrl ? [reply.mediaUrl] : [];
|
|
1780
|
-
const shouldIncludeReply = (id) => Boolean(id) && (params.threadId || params.replyToMode === "all" || !hasReplied);
|
|
1781
|
-
const replyToIdForReply = shouldIncludeReply(replyToId) ? replyToId : void 0;
|
|
1782
|
-
if (mediaList.length === 0) {
|
|
1783
|
-
let sentTextChunk = false;
|
|
1784
|
-
const { chunks } = chunkMatrixText(rawText, {
|
|
1785
|
-
cfg: params.cfg,
|
|
1786
|
-
accountId: params.accountId,
|
|
1787
|
-
tableMode
|
|
1788
|
-
});
|
|
1789
|
-
for (const chunk of chunks) {
|
|
1790
|
-
const trimmed = chunk.trim();
|
|
1791
|
-
if (!trimmed) continue;
|
|
1792
|
-
await sendMessageMatrix(params.roomId, trimmed, {
|
|
1793
|
-
client: params.client,
|
|
1794
|
-
cfg: params.cfg,
|
|
1795
|
-
replyToId: replyToIdForReply,
|
|
1796
|
-
threadId: params.threadId,
|
|
1797
|
-
accountId: params.accountId
|
|
1798
|
-
});
|
|
1799
|
-
deliveredAny = true;
|
|
1800
|
-
sentTextChunk = true;
|
|
1801
|
-
}
|
|
1802
|
-
if (replyToIdForReply && !hasReplied && sentTextChunk) hasReplied = true;
|
|
1803
|
-
continue;
|
|
1804
|
-
}
|
|
1805
|
-
let first = true;
|
|
1806
|
-
for (const mediaUrl of mediaList) {
|
|
1807
|
-
const caption = first ? rawText : "";
|
|
1808
|
-
await sendMessageMatrix(params.roomId, caption, {
|
|
1809
|
-
client: params.client,
|
|
1810
|
-
cfg: params.cfg,
|
|
1811
|
-
mediaUrl,
|
|
1812
|
-
mediaLocalRoots: params.mediaLocalRoots,
|
|
1813
|
-
replyToId: replyToIdForReply,
|
|
1814
|
-
threadId: params.threadId,
|
|
1815
|
-
audioAsVoice: reply.audioAsVoice,
|
|
1816
|
-
accountId: params.accountId
|
|
1817
|
-
});
|
|
1818
|
-
deliveredAny = true;
|
|
1819
|
-
first = false;
|
|
1820
|
-
}
|
|
1821
|
-
if (replyToIdForReply && !hasReplied) hasReplied = true;
|
|
1510
|
+
return formatMatrixMediaUnavailableText({
|
|
1511
|
+
body: params.rawBody,
|
|
1512
|
+
filename: params.filename,
|
|
1513
|
+
msgtype: params.msgtype
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
function markTrackedRoomIfFirst(set, roomId) {
|
|
1517
|
+
if (set.has(roomId)) return false;
|
|
1518
|
+
set.add(roomId);
|
|
1519
|
+
if (set.size > MAX_TRACKED_SHARED_DM_CONTEXT_NOTICES) {
|
|
1520
|
+
const oldest = set.keys().next().value;
|
|
1521
|
+
if (typeof oldest === "string") set.delete(oldest);
|
|
1822
1522
|
}
|
|
1823
|
-
return
|
|
1523
|
+
return true;
|
|
1524
|
+
}
|
|
1525
|
+
function resolveMatrixSharedDmContextNotice(params) {
|
|
1526
|
+
if ((params.dmSessionScope ?? "per-user") === "per-room") return null;
|
|
1527
|
+
if (params.sentRooms.has(params.roomId)) return null;
|
|
1528
|
+
try {
|
|
1529
|
+
const currentSession = resolveMatrixStoredSessionMeta(getSessionEntry({
|
|
1530
|
+
storePath: params.storePath,
|
|
1531
|
+
sessionKey: params.sessionKey
|
|
1532
|
+
}));
|
|
1533
|
+
if (!currentSession) return null;
|
|
1534
|
+
if (currentSession.channel && currentSession.channel !== "matrix") return null;
|
|
1535
|
+
if (currentSession.accountId && currentSession.accountId !== params.accountId) return null;
|
|
1536
|
+
if (!currentSession.directUserId) return null;
|
|
1537
|
+
if (!currentSession.roomId || currentSession.roomId === params.roomId) return null;
|
|
1538
|
+
return [
|
|
1539
|
+
"This Matrix DM is sharing a session with another Matrix DM room.",
|
|
1540
|
+
"Use /focus here for a one-off isolated thread session when thread bindings are enabled, or set",
|
|
1541
|
+
"channels.matrix.dm.sessionScope to per-room to isolate each Matrix DM room."
|
|
1542
|
+
].join(" ");
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
params.logVerboseMessage(`matrix: failed checking shared DM session notice room=${params.roomId} (${String(err)})`);
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function resolveMatrixPendingHistoryText(params) {
|
|
1549
|
+
if (params.mentionPrecheckText) return params.mentionPrecheckText;
|
|
1550
|
+
if (!params.mediaUrl) return "";
|
|
1551
|
+
const body = typeof params.content.body === "string" ? params.content.body.trim() : void 0;
|
|
1552
|
+
const filename = typeof params.content.filename === "string" ? params.content.filename.trim() : void 0;
|
|
1553
|
+
const msgtype = typeof params.content.msgtype === "string" ? params.content.msgtype : void 0;
|
|
1554
|
+
return formatMatrixMessageText({
|
|
1555
|
+
body: resolveMatrixMessageBody({
|
|
1556
|
+
body,
|
|
1557
|
+
filename,
|
|
1558
|
+
msgtype
|
|
1559
|
+
}),
|
|
1560
|
+
attachment: resolveMatrixMessageAttachment({
|
|
1561
|
+
body,
|
|
1562
|
+
filename,
|
|
1563
|
+
msgtype
|
|
1564
|
+
})
|
|
1565
|
+
}) ?? "";
|
|
1566
|
+
}
|
|
1567
|
+
function isMatrixAudioMediaEnabled(cfg) {
|
|
1568
|
+
return cfg.tools?.media?.audio?.enabled !== false;
|
|
1569
|
+
}
|
|
1570
|
+
function shouldDeferMatrixAudioPreflightForRoomIngress(params) {
|
|
1571
|
+
if (!isMatrixAudioMediaEnabled(params.cfg)) return false;
|
|
1572
|
+
const content = params.content;
|
|
1573
|
+
const contentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
1574
|
+
const contentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
1575
|
+
const mediaUrl = contentUrl ?? contentFile?.url;
|
|
1576
|
+
const contentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
1577
|
+
return mediaUrl?.startsWith("mxc://") === true && isMatrixAudioContent({
|
|
1578
|
+
msgtype: typeof content.msgtype === "string" ? content.msgtype : void 0,
|
|
1579
|
+
mimetype: contentInfo?.mimetype
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
function resolveMatrixAllowBotsMode(value) {
|
|
1583
|
+
if (value === true) return "all";
|
|
1584
|
+
if (value === "mentions") return "mentions";
|
|
1585
|
+
return "off";
|
|
1586
|
+
}
|
|
1587
|
+
function formatMatrixToolProgressMarkdownCode(text) {
|
|
1588
|
+
return `\`${(text.length <= MATRIX_TOOL_PROGRESS_MAX_CHARS ? text : `${truncateUtf16Safe(text, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`).replaceAll("`", "'")}\``;
|
|
1824
1589
|
}
|
|
1825
1590
|
//#endregion
|
|
1826
|
-
//#region extensions/matrix/src/matrix/monitor/
|
|
1827
|
-
function
|
|
1828
|
-
|
|
1829
|
-
|
|
1591
|
+
//#region extensions/matrix/src/matrix/monitor/handler-runtime.ts
|
|
1592
|
+
async function redactMatrixDraftEvent(client, roomId, draftEventId) {
|
|
1593
|
+
await client.redactEvent(roomId, draftEventId).catch(() => {});
|
|
1594
|
+
}
|
|
1595
|
+
function buildMatrixFinalizedPreviewContent() {
|
|
1596
|
+
return { [MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY]: true };
|
|
1597
|
+
}
|
|
1598
|
+
const loadMatrixSendModule = createLazyRuntimeModule(() => import("./send-C9Ssx-iV.js").then((n) => n.s));
|
|
1599
|
+
const loadAcpBindingRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/acp-binding-runtime"));
|
|
1600
|
+
const loadSessionBindingRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/session-binding-runtime"));
|
|
1601
|
+
const loadMatrixReactionEvents = createLazyRuntimeModule(() => import("./reaction-events-DrK9Zua7.js"));
|
|
1602
|
+
const loadMatrixDraftStream = createLazyRuntimeModule(() => import("./draft-stream-CIjafRr0.js"));
|
|
1603
|
+
async function matrixTextWouldActivateMentions(client, text) {
|
|
1604
|
+
const { resolveMatrixMentionsForBody } = await loadMatrixSendModule();
|
|
1605
|
+
const mentions = await resolveMatrixMentionsForBody({
|
|
1606
|
+
client,
|
|
1607
|
+
body: text
|
|
1608
|
+
});
|
|
1609
|
+
return mentions.room === true || (mentions.user_ids?.length ?? 0) > 0;
|
|
1830
1610
|
}
|
|
1611
|
+
//#endregion
|
|
1612
|
+
//#region extensions/matrix/src/matrix/monitor/context-summary.ts
|
|
1831
1613
|
function summarizeMatrixMessageContextEvent(event) {
|
|
1832
1614
|
if (isPollStartType(event.type)) {
|
|
1833
1615
|
const pollSummary = parsePollStartContent(event.content);
|
|
@@ -1836,14 +1618,14 @@ function summarizeMatrixMessageContextEvent(event) {
|
|
|
1836
1618
|
const content = event.content;
|
|
1837
1619
|
return formatMatrixMessageText({
|
|
1838
1620
|
body: resolveMatrixMessageBody({
|
|
1839
|
-
body:
|
|
1840
|
-
filename:
|
|
1841
|
-
msgtype:
|
|
1621
|
+
body: normalizeOptionalString(content.body),
|
|
1622
|
+
filename: normalizeOptionalString(content.filename),
|
|
1623
|
+
msgtype: normalizeOptionalString(content.msgtype)
|
|
1842
1624
|
}),
|
|
1843
1625
|
attachment: resolveMatrixMessageAttachment({
|
|
1844
|
-
body:
|
|
1845
|
-
filename:
|
|
1846
|
-
msgtype:
|
|
1626
|
+
body: normalizeOptionalString(content.body),
|
|
1627
|
+
filename: normalizeOptionalString(content.filename),
|
|
1628
|
+
msgtype: normalizeOptionalString(content.msgtype)
|
|
1847
1629
|
})
|
|
1848
1630
|
});
|
|
1849
1631
|
}
|
|
@@ -1891,7 +1673,7 @@ function createMatrixReplyContextResolver(params) {
|
|
|
1891
1673
|
if (rawEvent.unsigned?.redacted_because) return remember(cacheKey, {});
|
|
1892
1674
|
const replyToBody = summarizeMatrixReplyEvent(rawEvent);
|
|
1893
1675
|
if (!replyToBody) return remember(cacheKey, {});
|
|
1894
|
-
const senderId =
|
|
1676
|
+
const senderId = normalizeOptionalString(rawEvent.sender);
|
|
1895
1677
|
const senderName = senderId && await params.getMemberDisplayName(input.roomId, senderId).catch(() => void 0);
|
|
1896
1678
|
return remember(cacheKey, {
|
|
1897
1679
|
replyToBody,
|
|
@@ -1901,35 +1683,540 @@ function createMatrixReplyContextResolver(params) {
|
|
|
1901
1683
|
};
|
|
1902
1684
|
}
|
|
1903
1685
|
//#endregion
|
|
1904
|
-
//#region extensions/matrix/src/matrix/monitor/
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
const
|
|
1913
|
-
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1686
|
+
//#region extensions/matrix/src/matrix/monitor/thread-context.ts
|
|
1687
|
+
const MAX_TRACKED_THREAD_STARTERS = 256;
|
|
1688
|
+
const MAX_THREAD_STARTER_BODY_LENGTH = 500;
|
|
1689
|
+
function truncateThreadStarterBody(value) {
|
|
1690
|
+
if (value.length <= MAX_THREAD_STARTER_BODY_LENGTH) return value;
|
|
1691
|
+
return `${sliceUtf16Safe(value, 0, MAX_THREAD_STARTER_BODY_LENGTH - 3)}...`;
|
|
1692
|
+
}
|
|
1693
|
+
function summarizeMatrixThreadStarterEvent(event) {
|
|
1694
|
+
const body = summarizeMatrixMessageContextEvent(event);
|
|
1695
|
+
if (body) return truncateThreadStarterBody(body);
|
|
1696
|
+
const content = event.content;
|
|
1697
|
+
const msgtype = normalizeOptionalString(content.msgtype);
|
|
1698
|
+
if (msgtype) return `Matrix ${msgtype} message`;
|
|
1699
|
+
const eventType = normalizeOptionalString(event.type);
|
|
1700
|
+
return eventType ? `Matrix ${eventType} event` : void 0;
|
|
1701
|
+
}
|
|
1702
|
+
function formatMatrixThreadStarterBody(params) {
|
|
1703
|
+
const senderLabel = params.senderName ?? params.senderId ?? "unknown sender";
|
|
1704
|
+
const lines = [`Matrix thread root ${params.threadRootId} from ${senderLabel}:`];
|
|
1705
|
+
if (params.summary) lines.push(params.summary);
|
|
1706
|
+
return lines.join("\n");
|
|
1707
|
+
}
|
|
1708
|
+
function createMatrixThreadContextResolver(params) {
|
|
1709
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1710
|
+
const remember = (key, value) => {
|
|
1711
|
+
cache.set(key, value);
|
|
1712
|
+
if (cache.size > MAX_TRACKED_THREAD_STARTERS) {
|
|
1713
|
+
const oldest = cache.keys().next().value;
|
|
1714
|
+
if (typeof oldest === "string") cache.delete(oldest);
|
|
1927
1715
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1716
|
+
return value;
|
|
1717
|
+
};
|
|
1718
|
+
return async (input) => {
|
|
1719
|
+
const cacheKey = `${input.roomId}:${input.threadRootId}`;
|
|
1720
|
+
const cached = cache.get(cacheKey);
|
|
1721
|
+
if (cached) return cached;
|
|
1722
|
+
const rootEvent = await params.client.getEvent(input.roomId, input.threadRootId).catch((err) => {
|
|
1723
|
+
params.logVerboseMessage(`matrix: failed resolving thread root room=${input.roomId} id=${input.threadRootId}: ${String(err)}`);
|
|
1724
|
+
return null;
|
|
1725
|
+
});
|
|
1726
|
+
if (!rootEvent) return { threadStarterBody: `Matrix thread root ${input.threadRootId}` };
|
|
1727
|
+
const rawEvent = rootEvent;
|
|
1728
|
+
const senderId = normalizeOptionalString(rawEvent.sender);
|
|
1729
|
+
const senderName = senderId && await params.getMemberDisplayName(input.roomId, senderId).catch(() => void 0);
|
|
1730
|
+
const senderLabel = senderName ?? senderId;
|
|
1731
|
+
const summary = summarizeMatrixThreadStarterEvent(rawEvent);
|
|
1732
|
+
return remember(cacheKey, {
|
|
1733
|
+
threadStarterBody: formatMatrixThreadStarterBody({
|
|
1734
|
+
threadRootId: input.threadRootId,
|
|
1735
|
+
senderId,
|
|
1736
|
+
senderName,
|
|
1737
|
+
summary
|
|
1738
|
+
}),
|
|
1739
|
+
senderId,
|
|
1740
|
+
senderLabel,
|
|
1741
|
+
summary
|
|
1742
|
+
});
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
//#endregion
|
|
1746
|
+
//#region extensions/matrix/src/matrix/monitor/handler-context.ts
|
|
1747
|
+
async function resolveMatrixInboundContext(config) {
|
|
1748
|
+
const { client, core, cfg, accountId, runtime, logVerboseMessage, roomId, event, eventTs, route: _route, isDirectMessage, isRoom, effectiveRoomUsers, groupPolicy, effectiveGroupAllowFrom, contextVisibilityMode, resolveThreadContext, resolveReplyContext, threadRootId, thread, getRoomInfo, senderId, senderName, bodyText, commandBodyText, roomConfig, messageId, inboundHistory, wasMentioned, effectiveWasMentioned, shouldBypassMention, canDetectMention, shouldRequireMention, commandAuthorized, locationPayload, media, preflightAudioTranscript, historyLimit, hasExplicitSessionBinding, dmSessionScope, sharedDmContextNoticeRooms, resolveStorePath: resolveStorePathImpl, createChannelInboundEnvelopeBuilder: createChannelInboundEnvelopeBuilderImpl, finalizeInboundContext } = config;
|
|
1749
|
+
const replyToEventId = resolveMatrixReplyToEventId(event.content);
|
|
1750
|
+
const threadTarget = thread.threadId;
|
|
1751
|
+
const isRoomContextSenderAllowed = (contextSenderId) => {
|
|
1752
|
+
if (!isRoom || !contextSenderId) return true;
|
|
1753
|
+
if (effectiveRoomUsers.length > 0) return resolveMatrixAllowListMatch({
|
|
1754
|
+
allowList: effectiveRoomUsers,
|
|
1755
|
+
userId: contextSenderId
|
|
1756
|
+
}).allowed;
|
|
1757
|
+
if (groupPolicy === "allowlist" && effectiveGroupAllowFrom.length > 0) return resolveMatrixAllowListMatch({
|
|
1758
|
+
allowList: effectiveGroupAllowFrom,
|
|
1759
|
+
userId: contextSenderId
|
|
1760
|
+
}).allowed;
|
|
1761
|
+
return true;
|
|
1762
|
+
};
|
|
1763
|
+
const shouldIncludeRoomContextSender = (kind, contextSenderId) => evaluateSupplementalContextVisibility({
|
|
1764
|
+
mode: contextVisibilityMode,
|
|
1765
|
+
kind,
|
|
1766
|
+
senderAllowed: isRoomContextSenderAllowed(contextSenderId)
|
|
1767
|
+
}).include;
|
|
1768
|
+
let threadContext = threadRootId ? await resolveThreadContext({
|
|
1769
|
+
roomId,
|
|
1770
|
+
threadRootId
|
|
1771
|
+
}) : void 0;
|
|
1772
|
+
let threadContextBlockedByPolicy = false;
|
|
1773
|
+
if (threadContext?.senderId && !shouldIncludeRoomContextSender("thread", threadContext.senderId)) {
|
|
1774
|
+
logVerboseMessage(`matrix: drop thread root context (mode=${contextVisibilityMode})`);
|
|
1775
|
+
threadContextBlockedByPolicy = true;
|
|
1776
|
+
threadContext = void 0;
|
|
1777
|
+
}
|
|
1778
|
+
let replyContext;
|
|
1779
|
+
if (replyToEventId && replyToEventId === threadRootId && threadContext?.summary) replyContext = {
|
|
1780
|
+
replyToBody: threadContext.summary,
|
|
1781
|
+
replyToSender: threadContext.senderLabel,
|
|
1782
|
+
replyToSenderId: threadContext.senderId
|
|
1783
|
+
};
|
|
1784
|
+
else if (replyToEventId && replyToEventId === threadRootId && threadContextBlockedByPolicy) replyContext = await resolveReplyContext({
|
|
1785
|
+
roomId,
|
|
1786
|
+
eventId: replyToEventId
|
|
1787
|
+
});
|
|
1788
|
+
else replyContext = replyToEventId ? await resolveReplyContext({
|
|
1789
|
+
roomId,
|
|
1790
|
+
eventId: replyToEventId
|
|
1791
|
+
}) : void 0;
|
|
1792
|
+
const replySenderAllowed = !replyContext?.replyToSenderId || isRoomContextSenderAllowed(replyContext.replyToSenderId);
|
|
1793
|
+
const roomName = (isRoom ? await getRoomInfo(roomId) : void 0)?.name;
|
|
1794
|
+
const envelopeFrom = isDirectMessage ? senderName : roomName ?? roomId;
|
|
1795
|
+
const textWithId = `${bodyText}\n[matrix event id: ${messageId} room: ${roomId}]`;
|
|
1796
|
+
const storePath = resolveStorePathImpl(cfg.session?.store, { agentId: _route.agentId });
|
|
1797
|
+
const buildEnvelope = createChannelInboundEnvelopeBuilderImpl({
|
|
1798
|
+
cfg,
|
|
1799
|
+
route: _route
|
|
1800
|
+
});
|
|
1801
|
+
const sharedDmNoticeSessionKey = threadTarget ? _route.mainSessionKey || _route.sessionKey : _route.sessionKey;
|
|
1802
|
+
const sharedDmContextNotice = isDirectMessage ? hasExplicitSessionBinding ? null : resolveMatrixSharedDmContextNotice({
|
|
1803
|
+
storePath,
|
|
1804
|
+
sessionKey: sharedDmNoticeSessionKey,
|
|
1805
|
+
roomId,
|
|
1806
|
+
accountId: _route.accountId,
|
|
1807
|
+
dmSessionScope,
|
|
1808
|
+
sentRooms: sharedDmContextNoticeRooms,
|
|
1809
|
+
logVerboseMessage
|
|
1810
|
+
}) : null;
|
|
1811
|
+
const body = buildEnvelope({
|
|
1812
|
+
channel: "Matrix",
|
|
1813
|
+
from: envelopeFrom,
|
|
1814
|
+
timestamp: eventTs ?? void 0,
|
|
1815
|
+
body: textWithId
|
|
1816
|
+
});
|
|
1817
|
+
const groupSystemPrompt = normalizeOptionalString(roomConfig?.systemPrompt);
|
|
1818
|
+
const quoteHidden = Boolean(replyContext && !evaluateSupplementalContextVisibility({
|
|
1819
|
+
mode: contextVisibilityMode,
|
|
1820
|
+
kind: "quote",
|
|
1821
|
+
senderAllowed: replySenderAllowed
|
|
1822
|
+
}).include);
|
|
1823
|
+
const ctxPayload = buildChannelInboundEventContext({
|
|
1824
|
+
channel: "matrix",
|
|
1825
|
+
contextVisibility: contextVisibilityMode,
|
|
1826
|
+
finalize: finalizeInboundContext,
|
|
1827
|
+
supplemental: {
|
|
1828
|
+
quote: replyContext ? {
|
|
1829
|
+
id: threadTarget ? void 0 : replyToEventId ?? void 0,
|
|
1830
|
+
body: replyContext.replyToBody,
|
|
1831
|
+
sender: replyContext.replyToSender,
|
|
1832
|
+
senderAllowed: replySenderAllowed
|
|
1833
|
+
} : void 0,
|
|
1834
|
+
thread: {
|
|
1835
|
+
starterBody: threadContext?.threadStarterBody,
|
|
1836
|
+
senderAllowed: threadContext ? true : void 0
|
|
1837
|
+
},
|
|
1838
|
+
groupSystemPrompt: isRoom ? groupSystemPrompt : void 0
|
|
1839
|
+
},
|
|
1840
|
+
media: toInboundMediaFacts(media ? [{
|
|
1841
|
+
path: media.path,
|
|
1842
|
+
url: media.path,
|
|
1843
|
+
contentType: media.contentType,
|
|
1844
|
+
transcribed: preflightAudioTranscript !== void 0
|
|
1845
|
+
}] : void 0),
|
|
1846
|
+
messageId,
|
|
1847
|
+
timestamp: eventTs ?? void 0,
|
|
1848
|
+
from: isDirectMessage ? `matrix:${senderId}` : `matrix:channel:${roomId}`,
|
|
1849
|
+
sender: {
|
|
1850
|
+
id: senderId,
|
|
1851
|
+
name: senderName,
|
|
1852
|
+
username: senderId.split(":")[0]?.replace(/^@/, "")
|
|
1853
|
+
},
|
|
1854
|
+
conversation: {
|
|
1855
|
+
kind: isDirectMessage ? "direct" : "channel",
|
|
1856
|
+
id: roomId,
|
|
1857
|
+
label: envelopeFrom,
|
|
1858
|
+
nativeChannelId: roomId,
|
|
1859
|
+
threadId: threadTarget
|
|
1860
|
+
},
|
|
1861
|
+
route: {
|
|
1862
|
+
agentId: _route.agentId,
|
|
1863
|
+
dmScope: _route.dmScope,
|
|
1864
|
+
accountId: _route.accountId,
|
|
1865
|
+
routeSessionKey: _route.sessionKey
|
|
1866
|
+
},
|
|
1867
|
+
reply: {
|
|
1868
|
+
to: `room:${roomId}`,
|
|
1869
|
+
replyToId: threadTarget ? void 0 : replyToEventId ?? void 0,
|
|
1870
|
+
messageThreadId: threadTarget,
|
|
1871
|
+
nativeChannelId: roomId
|
|
1872
|
+
},
|
|
1873
|
+
message: {
|
|
1874
|
+
body,
|
|
1875
|
+
rawBody: bodyText,
|
|
1876
|
+
commandBody: commandBodyText,
|
|
1877
|
+
bodyForAgent: bodyText,
|
|
1878
|
+
inboundHistory: inboundHistory && inboundHistory.length > 0 ? inboundHistory : void 0
|
|
1879
|
+
},
|
|
1880
|
+
sessionTranscript: { historyLimit: isRoom ? historyLimit : 0 },
|
|
1881
|
+
access: {
|
|
1882
|
+
...isRoom ? { mentions: {
|
|
1883
|
+
canDetectMention: true,
|
|
1884
|
+
wasMentioned,
|
|
1885
|
+
requireMention: shouldRequireMention
|
|
1886
|
+
} } : {},
|
|
1887
|
+
commands: { authorized: commandAuthorized }
|
|
1888
|
+
},
|
|
1889
|
+
extra: {
|
|
1890
|
+
GroupSubject: isRoom ? roomName ?? roomId : void 0,
|
|
1891
|
+
GroupId: isRoom ? roomId : void 0,
|
|
1892
|
+
GroupChannel: isRoom ? roomId : void 0,
|
|
1893
|
+
...locationPayload?.context,
|
|
1894
|
+
CommandSource: "text",
|
|
1895
|
+
NativeDirectUserId: isDirectMessage ? senderId : void 0
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
if (quoteHidden) logVerboseMessage(`matrix: drop reply context (mode=${contextVisibilityMode})`);
|
|
1899
|
+
logVerboseMessage(`matrix inbound: room=${roomId} from=${senderId} preview="${truncateUtf16Safe(bodyText, 200).replace(/\n/g, "\\n")}"`);
|
|
1900
|
+
const replyTarget = ctxPayload.To;
|
|
1901
|
+
if (!replyTarget) {
|
|
1902
|
+
runtime.error?.("matrix: missing reply target");
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
const { ackReaction, ackReactionScope: ackScope } = resolveMatrixAckReactionConfig({
|
|
1906
|
+
cfg,
|
|
1907
|
+
agentId: _route.agentId,
|
|
1908
|
+
accountId
|
|
1909
|
+
});
|
|
1910
|
+
const shouldAckReaction = () => Boolean(ackReaction && core.channel.reactions.shouldAckReaction({
|
|
1911
|
+
scope: ackScope,
|
|
1912
|
+
isDirect: isDirectMessage,
|
|
1913
|
+
isGroup: isRoom,
|
|
1914
|
+
isMentionableGroup: isRoom,
|
|
1915
|
+
requireMention: shouldRequireMention,
|
|
1916
|
+
canDetectMention,
|
|
1917
|
+
effectiveWasMentioned,
|
|
1918
|
+
shouldBypassMention
|
|
1919
|
+
}));
|
|
1920
|
+
if (shouldAckReaction() && messageId) loadMatrixSendModule().then(({ reactMatrixMessage }) => reactMatrixMessage(roomId, messageId, ackReaction, client)).catch((err) => {
|
|
1921
|
+
logVerboseMessage(`matrix react failed for room ${roomId}: ${String(err)}`);
|
|
1922
|
+
});
|
|
1923
|
+
if (messageId) loadMatrixSendModule().then(({ sendReadReceiptMatrix }) => sendReadReceiptMatrix(roomId, messageId, client)).catch((err) => {
|
|
1924
|
+
logVerboseMessage(`matrix: read receipt failed room=${roomId} id=${messageId}: ${String(err)}`);
|
|
1925
|
+
});
|
|
1926
|
+
return {
|
|
1927
|
+
replyToEventId,
|
|
1928
|
+
threadTarget,
|
|
1929
|
+
storePath,
|
|
1930
|
+
ctxPayload,
|
|
1931
|
+
replyTarget,
|
|
1932
|
+
sharedDmContextNotice
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
//#endregion
|
|
1936
|
+
//#region extensions/matrix/src/matrix/monitor/handler-draft-controller.ts
|
|
1937
|
+
async function createMatrixDraftController(params) {
|
|
1938
|
+
const { streaming, previewToolProgressEnabled, replyToMode, messageId, threadTarget, accountConfig, cfg, accountId, roomId, client, logVerboseMessage } = params;
|
|
1939
|
+
let draftConsumed = false;
|
|
1940
|
+
const draftStreamingEnabled = streaming !== "off";
|
|
1941
|
+
const quietDraftStreaming = streaming === "quiet" || streaming === "progress";
|
|
1942
|
+
const progressDraftStreaming = streaming === "progress";
|
|
1943
|
+
const draftReplyToId = replyToMode !== "off" && !threadTarget ? messageId : void 0;
|
|
1944
|
+
const draftStream = draftStreamingEnabled ? await loadMatrixDraftStream().then(({ createMatrixDraftStream }) => createMatrixDraftStream({
|
|
1945
|
+
roomId,
|
|
1946
|
+
client,
|
|
1947
|
+
cfg,
|
|
1948
|
+
mode: quietDraftStreaming ? "quiet" : "partial",
|
|
1949
|
+
threadId: threadTarget,
|
|
1950
|
+
replyToId: draftReplyToId,
|
|
1951
|
+
preserveReplyId: replyToMode === "all",
|
|
1952
|
+
accountId,
|
|
1953
|
+
log: logVerboseMessage
|
|
1954
|
+
})) : void 0;
|
|
1955
|
+
const shouldStreamPreviewToolProgress = Boolean(draftStream) && previewToolProgressEnabled;
|
|
1956
|
+
const shouldSuppressDefaultToolProgressMessages = Boolean(draftStream) && (shouldStreamPreviewToolProgress || params.streaming === "progress");
|
|
1957
|
+
let currentDraftMessageGeneration = 0;
|
|
1958
|
+
let currentDraftBlockOffset = 0;
|
|
1959
|
+
let latestDraftFullText = "";
|
|
1960
|
+
const pendingDraftBoundaries = [];
|
|
1961
|
+
const latestQueuedDraftBoundaryOffsets = /* @__PURE__ */ new Map();
|
|
1962
|
+
let currentDraftReplyToId = draftReplyToId;
|
|
1963
|
+
let previewToolProgressSuppressed = false;
|
|
1964
|
+
let previewToolProgressLines = [];
|
|
1965
|
+
let latestPlan;
|
|
1966
|
+
let latestPlanExplanation;
|
|
1967
|
+
const progressConfigEntry = accountConfig ?? cfg.channels?.matrix;
|
|
1968
|
+
const progressSeed = `${accountId}:${roomId}`;
|
|
1969
|
+
const renderProgressDraft = () => {
|
|
1970
|
+
if (!draftStream) return;
|
|
1971
|
+
const previewText = formatChannelProgressDraftText({
|
|
1972
|
+
entry: progressConfigEntry,
|
|
1973
|
+
lines: previewToolProgressLines,
|
|
1974
|
+
seed: progressSeed,
|
|
1975
|
+
formatLine: formatMatrixToolProgressMarkdownCode,
|
|
1976
|
+
bullet: "-",
|
|
1977
|
+
narration: latestPlanExplanation,
|
|
1978
|
+
plan: latestPlan
|
|
1979
|
+
});
|
|
1980
|
+
if (!previewText) return;
|
|
1981
|
+
draftStream.update(previewText);
|
|
1982
|
+
};
|
|
1983
|
+
const progressDraftGate = createChannelProgressDraftGate({ onStart: renderProgressDraft });
|
|
1984
|
+
const pushPreviewToolProgress = async (line, options) => {
|
|
1985
|
+
if (!draftStream) return;
|
|
1986
|
+
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1987
|
+
const normalized = normalizeChannelProgressDraftLineIdentity(line);
|
|
1988
|
+
const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
|
|
1989
|
+
if (!progressDraftStreaming) {
|
|
1990
|
+
if (!shouldStreamPreviewToolProgress || previewToolProgressSuppressed || !normalized) return;
|
|
1991
|
+
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(progressConfigEntry) });
|
|
1992
|
+
if (nextLines === previewToolProgressLines) return;
|
|
1993
|
+
previewToolProgressLines = nextLines;
|
|
1994
|
+
draftStream.update(formatChannelProgressDraftText({
|
|
1995
|
+
entry: progressConfigEntry,
|
|
1996
|
+
lines: previewToolProgressLines,
|
|
1997
|
+
seed: progressSeed,
|
|
1998
|
+
formatLine: formatMatrixToolProgressMarkdownCode,
|
|
1999
|
+
bullet: "-",
|
|
2000
|
+
narration: latestPlanExplanation,
|
|
2001
|
+
plan: latestPlan
|
|
2002
|
+
}));
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (shouldStreamPreviewToolProgress && !previewToolProgressSuppressed && normalized) previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(progressConfigEntry) });
|
|
2006
|
+
const alreadyStarted = progressDraftGate.hasStarted;
|
|
2007
|
+
const progressActive = await progressDraftGate.noteWork();
|
|
2008
|
+
if ((alreadyStarted || progressActive) && progressDraftGate.hasStarted) renderProgressDraft();
|
|
2009
|
+
};
|
|
2010
|
+
const pushPlanProgress = async (steps, explanation) => {
|
|
2011
|
+
latestPlan = steps?.length ? steps.map((entry) => ({ ...entry })) : void 0;
|
|
2012
|
+
latestPlanExplanation = explanation?.replace(/\s+/g, " ").trim() || void 0;
|
|
2013
|
+
if (!draftStream || previewToolProgressSuppressed) return;
|
|
2014
|
+
if (!progressDraftStreaming) {
|
|
2015
|
+
renderProgressDraft();
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const alreadyStarted = progressDraftGate.hasStarted;
|
|
2019
|
+
await progressDraftGate.startNow();
|
|
2020
|
+
if (alreadyStarted && progressDraftGate.hasStarted) renderProgressDraft();
|
|
2021
|
+
};
|
|
2022
|
+
const suppressPreviewToolProgressForAnswerText = (text) => {
|
|
2023
|
+
if (!text?.trim()) return;
|
|
2024
|
+
previewToolProgressSuppressed = true;
|
|
2025
|
+
previewToolProgressLines = [];
|
|
2026
|
+
latestPlan = void 0;
|
|
2027
|
+
latestPlanExplanation = void 0;
|
|
2028
|
+
};
|
|
2029
|
+
const resetPreviewToolProgress = () => {
|
|
2030
|
+
previewToolProgressSuppressed = false;
|
|
2031
|
+
previewToolProgressLines = [];
|
|
2032
|
+
latestPlan = void 0;
|
|
2033
|
+
latestPlanExplanation = void 0;
|
|
2034
|
+
};
|
|
2035
|
+
const buildPreviewToolProgressReplyOptions = () => {
|
|
2036
|
+
if (!shouldSuppressDefaultToolProgressMessages) return {};
|
|
2037
|
+
const options = { suppressDefaultToolProgressMessages: true };
|
|
2038
|
+
if (!shouldStreamPreviewToolProgress) return options;
|
|
2039
|
+
return {
|
|
2040
|
+
...options,
|
|
2041
|
+
onToolStart: async (payload) => {
|
|
2042
|
+
const toolName = payload.name?.trim();
|
|
2043
|
+
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
2044
|
+
event: "tool",
|
|
2045
|
+
itemId: payload.itemId,
|
|
2046
|
+
toolCallId: payload.toolCallId,
|
|
2047
|
+
name: toolName,
|
|
2048
|
+
phase: payload.phase,
|
|
2049
|
+
args: payload.args
|
|
2050
|
+
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName });
|
|
2051
|
+
},
|
|
2052
|
+
onItemEvent: async (payload) => {
|
|
2053
|
+
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
2054
|
+
event: "item",
|
|
2055
|
+
itemId: payload.itemId,
|
|
2056
|
+
toolCallId: payload.toolCallId,
|
|
2057
|
+
itemKind: payload.kind,
|
|
2058
|
+
title: payload.title,
|
|
2059
|
+
name: payload.name,
|
|
2060
|
+
phase: payload.phase,
|
|
2061
|
+
status: payload.status,
|
|
2062
|
+
summary: payload.summary,
|
|
2063
|
+
progressText: payload.progressText,
|
|
2064
|
+
meta: payload.meta
|
|
2065
|
+
}));
|
|
2066
|
+
},
|
|
2067
|
+
onPlanUpdate: async (payload) => {
|
|
2068
|
+
if (payload.phase !== "update") return;
|
|
2069
|
+
await pushPlanProgress(payload.steps, payload.explanation);
|
|
2070
|
+
},
|
|
2071
|
+
onApprovalEvent: async (payload) => {
|
|
2072
|
+
if (payload.phase !== "requested") return;
|
|
2073
|
+
await pushPreviewToolProgress(formatChannelProgressDraftLine({
|
|
2074
|
+
event: "approval",
|
|
2075
|
+
phase: payload.phase,
|
|
2076
|
+
title: payload.title,
|
|
2077
|
+
command: payload.command,
|
|
2078
|
+
reason: payload.reason,
|
|
2079
|
+
message: payload.message
|
|
2080
|
+
}));
|
|
2081
|
+
},
|
|
2082
|
+
onCommandOutput: async (payload) => {
|
|
2083
|
+
if (payload.phase !== "end") return;
|
|
2084
|
+
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
2085
|
+
event: "command-output",
|
|
2086
|
+
itemId: payload.itemId,
|
|
2087
|
+
toolCallId: payload.toolCallId,
|
|
2088
|
+
phase: payload.phase,
|
|
2089
|
+
title: payload.title,
|
|
2090
|
+
name: payload.name,
|
|
2091
|
+
status: payload.status,
|
|
2092
|
+
exitCode: payload.exitCode
|
|
2093
|
+
}));
|
|
2094
|
+
},
|
|
2095
|
+
onPatchSummary: async (payload) => {
|
|
2096
|
+
if (payload.phase !== "end") return;
|
|
2097
|
+
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
2098
|
+
event: "patch",
|
|
2099
|
+
itemId: payload.itemId,
|
|
2100
|
+
toolCallId: payload.toolCallId,
|
|
2101
|
+
phase: payload.phase,
|
|
2102
|
+
title: payload.title,
|
|
2103
|
+
name: payload.name,
|
|
2104
|
+
added: payload.added,
|
|
2105
|
+
modified: payload.modified,
|
|
2106
|
+
deleted: payload.deleted,
|
|
2107
|
+
summary: payload.summary
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
};
|
|
2112
|
+
const getDisplayableDraftText = () => {
|
|
2113
|
+
const nextDraftBoundaryOffset = pendingDraftBoundaries.find((boundary) => boundary.messageGeneration === currentDraftMessageGeneration)?.endOffset;
|
|
2114
|
+
if (nextDraftBoundaryOffset === void 0) return latestDraftFullText.slice(currentDraftBlockOffset);
|
|
2115
|
+
return latestDraftFullText.slice(currentDraftBlockOffset, nextDraftBoundaryOffset);
|
|
2116
|
+
};
|
|
2117
|
+
const updateDraftFromLatestFullText = () => {
|
|
2118
|
+
const blockText = getDisplayableDraftText();
|
|
2119
|
+
if (blockText) draftStream?.update(blockText);
|
|
2120
|
+
};
|
|
2121
|
+
const queueDraftBlockBoundary = (payload, context) => {
|
|
2122
|
+
const payloadTextLength = payload.text?.length ?? 0;
|
|
2123
|
+
const messageGeneration = context?.assistantMessageIndex ?? currentDraftMessageGeneration;
|
|
2124
|
+
const nextDraftBoundaryOffset = (latestQueuedDraftBoundaryOffsets.get(messageGeneration) ?? 0) + payloadTextLength;
|
|
2125
|
+
latestQueuedDraftBoundaryOffsets.set(messageGeneration, nextDraftBoundaryOffset);
|
|
2126
|
+
pendingDraftBoundaries.push({
|
|
2127
|
+
messageGeneration,
|
|
2128
|
+
endOffset: nextDraftBoundaryOffset
|
|
2129
|
+
});
|
|
2130
|
+
};
|
|
2131
|
+
const advanceDraftBlockBoundary = (options) => {
|
|
2132
|
+
const completedBoundary = pendingDraftBoundaries.shift();
|
|
2133
|
+
if (completedBoundary) {
|
|
2134
|
+
if (!pendingDraftBoundaries.some((entry) => entry.messageGeneration === completedBoundary.messageGeneration)) latestQueuedDraftBoundaryOffsets.delete(completedBoundary.messageGeneration);
|
|
2135
|
+
if (completedBoundary.messageGeneration === currentDraftMessageGeneration) currentDraftBlockOffset = completedBoundary.endOffset;
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (options?.fallbackToLatestEnd) currentDraftBlockOffset = latestDraftFullText.length;
|
|
2139
|
+
};
|
|
2140
|
+
const resetDraftBlockOffsets = () => {
|
|
2141
|
+
currentDraftMessageGeneration += 1;
|
|
2142
|
+
currentDraftBlockOffset = 0;
|
|
2143
|
+
latestDraftFullText = "";
|
|
2144
|
+
};
|
|
2145
|
+
const resetDraftDeliveryState = async () => {
|
|
2146
|
+
await draftStream?.discardPending();
|
|
2147
|
+
draftStream?.reset();
|
|
2148
|
+
draftConsumed = false;
|
|
2149
|
+
currentDraftMessageGeneration = 0;
|
|
2150
|
+
currentDraftBlockOffset = 0;
|
|
2151
|
+
latestDraftFullText = "";
|
|
2152
|
+
pendingDraftBoundaries.length = 0;
|
|
2153
|
+
latestQueuedDraftBoundaryOffsets.clear();
|
|
2154
|
+
currentDraftReplyToId = draftReplyToId;
|
|
2155
|
+
progressDraftGate.reset();
|
|
2156
|
+
resetPreviewToolProgress();
|
|
2157
|
+
};
|
|
2158
|
+
return {
|
|
2159
|
+
draftStream,
|
|
2160
|
+
progressDraftGate,
|
|
2161
|
+
buildPreviewToolProgressReplyOptions,
|
|
2162
|
+
queueDraftBlockBoundary,
|
|
2163
|
+
advanceDraftBlockBoundary,
|
|
2164
|
+
resetDraftBlockOffsets,
|
|
2165
|
+
resetPreviewToolProgress,
|
|
2166
|
+
resetDraftDeliveryState,
|
|
2167
|
+
updateDraftFromLatestFullText,
|
|
2168
|
+
isDraftConsumed: () => draftConsumed,
|
|
2169
|
+
markDraftConsumed: () => {
|
|
2170
|
+
draftConsumed = true;
|
|
2171
|
+
},
|
|
2172
|
+
clearDraftConsumed: () => {
|
|
2173
|
+
draftConsumed = false;
|
|
2174
|
+
},
|
|
2175
|
+
currentReplyToId: () => currentDraftReplyToId,
|
|
2176
|
+
setCurrentReplyToId: (replyToId) => {
|
|
2177
|
+
currentDraftReplyToId = replyToId;
|
|
2178
|
+
},
|
|
2179
|
+
resetReplyToIdForNextBlock: () => {
|
|
2180
|
+
currentDraftReplyToId = replyToMode === "all" ? draftReplyToId : void 0;
|
|
2181
|
+
},
|
|
2182
|
+
onPartialReply: (text) => {
|
|
2183
|
+
if (progressDraftStreaming) return;
|
|
2184
|
+
latestDraftFullText = text;
|
|
2185
|
+
suppressPreviewToolProgressForAnswerText(text);
|
|
2186
|
+
updateDraftFromLatestFullText();
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
//#endregion
|
|
2191
|
+
//#region extensions/matrix/src/matrix/monitor/room-history.ts
|
|
2192
|
+
/** Maximum entries retained per room (hard cap to bound memory). */
|
|
2193
|
+
const DEFAULT_MAX_QUEUE_SIZE = 200;
|
|
2194
|
+
/** Maximum number of rooms to retain queues for (FIFO eviction beyond this). */
|
|
2195
|
+
const DEFAULT_MAX_ROOM_QUEUES = 1e3;
|
|
2196
|
+
/** Maximum number of (agentId, roomId) watermark entries to retain. */
|
|
2197
|
+
const MAX_WATERMARK_ENTRIES = 5e3;
|
|
2198
|
+
/** Maximum prepared trigger snapshots retained per room for retry reuse. */
|
|
2199
|
+
const MAX_PREPARED_TRIGGER_ENTRIES = 500;
|
|
2200
|
+
/** Maximum thread queues retained per room (FIFO eviction beyond this). */
|
|
2201
|
+
const MAX_THREAD_QUEUES_PER_ROOM = 200;
|
|
2202
|
+
function createRoomHistoryTrackerInternal(maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, maxRoomQueues = DEFAULT_MAX_ROOM_QUEUES, maxWatermarkEntries = MAX_WATERMARK_ENTRIES, maxPreparedTriggerEntries = MAX_PREPARED_TRIGGER_ENTRIES) {
|
|
2203
|
+
const roomQueues = /* @__PURE__ */ new Map();
|
|
2204
|
+
/** Maps `{agentId, roomId, scope}` → absolute consumed-up-to index */
|
|
2205
|
+
const agentWatermarks = /* @__PURE__ */ new Map();
|
|
2206
|
+
let nextQueueGeneration = 1;
|
|
2207
|
+
function clearRoomWatermarks(roomId) {
|
|
2208
|
+
for (const key of agentWatermarks.keys()) if (JSON.parse(key)?.roomId === roomId) agentWatermarks.delete(key);
|
|
2209
|
+
}
|
|
2210
|
+
function clearThreadWatermarks(roomId, threadRootId) {
|
|
2211
|
+
for (const key of agentWatermarks.keys()) {
|
|
2212
|
+
const parsed = JSON.parse(key);
|
|
2213
|
+
if (parsed?.roomId === roomId && parsed.scope === threadRootId) agentWatermarks.delete(key);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
function createHistoryQueue() {
|
|
2217
|
+
return {
|
|
2218
|
+
entries: [],
|
|
2219
|
+
baseIndex: 0,
|
|
1933
2220
|
generation: nextQueueGeneration++,
|
|
1934
2221
|
preparedTriggers: /* @__PURE__ */ new Map()
|
|
1935
2222
|
};
|
|
@@ -2141,200 +2428,1124 @@ function createRoomHistoryTracker(maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, maxRoom
|
|
|
2141
2428
|
};
|
|
2142
2429
|
}
|
|
2143
2430
|
//#endregion
|
|
2144
|
-
//#region extensions/matrix/src/matrix/monitor/
|
|
2145
|
-
|
|
2146
|
-
const
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2431
|
+
//#region extensions/matrix/src/matrix/monitor/handler-ingress-access.ts
|
|
2432
|
+
async function resolveMatrixIngressAccess(config) {
|
|
2433
|
+
const { handler, params: paramsLocal, roomId, event, eventTs, senderId, isReactionEvent, readStoreAllowFrom, shouldSendPairingReply, resolveLiveAccountAllowlists, roomHistoryTracker, commitInboundEventIfClaimed } = config;
|
|
2434
|
+
const { core, cfg, accountId, accountConfig, client, logVerboseMessage, groupPolicy, dmEnabled, dmPolicy, dmThreadReplies, threadReplies, roomsConfig, accountAllowBots, configuredBotUserIds, needsRoomAliasesForConfig, getRoomInfo, getMemberDisplayName } = handler;
|
|
2435
|
+
const content = paramsLocal.content;
|
|
2436
|
+
const isDirectMessage = paramsLocal.isDirectMessage;
|
|
2437
|
+
const isRoom = !isDirectMessage;
|
|
2438
|
+
const { audioPreflightMode, locationPayload, reservedHistorySlot, selfUserId } = paramsLocal;
|
|
2439
|
+
const messageId = event.event_id ?? "";
|
|
2440
|
+
const threadRootId = resolveMatrixThreadRootId({
|
|
2441
|
+
event,
|
|
2442
|
+
content
|
|
2443
|
+
});
|
|
2444
|
+
const thread = resolveMatrixThreadRouting({
|
|
2445
|
+
isDirectMessage,
|
|
2446
|
+
threadReplies,
|
|
2447
|
+
dmThreadReplies,
|
|
2448
|
+
messageId,
|
|
2449
|
+
threadRootId
|
|
2450
|
+
});
|
|
2451
|
+
const historyThreadId = threadRootId ? thread.threadId : void 0;
|
|
2452
|
+
let reservedHistorySlotConsumed = false;
|
|
2453
|
+
const discardReservedHistorySlot = () => {
|
|
2454
|
+
if (reservedHistorySlot && !reservedHistorySlotConsumed) {
|
|
2455
|
+
roomHistoryTracker.discardPending(roomId, reservedHistorySlot, historyThreadId);
|
|
2456
|
+
reservedHistorySlotConsumed = true;
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
const markReservedHistorySlotConsumed = () => {
|
|
2460
|
+
reservedHistorySlotConsumed = true;
|
|
2461
|
+
};
|
|
2462
|
+
const commitInboundEventIfClaimedAndDiscardReserved = async () => {
|
|
2463
|
+
discardReservedHistorySlot();
|
|
2464
|
+
await commitInboundEventIfClaimed();
|
|
2465
|
+
};
|
|
2466
|
+
if (isRoom && groupPolicy === "disabled") {
|
|
2467
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
const roomInfoForConfig = isRoom && needsRoomAliasesForConfig ? await getRoomInfo(roomId, { includeAliases: true }) : void 0;
|
|
2471
|
+
const roomAliasesForConfig = roomInfoForConfig ? [roomInfoForConfig.canonicalAlias ?? "", ...roomInfoForConfig.altAliases].filter(Boolean) : [];
|
|
2472
|
+
const roomConfigInfo = isRoom ? resolveMatrixRoomConfig({
|
|
2473
|
+
rooms: roomsConfig,
|
|
2474
|
+
roomId,
|
|
2475
|
+
aliases: roomAliasesForConfig
|
|
2476
|
+
}) : void 0;
|
|
2477
|
+
const roomConfig = roomConfigInfo?.config;
|
|
2478
|
+
const allowBotsMode = resolveMatrixAllowBotsMode(roomConfig?.allowBots ?? accountAllowBots);
|
|
2479
|
+
const isConfiguredBotSender = configuredBotUserIds.has(senderId);
|
|
2480
|
+
const roomMatchMeta = roomConfigInfo ? `matchKey=${roomConfigInfo.matchKey ?? "none"} matchSource=${roomConfigInfo.matchSource ?? "none"}` : "matchKey=none matchSource=none";
|
|
2481
|
+
if (isConfiguredBotSender && allowBotsMode === "off") {
|
|
2482
|
+
logVerboseMessage(`matrix: drop configured bot sender=${senderId} (allowBots=false${isDirectMessage ? "" : `, ${roomMatchMeta}`})`);
|
|
2483
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
const botLoopProtection = isConfiguredBotSender && senderId !== selfUserId ? {
|
|
2487
|
+
scopeId: accountId,
|
|
2488
|
+
conversationId: roomId,
|
|
2489
|
+
senderId,
|
|
2490
|
+
receiverId: selfUserId,
|
|
2491
|
+
config: mergePairLoopGuardConfig(accountConfig?.botLoopProtection, roomConfig?.botLoopProtection),
|
|
2492
|
+
defaultsConfig: cfg.channels?.defaults?.botLoopProtection,
|
|
2493
|
+
defaultEnabled: true,
|
|
2494
|
+
nowMs: eventTs ?? void 0
|
|
2495
|
+
} : void 0;
|
|
2496
|
+
if (isRoom && roomConfig && !roomConfigInfo?.allowed) {
|
|
2497
|
+
logVerboseMessage(`matrix: room disabled room=${roomId} (${roomMatchMeta})`);
|
|
2498
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
if (isRoom && groupPolicy === "allowlist") {
|
|
2502
|
+
if (!roomConfigInfo?.allowlistConfigured) {
|
|
2503
|
+
logVerboseMessage(`matrix: drop room message (no allowlist, ${roomMatchMeta})`);
|
|
2504
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
if (!roomConfig) {
|
|
2508
|
+
logVerboseMessage(`matrix: drop room message (not in allowlist, ${roomMatchMeta})`);
|
|
2509
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
let senderNamePromise = null;
|
|
2514
|
+
const getSenderName = async () => {
|
|
2515
|
+
senderNamePromise ??= getMemberDisplayName(roomId, senderId).catch(() => senderId);
|
|
2516
|
+
return await senderNamePromise;
|
|
2517
|
+
};
|
|
2518
|
+
const storeAllowFrom = isDirectMessage && dmPolicy !== "allowlist" && dmPolicy !== "open" ? await readStoreAllowFrom() : [];
|
|
2519
|
+
const roomUsers = roomConfig?.users ?? [];
|
|
2520
|
+
const { liveDmAllowFrom, liveGroupAllowFrom } = await resolveLiveAccountAllowlists();
|
|
2521
|
+
const accessState = await resolveMatrixMonitorAccessState({
|
|
2522
|
+
allowFrom: liveDmAllowFrom,
|
|
2523
|
+
storeAllowFrom,
|
|
2524
|
+
dmPolicy,
|
|
2525
|
+
groupPolicy,
|
|
2526
|
+
groupAllowFrom: liveGroupAllowFrom,
|
|
2527
|
+
roomUsers,
|
|
2528
|
+
senderId,
|
|
2529
|
+
isRoom,
|
|
2530
|
+
accountId,
|
|
2531
|
+
eventKind: isReactionEvent ? "reaction" : "message"
|
|
2532
|
+
});
|
|
2533
|
+
const { effectiveGroupAllowFrom, effectiveRoomUsers, messageIngress } = accessState;
|
|
2534
|
+
const ingressDecision = messageIngress.ingress;
|
|
2535
|
+
if (isDirectMessage) {
|
|
2536
|
+
if (!dmEnabled || dmPolicy === "disabled") {
|
|
2537
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
const senderReason = messageIngress.senderAccess.reasonCode;
|
|
2541
|
+
if (ingressDecision.decision !== "allow") {
|
|
2542
|
+
if (ingressDecision.admission === "pairing-required") {
|
|
2543
|
+
const senderName = await getSenderName();
|
|
2544
|
+
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
|
2545
|
+
channel: "matrix",
|
|
2546
|
+
id: senderId,
|
|
2547
|
+
accountId,
|
|
2548
|
+
meta: { name: senderName }
|
|
2549
|
+
});
|
|
2550
|
+
if (shouldSendPairingReply(senderId, created)) {
|
|
2551
|
+
const pairingReply = core.channel.pairing.buildPairingReply({
|
|
2552
|
+
channel: "matrix",
|
|
2553
|
+
idLine: `Your Matrix user id: ${senderId}`,
|
|
2554
|
+
code
|
|
2555
|
+
});
|
|
2556
|
+
logVerboseMessage(created ? `matrix pairing request sender=${senderId} name=${senderName ?? "unknown"} (reason=${senderReason})` : `matrix pairing reminder sender=${senderId} name=${senderName ?? "unknown"} (reason=${senderReason})`);
|
|
2557
|
+
try {
|
|
2558
|
+
const { sendMessageMatrix } = await loadMatrixSendModule();
|
|
2559
|
+
await sendMessageMatrix(`room:${roomId}`, created ? pairingReply : `${pairingReply}\n\nPairing request is still pending approval. Reusing existing code.`, {
|
|
2560
|
+
client,
|
|
2561
|
+
cfg,
|
|
2562
|
+
accountId
|
|
2563
|
+
});
|
|
2564
|
+
await commitInboundEventIfClaimed();
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
logVerboseMessage(`matrix pairing reply failed for ${senderId}: ${String(err)}`);
|
|
2567
|
+
discardReservedHistorySlot();
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
} else {
|
|
2571
|
+
logVerboseMessage(`matrix pairing reminder suppressed sender=${senderId} (cooldown)`);
|
|
2572
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (isReactionEvent || dmPolicy !== "pairing") {
|
|
2576
|
+
logVerboseMessage(`matrix: blocked ${isReactionEvent ? "reaction" : "dm"} sender ${senderId} (dmPolicy=${dmPolicy}, reason=${senderReason})`);
|
|
2577
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2578
|
+
}
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
if (isRoom && ingressDecision.decision !== "allow") {
|
|
2583
|
+
logVerboseMessage(`matrix: blocked sender ${senderId} (ingress=${ingressDecision.reasonCode}, ${roomMatchMeta})`);
|
|
2584
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
if (isRoom) logVerboseMessage(`matrix: allow room ${roomId} (${roomMatchMeta})`);
|
|
2588
|
+
if (isReactionEvent) {
|
|
2589
|
+
const senderName = await getSenderName();
|
|
2590
|
+
const { handleInboundMatrixReaction } = await loadMatrixReactionEvents();
|
|
2591
|
+
await handleInboundMatrixReaction({
|
|
2592
|
+
client,
|
|
2593
|
+
core,
|
|
2594
|
+
cfg,
|
|
2595
|
+
accountId,
|
|
2596
|
+
roomId,
|
|
2597
|
+
event,
|
|
2598
|
+
senderId,
|
|
2599
|
+
senderLabel: senderName,
|
|
2600
|
+
selfUserId,
|
|
2601
|
+
isDirectMessage,
|
|
2602
|
+
logVerboseMessage
|
|
2603
|
+
});
|
|
2604
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
return {
|
|
2608
|
+
content,
|
|
2609
|
+
messageId,
|
|
2610
|
+
audioPreflightMode,
|
|
2611
|
+
isDirectMessage,
|
|
2612
|
+
isRoom,
|
|
2613
|
+
locationPayload,
|
|
2614
|
+
reservedHistorySlot,
|
|
2615
|
+
threadRootId,
|
|
2616
|
+
thread,
|
|
2617
|
+
historyThreadId,
|
|
2618
|
+
discardReservedHistorySlot,
|
|
2619
|
+
markReservedHistorySlotConsumed,
|
|
2620
|
+
commitInboundEventIfClaimedAndDiscardReserved,
|
|
2621
|
+
roomConfig,
|
|
2622
|
+
allowBotsMode,
|
|
2623
|
+
isConfiguredBotSender,
|
|
2624
|
+
selfUserId,
|
|
2625
|
+
botLoopProtection,
|
|
2626
|
+
roomMatchMeta,
|
|
2627
|
+
getSenderName,
|
|
2628
|
+
accessState,
|
|
2629
|
+
effectiveGroupAllowFrom,
|
|
2630
|
+
effectiveRoomUsers
|
|
2631
|
+
};
|
|
2150
2632
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2633
|
+
//#endregion
|
|
2634
|
+
//#region extensions/matrix/src/matrix/monitor/media.ts
|
|
2635
|
+
const MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS = 3e4;
|
|
2636
|
+
async function fetchMatrixMediaBuffer(params) {
|
|
2637
|
+
try {
|
|
2638
|
+
return { buffer: await params.client.downloadContent(params.mxcUrl, {
|
|
2639
|
+
maxBytes: params.maxBytes,
|
|
2640
|
+
readIdleTimeoutMs: MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS
|
|
2641
|
+
}) };
|
|
2642
|
+
} catch (err) {
|
|
2643
|
+
if (isMatrixMediaSizeLimitError(err)) throw err;
|
|
2644
|
+
throw new Error(`Matrix media download failed: ${String(err)}`, { cause: err });
|
|
2645
|
+
}
|
|
2159
2646
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2647
|
+
/**
|
|
2648
|
+
* Download and decrypt encrypted media from a Matrix room.
|
|
2649
|
+
* Uses the Matrix crypto adapter's decryptMedia helper.
|
|
2650
|
+
*/
|
|
2651
|
+
async function fetchEncryptedMediaBuffer(params) {
|
|
2652
|
+
if (!params.client.crypto) throw new Error("Cannot decrypt media: crypto not enabled");
|
|
2653
|
+
const decrypted = await params.client.crypto.decryptMedia(params.file, {
|
|
2654
|
+
maxBytes: params.maxBytes,
|
|
2655
|
+
readIdleTimeoutMs: MATRIX_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS
|
|
2656
|
+
});
|
|
2657
|
+
if (decrypted.byteLength > params.maxBytes) throw new MatrixMediaSizeLimitError();
|
|
2658
|
+
return { buffer: decrypted };
|
|
2165
2659
|
}
|
|
2166
|
-
function
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2660
|
+
async function downloadMatrixMedia(params) {
|
|
2661
|
+
let fetched;
|
|
2662
|
+
if (typeof params.sizeBytes === "number" && params.sizeBytes > params.maxBytes) throw new MatrixMediaSizeLimitError();
|
|
2663
|
+
if (params.file) fetched = await fetchEncryptedMediaBuffer({
|
|
2664
|
+
client: params.client,
|
|
2665
|
+
file: params.file,
|
|
2666
|
+
maxBytes: params.maxBytes
|
|
2667
|
+
});
|
|
2668
|
+
else fetched = await fetchMatrixMediaBuffer({
|
|
2669
|
+
client: params.client,
|
|
2670
|
+
mxcUrl: params.mxcUrl,
|
|
2671
|
+
maxBytes: params.maxBytes
|
|
2672
|
+
});
|
|
2673
|
+
if (!fetched) return null;
|
|
2674
|
+
const headerType = params.contentType ?? void 0;
|
|
2675
|
+
const saved = await getMatrixRuntime().channel.media.saveMediaBuffer(fetched.buffer, headerType, "inbound", params.maxBytes, params.originalFilename);
|
|
2676
|
+
return {
|
|
2677
|
+
path: saved.path,
|
|
2678
|
+
contentType: saved.contentType,
|
|
2679
|
+
placeholder: "[matrix media]"
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
//#endregion
|
|
2683
|
+
//#region extensions/matrix/src/matrix/monitor/mentions.ts
|
|
2684
|
+
const MATRIX_HTML_ENTITY_RE = /&(?:#x?[0-9a-f]+|amp|apos|gt|lt|nbsp|quot);/gi;
|
|
2685
|
+
function decodeVisibleHtmlEntities(value) {
|
|
2686
|
+
return value.replace(MATRIX_HTML_ENTITY_RE, (entity) => {
|
|
2687
|
+
const decoded = decodeHtmlEntities(entity.startsWith("&#") ? entity : entity.toLowerCase());
|
|
2688
|
+
return decoded === "\xA0" ? " " : decoded;
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
function normalizeVisibleMentionText(value) {
|
|
2692
|
+
return normalizeLowercaseStringOrEmpty(decodeVisibleHtmlEntities(value.replace(/<[^>]+>/g, " ").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")).replace(/\s+/g, " "));
|
|
2693
|
+
}
|
|
2694
|
+
function extractVisibleMentionText(value) {
|
|
2695
|
+
return normalizeVisibleMentionText(value ?? "");
|
|
2696
|
+
}
|
|
2697
|
+
function resolveMatrixUserLocalpart(userId) {
|
|
2698
|
+
const trimmed = userId.trim();
|
|
2699
|
+
if (!trimmed.startsWith("@")) return null;
|
|
2700
|
+
const colonIndex = trimmed.indexOf(":");
|
|
2701
|
+
if (colonIndex <= 1) return null;
|
|
2702
|
+
return trimmed.slice(1, colonIndex).trim() || null;
|
|
2703
|
+
}
|
|
2704
|
+
function resolveMatrixMentionPrefixCandidates(params) {
|
|
2705
|
+
const candidates = [];
|
|
2706
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2707
|
+
const append = (candidate) => {
|
|
2708
|
+
const trimmed = candidate?.trim();
|
|
2709
|
+
if (!trimmed) return;
|
|
2710
|
+
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
|
|
2711
|
+
if (seen.has(normalized)) return;
|
|
2712
|
+
seen.add(normalized);
|
|
2713
|
+
candidates.push(trimmed);
|
|
2714
|
+
};
|
|
2715
|
+
append(params.userId);
|
|
2716
|
+
const localpart = params.userId ? resolveMatrixUserLocalpart(params.userId) : null;
|
|
2717
|
+
append(localpart ? `@${localpart}` : null);
|
|
2718
|
+
append(params.displayName);
|
|
2719
|
+
append(params.displayName ? `@${params.displayName}` : null);
|
|
2720
|
+
append(params.displayName ? `@[${params.displayName}]` : null);
|
|
2721
|
+
return candidates;
|
|
2722
|
+
}
|
|
2723
|
+
function stripMatchedMatrixMentionPrefix(text, pattern) {
|
|
2724
|
+
const match = text.match(pattern);
|
|
2725
|
+
if (!match) return null;
|
|
2726
|
+
return text.slice(match[0].length).trimStart();
|
|
2727
|
+
}
|
|
2728
|
+
function stripNativeMatrixMentionPrefix(text, candidate) {
|
|
2729
|
+
return stripMatchedMatrixMentionPrefix(text, new RegExp(`^\\s*${escapeRegExp(candidate)}(?:\\s*[:,])?(?:\\s+|$)`, "i"));
|
|
2730
|
+
}
|
|
2731
|
+
function stripRegexMatrixMentionPrefix(text, pattern) {
|
|
2732
|
+
const flags = pattern.flags.replace(/[gy]/g, "");
|
|
2733
|
+
return stripMatchedMatrixMentionPrefix(text, new RegExp(`^\\s*(?:${pattern.source})(?:\\s*[:,])?(?:\\s+|$)`, flags));
|
|
2734
|
+
}
|
|
2735
|
+
function stripMatrixMentionPrefix(params) {
|
|
2736
|
+
const text = params.text;
|
|
2737
|
+
if (!text) return text;
|
|
2738
|
+
for (const candidate of resolveMatrixMentionPrefixCandidates(params)) {
|
|
2739
|
+
const stripped = stripNativeMatrixMentionPrefix(text, candidate);
|
|
2740
|
+
if (stripped !== null) return stripped;
|
|
2741
|
+
}
|
|
2742
|
+
for (const pattern of params.mentionRegexes ?? []) {
|
|
2743
|
+
const stripped = stripRegexMatrixMentionPrefix(text, pattern);
|
|
2744
|
+
if (stripped !== null) return stripped;
|
|
2745
|
+
}
|
|
2746
|
+
return text;
|
|
2747
|
+
}
|
|
2748
|
+
function isVisibleMentionLabel(params) {
|
|
2749
|
+
const cleaned = extractVisibleMentionText(params.text);
|
|
2750
|
+
if (!cleaned) return false;
|
|
2751
|
+
if (params.mentionRegexes.some((pattern) => pattern.test(cleaned))) return true;
|
|
2752
|
+
const localpart = resolveMatrixUserLocalpart(params.userId);
|
|
2753
|
+
return [
|
|
2754
|
+
extractVisibleMentionText(params.userId),
|
|
2755
|
+
localpart ? extractVisibleMentionText(localpart) : null,
|
|
2756
|
+
localpart ? extractVisibleMentionText(`@${localpart}`) : null,
|
|
2757
|
+
params.displayName ? extractVisibleMentionText(params.displayName) : null,
|
|
2758
|
+
params.displayName ? extractVisibleMentionText(`@${params.displayName}`) : null,
|
|
2759
|
+
params.displayName ? extractVisibleMentionText(`@[${params.displayName}]`) : null
|
|
2760
|
+
].filter((value) => Boolean(value)).includes(cleaned);
|
|
2761
|
+
}
|
|
2762
|
+
function hasVisibleRoomMention(value) {
|
|
2763
|
+
const cleaned = extractVisibleMentionText(value);
|
|
2764
|
+
return /(^|[^a-z0-9_])@room\b/i.test(cleaned);
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Check if formatted_body contains a matrix.to link whose visible label still
|
|
2768
|
+
* looks like a real mention for the given user. Do not trust href alone, since
|
|
2769
|
+
* senders can hide arbitrary matrix.to links behind unrelated link text.
|
|
2770
|
+
* Many Matrix clients (including Element) use HTML links in formatted_body instead of
|
|
2771
|
+
* or in addition to the m.mentions field.
|
|
2772
|
+
*/
|
|
2773
|
+
function checkFormattedBodyMention(params) {
|
|
2774
|
+
if (!params.formattedBody || !params.userId) return false;
|
|
2775
|
+
for (const match of params.formattedBody.matchAll(/<a\b[^>]*href=(["'])(https:\/\/matrix\.to\/#[^"']+)\1[^>]*>(.*?)<\/a>/gis)) {
|
|
2776
|
+
const href = match[2];
|
|
2777
|
+
const visibleLabel = match[3] ?? "";
|
|
2778
|
+
if (!href) continue;
|
|
2779
|
+
try {
|
|
2780
|
+
const parsed = new URL(href);
|
|
2781
|
+
if (decodeURIComponent(parsed.hash.replace(/^#\/?/, "").trim()) !== params.userId.trim()) continue;
|
|
2782
|
+
if (isVisibleMentionLabel({
|
|
2783
|
+
text: visibleLabel,
|
|
2784
|
+
userId: params.userId,
|
|
2785
|
+
mentionRegexes: params.mentionRegexes,
|
|
2786
|
+
displayName: params.displayName
|
|
2787
|
+
})) return true;
|
|
2788
|
+
} catch {
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
return false;
|
|
2793
|
+
}
|
|
2794
|
+
function resolveMentions(params) {
|
|
2795
|
+
const mentions = params.content["m.mentions"];
|
|
2796
|
+
const mentionedUsers = Array.isArray(mentions?.user_ids) ? new Set(mentions.user_ids) : /* @__PURE__ */ new Set();
|
|
2797
|
+
const textMentioned = getMatrixRuntime().channel.mentions.matchesMentionPatterns(params.text ?? "", params.mentionRegexes);
|
|
2798
|
+
const visibleRoomMention = hasVisibleRoomMention(params.text) || hasVisibleRoomMention(params.content.formatted_body);
|
|
2799
|
+
const mentionedInFormattedBody = params.userId ? checkFormattedBodyMention({
|
|
2800
|
+
formattedBody: params.content.formatted_body,
|
|
2801
|
+
userId: params.userId,
|
|
2802
|
+
displayName: params.displayName,
|
|
2803
|
+
mentionRegexes: params.mentionRegexes
|
|
2804
|
+
}) : false;
|
|
2805
|
+
const metadataBackedUserMention = Boolean(params.userId && mentionedUsers.has(params.userId) && (mentionedInFormattedBody || textMentioned));
|
|
2806
|
+
const metadataBackedRoomMention = Boolean(mentions?.room) && visibleRoomMention;
|
|
2807
|
+
const explicitMention = mentionedInFormattedBody || metadataBackedUserMention || metadataBackedRoomMention;
|
|
2808
|
+
return {
|
|
2809
|
+
wasMentioned: explicitMention || textMentioned || visibleRoomMention,
|
|
2810
|
+
hasExplicitMention: explicitMention
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
//#endregion
|
|
2814
|
+
//#region extensions/matrix/src/matrix/monitor/handler-ingress-content.ts
|
|
2815
|
+
async function resolveMatrixIngressContent(config) {
|
|
2816
|
+
const { handler, params: paramsLocal, access, roomId, event, eventType, isPollEvent, eventTs, senderId, roomHistoryTracker, commitInboundEventIfClaimed } = config;
|
|
2817
|
+
const { client, core, cfg, accountId, accountConfig, logger, logVerboseMessage, historyLimit, mediaMaxBytes, dmSessionScope, getMemberDisplayName } = handler;
|
|
2818
|
+
const { content: accessContent, messageId, audioPreflightMode, isDirectMessage, isRoom, locationPayload, reservedHistorySlot, threadRootId, thread, historyThreadId, discardReservedHistorySlot, markReservedHistorySlotConsumed, commitInboundEventIfClaimedAndDiscardReserved, roomConfig, allowBotsMode, isConfiguredBotSender, selfUserId, botLoopProtection, roomMatchMeta, getSenderName, accessState, effectiveGroupAllowFrom, effectiveRoomUsers } = access;
|
|
2819
|
+
let content = accessContent;
|
|
2820
|
+
let pollSnapshotPromise = null;
|
|
2821
|
+
const getPollSnapshot = async () => {
|
|
2822
|
+
if (!isPollEvent) return null;
|
|
2823
|
+
pollSnapshotPromise ??= fetchMatrixPollSnapshot(client, roomId, event).catch((err) => {
|
|
2824
|
+
logVerboseMessage(`matrix: failed resolving poll snapshot room=${roomId} id=${event.event_id ?? "unknown"}: ${String(err)}`);
|
|
2825
|
+
return null;
|
|
2826
|
+
});
|
|
2827
|
+
return await pollSnapshotPromise;
|
|
2828
|
+
};
|
|
2829
|
+
const mentionPrecheckText = resolveMatrixMentionPrecheckText({
|
|
2830
|
+
eventType,
|
|
2831
|
+
content,
|
|
2832
|
+
locationText: locationPayload?.text
|
|
2833
|
+
});
|
|
2834
|
+
const contentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
2835
|
+
const contentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
2836
|
+
const mediaUrl = contentUrl ?? contentFile?.url;
|
|
2837
|
+
const earlyContentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
2838
|
+
const earlyContentType = earlyContentInfo?.mimetype;
|
|
2839
|
+
const earlyContentSize = typeof earlyContentInfo?.size === "number" ? earlyContentInfo.size : void 0;
|
|
2840
|
+
const earlyContentBody = typeof content.body === "string" ? content.body.trim() : "";
|
|
2841
|
+
const earlyOriginalFilename = (typeof content.filename === "string" ? content.filename.trim() : "") || earlyContentBody || void 0;
|
|
2842
|
+
const pendingHistoryText = resolveMatrixPendingHistoryText({
|
|
2843
|
+
mentionPrecheckText,
|
|
2844
|
+
content,
|
|
2845
|
+
mediaUrl
|
|
2846
|
+
});
|
|
2847
|
+
const pendingHistoryPollText = !pendingHistoryText && isPollEvent && historyLimit > 0 ? (await getPollSnapshot())?.text : "";
|
|
2848
|
+
if (!mentionPrecheckText && !mediaUrl && !isPollEvent) {
|
|
2849
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
let preflightMedia = null;
|
|
2853
|
+
let preflightMediaDownloadFailed = false;
|
|
2854
|
+
let preflightMediaSizeLimitExceeded = false;
|
|
2855
|
+
let preflightAudioTranscript;
|
|
2856
|
+
const { route: _route, configuredBinding: _configuredBinding, runtimeBindingId: _runtimeBindingId } = resolveMatrixInboundRoute({
|
|
2857
|
+
cfg,
|
|
2858
|
+
accountId,
|
|
2859
|
+
roomId,
|
|
2860
|
+
senderId,
|
|
2861
|
+
isDirectMessage,
|
|
2862
|
+
dmSessionScope,
|
|
2863
|
+
threadId: thread.threadId,
|
|
2864
|
+
eventTs: eventTs ?? void 0,
|
|
2865
|
+
resolveAgentRoute: core.channel.routing.resolveAgentRoute
|
|
2866
|
+
});
|
|
2867
|
+
const hasExplicitSessionBinding = _configuredBinding !== null || _runtimeBindingId !== null;
|
|
2868
|
+
const preflightAudioMediaUrl = mediaUrl?.startsWith("mxc://") ? mediaUrl : void 0;
|
|
2869
|
+
const shouldRunMatrixAudioPreflight = isMatrixAudioContent({
|
|
2870
|
+
msgtype: typeof content.msgtype === "string" ? content.msgtype : void 0,
|
|
2871
|
+
mimetype: earlyContentType
|
|
2872
|
+
}) && isMatrixAudioMediaEnabled(cfg) && preflightAudioMediaUrl !== void 0;
|
|
2873
|
+
if (shouldRunMatrixAudioPreflight && audioPreflightMode === "defer" && isRoom && historyLimit > 0 && !reservedHistorySlot) {
|
|
2874
|
+
const reserved = roomHistoryTracker.reservePending(_route.agentId, roomId, {
|
|
2875
|
+
sender: senderId,
|
|
2876
|
+
body: pendingHistoryText,
|
|
2877
|
+
timestamp: eventTs ?? void 0,
|
|
2878
|
+
messageId
|
|
2879
|
+
}, historyThreadId);
|
|
2880
|
+
return { deferredPrefix: {
|
|
2881
|
+
...paramsLocal,
|
|
2882
|
+
audioPreflightMode: "run",
|
|
2883
|
+
reservedHistorySlot: reserved
|
|
2884
|
+
} };
|
|
2885
|
+
}
|
|
2886
|
+
if (shouldRunMatrixAudioPreflight) {
|
|
2887
|
+
try {
|
|
2888
|
+
preflightMedia = await downloadMatrixMedia({
|
|
2889
|
+
client,
|
|
2890
|
+
mxcUrl: preflightAudioMediaUrl,
|
|
2891
|
+
contentType: earlyContentType,
|
|
2892
|
+
sizeBytes: earlyContentSize,
|
|
2893
|
+
maxBytes: mediaMaxBytes,
|
|
2894
|
+
file: contentFile,
|
|
2895
|
+
originalFilename: earlyOriginalFilename
|
|
2896
|
+
});
|
|
2897
|
+
} catch (err) {
|
|
2898
|
+
preflightMediaDownloadFailed = true;
|
|
2899
|
+
if (isMatrixMediaSizeLimitError(err)) preflightMediaSizeLimitExceeded = true;
|
|
2900
|
+
const errorText = formatMatrixErrorMessage(err);
|
|
2901
|
+
logVerboseMessage(`matrix: media download failed room=${roomId} id=${event.event_id ?? "unknown"} type=${content.msgtype} error=${errorText}`);
|
|
2902
|
+
logger.warn("matrix media download failed", {
|
|
2903
|
+
roomId,
|
|
2904
|
+
eventId: event.event_id,
|
|
2905
|
+
msgtype: content.msgtype,
|
|
2906
|
+
encrypted: Boolean(contentFile),
|
|
2907
|
+
error: errorText
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
if (preflightMedia) preflightAudioTranscript = await resolveMatrixPreflightAudioTranscript({
|
|
2911
|
+
mediaPath: preflightMedia.path,
|
|
2912
|
+
mediaContentType: preflightMedia.contentType,
|
|
2913
|
+
cfg,
|
|
2914
|
+
accountId,
|
|
2915
|
+
chatType: isDirectMessage ? "direct" : "channel",
|
|
2916
|
+
originatingTo: `room:${roomId}`,
|
|
2917
|
+
messageThreadId: thread.threadId,
|
|
2918
|
+
sessionKey: _route.sessionKey
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId, {
|
|
2922
|
+
provider: "matrix",
|
|
2923
|
+
conversationId: roomId,
|
|
2924
|
+
providerPolicy: accountConfig?.mentionPatterns
|
|
2925
|
+
});
|
|
2926
|
+
const selfDisplayName = content.formatted_body ? await getMemberDisplayName(roomId, selfUserId).catch(() => void 0) : void 0;
|
|
2927
|
+
const mentionPrecheckTextWithTranscript = preflightAudioTranscript ? [mentionPrecheckText, preflightAudioTranscript].filter(Boolean).join("\n").trim() : mentionPrecheckText;
|
|
2928
|
+
const { wasMentioned, hasExplicitMention } = resolveMentions({
|
|
2929
|
+
content,
|
|
2930
|
+
userId: selfUserId,
|
|
2931
|
+
displayName: selfDisplayName,
|
|
2932
|
+
text: mentionPrecheckTextWithTranscript,
|
|
2933
|
+
mentionRegexes: agentMentionRegexes
|
|
2934
|
+
});
|
|
2935
|
+
if (isConfiguredBotSender && allowBotsMode === "mentions" && !isDirectMessage && !wasMentioned) {
|
|
2936
|
+
logVerboseMessage(`matrix: drop configured bot sender=${senderId} (allowBots=mentions, missing mention, ${roomMatchMeta})`);
|
|
2937
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
2941
|
+
cfg,
|
|
2942
|
+
surface: "matrix"
|
|
2943
|
+
});
|
|
2944
|
+
const useAccessGroups = true;
|
|
2945
|
+
const commandCheckText = stripMatrixMentionPrefix({
|
|
2946
|
+
text: mentionPrecheckText,
|
|
2947
|
+
userId: selfUserId,
|
|
2948
|
+
displayName: selfDisplayName,
|
|
2949
|
+
mentionRegexes: agentMentionRegexes
|
|
2950
|
+
});
|
|
2951
|
+
const hasControlCommandInMessage = core.channel.text.hasControlCommand(commandCheckText, cfg);
|
|
2952
|
+
const commandAccess = await resolveMatrixMonitorCommandAccess(accessState, {
|
|
2953
|
+
useAccessGroups,
|
|
2954
|
+
allowTextCommands,
|
|
2955
|
+
hasControlCommand: hasControlCommandInMessage
|
|
2956
|
+
});
|
|
2957
|
+
const commandAuthorized = commandAccess.authorized;
|
|
2958
|
+
if (isRoom && commandAccess.shouldBlockControlCommand) {
|
|
2959
|
+
logInboundDrop({
|
|
2960
|
+
log: logVerboseMessage,
|
|
2961
|
+
channel: "matrix",
|
|
2962
|
+
reason: "control command (unauthorized)",
|
|
2963
|
+
target: senderId
|
|
2964
|
+
});
|
|
2965
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
const shouldRequireMention = isRoom ? roomConfig?.autoReply === true ? false : roomConfig?.autoReply === false ? true : typeof roomConfig?.requireMention === "boolean" ? roomConfig?.requireMention : true : false;
|
|
2969
|
+
const mentionDecision = resolveInboundMentionDecision({
|
|
2970
|
+
facts: {
|
|
2971
|
+
canDetectMention: true,
|
|
2972
|
+
wasMentioned,
|
|
2973
|
+
hasAnyMention: hasExplicitMention
|
|
2974
|
+
},
|
|
2975
|
+
policy: {
|
|
2976
|
+
isGroup: isRoom,
|
|
2977
|
+
requireMention: shouldRequireMention,
|
|
2978
|
+
allowTextCommands,
|
|
2979
|
+
hasControlCommand: hasControlCommandInMessage,
|
|
2980
|
+
commandAuthorized
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2983
|
+
const { effectiveWasMentioned, shouldBypassMention } = mentionDecision;
|
|
2984
|
+
const canDetectMention = agentMentionRegexes.length > 0 || hasExplicitMention;
|
|
2985
|
+
if (mentionDecision.shouldSkip) {
|
|
2986
|
+
const pendingHistoryBody = preflightAudioTranscript ? formatMatrixAudioTranscript(preflightAudioTranscript) : pendingHistoryText || pendingHistoryPollText;
|
|
2987
|
+
if (historyLimit > 0 && pendingHistoryBody) {
|
|
2988
|
+
const pendingEntry = {
|
|
2989
|
+
sender: senderId,
|
|
2990
|
+
body: pendingHistoryBody,
|
|
2991
|
+
timestamp: eventTs ?? void 0,
|
|
2992
|
+
messageId
|
|
2993
|
+
};
|
|
2994
|
+
if (reservedHistorySlot) {
|
|
2995
|
+
roomHistoryTracker.finalizePending(roomId, reservedHistorySlot, pendingEntry, historyThreadId);
|
|
2996
|
+
markReservedHistorySlotConsumed();
|
|
2997
|
+
} else roomHistoryTracker.recordPending(roomId, pendingEntry, historyThreadId);
|
|
2998
|
+
}
|
|
2999
|
+
logger.info("skipping room message", {
|
|
3000
|
+
roomId,
|
|
3001
|
+
reason: "no-mention"
|
|
3002
|
+
});
|
|
3003
|
+
await commitInboundEventIfClaimed();
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
if (preflightAudioTranscript) await sendMatrixPreflightAudioTranscriptEcho({
|
|
3007
|
+
transcript: preflightAudioTranscript,
|
|
3008
|
+
cfg,
|
|
3009
|
+
accountId,
|
|
3010
|
+
originatingTo: `room:${roomId}`,
|
|
3011
|
+
messageThreadId: thread.threadId
|
|
3012
|
+
});
|
|
3013
|
+
if (isPollEvent) {
|
|
3014
|
+
const pollSnapshot = await getPollSnapshot();
|
|
3015
|
+
if (!pollSnapshot) {
|
|
3016
|
+
discardReservedHistorySlot();
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
content = {
|
|
3020
|
+
msgtype: "m.text",
|
|
3021
|
+
body: pollSnapshot.text
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
let media = preflightMedia;
|
|
3025
|
+
let mediaDownloadFailed = preflightMediaDownloadFailed;
|
|
3026
|
+
let mediaSizeLimitExceeded = preflightMediaSizeLimitExceeded;
|
|
3027
|
+
const finalContentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
3028
|
+
const finalContentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
3029
|
+
const finalMediaUrl = finalContentUrl ?? finalContentFile?.url;
|
|
3030
|
+
const contentBody = typeof content.body === "string" ? content.body.trim() : "";
|
|
3031
|
+
const originalFilename = (typeof content.filename === "string" ? content.filename.trim() : "") || contentBody || void 0;
|
|
3032
|
+
const contentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
3033
|
+
const contentType = contentInfo?.mimetype;
|
|
3034
|
+
const contentSize = typeof contentInfo?.size === "number" ? contentInfo.size : void 0;
|
|
3035
|
+
if (!media && !mediaDownloadFailed && finalMediaUrl?.startsWith("mxc://")) try {
|
|
3036
|
+
media = await downloadMatrixMedia({
|
|
3037
|
+
client,
|
|
3038
|
+
mxcUrl: finalMediaUrl,
|
|
3039
|
+
contentType,
|
|
3040
|
+
sizeBytes: contentSize,
|
|
3041
|
+
maxBytes: mediaMaxBytes,
|
|
3042
|
+
file: finalContentFile,
|
|
3043
|
+
originalFilename
|
|
3044
|
+
});
|
|
3045
|
+
} catch (err) {
|
|
3046
|
+
mediaDownloadFailed = true;
|
|
3047
|
+
if (isMatrixMediaSizeLimitError(err)) mediaSizeLimitExceeded = true;
|
|
3048
|
+
const errorText = formatMatrixErrorMessage(err);
|
|
3049
|
+
logVerboseMessage(`matrix: media download failed room=${roomId} id=${event.event_id ?? "unknown"} type=${content.msgtype} error=${errorText}`);
|
|
3050
|
+
logger.warn("matrix media download failed", {
|
|
3051
|
+
roomId,
|
|
3052
|
+
eventId: event.event_id,
|
|
3053
|
+
msgtype: content.msgtype,
|
|
3054
|
+
encrypted: Boolean(finalContentFile),
|
|
3055
|
+
error: errorText
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
let bodyText = resolveMatrixInboundBodyText({
|
|
3059
|
+
rawBody: locationPayload?.text ?? contentBody,
|
|
3060
|
+
filename: typeof content.filename === "string" ? content.filename : void 0,
|
|
3061
|
+
mediaPlaceholder: media?.placeholder,
|
|
3062
|
+
msgtype: content.msgtype,
|
|
3063
|
+
hadMediaUrl: Boolean(finalMediaUrl),
|
|
3064
|
+
mediaDownloadFailed,
|
|
3065
|
+
mediaSizeLimitExceeded
|
|
3066
|
+
});
|
|
3067
|
+
if (preflightMedia && bodyText && bodyText !== preflightMedia.placeholder && isLikelyBareFilename(bodyText)) bodyText = preflightMedia.placeholder;
|
|
3068
|
+
if (preflightAudioTranscript) {
|
|
3069
|
+
const transcriptBody = formatMatrixAudioTranscript(preflightAudioTranscript);
|
|
3070
|
+
bodyText = !bodyText || bodyText === media?.placeholder ? transcriptBody : `${bodyText}\n${transcriptBody}`;
|
|
3071
|
+
}
|
|
3072
|
+
if (!bodyText) {
|
|
3073
|
+
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
3076
|
+
const commandBodyText = hasControlCommandInMessage ? commandCheckText : bodyText;
|
|
3077
|
+
const senderName = await getSenderName();
|
|
3078
|
+
if (_configuredBinding) {
|
|
3079
|
+
const { ensureConfiguredAcpBindingReady } = await loadAcpBindingRuntime();
|
|
3080
|
+
if (!(await ensureConfiguredAcpBindingReady({
|
|
3081
|
+
cfg,
|
|
3082
|
+
configuredBinding: _configuredBinding
|
|
3083
|
+
})).ok) {
|
|
3084
|
+
logInboundDrop({
|
|
3085
|
+
log: logVerboseMessage,
|
|
3086
|
+
channel: "matrix",
|
|
3087
|
+
reason: "configured ACP binding unavailable",
|
|
3088
|
+
target: _configuredBinding.spec.conversationId
|
|
3089
|
+
});
|
|
3090
|
+
discardReservedHistorySlot();
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
if (_runtimeBindingId) {
|
|
3095
|
+
const { getSessionBindingService } = await loadSessionBindingRuntime();
|
|
3096
|
+
getSessionBindingService().touch(_runtimeBindingId, eventTs ?? void 0);
|
|
3097
|
+
}
|
|
3098
|
+
const preparedTrigger = isRoom && historyLimit > 0 ? reservedHistorySlot ? roomHistoryTracker.prepareReservedTrigger(_route.agentId, roomId, historyLimit, reservedHistorySlot, {
|
|
3099
|
+
sender: senderName,
|
|
3100
|
+
body: bodyText,
|
|
3101
|
+
timestamp: eventTs ?? void 0,
|
|
3102
|
+
messageId
|
|
3103
|
+
}, historyThreadId) : roomHistoryTracker.prepareTrigger(_route.agentId, roomId, historyLimit, {
|
|
3104
|
+
sender: senderName,
|
|
3105
|
+
body: bodyText,
|
|
3106
|
+
timestamp: eventTs ?? void 0,
|
|
3107
|
+
messageId
|
|
3108
|
+
}, historyThreadId) : void 0;
|
|
3109
|
+
if (reservedHistorySlot && preparedTrigger) markReservedHistorySlotConsumed();
|
|
3110
|
+
return {
|
|
3111
|
+
route: _route,
|
|
3112
|
+
hasExplicitSessionBinding,
|
|
3113
|
+
roomConfig,
|
|
3114
|
+
isDirectMessage,
|
|
3115
|
+
isRoom,
|
|
3116
|
+
shouldRequireMention,
|
|
3117
|
+
wasMentioned,
|
|
3118
|
+
effectiveWasMentioned,
|
|
3119
|
+
shouldBypassMention,
|
|
3120
|
+
canDetectMention,
|
|
3121
|
+
commandAuthorized,
|
|
3122
|
+
inboundHistory: preparedTrigger ? buildInboundHistoryFromEntries({
|
|
3123
|
+
entries: preparedTrigger.history,
|
|
3124
|
+
limit: historyLimit
|
|
3125
|
+
}) : void 0,
|
|
3126
|
+
senderName,
|
|
3127
|
+
bodyText,
|
|
3128
|
+
commandBodyText,
|
|
3129
|
+
media,
|
|
3130
|
+
preflightAudioTranscript,
|
|
3131
|
+
locationPayload,
|
|
3132
|
+
messageId,
|
|
3133
|
+
triggerSnapshot: preparedTrigger,
|
|
3134
|
+
threadRootId,
|
|
3135
|
+
thread,
|
|
3136
|
+
botLoopProtection,
|
|
3137
|
+
effectiveGroupAllowFrom,
|
|
3138
|
+
effectiveRoomUsers
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
//#endregion
|
|
3142
|
+
//#region extensions/matrix/src/matrix/monitor/location.ts
|
|
3143
|
+
function decodeGeoUriParamValue(value) {
|
|
3144
|
+
try {
|
|
3145
|
+
return decodeURIComponent(value);
|
|
3146
|
+
} catch {
|
|
3147
|
+
return value;
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
function parseGeoUri(value) {
|
|
3151
|
+
const trimmed = value.trim();
|
|
3152
|
+
if (!trimmed) return null;
|
|
3153
|
+
if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("geo:")) return null;
|
|
3154
|
+
const [coordsPart, ...paramParts] = trimmed.slice(4).split(";");
|
|
3155
|
+
if (!coordsPart) return null;
|
|
3156
|
+
const coords = coordsPart.split(",");
|
|
3157
|
+
if (coords.length < 2) return null;
|
|
3158
|
+
const latitude = parseStrictFiniteNumber(coords[0] ?? "");
|
|
3159
|
+
const longitude = parseStrictFiniteNumber(coords[1] ?? "");
|
|
3160
|
+
if (latitude === void 0 || longitude === void 0) return null;
|
|
3161
|
+
const params = /* @__PURE__ */ new Map();
|
|
3162
|
+
for (const part of paramParts) {
|
|
3163
|
+
const segment = part.trim();
|
|
3164
|
+
if (!segment) continue;
|
|
3165
|
+
const eqIndex = segment.indexOf("=");
|
|
3166
|
+
const rawKey = eqIndex === -1 ? segment : segment.slice(0, eqIndex);
|
|
3167
|
+
const rawValue = eqIndex === -1 ? "" : segment.slice(eqIndex + 1);
|
|
3168
|
+
const key = normalizeLowercaseStringOrEmpty(rawKey);
|
|
3169
|
+
if (!key) continue;
|
|
3170
|
+
const valuePart = rawValue.trim();
|
|
3171
|
+
params.set(key, valuePart ? decodeGeoUriParamValue(valuePart) : "");
|
|
3172
|
+
}
|
|
3173
|
+
const accuracyRaw = params.get("u");
|
|
3174
|
+
return {
|
|
3175
|
+
latitude,
|
|
3176
|
+
longitude,
|
|
3177
|
+
accuracy: accuracyRaw ? parseStrictFiniteNumber(accuracyRaw) : void 0
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
function resolveMatrixLocation(params) {
|
|
3181
|
+
const { eventType, content } = params;
|
|
3182
|
+
if (!(eventType === EventType.Location || eventType === EventType.RoomMessage && content.msgtype === EventType.Location)) return null;
|
|
3183
|
+
const geoUri = normalizeOptionalString(content.geo_uri) ?? "";
|
|
3184
|
+
if (!geoUri) return null;
|
|
3185
|
+
const parsed = parseGeoUri(geoUri);
|
|
3186
|
+
if (!parsed) return null;
|
|
3187
|
+
const caption = normalizeOptionalString(content.body) ?? "";
|
|
3188
|
+
const location = {
|
|
3189
|
+
latitude: parsed.latitude,
|
|
3190
|
+
longitude: parsed.longitude,
|
|
3191
|
+
accuracy: parsed.accuracy,
|
|
3192
|
+
caption: caption || void 0,
|
|
3193
|
+
source: "pin",
|
|
3194
|
+
isLive: false
|
|
3195
|
+
};
|
|
3196
|
+
return {
|
|
3197
|
+
text: formatLocationText(location),
|
|
3198
|
+
context: toLocationContext(location)
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
//#endregion
|
|
3202
|
+
//#region extensions/matrix/src/matrix/monitor/handler-ingress-prefix.ts
|
|
3203
|
+
async function readMatrixIngressPrefix(config) {
|
|
3204
|
+
const { client, senderId, dropPreStartupMessages, eventTs, eventAge, startupMs, startupGraceMs, event, eventType, eventId, inboundDeduper, roomId, logVerboseMessage, directTracker, claimInboundReplay } = config;
|
|
3205
|
+
const selfUserId = await client.getUserId();
|
|
3206
|
+
if (senderId === selfUserId) return;
|
|
3207
|
+
if (dropPreStartupMessages) {
|
|
3208
|
+
if (typeof eventTs === "number" && eventTs < startupMs - startupGraceMs) return;
|
|
3209
|
+
if (typeof eventTs !== "number" && typeof eventAge === "number" && eventAge > startupGraceMs) return;
|
|
3210
|
+
}
|
|
3211
|
+
const content = event.content;
|
|
3212
|
+
if (eventType === EventType.RoomMessage && isMatrixVerificationRoomMessage({
|
|
3213
|
+
msgtype: content.msgtype,
|
|
3214
|
+
body: content.body
|
|
3215
|
+
})) {
|
|
3216
|
+
logVerboseMessage(`matrix: skip verification/system room message room=${roomId}`);
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
const locationPayload = resolveMatrixLocation({
|
|
3220
|
+
eventType,
|
|
3221
|
+
content
|
|
3222
|
+
});
|
|
3223
|
+
const relates = content["m.relates_to"];
|
|
3224
|
+
if (relates && "rel_type" in relates && relates.rel_type === RelationType.Replace) return;
|
|
3225
|
+
if (hasBundledMatrixReplacementRelation(event)) return;
|
|
3226
|
+
if (eventId && inboundDeduper) {
|
|
3227
|
+
const claim = await inboundDeduper.claim({
|
|
3228
|
+
roomId,
|
|
3229
|
+
eventId
|
|
3230
|
+
});
|
|
3231
|
+
if (claim.kind === "claimed") claimInboundReplay(claim.handle);
|
|
3232
|
+
else if (claim.kind !== "invalid") {
|
|
3233
|
+
logVerboseMessage(`matrix: skip duplicate inbound event room=${roomId} id=${eventId}`);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
return {
|
|
3238
|
+
content,
|
|
3239
|
+
isDirectMessage: await directTracker.isDirectMessage({
|
|
3240
|
+
roomId,
|
|
3241
|
+
senderId,
|
|
3242
|
+
selfUserId
|
|
3243
|
+
}),
|
|
3244
|
+
locationPayload,
|
|
3245
|
+
selfUserId
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
//#endregion
|
|
3249
|
+
//#region extensions/matrix/src/matrix/monitor/replies.ts
|
|
3250
|
+
const THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>/gi;
|
|
3251
|
+
const THINKING_BLOCK_RE = /<\s*(?:think(?:ing)?|thought|antthinking)\b[^<>]*>[\s\S]*?<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi;
|
|
3252
|
+
function shouldSuppressReasoningReplyText(text) {
|
|
3253
|
+
if (typeof text !== "string") return false;
|
|
3254
|
+
const trimmedStart = text.trimStart();
|
|
3255
|
+
if (!trimmedStart) return false;
|
|
3256
|
+
if (normalizeLowercaseStringOrEmpty(trimmedStart).startsWith("reasoning:")) return true;
|
|
3257
|
+
THINKING_TAG_RE.lastIndex = 0;
|
|
3258
|
+
if (!THINKING_TAG_RE.test(text)) return false;
|
|
3259
|
+
THINKING_BLOCK_RE.lastIndex = 0;
|
|
3260
|
+
const withoutThinkingBlocks = text.replace(THINKING_BLOCK_RE, "");
|
|
3261
|
+
THINKING_TAG_RE.lastIndex = 0;
|
|
3262
|
+
return !withoutThinkingBlocks.replace(THINKING_TAG_RE, "").trim();
|
|
3263
|
+
}
|
|
3264
|
+
async function deliverMatrixReplies(params) {
|
|
3265
|
+
const core = getMatrixRuntime();
|
|
3266
|
+
const tableMode = params.tableMode ?? core.channel.text.resolveMarkdownTableMode({
|
|
3267
|
+
cfg: params.cfg,
|
|
3268
|
+
channel: "matrix",
|
|
3269
|
+
accountId: params.accountId
|
|
3270
|
+
});
|
|
3271
|
+
const logVerbose = (message) => {
|
|
3272
|
+
if (core.logging.shouldLogVerbose()) params.runtime.log?.(message);
|
|
3273
|
+
};
|
|
3274
|
+
let hasReplied = false;
|
|
3275
|
+
let deliveredAny = false;
|
|
3276
|
+
for (const reply of params.replies) {
|
|
3277
|
+
if (reply.isReasoning === true || shouldSuppressReasoningReplyText(reply.text)) {
|
|
3278
|
+
logVerbose("matrix reply suppressed as reasoning-only");
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
const hasMedia = Boolean(reply?.mediaUrl) || (reply?.mediaUrls?.length ?? 0) > 0;
|
|
3282
|
+
if (!reply?.text && !hasMedia) {
|
|
3283
|
+
if (reply?.audioAsVoice) {
|
|
3284
|
+
logVerbose("matrix reply has audioAsVoice without media/text; skipping");
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
params.runtime.error?.("matrix reply missing text/media");
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
const replyToIdRaw = (reply.replyToId ?? params.replyToId)?.trim();
|
|
3291
|
+
const replyToId = params.threadId ? replyToIdRaw : params.replyToMode === "off" ? void 0 : replyToIdRaw;
|
|
3292
|
+
const rawText = reply.text ?? "";
|
|
3293
|
+
const mediaList = reply.mediaUrls?.length ? reply.mediaUrls : reply.mediaUrl ? [reply.mediaUrl] : [];
|
|
3294
|
+
const shouldIncludeReply = (id) => Boolean(id) && (params.threadId || params.replyToMode === "all" || !hasReplied);
|
|
3295
|
+
const replyToIdForReply = shouldIncludeReply(replyToId) ? replyToId : void 0;
|
|
3296
|
+
if (mediaList.length === 0) {
|
|
3297
|
+
let sentTextChunk = false;
|
|
3298
|
+
const { chunks } = chunkMatrixText(rawText, {
|
|
3299
|
+
cfg: params.cfg,
|
|
3300
|
+
accountId: params.accountId,
|
|
3301
|
+
tableMode
|
|
3302
|
+
});
|
|
3303
|
+
for (const chunk of chunks) {
|
|
3304
|
+
const trimmed = chunk.trim();
|
|
3305
|
+
if (!trimmed) continue;
|
|
3306
|
+
await sendMessageMatrix(params.roomId, trimmed, {
|
|
3307
|
+
client: params.client,
|
|
3308
|
+
cfg: params.cfg,
|
|
3309
|
+
replyToId: replyToIdForReply,
|
|
3310
|
+
threadId: params.threadId,
|
|
3311
|
+
accountId: params.accountId
|
|
3312
|
+
});
|
|
3313
|
+
deliveredAny = true;
|
|
3314
|
+
sentTextChunk = true;
|
|
3315
|
+
}
|
|
3316
|
+
if (replyToIdForReply && !hasReplied && sentTextChunk) hasReplied = true;
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
let first = true;
|
|
3320
|
+
for (const mediaUrl of mediaList) {
|
|
3321
|
+
const caption = first ? rawText : "";
|
|
3322
|
+
await sendMessageMatrix(params.roomId, caption, {
|
|
3323
|
+
client: params.client,
|
|
3324
|
+
cfg: params.cfg,
|
|
3325
|
+
mediaUrl,
|
|
3326
|
+
mediaLocalRoots: params.mediaLocalRoots,
|
|
3327
|
+
replyToId: replyToIdForReply,
|
|
3328
|
+
threadId: params.threadId,
|
|
3329
|
+
audioAsVoice: reply.audioAsVoice,
|
|
3330
|
+
accountId: params.accountId
|
|
3331
|
+
});
|
|
3332
|
+
deliveredAny = true;
|
|
3333
|
+
first = false;
|
|
3334
|
+
}
|
|
3335
|
+
if (replyToIdForReply && !hasReplied) hasReplied = true;
|
|
3336
|
+
}
|
|
3337
|
+
return deliveredAny;
|
|
3338
|
+
}
|
|
3339
|
+
//#endregion
|
|
3340
|
+
//#region extensions/matrix/src/matrix/monitor/handler-reply-dispatcher.ts
|
|
3341
|
+
function createMatrixReplyDispatcher(config) {
|
|
3342
|
+
const { cfg, prefixOptions, humanDelay, typingCallbacks, streaming, draftStream, draftController, client, roomId, runtime, textLimit, replyToMode, threadTarget, replyToEventId, accountId, mediaLocalRoots, tableMode, logVerboseMessage } = config;
|
|
3343
|
+
const quietDraftStreaming = streaming === "quiet" || streaming === "progress";
|
|
3344
|
+
let finalReplyDeliveryFailed = false;
|
|
3345
|
+
let nonFinalReplyDeliveryFailed = false;
|
|
3346
|
+
const { deliver: deliverReply, onError: onReplyError, ...turnDispatcherOptions } = {
|
|
3347
|
+
...prefixOptions,
|
|
3348
|
+
humanDelay,
|
|
3349
|
+
deliver: async (payload, info) => {
|
|
3350
|
+
if (draftStream && info.kind !== "tool" && !payload.isCompactionNotice) {
|
|
3351
|
+
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
|
3352
|
+
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
3353
|
+
const fallbackPayload = ttsSupplement && ttsSupplement.visibleTextAlreadyDelivered !== true && !payload.text?.trim() ? {
|
|
3354
|
+
...payload,
|
|
3355
|
+
text: ttsSupplement.spokenText
|
|
3356
|
+
} : payload;
|
|
3357
|
+
if (draftController.isDraftConsumed()) {
|
|
3358
|
+
await draftStream.discardPending();
|
|
3359
|
+
await deliverMatrixReplies({
|
|
3360
|
+
cfg,
|
|
3361
|
+
replies: [fallbackPayload],
|
|
3362
|
+
roomId,
|
|
3363
|
+
client,
|
|
3364
|
+
runtime,
|
|
3365
|
+
textLimit,
|
|
3366
|
+
replyToMode,
|
|
3367
|
+
threadId: threadTarget,
|
|
3368
|
+
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3369
|
+
accountId,
|
|
3370
|
+
mediaLocalRoots,
|
|
3371
|
+
tableMode
|
|
3372
|
+
});
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
const payloadReplyToId = normalizeOptionalString(payload.replyToId);
|
|
3376
|
+
const payloadReplyMismatch = replyToMode !== "off" && !threadTarget && payloadReplyToId !== draftController.currentReplyToId();
|
|
3377
|
+
let mustDeliverFinalNormally = draftStream.mustDeliverFinalNormally();
|
|
3378
|
+
if (Boolean(payload.text?.trim()) && !payload.isError && !payloadReplyMismatch && !mustDeliverFinalNormally) {
|
|
3379
|
+
await draftStream.stop();
|
|
3380
|
+
mustDeliverFinalNormally = draftStream.mustDeliverFinalNormally();
|
|
3381
|
+
} else await draftStream.discardPending();
|
|
3382
|
+
const draftEventId = draftStream.eventId();
|
|
3383
|
+
const draftFinalTextNeedsNormalMentionDelivery = Boolean(draftEventId) && typeof payload.text === "string" && Boolean(payload.text.trim()) && !payload.isError && !payloadReplyMismatch && !mustDeliverFinalNormally && await matrixTextWouldActivateMentions(client, payload.text);
|
|
3384
|
+
if (draftEventId && payload.text && !payload.isError && !hasMedia && !payloadReplyMismatch && !mustDeliverFinalNormally && !draftFinalTextNeedsNormalMentionDelivery) {
|
|
3385
|
+
const finalPreviewText = payload.text;
|
|
3386
|
+
await deliverWithFinalizableLivePreviewAdapter({
|
|
3387
|
+
kind: "final",
|
|
3388
|
+
payload,
|
|
3389
|
+
adapter: defineFinalizableLivePreviewAdapter({
|
|
3390
|
+
draft: {
|
|
3391
|
+
flush: async () => {},
|
|
3392
|
+
clear: async () => {},
|
|
3393
|
+
discardPending: async () => {},
|
|
3394
|
+
id: () => draftEventId
|
|
3395
|
+
},
|
|
3396
|
+
buildFinalEdit: () => ({
|
|
3397
|
+
text: finalPreviewText,
|
|
3398
|
+
finalizeLive: !(quietDraftStreaming || !draftStream.matchesPreparedText(finalPreviewText)),
|
|
3399
|
+
...quietDraftStreaming ? { extraContent: buildMatrixFinalizedPreviewContent() } : {}
|
|
3400
|
+
}),
|
|
3401
|
+
editFinal: async (_draftEventId, edit) => {
|
|
3402
|
+
if (edit.finalizeLive) {
|
|
3403
|
+
if (!await draftStream.finalizeLive()) throw new Error("Matrix draft live finalize failed");
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
const { editMessageMatrix } = await loadMatrixSendModule();
|
|
3407
|
+
await editMessageMatrix(roomId, _draftEventId, edit.text, {
|
|
3408
|
+
client,
|
|
3409
|
+
cfg,
|
|
3410
|
+
threadId: threadTarget,
|
|
3411
|
+
accountId,
|
|
3412
|
+
extraContent: edit.extraContent
|
|
3413
|
+
});
|
|
3414
|
+
},
|
|
3415
|
+
createPreviewReceipt: (id) => createPreviewMessageReceipt({
|
|
3416
|
+
id,
|
|
3417
|
+
...threadTarget ? { threadId: threadTarget } : {},
|
|
3418
|
+
...draftController.currentReplyToId() ? { replyToId: draftController.currentReplyToId() } : {}
|
|
3419
|
+
}),
|
|
3420
|
+
logPreviewEditFailure: (err) => {
|
|
3421
|
+
logVerboseMessage(`matrix: preview final edit failed: ${String(err)}`);
|
|
3422
|
+
}
|
|
3423
|
+
}),
|
|
3424
|
+
deliverNormally: async () => {
|
|
3425
|
+
await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3426
|
+
await deliverMatrixReplies({
|
|
3427
|
+
cfg,
|
|
3428
|
+
replies: [fallbackPayload],
|
|
3429
|
+
roomId,
|
|
3430
|
+
client,
|
|
3431
|
+
runtime,
|
|
3432
|
+
textLimit,
|
|
3433
|
+
replyToMode,
|
|
3434
|
+
threadId: threadTarget,
|
|
3435
|
+
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3436
|
+
accountId,
|
|
3437
|
+
mediaLocalRoots,
|
|
3438
|
+
tableMode
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
draftController.markDraftConsumed();
|
|
3443
|
+
} else if (draftEventId && hasMedia && !payloadReplyMismatch) {
|
|
3444
|
+
let textEditOk = !mustDeliverFinalNormally;
|
|
3445
|
+
const payloadText = payload.text ?? ttsSupplement?.spokenText;
|
|
3446
|
+
const payloadTextMatchesDraft = typeof payloadText === "string" && draftStream.matchesPreparedText(payloadText);
|
|
3447
|
+
const reusesDraftTextUnchanged = typeof payloadText === "string" && Boolean(payloadText.trim()) && payloadTextMatchesDraft;
|
|
3448
|
+
const mediaTextNeedsNormalMentionDelivery = typeof payloadText === "string" && Boolean(payloadText.trim()) && await matrixTextWouldActivateMentions(client, payloadText);
|
|
3449
|
+
const requiresFinalTextEdit = quietDraftStreaming || typeof payloadText === "string" && !payloadTextMatchesDraft;
|
|
3450
|
+
if (textEditOk && mediaTextNeedsNormalMentionDelivery) textEditOk = false;
|
|
3451
|
+
else if (textEditOk && payloadText && requiresFinalTextEdit) {
|
|
3452
|
+
const { editMessageMatrix } = await loadMatrixSendModule();
|
|
3453
|
+
textEditOk = await editMessageMatrix(roomId, draftEventId, payloadText, {
|
|
3454
|
+
client,
|
|
3455
|
+
cfg,
|
|
3456
|
+
threadId: threadTarget,
|
|
3457
|
+
accountId,
|
|
3458
|
+
extraContent: quietDraftStreaming ? buildMatrixFinalizedPreviewContent() : void 0
|
|
3459
|
+
}).then(() => true, () => false);
|
|
3460
|
+
} else if (textEditOk && reusesDraftTextUnchanged) textEditOk = await draftStream.finalizeLive();
|
|
3461
|
+
const reusesDraftAsFinalText = Boolean(payloadText?.trim()) && textEditOk;
|
|
3462
|
+
if (!reusesDraftAsFinalText) await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3463
|
+
const mediaPayload = ttsSupplement && reusesDraftAsFinalText ? buildTtsSupplementMediaPayload(payload) : {
|
|
3464
|
+
...payload,
|
|
3465
|
+
text: reusesDraftAsFinalText ? void 0 : payload.text ?? (ttsSupplement?.visibleTextAlreadyDelivered === true ? void 0 : ttsSupplement?.spokenText)
|
|
3466
|
+
};
|
|
3467
|
+
await deliverMatrixReplies({
|
|
3468
|
+
cfg,
|
|
3469
|
+
replies: [mediaPayload],
|
|
3470
|
+
roomId,
|
|
3471
|
+
client,
|
|
3472
|
+
runtime,
|
|
3473
|
+
textLimit,
|
|
3474
|
+
replyToMode,
|
|
3475
|
+
threadId: threadTarget,
|
|
3476
|
+
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3477
|
+
accountId,
|
|
3478
|
+
mediaLocalRoots,
|
|
3479
|
+
tableMode
|
|
3480
|
+
});
|
|
3481
|
+
draftController.markDraftConsumed();
|
|
3482
|
+
} else {
|
|
3483
|
+
const draftRedacted = Boolean(draftEventId) && (payload.isError || payloadReplyMismatch || mustDeliverFinalNormally || draftFinalTextNeedsNormalMentionDelivery);
|
|
3484
|
+
if (draftRedacted && draftEventId) await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3485
|
+
const deliveredFallback = await deliverMatrixReplies({
|
|
3486
|
+
cfg,
|
|
3487
|
+
replies: [fallbackPayload],
|
|
3488
|
+
roomId,
|
|
3489
|
+
client,
|
|
3490
|
+
runtime,
|
|
3491
|
+
textLimit,
|
|
3492
|
+
replyToMode,
|
|
3493
|
+
threadId: threadTarget,
|
|
3494
|
+
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3495
|
+
accountId,
|
|
3496
|
+
mediaLocalRoots,
|
|
3497
|
+
tableMode
|
|
3498
|
+
});
|
|
3499
|
+
if (draftRedacted || deliveredFallback) draftController.markDraftConsumed();
|
|
3500
|
+
}
|
|
3501
|
+
if (info.kind === "block") {
|
|
3502
|
+
draftController.clearDraftConsumed();
|
|
3503
|
+
draftController.advanceDraftBlockBoundary({ fallbackToLatestEnd: true });
|
|
3504
|
+
draftStream.reset();
|
|
3505
|
+
draftController.resetReplyToIdForNextBlock();
|
|
3506
|
+
draftController.updateDraftFromLatestFullText();
|
|
3507
|
+
const { sendTypingMatrix } = await loadMatrixSendModule();
|
|
3508
|
+
await sendTypingMatrix(roomId, true, void 0, client).catch(() => {});
|
|
3509
|
+
}
|
|
3510
|
+
} else await deliverMatrixReplies({
|
|
3511
|
+
cfg,
|
|
3512
|
+
replies: [payload],
|
|
3513
|
+
roomId,
|
|
3514
|
+
client,
|
|
3515
|
+
runtime,
|
|
3516
|
+
textLimit,
|
|
3517
|
+
replyToMode,
|
|
3518
|
+
threadId: threadTarget,
|
|
3519
|
+
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3520
|
+
accountId,
|
|
3521
|
+
mediaLocalRoots,
|
|
3522
|
+
tableMode
|
|
3523
|
+
});
|
|
3524
|
+
},
|
|
3525
|
+
onError: (err, info) => {
|
|
3526
|
+
if (info.kind === "final") finalReplyDeliveryFailed = true;
|
|
3527
|
+
else nonFinalReplyDeliveryFailed = true;
|
|
3528
|
+
if (info.kind === "block") draftController.advanceDraftBlockBoundary({ fallbackToLatestEnd: true });
|
|
3529
|
+
runtime.error?.(`matrix ${info.kind} reply failed: ${String(err)}`);
|
|
3530
|
+
},
|
|
3531
|
+
onReplyStart: typingCallbacks.onReplyStart,
|
|
3532
|
+
onIdle: typingCallbacks.onIdle
|
|
2175
3533
|
};
|
|
2176
|
-
return
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
return null;
|
|
2183
|
-
});
|
|
2184
|
-
if (!rootEvent) return { threadStarterBody: `Matrix thread root ${input.threadRootId}` };
|
|
2185
|
-
const rawEvent = rootEvent;
|
|
2186
|
-
const senderId = trimMatrixMaybeString(rawEvent.sender);
|
|
2187
|
-
const senderName = senderId && await params.getMemberDisplayName(input.roomId, senderId).catch(() => void 0);
|
|
2188
|
-
const senderLabel = senderName ?? senderId;
|
|
2189
|
-
const summary = summarizeMatrixThreadStarterEvent(rawEvent);
|
|
2190
|
-
return remember(cacheKey, {
|
|
2191
|
-
threadStarterBody: formatMatrixThreadStarterBody({
|
|
2192
|
-
threadRootId: input.threadRootId,
|
|
2193
|
-
senderId,
|
|
2194
|
-
senderName,
|
|
2195
|
-
summary
|
|
2196
|
-
}),
|
|
2197
|
-
senderId,
|
|
2198
|
-
senderLabel,
|
|
2199
|
-
summary
|
|
2200
|
-
});
|
|
3534
|
+
return {
|
|
3535
|
+
deliverReply,
|
|
3536
|
+
onReplyError,
|
|
3537
|
+
turnDispatcherOptions,
|
|
3538
|
+
finalReplyDeliveryFailed: () => finalReplyDeliveryFailed,
|
|
3539
|
+
nonFinalReplyDeliveryFailed: () => nonFinalReplyDeliveryFailed
|
|
2201
3540
|
};
|
|
2202
3541
|
}
|
|
2203
3542
|
//#endregion
|
|
2204
|
-
//#region extensions/matrix/src/matrix/monitor/handler.ts
|
|
3543
|
+
//#region extensions/matrix/src/matrix/monitor/handler-state.ts
|
|
2205
3544
|
const ALLOW_FROM_STORE_CACHE_TTL_MS = 3e4;
|
|
2206
3545
|
const PAIRING_REPLY_COOLDOWN_MS = 5 * 6e4;
|
|
2207
|
-
const MATRIX_TOOL_PROGRESS_MAX_CHARS = 300;
|
|
2208
|
-
const loadMatrixSendModule = createLazyRuntimeModule(() => import("./send-BJbrdmNm.js").then((n) => n.l));
|
|
2209
|
-
const loadAcpBindingRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/acp-binding-runtime"));
|
|
2210
|
-
const loadSessionBindingRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/session-binding-runtime"));
|
|
2211
|
-
const loadMatrixReactionEvents = createLazyRuntimeModule(() => import("./reaction-events-BwAKtUyQ.js"));
|
|
2212
|
-
const loadMatrixDraftStream = createLazyRuntimeModule(() => import("./draft-stream-CTLKtJJ0.js"));
|
|
2213
|
-
async function matrixTextWouldActivateMentions(client, text) {
|
|
2214
|
-
const { resolveMatrixMentionsForBody } = await loadMatrixSendModule();
|
|
2215
|
-
const mentions = await resolveMatrixMentionsForBody({
|
|
2216
|
-
client,
|
|
2217
|
-
body: text
|
|
2218
|
-
});
|
|
2219
|
-
return mentions.room === true || (mentions.user_ids?.length ?? 0) > 0;
|
|
2220
|
-
}
|
|
2221
3546
|
const MAX_TRACKED_PAIRING_REPLY_SENDERS = 512;
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
await client.redactEvent(roomId, draftEventId).catch(() => {});
|
|
2225
|
-
}
|
|
2226
|
-
function buildMatrixFinalizedPreviewContent() {
|
|
2227
|
-
return { [MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY]: true };
|
|
2228
|
-
}
|
|
2229
|
-
function resolveMatrixMentionPrecheckText(params) {
|
|
2230
|
-
if (params.locationText?.trim()) return params.locationText.trim();
|
|
2231
|
-
if (typeof params.content.body === "string" && params.content.body.trim()) return params.content.body.trim();
|
|
2232
|
-
if (isPollStartType(params.eventType)) {
|
|
2233
|
-
const parsed = parsePollStartContent(params.content);
|
|
2234
|
-
if (parsed) return formatPollAsText(parsed);
|
|
2235
|
-
}
|
|
2236
|
-
return "";
|
|
2237
|
-
}
|
|
2238
|
-
function hasBundledMatrixReplacementRelation(event) {
|
|
2239
|
-
const relations = event.unsigned?.["m.relations"];
|
|
2240
|
-
if (!relations || typeof relations !== "object") return false;
|
|
2241
|
-
return relations[RelationType.Replace] !== void 0;
|
|
2242
|
-
}
|
|
2243
|
-
function resolveMatrixInboundBodyText(params) {
|
|
2244
|
-
if (params.mediaPlaceholder) return params.rawBody || params.mediaPlaceholder;
|
|
2245
|
-
if (!params.mediaDownloadFailed || !params.hadMediaUrl) return params.rawBody;
|
|
2246
|
-
if (params.mediaSizeLimitExceeded) return formatMatrixMediaTooLargeText({
|
|
2247
|
-
body: params.rawBody,
|
|
2248
|
-
filename: params.filename,
|
|
2249
|
-
msgtype: params.msgtype
|
|
2250
|
-
});
|
|
2251
|
-
return formatMatrixMediaUnavailableText({
|
|
2252
|
-
body: params.rawBody,
|
|
2253
|
-
filename: params.filename,
|
|
2254
|
-
msgtype: params.msgtype
|
|
2255
|
-
});
|
|
2256
|
-
}
|
|
2257
|
-
function markTrackedRoomIfFirst(set, roomId) {
|
|
2258
|
-
if (set.has(roomId)) return false;
|
|
2259
|
-
set.add(roomId);
|
|
2260
|
-
if (set.size > MAX_TRACKED_SHARED_DM_CONTEXT_NOTICES) {
|
|
2261
|
-
const oldest = set.keys().next().value;
|
|
2262
|
-
if (typeof oldest === "string") set.delete(oldest);
|
|
2263
|
-
}
|
|
2264
|
-
return true;
|
|
2265
|
-
}
|
|
2266
|
-
function resolveMatrixSharedDmContextNotice(params) {
|
|
2267
|
-
if ((params.dmSessionScope ?? "per-user") === "per-room") return null;
|
|
2268
|
-
if (params.sentRooms.has(params.roomId)) return null;
|
|
2269
|
-
try {
|
|
2270
|
-
const currentSession = resolveMatrixStoredSessionMeta(getSessionEntry({
|
|
2271
|
-
storePath: params.storePath,
|
|
2272
|
-
sessionKey: params.sessionKey
|
|
2273
|
-
}));
|
|
2274
|
-
if (!currentSession) return null;
|
|
2275
|
-
if (currentSession.channel && currentSession.channel !== "matrix") return null;
|
|
2276
|
-
if (currentSession.accountId && currentSession.accountId !== params.accountId) return null;
|
|
2277
|
-
if (!currentSession.directUserId) return null;
|
|
2278
|
-
if (!currentSession.roomId || currentSession.roomId === params.roomId) return null;
|
|
2279
|
-
return [
|
|
2280
|
-
"This Matrix DM is sharing a session with another Matrix DM room.",
|
|
2281
|
-
"Use /focus here for a one-off isolated thread session when thread bindings are enabled, or set",
|
|
2282
|
-
"channels.matrix.dm.sessionScope to per-room to isolate each Matrix DM room."
|
|
2283
|
-
].join(" ");
|
|
2284
|
-
} catch (err) {
|
|
2285
|
-
params.logVerboseMessage(`matrix: failed checking shared DM session notice room=${params.roomId} (${String(err)})`);
|
|
2286
|
-
return null;
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
function resolveMatrixPendingHistoryText(params) {
|
|
2290
|
-
if (params.mentionPrecheckText) return params.mentionPrecheckText;
|
|
2291
|
-
if (!params.mediaUrl) return "";
|
|
2292
|
-
const body = typeof params.content.body === "string" ? params.content.body.trim() : void 0;
|
|
2293
|
-
const filename = typeof params.content.filename === "string" ? params.content.filename.trim() : void 0;
|
|
2294
|
-
const msgtype = typeof params.content.msgtype === "string" ? params.content.msgtype : void 0;
|
|
2295
|
-
return formatMatrixMessageText({
|
|
2296
|
-
body: resolveMatrixMessageBody({
|
|
2297
|
-
body,
|
|
2298
|
-
filename,
|
|
2299
|
-
msgtype
|
|
2300
|
-
}),
|
|
2301
|
-
attachment: resolveMatrixMessageAttachment({
|
|
2302
|
-
body,
|
|
2303
|
-
filename,
|
|
2304
|
-
msgtype
|
|
2305
|
-
})
|
|
2306
|
-
}) ?? "";
|
|
2307
|
-
}
|
|
2308
|
-
function isMatrixAudioMediaEnabled(cfg) {
|
|
2309
|
-
return cfg.tools?.media?.audio?.enabled !== false;
|
|
2310
|
-
}
|
|
2311
|
-
function shouldDeferMatrixAudioPreflightForRoomIngress(params) {
|
|
2312
|
-
if (!isMatrixAudioMediaEnabled(params.cfg)) return false;
|
|
2313
|
-
const content = params.content;
|
|
2314
|
-
const contentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
2315
|
-
const contentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
2316
|
-
const mediaUrl = contentUrl ?? contentFile?.url;
|
|
2317
|
-
const contentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
2318
|
-
return mediaUrl?.startsWith("mxc://") === true && isMatrixAudioContent({
|
|
2319
|
-
msgtype: typeof content.msgtype === "string" ? content.msgtype : void 0,
|
|
2320
|
-
mimetype: contentInfo?.mimetype
|
|
2321
|
-
});
|
|
2322
|
-
}
|
|
2323
|
-
function resolveMatrixAllowBotsMode(value) {
|
|
2324
|
-
if (value === true) return "all";
|
|
2325
|
-
if (value === "mentions") return "mentions";
|
|
2326
|
-
return "off";
|
|
2327
|
-
}
|
|
2328
|
-
function formatMatrixToolProgressMarkdownCode(text) {
|
|
2329
|
-
return `\`${(text.length <= MATRIX_TOOL_PROGRESS_MAX_CHARS ? text : `${truncateUtf16Safe(text, MATRIX_TOOL_PROGRESS_MAX_CHARS - 1).trimEnd()}...`).replaceAll("`", "'")}\``;
|
|
2330
|
-
}
|
|
2331
|
-
function createMatrixRoomMessageHandler(params) {
|
|
2332
|
-
const { client, core, cfg, accountId, accountConfig, runtime, logger, logVerboseMessage, allowFromResolvedEntries = [], groupAllowFromResolvedEntries = [], roomsConfig, accountAllowBots, configuredBotUserIds = /* @__PURE__ */ new Set(), groupPolicy, replyToMode, threadReplies, dmThreadReplies, dmSessionScope, streaming, previewToolProgressEnabled, blockStreamingEnabled, dmEnabled, dmPolicy, textLimit, mediaMaxBytes, historyLimit, startupMs, startupGraceMs, dropPreStartupMessages, inboundDeduper, directTracker, getRoomInfo, getMemberDisplayName, needsRoomAliasesForConfig, resolveLiveUserAllowlist = resolveMatrixMonitorLiveUserAllowlist, resolveStorePath: resolveStorePathImpl = resolveStorePath, createChannelInboundEnvelopeBuilder: createChannelInboundEnvelopeBuilderImpl = createChannelInboundEnvelopeBuilder, finalizeInboundContext, resolveHumanDelayConfig: resolveHumanDelayConfigImpl = resolveHumanDelayConfig } = params;
|
|
2333
|
-
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
2334
|
-
cfg,
|
|
2335
|
-
channel: "matrix",
|
|
2336
|
-
accountId
|
|
2337
|
-
});
|
|
3547
|
+
function createMatrixHandlerState(config) {
|
|
3548
|
+
const { core, accountId, runtime, allowFromResolvedEntries, groupAllowFromResolvedEntries, resolveLiveUserAllowlist } = config;
|
|
2338
3549
|
let cachedStoreAllowFrom = null;
|
|
2339
3550
|
let liveDmAllowlistCache = null;
|
|
2340
3551
|
let liveGroupAllowlistCache = null;
|
|
@@ -2365,19 +3576,35 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
2365
3576
|
return entries;
|
|
2366
3577
|
};
|
|
2367
3578
|
const pairingReplySentAtMsBySender = /* @__PURE__ */ new Map();
|
|
2368
|
-
const
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
3579
|
+
const resolveLiveAccountAllowlists = async () => {
|
|
3580
|
+
const liveCfg = core.config.current();
|
|
3581
|
+
const liveAccountAllowlists = resolveMatrixAccountAllowlistConfig({
|
|
3582
|
+
cfg: liveCfg,
|
|
3583
|
+
accountId
|
|
3584
|
+
});
|
|
3585
|
+
return {
|
|
3586
|
+
liveCfg,
|
|
3587
|
+
liveDmAllowFrom: await resolveCachedLiveAllowlist({
|
|
3588
|
+
cfg: liveCfg,
|
|
3589
|
+
entries: liveAccountAllowlists.dmAllowFrom,
|
|
3590
|
+
startupResolvedEntries: allowFromResolvedEntries,
|
|
3591
|
+
cache: liveDmAllowlistCache,
|
|
3592
|
+
updateCache: (next) => {
|
|
3593
|
+
liveDmAllowlistCache = next;
|
|
3594
|
+
}
|
|
3595
|
+
}),
|
|
3596
|
+
liveGroupAllowFrom: await resolveCachedLiveAllowlist({
|
|
3597
|
+
cfg: liveCfg,
|
|
3598
|
+
entries: liveAccountAllowlists.groupAllowFrom,
|
|
3599
|
+
failClosedOnUnresolved: true,
|
|
3600
|
+
startupResolvedEntries: groupAllowFromResolvedEntries,
|
|
3601
|
+
cache: liveGroupAllowlistCache,
|
|
3602
|
+
updateCache: (next) => {
|
|
3603
|
+
liveGroupAllowlistCache = next;
|
|
3604
|
+
}
|
|
3605
|
+
})
|
|
3606
|
+
};
|
|
3607
|
+
};
|
|
2381
3608
|
const readStoreAllowFrom = async () => {
|
|
2382
3609
|
const now = Date.now();
|
|
2383
3610
|
if (cachedStoreAllowFrom && isFutureDateTimestampMs(cachedStoreAllowFrom.expiresAtMs, { nowMs: now })) return cachedStoreAllowFrom.value;
|
|
@@ -2409,14 +3636,59 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
2409
3636
|
}
|
|
2410
3637
|
return true;
|
|
2411
3638
|
};
|
|
3639
|
+
return {
|
|
3640
|
+
resolveLiveAccountAllowlists,
|
|
3641
|
+
readStoreAllowFrom,
|
|
3642
|
+
shouldSendPairingReply
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
//#endregion
|
|
3646
|
+
//#region extensions/matrix/src/matrix/monitor/handler.ts
|
|
3647
|
+
function createMatrixRoomMessageHandler(params) {
|
|
3648
|
+
const { client, core, cfg, accountId, runtime, logger, logVerboseMessage, allowFromResolvedEntries = [], groupAllowFromResolvedEntries = [], configuredBotUserIds = /* @__PURE__ */ new Set(), groupPolicy, replyToMode, dmSessionScope, streaming, previewToolProgressEnabled, blockStreamingEnabled, textLimit, historyLimit, startupMs, startupGraceMs, dropPreStartupMessages, inboundDeduper, directTracker, getRoomInfo, getMemberDisplayName, resolveLiveUserAllowlist = resolveMatrixMonitorLiveUserAllowlist, resolveStorePath: resolveStorePathImpl = resolveStorePath, createChannelInboundEnvelopeBuilder: createChannelInboundEnvelopeBuilderImpl = createChannelInboundEnvelopeBuilder, finalizeInboundContext, resolveHumanDelayConfig: resolveHumanDelayConfigImpl = resolveHumanDelayConfig } = params;
|
|
3649
|
+
const handlerConfig = {
|
|
3650
|
+
...params,
|
|
3651
|
+
allowFromResolvedEntries,
|
|
3652
|
+
groupAllowFromResolvedEntries,
|
|
3653
|
+
configuredBotUserIds,
|
|
3654
|
+
resolveLiveUserAllowlist,
|
|
3655
|
+
resolveStorePath: resolveStorePathImpl,
|
|
3656
|
+
createChannelInboundEnvelopeBuilder: createChannelInboundEnvelopeBuilderImpl,
|
|
3657
|
+
resolveHumanDelayConfig: resolveHumanDelayConfigImpl
|
|
3658
|
+
};
|
|
3659
|
+
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
3660
|
+
cfg,
|
|
3661
|
+
channel: "matrix",
|
|
3662
|
+
accountId
|
|
3663
|
+
});
|
|
3664
|
+
const handlerState = createMatrixHandlerState({
|
|
3665
|
+
core,
|
|
3666
|
+
accountId,
|
|
3667
|
+
runtime,
|
|
3668
|
+
allowFromResolvedEntries,
|
|
3669
|
+
groupAllowFromResolvedEntries,
|
|
3670
|
+
resolveLiveUserAllowlist
|
|
3671
|
+
});
|
|
3672
|
+
const resolveThreadContext = createMatrixThreadContextResolver({
|
|
3673
|
+
client,
|
|
3674
|
+
getMemberDisplayName,
|
|
3675
|
+
logVerboseMessage
|
|
3676
|
+
});
|
|
3677
|
+
const resolveReplyContext = createMatrixReplyContextResolver({
|
|
3678
|
+
client,
|
|
3679
|
+
getMemberDisplayName,
|
|
3680
|
+
logVerboseMessage
|
|
3681
|
+
});
|
|
3682
|
+
const roomHistoryTracker = createRoomHistoryTracker();
|
|
3683
|
+
const roomIngressQueue = new KeyedAsyncQueue();
|
|
3684
|
+
const sharedDmContextNoticeRooms = /* @__PURE__ */ new Set();
|
|
2412
3685
|
const runRoomIngress = async (roomId, task) => {
|
|
2413
3686
|
return await roomIngressQueue.enqueue(roomId, task);
|
|
2414
3687
|
};
|
|
2415
3688
|
return async (roomId, event) => {
|
|
2416
3689
|
const eventId = typeof event.event_id === "string" ? event.event_id.trim() : "";
|
|
2417
3690
|
let inboundReplayClaim;
|
|
2418
|
-
let
|
|
2419
|
-
let draftConsumed = false;
|
|
3691
|
+
let draftControllerRef;
|
|
2420
3692
|
try {
|
|
2421
3693
|
const eventType = event.type;
|
|
2422
3694
|
if (eventType === EventType.RoomMessageEncrypted) return;
|
|
@@ -2436,563 +3708,54 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
2436
3708
|
await inboundReplayClaim.commit();
|
|
2437
3709
|
inboundReplayClaim = void 0;
|
|
2438
3710
|
};
|
|
2439
|
-
const readIngressPrefix =
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
content
|
|
2457
|
-
});
|
|
2458
|
-
const relates = content["m.relates_to"];
|
|
2459
|
-
if (relates && "rel_type" in relates && relates.rel_type === RelationType.Replace) return;
|
|
2460
|
-
if (hasBundledMatrixReplacementRelation(event)) return;
|
|
2461
|
-
if (eventId && inboundDeduper) {
|
|
2462
|
-
const claim = await inboundDeduper.claim({
|
|
2463
|
-
roomId,
|
|
2464
|
-
eventId
|
|
2465
|
-
});
|
|
2466
|
-
if (claim.kind === "claimed") inboundReplayClaim = claim.handle;
|
|
2467
|
-
else if (claim.kind !== "invalid") {
|
|
2468
|
-
logVerboseMessage(`matrix: skip duplicate inbound event room=${roomId} id=${eventId}`);
|
|
2469
|
-
return;
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
return {
|
|
2473
|
-
content,
|
|
2474
|
-
isDirectMessage: await directTracker.isDirectMessage({
|
|
2475
|
-
roomId,
|
|
2476
|
-
senderId,
|
|
2477
|
-
selfUserId
|
|
2478
|
-
}),
|
|
2479
|
-
locationPayload,
|
|
2480
|
-
selfUserId
|
|
2481
|
-
};
|
|
2482
|
-
};
|
|
2483
|
-
const continueIngress = async (paramsLocal) => {
|
|
2484
|
-
let content = paramsLocal.content;
|
|
2485
|
-
const isDirectMessage = paramsLocal.isDirectMessage;
|
|
2486
|
-
const isRoom = !isDirectMessage;
|
|
2487
|
-
const { audioPreflightMode, locationPayload, reservedHistorySlot, selfUserId } = paramsLocal;
|
|
2488
|
-
const messageId = event.event_id ?? "";
|
|
2489
|
-
const threadRootId = resolveMatrixThreadRootId({
|
|
2490
|
-
event,
|
|
2491
|
-
content
|
|
2492
|
-
});
|
|
2493
|
-
const thread = resolveMatrixThreadRouting({
|
|
2494
|
-
isDirectMessage,
|
|
2495
|
-
threadReplies,
|
|
2496
|
-
dmThreadReplies,
|
|
2497
|
-
messageId,
|
|
2498
|
-
threadRootId
|
|
2499
|
-
});
|
|
2500
|
-
const historyThreadId = threadRootId ? thread.threadId : void 0;
|
|
2501
|
-
let reservedHistorySlotConsumed = false;
|
|
2502
|
-
const discardReservedHistorySlot = () => {
|
|
2503
|
-
if (reservedHistorySlot && !reservedHistorySlotConsumed) {
|
|
2504
|
-
roomHistoryTracker.discardPending(roomId, reservedHistorySlot, historyThreadId);
|
|
2505
|
-
reservedHistorySlotConsumed = true;
|
|
2506
|
-
}
|
|
2507
|
-
};
|
|
2508
|
-
const commitInboundEventIfClaimedAndDiscardReserved = async () => {
|
|
2509
|
-
discardReservedHistorySlot();
|
|
2510
|
-
await commitInboundEventIfClaimed();
|
|
2511
|
-
};
|
|
2512
|
-
if (isRoom && groupPolicy === "disabled") {
|
|
2513
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2514
|
-
return;
|
|
2515
|
-
}
|
|
2516
|
-
const roomInfoForConfig = isRoom && needsRoomAliasesForConfig ? await getRoomInfo(roomId, { includeAliases: true }) : void 0;
|
|
2517
|
-
const roomAliasesForConfig = roomInfoForConfig ? [roomInfoForConfig.canonicalAlias ?? "", ...roomInfoForConfig.altAliases].filter(Boolean) : [];
|
|
2518
|
-
const roomConfigInfo = isRoom ? resolveMatrixRoomConfig({
|
|
2519
|
-
rooms: roomsConfig,
|
|
2520
|
-
roomId,
|
|
2521
|
-
aliases: roomAliasesForConfig
|
|
2522
|
-
}) : void 0;
|
|
2523
|
-
const roomConfig = roomConfigInfo?.config;
|
|
2524
|
-
const allowBotsMode = resolveMatrixAllowBotsMode(roomConfig?.allowBots ?? accountAllowBots);
|
|
2525
|
-
const isConfiguredBotSender = configuredBotUserIds.has(senderId);
|
|
2526
|
-
const roomMatchMeta = roomConfigInfo ? `matchKey=${roomConfigInfo.matchKey ?? "none"} matchSource=${roomConfigInfo.matchSource ?? "none"}` : "matchKey=none matchSource=none";
|
|
2527
|
-
if (isConfiguredBotSender && allowBotsMode === "off") {
|
|
2528
|
-
logVerboseMessage(`matrix: drop configured bot sender=${senderId} (allowBots=false${isDirectMessage ? "" : `, ${roomMatchMeta}`})`);
|
|
2529
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
const botLoopProtection = isConfiguredBotSender && senderId !== selfUserId ? {
|
|
2533
|
-
scopeId: accountId,
|
|
2534
|
-
conversationId: roomId,
|
|
2535
|
-
senderId,
|
|
2536
|
-
receiverId: selfUserId,
|
|
2537
|
-
config: mergePairLoopGuardConfig(accountConfig?.botLoopProtection, roomConfig?.botLoopProtection),
|
|
2538
|
-
defaultsConfig: cfg.channels?.defaults?.botLoopProtection,
|
|
2539
|
-
defaultEnabled: true,
|
|
2540
|
-
nowMs: eventTs ?? void 0
|
|
2541
|
-
} : void 0;
|
|
2542
|
-
if (isRoom && roomConfig && !roomConfigInfo?.allowed) {
|
|
2543
|
-
logVerboseMessage(`matrix: room disabled room=${roomId} (${roomMatchMeta})`);
|
|
2544
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2545
|
-
return;
|
|
2546
|
-
}
|
|
2547
|
-
if (isRoom && groupPolicy === "allowlist") {
|
|
2548
|
-
if (!roomConfigInfo?.allowlistConfigured) {
|
|
2549
|
-
logVerboseMessage(`matrix: drop room message (no allowlist, ${roomMatchMeta})`);
|
|
2550
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2551
|
-
return;
|
|
2552
|
-
}
|
|
2553
|
-
if (!roomConfig) {
|
|
2554
|
-
logVerboseMessage(`matrix: drop room message (not in allowlist, ${roomMatchMeta})`);
|
|
2555
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2556
|
-
return;
|
|
2557
|
-
}
|
|
3711
|
+
const readIngressPrefix = () => readMatrixIngressPrefix({
|
|
3712
|
+
client,
|
|
3713
|
+
senderId,
|
|
3714
|
+
dropPreStartupMessages,
|
|
3715
|
+
eventTs: eventTs ?? void 0,
|
|
3716
|
+
eventAge: eventAge ?? void 0,
|
|
3717
|
+
startupMs,
|
|
3718
|
+
startupGraceMs,
|
|
3719
|
+
event,
|
|
3720
|
+
eventType,
|
|
3721
|
+
eventId,
|
|
3722
|
+
inboundDeduper,
|
|
3723
|
+
roomId,
|
|
3724
|
+
logVerboseMessage,
|
|
3725
|
+
directTracker,
|
|
3726
|
+
claimInboundReplay: (handle) => {
|
|
3727
|
+
inboundReplayClaim = handle;
|
|
2558
3728
|
}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
const liveAccountAllowlists = resolveMatrixAccountAllowlistConfig({
|
|
2568
|
-
cfg: liveCfg,
|
|
2569
|
-
accountId
|
|
2570
|
-
});
|
|
2571
|
-
const liveDmAllowFrom = await resolveCachedLiveAllowlist({
|
|
2572
|
-
cfg: liveCfg,
|
|
2573
|
-
entries: liveAccountAllowlists.dmAllowFrom,
|
|
2574
|
-
startupResolvedEntries: allowFromResolvedEntries,
|
|
2575
|
-
cache: liveDmAllowlistCache,
|
|
2576
|
-
updateCache: (next) => {
|
|
2577
|
-
liveDmAllowlistCache = next;
|
|
2578
|
-
}
|
|
2579
|
-
});
|
|
2580
|
-
const liveGroupAllowFrom = await resolveCachedLiveAllowlist({
|
|
2581
|
-
cfg: liveCfg,
|
|
2582
|
-
entries: liveAccountAllowlists.groupAllowFrom,
|
|
2583
|
-
failClosedOnUnresolved: true,
|
|
2584
|
-
startupResolvedEntries: groupAllowFromResolvedEntries,
|
|
2585
|
-
cache: liveGroupAllowlistCache,
|
|
2586
|
-
updateCache: (next) => {
|
|
2587
|
-
liveGroupAllowlistCache = next;
|
|
2588
|
-
}
|
|
2589
|
-
});
|
|
2590
|
-
const accessState = await resolveMatrixMonitorAccessState({
|
|
2591
|
-
allowFrom: liveDmAllowFrom,
|
|
2592
|
-
storeAllowFrom,
|
|
2593
|
-
dmPolicy,
|
|
2594
|
-
groupPolicy,
|
|
2595
|
-
groupAllowFrom: liveGroupAllowFrom,
|
|
2596
|
-
roomUsers,
|
|
3729
|
+
});
|
|
3730
|
+
const continueIngress = async (paramsLocal) => {
|
|
3731
|
+
const access = await resolveMatrixIngressAccess({
|
|
3732
|
+
handler: handlerConfig,
|
|
3733
|
+
params: paramsLocal,
|
|
3734
|
+
roomId,
|
|
3735
|
+
event,
|
|
3736
|
+
eventTs: eventTs ?? void 0,
|
|
2597
3737
|
senderId,
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
if (isDirectMessage) {
|
|
2605
|
-
if (!dmEnabled || dmPolicy === "disabled") {
|
|
2606
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2609
|
-
const senderReason = messageIngress.senderAccess.reasonCode;
|
|
2610
|
-
if (ingressDecision.decision !== "allow") {
|
|
2611
|
-
if (ingressDecision.admission === "pairing-required") {
|
|
2612
|
-
const senderName = await getSenderName();
|
|
2613
|
-
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
|
2614
|
-
channel: "matrix",
|
|
2615
|
-
id: senderId,
|
|
2616
|
-
accountId,
|
|
2617
|
-
meta: { name: senderName }
|
|
2618
|
-
});
|
|
2619
|
-
if (shouldSendPairingReply(senderId, created)) {
|
|
2620
|
-
const pairingReply = core.channel.pairing.buildPairingReply({
|
|
2621
|
-
channel: "matrix",
|
|
2622
|
-
idLine: `Your Matrix user id: ${senderId}`,
|
|
2623
|
-
code
|
|
2624
|
-
});
|
|
2625
|
-
logVerboseMessage(created ? `matrix pairing request sender=${senderId} name=${senderName ?? "unknown"} (reason=${senderReason})` : `matrix pairing reminder sender=${senderId} name=${senderName ?? "unknown"} (reason=${senderReason})`);
|
|
2626
|
-
try {
|
|
2627
|
-
const { sendMessageMatrix } = await loadMatrixSendModule();
|
|
2628
|
-
await sendMessageMatrix(`room:${roomId}`, created ? pairingReply : `${pairingReply}\n\nPairing request is still pending approval. Reusing existing code.`, {
|
|
2629
|
-
client,
|
|
2630
|
-
cfg,
|
|
2631
|
-
accountId
|
|
2632
|
-
});
|
|
2633
|
-
await commitInboundEventIfClaimed();
|
|
2634
|
-
} catch (err) {
|
|
2635
|
-
logVerboseMessage(`matrix pairing reply failed for ${senderId}: ${String(err)}`);
|
|
2636
|
-
discardReservedHistorySlot();
|
|
2637
|
-
return;
|
|
2638
|
-
}
|
|
2639
|
-
} else {
|
|
2640
|
-
logVerboseMessage(`matrix pairing reminder suppressed sender=${senderId} (cooldown)`);
|
|
2641
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
if (isReactionEvent || dmPolicy !== "pairing") {
|
|
2645
|
-
logVerboseMessage(`matrix: blocked ${isReactionEvent ? "reaction" : "dm"} sender ${senderId} (dmPolicy=${dmPolicy}, reason=${senderReason})`);
|
|
2646
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2647
|
-
}
|
|
2648
|
-
return;
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
if (isRoom && ingressDecision.decision !== "allow") {
|
|
2652
|
-
logVerboseMessage(`matrix: blocked sender ${senderId} (ingress=${ingressDecision.reasonCode}, ${roomMatchMeta})`);
|
|
2653
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2654
|
-
return;
|
|
2655
|
-
}
|
|
2656
|
-
if (isRoom) logVerboseMessage(`matrix: allow room ${roomId} (${roomMatchMeta})`);
|
|
2657
|
-
if (isReactionEvent) {
|
|
2658
|
-
const senderName = await getSenderName();
|
|
2659
|
-
const { handleInboundMatrixReaction } = await loadMatrixReactionEvents();
|
|
2660
|
-
await handleInboundMatrixReaction({
|
|
2661
|
-
client,
|
|
2662
|
-
core,
|
|
2663
|
-
cfg,
|
|
2664
|
-
accountId,
|
|
2665
|
-
roomId,
|
|
2666
|
-
event,
|
|
2667
|
-
senderId,
|
|
2668
|
-
senderLabel: senderName,
|
|
2669
|
-
selfUserId,
|
|
2670
|
-
isDirectMessage,
|
|
2671
|
-
logVerboseMessage
|
|
2672
|
-
});
|
|
2673
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2674
|
-
return;
|
|
2675
|
-
}
|
|
2676
|
-
let pollSnapshotPromise = null;
|
|
2677
|
-
const getPollSnapshot = async () => {
|
|
2678
|
-
if (!isPollEvent) return null;
|
|
2679
|
-
pollSnapshotPromise ??= fetchMatrixPollSnapshot(client, roomId, event).catch((err) => {
|
|
2680
|
-
logVerboseMessage(`matrix: failed resolving poll snapshot room=${roomId} id=${event.event_id ?? "unknown"}: ${String(err)}`);
|
|
2681
|
-
return null;
|
|
2682
|
-
});
|
|
2683
|
-
return await pollSnapshotPromise;
|
|
2684
|
-
};
|
|
2685
|
-
const mentionPrecheckText = resolveMatrixMentionPrecheckText({
|
|
2686
|
-
eventType,
|
|
2687
|
-
content,
|
|
2688
|
-
locationText: locationPayload?.text
|
|
2689
|
-
});
|
|
2690
|
-
const contentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
2691
|
-
const contentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
2692
|
-
const mediaUrl = contentUrl ?? contentFile?.url;
|
|
2693
|
-
const earlyContentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
2694
|
-
const earlyContentType = earlyContentInfo?.mimetype;
|
|
2695
|
-
const earlyContentSize = typeof earlyContentInfo?.size === "number" ? earlyContentInfo.size : void 0;
|
|
2696
|
-
const earlyContentBody = typeof content.body === "string" ? content.body.trim() : "";
|
|
2697
|
-
const earlyOriginalFilename = (typeof content.filename === "string" ? content.filename.trim() : "") || earlyContentBody || void 0;
|
|
2698
|
-
const pendingHistoryText = resolveMatrixPendingHistoryText({
|
|
2699
|
-
mentionPrecheckText,
|
|
2700
|
-
content,
|
|
2701
|
-
mediaUrl
|
|
3738
|
+
isReactionEvent,
|
|
3739
|
+
readStoreAllowFrom: handlerState.readStoreAllowFrom,
|
|
3740
|
+
shouldSendPairingReply: handlerState.shouldSendPairingReply,
|
|
3741
|
+
resolveLiveAccountAllowlists: handlerState.resolveLiveAccountAllowlists,
|
|
3742
|
+
roomHistoryTracker,
|
|
3743
|
+
commitInboundEventIfClaimed
|
|
2702
3744
|
});
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
let preflightMedia = null;
|
|
2709
|
-
let preflightMediaDownloadFailed = false;
|
|
2710
|
-
let preflightMediaSizeLimitExceeded = false;
|
|
2711
|
-
let preflightAudioTranscript;
|
|
2712
|
-
const { route: _route, configuredBinding: _configuredBinding, runtimeBindingId: _runtimeBindingId } = resolveMatrixInboundRoute({
|
|
2713
|
-
cfg,
|
|
2714
|
-
accountId,
|
|
3745
|
+
if (!access) return;
|
|
3746
|
+
return await resolveMatrixIngressContent({
|
|
3747
|
+
handler: handlerConfig,
|
|
3748
|
+
params: paramsLocal,
|
|
3749
|
+
access,
|
|
2715
3750
|
roomId,
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
threadId: thread.threadId,
|
|
3751
|
+
event,
|
|
3752
|
+
eventType,
|
|
3753
|
+
isPollEvent,
|
|
2720
3754
|
eventTs: eventTs ?? void 0,
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
const preflightAudioMediaUrl = mediaUrl?.startsWith("mxc://") ? mediaUrl : void 0;
|
|
2725
|
-
const shouldRunMatrixAudioPreflight = isMatrixAudioContent({
|
|
2726
|
-
msgtype: typeof content.msgtype === "string" ? content.msgtype : void 0,
|
|
2727
|
-
mimetype: earlyContentType
|
|
2728
|
-
}) && isMatrixAudioMediaEnabled(cfg) && preflightAudioMediaUrl !== void 0;
|
|
2729
|
-
if (shouldRunMatrixAudioPreflight && audioPreflightMode === "defer" && isRoom && historyLimit > 0 && !reservedHistorySlot) {
|
|
2730
|
-
const reserved = roomHistoryTracker.reservePending(_route.agentId, roomId, {
|
|
2731
|
-
sender: senderId,
|
|
2732
|
-
body: pendingHistoryText,
|
|
2733
|
-
timestamp: eventTs ?? void 0,
|
|
2734
|
-
messageId
|
|
2735
|
-
}, historyThreadId);
|
|
2736
|
-
return { deferredPrefix: {
|
|
2737
|
-
...paramsLocal,
|
|
2738
|
-
audioPreflightMode: "run",
|
|
2739
|
-
reservedHistorySlot: reserved
|
|
2740
|
-
} };
|
|
2741
|
-
}
|
|
2742
|
-
if (shouldRunMatrixAudioPreflight) {
|
|
2743
|
-
try {
|
|
2744
|
-
preflightMedia = await downloadMatrixMedia({
|
|
2745
|
-
client,
|
|
2746
|
-
mxcUrl: preflightAudioMediaUrl,
|
|
2747
|
-
contentType: earlyContentType,
|
|
2748
|
-
sizeBytes: earlyContentSize,
|
|
2749
|
-
maxBytes: mediaMaxBytes,
|
|
2750
|
-
file: contentFile,
|
|
2751
|
-
originalFilename: earlyOriginalFilename
|
|
2752
|
-
});
|
|
2753
|
-
} catch (err) {
|
|
2754
|
-
preflightMediaDownloadFailed = true;
|
|
2755
|
-
if (isMatrixMediaSizeLimitError(err)) preflightMediaSizeLimitExceeded = true;
|
|
2756
|
-
const errorText = formatMatrixErrorMessage(err);
|
|
2757
|
-
logVerboseMessage(`matrix: media download failed room=${roomId} id=${event.event_id ?? "unknown"} type=${content.msgtype} error=${errorText}`);
|
|
2758
|
-
logger.warn("matrix media download failed", {
|
|
2759
|
-
roomId,
|
|
2760
|
-
eventId: event.event_id,
|
|
2761
|
-
msgtype: content.msgtype,
|
|
2762
|
-
encrypted: Boolean(contentFile),
|
|
2763
|
-
error: errorText
|
|
2764
|
-
});
|
|
2765
|
-
}
|
|
2766
|
-
if (preflightMedia) preflightAudioTranscript = await resolveMatrixPreflightAudioTranscript({
|
|
2767
|
-
mediaPath: preflightMedia.path,
|
|
2768
|
-
mediaContentType: preflightMedia.contentType,
|
|
2769
|
-
cfg,
|
|
2770
|
-
accountId,
|
|
2771
|
-
chatType: isDirectMessage ? "direct" : "channel",
|
|
2772
|
-
originatingTo: `room:${roomId}`,
|
|
2773
|
-
messageThreadId: thread.threadId,
|
|
2774
|
-
sessionKey: _route.sessionKey
|
|
2775
|
-
});
|
|
2776
|
-
}
|
|
2777
|
-
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId, {
|
|
2778
|
-
provider: "matrix",
|
|
2779
|
-
conversationId: roomId,
|
|
2780
|
-
providerPolicy: accountConfig?.mentionPatterns
|
|
2781
|
-
});
|
|
2782
|
-
const selfDisplayName = content.formatted_body ? await getMemberDisplayName(roomId, selfUserId).catch(() => void 0) : void 0;
|
|
2783
|
-
const mentionPrecheckTextWithTranscript = preflightAudioTranscript ? [mentionPrecheckText, preflightAudioTranscript].filter(Boolean).join("\n").trim() : mentionPrecheckText;
|
|
2784
|
-
const { wasMentioned, hasExplicitMention } = resolveMentions({
|
|
2785
|
-
content,
|
|
2786
|
-
userId: selfUserId,
|
|
2787
|
-
displayName: selfDisplayName,
|
|
2788
|
-
text: mentionPrecheckTextWithTranscript,
|
|
2789
|
-
mentionRegexes: agentMentionRegexes
|
|
2790
|
-
});
|
|
2791
|
-
if (isConfiguredBotSender && allowBotsMode === "mentions" && !isDirectMessage && !wasMentioned) {
|
|
2792
|
-
logVerboseMessage(`matrix: drop configured bot sender=${senderId} (allowBots=mentions, missing mention, ${roomMatchMeta})`);
|
|
2793
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2794
|
-
return;
|
|
2795
|
-
}
|
|
2796
|
-
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
2797
|
-
cfg,
|
|
2798
|
-
surface: "matrix"
|
|
2799
|
-
});
|
|
2800
|
-
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
|
2801
|
-
const commandCheckText = stripMatrixMentionPrefix({
|
|
2802
|
-
text: mentionPrecheckText,
|
|
2803
|
-
userId: selfUserId,
|
|
2804
|
-
displayName: selfDisplayName,
|
|
2805
|
-
mentionRegexes: agentMentionRegexes
|
|
2806
|
-
});
|
|
2807
|
-
const hasControlCommandInMessage = core.channel.text.hasControlCommand(commandCheckText, cfg);
|
|
2808
|
-
const commandAccess = await resolveMatrixMonitorCommandAccess(accessState, {
|
|
2809
|
-
useAccessGroups,
|
|
2810
|
-
allowTextCommands,
|
|
2811
|
-
hasControlCommand: hasControlCommandInMessage
|
|
2812
|
-
});
|
|
2813
|
-
const commandAuthorized = commandAccess.authorized;
|
|
2814
|
-
if (isRoom && commandAccess.shouldBlockControlCommand) {
|
|
2815
|
-
logInboundDrop({
|
|
2816
|
-
log: logVerboseMessage,
|
|
2817
|
-
channel: "matrix",
|
|
2818
|
-
reason: "control command (unauthorized)",
|
|
2819
|
-
target: senderId
|
|
2820
|
-
});
|
|
2821
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2822
|
-
return;
|
|
2823
|
-
}
|
|
2824
|
-
const shouldRequireMention = isRoom ? roomConfig?.autoReply === true ? false : roomConfig?.autoReply === false ? true : typeof roomConfig?.requireMention === "boolean" ? roomConfig?.requireMention : true : false;
|
|
2825
|
-
const mentionDecision = resolveInboundMentionDecision({
|
|
2826
|
-
facts: {
|
|
2827
|
-
canDetectMention: true,
|
|
2828
|
-
wasMentioned,
|
|
2829
|
-
hasAnyMention: hasExplicitMention
|
|
2830
|
-
},
|
|
2831
|
-
policy: {
|
|
2832
|
-
isGroup: isRoom,
|
|
2833
|
-
requireMention: shouldRequireMention,
|
|
2834
|
-
allowTextCommands,
|
|
2835
|
-
hasControlCommand: hasControlCommandInMessage,
|
|
2836
|
-
commandAuthorized
|
|
2837
|
-
}
|
|
2838
|
-
});
|
|
2839
|
-
const { effectiveWasMentioned, shouldBypassMention } = mentionDecision;
|
|
2840
|
-
const canDetectMention = agentMentionRegexes.length > 0 || hasExplicitMention;
|
|
2841
|
-
if (mentionDecision.shouldSkip) {
|
|
2842
|
-
const pendingHistoryBody = preflightAudioTranscript ? formatMatrixAudioTranscript(preflightAudioTranscript) : pendingHistoryText || pendingHistoryPollText;
|
|
2843
|
-
if (historyLimit > 0 && pendingHistoryBody) {
|
|
2844
|
-
const pendingEntry = {
|
|
2845
|
-
sender: senderId,
|
|
2846
|
-
body: pendingHistoryBody,
|
|
2847
|
-
timestamp: eventTs ?? void 0,
|
|
2848
|
-
messageId
|
|
2849
|
-
};
|
|
2850
|
-
if (reservedHistorySlot) {
|
|
2851
|
-
roomHistoryTracker.finalizePending(roomId, reservedHistorySlot, pendingEntry, historyThreadId);
|
|
2852
|
-
reservedHistorySlotConsumed = true;
|
|
2853
|
-
} else roomHistoryTracker.recordPending(roomId, pendingEntry, historyThreadId);
|
|
2854
|
-
}
|
|
2855
|
-
logger.info("skipping room message", {
|
|
2856
|
-
roomId,
|
|
2857
|
-
reason: "no-mention"
|
|
2858
|
-
});
|
|
2859
|
-
await commitInboundEventIfClaimed();
|
|
2860
|
-
return;
|
|
2861
|
-
}
|
|
2862
|
-
if (preflightAudioTranscript) await sendMatrixPreflightAudioTranscriptEcho({
|
|
2863
|
-
transcript: preflightAudioTranscript,
|
|
2864
|
-
cfg,
|
|
2865
|
-
accountId,
|
|
2866
|
-
originatingTo: `room:${roomId}`,
|
|
2867
|
-
messageThreadId: thread.threadId
|
|
2868
|
-
});
|
|
2869
|
-
if (isPollEvent) {
|
|
2870
|
-
const pollSnapshot = await getPollSnapshot();
|
|
2871
|
-
if (!pollSnapshot) {
|
|
2872
|
-
discardReservedHistorySlot();
|
|
2873
|
-
return;
|
|
2874
|
-
}
|
|
2875
|
-
content = {
|
|
2876
|
-
msgtype: "m.text",
|
|
2877
|
-
body: pollSnapshot.text
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
let media = preflightMedia;
|
|
2881
|
-
let mediaDownloadFailed = preflightMediaDownloadFailed;
|
|
2882
|
-
let mediaSizeLimitExceeded = preflightMediaSizeLimitExceeded;
|
|
2883
|
-
const finalContentUrl = "url" in content && typeof content.url === "string" ? content.url : void 0;
|
|
2884
|
-
const finalContentFile = "file" in content && content.file && typeof content.file === "object" ? content.file : void 0;
|
|
2885
|
-
const finalMediaUrl = finalContentUrl ?? finalContentFile?.url;
|
|
2886
|
-
const contentBody = typeof content.body === "string" ? content.body.trim() : "";
|
|
2887
|
-
const originalFilename = (typeof content.filename === "string" ? content.filename.trim() : "") || contentBody || void 0;
|
|
2888
|
-
const contentInfo = "info" in content && content.info && typeof content.info === "object" ? content.info : void 0;
|
|
2889
|
-
const contentType = contentInfo?.mimetype;
|
|
2890
|
-
const contentSize = typeof contentInfo?.size === "number" ? contentInfo.size : void 0;
|
|
2891
|
-
if (!media && !mediaDownloadFailed && finalMediaUrl?.startsWith("mxc://")) try {
|
|
2892
|
-
media = await downloadMatrixMedia({
|
|
2893
|
-
client,
|
|
2894
|
-
mxcUrl: finalMediaUrl,
|
|
2895
|
-
contentType,
|
|
2896
|
-
sizeBytes: contentSize,
|
|
2897
|
-
maxBytes: mediaMaxBytes,
|
|
2898
|
-
file: finalContentFile,
|
|
2899
|
-
originalFilename
|
|
2900
|
-
});
|
|
2901
|
-
} catch (err) {
|
|
2902
|
-
mediaDownloadFailed = true;
|
|
2903
|
-
if (isMatrixMediaSizeLimitError(err)) mediaSizeLimitExceeded = true;
|
|
2904
|
-
const errorText = formatMatrixErrorMessage(err);
|
|
2905
|
-
logVerboseMessage(`matrix: media download failed room=${roomId} id=${event.event_id ?? "unknown"} type=${content.msgtype} error=${errorText}`);
|
|
2906
|
-
logger.warn("matrix media download failed", {
|
|
2907
|
-
roomId,
|
|
2908
|
-
eventId: event.event_id,
|
|
2909
|
-
msgtype: content.msgtype,
|
|
2910
|
-
encrypted: Boolean(finalContentFile),
|
|
2911
|
-
error: errorText
|
|
2912
|
-
});
|
|
2913
|
-
}
|
|
2914
|
-
let bodyText = resolveMatrixInboundBodyText({
|
|
2915
|
-
rawBody: locationPayload?.text ?? contentBody,
|
|
2916
|
-
filename: typeof content.filename === "string" ? content.filename : void 0,
|
|
2917
|
-
mediaPlaceholder: media?.placeholder,
|
|
2918
|
-
msgtype: content.msgtype,
|
|
2919
|
-
hadMediaUrl: Boolean(finalMediaUrl),
|
|
2920
|
-
mediaDownloadFailed,
|
|
2921
|
-
mediaSizeLimitExceeded
|
|
3755
|
+
senderId,
|
|
3756
|
+
roomHistoryTracker,
|
|
3757
|
+
commitInboundEventIfClaimed
|
|
2922
3758
|
});
|
|
2923
|
-
if (preflightMedia && bodyText && bodyText !== preflightMedia.placeholder && isLikelyBareFilename(bodyText)) bodyText = preflightMedia.placeholder;
|
|
2924
|
-
if (preflightAudioTranscript) {
|
|
2925
|
-
const transcriptBody = formatMatrixAudioTranscript(preflightAudioTranscript);
|
|
2926
|
-
bodyText = !bodyText || bodyText === media?.placeholder ? transcriptBody : `${bodyText}\n${transcriptBody}`;
|
|
2927
|
-
}
|
|
2928
|
-
if (!bodyText) {
|
|
2929
|
-
await commitInboundEventIfClaimedAndDiscardReserved();
|
|
2930
|
-
return;
|
|
2931
|
-
}
|
|
2932
|
-
const commandBodyText = hasControlCommandInMessage ? commandCheckText : bodyText;
|
|
2933
|
-
const senderName = await getSenderName();
|
|
2934
|
-
if (_configuredBinding) {
|
|
2935
|
-
const { ensureConfiguredAcpBindingReady } = await loadAcpBindingRuntime();
|
|
2936
|
-
if (!(await ensureConfiguredAcpBindingReady({
|
|
2937
|
-
cfg,
|
|
2938
|
-
configuredBinding: _configuredBinding
|
|
2939
|
-
})).ok) {
|
|
2940
|
-
logInboundDrop({
|
|
2941
|
-
log: logVerboseMessage,
|
|
2942
|
-
channel: "matrix",
|
|
2943
|
-
reason: "configured ACP binding unavailable",
|
|
2944
|
-
target: _configuredBinding.spec.conversationId
|
|
2945
|
-
});
|
|
2946
|
-
discardReservedHistorySlot();
|
|
2947
|
-
return;
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
if (_runtimeBindingId) {
|
|
2951
|
-
const { getSessionBindingService } = await loadSessionBindingRuntime();
|
|
2952
|
-
getSessionBindingService().touch(_runtimeBindingId, eventTs ?? void 0);
|
|
2953
|
-
}
|
|
2954
|
-
const preparedTrigger = isRoom && historyLimit > 0 ? reservedHistorySlot ? roomHistoryTracker.prepareReservedTrigger(_route.agentId, roomId, historyLimit, reservedHistorySlot, {
|
|
2955
|
-
sender: senderName,
|
|
2956
|
-
body: bodyText,
|
|
2957
|
-
timestamp: eventTs ?? void 0,
|
|
2958
|
-
messageId
|
|
2959
|
-
}, historyThreadId) : roomHistoryTracker.prepareTrigger(_route.agentId, roomId, historyLimit, {
|
|
2960
|
-
sender: senderName,
|
|
2961
|
-
body: bodyText,
|
|
2962
|
-
timestamp: eventTs ?? void 0,
|
|
2963
|
-
messageId
|
|
2964
|
-
}, historyThreadId) : void 0;
|
|
2965
|
-
if (reservedHistorySlot && preparedTrigger) reservedHistorySlotConsumed = true;
|
|
2966
|
-
return {
|
|
2967
|
-
route: _route,
|
|
2968
|
-
hasExplicitSessionBinding,
|
|
2969
|
-
roomConfig,
|
|
2970
|
-
isDirectMessage,
|
|
2971
|
-
isRoom,
|
|
2972
|
-
shouldRequireMention,
|
|
2973
|
-
wasMentioned,
|
|
2974
|
-
effectiveWasMentioned,
|
|
2975
|
-
shouldBypassMention,
|
|
2976
|
-
canDetectMention,
|
|
2977
|
-
commandAuthorized,
|
|
2978
|
-
inboundHistory: preparedTrigger ? buildInboundHistoryFromEntries({
|
|
2979
|
-
entries: preparedTrigger.history,
|
|
2980
|
-
limit: historyLimit
|
|
2981
|
-
}) : void 0,
|
|
2982
|
-
senderName,
|
|
2983
|
-
bodyText,
|
|
2984
|
-
commandBodyText,
|
|
2985
|
-
media,
|
|
2986
|
-
preflightAudioTranscript,
|
|
2987
|
-
locationPayload,
|
|
2988
|
-
messageId,
|
|
2989
|
-
triggerSnapshot: preparedTrigger,
|
|
2990
|
-
threadRootId,
|
|
2991
|
-
thread,
|
|
2992
|
-
botLoopProtection,
|
|
2993
|
-
effectiveGroupAllowFrom,
|
|
2994
|
-
effectiveRoomUsers
|
|
2995
|
-
};
|
|
2996
3759
|
};
|
|
2997
3760
|
const ingressResult = historyLimit > 0 ? await runRoomIngress(roomId, async () => {
|
|
2998
3761
|
const prefix = await readIngressPrefix();
|
|
@@ -3015,191 +3778,60 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
3015
3778
|
if (!resolvedIngressResult) return;
|
|
3016
3779
|
if ("deferredPrefix" in resolvedIngressResult) return;
|
|
3017
3780
|
const { route: _route, hasExplicitSessionBinding, roomConfig, isDirectMessage, isRoom, shouldRequireMention, wasMentioned, effectiveWasMentioned, shouldBypassMention, canDetectMention, commandAuthorized, inboundHistory, senderName, bodyText, commandBodyText, media, preflightAudioTranscript, locationPayload, messageId, triggerSnapshot, threadRootId, thread, botLoopProtection, effectiveGroupAllowFrom, effectiveRoomUsers } = resolvedIngressResult;
|
|
3018
|
-
const
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
if (!isRoom || !contextSenderId) return true;
|
|
3022
|
-
if (effectiveRoomUsers.length > 0) return resolveMatrixAllowListMatch({
|
|
3023
|
-
allowList: effectiveRoomUsers,
|
|
3024
|
-
userId: contextSenderId
|
|
3025
|
-
}).allowed;
|
|
3026
|
-
if (groupPolicy === "allowlist" && effectiveGroupAllowFrom.length > 0) return resolveMatrixAllowListMatch({
|
|
3027
|
-
allowList: effectiveGroupAllowFrom,
|
|
3028
|
-
userId: contextSenderId
|
|
3029
|
-
}).allowed;
|
|
3030
|
-
return true;
|
|
3031
|
-
};
|
|
3032
|
-
const shouldIncludeRoomContextSender = (kind, contextSenderId) => evaluateSupplementalContextVisibility({
|
|
3033
|
-
mode: contextVisibilityMode,
|
|
3034
|
-
kind,
|
|
3035
|
-
senderAllowed: isRoomContextSenderAllowed(contextSenderId)
|
|
3036
|
-
}).include;
|
|
3037
|
-
let threadContext = threadRootId ? await resolveThreadContext({
|
|
3038
|
-
roomId,
|
|
3039
|
-
threadRootId
|
|
3040
|
-
}) : void 0;
|
|
3041
|
-
let threadContextBlockedByPolicy = false;
|
|
3042
|
-
if (threadContext?.senderId && !shouldIncludeRoomContextSender("thread", threadContext.senderId)) {
|
|
3043
|
-
logVerboseMessage(`matrix: drop thread root context (mode=${contextVisibilityMode})`);
|
|
3044
|
-
threadContextBlockedByPolicy = true;
|
|
3045
|
-
threadContext = void 0;
|
|
3046
|
-
}
|
|
3047
|
-
let replyContext;
|
|
3048
|
-
if (replyToEventId && replyToEventId === threadRootId && threadContext?.summary) replyContext = {
|
|
3049
|
-
replyToBody: threadContext.summary,
|
|
3050
|
-
replyToSender: threadContext.senderLabel,
|
|
3051
|
-
replyToSenderId: threadContext.senderId
|
|
3052
|
-
};
|
|
3053
|
-
else if (replyToEventId && replyToEventId === threadRootId && threadContextBlockedByPolicy) replyContext = await resolveReplyContext({
|
|
3054
|
-
roomId,
|
|
3055
|
-
eventId: replyToEventId
|
|
3056
|
-
});
|
|
3057
|
-
else replyContext = replyToEventId ? await resolveReplyContext({
|
|
3058
|
-
roomId,
|
|
3059
|
-
eventId: replyToEventId
|
|
3060
|
-
}) : void 0;
|
|
3061
|
-
const replySenderAllowed = !replyContext?.replyToSenderId || isRoomContextSenderAllowed(replyContext.replyToSenderId);
|
|
3062
|
-
const roomName = (isRoom ? await getRoomInfo(roomId) : void 0)?.name;
|
|
3063
|
-
const envelopeFrom = isDirectMessage ? senderName : roomName ?? roomId;
|
|
3064
|
-
const textWithId = `${bodyText}\n[matrix event id: ${messageId} room: ${roomId}]`;
|
|
3065
|
-
const storePath = resolveStorePathImpl(cfg.session?.store, { agentId: _route.agentId });
|
|
3066
|
-
const buildEnvelope = createChannelInboundEnvelopeBuilderImpl({
|
|
3781
|
+
const inboundContext = await resolveMatrixInboundContext({
|
|
3782
|
+
client,
|
|
3783
|
+
core,
|
|
3067
3784
|
cfg,
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
const sharedDmContextNotice = isDirectMessage ? hasExplicitSessionBinding ? null : resolveMatrixSharedDmContextNotice({
|
|
3072
|
-
storePath,
|
|
3073
|
-
sessionKey: sharedDmNoticeSessionKey,
|
|
3785
|
+
accountId,
|
|
3786
|
+
runtime,
|
|
3787
|
+
logVerboseMessage,
|
|
3074
3788
|
roomId,
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
contextVisibility: contextVisibilityMode,
|
|
3095
|
-
finalize: finalizeInboundContext,
|
|
3096
|
-
supplemental: {
|
|
3097
|
-
quote: replyContext ? {
|
|
3098
|
-
id: threadTarget ? void 0 : replyToEventId ?? void 0,
|
|
3099
|
-
body: replyContext.replyToBody,
|
|
3100
|
-
sender: replyContext.replyToSender,
|
|
3101
|
-
senderAllowed: replySenderAllowed
|
|
3102
|
-
} : void 0,
|
|
3103
|
-
thread: {
|
|
3104
|
-
starterBody: threadContext?.threadStarterBody,
|
|
3105
|
-
senderAllowed: threadContext ? true : void 0
|
|
3106
|
-
},
|
|
3107
|
-
groupSystemPrompt: isRoom ? groupSystemPrompt : void 0
|
|
3108
|
-
},
|
|
3109
|
-
media: toInboundMediaFacts(media ? [{
|
|
3110
|
-
path: media.path,
|
|
3111
|
-
url: media.path,
|
|
3112
|
-
contentType: media.contentType,
|
|
3113
|
-
transcribed: preflightAudioTranscript !== void 0
|
|
3114
|
-
}] : void 0),
|
|
3789
|
+
event,
|
|
3790
|
+
eventTs: eventTs ?? void 0,
|
|
3791
|
+
route: _route,
|
|
3792
|
+
isDirectMessage,
|
|
3793
|
+
isRoom,
|
|
3794
|
+
effectiveRoomUsers,
|
|
3795
|
+
groupPolicy,
|
|
3796
|
+
effectiveGroupAllowFrom,
|
|
3797
|
+
contextVisibilityMode,
|
|
3798
|
+
resolveThreadContext,
|
|
3799
|
+
resolveReplyContext,
|
|
3800
|
+
threadRootId,
|
|
3801
|
+
thread,
|
|
3802
|
+
getRoomInfo,
|
|
3803
|
+
senderId,
|
|
3804
|
+
senderName,
|
|
3805
|
+
bodyText,
|
|
3806
|
+
commandBodyText,
|
|
3807
|
+
roomConfig,
|
|
3115
3808
|
messageId,
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
sender: {
|
|
3119
|
-
id: senderId,
|
|
3120
|
-
name: senderName,
|
|
3121
|
-
username: senderId.split(":")[0]?.replace(/^@/, "")
|
|
3122
|
-
},
|
|
3123
|
-
conversation: {
|
|
3124
|
-
kind: isDirectMessage ? "direct" : "channel",
|
|
3125
|
-
id: roomId,
|
|
3126
|
-
label: envelopeFrom,
|
|
3127
|
-
nativeChannelId: roomId,
|
|
3128
|
-
threadId: threadTarget
|
|
3129
|
-
},
|
|
3130
|
-
route: {
|
|
3131
|
-
agentId: _route.agentId,
|
|
3132
|
-
dmScope: _route.dmScope,
|
|
3133
|
-
accountId: _route.accountId,
|
|
3134
|
-
routeSessionKey: _route.sessionKey
|
|
3135
|
-
},
|
|
3136
|
-
reply: {
|
|
3137
|
-
to: `room:${roomId}`,
|
|
3138
|
-
replyToId: threadTarget ? void 0 : replyToEventId ?? void 0,
|
|
3139
|
-
messageThreadId: threadTarget,
|
|
3140
|
-
nativeChannelId: roomId
|
|
3141
|
-
},
|
|
3142
|
-
message: {
|
|
3143
|
-
body,
|
|
3144
|
-
rawBody: bodyText,
|
|
3145
|
-
commandBody: commandBodyText,
|
|
3146
|
-
bodyForAgent: bodyText,
|
|
3147
|
-
inboundHistory: inboundHistory && inboundHistory.length > 0 ? inboundHistory : void 0
|
|
3148
|
-
},
|
|
3149
|
-
access: {
|
|
3150
|
-
...isRoom ? { mentions: {
|
|
3151
|
-
canDetectMention: true,
|
|
3152
|
-
wasMentioned,
|
|
3153
|
-
requireMention: shouldRequireMention
|
|
3154
|
-
} } : {},
|
|
3155
|
-
commands: { authorized: commandAuthorized }
|
|
3156
|
-
},
|
|
3157
|
-
extra: {
|
|
3158
|
-
GroupSubject: isRoom ? roomName ?? roomId : void 0,
|
|
3159
|
-
GroupId: isRoom ? roomId : void 0,
|
|
3160
|
-
GroupChannel: isRoom ? roomId : void 0,
|
|
3161
|
-
...locationPayload?.context,
|
|
3162
|
-
CommandSource: "text",
|
|
3163
|
-
NativeDirectUserId: isDirectMessage ? senderId : void 0
|
|
3164
|
-
}
|
|
3165
|
-
});
|
|
3166
|
-
if (quoteHidden) logVerboseMessage(`matrix: drop reply context (mode=${contextVisibilityMode})`);
|
|
3167
|
-
const preview = truncateUtf16Safe(bodyText, 200).replace(/\n/g, "\\n");
|
|
3168
|
-
logVerboseMessage(`matrix inbound: room=${roomId} from=${senderId} preview="${preview}"`);
|
|
3169
|
-
const replyTarget = ctxPayload.To;
|
|
3170
|
-
if (!replyTarget) {
|
|
3171
|
-
runtime.error?.("matrix: missing reply target");
|
|
3172
|
-
return;
|
|
3173
|
-
}
|
|
3174
|
-
const { ackReaction, ackReactionScope: ackScope } = resolveMatrixAckReactionConfig({
|
|
3175
|
-
cfg,
|
|
3176
|
-
agentId: _route.agentId,
|
|
3177
|
-
accountId
|
|
3178
|
-
});
|
|
3179
|
-
const shouldAckReaction = () => Boolean(ackReaction && core.channel.reactions.shouldAckReaction({
|
|
3180
|
-
scope: ackScope,
|
|
3181
|
-
isDirect: isDirectMessage,
|
|
3182
|
-
isGroup: isRoom,
|
|
3183
|
-
isMentionableGroup: isRoom,
|
|
3184
|
-
requireMention: shouldRequireMention,
|
|
3185
|
-
canDetectMention,
|
|
3809
|
+
inboundHistory,
|
|
3810
|
+
wasMentioned,
|
|
3186
3811
|
effectiveWasMentioned,
|
|
3187
|
-
shouldBypassMention
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3812
|
+
shouldBypassMention,
|
|
3813
|
+
canDetectMention,
|
|
3814
|
+
shouldRequireMention,
|
|
3815
|
+
commandAuthorized,
|
|
3816
|
+
locationPayload,
|
|
3817
|
+
media,
|
|
3818
|
+
preflightAudioTranscript,
|
|
3819
|
+
historyLimit,
|
|
3820
|
+
hasExplicitSessionBinding,
|
|
3821
|
+
dmSessionScope,
|
|
3822
|
+
sharedDmContextNoticeRooms,
|
|
3823
|
+
resolveStorePath: resolveStorePathImpl,
|
|
3824
|
+
createChannelInboundEnvelopeBuilder: createChannelInboundEnvelopeBuilderImpl,
|
|
3825
|
+
finalizeInboundContext
|
|
3194
3826
|
});
|
|
3827
|
+
if (!inboundContext) return;
|
|
3828
|
+
const { replyToEventId, threadTarget, storePath, ctxPayload, replyTarget, sharedDmContextNotice } = inboundContext;
|
|
3195
3829
|
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
3196
3830
|
cfg,
|
|
3197
3831
|
channel: "matrix",
|
|
3198
3832
|
accountId: _route.accountId
|
|
3199
3833
|
});
|
|
3200
3834
|
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, _route.agentId);
|
|
3201
|
-
let finalReplyDeliveryFailed = false;
|
|
3202
|
-
let nonFinalReplyDeliveryFailed = false;
|
|
3203
3835
|
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
|
3204
3836
|
cfg,
|
|
3205
3837
|
agentId: _route.agentId,
|
|
@@ -3234,431 +3866,47 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
3234
3866
|
});
|
|
3235
3867
|
}
|
|
3236
3868
|
});
|
|
3237
|
-
const
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3869
|
+
const draftController = await createMatrixDraftController({
|
|
3870
|
+
streaming,
|
|
3871
|
+
previewToolProgressEnabled,
|
|
3872
|
+
replyToMode,
|
|
3873
|
+
messageId,
|
|
3874
|
+
threadTarget,
|
|
3875
|
+
accountConfig: params.accountConfig,
|
|
3876
|
+
cfg,
|
|
3877
|
+
accountId: _route.accountId,
|
|
3242
3878
|
roomId,
|
|
3243
3879
|
client,
|
|
3880
|
+
logVerboseMessage
|
|
3881
|
+
});
|
|
3882
|
+
const { draftStream } = draftController;
|
|
3883
|
+
draftControllerRef = draftController;
|
|
3884
|
+
const replyDispatcher = createMatrixReplyDispatcher({
|
|
3244
3885
|
cfg,
|
|
3245
|
-
|
|
3246
|
-
threadId: threadTarget,
|
|
3247
|
-
replyToId: draftReplyToId,
|
|
3248
|
-
preserveReplyId: replyToMode === "all",
|
|
3249
|
-
accountId: _route.accountId,
|
|
3250
|
-
log: logVerboseMessage
|
|
3251
|
-
})) : void 0;
|
|
3252
|
-
draftStreamRef = draftStream;
|
|
3253
|
-
const shouldStreamPreviewToolProgress = Boolean(draftStream) && previewToolProgressEnabled;
|
|
3254
|
-
const shouldSuppressDefaultToolProgressMessages = Boolean(draftStream) && (shouldStreamPreviewToolProgress || params.streaming === "progress");
|
|
3255
|
-
let currentDraftMessageGeneration = 0;
|
|
3256
|
-
let currentDraftBlockOffset = 0;
|
|
3257
|
-
let latestDraftFullText = "";
|
|
3258
|
-
const pendingDraftBoundaries = [];
|
|
3259
|
-
const latestQueuedDraftBoundaryOffsets = /* @__PURE__ */ new Map();
|
|
3260
|
-
let currentDraftReplyToId = draftReplyToId;
|
|
3261
|
-
let previewToolProgressSuppressed = false;
|
|
3262
|
-
let previewToolProgressLines = [];
|
|
3263
|
-
let latestPlan;
|
|
3264
|
-
let latestPlanExplanation;
|
|
3265
|
-
const progressConfigEntry = params.accountConfig ?? cfg.channels?.matrix;
|
|
3266
|
-
const progressSeed = `${_route.accountId}:${roomId}`;
|
|
3267
|
-
const renderProgressDraft = () => {
|
|
3268
|
-
if (!draftStream) return;
|
|
3269
|
-
const previewText = formatChannelProgressDraftText({
|
|
3270
|
-
entry: progressConfigEntry,
|
|
3271
|
-
lines: previewToolProgressLines,
|
|
3272
|
-
seed: progressSeed,
|
|
3273
|
-
formatLine: formatMatrixToolProgressMarkdownCode,
|
|
3274
|
-
bullet: "-",
|
|
3275
|
-
narration: latestPlanExplanation,
|
|
3276
|
-
plan: latestPlan
|
|
3277
|
-
});
|
|
3278
|
-
if (!previewText) return;
|
|
3279
|
-
draftStream.update(previewText);
|
|
3280
|
-
};
|
|
3281
|
-
const progressDraftGate = createChannelProgressDraftGate({ onStart: renderProgressDraft });
|
|
3282
|
-
const pushPreviewToolProgress = async (line, options) => {
|
|
3283
|
-
if (!draftStream) return;
|
|
3284
|
-
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
3285
|
-
const normalized = normalizeChannelProgressDraftLineIdentity(line);
|
|
3286
|
-
const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
|
|
3287
|
-
if (!progressDraftStreaming) {
|
|
3288
|
-
if (!shouldStreamPreviewToolProgress || previewToolProgressSuppressed || !normalized) return;
|
|
3289
|
-
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(progressConfigEntry) });
|
|
3290
|
-
if (nextLines === previewToolProgressLines) return;
|
|
3291
|
-
previewToolProgressLines = nextLines;
|
|
3292
|
-
draftStream.update(formatChannelProgressDraftText({
|
|
3293
|
-
entry: progressConfigEntry,
|
|
3294
|
-
lines: previewToolProgressLines,
|
|
3295
|
-
seed: progressSeed,
|
|
3296
|
-
formatLine: formatMatrixToolProgressMarkdownCode,
|
|
3297
|
-
bullet: "-",
|
|
3298
|
-
narration: latestPlanExplanation,
|
|
3299
|
-
plan: latestPlan
|
|
3300
|
-
}));
|
|
3301
|
-
return;
|
|
3302
|
-
}
|
|
3303
|
-
if (shouldStreamPreviewToolProgress && !previewToolProgressSuppressed && normalized) previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(progressConfigEntry) });
|
|
3304
|
-
const alreadyStarted = progressDraftGate.hasStarted;
|
|
3305
|
-
const progressActive = await progressDraftGate.noteWork();
|
|
3306
|
-
if ((alreadyStarted || progressActive) && progressDraftGate.hasStarted) renderProgressDraft();
|
|
3307
|
-
};
|
|
3308
|
-
const pushPlanProgress = async (steps, explanation) => {
|
|
3309
|
-
latestPlan = steps?.length ? steps.map((entry) => ({ ...entry })) : void 0;
|
|
3310
|
-
latestPlanExplanation = explanation?.replace(/\s+/g, " ").trim() || void 0;
|
|
3311
|
-
if (!draftStream || previewToolProgressSuppressed) return;
|
|
3312
|
-
if (!progressDraftStreaming) {
|
|
3313
|
-
renderProgressDraft();
|
|
3314
|
-
return;
|
|
3315
|
-
}
|
|
3316
|
-
const alreadyStarted = progressDraftGate.hasStarted;
|
|
3317
|
-
await progressDraftGate.startNow();
|
|
3318
|
-
if (alreadyStarted && progressDraftGate.hasStarted) renderProgressDraft();
|
|
3319
|
-
};
|
|
3320
|
-
const suppressPreviewToolProgressForAnswerText = (text) => {
|
|
3321
|
-
if (!text?.trim()) return;
|
|
3322
|
-
previewToolProgressSuppressed = true;
|
|
3323
|
-
previewToolProgressLines = [];
|
|
3324
|
-
latestPlan = void 0;
|
|
3325
|
-
latestPlanExplanation = void 0;
|
|
3326
|
-
};
|
|
3327
|
-
const resetPreviewToolProgress = () => {
|
|
3328
|
-
previewToolProgressSuppressed = false;
|
|
3329
|
-
previewToolProgressLines = [];
|
|
3330
|
-
latestPlan = void 0;
|
|
3331
|
-
latestPlanExplanation = void 0;
|
|
3332
|
-
};
|
|
3333
|
-
const buildPreviewToolProgressReplyOptions = () => {
|
|
3334
|
-
if (!shouldSuppressDefaultToolProgressMessages) return {};
|
|
3335
|
-
const options = { suppressDefaultToolProgressMessages: true };
|
|
3336
|
-
if (!shouldStreamPreviewToolProgress) return options;
|
|
3337
|
-
return {
|
|
3338
|
-
...options,
|
|
3339
|
-
onToolStart: async (payload) => {
|
|
3340
|
-
const toolName = payload.name?.trim();
|
|
3341
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
3342
|
-
event: "tool",
|
|
3343
|
-
itemId: payload.itemId,
|
|
3344
|
-
toolCallId: payload.toolCallId,
|
|
3345
|
-
name: toolName,
|
|
3346
|
-
phase: payload.phase,
|
|
3347
|
-
args: payload.args
|
|
3348
|
-
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName });
|
|
3349
|
-
},
|
|
3350
|
-
onItemEvent: async (payload) => {
|
|
3351
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
3352
|
-
event: "item",
|
|
3353
|
-
itemId: payload.itemId,
|
|
3354
|
-
toolCallId: payload.toolCallId,
|
|
3355
|
-
itemKind: payload.kind,
|
|
3356
|
-
title: payload.title,
|
|
3357
|
-
name: payload.name,
|
|
3358
|
-
phase: payload.phase,
|
|
3359
|
-
status: payload.status,
|
|
3360
|
-
summary: payload.summary,
|
|
3361
|
-
progressText: payload.progressText,
|
|
3362
|
-
meta: payload.meta
|
|
3363
|
-
}));
|
|
3364
|
-
},
|
|
3365
|
-
onPlanUpdate: async (payload) => {
|
|
3366
|
-
if (payload.phase !== "update") return;
|
|
3367
|
-
await pushPlanProgress(payload.steps, payload.explanation);
|
|
3368
|
-
},
|
|
3369
|
-
onApprovalEvent: async (payload) => {
|
|
3370
|
-
if (payload.phase !== "requested") return;
|
|
3371
|
-
await pushPreviewToolProgress(formatChannelProgressDraftLine({
|
|
3372
|
-
event: "approval",
|
|
3373
|
-
phase: payload.phase,
|
|
3374
|
-
title: payload.title,
|
|
3375
|
-
command: payload.command,
|
|
3376
|
-
reason: payload.reason,
|
|
3377
|
-
message: payload.message
|
|
3378
|
-
}));
|
|
3379
|
-
},
|
|
3380
|
-
onCommandOutput: async (payload) => {
|
|
3381
|
-
if (payload.phase !== "end") return;
|
|
3382
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
3383
|
-
event: "command-output",
|
|
3384
|
-
itemId: payload.itemId,
|
|
3385
|
-
toolCallId: payload.toolCallId,
|
|
3386
|
-
phase: payload.phase,
|
|
3387
|
-
title: payload.title,
|
|
3388
|
-
name: payload.name,
|
|
3389
|
-
status: payload.status,
|
|
3390
|
-
exitCode: payload.exitCode
|
|
3391
|
-
}));
|
|
3392
|
-
},
|
|
3393
|
-
onPatchSummary: async (payload) => {
|
|
3394
|
-
if (payload.phase !== "end") return;
|
|
3395
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(progressConfigEntry, {
|
|
3396
|
-
event: "patch",
|
|
3397
|
-
itemId: payload.itemId,
|
|
3398
|
-
toolCallId: payload.toolCallId,
|
|
3399
|
-
phase: payload.phase,
|
|
3400
|
-
title: payload.title,
|
|
3401
|
-
name: payload.name,
|
|
3402
|
-
added: payload.added,
|
|
3403
|
-
modified: payload.modified,
|
|
3404
|
-
deleted: payload.deleted,
|
|
3405
|
-
summary: payload.summary
|
|
3406
|
-
}));
|
|
3407
|
-
}
|
|
3408
|
-
};
|
|
3409
|
-
};
|
|
3410
|
-
const getDisplayableDraftText = () => {
|
|
3411
|
-
const nextDraftBoundaryOffset = pendingDraftBoundaries.find((boundary) => boundary.messageGeneration === currentDraftMessageGeneration)?.endOffset;
|
|
3412
|
-
if (nextDraftBoundaryOffset === void 0) return latestDraftFullText.slice(currentDraftBlockOffset);
|
|
3413
|
-
return latestDraftFullText.slice(currentDraftBlockOffset, nextDraftBoundaryOffset);
|
|
3414
|
-
};
|
|
3415
|
-
const updateDraftFromLatestFullText = () => {
|
|
3416
|
-
const blockText = getDisplayableDraftText();
|
|
3417
|
-
if (blockText) draftStream?.update(blockText);
|
|
3418
|
-
};
|
|
3419
|
-
const queueDraftBlockBoundary = (payload, context) => {
|
|
3420
|
-
const payloadTextLength = payload.text?.length ?? 0;
|
|
3421
|
-
const messageGeneration = context?.assistantMessageIndex ?? currentDraftMessageGeneration;
|
|
3422
|
-
const nextDraftBoundaryOffset = (latestQueuedDraftBoundaryOffsets.get(messageGeneration) ?? 0) + payloadTextLength;
|
|
3423
|
-
latestQueuedDraftBoundaryOffsets.set(messageGeneration, nextDraftBoundaryOffset);
|
|
3424
|
-
pendingDraftBoundaries.push({
|
|
3425
|
-
messageGeneration,
|
|
3426
|
-
endOffset: nextDraftBoundaryOffset
|
|
3427
|
-
});
|
|
3428
|
-
};
|
|
3429
|
-
const advanceDraftBlockBoundary = (options) => {
|
|
3430
|
-
const completedBoundary = pendingDraftBoundaries.shift();
|
|
3431
|
-
if (completedBoundary) {
|
|
3432
|
-
if (!pendingDraftBoundaries.some((entry) => entry.messageGeneration === completedBoundary.messageGeneration)) latestQueuedDraftBoundaryOffsets.delete(completedBoundary.messageGeneration);
|
|
3433
|
-
if (completedBoundary.messageGeneration === currentDraftMessageGeneration) currentDraftBlockOffset = completedBoundary.endOffset;
|
|
3434
|
-
return;
|
|
3435
|
-
}
|
|
3436
|
-
if (options?.fallbackToLatestEnd) currentDraftBlockOffset = latestDraftFullText.length;
|
|
3437
|
-
};
|
|
3438
|
-
const resetDraftBlockOffsets = () => {
|
|
3439
|
-
currentDraftMessageGeneration += 1;
|
|
3440
|
-
currentDraftBlockOffset = 0;
|
|
3441
|
-
latestDraftFullText = "";
|
|
3442
|
-
};
|
|
3443
|
-
const resetDraftDeliveryState = async () => {
|
|
3444
|
-
await draftStream?.discardPending();
|
|
3445
|
-
draftStream?.reset();
|
|
3446
|
-
draftConsumed = false;
|
|
3447
|
-
currentDraftMessageGeneration = 0;
|
|
3448
|
-
currentDraftBlockOffset = 0;
|
|
3449
|
-
latestDraftFullText = "";
|
|
3450
|
-
pendingDraftBoundaries.length = 0;
|
|
3451
|
-
latestQueuedDraftBoundaryOffsets.clear();
|
|
3452
|
-
currentDraftReplyToId = draftReplyToId;
|
|
3453
|
-
progressDraftGate.reset();
|
|
3454
|
-
resetPreviewToolProgress();
|
|
3455
|
-
};
|
|
3456
|
-
const { deliver: deliverReply, onError: onReplyError, ...turnDispatcherOptions } = {
|
|
3457
|
-
...prefixOptions,
|
|
3886
|
+
prefixOptions,
|
|
3458
3887
|
humanDelay: resolveHumanDelayConfigImpl(cfg, _route.agentId),
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
replyToMode,
|
|
3477
|
-
threadId: threadTarget,
|
|
3478
|
-
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3479
|
-
accountId: _route.accountId,
|
|
3480
|
-
mediaLocalRoots,
|
|
3481
|
-
tableMode
|
|
3482
|
-
});
|
|
3483
|
-
return;
|
|
3484
|
-
}
|
|
3485
|
-
const payloadReplyToId = normalizeOptionalString(payload.replyToId);
|
|
3486
|
-
const payloadReplyMismatch = replyToMode !== "off" && !threadTarget && payloadReplyToId !== currentDraftReplyToId;
|
|
3487
|
-
let mustDeliverFinalNormally = draftStream.mustDeliverFinalNormally();
|
|
3488
|
-
if (Boolean(payload.text?.trim()) && !payload.isError && !payloadReplyMismatch && !mustDeliverFinalNormally) {
|
|
3489
|
-
await draftStream.stop();
|
|
3490
|
-
mustDeliverFinalNormally = draftStream.mustDeliverFinalNormally();
|
|
3491
|
-
} else await draftStream.discardPending();
|
|
3492
|
-
const draftEventId = draftStream.eventId();
|
|
3493
|
-
const draftFinalTextNeedsNormalMentionDelivery = Boolean(draftEventId) && typeof payload.text === "string" && Boolean(payload.text.trim()) && !payload.isError && !payloadReplyMismatch && !mustDeliverFinalNormally && await matrixTextWouldActivateMentions(client, payload.text);
|
|
3494
|
-
if (draftEventId && payload.text && !payload.isError && !hasMedia && !payloadReplyMismatch && !mustDeliverFinalNormally && !draftFinalTextNeedsNormalMentionDelivery) {
|
|
3495
|
-
const finalPreviewText = payload.text;
|
|
3496
|
-
await deliverWithFinalizableLivePreviewAdapter({
|
|
3497
|
-
kind: "final",
|
|
3498
|
-
payload,
|
|
3499
|
-
adapter: defineFinalizableLivePreviewAdapter({
|
|
3500
|
-
draft: {
|
|
3501
|
-
flush: async () => {},
|
|
3502
|
-
clear: async () => {},
|
|
3503
|
-
discardPending: async () => {},
|
|
3504
|
-
id: () => draftEventId
|
|
3505
|
-
},
|
|
3506
|
-
buildFinalEdit: () => ({
|
|
3507
|
-
text: finalPreviewText,
|
|
3508
|
-
finalizeLive: !(quietDraftStreaming || !draftStream.matchesPreparedText(finalPreviewText)),
|
|
3509
|
-
...quietDraftStreaming ? { extraContent: buildMatrixFinalizedPreviewContent() } : {}
|
|
3510
|
-
}),
|
|
3511
|
-
editFinal: async (_draftEventId, edit) => {
|
|
3512
|
-
if (edit.finalizeLive) {
|
|
3513
|
-
if (!await draftStream.finalizeLive()) throw new Error("Matrix draft live finalize failed");
|
|
3514
|
-
return;
|
|
3515
|
-
}
|
|
3516
|
-
const { editMessageMatrix } = await loadMatrixSendModule();
|
|
3517
|
-
await editMessageMatrix(roomId, _draftEventId, edit.text, {
|
|
3518
|
-
client,
|
|
3519
|
-
cfg,
|
|
3520
|
-
threadId: threadTarget,
|
|
3521
|
-
accountId: _route.accountId,
|
|
3522
|
-
extraContent: edit.extraContent
|
|
3523
|
-
});
|
|
3524
|
-
},
|
|
3525
|
-
createPreviewReceipt: (id) => createPreviewMessageReceipt({
|
|
3526
|
-
id,
|
|
3527
|
-
...threadTarget ? { threadId: threadTarget } : {},
|
|
3528
|
-
...currentDraftReplyToId ? { replyToId: currentDraftReplyToId } : {}
|
|
3529
|
-
}),
|
|
3530
|
-
logPreviewEditFailure: (err) => {
|
|
3531
|
-
logVerboseMessage(`matrix: preview final edit failed: ${String(err)}`);
|
|
3532
|
-
}
|
|
3533
|
-
}),
|
|
3534
|
-
deliverNormally: async () => {
|
|
3535
|
-
await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3536
|
-
await deliverMatrixReplies({
|
|
3537
|
-
cfg,
|
|
3538
|
-
replies: [fallbackPayload],
|
|
3539
|
-
roomId,
|
|
3540
|
-
client,
|
|
3541
|
-
runtime,
|
|
3542
|
-
textLimit,
|
|
3543
|
-
replyToMode,
|
|
3544
|
-
threadId: threadTarget,
|
|
3545
|
-
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3546
|
-
accountId: _route.accountId,
|
|
3547
|
-
mediaLocalRoots,
|
|
3548
|
-
tableMode
|
|
3549
|
-
});
|
|
3550
|
-
}
|
|
3551
|
-
});
|
|
3552
|
-
draftConsumed = true;
|
|
3553
|
-
} else if (draftEventId && hasMedia && !payloadReplyMismatch) {
|
|
3554
|
-
let textEditOk = !mustDeliverFinalNormally;
|
|
3555
|
-
const payloadText = payload.text ?? ttsSupplement?.spokenText;
|
|
3556
|
-
const payloadTextMatchesDraft = typeof payloadText === "string" && draftStream.matchesPreparedText(payloadText);
|
|
3557
|
-
const reusesDraftTextUnchanged = typeof payloadText === "string" && Boolean(payloadText.trim()) && payloadTextMatchesDraft;
|
|
3558
|
-
const mediaTextNeedsNormalMentionDelivery = typeof payloadText === "string" && Boolean(payloadText.trim()) && await matrixTextWouldActivateMentions(client, payloadText);
|
|
3559
|
-
const requiresFinalTextEdit = quietDraftStreaming || typeof payloadText === "string" && !payloadTextMatchesDraft;
|
|
3560
|
-
if (textEditOk && mediaTextNeedsNormalMentionDelivery) textEditOk = false;
|
|
3561
|
-
else if (textEditOk && payloadText && requiresFinalTextEdit) {
|
|
3562
|
-
const { editMessageMatrix } = await loadMatrixSendModule();
|
|
3563
|
-
textEditOk = await editMessageMatrix(roomId, draftEventId, payloadText, {
|
|
3564
|
-
client,
|
|
3565
|
-
cfg,
|
|
3566
|
-
threadId: threadTarget,
|
|
3567
|
-
accountId: _route.accountId,
|
|
3568
|
-
extraContent: quietDraftStreaming ? buildMatrixFinalizedPreviewContent() : void 0
|
|
3569
|
-
}).then(() => true, () => false);
|
|
3570
|
-
} else if (textEditOk && reusesDraftTextUnchanged) textEditOk = await draftStream.finalizeLive();
|
|
3571
|
-
const reusesDraftAsFinalText = Boolean(payloadText?.trim()) && textEditOk;
|
|
3572
|
-
if (!reusesDraftAsFinalText) await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3573
|
-
const mediaPayload = ttsSupplement && reusesDraftAsFinalText ? buildTtsSupplementMediaPayload(payload) : {
|
|
3574
|
-
...payload,
|
|
3575
|
-
text: reusesDraftAsFinalText ? void 0 : payload.text ?? (ttsSupplement?.visibleTextAlreadyDelivered === true ? void 0 : ttsSupplement?.spokenText)
|
|
3576
|
-
};
|
|
3577
|
-
await deliverMatrixReplies({
|
|
3578
|
-
cfg,
|
|
3579
|
-
replies: [mediaPayload],
|
|
3580
|
-
roomId,
|
|
3581
|
-
client,
|
|
3582
|
-
runtime,
|
|
3583
|
-
textLimit,
|
|
3584
|
-
replyToMode,
|
|
3585
|
-
threadId: threadTarget,
|
|
3586
|
-
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3587
|
-
accountId: _route.accountId,
|
|
3588
|
-
mediaLocalRoots,
|
|
3589
|
-
tableMode
|
|
3590
|
-
});
|
|
3591
|
-
draftConsumed = true;
|
|
3592
|
-
} else {
|
|
3593
|
-
const draftRedacted = Boolean(draftEventId) && (payload.isError || payloadReplyMismatch || mustDeliverFinalNormally || draftFinalTextNeedsNormalMentionDelivery);
|
|
3594
|
-
if (draftRedacted && draftEventId) await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3595
|
-
const deliveredFallback = await deliverMatrixReplies({
|
|
3596
|
-
cfg,
|
|
3597
|
-
replies: [fallbackPayload],
|
|
3598
|
-
roomId,
|
|
3599
|
-
client,
|
|
3600
|
-
runtime,
|
|
3601
|
-
textLimit,
|
|
3602
|
-
replyToMode,
|
|
3603
|
-
threadId: threadTarget,
|
|
3604
|
-
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3605
|
-
accountId: _route.accountId,
|
|
3606
|
-
mediaLocalRoots,
|
|
3607
|
-
tableMode
|
|
3608
|
-
});
|
|
3609
|
-
if (draftRedacted || deliveredFallback) draftConsumed = true;
|
|
3610
|
-
}
|
|
3611
|
-
if (info.kind === "block") {
|
|
3612
|
-
draftConsumed = false;
|
|
3613
|
-
advanceDraftBlockBoundary({ fallbackToLatestEnd: true });
|
|
3614
|
-
draftStream.reset();
|
|
3615
|
-
currentDraftReplyToId = replyToMode === "all" ? draftReplyToId : void 0;
|
|
3616
|
-
updateDraftFromLatestFullText();
|
|
3617
|
-
const { sendTypingMatrix } = await loadMatrixSendModule();
|
|
3618
|
-
await sendTypingMatrix(roomId, true, void 0, client).catch(() => {});
|
|
3619
|
-
}
|
|
3620
|
-
} else await deliverMatrixReplies({
|
|
3621
|
-
cfg,
|
|
3622
|
-
replies: [payload],
|
|
3623
|
-
roomId,
|
|
3624
|
-
client,
|
|
3625
|
-
runtime,
|
|
3626
|
-
textLimit,
|
|
3627
|
-
replyToMode,
|
|
3628
|
-
threadId: threadTarget,
|
|
3629
|
-
replyToId: threadTarget ?? replyToEventId ?? void 0,
|
|
3630
|
-
accountId: _route.accountId,
|
|
3631
|
-
mediaLocalRoots,
|
|
3632
|
-
tableMode
|
|
3633
|
-
});
|
|
3634
|
-
},
|
|
3635
|
-
onError: (err, info) => {
|
|
3636
|
-
if (info.kind === "final") finalReplyDeliveryFailed = true;
|
|
3637
|
-
else nonFinalReplyDeliveryFailed = true;
|
|
3638
|
-
if (info.kind === "block") advanceDraftBlockBoundary({ fallbackToLatestEnd: true });
|
|
3639
|
-
runtime.error?.(`matrix ${info.kind} reply failed: ${String(err)}`);
|
|
3640
|
-
},
|
|
3641
|
-
onReplyStart: typingCallbacks.onReplyStart,
|
|
3642
|
-
onIdle: typingCallbacks.onIdle
|
|
3643
|
-
};
|
|
3888
|
+
typingCallbacks,
|
|
3889
|
+
streaming,
|
|
3890
|
+
draftStream,
|
|
3891
|
+
draftController,
|
|
3892
|
+
client,
|
|
3893
|
+
roomId,
|
|
3894
|
+
runtime,
|
|
3895
|
+
textLimit,
|
|
3896
|
+
replyToMode,
|
|
3897
|
+
threadTarget,
|
|
3898
|
+
replyToEventId: replyToEventId ?? void 0,
|
|
3899
|
+
accountId: _route.accountId,
|
|
3900
|
+
mediaLocalRoots,
|
|
3901
|
+
tableMode,
|
|
3902
|
+
logVerboseMessage
|
|
3903
|
+
});
|
|
3904
|
+
const { deliverReply, onReplyError, turnDispatcherOptions } = replyDispatcher;
|
|
3644
3905
|
const pinnedMainDmOwner = isDirectMessage ? await (async () => {
|
|
3645
|
-
const
|
|
3646
|
-
const livePinnedAllowlists = resolveMatrixAccountAllowlistConfig({
|
|
3647
|
-
cfg: livePinnedCfg,
|
|
3648
|
-
accountId
|
|
3649
|
-
});
|
|
3650
|
-
const livePinnedDmAllowFrom = await resolveCachedLiveAllowlist({
|
|
3651
|
-
cfg: livePinnedCfg,
|
|
3652
|
-
entries: livePinnedAllowlists.dmAllowFrom,
|
|
3653
|
-
startupResolvedEntries: allowFromResolvedEntries,
|
|
3654
|
-
cache: liveDmAllowlistCache,
|
|
3655
|
-
updateCache: (next) => {
|
|
3656
|
-
liveDmAllowlistCache = next;
|
|
3657
|
-
}
|
|
3658
|
-
});
|
|
3906
|
+
const { liveCfg, liveDmAllowFrom } = await handlerState.resolveLiveAccountAllowlists();
|
|
3659
3907
|
return resolvePinnedMainDmOwnerFromAllowlist({
|
|
3660
|
-
dmScope:
|
|
3661
|
-
allowFrom:
|
|
3908
|
+
dmScope: liveCfg.session?.dmScope,
|
|
3909
|
+
allowFrom: liveDmAllowFrom,
|
|
3662
3910
|
normalizeEntry: normalizeMatrixUserId
|
|
3663
3911
|
});
|
|
3664
3912
|
})() : null;
|
|
@@ -3726,27 +3974,22 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
3726
3974
|
},
|
|
3727
3975
|
dispatcherOptions: {
|
|
3728
3976
|
...turnDispatcherOptions,
|
|
3729
|
-
onSettled: () => progressDraftGate.cancel()
|
|
3977
|
+
onSettled: () => draftController.progressDraftGate.cancel()
|
|
3730
3978
|
},
|
|
3731
3979
|
replyOptions: {
|
|
3732
3980
|
skillFilter: roomConfig?.skills,
|
|
3733
3981
|
disableBlockStreaming: !blockStreamingEnabled,
|
|
3734
|
-
onPartialReply: draftStream ? (payload) =>
|
|
3735
|
-
if (progressDraftStreaming) return;
|
|
3736
|
-
latestDraftFullText = payload.text ?? "";
|
|
3737
|
-
suppressPreviewToolProgressForAnswerText(latestDraftFullText);
|
|
3738
|
-
updateDraftFromLatestFullText();
|
|
3739
|
-
} : void 0,
|
|
3982
|
+
onPartialReply: draftStream ? (payload) => draftController.onPartialReply(payload.text ?? "") : void 0,
|
|
3740
3983
|
onBlockReplyQueued: draftStream ? (payload, context) => {
|
|
3741
3984
|
if (payload.isCompactionNotice === true) return;
|
|
3742
|
-
queueDraftBlockBoundary(payload, context);
|
|
3985
|
+
draftController.queueDraftBlockBoundary(payload, context);
|
|
3743
3986
|
} : void 0,
|
|
3744
3987
|
onAssistantMessageStart: draftStream ? () => {
|
|
3745
|
-
resetDraftBlockOffsets();
|
|
3746
|
-
resetPreviewToolProgress();
|
|
3988
|
+
draftController.resetDraftBlockOffsets();
|
|
3989
|
+
draftController.resetPreviewToolProgress();
|
|
3747
3990
|
} : void 0,
|
|
3748
|
-
onQueuedFollowupAdmitted: draftStream ? resetDraftDeliveryState : void 0,
|
|
3749
|
-
...buildPreviewToolProgressReplyOptions(),
|
|
3991
|
+
onQueuedFollowupAdmitted: draftStream ? draftController.resetDraftDeliveryState : void 0,
|
|
3992
|
+
...draftController.buildPreviewToolProgressReplyOptions(),
|
|
3750
3993
|
onModelSelected
|
|
3751
3994
|
}
|
|
3752
3995
|
})
|
|
@@ -3758,12 +4001,12 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
3758
4001
|
}
|
|
3759
4002
|
const { dispatchResult } = turnResult;
|
|
3760
4003
|
const { queuedFinal, counts } = dispatchResult;
|
|
3761
|
-
if (finalReplyDeliveryFailed) {
|
|
4004
|
+
if (replyDispatcher.finalReplyDeliveryFailed()) {
|
|
3762
4005
|
logVerboseMessage(`matrix: final reply delivery failed room=${roomId} id=${messageId}; keeping replay committed`);
|
|
3763
4006
|
await commitInboundEventIfClaimed();
|
|
3764
4007
|
return;
|
|
3765
4008
|
}
|
|
3766
|
-
if (!queuedFinal && nonFinalReplyDeliveryFailed) {
|
|
4009
|
+
if (!queuedFinal && replyDispatcher.nonFinalReplyDeliveryFailed()) {
|
|
3767
4010
|
logVerboseMessage(`matrix: non-final reply delivery failed room=${roomId} id=${messageId}; keeping replay committed`);
|
|
3768
4011
|
await commitInboundEventIfClaimed();
|
|
3769
4012
|
return;
|
|
@@ -3782,9 +4025,10 @@ function createMatrixRoomMessageHandler(params) {
|
|
|
3782
4025
|
} catch (err) {
|
|
3783
4026
|
runtime.error?.(`matrix handler failed: ${String(err)}`);
|
|
3784
4027
|
} finally {
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
4028
|
+
const draftStream = draftControllerRef?.draftStream;
|
|
4029
|
+
if (draftStream) {
|
|
4030
|
+
const draftEventId = await draftStream.stop().catch(() => void 0);
|
|
4031
|
+
if (draftEventId && draftControllerRef?.isDraftConsumed() !== true) await redactMatrixDraftEvent(client, roomId, draftEventId);
|
|
3788
4032
|
}
|
|
3789
4033
|
inboundReplayClaim?.release();
|
|
3790
4034
|
}
|
|
@@ -3805,10 +4049,10 @@ function shouldPromoteRecentInviteRoom(params) {
|
|
|
3805
4049
|
//#endregion
|
|
3806
4050
|
//#region extensions/matrix/src/matrix/monitor/startup.ts
|
|
3807
4051
|
const loadMatrixStartupMaintenanceDeps = createLazyRuntimeModule(() => Promise.all([
|
|
3808
|
-
import("./setup-core-
|
|
3809
|
-
import("./device-health-
|
|
3810
|
-
import("./setup-core-
|
|
3811
|
-
import("./startup-verification-
|
|
4052
|
+
import("./setup-core-a542Ernq.js").then((n) => n.c),
|
|
4053
|
+
import("./device-health-B4-YXtJR.js").then((n) => n.t),
|
|
4054
|
+
import("./setup-core-a542Ernq.js").then((n) => n.o),
|
|
4055
|
+
import("./startup-verification-wm3_i88s.js")
|
|
3812
4056
|
]).then(([configUpdateModule, deviceHealthModule, profileModule, startupVerificationModule]) => ({
|
|
3813
4057
|
updateMatrixAccountConfig: configUpdateModule.updateMatrixAccountConfig,
|
|
3814
4058
|
summarizeMatrixDeviceHealth: deviceHealthModule.summarizeMatrixDeviceHealth,
|
|
@@ -4037,20 +4281,13 @@ function createMatrixMonitorTaskRunner(params) {
|
|
|
4037
4281
|
}
|
|
4038
4282
|
//#endregion
|
|
4039
4283
|
//#region extensions/matrix/src/matrix/monitor/index.ts
|
|
4040
|
-
function isMatrixStreamingConfig(streaming) {
|
|
4041
|
-
return Boolean(streaming && typeof streaming === "object" && !Array.isArray(streaming));
|
|
4042
|
-
}
|
|
4043
4284
|
function resolveMatrixStreamingMode(streaming) {
|
|
4044
|
-
|
|
4045
|
-
if (
|
|
4046
|
-
if (streaming === "progress") return "progress";
|
|
4047
|
-
if (isMatrixStreamingConfig(streaming)) {
|
|
4048
|
-
if (streaming.mode === "partial" || streaming.mode === "quiet" || streaming.mode === "progress") return streaming.mode;
|
|
4049
|
-
}
|
|
4285
|
+
const mode = streaming?.mode;
|
|
4286
|
+
if (mode === "partial" || mode === "quiet" || mode === "progress") return mode;
|
|
4050
4287
|
return "off";
|
|
4051
4288
|
}
|
|
4052
4289
|
function resolveMatrixPreviewToolProgress(streaming) {
|
|
4053
|
-
if (!
|
|
4290
|
+
if (!streaming) return true;
|
|
4054
4291
|
if (resolveMatrixStreamingMode(streaming) === "progress") return streaming.progress?.toolProgress ?? streaming.preview?.toolProgress ?? true;
|
|
4055
4292
|
return streaming.preview?.toolProgress ?? true;
|
|
4056
4293
|
}
|