@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.
Files changed (62) hide show
  1. package/config.template.json +8 -4
  2. package/dist/agent/builtin-commands.d.ts +1 -1
  3. package/dist/agent/builtin-commands.d.ts.map +1 -1
  4. package/dist/agent/builtin-commands.js +5 -0
  5. package/dist/agent/builtin-commands.js.map +1 -1
  6. package/dist/agent/line-pure.d.ts +31 -0
  7. package/dist/agent/line-pure.d.ts.map +1 -0
  8. package/dist/agent/line-pure.js +99 -0
  9. package/dist/agent/line-pure.js.map +1 -0
  10. package/dist/agent/line-reply-manager.d.ts +68 -0
  11. package/dist/agent/line-reply-manager.d.ts.map +1 -0
  12. package/dist/agent/line-reply-manager.js +383 -0
  13. package/dist/agent/line-reply-manager.js.map +1 -0
  14. package/dist/agent/runner.d.ts +11 -2
  15. package/dist/agent/runner.d.ts.map +1 -1
  16. package/dist/agent/runner.js +106 -8
  17. package/dist/agent/runner.js.map +1 -1
  18. package/dist/api/gateway-router.d.ts.map +1 -1
  19. package/dist/api/gateway-router.js +4 -0
  20. package/dist/api/gateway-router.js.map +1 -1
  21. package/dist/api/line-access.d.ts +60 -0
  22. package/dist/api/line-access.d.ts.map +1 -0
  23. package/dist/api/line-access.js +69 -0
  24. package/dist/api/line-access.js.map +1 -0
  25. package/dist/api/line-mention.d.ts +35 -0
  26. package/dist/api/line-mention.d.ts.map +1 -0
  27. package/dist/api/line-mention.js +33 -0
  28. package/dist/api/line-mention.js.map +1 -0
  29. package/dist/api/line-pending-senders.d.ts +40 -0
  30. package/dist/api/line-pending-senders.d.ts.map +1 -0
  31. package/dist/api/line-pending-senders.js +107 -0
  32. package/dist/api/line-pending-senders.js.map +1 -0
  33. package/dist/api/line-webhook-router.d.ts +51 -0
  34. package/dist/api/line-webhook-router.d.ts.map +1 -0
  35. package/dist/api/line-webhook-router.js +416 -0
  36. package/dist/api/line-webhook-router.js.map +1 -0
  37. package/dist/api/router.d.ts.map +1 -1
  38. package/dist/api/router.js +244 -7
  39. package/dist/api/router.js.map +1 -1
  40. package/dist/history/types.d.ts +1 -1
  41. package/dist/history/types.d.ts.map +1 -1
  42. package/dist/session/compactor.d.ts +1 -1
  43. package/dist/session/compactor.d.ts.map +1 -1
  44. package/dist/session/compactor.js.map +1 -1
  45. package/dist/session/process.d.ts +2 -2
  46. package/dist/session/process.d.ts.map +1 -1
  47. package/dist/session/process.js +14 -3
  48. package/dist/session/process.js.map +1 -1
  49. package/dist/session/store.d.ts +13 -13
  50. package/dist/session/store.d.ts.map +1 -1
  51. package/dist/session/store.js.map +1 -1
  52. package/dist/shell/claude-pty-shell.js +12 -0
  53. package/dist/shell/claude-pty-shell.js.map +1 -1
  54. package/dist/types.d.ts +60 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/mcp/bun.lock +9 -2
  57. package/mcp/package.json +1 -0
  58. package/mcp/server.ts +2 -0
  59. package/mcp/tools/line/module.ts +127 -0
  60. package/mcp/tools/line/pure.ts +62 -0
  61. package/mcp/tools/telegram/receiver-server.ts +8 -5
  62. 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":"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"}
@@ -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"}