@0xmaxma/claude-gateway 1.3.16 → 1.3.18
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/config.template.json +8 -4
- package/dist/agent/builtin-commands.d.ts +1 -1
- package/dist/agent/builtin-commands.d.ts.map +1 -1
- package/dist/agent/builtin-commands.js +5 -0
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/line-pure.d.ts +31 -0
- package/dist/agent/line-pure.d.ts.map +1 -0
- package/dist/agent/line-pure.js +99 -0
- package/dist/agent/line-pure.js.map +1 -0
- package/dist/agent/line-reply-manager.d.ts +68 -0
- package/dist/agent/line-reply-manager.d.ts.map +1 -0
- package/dist/agent/line-reply-manager.js +383 -0
- package/dist/agent/line-reply-manager.js.map +1 -0
- package/dist/agent/runner.d.ts +11 -2
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +106 -8
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +4 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/line-access.d.ts +60 -0
- package/dist/api/line-access.d.ts.map +1 -0
- package/dist/api/line-access.js +69 -0
- package/dist/api/line-access.js.map +1 -0
- package/dist/api/line-mention.d.ts +35 -0
- package/dist/api/line-mention.d.ts.map +1 -0
- package/dist/api/line-mention.js +33 -0
- package/dist/api/line-mention.js.map +1 -0
- package/dist/api/line-pending-senders.d.ts +40 -0
- package/dist/api/line-pending-senders.d.ts.map +1 -0
- package/dist/api/line-pending-senders.js +107 -0
- package/dist/api/line-pending-senders.js.map +1 -0
- package/dist/api/line-webhook-router.d.ts +51 -0
- package/dist/api/line-webhook-router.d.ts.map +1 -0
- package/dist/api/line-webhook-router.js +416 -0
- package/dist/api/line-webhook-router.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +244 -7
- package/dist/api/router.js.map +1 -1
- package/dist/history/types.d.ts +1 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/session/compactor.d.ts +1 -1
- package/dist/session/compactor.d.ts.map +1 -1
- package/dist/session/compactor.js.map +1 -1
- package/dist/session/process.d.ts +2 -2
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +14 -3
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +13 -13
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +12 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -1
- package/mcp/bun.lock +9 -2
- package/mcp/package.json +1 -0
- package/mcp/server.ts +2 -0
- package/mcp/tools/line/module.ts +127 -0
- package/mcp/tools/line/pure.ts +62 -0
- package/mcp/tools/telegram/receiver-server.ts +8 -5
- package/package.json +2 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LINE DM access control (Tier 1) — pure, stateless sender gate.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the simple DM branches of `mcp/tools/discord/access.ts` (minus the
|
|
5
|
+
* pairing/state machine) and the closed-by-default posture of hermes-agent's
|
|
6
|
+
* three-list gate (`plugins/platforms/line/adapter.py:_allowed_for_source`) and
|
|
7
|
+
* openclaw's LINE `dmPolicy`. The webhook router reads `line` config directly
|
|
8
|
+
* and calls this once per inbound event — no env plumbing, no state files.
|
|
9
|
+
*
|
|
10
|
+
* Default (policy absent) is CLOSED: only userIds in the allowlist pass. Set
|
|
11
|
+
* `dmPolicy: 'open'` to restore "reply to anyone" behavior.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isLineSenderAllowed(policy: 'open' | 'allowlist' | 'disabled' | undefined, allowlist: string[] | undefined, userId: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Structural view of a LINE event `source` block — the only fields the gate
|
|
16
|
+
* needs. Kept local (not the SDK type) so this module stays pure and trivially
|
|
17
|
+
* testable. LINE sources are one of:
|
|
18
|
+
* user → { type: 'user', userId }
|
|
19
|
+
* group → { type: 'group', groupId, userId? } (userId present if the sender
|
|
20
|
+
* room → { type: 'room', roomId, userId? } consented to profile access)
|
|
21
|
+
*/
|
|
22
|
+
export interface LineSourceLike {
|
|
23
|
+
type?: string;
|
|
24
|
+
userId?: string;
|
|
25
|
+
groupId?: string;
|
|
26
|
+
roomId?: string;
|
|
27
|
+
}
|
|
28
|
+
export type LineSourceKind = 'user' | 'group' | 'room' | 'other';
|
|
29
|
+
export interface ResolvedLineSource {
|
|
30
|
+
/** Conversation key — the reply/push target: userId (DM) / groupId / roomId. */
|
|
31
|
+
conversationId: string;
|
|
32
|
+
/** The human who sent the event (may be '' in groups w/o profile consent). */
|
|
33
|
+
senderId: string;
|
|
34
|
+
kind: LineSourceKind;
|
|
35
|
+
}
|
|
36
|
+
/** Map a raw LINE source to {conversationId, senderId, kind}. */
|
|
37
|
+
export declare function resolveLineSource(source: LineSourceLike | undefined | null): ResolvedLineSource;
|
|
38
|
+
/** The subset of `line` config the conversation gate reads. */
|
|
39
|
+
export interface LineAccessConfig {
|
|
40
|
+
dmPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
41
|
+
dmAllowlist?: string[];
|
|
42
|
+
groupPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
43
|
+
groupAllowlist?: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Gate an already-resolved LINE source (the hermes three-list model). DMs keep
|
|
47
|
+
* the exact `isLineSenderAllowed` behavior; group/room are gated on the
|
|
48
|
+
* conversation id (groupId/roomId) against `groupPolicy` + `groupAllowlist`,
|
|
49
|
+
* closed by default — same posture as DMs. Unknown source kinds are denied.
|
|
50
|
+
*
|
|
51
|
+
* The webhook resolves the source once per event and reuses that result here (and
|
|
52
|
+
* for `normalizeLineEvent`) rather than re-parsing the raw source three times.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isResolvedSourceAllowed(cfg: LineAccessConfig | undefined, resolved: ResolvedLineSource): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Convenience wrapper that resolves a raw source and gates it in one call.
|
|
57
|
+
* Equivalent to `isResolvedSourceAllowed(cfg, resolveLineSource(source))`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isLineConversationAllowed(cfg: LineAccessConfig | undefined, source: LineSourceLike | undefined | null): boolean;
|
|
60
|
+
//# sourceMappingURL=line-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-access.d.ts","sourceRoot":"","sources":["../../src/api/line-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,EACrD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,MAAM,EAAE,MAAM,GACb,OAAO,CAKT;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,GAAG,kBAAkB,CAa/F;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,gBAAgB,GAAG,SAAS,EACjC,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAUT;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,gBAAgB,GAAG,SAAS,EACjC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,GACxC,OAAO,CAET"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLineSenderAllowed = isLineSenderAllowed;
|
|
4
|
+
exports.resolveLineSource = resolveLineSource;
|
|
5
|
+
exports.isResolvedSourceAllowed = isResolvedSourceAllowed;
|
|
6
|
+
exports.isLineConversationAllowed = isLineConversationAllowed;
|
|
7
|
+
/**
|
|
8
|
+
* LINE DM access control (Tier 1) — pure, stateless sender gate.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the simple DM branches of `mcp/tools/discord/access.ts` (minus the
|
|
11
|
+
* pairing/state machine) and the closed-by-default posture of hermes-agent's
|
|
12
|
+
* three-list gate (`plugins/platforms/line/adapter.py:_allowed_for_source`) and
|
|
13
|
+
* openclaw's LINE `dmPolicy`. The webhook router reads `line` config directly
|
|
14
|
+
* and calls this once per inbound event — no env plumbing, no state files.
|
|
15
|
+
*
|
|
16
|
+
* Default (policy absent) is CLOSED: only userIds in the allowlist pass. Set
|
|
17
|
+
* `dmPolicy: 'open'` to restore "reply to anyone" behavior.
|
|
18
|
+
*/
|
|
19
|
+
function isLineSenderAllowed(policy, allowlist, userId) {
|
|
20
|
+
if (policy === 'open')
|
|
21
|
+
return true;
|
|
22
|
+
if (policy === 'disabled')
|
|
23
|
+
return false;
|
|
24
|
+
// 'allowlist' OR undefined (closed default) → only listed senders pass.
|
|
25
|
+
return !!userId && (allowlist ?? []).includes(userId);
|
|
26
|
+
}
|
|
27
|
+
/** Map a raw LINE source to {conversationId, senderId, kind}. */
|
|
28
|
+
function resolveLineSource(source) {
|
|
29
|
+
const type = source?.type;
|
|
30
|
+
if (type === 'user') {
|
|
31
|
+
const userId = source?.userId ?? '';
|
|
32
|
+
return { conversationId: userId, senderId: userId, kind: 'user' };
|
|
33
|
+
}
|
|
34
|
+
if (type === 'group') {
|
|
35
|
+
return { conversationId: source?.groupId ?? '', senderId: source?.userId ?? '', kind: 'group' };
|
|
36
|
+
}
|
|
37
|
+
if (type === 'room') {
|
|
38
|
+
return { conversationId: source?.roomId ?? '', senderId: source?.userId ?? '', kind: 'room' };
|
|
39
|
+
}
|
|
40
|
+
return { conversationId: '', senderId: '', kind: 'other' };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Gate an already-resolved LINE source (the hermes three-list model). DMs keep
|
|
44
|
+
* the exact `isLineSenderAllowed` behavior; group/room are gated on the
|
|
45
|
+
* conversation id (groupId/roomId) against `groupPolicy` + `groupAllowlist`,
|
|
46
|
+
* closed by default — same posture as DMs. Unknown source kinds are denied.
|
|
47
|
+
*
|
|
48
|
+
* The webhook resolves the source once per event and reuses that result here (and
|
|
49
|
+
* for `normalizeLineEvent`) rather than re-parsing the raw source three times.
|
|
50
|
+
*/
|
|
51
|
+
function isResolvedSourceAllowed(cfg, resolved) {
|
|
52
|
+
const { conversationId, kind } = resolved;
|
|
53
|
+
if (kind === 'user') {
|
|
54
|
+
return isLineSenderAllowed(cfg?.dmPolicy, cfg?.dmAllowlist, conversationId);
|
|
55
|
+
}
|
|
56
|
+
if (kind === 'group' || kind === 'room') {
|
|
57
|
+
// Reuse the same closed-by-default semantics, keyed on the group/room id.
|
|
58
|
+
return isLineSenderAllowed(cfg?.groupPolicy, cfg?.groupAllowlist, conversationId);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convenience wrapper that resolves a raw source and gates it in one call.
|
|
64
|
+
* Equivalent to `isResolvedSourceAllowed(cfg, resolveLineSource(source))`.
|
|
65
|
+
*/
|
|
66
|
+
function isLineConversationAllowed(cfg, source) {
|
|
67
|
+
return isResolvedSourceAllowed(cfg, resolveLineSource(source));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=line-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-access.js","sourceRoot":"","sources":["../../src/api/line-access.ts"],"names":[],"mappings":";;AAYA,kDASC;AA4BD,8CAaC;AAmBD,0DAaC;AAMD,8DAKC;AAzGD;;;;;;;;;;;GAWG;AACH,SAAgB,mBAAmB,CACjC,MAAqD,EACrD,SAA+B,EAC/B,MAAc;IAEd,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACxC,wEAAwE;IACxE,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxD,CAAC;AA2BD,iEAAiE;AACjE,SAAgB,iBAAiB,CAAC,MAAyC;IACzE,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC;QACpC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAChG,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC7D,CAAC;AAUD;;;;;;;;GAQG;AACH,SAAgB,uBAAuB,CACrC,GAAiC,EACjC,QAA4B;IAE5B,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC1C,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,mBAAmB,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACxC,0EAA0E;QAC1E,OAAO,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAgB,yBAAyB,CACvC,GAAiC,EACjC,MAAyC;IAEzC,OAAO,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LINE bot-mention detection — pure helper for the group/room activation gate.
|
|
3
|
+
*
|
|
4
|
+
* In groups/rooms the bot answers only when it is @mentioned (see
|
|
5
|
+
* `line.requireMention`), and only via LINE's NATIVE mention:
|
|
6
|
+
* `message.mention.mentionees[]`, each carrying `isSelf` — true when that
|
|
7
|
+
* mentionee IS the bot receiving the webhook. This is the reliable signal and
|
|
8
|
+
* needs no knowledge of the bot's own userId.
|
|
9
|
+
*
|
|
10
|
+
* A user who merely TYPES the bot's name as plain text (no native mention) is
|
|
11
|
+
* intentionally NOT treated as calling the bot — that message is let through
|
|
12
|
+
* silently. `@All` (mentionee `type: 'all'`) is likewise NOT a bot mention —
|
|
13
|
+
* otherwise the bot would wake on every group-wide announcement.
|
|
14
|
+
*/
|
|
15
|
+
interface MentioneeLike {
|
|
16
|
+
type?: string;
|
|
17
|
+
isSelf?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface MentionLike {
|
|
20
|
+
mentionees?: MentioneeLike[];
|
|
21
|
+
}
|
|
22
|
+
export interface LineMessageLike {
|
|
23
|
+
type?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
mention?: MentionLike;
|
|
26
|
+
}
|
|
27
|
+
/** True if the bot itself is a named mentionee (excludes @All). */
|
|
28
|
+
export declare function hasNativeBotMention(message: LineMessageLike | undefined | null): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the bot was mentioned in this message. Native `isSelf` only — a typed
|
|
31
|
+
* `@name` in plain text does not count, and `@All` never counts.
|
|
32
|
+
*/
|
|
33
|
+
export declare function wasBotMentioned(message: LineMessageLike | undefined | null): boolean;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=line-mention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-mention.d.ts","sourceRoot":"","sources":["../../src/api/line-mention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,WAAW;IACnB,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAIxF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAEpF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LINE bot-mention detection — pure helper for the group/room activation gate.
|
|
4
|
+
*
|
|
5
|
+
* In groups/rooms the bot answers only when it is @mentioned (see
|
|
6
|
+
* `line.requireMention`), and only via LINE's NATIVE mention:
|
|
7
|
+
* `message.mention.mentionees[]`, each carrying `isSelf` — true when that
|
|
8
|
+
* mentionee IS the bot receiving the webhook. This is the reliable signal and
|
|
9
|
+
* needs no knowledge of the bot's own userId.
|
|
10
|
+
*
|
|
11
|
+
* A user who merely TYPES the bot's name as plain text (no native mention) is
|
|
12
|
+
* intentionally NOT treated as calling the bot — that message is let through
|
|
13
|
+
* silently. `@All` (mentionee `type: 'all'`) is likewise NOT a bot mention —
|
|
14
|
+
* otherwise the bot would wake on every group-wide announcement.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.hasNativeBotMention = hasNativeBotMention;
|
|
18
|
+
exports.wasBotMentioned = wasBotMentioned;
|
|
19
|
+
/** True if the bot itself is a named mentionee (excludes @All). */
|
|
20
|
+
function hasNativeBotMention(message) {
|
|
21
|
+
const mentionees = message?.mention?.mentionees;
|
|
22
|
+
if (!Array.isArray(mentionees))
|
|
23
|
+
return false;
|
|
24
|
+
return mentionees.some((m) => m?.type === 'user' && m?.isSelf === true);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether the bot was mentioned in this message. Native `isSelf` only — a typed
|
|
28
|
+
* `@name` in plain text does not count, and `@All` never counts.
|
|
29
|
+
*/
|
|
30
|
+
function wasBotMentioned(message) {
|
|
31
|
+
return hasNativeBotMention(message);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=line-mention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-mention.js","sourceRoot":"","sources":["../../src/api/line-mention.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAkBH,kDAIC;AAMD,0CAEC;AAbD,mEAAmE;AACnE,SAAgB,mBAAmB,CAAC,OAA2C;IAC7E,MAAM,UAAU,GAAG,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,OAA2C;IACzE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface PendingSender {
|
|
2
|
+
/** The conversation/sender id: a userId (DM), groupId, or roomId. */
|
|
3
|
+
userId: string;
|
|
4
|
+
/** Best-effort display name: LINE profile name (user) or group name (group). */
|
|
5
|
+
displayName?: string;
|
|
6
|
+
/** Source kind, so the UI can label rooms and filter against the right list. */
|
|
7
|
+
kind?: 'user' | 'group' | 'room';
|
|
8
|
+
/**
|
|
9
|
+
* One-time pairing code (pairing mode). Minted once when the entry is created
|
|
10
|
+
* and never overwritten on dedup, so it stays stable for the admin to match
|
|
11
|
+
* against what the sender reports. Absent when pairing is off.
|
|
12
|
+
*/
|
|
13
|
+
code?: string;
|
|
14
|
+
firstSeen: number;
|
|
15
|
+
lastSeen: number;
|
|
16
|
+
count: number;
|
|
17
|
+
}
|
|
18
|
+
/** Mint a short visual-match pairing code (6 uppercase hex). */
|
|
19
|
+
export declare function generatePairingCode(): string;
|
|
20
|
+
/** Max distinct senders retained per agent (oldest evicted first). */
|
|
21
|
+
export declare const MAX_PENDING_PER_AGENT = 20;
|
|
22
|
+
/**
|
|
23
|
+
* Record a denied 1:1 DM sender (Tier 1 discovery). Returns true on first
|
|
24
|
+
* contact (entry newly created).
|
|
25
|
+
*/
|
|
26
|
+
export declare function recordDeniedSender(agentId: string, userId: string, displayName?: string, now?: number, code?: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Record a denied group/room conversation (Tier 3 discovery). Returns true on
|
|
29
|
+
* first contact (entry newly created).
|
|
30
|
+
*/
|
|
31
|
+
export declare function recordDeniedConversation(agentId: string, conversationId: string, kind: 'group' | 'room', name?: string, now?: number, code?: string): boolean;
|
|
32
|
+
/** Pending senders for an agent, most-recent first. */
|
|
33
|
+
export declare function getPendingSenders(agentId: string): PendingSender[];
|
|
34
|
+
/** One pending-sender entry by id (used to reuse an already-minted pairing code). */
|
|
35
|
+
export declare function getPendingSender(agentId: string, id: string): PendingSender | undefined;
|
|
36
|
+
/** Drop an entry from the pending list (e.g. once added to an allowlist). */
|
|
37
|
+
export declare function clearPendingSender(agentId: string, id: string): void;
|
|
38
|
+
/** Test/maintenance helper — wipe all retained senders. */
|
|
39
|
+
export declare function _resetPendingSenders(): void;
|
|
40
|
+
//# sourceMappingURL=line-pending-senders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-pending-senders.d.ts","sourceRoot":"","sources":["../../src/api/line-pending-senders.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAuDxC;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,GAAG,GAAE,MAAmB,EACxB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAET;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,IAAI,CAAC,EAAE,MAAM,EACb,GAAG,GAAE,MAAmB,EACxB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAET;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAIlE;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEvF;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAEpE;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_PENDING_PER_AGENT = void 0;
|
|
4
|
+
exports.generatePairingCode = generatePairingCode;
|
|
5
|
+
exports.recordDeniedSender = recordDeniedSender;
|
|
6
|
+
exports.recordDeniedConversation = recordDeniedConversation;
|
|
7
|
+
exports.getPendingSenders = getPendingSenders;
|
|
8
|
+
exports.getPendingSender = getPendingSender;
|
|
9
|
+
exports.clearPendingSender = clearPendingSender;
|
|
10
|
+
exports._resetPendingSenders = _resetPendingSenders;
|
|
11
|
+
/**
|
|
12
|
+
* LINE "pending senders" — in-memory discovery aid for the Tier 1 allowlist.
|
|
13
|
+
* When the webhook access gate drops a sender that is not on the allowlist, we
|
|
14
|
+
* remember them here (with their LINE display name, best-effort) so the admin
|
|
15
|
+
* can see who is pending and one-click-add them from the UI instead of grepping
|
|
16
|
+
* `line-webhook.log`.
|
|
17
|
+
*
|
|
18
|
+
* Intentionally ephemeral: a process-lifetime Map, no persistence. Adding to the
|
|
19
|
+
* allowlist is the durable action (config.json via the agent PATCH API); this is
|
|
20
|
+
* just a transient "pending list". Bounded per agent so a flood can't grow it.
|
|
21
|
+
*/
|
|
22
|
+
const crypto_1 = require("crypto");
|
|
23
|
+
/** Mint a short visual-match pairing code (6 uppercase hex). */
|
|
24
|
+
function generatePairingCode() {
|
|
25
|
+
return (0, crypto_1.randomBytes)(3).toString('hex').toUpperCase();
|
|
26
|
+
}
|
|
27
|
+
/** Max distinct senders retained per agent (oldest evicted first). */
|
|
28
|
+
exports.MAX_PENDING_PER_AGENT = 20;
|
|
29
|
+
// agentId -> (id -> entry) (id = userId | groupId | roomId)
|
|
30
|
+
const store = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Record a denied knock (DM sender or group/room conversation). Dedups by id
|
|
33
|
+
* (bumps count + lastSeen, fills in a name/kind if newly resolved). Evicts the
|
|
34
|
+
* least-recently-seen entry when the per-agent cap is exceeded.
|
|
35
|
+
*
|
|
36
|
+
* `code` is set only when the entry is newly created (never overwritten on
|
|
37
|
+
* dedup), so a pairing code stays stable. Returns `true` when this call created
|
|
38
|
+
* a new entry (first contact) — the webhook uses this to send the pairing code
|
|
39
|
+
* exactly once.
|
|
40
|
+
*/
|
|
41
|
+
function recordDenied(agentId, id, kind, displayName, now, code) {
|
|
42
|
+
if (!agentId || !id)
|
|
43
|
+
return false;
|
|
44
|
+
let byId = store.get(agentId);
|
|
45
|
+
if (!byId) {
|
|
46
|
+
byId = new Map();
|
|
47
|
+
store.set(agentId, byId);
|
|
48
|
+
}
|
|
49
|
+
const existing = byId.get(id);
|
|
50
|
+
if (existing) {
|
|
51
|
+
existing.lastSeen = now;
|
|
52
|
+
existing.count += 1;
|
|
53
|
+
if (displayName && !existing.displayName)
|
|
54
|
+
existing.displayName = displayName;
|
|
55
|
+
if (!existing.kind)
|
|
56
|
+
existing.kind = kind;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
byId.set(id, { userId: id, displayName, kind, code, firstSeen: now, lastSeen: now, count: 1 });
|
|
60
|
+
if (byId.size > exports.MAX_PENDING_PER_AGENT) {
|
|
61
|
+
let oldestId = null;
|
|
62
|
+
let oldestTs = Infinity;
|
|
63
|
+
for (const [eid, entry] of byId) {
|
|
64
|
+
if (entry.lastSeen < oldestTs) {
|
|
65
|
+
oldestTs = entry.lastSeen;
|
|
66
|
+
oldestId = eid;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (oldestId)
|
|
70
|
+
byId.delete(oldestId);
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Record a denied 1:1 DM sender (Tier 1 discovery). Returns true on first
|
|
76
|
+
* contact (entry newly created).
|
|
77
|
+
*/
|
|
78
|
+
function recordDeniedSender(agentId, userId, displayName, now = Date.now(), code) {
|
|
79
|
+
return recordDenied(agentId, userId, 'user', displayName, now, code);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Record a denied group/room conversation (Tier 3 discovery). Returns true on
|
|
83
|
+
* first contact (entry newly created).
|
|
84
|
+
*/
|
|
85
|
+
function recordDeniedConversation(agentId, conversationId, kind, name, now = Date.now(), code) {
|
|
86
|
+
return recordDenied(agentId, conversationId, kind, name, now, code);
|
|
87
|
+
}
|
|
88
|
+
/** Pending senders for an agent, most-recent first. */
|
|
89
|
+
function getPendingSenders(agentId) {
|
|
90
|
+
const byId = store.get(agentId);
|
|
91
|
+
if (!byId)
|
|
92
|
+
return [];
|
|
93
|
+
return [...byId.values()].sort((a, b) => b.lastSeen - a.lastSeen);
|
|
94
|
+
}
|
|
95
|
+
/** One pending-sender entry by id (used to reuse an already-minted pairing code). */
|
|
96
|
+
function getPendingSender(agentId, id) {
|
|
97
|
+
return store.get(agentId)?.get(id);
|
|
98
|
+
}
|
|
99
|
+
/** Drop an entry from the pending list (e.g. once added to an allowlist). */
|
|
100
|
+
function clearPendingSender(agentId, id) {
|
|
101
|
+
store.get(agentId)?.delete(id);
|
|
102
|
+
}
|
|
103
|
+
/** Test/maintenance helper — wipe all retained senders. */
|
|
104
|
+
function _resetPendingSenders() {
|
|
105
|
+
store.clear();
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=line-pending-senders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-pending-senders.js","sourceRoot":"","sources":["../../src/api/line-pending-senders.ts"],"names":[],"mappings":";;;AAgCA,kDAEC;AA8DD,gDAQC;AAMD,4DASC;AAGD,8CAIC;AAGD,4CAEC;AAGD,gDAEC;AAGD,oDAEC;AA7ID;;;;;;;;;;GAUG;AACH,mCAAqC;AAoBrC,gEAAgE;AAChE,SAAgB,mBAAmB;IACjC,OAAO,IAAA,oBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AACtD,CAAC;AAED,sEAAsE;AACzD,QAAA,qBAAqB,GAAG,EAAE,CAAC;AAExC,6DAA6D;AAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsC,CAAC;AAE5D;;;;;;;;;GASG;AACH,SAAS,YAAY,CACnB,OAAe,EACf,EAAU,EACV,IAA+B,EAC/B,WAA+B,EAC/B,GAAW,EACX,IAAa;IAEb,IAAI,CAAC,OAAO,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;QACxC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,QAAQ,GAAG,GAAG,CAAC;QACxB,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;QACpB,IAAI,WAAW,IAAI,CAAC,QAAQ,CAAC,WAAW;YAAE,QAAQ,CAAC,WAAW,GAAG,WAAW,CAAC;QAC7E,IAAI,CAAC,QAAQ,CAAC,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAE/F,IAAI,IAAI,CAAC,IAAI,GAAG,6BAAqB,EAAE,CAAC;QACtC,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,QAAQ,GAAG,QAAQ,CAAC;QACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,QAAQ,GAAG,QAAQ,EAAE,CAAC;gBAC9B,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;gBAC1B,QAAQ,GAAG,GAAG,CAAC;YACjB,CAAC;QACH,CAAC;QACD,IAAI,QAAQ;YAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAChC,OAAe,EACf,MAAc,EACd,WAAoB,EACpB,MAAc,IAAI,CAAC,GAAG,EAAE,EACxB,IAAa;IAEb,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,SAAgB,wBAAwB,CACtC,OAAe,EACf,cAAsB,EACtB,IAAsB,EACtB,IAAa,EACb,MAAc,IAAI,CAAC,GAAG,EAAE,EACxB,IAAa;IAEb,OAAO,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,uDAAuD;AACvD,SAAgB,iBAAiB,CAAC,OAAe;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,qFAAqF;AACrF,SAAgB,gBAAgB,CAAC,OAAe,EAAE,EAAU;IAC1D,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,6EAA6E;AAC7E,SAAgB,kBAAkB,CAAC,OAAe,EAAE,EAAU;IAC5D,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,2DAA2D;AAC3D,SAAgB,oBAAoB;IAClC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LINE inbound webhook route (openclaw-style: LINE is webhook-only, no polling).
|
|
3
|
+
*
|
|
4
|
+
* Mounted on the gateway's Express app BEFORE express.json() so the raw request
|
|
5
|
+
* bytes are available for signature validation (LINE signs the raw body; a
|
|
6
|
+
* re-serialized parsed body would not match).
|
|
7
|
+
*
|
|
8
|
+
* Flow: verify x-line-signature → 200 → for each text message from a 1:1 user,
|
|
9
|
+
* show a loading animation and forward a normalized {content, meta} to the
|
|
10
|
+
* target agent's existing /channel callback (the same intake Telegram uses).
|
|
11
|
+
*/
|
|
12
|
+
import { Router } from 'express';
|
|
13
|
+
import { type webhook } from '@line/bot-sdk';
|
|
14
|
+
import type { AgentRunner } from '../agent/runner';
|
|
15
|
+
import { type ResolvedLineSource } from './line-access';
|
|
16
|
+
export type NormalizedLineMessage = {
|
|
17
|
+
content: string;
|
|
18
|
+
meta: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a LINE webhook event into the gateway's {content, meta} intake shape.
|
|
22
|
+
* Returns null for anything we don't handle in the POC (non-text, non-user source).
|
|
23
|
+
*
|
|
24
|
+
* `resolved` lets the caller pass the source it already resolved for the access
|
|
25
|
+
* gate, so a single event isn't re-parsed; omitted, it resolves here.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeLineEvent(event: webhook.Event, resolved?: ResolvedLineSource): NormalizedLineMessage | null;
|
|
28
|
+
export type NormalizedLinePostback = {
|
|
29
|
+
chatId: string;
|
|
30
|
+
replyToken: string;
|
|
31
|
+
data: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Normalize a LINE postback event (the slow-LLM "Get answer" button tap) into
|
|
35
|
+
* {chatId, replyToken, data}. Returns null for non-postback / non-user / tokenless
|
|
36
|
+
* events. `normalizeLineEvent` still drops postbacks; this is handled separately.
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeLinePostback(event: webhook.Event): NormalizedLinePostback | null;
|
|
39
|
+
/**
|
|
40
|
+
* Optional outbound base-URL overrides — only used to point the LINE SDK at a
|
|
41
|
+
* mock server in tests. Production passes nothing, so the SDK uses its real
|
|
42
|
+
* defaults (api.line.me / api-data.line.me). Replaces the former
|
|
43
|
+
* LINE_API_BASE / LINE_DATA_API_BASE env reads so there's no test-only env seam
|
|
44
|
+
* leaking into production code.
|
|
45
|
+
*/
|
|
46
|
+
export interface LineWebhookOptions {
|
|
47
|
+
apiBase?: string;
|
|
48
|
+
dataApiBase?: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function createLineWebhookRouter(agents: Map<string, AgentRunner>, logDir: string, opts?: LineWebhookOptions): Router;
|
|
51
|
+
//# sourceMappingURL=line-webhook-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-webhook-router.d.ts","sourceRoot":"","sources":["../../src/api/line-webhook-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAgB,EAAE,MAAM,EAA+B,MAAM,SAAS,CAAC;AAIvE,OAAO,EAAmC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AA+EvB,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,QAAQ,CAAC,EAAE,kBAAkB,GAC5B,qBAAqB,GAAG,IAAI,CA4B9B;AAED,MAAM,MAAM,sBAAsB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,GAAG,sBAAsB,GAAG,IAAI,CAwBzF;AAiBD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,kBAAuB,GAC5B,MAAM,CAmNR"}
|