@diegoaltoworks/chatter 0.59.0 → 0.61.0
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 +2 -1
- package/dist/channels/index.js +81 -0
- package/dist/channels/index.mjs +81 -0
- package/dist/channels/matrix/api.d.ts +133 -0
- package/dist/channels/matrix/api.d.ts.map +1 -0
- package/dist/channels/matrix/channel.d.ts +98 -0
- package/dist/channels/matrix/channel.d.ts.map +1 -0
- package/dist/channels/matrix/handler.d.ts +51 -0
- package/dist/channels/matrix/handler.d.ts.map +1 -0
- package/dist/channels/matrix/index.d.ts +37 -0
- package/dist/channels/matrix/index.d.ts.map +1 -0
- package/dist/channels/matrix/index.js +972 -0
- package/dist/channels/matrix/index.mjs +929 -0
- package/dist/channels/matrix/sync.d.ts +50 -0
- package/dist/channels/matrix/sync.d.ts.map +1 -0
- package/dist/channels/matrix/updates.d.ts +75 -0
- package/dist/channels/matrix/updates.d.ts.map +1 -0
- package/dist/channels/pipeline.d.ts +8 -0
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/telegram/channel.d.ts +7 -0
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +81 -0
- package/dist/channels/telegram/index.mjs +81 -0
- package/dist/channels/telegram/webhook.d.ts +7 -0
- package/dist/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +7 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +81 -0
- package/dist/channels/whatsapp/index.mjs +81 -0
- package/dist/history/compaction.d.ts +61 -0
- package/dist/history/compaction.d.ts.map +1 -0
- package/dist/history/index.d.ts +7 -0
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +143 -0
- package/dist/history/index.mjs +141 -0
- package/package.json +8 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/sync` long-poll loop, isolated from both the HTTP client and the
|
|
3
|
+
* channel so its failure behaviour is testable without either — the same
|
|
4
|
+
* shape as `../telegram/poll.ts`'s `runLongPoll`, adapted to Matrix's
|
|
5
|
+
* `since`-token pagination instead of an acknowledged offset.
|
|
6
|
+
*
|
|
7
|
+
* The property that matters: a homeserver that is down, rate-limiting, or
|
|
8
|
+
* rejecting the token must never turn into a tight retry loop. Every
|
|
9
|
+
* failure backs off exponentially, and `M_LIMIT_EXCEEDED`'s own
|
|
10
|
+
* `retry_after_ms` wins over that backoff when the homeserver sends one.
|
|
11
|
+
*/
|
|
12
|
+
import { type Logger } from "../../core/logger";
|
|
13
|
+
import { type MatrixSyncResponse } from "./api";
|
|
14
|
+
/** Exponential backoff: 2s, 4s, 8s ... capped at a minute. `failures` is the count including the one just seen. */
|
|
15
|
+
export declare function syncBackoffMs(failures: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* How long to wait after a failed sync. `M_LIMIT_EXCEEDED`'s
|
|
18
|
+
* `retry_after_ms` is an instruction, not a hint — honour it verbatim even
|
|
19
|
+
* when it exceeds the backoff cap, because syncing again sooner just
|
|
20
|
+
* extends the rate-limit window.
|
|
21
|
+
*/
|
|
22
|
+
export declare function retryDelayMs(error: unknown, failures: number): number;
|
|
23
|
+
export interface MatrixSyncLoopDeps {
|
|
24
|
+
/** One `/sync` call. Rejections are backed off; they never end the loop. */
|
|
25
|
+
sync(since: string | undefined): Promise<MatrixSyncResponse>;
|
|
26
|
+
/** Per-batch work. A throw is logged and skipped — one bad batch never stops the loop. */
|
|
27
|
+
handleSync(response: MatrixSyncResponse): Promise<void>;
|
|
28
|
+
/** Checked before every sync, and again after a failure, so `stop()` cuts the loop instead of sleeping out a backoff first. */
|
|
29
|
+
isStopped(): boolean;
|
|
30
|
+
/** Overridable for tests; a real channel passes a `setTimeout`-based sleep. */
|
|
31
|
+
sleep(ms: number): Promise<void>;
|
|
32
|
+
/** Where this loop's `since` token resumes from. Omitted = an initial full sync. */
|
|
33
|
+
initialSince?: string;
|
|
34
|
+
/** Called with each newly acknowledged `since` token, for a host that persists it. */
|
|
35
|
+
onSince?(since: string): void;
|
|
36
|
+
logger?: Logger;
|
|
37
|
+
/** Prefix for log lines, e.g. `Matrix[@bot:example.org]`. */
|
|
38
|
+
label?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Syncs until `isStopped()`, resolving with the last `since` token reached
|
|
42
|
+
* (what a restart would resume from).
|
|
43
|
+
*
|
|
44
|
+
* The token advances BEFORE the batch is handled, matching the Telegram
|
|
45
|
+
* poll loop's offset-first ordering: a handler that throws on one bad
|
|
46
|
+
* batch must not make the homeserver redeliver it forever, which would
|
|
47
|
+
* wedge the loop on a single poison event and starve every later one.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runMatrixSyncLoop(deps: MatrixSyncLoopDeps): Promise<string | undefined>;
|
|
50
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAKhE,mHAAmH;AACnH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC7D,0FAA0F;IAC1F,UAAU,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,+HAA+H;IAC/H,SAAS,IAAI,OAAO,CAAC;IACrB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,OAAO,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiC7F"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matrix room event -> {@link ChannelMessage}: the transport's own half of
|
|
3
|
+
* the channel contract (see docs/build-a-channel.md), kept pure so mention,
|
|
4
|
+
* reply-to and DM detection are testable without a network or a running
|
|
5
|
+
* channel.
|
|
6
|
+
*
|
|
7
|
+
* Addressing parity with the other built-in channels is the point of this
|
|
8
|
+
* module. WhatsApp and Telegram both resolve "was the bot addressed?" from
|
|
9
|
+
* transport-native structures; Matrix resolves it from the stable
|
|
10
|
+
* `m.mentions` field (MSC3952, in the spec since v1.7) with a pill-in-HTML
|
|
11
|
+
* fallback for clients that only set that. The gate policy that consumes
|
|
12
|
+
* those booleans (`decideChannelAction`) is identical for every channel.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChannelMessage } from "../gates";
|
|
15
|
+
import type { MatrixAccountDataEvent, MatrixEvent } from "./api";
|
|
16
|
+
/** The bot's own identity, as `whoami` reports it. */
|
|
17
|
+
export interface MatrixIdentity {
|
|
18
|
+
userId: string;
|
|
19
|
+
}
|
|
20
|
+
interface MatrixMentions {
|
|
21
|
+
user_ids?: string[];
|
|
22
|
+
room?: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface MatrixInReplyTo {
|
|
25
|
+
event_id?: string;
|
|
26
|
+
}
|
|
27
|
+
interface MatrixMessageContent {
|
|
28
|
+
msgtype?: string;
|
|
29
|
+
body?: string;
|
|
30
|
+
format?: string;
|
|
31
|
+
formatted_body?: string;
|
|
32
|
+
"m.mentions"?: MatrixMentions;
|
|
33
|
+
"m.relates_to"?: {
|
|
34
|
+
"m.in_reply_to"?: MatrixInReplyTo;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The message text, or "" for an event this channel doesn't render as text (a redaction, a non-text msgtype with no body). */
|
|
38
|
+
export declare function messageText(content: MatrixMessageContent): string;
|
|
39
|
+
export declare function mentionsBot(content: MatrixMessageContent, me: MatrixIdentity): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Was this message a reply to one the bot sent? `sentEventIds` is the
|
|
42
|
+
* session's own record of event ids it has sent (see `./channel`) — Matrix
|
|
43
|
+
* has no cheap "who sent event X" lookup without fetching that event, and a
|
|
44
|
+
* bot only ever needs to recognise its OWN messages, which it already knows
|
|
45
|
+
* without asking the server.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isReplyToBot(content: MatrixMessageContent, sentEventIds: ReadonlySet<string>): boolean;
|
|
48
|
+
/** How many of the session's own sent event ids {@link isReplyToBot} remembers — bounded so a long-running bot's memory doesn't grow with every message it ever sent. */
|
|
49
|
+
export declare const MAX_TRACKED_SENT_EVENTS = 500;
|
|
50
|
+
/** Records `eventId` as one of ours, evicting the oldest entry first once the set is at capacity (`Set` iterates in insertion order, so the first value is the oldest). */
|
|
51
|
+
export declare function recordSentEventId(sentEventIds: Set<string>, eventId: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Room ids the homeserver's `m.direct` account data lists as direct
|
|
54
|
+
* messages, across every peer — the canonical way a Matrix client (and so a
|
|
55
|
+
* bot) tells a DM from a group: there is no per-room "this is a DM" flag.
|
|
56
|
+
* A room absent from this set is treated as a group and needs addressing,
|
|
57
|
+
* the safer default when a client never populated `m.direct` for it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function directRoomIds(accountDataEvents: MatrixAccountDataEvent[]): Set<string>;
|
|
60
|
+
/**
|
|
61
|
+
* The {@link ChannelMessage} for a timeline event, or `undefined` when
|
|
62
|
+
* there is nothing for a chat pipeline to look at: not an `m.room.message`
|
|
63
|
+
* (a reaction, a membership change, an encrypted event this channel can't
|
|
64
|
+
* decrypt), or one with no `msgtype` at all (a redacted message).
|
|
65
|
+
*/
|
|
66
|
+
export declare function toChannelMessage(roomId: string, event: MatrixEvent, me: MatrixIdentity, directRooms: ReadonlySet<string>, sentEventIds: ReadonlySet<string>): ChannelMessage | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* A stable identity key for `bucketsFor`/`personaResolver`/`answerFn`.
|
|
69
|
+
* Namespaced so a bare Matrix user id (which is itself already `@user:
|
|
70
|
+
* server.tld`, unlike Telegram's bare numeral) never collides with another
|
|
71
|
+
* channel's key when a host shares persona/spend state across channels.
|
|
72
|
+
*/
|
|
73
|
+
export declare function matrixSenderKey(userId: string): string;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=updates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updates.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/updates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEjE,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,cAAc;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,cAAc,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,eAAe,CAAA;KAAE,CAAC;CACxD;AAED,+HAA+H;AAC/H,wBAAgB,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAEjE;AAiCD,wBAAgB,WAAW,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,EAAE,cAAc,GAAG,OAAO,CAQtF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,oBAAoB,EAC7B,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,OAAO,CAGT;AAED,yKAAyK;AACzK,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,2KAA2K;AAC3K,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAMlF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,iBAAiB,EAAE,sBAAsB,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAYtF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,cAAc,EAClB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,cAAc,GAAG,SAAS,CAe5B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
|
@@ -20,6 +20,7 @@ import type { Logger } from "../core/logger";
|
|
|
20
20
|
import { type RerankContext, type RewriteQuery } from "../core/pipeline";
|
|
21
21
|
import type { PromptLoader } from "../core/prompts";
|
|
22
22
|
import type { VectorStore } from "../core/retrieval";
|
|
23
|
+
import type { HistoryCompactionOptions } from "../history/compaction";
|
|
23
24
|
import type { HistoryStore } from "../history/types";
|
|
24
25
|
import { type ChannelMessage } from "./gates";
|
|
25
26
|
/** Where a pipeline turn's output goes. Distinct from `ChannelSender` (`./senders`) — a transport implementing this may thread/quote the chat answer without doing the same for a plain gate acknowledgement. */
|
|
@@ -69,6 +70,13 @@ export interface InboundPipelineConfig {
|
|
|
69
70
|
* opted out. @default every sender is enabled
|
|
70
71
|
*/
|
|
71
72
|
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Summarize-then-truncate compaction, checked right after each reply is
|
|
75
|
+
* appended. Off by default — a conversation's window grows unbounded
|
|
76
|
+
* (aside from the store's own `maxPerConversation`) until this is set.
|
|
77
|
+
* See `../history/compaction`.
|
|
78
|
+
*/
|
|
79
|
+
compaction?: HistoryCompactionOptions;
|
|
72
80
|
};
|
|
73
81
|
/** Group chats eligible for a reply. Empty (default) = every group. Has no effect on DMs, which always reply. */
|
|
74
82
|
allowedChats?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,SAAS,CAAC;AAEjB,iNAAiN;AACjN,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,kFAAkF;IAClF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iHAAiH;IACjH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kGAAkG;IAClG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sGAAsG;IACtG,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,gFAAgF;IAChF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;;;;WAMG;QACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/channels/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,7 @@ import type { AnswerFn, TransformReply } from "../../core/answer";
|
|
|
24
24
|
import type { BucketsFor } from "../../core/buckets";
|
|
25
25
|
import { type Logger } from "../../core/logger";
|
|
26
26
|
import type { RerankContext, RewriteQuery } from "../../core/pipeline";
|
|
27
|
+
import type { HistoryCompactionOptions } from "../../history/compaction";
|
|
27
28
|
import type { HistoryStore } from "../../history/types";
|
|
28
29
|
import type { Channel } from "../index";
|
|
29
30
|
import { type TelegramApi } from "./api";
|
|
@@ -61,6 +62,12 @@ export interface TelegramChannelConfig {
|
|
|
61
62
|
* @default every sender is enabled
|
|
62
63
|
*/
|
|
63
64
|
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Summarize-then-truncate compaction — see
|
|
67
|
+
* `InboundPipelineConfig.history.compaction` in `./channels`.
|
|
68
|
+
* @default off
|
|
69
|
+
*/
|
|
70
|
+
compaction?: HistoryCompactionOptions;
|
|
64
71
|
};
|
|
65
72
|
muteRegex?: RegExp;
|
|
66
73
|
unmuteRegex?: RegExp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAQ5D,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,0IAA0I;IAC1I,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iJAAiJ;IACjJ,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kIAAkI;IAClI,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;;WAIG;QACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -393,6 +393,75 @@ ${ctx.join("\n\n")}`
|
|
|
393
393
|
return { system, messages, context: ctx };
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
// src/history/compaction.ts
|
|
397
|
+
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
398
|
+
var LOAD_ALL = Number.MAX_SAFE_INTEGER;
|
|
399
|
+
var SUMMARY_PREFIX = "Summary of earlier conversation: ";
|
|
400
|
+
function defaultSummarize(client, model) {
|
|
401
|
+
return async ({ turns }) => {
|
|
402
|
+
const transcript = turns.map((turn) => `${turn.role}: ${turn.content}`).join("\n");
|
|
403
|
+
const { content } = await answerOnce({
|
|
404
|
+
client,
|
|
405
|
+
system: "Summarize the conversation turns below into a short paragraph a reader would need to continue the conversation. Preserve names, facts, and decisions; drop pleasantries. Reply with the summary only, no preamble.",
|
|
406
|
+
messages: [{ role: "user", content: transcript }],
|
|
407
|
+
// No `answerFn`: compaction is an internal chatter operation, not a
|
|
408
|
+
// chat surface, so it must not conscript a caller's brain hook, which
|
|
409
|
+
// is shaped to answer the user, not to summarize on the engine's behalf.
|
|
410
|
+
mode: "public",
|
|
411
|
+
model
|
|
412
|
+
});
|
|
413
|
+
return content;
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function createHistoryCompactor(deps, options) {
|
|
417
|
+
const { threshold, keep, logger } = options;
|
|
418
|
+
if (!Number.isInteger(threshold) || threshold < 1) {
|
|
419
|
+
throw new Error(`Invalid history compaction threshold: ${threshold}`);
|
|
420
|
+
}
|
|
421
|
+
if (!Number.isInteger(keep) || keep < 0 || keep >= threshold) {
|
|
422
|
+
throw new Error(`Invalid history compaction keep: ${keep}`);
|
|
423
|
+
}
|
|
424
|
+
const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
|
|
425
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
426
|
+
return {
|
|
427
|
+
async maybeCompact(store, conversationId) {
|
|
428
|
+
const turns = await store.load(conversationId, LOAD_ALL);
|
|
429
|
+
if (turns.length < threshold) return;
|
|
430
|
+
const older = turns.slice(0, turns.length - keep);
|
|
431
|
+
let summary;
|
|
432
|
+
let timer;
|
|
433
|
+
try {
|
|
434
|
+
summary = await Promise.race([
|
|
435
|
+
summarize({ conversationId, turns: older }),
|
|
436
|
+
new Promise((_, reject) => {
|
|
437
|
+
timer = setTimeout(() => reject(new Error("history compaction timed out")), timeoutMs);
|
|
438
|
+
})
|
|
439
|
+
]);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
logger?.error(
|
|
442
|
+
`history compaction summarize failed for "${conversationId}"; history left untouched`,
|
|
443
|
+
error
|
|
444
|
+
);
|
|
445
|
+
return;
|
|
446
|
+
} finally {
|
|
447
|
+
clearTimeout(timer);
|
|
448
|
+
}
|
|
449
|
+
const trimmed = summary.trim();
|
|
450
|
+
if (!trimmed) return;
|
|
451
|
+
const fresh = await store.load(conversationId, LOAD_ALL);
|
|
452
|
+
const recent = fresh.slice(older.length);
|
|
453
|
+
await store.clear(conversationId);
|
|
454
|
+
await store.append(conversationId, {
|
|
455
|
+
role: "assistant",
|
|
456
|
+
content: `${SUMMARY_PREFIX}${trimmed}`
|
|
457
|
+
});
|
|
458
|
+
for (const turn of recent) {
|
|
459
|
+
await store.append(conversationId, turn);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
396
465
|
// src/channels/gates.ts
|
|
397
466
|
function matches(re, text) {
|
|
398
467
|
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
@@ -473,6 +542,10 @@ function createInboundPipeline(deps, config) {
|
|
|
473
542
|
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
474
543
|
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
475
544
|
const mode = "public";
|
|
545
|
+
const compactor = config.history?.compaction ? createHistoryCompactor(
|
|
546
|
+
{ client: deps.client },
|
|
547
|
+
{ model: config.model, ...config.history.compaction }
|
|
548
|
+
) : void 0;
|
|
476
549
|
return async function handleInboundMessage(msg, turn) {
|
|
477
550
|
const allowedChats = config.allowedChats ?? [];
|
|
478
551
|
const action = decideChannelAction(msg, {
|
|
@@ -548,6 +621,14 @@ function createInboundPipeline(deps, config) {
|
|
|
548
621
|
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
549
622
|
}
|
|
550
623
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
624
|
+
const history = config.history;
|
|
625
|
+
if (compactor && historyEnabled && history) {
|
|
626
|
+
try {
|
|
627
|
+
await compactor.maybeCompact(history.store, conversationId);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
config.logger?.error(`history compaction failed for "${conversationId}"`, error);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
551
632
|
return { action: "reply", content };
|
|
552
633
|
};
|
|
553
634
|
}
|
|
@@ -348,6 +348,75 @@ ${ctx.join("\n\n")}`
|
|
|
348
348
|
return { system, messages, context: ctx };
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
// src/history/compaction.ts
|
|
352
|
+
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
353
|
+
var LOAD_ALL = Number.MAX_SAFE_INTEGER;
|
|
354
|
+
var SUMMARY_PREFIX = "Summary of earlier conversation: ";
|
|
355
|
+
function defaultSummarize(client, model) {
|
|
356
|
+
return async ({ turns }) => {
|
|
357
|
+
const transcript = turns.map((turn) => `${turn.role}: ${turn.content}`).join("\n");
|
|
358
|
+
const { content } = await answerOnce({
|
|
359
|
+
client,
|
|
360
|
+
system: "Summarize the conversation turns below into a short paragraph a reader would need to continue the conversation. Preserve names, facts, and decisions; drop pleasantries. Reply with the summary only, no preamble.",
|
|
361
|
+
messages: [{ role: "user", content: transcript }],
|
|
362
|
+
// No `answerFn`: compaction is an internal chatter operation, not a
|
|
363
|
+
// chat surface, so it must not conscript a caller's brain hook, which
|
|
364
|
+
// is shaped to answer the user, not to summarize on the engine's behalf.
|
|
365
|
+
mode: "public",
|
|
366
|
+
model
|
|
367
|
+
});
|
|
368
|
+
return content;
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function createHistoryCompactor(deps, options) {
|
|
372
|
+
const { threshold, keep, logger } = options;
|
|
373
|
+
if (!Number.isInteger(threshold) || threshold < 1) {
|
|
374
|
+
throw new Error(`Invalid history compaction threshold: ${threshold}`);
|
|
375
|
+
}
|
|
376
|
+
if (!Number.isInteger(keep) || keep < 0 || keep >= threshold) {
|
|
377
|
+
throw new Error(`Invalid history compaction keep: ${keep}`);
|
|
378
|
+
}
|
|
379
|
+
const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
|
|
380
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
381
|
+
return {
|
|
382
|
+
async maybeCompact(store, conversationId) {
|
|
383
|
+
const turns = await store.load(conversationId, LOAD_ALL);
|
|
384
|
+
if (turns.length < threshold) return;
|
|
385
|
+
const older = turns.slice(0, turns.length - keep);
|
|
386
|
+
let summary;
|
|
387
|
+
let timer;
|
|
388
|
+
try {
|
|
389
|
+
summary = await Promise.race([
|
|
390
|
+
summarize({ conversationId, turns: older }),
|
|
391
|
+
new Promise((_, reject) => {
|
|
392
|
+
timer = setTimeout(() => reject(new Error("history compaction timed out")), timeoutMs);
|
|
393
|
+
})
|
|
394
|
+
]);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
logger?.error(
|
|
397
|
+
`history compaction summarize failed for "${conversationId}"; history left untouched`,
|
|
398
|
+
error
|
|
399
|
+
);
|
|
400
|
+
return;
|
|
401
|
+
} finally {
|
|
402
|
+
clearTimeout(timer);
|
|
403
|
+
}
|
|
404
|
+
const trimmed = summary.trim();
|
|
405
|
+
if (!trimmed) return;
|
|
406
|
+
const fresh = await store.load(conversationId, LOAD_ALL);
|
|
407
|
+
const recent = fresh.slice(older.length);
|
|
408
|
+
await store.clear(conversationId);
|
|
409
|
+
await store.append(conversationId, {
|
|
410
|
+
role: "assistant",
|
|
411
|
+
content: `${SUMMARY_PREFIX}${trimmed}`
|
|
412
|
+
});
|
|
413
|
+
for (const turn of recent) {
|
|
414
|
+
await store.append(conversationId, turn);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
351
420
|
// src/channels/gates.ts
|
|
352
421
|
function matches(re, text) {
|
|
353
422
|
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
@@ -428,6 +497,10 @@ function createInboundPipeline(deps, config) {
|
|
|
428
497
|
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
429
498
|
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
430
499
|
const mode = "public";
|
|
500
|
+
const compactor = config.history?.compaction ? createHistoryCompactor(
|
|
501
|
+
{ client: deps.client },
|
|
502
|
+
{ model: config.model, ...config.history.compaction }
|
|
503
|
+
) : void 0;
|
|
431
504
|
return async function handleInboundMessage(msg, turn) {
|
|
432
505
|
const allowedChats = config.allowedChats ?? [];
|
|
433
506
|
const action = decideChannelAction(msg, {
|
|
@@ -503,6 +576,14 @@ function createInboundPipeline(deps, config) {
|
|
|
503
576
|
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
504
577
|
}
|
|
505
578
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
579
|
+
const history = config.history;
|
|
580
|
+
if (compactor && historyEnabled && history) {
|
|
581
|
+
try {
|
|
582
|
+
await compactor.maybeCompact(history.store, conversationId);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
config.logger?.error(`history compaction failed for "${conversationId}"`, error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
506
587
|
return { action: "reply", content };
|
|
507
588
|
};
|
|
508
589
|
}
|
|
@@ -23,6 +23,7 @@ import type { AnswerFn, TransformReply } from "../../core/answer";
|
|
|
23
23
|
import type { BucketsFor } from "../../core/buckets";
|
|
24
24
|
import { type Logger } from "../../core/logger";
|
|
25
25
|
import type { RerankContext, RewriteQuery } from "../../core/pipeline";
|
|
26
|
+
import type { HistoryCompactionOptions } from "../../history/compaction";
|
|
26
27
|
import type { HistoryStore } from "../../history/types";
|
|
27
28
|
import type { CustomRoutes } from "../../types";
|
|
28
29
|
import { type TelegramApi } from "./api";
|
|
@@ -62,6 +63,12 @@ export interface TelegramWebhookConfig {
|
|
|
62
63
|
/** Most recent turns to load per reply. @default 20 */
|
|
63
64
|
limit?: number;
|
|
64
65
|
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Summarize-then-truncate compaction — see
|
|
68
|
+
* `InboundPipelineConfig.history.compaction` in `./channels`.
|
|
69
|
+
* @default off
|
|
70
|
+
*/
|
|
71
|
+
compaction?: HistoryCompactionOptions;
|
|
65
72
|
};
|
|
66
73
|
muteRegex?: RegExp;
|
|
67
74
|
unmuteRegex?: RegExp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAMjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAMjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACnE;;;;WAIG;QACH,UAAU,CAAC,EAAE,wBAAwB,CAAC;KACvC,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qJAAqJ;IACrJ,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,qBAAqB,GAAG,YAAY,CAkHtF"}
|
|
@@ -54,6 +54,7 @@ import { type Logger } from "../../core/logger";
|
|
|
54
54
|
import type { RerankContext, RewriteQuery } from "../../core/pipeline";
|
|
55
55
|
import type { PromptLoader } from "../../core/prompts";
|
|
56
56
|
import type { VectorStore } from "../../core/retrieval";
|
|
57
|
+
import type { HistoryCompactionOptions } from "../../history/compaction";
|
|
57
58
|
import type { HistoryStore } from "../../history/types";
|
|
58
59
|
import { type ChannelMessage, type SessionIdentityRegistry } from "../gates";
|
|
59
60
|
import type { WhatsAppMessageEvent } from "./channel";
|
|
@@ -199,6 +200,12 @@ export interface WhatsAppInboundConfig {
|
|
|
199
200
|
* @default every sender is enabled
|
|
200
201
|
*/
|
|
201
202
|
historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
|
|
203
|
+
/**
|
|
204
|
+
* Summarize-then-truncate compaction — see
|
|
205
|
+
* `InboundPipelineConfig.history.compaction` in `./channels`.
|
|
206
|
+
* @default off
|
|
207
|
+
*/
|
|
208
|
+
compaction?: HistoryCompactionOptions;
|
|
202
209
|
};
|
|
203
210
|
now?: () => number;
|
|
204
211
|
/** Logger for gate/dispatch diagnostics. Default: a console logger writing to stderr. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -840,6 +840,75 @@ ${ctx.join("\n\n")}`
|
|
|
840
840
|
return { system, messages, context: ctx };
|
|
841
841
|
}
|
|
842
842
|
|
|
843
|
+
// src/history/compaction.ts
|
|
844
|
+
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
845
|
+
var LOAD_ALL = Number.MAX_SAFE_INTEGER;
|
|
846
|
+
var SUMMARY_PREFIX = "Summary of earlier conversation: ";
|
|
847
|
+
function defaultSummarize(client, model) {
|
|
848
|
+
return async ({ turns }) => {
|
|
849
|
+
const transcript = turns.map((turn) => `${turn.role}: ${turn.content}`).join("\n");
|
|
850
|
+
const { content } = await answerOnce({
|
|
851
|
+
client,
|
|
852
|
+
system: "Summarize the conversation turns below into a short paragraph a reader would need to continue the conversation. Preserve names, facts, and decisions; drop pleasantries. Reply with the summary only, no preamble.",
|
|
853
|
+
messages: [{ role: "user", content: transcript }],
|
|
854
|
+
// No `answerFn`: compaction is an internal chatter operation, not a
|
|
855
|
+
// chat surface, so it must not conscript a caller's brain hook, which
|
|
856
|
+
// is shaped to answer the user, not to summarize on the engine's behalf.
|
|
857
|
+
mode: "public",
|
|
858
|
+
model
|
|
859
|
+
});
|
|
860
|
+
return content;
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function createHistoryCompactor(deps, options) {
|
|
864
|
+
const { threshold, keep, logger } = options;
|
|
865
|
+
if (!Number.isInteger(threshold) || threshold < 1) {
|
|
866
|
+
throw new Error(`Invalid history compaction threshold: ${threshold}`);
|
|
867
|
+
}
|
|
868
|
+
if (!Number.isInteger(keep) || keep < 0 || keep >= threshold) {
|
|
869
|
+
throw new Error(`Invalid history compaction keep: ${keep}`);
|
|
870
|
+
}
|
|
871
|
+
const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
|
|
872
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
873
|
+
return {
|
|
874
|
+
async maybeCompact(store, conversationId) {
|
|
875
|
+
const turns = await store.load(conversationId, LOAD_ALL);
|
|
876
|
+
if (turns.length < threshold) return;
|
|
877
|
+
const older = turns.slice(0, turns.length - keep);
|
|
878
|
+
let summary;
|
|
879
|
+
let timer;
|
|
880
|
+
try {
|
|
881
|
+
summary = await Promise.race([
|
|
882
|
+
summarize({ conversationId, turns: older }),
|
|
883
|
+
new Promise((_, reject) => {
|
|
884
|
+
timer = setTimeout(() => reject(new Error("history compaction timed out")), timeoutMs);
|
|
885
|
+
})
|
|
886
|
+
]);
|
|
887
|
+
} catch (error) {
|
|
888
|
+
logger?.error(
|
|
889
|
+
`history compaction summarize failed for "${conversationId}"; history left untouched`,
|
|
890
|
+
error
|
|
891
|
+
);
|
|
892
|
+
return;
|
|
893
|
+
} finally {
|
|
894
|
+
clearTimeout(timer);
|
|
895
|
+
}
|
|
896
|
+
const trimmed = summary.trim();
|
|
897
|
+
if (!trimmed) return;
|
|
898
|
+
const fresh = await store.load(conversationId, LOAD_ALL);
|
|
899
|
+
const recent = fresh.slice(older.length);
|
|
900
|
+
await store.clear(conversationId);
|
|
901
|
+
await store.append(conversationId, {
|
|
902
|
+
role: "assistant",
|
|
903
|
+
content: `${SUMMARY_PREFIX}${trimmed}`
|
|
904
|
+
});
|
|
905
|
+
for (const turn of recent) {
|
|
906
|
+
await store.append(conversationId, turn);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
843
912
|
// src/channels/pipeline.ts
|
|
844
913
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
845
914
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
@@ -870,6 +939,10 @@ function createInboundPipeline(deps, config) {
|
|
|
870
939
|
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
871
940
|
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
872
941
|
const mode = "public";
|
|
942
|
+
const compactor = config.history?.compaction ? createHistoryCompactor(
|
|
943
|
+
{ client: deps.client },
|
|
944
|
+
{ model: config.model, ...config.history.compaction }
|
|
945
|
+
) : void 0;
|
|
873
946
|
return async function handleInboundMessage(msg, turn) {
|
|
874
947
|
const allowedChats = config.allowedChats ?? [];
|
|
875
948
|
const action = decideChannelAction(msg, {
|
|
@@ -945,6 +1018,14 @@ function createInboundPipeline(deps, config) {
|
|
|
945
1018
|
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
946
1019
|
}
|
|
947
1020
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
1021
|
+
const history = config.history;
|
|
1022
|
+
if (compactor && historyEnabled && history) {
|
|
1023
|
+
try {
|
|
1024
|
+
await compactor.maybeCompact(history.store, conversationId);
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
config.logger?.error(`history compaction failed for "${conversationId}"`, error);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
948
1029
|
return { action: "reply", content };
|
|
949
1030
|
};
|
|
950
1031
|
}
|