@moikapy/lich 0.7.0 → 0.8.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/CHANGELOG.md +75 -5
- package/README.md +38 -9
- package/dist/{chunk-QVJCIZIF.js → chunk-EDRUZF22.js} +763 -286
- package/dist/chunk-EDRUZF22.js.map +1 -0
- package/dist/chunk-PL6MKRKE.js +75 -0
- package/dist/chunk-PL6MKRKE.js.map +1 -0
- package/dist/{chunk-JYURFAGB.js → chunk-QOTECFCN.js} +2 -2
- package/dist/chunk-VKEOHUCB.js +86 -0
- package/dist/chunk-VKEOHUCB.js.map +1 -0
- package/dist/{chunk-SAEB3QL3.js → chunk-W6JXZBUE.js} +2 -2
- package/dist/cli.d.ts +19 -1
- package/dist/cli.js +53 -11
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-5BG3YCZF.js → gateway-WU5G4ODO.js} +109 -49
- package/dist/gateway-WU5G4ODO.js.map +1 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.js +3 -2
- package/dist/resolve-H22TOVB5.js +7 -0
- package/dist/resolve-H22TOVB5.js.map +1 -0
- package/dist/store-COOLBAHB.js +9 -0
- package/dist/store-COOLBAHB.js.map +1 -0
- package/dist/{tui-L6RABP2J.js → tui-4BP3TI7J.js} +224 -30
- package/dist/tui-4BP3TI7J.js.map +1 -0
- package/docs/architecture/agent-loop.md +28 -9
- package/docs/architecture/overview.md +10 -10
- package/docs/architecture/tools.md +30 -3
- package/docs/getting-started.md +3 -3
- package/docs/index.md +1 -1
- package/docs/user-guide/cli.md +13 -5
- package/docs/user-guide/games.md +3 -3
- package/docs/user-guide/gateway.md +70 -16
- package/docs/user-guide/library.md +6 -3
- package/docs/user-guide/redot.md +2 -2
- package/docs/user-guide/tui.md +3 -2
- package/package.json +1 -1
- package/dist/chunk-QVJCIZIF.js.map +0 -1
- package/dist/gateway-5BG3YCZF.js.map +0 -1
- package/dist/tui-L6RABP2J.js.map +0 -1
- /package/dist/{chunk-JYURFAGB.js.map → chunk-QOTECFCN.js.map} +0 -0
- /package/dist/{chunk-SAEB3QL3.js.map → chunk-W6JXZBUE.js.map} +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
check_gateway_sender,
|
|
2
3
|
create_agent_with_plugins,
|
|
4
|
+
gateway_tools_enabled,
|
|
3
5
|
logger,
|
|
4
6
|
platform_token_env,
|
|
5
7
|
read_platform_token,
|
|
6
8
|
sleep
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EDRUZF22.js";
|
|
10
|
+
import "./chunk-VKEOHUCB.js";
|
|
8
11
|
|
|
9
12
|
// src/gateway/types.ts
|
|
10
13
|
var ERROR_SNIPPET_CHARS = 300;
|
|
@@ -65,6 +68,9 @@ var GatewayBus = class {
|
|
|
65
68
|
}
|
|
66
69
|
/** Serializes runs per conversation and resolves to the reply text. */
|
|
67
70
|
async handle(platform, chat_id, user_id, text) {
|
|
71
|
+
if (check_gateway_sender(this.config, platform, chat_id, user_id) === false) {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
68
74
|
const key = conversation_key(platform, chat_id);
|
|
69
75
|
const previous = this.chains.get(key) ?? Promise.resolve();
|
|
70
76
|
const run = previous.then(() => this.run_once(key, platform, chat_id, user_id, text));
|
|
@@ -131,7 +137,11 @@ function cap_history(messages, cap) {
|
|
|
131
137
|
if (overflow <= 0) {
|
|
132
138
|
return messages;
|
|
133
139
|
}
|
|
134
|
-
|
|
140
|
+
let sliced = messages.slice(overflow);
|
|
141
|
+
while (sliced.length > 0 && sliced[0]?.role !== "user") {
|
|
142
|
+
sliced = sliced.slice(1);
|
|
143
|
+
}
|
|
144
|
+
return sliced;
|
|
135
145
|
}
|
|
136
146
|
function final_reply_text(content) {
|
|
137
147
|
return content === void 0 || content.length === 0 ? void 0 : content;
|
|
@@ -216,18 +226,25 @@ function identify_body(token) {
|
|
|
216
226
|
return { token, intents: INTENTS, properties: { os: "linux", browser: "lich", device: "lich" } };
|
|
217
227
|
}
|
|
218
228
|
async function on_message_create(params, data) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
229
|
+
try {
|
|
230
|
+
const message = normalize_message(data);
|
|
231
|
+
if (message === void 0) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const reply = await run_inbound_message(
|
|
235
|
+
params.handle_message,
|
|
236
|
+
"discord",
|
|
237
|
+
message.channel_id,
|
|
238
|
+
message.author_id,
|
|
239
|
+
message.content
|
|
240
|
+
);
|
|
241
|
+
if (reply.length === 0) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await rest_send_message(params, message.channel_id, reply);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.warn("gateway discord deliver failed", error);
|
|
222
247
|
}
|
|
223
|
-
const reply = await run_inbound_message(
|
|
224
|
-
params.handle_message,
|
|
225
|
-
"discord",
|
|
226
|
-
message.channel_id,
|
|
227
|
-
message.author_id,
|
|
228
|
-
message.content
|
|
229
|
-
);
|
|
230
|
-
await rest_send_message(params, message.channel_id, reply);
|
|
231
248
|
}
|
|
232
249
|
async function rest_send_message(params, channel_id, text) {
|
|
233
250
|
const token = read_platform_token(params.config, "discord");
|
|
@@ -344,23 +361,30 @@ async function fetch_updates(token, offset) {
|
|
|
344
361
|
return Array.isArray(body.result) === true ? body.result : [];
|
|
345
362
|
}
|
|
346
363
|
async function deliver_update(params, token, update) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
try {
|
|
365
|
+
const message = update.message;
|
|
366
|
+
if (message === void 0 || message.from?.is_bot === true) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const chat_id = message.chat?.id;
|
|
370
|
+
if (chat_id === void 0) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const text = message.text ?? "media not supported yet";
|
|
374
|
+
const reply = await run_inbound_message(
|
|
375
|
+
params.handle_message,
|
|
376
|
+
"telegram",
|
|
377
|
+
String(chat_id),
|
|
378
|
+
String(message.from?.id ?? "unknown"),
|
|
379
|
+
text
|
|
380
|
+
);
|
|
381
|
+
if (reply.length === 0) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
await send_reply(token, String(chat_id), reply);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
logger.warn("gateway telegram deliver failed", error);
|
|
354
387
|
}
|
|
355
|
-
const text = message.text ?? "media not supported yet";
|
|
356
|
-
const reply = await run_inbound_message(
|
|
357
|
-
params.handle_message,
|
|
358
|
-
"telegram",
|
|
359
|
-
String(chat_id),
|
|
360
|
-
String(message.from?.id ?? "unknown"),
|
|
361
|
-
text
|
|
362
|
-
);
|
|
363
|
-
await send_reply(token, String(chat_id), reply);
|
|
364
388
|
}
|
|
365
389
|
async function send_reply(token, chat_id, text) {
|
|
366
390
|
for (const chunk of split_text(text, TELEGRAM_MAX_MESSAGE_CHARS)) {
|
|
@@ -506,16 +530,16 @@ function parse_irc_line(line) {
|
|
|
506
530
|
}
|
|
507
531
|
function match_privmsg(line) {
|
|
508
532
|
const without_tags = line.startsWith("@") === true ? line.slice(line.indexOf(" ") + 1) : line;
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
533
|
+
const matched = /^:(\w+)!\S+ PRIVMSG #(\w+) :(.*)$/.exec(without_tags);
|
|
534
|
+
if (matched === null) {
|
|
511
535
|
return void 0;
|
|
512
536
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
537
|
+
return {
|
|
538
|
+
kind: "privmsg",
|
|
539
|
+
user: matched[1] ?? "",
|
|
540
|
+
channel: matched[2] ?? "",
|
|
541
|
+
text: matched[3] ?? ""
|
|
542
|
+
};
|
|
519
543
|
}
|
|
520
544
|
function split_chunks2(text, limit) {
|
|
521
545
|
const chunks = [];
|
|
@@ -548,17 +572,20 @@ function format_agent_reply(text, usage, platform) {
|
|
|
548
572
|
|
|
549
573
|
// src/gateway/webhook.ts
|
|
550
574
|
var DEFAULT_GATEWAY_PORT = 8089;
|
|
575
|
+
var DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
551
576
|
function create_webhook_adapter(params) {
|
|
552
577
|
const port = params.port ?? read_port_env() ?? DEFAULT_GATEWAY_PORT;
|
|
578
|
+
const host = params.host ?? read_host_env() ?? DEFAULT_GATEWAY_HOST;
|
|
553
579
|
const token = read_platform_token(params.config, "webhook");
|
|
554
580
|
let server;
|
|
555
581
|
return {
|
|
556
582
|
name: "webhook",
|
|
557
583
|
start: async () => {
|
|
584
|
+
assert_bind_allowed(host, token);
|
|
558
585
|
server = createServer((request, response) => {
|
|
559
586
|
void dispatch_webhook(params, request, response, token);
|
|
560
587
|
});
|
|
561
|
-
await listen_on(server, port, (bound) => {
|
|
588
|
+
await listen_on(server, port, host, (bound) => {
|
|
562
589
|
params.on_listening?.(bound);
|
|
563
590
|
});
|
|
564
591
|
},
|
|
@@ -598,10 +625,10 @@ async function handle_message_post(params, request, response) {
|
|
|
598
625
|
send_json(response, 400, { error: "text is required" });
|
|
599
626
|
return;
|
|
600
627
|
}
|
|
601
|
-
const platform =
|
|
628
|
+
const platform = "webhook";
|
|
602
629
|
const chat_id = payload.chat_id ?? "default";
|
|
603
630
|
const user_id = payload.user_id ?? "anonymous";
|
|
604
|
-
const reply = await params.handle_message(
|
|
631
|
+
const reply = await params.handle_message(platform, String(chat_id), String(user_id), String(text));
|
|
605
632
|
respond_json_text(response, 200, format_agent_reply(reply ?? "", void 0, "webhook"));
|
|
606
633
|
}
|
|
607
634
|
function read_body(request) {
|
|
@@ -636,12 +663,12 @@ function respond_json_text(response, status, json_text) {
|
|
|
636
663
|
response.setHeader("content-type", "application/json");
|
|
637
664
|
response.end(json_text);
|
|
638
665
|
}
|
|
639
|
-
function listen_on(server, port, on_listening) {
|
|
666
|
+
function listen_on(server, port, host, on_listening) {
|
|
640
667
|
return new Promise((resolve, reject) => {
|
|
641
668
|
server.once("error", reject);
|
|
642
|
-
server.listen(port,
|
|
669
|
+
server.listen(port, host, () => {
|
|
643
670
|
const bound = server.address()?.port ?? port;
|
|
644
|
-
logger.info(`gateway webhook listening on :${bound}`);
|
|
671
|
+
logger.info(`gateway webhook listening on ${host}:${bound}`);
|
|
645
672
|
on_listening(bound);
|
|
646
673
|
resolve();
|
|
647
674
|
});
|
|
@@ -655,6 +682,23 @@ async function close_server(server) {
|
|
|
655
682
|
server.close(() => resolve());
|
|
656
683
|
});
|
|
657
684
|
}
|
|
685
|
+
function assert_bind_allowed(host, token) {
|
|
686
|
+
if (is_loopback_host(host) === true) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (token === void 0 || token.length === 0) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`gateway webhook refuses non-loopback bind (${host}) without a token \u2014 set LICH_GATEWAY_TOKEN or bind 127.0.0.1`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
logger.warn(
|
|
695
|
+
`gateway webhook binding ${host} with token auth \u2014 this exposes the agent beyond loopback; keep the token secret`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
function is_loopback_host(host) {
|
|
699
|
+
const normalized = host.trim().toLowerCase();
|
|
700
|
+
return normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost";
|
|
701
|
+
}
|
|
658
702
|
function read_port_env() {
|
|
659
703
|
const raw = process.env.LICH_GATEWAY_PORT;
|
|
660
704
|
if (raw === void 0 || raw.length === 0) {
|
|
@@ -663,10 +707,18 @@ function read_port_env() {
|
|
|
663
707
|
const port = Number(raw);
|
|
664
708
|
return Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
|
|
665
709
|
}
|
|
710
|
+
function read_host_env() {
|
|
711
|
+
const raw = process.env.LICH_GATEWAY_HOST;
|
|
712
|
+
if (raw === void 0 || raw.trim().length === 0) {
|
|
713
|
+
return void 0;
|
|
714
|
+
}
|
|
715
|
+
return raw.trim();
|
|
716
|
+
}
|
|
666
717
|
|
|
667
718
|
// src/gateway/runner.ts
|
|
668
719
|
async function create_gateway_bus(config) {
|
|
669
|
-
const
|
|
720
|
+
const agent_config = { ...config, tools_enabled: gateway_tools_enabled(config) };
|
|
721
|
+
const agent = await create_agent_with_plugins(agent_config);
|
|
670
722
|
const bus = new GatewayBus({ config, agent_factory: () => agent, wire_tool_logging: true });
|
|
671
723
|
return { agent, bus };
|
|
672
724
|
}
|
|
@@ -684,9 +736,9 @@ async function run_gateway(config, platforms) {
|
|
|
684
736
|
process.stderr.write("lich: gateway needs at least one valid platform (webhook|telegram|discord|twitch)\n");
|
|
685
737
|
return 1;
|
|
686
738
|
}
|
|
687
|
-
const { bus } = await create_gateway_bus(config);
|
|
739
|
+
const { agent, bus } = await create_gateway_bus(config);
|
|
688
740
|
const adapters = build_adapters(config, bus, valid);
|
|
689
|
-
install_signal_handlers(bus, adapters);
|
|
741
|
+
install_signal_handlers(agent, bus, adapters);
|
|
690
742
|
logger.info(`gateway starting: platforms=${valid.join(",")}, port=${process.env.LICH_GATEWAY_PORT ?? "8089"}, pid=${process.pid}`);
|
|
691
743
|
await start_all_adapters(adapters);
|
|
692
744
|
return await new Promise(() => void 0);
|
|
@@ -715,7 +767,7 @@ function build_adapters(config, bus, platforms) {
|
|
|
715
767
|
function create_platform_adapter(params, platform) {
|
|
716
768
|
switch (platform) {
|
|
717
769
|
case "webhook":
|
|
718
|
-
return create_webhook_adapter({ ...params, port: read_webhook_port() });
|
|
770
|
+
return create_webhook_adapter({ ...params, port: read_webhook_port(), host: read_webhook_host() });
|
|
719
771
|
case "telegram":
|
|
720
772
|
return create_telegram_adapter(params);
|
|
721
773
|
case "discord":
|
|
@@ -734,6 +786,13 @@ function read_webhook_port() {
|
|
|
734
786
|
const port = Number(raw);
|
|
735
787
|
return Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
|
|
736
788
|
}
|
|
789
|
+
function read_webhook_host() {
|
|
790
|
+
const raw = process.env.LICH_GATEWAY_HOST;
|
|
791
|
+
if (raw === void 0 || raw.trim().length === 0) {
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
return raw.trim();
|
|
795
|
+
}
|
|
737
796
|
async function start_all_adapters(adapters) {
|
|
738
797
|
for (const adapter of adapters) {
|
|
739
798
|
try {
|
|
@@ -744,12 +803,13 @@ async function start_all_adapters(adapters) {
|
|
|
744
803
|
}
|
|
745
804
|
}
|
|
746
805
|
}
|
|
747
|
-
function install_signal_handlers(bus, adapters) {
|
|
806
|
+
function install_signal_handlers(agent, bus, adapters) {
|
|
748
807
|
const shutdown = () => {
|
|
749
808
|
for (const adapter of adapters) {
|
|
750
809
|
void adapter.stop().catch((error) => logger.warn(`gateway adapter stop failed: ${adapter.name}`, error));
|
|
751
810
|
}
|
|
752
811
|
bus.stop();
|
|
812
|
+
agent.close();
|
|
753
813
|
process.exit(0);
|
|
754
814
|
};
|
|
755
815
|
process.once("SIGINT", shutdown);
|
|
@@ -758,4 +818,4 @@ function install_signal_handlers(bus, adapters) {
|
|
|
758
818
|
export {
|
|
759
819
|
run_gateway
|
|
760
820
|
};
|
|
761
|
-
//# sourceMappingURL=gateway-
|
|
821
|
+
//# sourceMappingURL=gateway-WU5G4ODO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gateway/types.ts","../src/gateway/bus.ts","../src/gateway/discord.ts","../src/gateway/telegram.ts","../src/gateway/twitch.ts","../src/gateway/webhook.ts","../src/gateway/format.ts","../src/gateway/runner.ts"],"sourcesContent":["/**\n * Shared contracts for the messaging gateway: platform message shapes,\n * reply sinks, adapter lifecycle, and the small runtime helpers every\n * platform adapter shares (idle fallback, raw WebSocket, inbound dispatch).\n */\nimport type { Agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport { logger } from \"../util/log.js\";\n\nexport type PlatformName = \"webhook\" | \"telegram\" | \"discord\" | \"twitch\";\n\n/** Normalized inbound message from any supported platform. */\nexport interface PlatformMessage {\n platform: PlatformName;\n chat_id: string;\n user_id: string;\n text: string;\n}\n\n/** Outbound-only view used to deliver a reply to a conversation. */\nexport interface ReplySink {\n send(text: string): Promise<void>;\n}\n\n/** Lifecycle contract implemented by every platform adapter. */\nexport interface PlatformAdapter {\n readonly name: string;\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n/** Handles one inbound message and resolves to the reply text. */\nexport type InboundHandler = (\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n) => Promise<string | undefined>;\n\n/** Dependencies handed to every adapter factory. */\nexport interface AdapterParams {\n config: AgentConfig;\n handle_message: InboundHandler;\n get_agent: () => Agent;\n reply_router: (platform: string, chat_id: string) => ReplySink | undefined;\n}\n\n/** Minimal structural WebSocket surface shared by bun and node runtimes. */\nexport interface RawSocket {\n onopen: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: (() => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(): void;\n}\n\nconst ERROR_SNIPPET_CHARS = 300;\n\n/** Flattens a failure into a single-line, bounded, safe reply string. */\nexport function sanitize_agent_error(error: unknown): string {\n const raw = error instanceof Error ? error.message : String(error);\n const flat = raw.replace(/\\s+/g, \" \").trim();\n return `agent error: ${flat.slice(0, ERROR_SNIPPET_CHARS) || \"unknown\"}`;\n}\n\n/** Adapter that logs why it is idle once and otherwise does nothing. */\nexport function create_idle_adapter(name: string, reason: string): PlatformAdapter {\n logger.warn(`gateway ${name} adapter idle: ${reason}`);\n return {\n name,\n start: async () => undefined,\n stop: async () => undefined,\n };\n}\n\n/** Opens a WebSocket and resolves once connected; rejects on open error. */\nexport function open_socket(url: string): Promise<RawSocket> {\n return new Promise((resolve, reject) => {\n const ctor = (globalThis as { WebSocket?: new (url: string) => unknown }).WebSocket;\n if (ctor === undefined) {\n reject(new Error(\"runtime does not expose a WebSocket constructor\"));\n return;\n }\n const socket = new ctor(url) as unknown as RawSocket;\n socket.onopen = () => resolve(socket);\n socket.onerror = () => reject(new Error(`websocket connect failed: ${url}`));\n });\n}\n\n/** Runs one inbound message, converting failures into a safe reply string. */\nexport async function run_inbound_message(\n handle: InboundHandler,\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n): Promise<string> {\n try {\n return (await handle(platform, chat_id, user_id, text)) ?? \"\";\n } catch (error) {\n logger.error(`gateway ${platform} message handling failed`, error);\n return sanitize_agent_error(error);\n }\n}","/**\n * GatewayBus: conversation-keyed runner over one shared Agent.\n *\n * Per-conversation history lives in a bounded Map; concurrent messages for\n * the same conversation are serialized through a promise chain so history\n * never interleaves. Agent failures become sanitized reply strings.\n */\nimport type { Agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport type { Message } from \"../providers/types.js\";\nimport { logger } from \"../util/log.js\";\nimport { check_gateway_sender } from \"./access.js\";\nimport { sanitize_agent_error } from \"./types.js\";\n\nexport interface GatewayBusOptions {\n history_cap?: number;\n max_conversations?: number;\n}\n\nexport interface BusParams {\n config: AgentConfig;\n agent_factory: () => Agent;\n /** Subscribe to agent tool events for debug logging (creates the agent). */\n wire_tool_logging?: boolean;\n}\n\nconst DEFAULT_HISTORY_CAP = 40;\nconst DEFAULT_MAX_CONVERSATIONS = 200;\n\nexport class GatewayBus {\n private readonly config: AgentConfig;\n private readonly agent_factory: () => Agent;\n private agent: Agent | undefined;\n private readonly histories: Map<string, Message[]> = new Map();\n private readonly chains: Map<string, Promise<void>> = new Map();\n private readonly history_cap: number;\n private readonly max_conversations: number;\n private stop_logging: (() => void) | undefined;\n\n constructor(params: BusParams, options?: GatewayBusOptions) {\n this.config = params.config;\n this.agent_factory = params.agent_factory;\n this.history_cap = options?.history_cap ?? DEFAULT_HISTORY_CAP;\n this.max_conversations = options?.max_conversations ?? DEFAULT_MAX_CONVERSATIONS;\n if (params.wire_tool_logging === true) {\n this.wire_tool_logging();\n }\n }\n\n /** Serializes runs per conversation and resolves to the reply text. */\n async handle(platform: string, chat_id: string, user_id: string, text: string): Promise<string | undefined> {\n if (check_gateway_sender(this.config, platform, chat_id, user_id) === false) {\n return undefined;\n }\n const key = conversation_key(platform, chat_id);\n const previous = this.chains.get(key) ?? Promise.resolve();\n const run = previous.then(() => this.run_once(key, platform, chat_id, user_id, text));\n this.chains.set(\n key,\n run.then(\n () => undefined,\n () => undefined,\n ),\n );\n return run;\n }\n\n /** Unsubscribes the debug tool logger (bus owns no other resources). */\n stop(): void {\n this.stop_logging?.();\n this.stop_logging = undefined;\n }\n\n private async run_once(\n key: string,\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n ): Promise<string | undefined> {\n const input = text.startsWith(\"/start\") === true ? \"hello\" : text;\n const history = this.history_for(key);\n const agent = this.ensure_agent();\n try {\n const result = await agent.run({ input, history, label: `gw:${platform}:${chat_id}` });\n this.histories.set(key, cap_history(result.messages, this.history_cap));\n return final_reply_text(result.outcome.final?.content);\n } catch (error) {\n logger.error(`gateway bus run failed for ${key} (user ${user_id})`, error);\n return sanitize_agent_error(error);\n }\n }\n\n private ensure_agent(): Agent {\n if (this.agent === undefined) {\n this.agent = this.agent_factory();\n }\n return this.agent;\n }\n\n /** Oldest-first eviction keeps the conversation map bounded. */\n private history_for(key: string): Message[] {\n while (this.histories.size >= this.max_conversations && this.histories.has(key) === false) {\n const oldest = this.histories.keys().next();\n if (oldest.done === true) {\n break;\n }\n this.histories.delete(oldest.value);\n }\n return this.histories.get(key) ?? [];\n }\n\n /** Logs completed tool calls at debug level for gateway observability. */\n private wire_tool_logging(): void {\n const agent = this.agent_factory();\n this.agent = agent;\n this.stop_logging = agent.events.on((event) => {\n if (event.type === \"tool_call_end\") {\n logger.debug(`tool ${event.call.name} ${event.result.ok === true ? \"ok\" : \"failed\"}`);\n }\n });\n }\n}\n\nfunction conversation_key(platform: string, chat_id: string): string {\n return `${platform}:${chat_id}`;\n}\n\nfunction cap_history(messages: Message[], cap: number): Message[] {\n const overflow = messages.length - cap;\n if (overflow <= 0) {\n return messages;\n }\n let sliced = messages.slice(overflow);\n while (sliced.length > 0 && sliced[0]?.role !== \"user\") {\n sliced = sliced.slice(1);\n }\n return sliced;\n}\n\nfunction final_reply_text(content: string | undefined): string | undefined {\n return content === undefined || content.length === 0 ? undefined : content;\n}","/**\n * Discord adapter: gateway WebSocket (op-code switch, heartbeat, fresh\n * reconnect) plus REST replies. Token optional; absent token degrades to\n * an idle adapter. No resume support — reconnects are fresh by design.\n */\nimport { logger } from \"../util/log.js\";\nimport { platform_token_env, read_platform_token } from \"./token_env.js\";\nimport type { AdapterParams, PlatformAdapter, RawSocket } from \"./types.js\";\nimport { create_idle_adapter, open_socket, run_inbound_message } from \"./types.js\";\n\nconst DISCORD_API = \"https://discord.com/api/v10\";\nconst GATEWAY_URL = \"wss://gateway.discord.gg/?v=10&encoding=json\";\nconst INTENTS = 512 | 32768;\n/** Live heartbeat timers keyed by socket, cleared when the session ends. */\nconst heartbeat_timers = new WeakMap<RawSocket, ReturnType<typeof setInterval>>();\n\ntype DiscordPayload = {\n op?: number;\n t?: string;\n s?: number | null;\n d?: Record<string, unknown>;\n};\n\ninterface DiscordMessageData {\n content: string;\n channel_id: string;\n author_id: string;\n}\n\nexport function create_discord_adapter(params: AdapterParams): PlatformAdapter {\n const token = read_platform_token(params.config, \"discord\");\n if (token === undefined) {\n return create_idle_adapter(\"discord\", `${platform_token_env(params.config, \"discord\")} not set`);\n }\n let running = false;\n let socket: RawSocket | undefined;\n return {\n name: \"discord\",\n start: async () => {\n running = true;\n void connect_loop(params, token, () => running, (opened) => {\n socket = opened;\n });\n },\n stop: async () => {\n running = false;\n socket?.close();\n socket = undefined;\n },\n };\n}\n\nasync function connect_loop(\n params: AdapterParams,\n token: string,\n keep_running: () => boolean,\n set_socket: (socket: RawSocket) => void,\n): Promise<void> {\n while (keep_running()) {\n try {\n const socket = await open_socket(GATEWAY_URL);\n set_socket(socket);\n await socket_session(socket, token, params);\n } catch (error) {\n logger.warn(\"gateway discord connection failed; reconnecting in 5s\", error);\n await new Promise((resolve) => setTimeout(resolve, 5000));\n }\n }\n}\n\nasync function socket_session(socket: RawSocket, token: string, params: AdapterParams): Promise<void> {\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n const done = new Promise<void>((resolve) => {\n socket.onclose = () => resolve();\n });\n socket.onmessage = (event) => {\n const payload = parse_payload(event.data);\n if (payload === undefined) {\n return;\n }\n handle_discord_payload(socket, token, params, payload);\n };\n await done;\n heartbeat = heartbeat_timers.get(socket);\n if (heartbeat !== undefined) {\n clearInterval(heartbeat);\n heartbeat_timers.delete(socket);\n }\n}\n\nfunction handle_discord_payload(\n socket: RawSocket,\n token: string,\n params: AdapterParams,\n payload: DiscordPayload,\n): void {\n if (payload.op === 10 && is_hello(payload.d)) {\n socket.send(JSON.stringify({ op: 2, d: identify_body(token) }));\n schedule_heartbeat(socket, payload.d.heartbeat_interval);\n return;\n }\n if (payload.t === \"MESSAGE_CREATE\") {\n void on_message_create(params, payload.d);\n }\n}\n\nfunction schedule_heartbeat(\n socket: RawSocket,\n interval_ms: unknown,\n): void {\n const interval = typeof interval_ms === \"number\" ? interval_ms : 45000;\n const timer = setInterval(() => {\n socket.send(JSON.stringify({ op: 1, d: null }));\n }, Math.max(1000, interval - 1000));\n heartbeat_timers.set(socket, timer);\n}\n\nfunction identify_body(token: string): Record<string, unknown> {\n return { token, intents: INTENTS, properties: { os: \"linux\", browser: \"lich\", device: \"lich\" } };\n}\n\nasync function on_message_create(params: AdapterParams, data: Record<string, unknown> | undefined): Promise<void> {\n try {\n const message = normalize_message(data);\n if (message === undefined) {\n return;\n }\n const reply = await run_inbound_message(\n params.handle_message,\n \"discord\",\n message.channel_id,\n message.author_id,\n message.content,\n );\n if (reply.length === 0) {\n return;\n }\n await rest_send_message(params, message.channel_id, reply);\n } catch (error) {\n logger.warn(\"gateway discord deliver failed\", error);\n }\n}\n\nasync function rest_send_message(params: AdapterParams, channel_id: string, text: string): Promise<void> {\n const token = read_platform_token(params.config, \"discord\");\n if (token === undefined || channel_id === \"\") {\n return;\n }\n for (const chunk of split_chunks(text, 2000)) {\n const response = await fetch(`${DISCORD_API}/channels/${channel_id}/messages`, {\n method: \"POST\",\n headers: { authorization: `Bot ${token}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ content: chunk }),\n signal: AbortSignal.timeout(30_000),\n });\n if (response.ok === false) {\n logger.warn(`gateway discord sendMessage failed with http ${response.status}`);\n }\n }\n}\n\nfunction normalize_message(data: Record<string, unknown> | undefined): DiscordMessageData | undefined {\n if (data === undefined) {\n return undefined;\n }\n const author = data.author;\n if (typeof author !== \"object\" || author === null) {\n return undefined;\n }\n const info = author as { id?: unknown; bot?: unknown };\n if (info.bot === true) {\n return undefined;\n }\n const channel_id = typeof data.channel_id === \"string\" ? data.channel_id : \"\";\n const content = strip_mention(typeof data.content === \"string\" ? data.content : \"\");\n return { channel_id, content, author_id: typeof info.id === \"string\" ? info.id : \"\" };\n}\n\nfunction strip_mention(content: string): string {\n const bot_id = process.env.LICH_DISCORD_BOT_ID;\n if (bot_id === undefined) {\n return content.trim();\n }\n const mention = `<@${bot_id}>`;\n return content.startsWith(mention) === true ? content.slice(mention.length).trim() : content.trim();\n}\n\nfunction is_hello(d: Record<string, unknown> | undefined): d is { heartbeat_interval: number } {\n return d !== undefined && typeof d.heartbeat_interval === \"number\";\n}\n\nfunction parse_payload(data: unknown): DiscordPayload | undefined {\n if (typeof data !== \"string\") {\n return undefined;\n }\n try {\n return JSON.parse(data) as DiscordPayload;\n } catch {\n return undefined;\n }\n}\n\nfunction split_chunks(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n chunks.push(rest.slice(0, limit));\n rest = rest.slice(limit);\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Telegram adapter: long-poll getUpdates over fetch and sendMessage\n * replies with 4096-char splitting. Missing token degrades to an idle\n * adapter instead of failing the gateway.\n */\nimport { sleep } from \"../util/sleep.js\";\nimport { logger } from \"../util/log.js\";\nimport { platform_token_env, read_platform_token } from \"./token_env.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\nimport { create_idle_adapter, run_inbound_message } from \"./types.js\";\n\nexport const TELEGRAM_MAX_MESSAGE_CHARS = 4096;\n\ninterface TelegramUpdate {\n update_id?: number;\n message?: {\n text?: string;\n chat?: { id?: number | string };\n from?: { id?: number | string; is_bot?: boolean };\n };\n}\n\nexport const TELEGRAM_BACKOFF_MS = [2000, 4000, 8000, 16000, 30000] as const;\n\nexport function create_telegram_adapter(params: AdapterParams): PlatformAdapter {\n const token = read_platform_token(params.config, \"telegram\");\n if (token === undefined) {\n return create_idle_adapter(\"telegram\", `${platform_token_env(params.config, \"telegram\")} not set`);\n }\n let running = false;\n return {\n name: \"telegram\",\n start: async () => {\n running = true;\n void poll_loop(params, token, () => running);\n },\n stop: async () => {\n running = false;\n },\n };\n}\n\nasync function poll_loop(params: AdapterParams, token: string, keep_running: () => boolean): Promise<void> {\n let offset = 0;\n let backoff_index = 0;\n while (keep_running()) {\n try {\n const updates = await fetch_updates(token, offset);\n backoff_index = 0;\n for (const update of updates) {\n offset = update.update_id !== undefined ? update.update_id + 1 : offset;\n void deliver_update(params, token, update);\n }\n } catch (error) {\n logger.warn(\"gateway telegram poll failed; backing off\", error);\n await sleep(TELEGRAM_BACKOFF_MS[backoff_index] ?? 30000);\n backoff_index = Math.min(backoff_index + 1, TELEGRAM_BACKOFF_MS.length - 1);\n }\n }\n}\n\nasync function fetch_updates(token: string, offset: number): Promise<TelegramUpdate[]> {\n const url = api_url(token, \"getUpdates\") + `?timeout=50&offset=${offset}`;\n const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });\n if (response.ok === false) {\n throw new Error(`getUpdates http ${response.status}`);\n }\n const body = (await response.json()) as { result?: TelegramUpdate[] };\n return Array.isArray(body.result) === true ? body.result : [];\n}\n\nasync function deliver_update(params: AdapterParams, token: string, update: TelegramUpdate): Promise<void> {\n try {\n const message = update.message;\n if (message === undefined || message.from?.is_bot === true) {\n return;\n }\n const chat_id = message.chat?.id;\n if (chat_id === undefined) {\n return;\n }\n const text = message.text ?? \"media not supported yet\";\n const reply = await run_inbound_message(\n params.handle_message,\n \"telegram\",\n String(chat_id),\n String(message.from?.id ?? \"unknown\"),\n text,\n );\n if (reply.length === 0) {\n return;\n }\n await send_reply(token, String(chat_id), reply);\n } catch (error) {\n logger.warn(\"gateway telegram deliver failed\", error);\n }\n}\n\nasync function send_reply(token: string, chat_id: string, text: string): Promise<void> {\n for (const chunk of split_text(text, TELEGRAM_MAX_MESSAGE_CHARS)) {\n await post_json(api_url(token, \"sendMessage\"), { chat_id, text: chunk });\n }\n}\n\nasync function post_json(url: string, payload: Record<string, unknown>): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(30_000),\n });\n if (response.ok === false) {\n logger.warn(`gateway telegram sendMessage failed with http ${response.status}`);\n }\n}\n\nfunction api_url(token: string, method: string): string {\n return `https://api.telegram.org/bot${token}/${method}`;\n}\n\n/** Splits at the last newline/space inside each window; hard cut fallback. */\nexport function split_text(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n const window = rest.slice(0, limit + 1);\n const newline = window.lastIndexOf(\"\\n\");\n const space = window.lastIndexOf(\" \");\n const cut = newline > 0 ? newline : space > 0 ? space : limit;\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Twitch adapter: IRC-over-WebSocket on irc-ws.chat.twitch.tv. Handles\n * CAP/JOIN setup, tag-prefixed PRIVMSG parsing, PING/PONG, and 512-char\n * message splitting. Absent config degrades to an idle adapter.\n */\nimport type { AgentConfig } from \"../agent/config.js\";\nimport { logger } from \"../util/log.js\";\nimport { platform_token_env, read_platform_token } from \"./token_env.js\";\nimport type { AdapterParams, PlatformAdapter, RawSocket } from \"./types.js\";\nimport { create_idle_adapter, open_socket, run_inbound_message } from \"./types.js\";\n\nexport const TWITCH_IRC_URL = \"wss://irc-ws.chat.twitch.tv:443\";\nexport const TWITCH_MESSAGE_CAP = 512;\n\ninterface TwitchConfig {\n token: string;\n nick: string;\n channels: string[];\n}\n\ninterface ParsedLine {\n kind: \"ping\" | \"privmsg\" | \"other\";\n channel: string;\n user: string;\n text: string;\n}\n\nexport function create_twitch_adapter(params: AdapterParams): PlatformAdapter {\n const twitch = read_twitch_env(params.config);\n if (twitch === undefined) {\n const token_env = platform_token_env(params.config, \"twitch\");\n const reason = token_env === \"LICH_TWITCH_OAUTH_TOKEN\" ? \"LICH_TWITCH_OAUTH_TOKEN / NICK not set\" : `${token_env} / LICH_TWITCH_NICK not set`;\n return create_idle_adapter(\"twitch\", reason);\n }\n let running = false;\n let socket: RawSocket | undefined;\n return {\n name: \"twitch\",\n start: async () => {\n running = true;\n void irc_loop(params, twitch, () => running, (opened) => {\n socket = opened;\n });\n },\n stop: async () => {\n running = false;\n socket?.close();\n socket = undefined;\n },\n };\n}\n\nfunction read_twitch_env(config: AgentConfig): TwitchConfig | undefined {\n const raw_token = read_platform_token(config, \"twitch\");\n const nick = process.env.LICH_TWITCH_NICK;\n const channels_env = process.env.LICH_TWITCH_CHANNELS;\n if (raw_token === undefined || raw_token.length === 0 || nick === undefined || nick.length === 0) {\n return undefined;\n }\n const token = raw_token.startsWith(\"oauth:\") === true ? raw_token : `oauth:${raw_token}`;\n const channels = (channels_env ?? \"\")\n .split(\",\")\n .map((channel) => channel.trim().toLowerCase())\n .filter((channel) => channel.length > 0);\n if (channels.length === 0) {\n return undefined;\n }\n return { token, nick, channels };\n}\n\nasync function irc_loop(\n params: AdapterParams,\n twitch: TwitchConfig,\n keep_running: () => boolean,\n set_socket: (socket: RawSocket) => void,\n): Promise<void> {\n while (keep_running()) {\n try {\n const socket = await open_socket(TWITCH_IRC_URL);\n set_socket(socket);\n await irc_session(params, twitch, socket, keep_running);\n } catch (error) {\n logger.warn(\"gateway twitch connection failed; reconnecting in 5s\", error);\n await new Promise((resolve) => setTimeout(resolve, 5000));\n }\n }\n}\n\nasync function irc_session(\n params: AdapterParams,\n twitch: TwitchConfig,\n socket: RawSocket,\n keep_running: () => boolean,\n): Promise<void> {\n const closed = new Promise<void>((resolve) => {\n socket.onclose = () => resolve();\n });\n socket.onmessage = (event) => {\n for (const line of String(event.data).split(\"\\r\\n\")) {\n void handle_irc_line(params, twitch, socket, line);\n }\n };\n socket.send(\"CAP REQ :twitch.tv/tags twitch.tv/commands\");\n socket.send(`PASS ${twitch.token}`);\n socket.send(`NICK ${twitch.nick}`);\n for (const channel of twitch.channels) {\n socket.send(`JOIN #${channel}`);\n }\n await closed;\n}\n\nasync function handle_irc_line(\n params: AdapterParams,\n twitch: TwitchConfig,\n socket: RawSocket,\n line: string,\n): Promise<void> {\n if (line.length === 0) {\n return;\n }\n const parsed = parse_irc_line(line);\n if (parsed.kind === \"ping\") {\n socket.send(\"PONG :tmi.twitch.tv\");\n return;\n }\n if (parsed.kind !== \"privmsg\" || parsed.user === twitch.nick) {\n return;\n }\n const reply = await run_inbound_message(\n params.handle_message,\n \"twitch\",\n parsed.channel,\n parsed.user,\n parsed.text,\n );\n send_twitch_message(socket, parsed.channel, reply);\n}\n\nfunction send_twitch_message(socket: RawSocket, channel: string, text: string): void {\n for (const chunk of split_chunks(text, TWITCH_MESSAGE_CAP)) {\n socket.send(`PRIVMSG #${channel} :${chunk}`);\n }\n}\n\nexport function parse_irc_line(line: string): ParsedLine {\n if (line.startsWith(\"PING\") === true) {\n return { kind: \"ping\", channel: \"\", user: \"\", text: \"\" };\n }\n const privmsg = match_privmsg(line);\n if (privmsg === undefined) {\n return { kind: \"other\", channel: \"\", user: \"\", text: \"\" };\n }\n return privmsg;\n}\n\nfunction match_privmsg(line: string): ParsedLine | undefined {\n const without_tags = line.startsWith(\"@\") === true ? line.slice(line.indexOf(\" \") + 1) : line;\n const matched = /^:(\\w+)!\\S+ PRIVMSG #(\\w+) :(.*)$/.exec(without_tags);\n if (matched === null) {\n return undefined;\n }\n return {\n kind: \"privmsg\",\n user: matched[1] ?? \"\",\n channel: matched[2] ?? \"\",\n text: matched[3] ?? \"\",\n };\n}\n\nfunction split_chunks(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n chunks.push(rest.slice(0, limit));\n rest = rest.slice(limit);\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Webhook adapter: the default zero-config platform. A plain node:http\n * server exposing POST /message (+ GET /health), optionally guarded by a\n * shared token via the x-lich-token header. Binds loopback by default.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { logger } from \"../util/log.js\";\nimport { format_agent_reply } from \"./format.js\";\nimport { read_platform_token } from \"./token_env.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\n\nexport const DEFAULT_GATEWAY_PORT = 8089;\nexport const DEFAULT_GATEWAY_HOST = \"127.0.0.1\";\n\ninterface WebhookAdapterParams extends AdapterParams {\n port?: number;\n host?: string;\n /** Test hook: reports the resolved bound port once listening. */\n on_listening?: (port: number) => void;\n}\n\ninterface IncomingPayload {\n chat_id?: unknown;\n user_id?: unknown;\n text?: unknown;\n}\n\nexport function create_webhook_adapter(params: WebhookAdapterParams): PlatformAdapter {\n const port = params.port ?? read_port_env() ?? DEFAULT_GATEWAY_PORT;\n const host = params.host ?? read_host_env() ?? DEFAULT_GATEWAY_HOST;\n const token = read_platform_token(params.config, \"webhook\");\n let server: Server | undefined;\n\n return {\n name: \"webhook\",\n start: async () => {\n assert_bind_allowed(host, token);\n server = createServer((request, response) => {\n void dispatch_webhook(params, request, response, token);\n });\n await listen_on(server, port, host, (bound) => {\n params.on_listening?.(bound);\n });\n },\n stop: async () => {\n await close_server(server);\n server = undefined;\n },\n };\n}\n\nasync function dispatch_webhook(\n params: AdapterParams,\n request: IncomingMessage,\n response: ServerResponse,\n token: string | undefined,\n): Promise<void> {\n try {\n if (request.method === \"GET\" && request.url === \"/health\") {\n send_json(response, 200, { status: \"ok\" });\n return;\n }\n if (request.method !== \"POST\" || request.url !== \"/message\") {\n send_json(response, 404, { error: \"not found\" });\n return;\n }\n if (token !== undefined && request.headers[\"x-lich-token\"] !== token) {\n send_json(response, 401, { error: \"unauthorized\" });\n return;\n }\n await handle_message_post(params, request, response);\n } catch (error) {\n logger.error(\"gateway webhook request failed\", error);\n if (response.headersSent === false) {\n send_json(response, 500, { error: \"internal error\" });\n }\n }\n}\n\nasync function handle_message_post(\n params: AdapterParams,\n request: IncomingMessage,\n response: ServerResponse,\n): Promise<void> {\n const body = await read_body(request);\n const payload = parse_payload(body);\n const text = payload.text;\n if (text === undefined) {\n send_json(response, 400, { error: \"text is required\" });\n return;\n }\n // G-5: callers must not choose another platform's conversation key.\n const platform = \"webhook\";\n const chat_id = payload.chat_id ?? \"default\";\n const user_id = payload.user_id ?? \"anonymous\";\n const reply = await params.handle_message(platform, String(chat_id), String(user_id), String(text));\n respond_json_text(response, 200, format_agent_reply(reply ?? \"\", undefined, \"webhook\"));\n}\n\nfunction read_body(request: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = \"\";\n request.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf8\");\n });\n request.on(\"end\", () => resolve(body));\n request.on(\"error\", reject);\n });\n}\n\nfunction parse_payload(body: string): IncomingPayload {\n if (body.trim().length === 0) {\n return {};\n }\n try {\n const parsed = JSON.parse(body) as unknown;\n if (typeof parsed !== \"object\" || parsed === null) {\n return {};\n }\n return parsed as IncomingPayload;\n } catch {\n return {};\n }\n}\n\nfunction send_json(response: ServerResponse, status: number, payload: Record<string, unknown>): void {\n respond_json_text(response, status, JSON.stringify(payload));\n}\n\nfunction respond_json_text(response: ServerResponse, status: number, json_text: string): void {\n response.statusCode = status;\n response.setHeader(\"content-type\", \"application/json\");\n response.end(json_text);\n}\n\nfunction listen_on(\n server: Server,\n port: number,\n host: string,\n on_listening: (port: number) => void,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n const bound = (server.address() as { port?: number } | null)?.port ?? port;\n logger.info(`gateway webhook listening on ${host}:${bound}`);\n on_listening(bound);\n resolve();\n });\n });\n}\n\nasync function close_server(server: Server | undefined): Promise<void> {\n if (server === undefined) {\n return;\n }\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n}\n\n/** Loopback binds are always fine; non-loopback requires an auth token. */\nexport function assert_bind_allowed(host: string, token: string | undefined): void {\n if (is_loopback_host(host) === true) {\n return;\n }\n if (token === undefined || token.length === 0) {\n throw new Error(\n `gateway webhook refuses non-loopback bind (${host}) without a token — set LICH_GATEWAY_TOKEN or bind 127.0.0.1`,\n );\n }\n logger.warn(\n `gateway webhook binding ${host} with token auth — this exposes the agent beyond loopback; keep the token secret`,\n );\n}\n\nexport function is_loopback_host(host: string): boolean {\n const normalized = host.trim().toLowerCase();\n return normalized === \"127.0.0.1\" || normalized === \"::1\" || normalized === \"localhost\";\n}\n\nfunction read_port_env(): number | undefined {\n const raw = process.env.LICH_GATEWAY_PORT;\n if (raw === undefined || raw.length === 0) {\n return undefined;\n }\n const port = Number(raw);\n return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;\n}\n\nfunction read_host_env(): string | undefined {\n const raw = process.env.LICH_GATEWAY_HOST;\n if (raw === undefined || raw.trim().length === 0) {\n return undefined;\n }\n return raw.trim();\n}\n","/**\n * Gateway reply formatting: per-platform shaping and whitespace-aware\n * splitting for size-capped transports (telegram 4096, discord 2000, …).\n */\nimport type { Usage } from \"../providers/types.js\";\n\nconst USAGE_FOOTER_PREFIX = \"\\n\\n_tokens: \";\n\nexport function format_agent_reply(text: string, usage: Usage | undefined, platform: string): string {\n if (platform === \"webhook\") {\n return JSON.stringify({ reply: text, usage: usage ?? null });\n }\n const total_tokens = usage?.total_tokens ?? 0;\n if (total_tokens > 0) {\n return `${text}${USAGE_FOOTER_PREFIX}${total_tokens}_`;\n }\n return text;\n}\n\n/** Splits text into chunks of at most `limit` chars, preferring whitespace. */\nexport function split_text(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n const cut = find_split_point(rest, limit);\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}\n\n/** Last newline or space inside the window; falls back to a hard cut. */\nfunction find_split_point(text: string, limit: number): number {\n const window = text.slice(0, limit + 1);\n const newline = window.lastIndexOf(\"\\n\");\n if (newline > 0) {\n return newline;\n }\n const space = window.lastIndexOf(\" \");\n if (space > 0) {\n return space;\n }\n return limit;\n}","/**\n * Gateway runner: wires the shared Agent, the conversation bus, and the\n * requested platform adapters, then stays alive until a signal arrives.\n */\nimport { create_agent_with_plugins, type Agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport { logger } from \"../util/log.js\";\nimport { gateway_tools_enabled } from \"./access.js\";\nimport { GatewayBus } from \"./bus.js\";\nimport { create_discord_adapter } from \"./discord.js\";\nimport { create_telegram_adapter } from \"./telegram.js\";\nimport { create_twitch_adapter } from \"./twitch.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\nimport { create_webhook_adapter } from \"./webhook.js\";\n\n/** Preload plugins, then hand that same agent to the bus factory. */\nexport async function create_gateway_bus(config: AgentConfig): Promise<{ agent: Agent; bus: GatewayBus }> {\n const agent_config = { ...config, tools_enabled: gateway_tools_enabled(config) };\n const agent = await create_agent_with_plugins(agent_config);\n const bus = new GatewayBus({ config, agent_factory: () => agent, wire_tool_logging: true });\n return { agent, bus };\n}\n\nexport async function run_gateway(config: AgentConfig, platforms: readonly string[]): Promise<number> {\n const valid: string[] = [];\n for (const platform of platforms) {\n if (is_known_platform(platform) === true) {\n valid.push(platform);\n } else {\n process.stderr.write(`lich: unknown gateway platform \"${platform}\" (skipping)\\n`);\n }\n }\n if (valid.length === 0) {\n process.stderr.write(\"lich: gateway needs at least one valid platform (webhook|telegram|discord|twitch)\\n\");\n return 1;\n }\n const { agent, bus } = await create_gateway_bus(config);\n const adapters = build_adapters(config, bus, valid);\n install_signal_handlers(agent, bus, adapters);\n logger.info(`gateway starting: platforms=${valid.join(\",\")}, port=${process.env.LICH_GATEWAY_PORT ?? \"8089\"}, pid=${process.pid}`);\n await start_all_adapters(adapters);\n return await new Promise<number>(() => undefined);\n}\n\nfunction is_known_platform(platform: string): boolean {\n return platform === \"webhook\" || platform === \"telegram\" || platform === \"discord\" || platform === \"twitch\";\n}\n\nfunction build_adapters(config: AgentConfig, bus: GatewayBus, platforms: readonly string[]): PlatformAdapter[] {\n const params: AdapterParams = {\n config,\n handle_message: (platform, chat_id, user_id, text) => bus.handle(platform, chat_id, user_id, text),\n get_agent: () => {\n throw new Error(\"get_agent is reserved for future use\");\n },\n reply_router: () => undefined,\n };\n const adapters: PlatformAdapter[] = [];\n for (const platform of platforms) {\n const adapter = create_platform_adapter(params, platform);\n if (adapter !== undefined) {\n adapters.push(adapter);\n }\n }\n return adapters;\n}\n\nfunction create_platform_adapter(params: AdapterParams, platform: string): PlatformAdapter | undefined {\n switch (platform) {\n case \"webhook\":\n return create_webhook_adapter({ ...params, port: read_webhook_port(), host: read_webhook_host() });\n case \"telegram\":\n return create_telegram_adapter(params);\n case \"discord\":\n return create_discord_adapter(params);\n case \"twitch\":\n return create_twitch_adapter(params);\n default:\n return undefined;\n }\n}\n\nfunction read_webhook_port(): number | undefined {\n const raw = process.env.LICH_GATEWAY_PORT;\n if (raw === undefined || raw.length === 0) {\n return undefined;\n }\n const port = Number(raw);\n return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;\n}\n\nfunction read_webhook_host(): string | undefined {\n const raw = process.env.LICH_GATEWAY_HOST;\n if (raw === undefined || raw.trim().length === 0) {\n return undefined;\n }\n return raw.trim();\n}\n\n/** Adapter start failures log and are skipped; the rest keep running. */\nasync function start_all_adapters(adapters: readonly PlatformAdapter[]): Promise<void> {\n for (const adapter of adapters) {\n try {\n await adapter.start();\n logger.info(`gateway adapter started: ${adapter.name}`);\n } catch (error) {\n logger.error(`gateway adapter failed to start: ${adapter.name}`, error);\n }\n }\n}\n\nfunction install_signal_handlers(agent: Agent, bus: GatewayBus, adapters: readonly PlatformAdapter[]): void {\n const shutdown = (): void => {\n for (const adapter of adapters) {\n void adapter\n .stop()\n .catch((error: unknown) => logger.warn(`gateway adapter stop failed: ${adapter.name}`, error));\n }\n bus.stop();\n agent.close();\n process.exit(0);\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n}"],"mappings":"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","names":["split_chunks","parse_payload"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -160,12 +160,24 @@ declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
|
160
160
|
platforms: z.ZodDefault<z.ZodArray<z.ZodEnum<["webhook", "telegram", "discord", "twitch"]>, "many">>;
|
|
161
161
|
/** Env-var names that hold tokens. Never store the secrets themselves. */
|
|
162
162
|
token_envs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
163
|
+
/** Per-platform user ids allowed to talk to the bot (default-deny on public platforms). */
|
|
164
|
+
allowed_users: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
165
|
+
/** Per-platform chat/channel ids allowed (default-deny on public platforms). */
|
|
166
|
+
allowed_chats: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
167
|
+
/** Tool allowlist for the gateway agent; defaults to a read-only safe subset. */
|
|
168
|
+
tools_enabled: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodArray<z.ZodString, "many">]>>;
|
|
163
169
|
}, "strip", z.ZodTypeAny, {
|
|
170
|
+
tools_enabled: string[] | "all";
|
|
164
171
|
platforms: ("webhook" | "telegram" | "discord" | "twitch")[];
|
|
165
172
|
token_envs: Record<string, string>;
|
|
173
|
+
allowed_users: Record<string, string[]>;
|
|
174
|
+
allowed_chats: Record<string, string[]>;
|
|
166
175
|
}, {
|
|
176
|
+
tools_enabled?: string[] | "all" | undefined;
|
|
167
177
|
platforms?: ("webhook" | "telegram" | "discord" | "twitch")[] | undefined;
|
|
168
178
|
token_envs?: Record<string, string> | undefined;
|
|
179
|
+
allowed_users?: Record<string, string[]> | undefined;
|
|
180
|
+
allowed_chats?: Record<string, string[]> | undefined;
|
|
169
181
|
}>>;
|
|
170
182
|
log_level: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
|
|
171
183
|
theme: z.ZodDefault<z.ZodString>;
|
|
@@ -236,8 +248,11 @@ declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
|
236
248
|
system_prompt?: string | undefined;
|
|
237
249
|
session_dir?: string | undefined;
|
|
238
250
|
gateway?: {
|
|
251
|
+
tools_enabled: string[] | "all";
|
|
239
252
|
platforms: ("webhook" | "telegram" | "discord" | "twitch")[];
|
|
240
253
|
token_envs: Record<string, string>;
|
|
254
|
+
allowed_users: Record<string, string[]>;
|
|
255
|
+
allowed_chats: Record<string, string[]>;
|
|
241
256
|
} | undefined;
|
|
242
257
|
temperature?: number | undefined;
|
|
243
258
|
max_tokens?: number | undefined;
|
|
@@ -269,8 +284,11 @@ declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
|
269
284
|
log_level?: "debug" | "info" | "warn" | "error" | undefined;
|
|
270
285
|
theme?: string | undefined;
|
|
271
286
|
gateway?: {
|
|
287
|
+
tools_enabled?: string[] | "all" | undefined;
|
|
272
288
|
platforms?: ("webhook" | "telegram" | "discord" | "twitch")[] | undefined;
|
|
273
289
|
token_envs?: Record<string, string> | undefined;
|
|
290
|
+
allowed_users?: Record<string, string[]> | undefined;
|
|
291
|
+
allowed_chats?: Record<string, string[]> | undefined;
|
|
274
292
|
} | undefined;
|
|
275
293
|
agent_name?: string | undefined;
|
|
276
294
|
tools_enabled?: string[] | "all" | undefined;
|
|
@@ -304,8 +322,11 @@ declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
|
304
322
|
plugins: string[];
|
|
305
323
|
system_prompt?: string | undefined;
|
|
306
324
|
gateway?: {
|
|
325
|
+
tools_enabled: string[] | "all";
|
|
307
326
|
platforms: ("webhook" | "telegram" | "discord" | "twitch")[];
|
|
308
327
|
token_envs: Record<string, string>;
|
|
328
|
+
allowed_users: Record<string, string[]>;
|
|
329
|
+
allowed_chats: Record<string, string[]>;
|
|
309
330
|
} | undefined;
|
|
310
331
|
temperature?: number | undefined;
|
|
311
332
|
max_tokens?: number | undefined;
|
|
@@ -337,8 +358,11 @@ declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
|
337
358
|
log_level?: "debug" | "info" | "warn" | "error" | undefined;
|
|
338
359
|
theme?: string | undefined;
|
|
339
360
|
gateway?: {
|
|
361
|
+
tools_enabled?: string[] | "all" | undefined;
|
|
340
362
|
platforms?: ("webhook" | "telegram" | "discord" | "twitch")[] | undefined;
|
|
341
363
|
token_envs?: Record<string, string> | undefined;
|
|
364
|
+
allowed_users?: Record<string, string[]> | undefined;
|
|
365
|
+
allowed_chats?: Record<string, string[]> | undefined;
|
|
342
366
|
} | undefined;
|
|
343
367
|
agent_name?: string | undefined;
|
|
344
368
|
tools_enabled?: string[] | "all" | undefined;
|
|
@@ -496,6 +520,18 @@ declare function load_plugins(entries: readonly string[], base_dir: string): Pro
|
|
|
496
520
|
/** Joined one-liner per load error, for a single warn log. */
|
|
497
521
|
declare function plugin_errors_summary(errors: readonly PluginLoadError[]): string;
|
|
498
522
|
|
|
523
|
+
interface SessionRecord {
|
|
524
|
+
ts: string;
|
|
525
|
+
kind: "message" | "meta";
|
|
526
|
+
message?: Message;
|
|
527
|
+
meta?: Record<string, unknown>;
|
|
528
|
+
}
|
|
529
|
+
interface SessionHandle {
|
|
530
|
+
id: string;
|
|
531
|
+
path: string;
|
|
532
|
+
append(record: SessionRecord): Promise<void>;
|
|
533
|
+
}
|
|
534
|
+
|
|
499
535
|
/**
|
|
500
536
|
* Typed event emitter for the agent loop.
|
|
501
537
|
*
|
|
@@ -523,6 +559,7 @@ interface AgentEvents {
|
|
|
523
559
|
turn: number;
|
|
524
560
|
call: ToolCall;
|
|
525
561
|
result: ToolResult;
|
|
562
|
+
cancelled?: boolean;
|
|
526
563
|
};
|
|
527
564
|
compress_start: {
|
|
528
565
|
estimated_tokens: number;
|
|
@@ -589,6 +626,8 @@ interface AgentRunOptions {
|
|
|
589
626
|
history?: readonly Message[];
|
|
590
627
|
signal?: AbortSignal;
|
|
591
628
|
label?: string;
|
|
629
|
+
/** Shared transcript handle (TUI: one file per launch). When set, Agent reuses it. */
|
|
630
|
+
session?: SessionHandle;
|
|
592
631
|
}
|
|
593
632
|
interface AgentRunResult {
|
|
594
633
|
outcome: LoopOutcome;
|
|
@@ -605,21 +644,25 @@ declare class Agent {
|
|
|
605
644
|
private readonly executor;
|
|
606
645
|
private readonly hook_runner;
|
|
607
646
|
private readonly mcp_runtime;
|
|
608
|
-
private
|
|
647
|
+
private mcp_sessions;
|
|
648
|
+
private mcp_attach;
|
|
609
649
|
constructor(config: AgentConfig, plugins?: readonly LoadedPlugin[], runtime?: {
|
|
610
650
|
mcp?: McpRuntime;
|
|
611
651
|
});
|
|
612
652
|
run(options: AgentRunOptions): Promise<AgentRunResult>;
|
|
653
|
+
/** Close MCP sessions so stdio children do not keep the event loop alive. */
|
|
654
|
+
close(): void;
|
|
613
655
|
/** tools/list once, before the model sees definitions. Empty allowlists never connect. */
|
|
614
656
|
private attach_mcp_once;
|
|
657
|
+
private do_attach_mcp;
|
|
615
658
|
/** Per-run deps: the built-once ToolContext threads through every tool execution. */
|
|
616
659
|
private loop_deps;
|
|
617
660
|
/** Best-effort on_run_start fan-out; hook errors are logged, never fatal. */
|
|
618
661
|
private call_plugin_run_start;
|
|
619
662
|
/** Best-effort on_run_end fan-out; hook errors are logged, never fatal. */
|
|
620
663
|
private call_plugin_run_end;
|
|
621
|
-
/** Best-effort
|
|
622
|
-
private
|
|
664
|
+
/** Best-effort recorder: open failures warn and skip persistence for this run. */
|
|
665
|
+
private open_recorder;
|
|
623
666
|
}
|
|
624
667
|
declare function create_agent(raw_config: unknown): Agent;
|
|
625
668
|
/** create_agent plus plugin loading: broken entries are warned and skipped. */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LICH_VERSION,
|
|
3
3
|
catalog_client_entry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-W6JXZBUE.js";
|
|
5
5
|
import {
|
|
6
6
|
Agent,
|
|
7
7
|
AgentEmitter,
|
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
plugin_errors_summary,
|
|
17
17
|
register_builtin_tools,
|
|
18
18
|
run_agent
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EDRUZF22.js";
|
|
20
|
+
import "./chunk-VKEOHUCB.js";
|
|
20
21
|
export {
|
|
21
22
|
Agent,
|
|
22
23
|
AgentEmitter,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|