mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(telegram): expose live location updates to hooks (#122185)
* fix(telegram): expose live location updates to hooks * test(telegram): keep location coverage focused * fix(telegram): emit stopped live locations * test(plugin-sdk): refresh location hook contracts * docs(changelog): note Telegram live location hooks * fix(telegram): complete live-location observer path --------- Co-authored-by: mbelinky <mbelinky@users.noreply.github.com>
This commit is contained in:
@@ -68,6 +68,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Telegram live locations:** expose initial, moving, and stopped live-location updates through the channel-neutral `message_received` hook without starting agent turns for edits.
|
||||
- **Updater plugin convergence:** keep pre-plugin doctor passes from installing configured plugins before the updater's plugin sweep, while preserving the final post-plugin migration pass and preventing ambient update-phase state from leaking into fresh doctor processes.
|
||||
- **Control UI browser tab identity:** keep selected tab styling, accessibility, focus, address, and page snapshot aligned across in-place navigation and tab reordering. Fixes #120745. Thanks @shakkernerd.
|
||||
- **Control UI staged attachments:** preserve unsent images, files, pasted images, and large pasted text across same-tab route and narrow split-pane remounts while keeping pane close, mismatched pane/session/Gateway remounts, application shutdown, and hard reload as cleanup boundaries. Fixes #121519. Thanks @shakkernerd.
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"00b95749d23bbf126f7081ee8da4a303b2c984f181be7b68f8bf48b12b67e901","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
{"contentHash":"6d42269794e0473fdae832d6c1af49854a822438f4b3b0943fb855846cbd5f64","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"a806b806c516e82741cf2731d0d0375736eda65dcf130f0a66f3a631b6b1b482","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
{"contentHash":"d5a6edec36b8675110181c771b79fac5753d242ce8adc58022743eb0184c34f2","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"c97e7fbdbd52d6afe181fe0685495582ad5802f3f3fb7e531b2e123ddd4f6a32","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
{"contentHash":"02efab07a69f7899421c36ab7cee6dd2bd84ce2d689d10549129dc4dc45a1f15","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f93ca33e4a11459e4def3d6c09463fcbe0f93c6cd6bb967edf7ab6ebd3dbe584","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"}
|
||||
{"contentHash":"30b463a4e09c326f52255ad4015c8546c7bca5475d33d784969e2cc4f1feed40","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"60730746ea4ad9d8aef60a7f4d4aecd7cd28ae5d28b21e44646b3fe1398e6c95","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
{"contentHash":"74e044d5e8a93fd319609f0d130eea0c1e636111e7c2597cbc3c98b847515bd3","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"3d0d58f29870cbb7889517e24b874ea5b63dfde6c15433c13414e8484b74d266","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
{"contentHash":"176592119d65008bfa8d1b465670c9556b0275d432e738353ea5a25d0a61985d","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"dbbddd5dc44f96642d3885073c6d2939bca17d1ee59c4e65dfd734ccaa115b4b","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
{"contentHash":"39d7eea281be7a268c1878155930034027f73ae6404622ab09041300bda41991","entrypoint":"channel-inbound","importSpecifier":"openclaw/plugin-sdk/channel-inbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"f185748f1394f1d64653b21889954229bf0049e174526554e52c8e0bfc2c7dc5","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
{"contentHash":"d10f59babb0166e4db0a66e801ca674ad23b158737939d9213c3bc6258f2cc5c","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"59cfee1296e172c295a550d4adbfff8776f10b73e07902394a68aa143efecabc","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
{"contentHash":"dcbaf714f293fbe933587344f798c0d86bfd65dc74e05e552c08666695da5125","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"07f8d2b5d1186410f5233394028b5df8a5cdbcb7a40011ca089db78e202d3f8f","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"}
|
||||
{"contentHash":"b09818bcd98562155b66aafd6643b7487a4c267f662c1da87434b47a03446b4e","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"400ffb2b574275188fcbaeb2bc4fd6c5dd6cc1007c76223b1d63d2056b3ddd99","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
{"contentHash":"5e92fe659c6fa93639b6e197bca678ec533c190208c65eee3ef4cb24f3929848","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"a5feee467592d71486b388de14c3a08ccaf776c5e31d9355aa49bbfa3dbcb9d1","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"}
|
||||
{"contentHash":"41c8a0c1c9454afec387fb980900d4795ef29f45a05a025aeb1602bb7739b7a8","entrypoint":"command-auth-native","importSpecifier":"openclaw/plugin-sdk/command-auth-native"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"e0e0ab127fcab6833850a208ecf21cc26bbfda73d4240dcddebae4790ea641db","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"}
|
||||
{"contentHash":"59750b3a09074d7e6743f4f1ec4b9ee3623862521d6be196131ed50977a15539","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"f42f280c5fec25a7dae84fa52387525e3ef21c78ac1ee2c0ae19f8e58db829c1","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
|
||||
{"contentHash":"96df262c825f0096a8050df72f6391bfa8c399555d148491dfbdbd6c402fd63b","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"c48ebc2d528a2f3fb2496828c20b871f72c09dc8636ec8b12e94f9e91199f751","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"}
|
||||
{"contentHash":"b0b4951cd020c358a1d27888c7794a6021c9e714502fa73becafaae100e67003","entrypoint":"conversation-runtime","importSpecifier":"openclaw/plugin-sdk/conversation-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"589c2e34ab4910b95df172434a94e7a74838280c31f0955319fd696be3f3bc78","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
{"contentHash":"db4f872ae8ea2b1c36068c4fa4d0550e49550f34d31cbb4898533cdde9f4d103","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"55b33df13c3362c01f13580f9903900075a2bb50b05cae56533a3ce9a5170639","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
{"contentHash":"9a9729aaa9a46400464a7c2b5240541a9f379271b863b25e1c4dd1c3b7508e73","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"6066a7ed3f68774a9e108b71a5bedfa002582dfdfa64e53ba8fefecb2310c09f","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
|
||||
{"contentHash":"372c9a2f49fd5d431197b7f35c37321a4d4296d7a1cddc2b71c3ee757ebe95a7","entrypoint":"hook-runtime","importSpecifier":"openclaw/plugin-sdk/hook-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"61c67e6ebab5fc4d69226afa0d94f794e8d6798658dfaef60184506551b97a47","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
{"contentHash":"0006fe56489e1a95488e436e5b2d881c353003ba24163aa5620f8b52c87c0d99","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"d5e2f7be4ffffaff8ff33cf86761e8e88ccca74b10a9d5cc31ab7cd99012af74","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"}
|
||||
{"contentHash":"04ca7bf038cd8ad69f3145ebd3c774b8b27b18ed96fa8227e289abcecdbd83b5","entrypoint":"media-runtime","importSpecifier":"openclaw/plugin-sdk/media-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"b6ca0750b3c2ada85cec932d08e323e28284566869e2b38d4ae2a849d0947ba5","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"}
|
||||
{"contentHash":"7d5fd82c531675b86446df475d1744902b721dbcc345aeabaf7bdfee27a77bc0","entrypoint":"media-understanding-runtime","importSpecifier":"openclaw/plugin-sdk/media-understanding-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"8d9f9721597b619189cbaa30747e10d2fb8adcf8e8404880b85683fb8fcebd47","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
{"contentHash":"4d4adfe9f53f025c8bb22ee1a1dff2e224dae6bb25158ac1ac991cf7ebad249b","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"b25cfc5913de6cc44719995ec258226c7cb9fd1623afc277d75a5bc2e9870ccf","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
{"contentHash":"61e6ba4e3564811059b3d27ecfee1df8b63f151a8a43bab7190336b02f76889d","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"0a01c88224f2d5dfd489f7478940032838e8246c9b00ff94fb6bcaf8d6bf84f5","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
{"contentHash":"647420ef973a58b7078e974eba6ab85b6d2df96f28e07c3bce108c0da3f52c68","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"54687a6ab40ae684e91ee657a2188685a46df434232982807f5ebcadd941d3b8","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
{"contentHash":"61e41f87d2a11f6c85ff712b500fc2cf50b974ee2f701e9c69c1693dc206e15d","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"d852e0d74a12f6eae8695aabc3a3659ec447a5289bf8e9e3a91fce7ad24c9f5a","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"}
|
||||
{"contentHash":"3d4bc42977575af9ec21f48099cb3e74a781839c08e6d3e30baec39f2009ce03","entrypoint":"reply-dispatch-runtime","importSpecifier":"openclaw/plugin-sdk/reply-dispatch-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"3517a6a2ecc546ffac96bec9cfdaab32a08848c150794149581ca4b3932545eb","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"}
|
||||
{"contentHash":"d07602091d9f19dc5a53f2df01557a1fb579ea4b298caf596ce9d4e6eb3bef8d","entrypoint":"reply-runtime","importSpecifier":"openclaw/plugin-sdk/reply-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"db430e3abd5c061bbb057dcbc5d542d22ce33ee62a07170f8bac5fcbd4c92e8d","entrypoint":"runtime-store","importSpecifier":"openclaw/plugin-sdk/runtime-store"}
|
||||
{"contentHash":"6acb8e3118fe9f7a8bf0c3fa16838b0e9c8b69bf440b84c0757d4789a66950bc","entrypoint":"runtime-store","importSpecifier":"openclaw/plugin-sdk/runtime-store"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"791e6d3eccf652c146c79a743edaf6bc7cb5b6fdd313dade78faaf329b5507a1","entrypoint":"session-catalog","importSpecifier":"openclaw/plugin-sdk/session-catalog"}
|
||||
{"contentHash":"3b52fbfd0855f234f80dce3c062c8d0b48f18d512aa7a26223c9c62b8056848e","entrypoint":"session-catalog","importSpecifier":"openclaw/plugin-sdk/session-catalog"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"552e06b12f79cbcd09e31fd40be2316546dfeb7a1ea6235b06de4f33db3b721b","entrypoint":"session-store-runtime","importSpecifier":"openclaw/plugin-sdk/session-store-runtime"}
|
||||
{"contentHash":"3da3531231ad1e533b81a215f53d3feb555a3c73420bc6d5e13d170edb8dedcc","entrypoint":"session-store-runtime","importSpecifier":"openclaw/plugin-sdk/session-store-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"92132ddbd26e3d81621f59004521f3875158b4704ee0b9e39f4798766f229702","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
{"contentHash":"d2d5d792a37398f060d0e069077d794d7e2d91a23fb0a4849ec66b8591f86daa","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"50ffb05c64c0f2898b3543900132261e152eda3999462154360efc3db7194359","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
{"contentHash":"ae862c22e6d6f96239ea1666faa208c5445aef89b6f3bc4425e3a259c3c6cb2b","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
|
||||
@@ -755,7 +755,8 @@ Use message hooks for channel-level routing and delivery policy:
|
||||
|
||||
- `message_received`: observe inbound content, sender, `threadId`,
|
||||
`messageId`, `senderId`, optional run/session correlation, ordered `media`,
|
||||
and metadata.
|
||||
normalized `location`, stable `providerUpdate` identity when supplied by the
|
||||
channel, and metadata.
|
||||
- `message_sending`: rewrite `content` or return `{ cancel: true }`.
|
||||
- `reply_payload_sending`: rewrite normalized `ReplyPayload` objects
|
||||
(including `presentation`, `delivery`, media refs, and text) or return
|
||||
|
||||
@@ -24,11 +24,12 @@ import {
|
||||
} from "./bot/helpers.js";
|
||||
import { TelegramPairingStoreReadError } from "./bot/helpers.js";
|
||||
import type { TelegramContext, TelegramGetChat } from "./bot/types.js";
|
||||
import { emitTelegramLiveLocationMessageHook } from "./location-message-hook.js";
|
||||
import type { TelegramMessageDispatchReplayClaim } from "./message-dispatch-dedupe.js";
|
||||
|
||||
type TelegramMessageHandlerParams = Pick<
|
||||
RegisterTelegramHandlerParams,
|
||||
"bot" | "shouldSkipUpdate"
|
||||
"accountId" | "bot" | "shouldSkipUpdate"
|
||||
> & {
|
||||
opts: Pick<RegisterTelegramHandlerParams["opts"], "botInfo">;
|
||||
runtime: Pick<RegisterTelegramHandlerParams["runtime"], "error">;
|
||||
@@ -57,7 +58,7 @@ interface TelegramInboundHandlers {
|
||||
}
|
||||
|
||||
function createTelegramInboundHandlers(
|
||||
{ bot, opts, runtime, shouldSkipUpdate }: TelegramMessageHandlerParams,
|
||||
{ accountId, bot, opts, runtime, shouldSkipUpdate }: TelegramMessageHandlerParams,
|
||||
messageRuntime: TelegramMessageHandlerRuntime,
|
||||
authorizationRuntime: Pick<TelegramHandlerAuthorization, "authorizeInboundMessage">,
|
||||
inboundRuntime: Pick<TelegramInboundProcessing, "processInboundMessage">,
|
||||
@@ -128,6 +129,7 @@ function createTelegramInboundHandlers(
|
||||
msg: Message;
|
||||
requireConfiguredGroup: boolean;
|
||||
botUserId: number;
|
||||
providerUpdate?: { id: number; kind: "edited_message" | "edited_channel_post" };
|
||||
}) => {
|
||||
if (shouldSkipUpdate(params.ctxForDedupe)) {
|
||||
return;
|
||||
@@ -157,6 +159,15 @@ function createTelegramInboundHandlers(
|
||||
return;
|
||||
}
|
||||
await recordMessageForReplyChain(normalizedMsg, gate.context.threadSpec, params.botUserId);
|
||||
if (params.providerUpdate) {
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId,
|
||||
msg: normalizedMsg,
|
||||
updateId: params.providerUpdate.id,
|
||||
updateKind: params.providerUpdate.kind,
|
||||
isForum,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleInboundMessageLike = async (
|
||||
@@ -319,6 +330,10 @@ function createTelegramInboundHandlers(
|
||||
msg,
|
||||
requireConfiguredGroup: false,
|
||||
botUserId: resolveBotUserId(ctx),
|
||||
providerUpdate:
|
||||
typeof ctx.update?.update_id === "number"
|
||||
? { id: ctx.update.update_id, kind: "edited_message" }
|
||||
: undefined,
|
||||
});
|
||||
return { kind: "recorded" };
|
||||
};
|
||||
@@ -364,6 +379,10 @@ function createTelegramInboundHandlers(
|
||||
msg: normalizeChannelPostMessage(post),
|
||||
requireConfiguredGroup: true,
|
||||
botUserId: resolveBotUserId(ctx),
|
||||
providerUpdate:
|
||||
typeof ctx.update?.update_id === "number"
|
||||
? { id: ctx.update.update_id, kind: "edited_channel_post" }
|
||||
: undefined,
|
||||
});
|
||||
return { kind: "recorded" };
|
||||
};
|
||||
|
||||
@@ -153,9 +153,14 @@ export function buildSyntheticTextMessage(params: {
|
||||
}
|
||||
|
||||
export const buildSyntheticContext = (
|
||||
ctx: Pick<TelegramContext, "me" | "getFile">,
|
||||
ctx: Pick<TelegramContext, "me" | "getFile" | "update">,
|
||||
message: Message,
|
||||
): TelegramContext => ({ message, me: ctx.me, getFile: ctx.getFile.bind(ctx) });
|
||||
): TelegramContext => ({
|
||||
message,
|
||||
update: ctx.update,
|
||||
me: ctx.me,
|
||||
getFile: ctx.getFile.bind(ctx),
|
||||
});
|
||||
|
||||
export function formatTelegramAmbientTranscriptBody(
|
||||
messages: readonly Message[],
|
||||
|
||||
@@ -585,6 +585,18 @@ export async function buildTelegramInboundContextPayload(params: {
|
||||
: `telegram:${chatId}`;
|
||||
const telegramTo = buildTelegramInboundOriginTarget(chatId, threadSpec);
|
||||
const locationContext = locationData ? toLocationContext(locationData) : undefined;
|
||||
const telegramUpdate = primaryCtx.update;
|
||||
const providerUpdateKind = telegramUpdate
|
||||
? "edited_message" in telegramUpdate
|
||||
? "edited_message"
|
||||
: "message" in telegramUpdate
|
||||
? "message"
|
||||
: "edited_channel_post" in telegramUpdate
|
||||
? "edited_channel_post"
|
||||
: "channel_post" in telegramUpdate
|
||||
? "channel_post"
|
||||
: undefined
|
||||
: undefined;
|
||||
const inboundHistory =
|
||||
hasGroupHistoryContext && historyKey && historyLimit > 0
|
||||
? groupHistoryPromptEntries.length > 0
|
||||
@@ -734,6 +746,18 @@ export async function buildTelegramInboundContextPayload(params: {
|
||||
StickerMediaIncluded: allMedia[0]?.stickerMetadata ? currentMediaFacts.length > 0 : undefined,
|
||||
SkipStickerMediaUnderstanding: stickerCacheHit ? true : undefined,
|
||||
...locationContext,
|
||||
ProviderUpdateId:
|
||||
typeof telegramUpdate?.update_id === "number"
|
||||
? String(telegramUpdate.update_id)
|
||||
: undefined,
|
||||
ProviderUpdateKind: providerUpdateKind,
|
||||
ProviderMessageTimestamp: primaryCtx.message?.date
|
||||
? primaryCtx.message.date * 1000
|
||||
: undefined,
|
||||
ProviderEditTimestamp: primaryCtx.message?.edit_date
|
||||
? primaryCtx.message.edit_date * 1000
|
||||
: undefined,
|
||||
LocationLivePeriodSeconds: primaryCtx.message?.location?.live_period,
|
||||
IsForum: isForum,
|
||||
TopicName: isForum && topicName ? topicName : undefined,
|
||||
},
|
||||
|
||||
+6
@@ -295,6 +295,7 @@ describe("telegram inbound media", () => {
|
||||
|
||||
const cases = [
|
||||
{
|
||||
updateId: 7005,
|
||||
message: {
|
||||
chat: { id: 42, type: "private" as const },
|
||||
message_id: 5,
|
||||
@@ -313,9 +314,13 @@ describe("telegram inbound media", () => {
|
||||
expect(payload.LocationLon).toBe(2.294351);
|
||||
expect(payload.LocationSource).toBe("pin");
|
||||
expect(payload.LocationIsLive).toBe(false);
|
||||
expect(payload.ProviderUpdateId).toBe("7005");
|
||||
expect(payload.ProviderUpdateKind).toBe("message");
|
||||
expect(payload.ProviderMessageTimestamp).toBe(1736380800000);
|
||||
},
|
||||
},
|
||||
{
|
||||
updateId: 7006,
|
||||
message: {
|
||||
chat: { id: 42, type: "private" as const },
|
||||
message_id: 6,
|
||||
@@ -338,6 +343,7 @@ describe("telegram inbound media", () => {
|
||||
for (const testCase of cases) {
|
||||
replySpy.mockClear();
|
||||
await handler({
|
||||
update: { update_id: testCase.updateId, message: testCase.message },
|
||||
message: testCase.message,
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ file_path: "unused" }),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Telegram type declarations define plugin contracts.
|
||||
import type { Context } from "grammy";
|
||||
import type { ChatFullInfo, Message, UserFromGetMe } from "grammy/types";
|
||||
import type { ChatFullInfo, Message, Update, UserFromGetMe } from "grammy/types";
|
||||
|
||||
/** App-specific stream mode for Telegram stream previews. */
|
||||
export type TelegramStreamMode = "off" | "partial" | "block" | "progress";
|
||||
@@ -20,7 +20,7 @@ export type TelegramGetChat = (chatId: number | string) => Promise<TelegramChatD
|
||||
*/
|
||||
export type TelegramContext = {
|
||||
message: Message;
|
||||
update?: unknown;
|
||||
update?: Update;
|
||||
me?: UserFromGetMe;
|
||||
getFile: TelegramGetFile;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import type { Message } from "grammy/types";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const hookRunner = vi.hoisted(() => ({
|
||||
hasHooks: vi.fn(() => true),
|
||||
runInboundClaim: vi.fn(async () => undefined),
|
||||
runMessageReceived: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/plugin-runtime")>();
|
||||
return { ...actual, getGlobalHookRunner: () => hookRunner };
|
||||
});
|
||||
|
||||
const { emitTelegramLiveLocationMessageHook } = await import("./location-message-hook.js");
|
||||
|
||||
describe("Telegram location message hooks", () => {
|
||||
beforeEach(() => {
|
||||
hookRunner.hasHooks.mockClear();
|
||||
hookRunner.runInboundClaim.mockClear();
|
||||
hookRunner.runMessageReceived.mockReset();
|
||||
hookRunner.runMessageReceived.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("ignores non-location edits", () => {
|
||||
const msg = {
|
||||
chat: { id: 1234, type: "private" },
|
||||
message_id: 456,
|
||||
date: 1_786_094_460,
|
||||
edit_date: 1_786_094_520,
|
||||
from: { id: 789, is_bot: false, first_name: "Mariano" },
|
||||
text: "edited text",
|
||||
} as Message;
|
||||
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId: "main",
|
||||
msg,
|
||||
updateId: 9002,
|
||||
updateKind: "edited_message",
|
||||
isForum: false,
|
||||
});
|
||||
|
||||
expect(hookRunner.runMessageReceived).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits edited live locations through the global message-received contract", () => {
|
||||
const msg = {
|
||||
chat: { id: 1234, type: "private" },
|
||||
message_id: 456,
|
||||
date: 1_786_094_460,
|
||||
edit_date: 1_786_094_520,
|
||||
from: { id: 789, is_bot: false, first_name: "Pat" },
|
||||
location: {
|
||||
latitude: 43.8376,
|
||||
longitude: 18.4534,
|
||||
horizontal_accuracy: 12,
|
||||
live_period: 900,
|
||||
},
|
||||
} as Message;
|
||||
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId: "main",
|
||||
msg,
|
||||
updateId: 9002,
|
||||
updateKind: "edited_message",
|
||||
isForum: false,
|
||||
});
|
||||
|
||||
expect(hookRunner.hasHooks).toHaveBeenCalledWith("message_received", expect.any(Object));
|
||||
expect(hookRunner.runMessageReceived).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messageId: "456",
|
||||
senderId: "789",
|
||||
timestamp: 1_786_094_520_000,
|
||||
location: expect.objectContaining({
|
||||
latitude: 43.8376,
|
||||
longitude: 18.4534,
|
||||
accuracy: 12,
|
||||
source: "live",
|
||||
isLive: true,
|
||||
livePeriodSeconds: 900,
|
||||
}),
|
||||
providerUpdate: {
|
||||
id: "9002",
|
||||
kind: "edited_message",
|
||||
messageId: "456",
|
||||
messageTimestamp: 1_786_094_460_000,
|
||||
editedTimestamp: 1_786_094_520_000,
|
||||
},
|
||||
}),
|
||||
expect.objectContaining({
|
||||
channelId: "telegram",
|
||||
accountId: "main",
|
||||
conversationId: "telegram:1234",
|
||||
messageId: "456",
|
||||
senderId: "789",
|
||||
}),
|
||||
);
|
||||
expect(hookRunner.runInboundClaim).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits the terminal edit when live-location sharing stops", () => {
|
||||
const msg = {
|
||||
chat: { id: 1234, type: "private" },
|
||||
message_id: 456,
|
||||
date: 1_786_094_460,
|
||||
edit_date: 1_786_094_580,
|
||||
from: { id: 789, is_bot: false, first_name: "Pat" },
|
||||
location: { latitude: 43.8376, longitude: 18.4534 },
|
||||
} as Message;
|
||||
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId: "main",
|
||||
msg,
|
||||
updateId: 9003,
|
||||
updateKind: "edited_message",
|
||||
isForum: false,
|
||||
});
|
||||
|
||||
expect(hookRunner.runMessageReceived).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
location: expect.objectContaining({ isLive: false }),
|
||||
providerUpdate: expect.objectContaining({ id: "9003", kind: "edited_message" }),
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(hookRunner.runInboundClaim).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits edited channel-post live locations with their provider update kind", () => {
|
||||
const msg = {
|
||||
chat: { id: -1001234, type: "supergroup", title: "Travel updates" },
|
||||
message_id: 456,
|
||||
date: 1_786_094_460,
|
||||
edit_date: 1_786_094_580,
|
||||
from: { id: 789, is_bot: true, first_name: "Travel updates" },
|
||||
location: { latitude: 43.8376, longitude: 18.4534, live_period: 900 },
|
||||
} as Message;
|
||||
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId: "main",
|
||||
msg,
|
||||
updateId: 9004,
|
||||
updateKind: "edited_channel_post",
|
||||
isForum: false,
|
||||
});
|
||||
|
||||
expect(hookRunner.runMessageReceived).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerUpdate: expect.objectContaining({
|
||||
id: "9004",
|
||||
kind: "edited_channel_post",
|
||||
}),
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not wait for plugin observers on the Telegram inbound path", () => {
|
||||
hookRunner.runMessageReceived.mockReturnValueOnce(new Promise(() => {}));
|
||||
const msg = {
|
||||
chat: { id: 1234, type: "private" },
|
||||
message_id: 456,
|
||||
date: 1_786_094_460,
|
||||
edit_date: 1_786_094_520,
|
||||
from: { id: 789, is_bot: false, first_name: "Pat" },
|
||||
location: { latitude: 43.8376, longitude: 18.4534, live_period: 900 },
|
||||
} as Message;
|
||||
|
||||
expect(
|
||||
emitTelegramLiveLocationMessageHook({
|
||||
accountId: "main",
|
||||
msg,
|
||||
updateId: 9005,
|
||||
updateKind: "edited_message",
|
||||
isForum: false,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(hookRunner.runMessageReceived).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
// Telegram live-location edits bypass agent dispatch but still need the normal observation hook.
|
||||
import type { Message } from "grammy/types";
|
||||
import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import {
|
||||
deriveInboundMessageHookContext,
|
||||
fireAndForgetHook,
|
||||
toPluginMessageContext,
|
||||
toPluginMessageReceivedEvent,
|
||||
} from "openclaw/plugin-sdk/hook-runtime";
|
||||
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { extractTelegramLocation } from "./bot/body-helpers.js";
|
||||
import {
|
||||
buildTelegramGroupFrom,
|
||||
buildTelegramInboundOriginTarget,
|
||||
resolveTelegramMessageThreadSpec,
|
||||
} from "./bot/helpers.js";
|
||||
|
||||
function buildTelegramLocationMessageHook(params: {
|
||||
accountId: string;
|
||||
msg: Message;
|
||||
updateId: number;
|
||||
updateKind: "message" | "edited_message" | "channel_post" | "edited_channel_post";
|
||||
isForum: boolean;
|
||||
}) {
|
||||
const location = extractTelegramLocation(params.msg);
|
||||
if (!location) {
|
||||
return null;
|
||||
}
|
||||
const msg = params.msg;
|
||||
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
||||
const threadSpec = resolveTelegramMessageThreadSpec(msg, params.isForum);
|
||||
const originatingTo = buildTelegramInboundOriginTarget(msg.chat.id, threadSpec);
|
||||
const from = isGroup
|
||||
? buildTelegramGroupFrom(msg.chat.id, threadSpec.id)
|
||||
: `telegram:${msg.chat.id}`;
|
||||
const canonical = deriveInboundMessageHookContext({
|
||||
From: from,
|
||||
To: originatingTo,
|
||||
OriginatingChannel: "telegram",
|
||||
OriginatingTo: originatingTo,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
AccountId: params.accountId,
|
||||
MessageSid: String(msg.message_id),
|
||||
MessageSidFull: String(msg.message_id),
|
||||
SenderId: msg.from?.id != null ? String(msg.from.id) : undefined,
|
||||
SenderName: [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ") || undefined,
|
||||
SenderUsername: msg.from?.username,
|
||||
Timestamp:
|
||||
params.updateKind.startsWith("edited_") && msg.edit_date
|
||||
? msg.edit_date * 1000
|
||||
: msg.date
|
||||
? msg.date * 1000
|
||||
: undefined,
|
||||
Body: formatLocationText(location),
|
||||
RawBody: formatLocationText(location),
|
||||
BodyForAgent: formatLocationText(location),
|
||||
MessageThreadId: threadSpec.id,
|
||||
GroupSubject: isGroup ? msg.chat.title : undefined,
|
||||
LocationLat: location.latitude,
|
||||
LocationLon: location.longitude,
|
||||
LocationAccuracy: location.accuracy,
|
||||
LocationName: location.name,
|
||||
LocationAddress: location.address,
|
||||
LocationSource: location.source,
|
||||
LocationIsLive: location.isLive,
|
||||
LocationLivePeriodSeconds: msg.location?.live_period,
|
||||
LocationCaption: location.caption,
|
||||
ProviderUpdateId: String(params.updateId),
|
||||
ProviderUpdateKind: params.updateKind,
|
||||
ProviderMessageTimestamp: msg.date ? msg.date * 1000 : undefined,
|
||||
ProviderEditTimestamp: msg.edit_date ? msg.edit_date * 1000 : undefined,
|
||||
CommandAuthorized: false,
|
||||
});
|
||||
return {
|
||||
event: toPluginMessageReceivedEvent(canonical),
|
||||
context: toPluginMessageContext(canonical),
|
||||
};
|
||||
}
|
||||
|
||||
export function emitTelegramLiveLocationMessageHook(
|
||||
params: Parameters<typeof buildTelegramLocationMessageHook>[0],
|
||||
): void {
|
||||
const pair = buildTelegramLocationMessageHook(params);
|
||||
const runner = getGlobalHookRunner();
|
||||
if (!pair || !runner?.hasHooks("message_received", pair.context)) {
|
||||
return;
|
||||
}
|
||||
fireAndForgetHook(
|
||||
runner.runMessageReceived(pair.event, pair.context),
|
||||
"message_received plugin hook failed",
|
||||
);
|
||||
}
|
||||
@@ -270,7 +270,8 @@ export function readPluginSdkSurfaceBudgets(env: NodeJS.ProcessEnv = process.env
|
||||
// +1: add the account-aware native approval request selector.
|
||||
// +3: add canonical coercion exports while retaining the shipped asString compatibility name.
|
||||
// +2: add high-use coercion primitives while retaining shipped object-record exports.
|
||||
4868,
|
||||
// +2: channel-neutral location and provider-update hook contracts.
|
||||
4870,
|
||||
env,
|
||||
),
|
||||
publicFunctionExports: readPluginSdkSurfaceBudgetEnv(
|
||||
|
||||
@@ -325,7 +325,16 @@ export type MsgContext = Partial<CanonicalInboundText> & {
|
||||
LocationAddress?: string;
|
||||
LocationSource?: string;
|
||||
LocationIsLive?: boolean;
|
||||
LocationLivePeriodSeconds?: number;
|
||||
LocationCaption?: string;
|
||||
/** Stable identity of the provider update that carried this message. */
|
||||
ProviderUpdateId?: string;
|
||||
/** Provider update kind, for example `message` or `edited_message`. */
|
||||
ProviderUpdateKind?: string;
|
||||
/** Provider-native timestamp for the original message. */
|
||||
ProviderMessageTimestamp?: number;
|
||||
/** Provider-native timestamp for an edited message update. */
|
||||
ProviderEditTimestamp?: number;
|
||||
/** Provider label. */
|
||||
Provider?: string;
|
||||
/** Provider surface label. Prefer this over `Provider` when available. */
|
||||
|
||||
@@ -820,4 +820,42 @@ describe("message hook mappers", () => {
|
||||
groupId: "demo-chat:chat:456",
|
||||
});
|
||||
});
|
||||
|
||||
it("projects normalized location and stable provider update identity", () => {
|
||||
const canonical = deriveInboundMessageHookContext(
|
||||
makeInboundCtx({
|
||||
LocationLat: 43.8376,
|
||||
LocationLon: 18.4534,
|
||||
LocationAccuracy: 12,
|
||||
LocationSource: "live",
|
||||
LocationIsLive: true,
|
||||
LocationLivePeriodSeconds: 900,
|
||||
ProviderUpdateId: "9002",
|
||||
ProviderUpdateKind: "edited_message",
|
||||
ProviderMessageTimestamp: 1_786_094_460_000,
|
||||
ProviderEditTimestamp: 1_786_094_520_000,
|
||||
}),
|
||||
);
|
||||
|
||||
const { event } = toPluginInboundClaimPair(canonical);
|
||||
expect(event.location).toEqual({
|
||||
latitude: 43.8376,
|
||||
longitude: 18.4534,
|
||||
accuracy: 12,
|
||||
source: "live",
|
||||
isLive: true,
|
||||
livePeriodSeconds: 900,
|
||||
});
|
||||
expect(event.providerUpdate).toEqual({
|
||||
id: "9002",
|
||||
kind: "edited_message",
|
||||
messageId: "msg-1",
|
||||
messageTimestamp: 1_786_094_460_000,
|
||||
editedTimestamp: 1_786_094_520_000,
|
||||
});
|
||||
expect(toPluginMessageReceivedEvent(canonical)).toMatchObject({
|
||||
location: event.location,
|
||||
providerUpdate: event.providerUpdate,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,6 +81,8 @@ type CanonicalInboundMessageHookContext = {
|
||||
isGroup: boolean;
|
||||
groupId?: string;
|
||||
topicName?: string;
|
||||
location?: PluginHookInboundClaimEvent["location"];
|
||||
providerUpdate?: PluginHookInboundClaimEvent["providerUpdate"];
|
||||
trace?: DiagnosticTraceContext;
|
||||
callDepth?: number;
|
||||
};
|
||||
@@ -168,6 +170,17 @@ export function deriveInboundMessageHookContext(
|
||||
const mediaUrls = compact(media.map((fact) => fact.url ?? fact.path));
|
||||
const mediaTypes = compact(media.map((fact) => fact.contentType ?? fact.kind));
|
||||
const firstMedia = media[0];
|
||||
const hasLocation =
|
||||
typeof ctx.LocationLat === "number" &&
|
||||
Number.isFinite(ctx.LocationLat) &&
|
||||
typeof ctx.LocationLon === "number" &&
|
||||
Number.isFinite(ctx.LocationLon);
|
||||
const locationSource =
|
||||
ctx.LocationSource === "pin" || ctx.LocationSource === "place" || ctx.LocationSource === "live"
|
||||
? ctx.LocationSource
|
||||
: undefined;
|
||||
const providerUpdateId = normalizeOptionalString(ctx.ProviderUpdateId);
|
||||
const providerUpdateKind = normalizeOptionalString(ctx.ProviderUpdateKind);
|
||||
return {
|
||||
from: ctx.From ?? "",
|
||||
to: ctx.To,
|
||||
@@ -217,6 +230,43 @@ export function deriveInboundMessageHookContext(
|
||||
isGroup,
|
||||
groupId: isGroup ? conversationId : undefined,
|
||||
topicName: ctx.TopicName,
|
||||
...(hasLocation
|
||||
? {
|
||||
location: {
|
||||
latitude: ctx.LocationLat as number,
|
||||
longitude: ctx.LocationLon as number,
|
||||
...(typeof ctx.LocationAccuracy === "number" ? { accuracy: ctx.LocationAccuracy } : {}),
|
||||
...(ctx.LocationName ? { name: ctx.LocationName } : {}),
|
||||
...(ctx.LocationAddress ? { address: ctx.LocationAddress } : {}),
|
||||
...(locationSource ? { source: locationSource } : {}),
|
||||
...(typeof ctx.LocationIsLive === "boolean" ? { isLive: ctx.LocationIsLive } : {}),
|
||||
...(typeof ctx.LocationLivePeriodSeconds === "number" &&
|
||||
Number.isFinite(ctx.LocationLivePeriodSeconds)
|
||||
? { livePeriodSeconds: ctx.LocationLivePeriodSeconds }
|
||||
: {}),
|
||||
...(ctx.LocationCaption ? { caption: ctx.LocationCaption } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(providerUpdateId && providerUpdateKind
|
||||
? {
|
||||
providerUpdate: {
|
||||
id: providerUpdateId,
|
||||
kind: providerUpdateKind,
|
||||
...(normalizeOptionalString(ctx.MessageSidFull ?? ctx.MessageSid)
|
||||
? { messageId: normalizeOptionalString(ctx.MessageSidFull ?? ctx.MessageSid) }
|
||||
: {}),
|
||||
...(typeof ctx.ProviderMessageTimestamp === "number" &&
|
||||
Number.isFinite(ctx.ProviderMessageTimestamp)
|
||||
? { messageTimestamp: ctx.ProviderMessageTimestamp }
|
||||
: {}),
|
||||
...(typeof ctx.ProviderEditTimestamp === "number" &&
|
||||
Number.isFinite(ctx.ProviderEditTimestamp)
|
||||
? { editedTimestamp: ctx.ProviderEditTimestamp }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -436,6 +486,8 @@ function buildPluginInboundClaimEvent(
|
||||
isGroup: canonical.isGroup,
|
||||
commandAuthorized: extras?.commandAuthorized,
|
||||
wasMentioned: extras?.wasMentioned,
|
||||
...(canonical.location ? { location: { ...canonical.location } } : {}),
|
||||
...(canonical.providerUpdate ? { providerUpdate: { ...canonical.providerUpdate } } : {}),
|
||||
...projectHookMediaState(canonical),
|
||||
metadata: {
|
||||
from: canonical.from,
|
||||
@@ -504,6 +556,8 @@ export function toPluginMessageReceivedEvent(
|
||||
...(canonical.replyToIsQuote !== undefined ? { replyToIsQuote: canonical.replyToIsQuote } : {}),
|
||||
sessionKey: canonical.sessionKey,
|
||||
runId: canonical.runId,
|
||||
...(canonical.location ? { location: { ...canonical.location } } : {}),
|
||||
...(canonical.providerUpdate ? { providerUpdate: { ...canonical.providerUpdate } } : {}),
|
||||
...projectHookMediaState(canonical),
|
||||
metadata: {
|
||||
to: canonical.to,
|
||||
|
||||
@@ -163,8 +163,10 @@ export type {
|
||||
PluginHookInboundClaimEvent,
|
||||
PluginHookInboundClaimResult,
|
||||
PluginHookInboundMessageMetadata,
|
||||
PluginHookLocation,
|
||||
PluginHookMediaFact,
|
||||
PluginHookMessageReceivedEvent,
|
||||
PluginHookProviderUpdate,
|
||||
PluginHookSkillArtifact,
|
||||
PluginHookSkillBundleFile,
|
||||
PluginHookSkillBundleSnapshot,
|
||||
|
||||
@@ -5,6 +5,28 @@ import type { PluginConversationBinding } from "./conversation-binding.types.js"
|
||||
/** Ordered media fact exposed by inbound message hooks. */
|
||||
export type PluginHookMediaFact = MessageHookMediaFact;
|
||||
|
||||
/** Channel-neutral geographic fix carried by an inbound provider update. */
|
||||
export type PluginHookLocation = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
accuracy?: number;
|
||||
name?: string;
|
||||
address?: string;
|
||||
source?: "pin" | "place" | "live";
|
||||
isLive?: boolean;
|
||||
livePeriodSeconds?: number;
|
||||
caption?: string;
|
||||
};
|
||||
|
||||
/** Stable provider update identity for transport-level correlation and deduplication. */
|
||||
export type PluginHookProviderUpdate = {
|
||||
id: string;
|
||||
kind: string;
|
||||
messageId?: string;
|
||||
messageTimestamp?: number;
|
||||
editedTimestamp?: number;
|
||||
};
|
||||
|
||||
/** Provider metadata plus deprecated media aliases retained during the SDK migration window. */
|
||||
export type PluginHookInboundMessageMetadata = Record<string, unknown> & {
|
||||
/** @deprecated Use the first `event.media` fact with a defined `path`. */
|
||||
@@ -129,6 +151,8 @@ export type PluginHookInboundClaimEvent = {
|
||||
commandAuthorized?: boolean;
|
||||
senderIsOwner?: boolean;
|
||||
wasMentioned?: boolean;
|
||||
location?: PluginHookLocation;
|
||||
providerUpdate?: PluginHookProviderUpdate;
|
||||
/** Staged, locally usable attachments in stable source order. */
|
||||
media?: PluginHookMediaFact[];
|
||||
/** Original attachment facts when local staging has not completed yet. */
|
||||
@@ -156,6 +180,8 @@ export type PluginHookMessageReceivedEvent = {
|
||||
traceId?: string;
|
||||
spanId?: string;
|
||||
parentSpanId?: string;
|
||||
location?: PluginHookLocation;
|
||||
providerUpdate?: PluginHookProviderUpdate;
|
||||
/** Staged, locally usable attachments in stable source order. */
|
||||
media?: PluginHookMediaFact[];
|
||||
/** Original attachment facts when local staging has not completed yet. */
|
||||
|
||||
@@ -65,12 +65,14 @@ export type {
|
||||
PluginHookInboundClaimContext,
|
||||
PluginHookInboundClaimEvent,
|
||||
PluginHookInboundMessageMetadata,
|
||||
PluginHookLocation,
|
||||
PluginHookMediaFact,
|
||||
PluginHookMessageContext,
|
||||
PluginHookMessageReceivedEvent,
|
||||
PluginHookMessageSendingEvent,
|
||||
PluginHookMessageSendingResult,
|
||||
PluginHookMessageSentEvent,
|
||||
PluginHookProviderUpdate,
|
||||
} from "./hook-message.types.js";
|
||||
export {
|
||||
PluginApprovalResolutions,
|
||||
|
||||
Reference in New Issue
Block a user