@ashx-j/lunr 0.2.24 → 0.2.26
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 +24 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +1 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-computer-use.d.ts +3 -0
- package/dist/builtin-extensions/lunr-computer-use.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-computer-use.js +179 -0
- package/dist/builtin-extensions/lunr-computer-use.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +8 -5
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +11 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +15 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js +5 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +5 -3
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/core/computer-use/adapter.d.ts +28 -0
- package/dist/core/computer-use/adapter.d.ts.map +1 -0
- package/dist/core/computer-use/adapter.js +133 -0
- package/dist/core/computer-use/adapter.js.map +1 -0
- package/dist/core/computer-use/image.d.ts +27 -0
- package/dist/core/computer-use/image.d.ts.map +1 -0
- package/dist/core/computer-use/image.js +72 -0
- package/dist/core/computer-use/image.js.map +1 -0
- package/dist/core/computer-use/lease.d.ts +18 -0
- package/dist/core/computer-use/lease.d.ts.map +1 -0
- package/dist/core/computer-use/lease.js +129 -0
- package/dist/core/computer-use/lease.js.map +1 -0
- package/dist/core/computer-use/macos-runtime.d.ts +14 -0
- package/dist/core/computer-use/macos-runtime.d.ts.map +1 -0
- package/dist/core/computer-use/macos-runtime.js +148 -0
- package/dist/core/computer-use/macos-runtime.js.map +1 -0
- package/dist/core/computer-use/policy.d.ts +19 -0
- package/dist/core/computer-use/policy.d.ts.map +1 -0
- package/dist/core/computer-use/policy.js +67 -0
- package/dist/core/computer-use/policy.js.map +1 -0
- package/dist/core/computer-use/release.generated.d.ts +31 -0
- package/dist/core/computer-use/release.generated.d.ts.map +1 -0
- package/dist/core/computer-use/release.generated.js +36 -0
- package/dist/core/computer-use/release.generated.js.map +1 -0
- package/dist/core/computer-use/runtime.d.ts +13 -0
- package/dist/core/computer-use/runtime.d.ts.map +1 -0
- package/dist/core/computer-use/runtime.js +158 -0
- package/dist/core/computer-use/runtime.js.map +1 -0
- package/dist/core/computer-use/schemas.d.ts +100 -0
- package/dist/core/computer-use/schemas.d.ts.map +1 -0
- package/dist/core/computer-use/schemas.js +79 -0
- package/dist/core/computer-use/schemas.js.map +1 -0
- package/dist/core/computer-use/windows-desktop.d.ts +3 -0
- package/dist/core/computer-use/windows-desktop.d.ts.map +1 -0
- package/dist/core/computer-use/windows-desktop.js +48 -0
- package/dist/core/computer-use/windows-desktop.js.map +1 -0
- package/dist/core/computer-use/workflow.d.ts +38 -0
- package/dist/core/computer-use/workflow.d.ts.map +1 -0
- package/dist/core/computer-use/workflow.js +560 -0
- package/dist/core/computer-use/workflow.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +4 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +14 -3
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +9 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-event-transport.d.ts +3 -0
- package/dist/core/subagent-event-transport.d.ts.map +1 -0
- package/dist/core/subagent-event-transport.js +61 -0
- package/dist/core/subagent-event-transport.js.map +1 -0
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +4 -9
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/adapters/telegram.d.ts.map +1 -1
- package/dist/gateway/adapters/telegram.js +7 -0
- package/dist/gateway/adapters/telegram.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +4 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +57 -17
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +3 -3
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/authz.d.ts +4 -4
- package/dist/gateway/authz.d.ts.map +1 -1
- package/dist/gateway/authz.js +8 -12
- package/dist/gateway/authz.js.map +1 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +29 -19
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/commands.d.ts +2 -0
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +35 -18
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/config.d.ts +1 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +2 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/cron.d.ts +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +22 -8
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +26 -26
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts.map +1 -1
- package/dist/gateway/mobile-commands.js +11 -11
- package/dist/gateway/mobile-commands.js.map +1 -1
- package/dist/gateway/presenter.d.ts +12 -1
- package/dist/gateway/presenter.d.ts.map +1 -1
- package/dist/gateway/presenter.js +148 -50
- package/dist/gateway/presenter.js.map +1 -1
- package/dist/gateway/router.d.ts +5 -5
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +95 -43
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts.map +1 -1
- package/dist/gateway/service.js +13 -4
- package/dist/gateway/service.js.map +1 -1
- package/dist/gateway/setup.d.ts +5 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +101 -43
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/stream.d.ts +6 -5
- package/dist/gateway/stream.d.ts.map +1 -1
- package/dist/gateway/stream.js +24 -11
- package/dist/gateway/stream.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +8 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +9 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/node-runtime/adapter-VFG7WNPX.js +17 -0
- package/dist/node-runtime/{agent-session-services-IEF7LQWD.js → agent-session-services-5YXS2PLF.js} +13 -10
- package/dist/node-runtime/{builtin-extensions-EBAWTH4M.js → builtin-extensions-I2HIFEMY.js} +3 -2
- package/dist/node-runtime/{chunk-K5AGHH57.js → chunk-2CFDTH5B.js} +7 -3
- package/dist/node-runtime/{chunk-VWCE2HSZ.js → chunk-2HMIRK7W.js} +4 -4
- package/dist/node-runtime/chunk-2IDTZAG5.js +1415 -0
- package/dist/node-runtime/{chunk-IGV54Q5V.js → chunk-3MISGG4S.js} +137 -76
- package/dist/node-runtime/chunk-4SBQAI4T.js +2185 -0
- package/dist/node-runtime/{chunk-WHLRAGUQ.js → chunk-5B5QN3ZQ.js} +16 -16
- package/dist/node-runtime/chunk-5B6VKFEV.js +180 -0
- package/dist/node-runtime/{chunk-AD6VXAO7.js → chunk-5Q4A37RB.js} +15 -104
- package/dist/node-runtime/{chunk-4TLO4PO2.js → chunk-7IQGBKBJ.js} +7 -1401
- package/dist/node-runtime/{chunk-R3K2F5GI.js → chunk-B4HP6ZWM.js} +2 -0
- package/dist/node-runtime/{chunk-OCVIXVTZ.js → chunk-D5UCKPBQ.js} +12 -1
- package/dist/node-runtime/chunk-DVQSHCVP.js +97 -0
- package/dist/node-runtime/{chunk-HFVZUPFJ.js → chunk-E75WFZ33.js} +2 -2
- package/dist/node-runtime/chunk-FY4A63RK.js +73 -0
- package/dist/node-runtime/{chunk-RORY2HMJ.js → chunk-I74LN5JG.js} +16 -1
- package/dist/node-runtime/chunk-IKU6Y7DL.js +287 -0
- package/dist/node-runtime/{chunk-QM43FEAE.js → chunk-L24TPBS4.js} +6 -5
- package/dist/node-runtime/{chunk-DE2JB6J5.js → chunk-LJTIVM7I.js} +7 -7
- package/dist/node-runtime/{chunk-DSIKTUPW.js → chunk-LWJXTA6Z.js} +274 -2433
- package/dist/node-runtime/{chunk-YVI6L6LU.js → chunk-P7XBVXGT.js} +36 -21
- package/dist/node-runtime/{chunk-OVX6X377.js → chunk-PTFBPO3C.js} +1 -1
- package/dist/node-runtime/{chunk-F4FLTQKV.js → chunk-RAK2QA2B.js} +186 -1529
- package/dist/node-runtime/{chunk-L3LX4SNR.js → chunk-RCR3XAQY.js} +36 -8
- package/dist/node-runtime/chunk-RGQAEOM5.js +81 -0
- package/dist/node-runtime/{chunk-UWPJL22I.js → chunk-SLVOAQOY.js} +15 -4
- package/dist/node-runtime/chunk-SN2KSMPQ.js +1364 -0
- package/dist/node-runtime/{chunk-FU6OODRZ.js → chunk-SPVDBUOS.js} +1 -1
- package/dist/node-runtime/{chunk-5Q3EHOHI.js → chunk-T4DA4XE7.js} +19 -17
- package/dist/node-runtime/chunk-U5F4IC7R.js +53 -0
- package/dist/node-runtime/{chunk-UP2XBVH5.js → chunk-UOEDI7RP.js} +1 -1
- package/dist/node-runtime/{chunk-Y73WPE6S.js → chunk-UVWCDQYF.js} +79 -44
- package/dist/node-runtime/{chunk-ZXSGTGJ6.js → chunk-WIF24I7G.js} +21 -5
- package/dist/node-runtime/{chunk-OF7PWMG4.js → chunk-X3PGBLQ7.js} +3 -87
- package/dist/node-runtime/chunk-XHIRAZ26.js +108 -0
- package/dist/node-runtime/{chunk-YDO2F2IA.js → chunk-YWOJIGFY.js} +1 -1
- package/dist/node-runtime/{command-7BG7VVTS.js → command-FSBU2EVZ.js} +5 -5
- package/dist/node-runtime/{commands-NZLSOHPE.js → commands-EMBQQVVT.js} +4 -2
- package/dist/node-runtime/{commands-QFDKNQTL.js → commands-NAAL7KNT.js} +7 -6
- package/dist/node-runtime/{config-RORQUQFG.js → config-D4QUPAHX.js} +1 -1
- package/dist/node-runtime/{direct-tool-executor-77GW4F5P.js → direct-tool-executor-4D7UXYU4.js} +7 -4
- package/dist/node-runtime/{gateway-TWR3N65G.js → gateway-DQM333YV.js} +140 -85
- package/dist/node-runtime/index.js +28 -21
- package/dist/node-runtime/{init-HS6NQXSK.js → init-P5AUV77I.js} +4 -2
- package/dist/node-runtime/{install-cli-VAP6KBFN.js → install-cli-NN4YAU44.js} +3 -3
- package/dist/node-runtime/{interactive-mode-6J5VZ2TT.js → interactive-mode-YOHZVMKX.js} +14 -11
- package/dist/node-runtime/{lunr-browser-J5ZZYVGM.js → lunr-browser-IR7TL3J7.js} +3 -3
- package/dist/node-runtime/lunr-computer-use-Y23CCDIJ.js +158 -0
- package/dist/node-runtime/{lunr-cron-CXOL37AL.js → lunr-cron-USHC4LXH.js} +9 -7
- package/dist/node-runtime/main.js +19 -15
- package/dist/node-runtime/{mcp-auth-flow-QIXHW6QN.js → mcp-auth-flow-QVLFVLEO.js} +2 -1
- package/dist/node-runtime/{mobile-commands-VTBM5DSZ.js → mobile-commands-O5LCHL32.js} +6 -5
- package/dist/node-runtime/{package-manager-cli-ZLW5DZXF.js → package-manager-cli-Q7BL6KZO.js} +12 -8
- package/dist/node-runtime/{pi-mcp-adapter-5UKBZP44.js → pi-mcp-adapter-3PHF4KZ4.js} +5 -5
- package/dist/node-runtime/{pi-subagents-XPCLLKIM.js → pi-subagents-7IBSSGEW.js} +27 -21
- package/dist/node-runtime/{print-mode-XSFM26EL.js → print-mode-XQ76CYMT.js} +2 -1
- package/dist/node-runtime/{proxy-modes-LGEUYD7R.js → proxy-modes-5FAPSZEZ.js} +7 -4
- package/dist/node-runtime/rpc-entry.js +19 -15
- package/dist/node-runtime/runtime-EFPPD2UW.js +21 -0
- package/dist/node-runtime/{runtime-bridges-QYXUX2TG.js → runtime-bridges-7WVCLI76.js} +6 -5
- package/dist/node-runtime/{service-ISSP6VHE.js → service-MDQIPQ26.js} +1 -1
- package/dist/node-runtime/{settings-manager-WCQDUVQB.js → settings-manager-DJDM5AHM.js} +5 -4
- package/dist/node-runtime/setup-PFBWRNUI.js +215 -0
- package/dist/node-runtime/{subagent-executor-OTNISDQU.js → subagent-executor-LPZ4BHWE.js} +32 -14
- package/dist/node-runtime/{update-cli-VBR2777K.js → update-cli-HNMOTNGX.js} +5 -4
- package/dist/node-runtime/workflow-OHPBEFO4.js +637 -0
- package/dist/utils/image-resize-core.d.ts +2 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +10 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/docs/computer-use.md +362 -0
- package/docs/features.md +9 -7
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/install-lock/package-lock.json +3841 -0
- package/install-lock/package.json +18 -0
- package/native/computer-use/LICENSE.md +21 -0
- package/npm-shrinkwrap.json +3822 -0
- package/package.json +15 -9
- package/dist/node-runtime/setup-YRBKH2LD.js +0 -155
|
@@ -4,9 +4,9 @@ import { join } from "node:path";
|
|
|
4
4
|
import { getAgentDir } from "../config.js";
|
|
5
5
|
import { noOpUIContext } from "../core/extensions/runner.js";
|
|
6
6
|
import { runWithApprovalContext } from "./approval.js";
|
|
7
|
-
import { isAuthorized
|
|
7
|
+
import { isAuthorized } from "./authz.js";
|
|
8
8
|
import { createPicker } from "./buttons.js";
|
|
9
|
-
import { loadGatewayConfig } from "./config.js";
|
|
9
|
+
import { gatewayConfigPath, loadGatewayConfig } from "./config.js";
|
|
10
10
|
import { conversationBinding } from "./conversations.js";
|
|
11
11
|
import { createPairingStore } from "./pairing.js";
|
|
12
12
|
import { atomicJson } from "./service.js";
|
|
@@ -15,18 +15,34 @@ const epochs = new Map();
|
|
|
15
15
|
const inputs = new Map();
|
|
16
16
|
const dialogs = new Map();
|
|
17
17
|
let adapters = new Map();
|
|
18
|
+
const deliveryConfigs = new Map();
|
|
19
|
+
const roleGrants = new Map();
|
|
18
20
|
let timer;
|
|
19
|
-
|
|
21
|
+
const activeDeliveries = new Map();
|
|
22
|
+
const MAX_ATTEMPTS = 5;
|
|
20
23
|
const path = () => join(getAgentDir(), "gateway-outbox.json");
|
|
21
24
|
let outbox = [];
|
|
25
|
+
function sourceWithCurrentRole(key, source) {
|
|
26
|
+
const grant = roleGrants.get(key);
|
|
27
|
+
return {
|
|
28
|
+
...source,
|
|
29
|
+
roleAuthorized: grant?.userId === source.userId && grant.chatId === source.chatId && grant.threadId === source.threadId,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function rememberGatewayRole(key, source) {
|
|
33
|
+
if (source.roleAuthorized === true)
|
|
34
|
+
roleGrants.set(key, { userId: source.userId, chatId: source.chatId, threadId: source.threadId });
|
|
35
|
+
else
|
|
36
|
+
roleGrants.delete(key);
|
|
37
|
+
}
|
|
22
38
|
function canDeliver(key) {
|
|
23
39
|
const binding = conversationBinding(key);
|
|
24
40
|
if (!binding)
|
|
25
41
|
return false;
|
|
26
42
|
const cfg = loadGatewayConfig();
|
|
27
|
-
if (binding.owner &&
|
|
43
|
+
if (binding.owner && binding.owner !== binding.source.userId)
|
|
28
44
|
return false;
|
|
29
|
-
return isAuthorized(binding.source, cfg, createPairingStore());
|
|
45
|
+
return isAuthorized(sourceWithCurrentRole(key, binding.source), cfg, createPairingStore());
|
|
30
46
|
}
|
|
31
47
|
export function gatewayDestination(key) {
|
|
32
48
|
const binding = conversationBinding(key);
|
|
@@ -43,19 +59,29 @@ export function startGatewayPresenter(connected) {
|
|
|
43
59
|
adapters = connected;
|
|
44
60
|
if (existsSync(path())) {
|
|
45
61
|
try {
|
|
46
|
-
|
|
62
|
+
const raw = JSON.parse(readFileSync(path(), "utf8"));
|
|
63
|
+
if (!Array.isArray(raw) ||
|
|
64
|
+
!raw.every((item) => item && typeof item.id === "string" && typeof item.key === "string" && typeof item.text === "string"))
|
|
65
|
+
throw new Error("Invalid outbox");
|
|
66
|
+
outbox = raw;
|
|
47
67
|
}
|
|
48
68
|
catch {
|
|
49
69
|
throw new Error("Gateway outbox is invalid. Restore it before starting.");
|
|
50
70
|
}
|
|
51
71
|
}
|
|
72
|
+
else
|
|
73
|
+
outbox = [];
|
|
74
|
+
for (const item of outbox) {
|
|
75
|
+
if (item.kind === "notice")
|
|
76
|
+
epochs.set(item.key, Math.max(epochs.get(item.key) ?? 0, item.epoch ?? 0));
|
|
77
|
+
}
|
|
52
78
|
if (timer)
|
|
53
79
|
clearInterval(timer);
|
|
54
80
|
timer = setInterval(() => {
|
|
55
|
-
void flushOutbox();
|
|
81
|
+
void flushOutbox().catch((error) => console.error("[gateway] outbox flush failed:", error));
|
|
56
82
|
}, 3000);
|
|
57
83
|
timer.unref();
|
|
58
|
-
void flushOutbox();
|
|
84
|
+
void flushOutbox().catch((error) => console.error("[gateway] outbox flush failed:", error));
|
|
59
85
|
}
|
|
60
86
|
export function stopGatewayPresenter() {
|
|
61
87
|
if (timer)
|
|
@@ -64,52 +90,121 @@ export function stopGatewayPresenter() {
|
|
|
64
90
|
for (const key of dialogs.keys())
|
|
65
91
|
invalidateGatewayDialogs(key);
|
|
66
92
|
adapters = new Map();
|
|
93
|
+
deliveryConfigs.clear();
|
|
94
|
+
roleGrants.clear();
|
|
95
|
+
}
|
|
96
|
+
export function gatewayEpoch(key) {
|
|
97
|
+
return epochs.get(key) ?? 0;
|
|
67
98
|
}
|
|
68
|
-
export async function sendGatewayNotice(key, text) {
|
|
99
|
+
export async function sendGatewayNotice(key, text, kind = "notice", epoch = gatewayEpoch(key)) {
|
|
69
100
|
const clean = text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim();
|
|
70
101
|
if (!clean || !canDeliver(key))
|
|
71
102
|
return;
|
|
72
|
-
const { adapter } = gatewayDestination(key);
|
|
73
|
-
|
|
74
|
-
outbox.push({ id: randomUUID(), key, epoch: epochs.get(key) ?? 0, text: chunk });
|
|
75
|
-
atomicJson(path(), outbox);
|
|
76
|
-
await flushOutbox();
|
|
103
|
+
const { adapter, source } = gatewayDestination(key);
|
|
104
|
+
await queueGatewayText(key, source, adapter, clean, { kind, epoch });
|
|
77
105
|
}
|
|
78
|
-
async function
|
|
79
|
-
if (
|
|
106
|
+
export async function queueGatewayText(key, source, adapter, text, options) {
|
|
107
|
+
if (!text || (options.kind === "notice" && options.epoch !== undefined && options.epoch !== gatewayEpoch(key)))
|
|
80
108
|
return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
adapters.set(source.platform, adapter);
|
|
110
|
+
if (options.cfg)
|
|
111
|
+
deliveryConfigs.set(key, options.cfg);
|
|
112
|
+
const chunks = options.chunks ?? splitMessage(text, adapter.maxMessageLength);
|
|
113
|
+
const batchId = randomUUID();
|
|
114
|
+
for (const [index, chunk] of chunks.entries())
|
|
115
|
+
outbox.push({
|
|
116
|
+
id: randomUUID(),
|
|
117
|
+
batchId,
|
|
118
|
+
key,
|
|
119
|
+
source: { ...source, roleAuthorized: undefined },
|
|
120
|
+
epoch: options.epoch ?? gatewayEpoch(key),
|
|
121
|
+
text: chunk,
|
|
122
|
+
kind: options.kind,
|
|
123
|
+
replyTo: index === 0 ? options.replyTo : undefined,
|
|
124
|
+
});
|
|
125
|
+
atomicJson(path(), outbox);
|
|
126
|
+
if (activeDeliveries.has(key))
|
|
127
|
+
await activeDeliveries.get(key);
|
|
128
|
+
await flushKey(key);
|
|
129
|
+
}
|
|
130
|
+
export function flushGatewayOutbox() {
|
|
131
|
+
return flushOutbox();
|
|
132
|
+
}
|
|
133
|
+
function flushOutbox() {
|
|
134
|
+
return Promise.all([...new Set(outbox.map((item) => item.key))].map(flushKey)).then(() => { });
|
|
135
|
+
}
|
|
136
|
+
function flushKey(key) {
|
|
137
|
+
const current = activeDeliveries.get(key);
|
|
138
|
+
if (current)
|
|
139
|
+
return current;
|
|
140
|
+
const running = drainKey(key).finally(() => {
|
|
141
|
+
activeDeliveries.delete(key);
|
|
142
|
+
});
|
|
143
|
+
activeDeliveries.set(key, running);
|
|
144
|
+
return running;
|
|
145
|
+
}
|
|
146
|
+
async function drainKey(key) {
|
|
147
|
+
const failedBatches = new Set(outbox.filter((item) => item.failed && item.batchId).map((item) => item.batchId));
|
|
148
|
+
for (const item of [...outbox].filter((item) => item.key === key)) {
|
|
149
|
+
if (item.failed || (item.batchId && failedBatches.has(item.batchId)))
|
|
150
|
+
continue;
|
|
151
|
+
if (item.kind === "notice" && item.epoch !== (epochs.get(item.key) ?? 0)) {
|
|
152
|
+
outbox = outbox.filter((v) => v.id !== item.id);
|
|
153
|
+
atomicJson(path(), outbox);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const binding = conversationBinding(item.key);
|
|
157
|
+
const source = item.source ?? binding?.source;
|
|
158
|
+
if (!source)
|
|
159
|
+
break;
|
|
160
|
+
const cfg = existsSync(gatewayConfigPath())
|
|
161
|
+
? loadGatewayConfig()
|
|
162
|
+
: (deliveryConfigs.get(item.key) ?? loadGatewayConfig());
|
|
163
|
+
if ((binding &&
|
|
164
|
+
(binding.source.platform !== source.platform ||
|
|
165
|
+
binding.source.chatId !== source.chatId ||
|
|
166
|
+
binding.source.userId !== source.userId ||
|
|
167
|
+
binding.source.threadId !== source.threadId)) ||
|
|
168
|
+
!isAuthorized(sourceWithCurrentRole(key, source), cfg, createPairingStore()) ||
|
|
169
|
+
(binding?.owner && binding.owner !== source.userId)) {
|
|
170
|
+
outbox = outbox.filter((v) => v.id !== item.id);
|
|
171
|
+
atomicJson(path(), outbox);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const adapter = adapters.get(source.platform);
|
|
175
|
+
if (!adapter || (item.nextAttempt ?? 0) > Date.now())
|
|
176
|
+
break;
|
|
177
|
+
const opts = { threadId: source.threadId, replyTo: item.replyTo };
|
|
178
|
+
const result = await adapter
|
|
179
|
+
.send(source.chatId, item.text, opts)
|
|
180
|
+
.catch((error) => ({ success: false, error: String(error), retryable: true }));
|
|
181
|
+
if (result.success) {
|
|
182
|
+
outbox = outbox.filter((v) => v.id !== item.id);
|
|
183
|
+
atomicJson(path(), outbox);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
item.attempts = (item.attempts ?? 0) + 1;
|
|
187
|
+
item.failed = result.retryable === false || item.attempts >= MAX_ATTEMPTS;
|
|
188
|
+
item.nextAttempt = Date.now() + Math.min(60_000, 3000 * 2 ** (item.attempts - 1));
|
|
189
|
+
if (item.failed && item.batchId) {
|
|
190
|
+
failedBatches.add(item.batchId);
|
|
191
|
+
for (const pending of outbox)
|
|
192
|
+
if (pending.batchId === item.batchId)
|
|
193
|
+
pending.failed = true;
|
|
102
194
|
}
|
|
103
|
-
|
|
104
|
-
|
|
195
|
+
atomicJson(path(), outbox);
|
|
196
|
+
console.error(`[gateway] delivery ${item.failed ? "failed permanently" : "retry scheduled"} for ${source.platform}: ${result.error ?? "unknown error"}`);
|
|
197
|
+
break;
|
|
105
198
|
}
|
|
106
199
|
}
|
|
107
|
-
finally {
|
|
108
|
-
sending = false;
|
|
109
|
-
}
|
|
110
200
|
}
|
|
111
201
|
export function invalidateGatewayDialogs(key) {
|
|
112
202
|
epochs.set(key, (epochs.get(key) ?? 0) + 1);
|
|
203
|
+
const remaining = outbox.filter((item) => item.key !== key || item.kind !== "notice");
|
|
204
|
+
if (remaining.length !== outbox.length) {
|
|
205
|
+
outbox = remaining;
|
|
206
|
+
atomicJson(path(), outbox);
|
|
207
|
+
}
|
|
113
208
|
for (const cancel of dialogs.get(key) ?? [])
|
|
114
209
|
cancel();
|
|
115
210
|
dialogs.delete(key);
|
|
@@ -180,30 +275,33 @@ export function gatewaySelect(key, title, options, opts) {
|
|
|
180
275
|
export function gatewayInput(key, title, placeholder, opts) {
|
|
181
276
|
if (inputs.has(key))
|
|
182
277
|
return Promise.reject(new Error("Answer or cancel the existing question first."));
|
|
278
|
+
const epoch = gatewayEpoch(key);
|
|
183
279
|
return dialog(key, opts, async (done) => {
|
|
184
280
|
const { source } = gatewayDestination(key);
|
|
185
281
|
inputs.set(key, { userId: source.userId, resolve: done });
|
|
186
|
-
await sendGatewayNotice(key, `${title}${placeholder ? `\n${placeholder}` : ""}\nReply with text, or /cancel
|
|
282
|
+
await sendGatewayNotice(key, `${title}${placeholder ? `\n${placeholder}` : ""}\nReply with text, or /cancel.`, "notice", epoch);
|
|
187
283
|
});
|
|
188
284
|
}
|
|
189
285
|
export function createGatewayUI(key) {
|
|
286
|
+
const epoch = gatewayEpoch(key);
|
|
190
287
|
return {
|
|
191
288
|
...noOpUIContext,
|
|
192
|
-
select: (title, options, opts) => gatewaySelect(key, title, options, opts),
|
|
193
|
-
confirm: async (title, message, opts) => (
|
|
194
|
-
|
|
195
|
-
|
|
289
|
+
select: (title, options, opts) => gatewayEpoch(key) === epoch ? gatewaySelect(key, title, options, opts) : Promise.resolve(undefined),
|
|
290
|
+
confirm: async (title, message, opts) => gatewayEpoch(key) === epoch &&
|
|
291
|
+
(await gatewaySelect(key, `${title}\n${message}`, ["Approve", "Decline"], opts)) === "Approve",
|
|
292
|
+
input: (title, placeholder, opts) => gatewayEpoch(key) === epoch ? gatewayInput(key, title, placeholder, opts) : Promise.resolve(undefined),
|
|
293
|
+
editor: (title, prefill) => gatewayEpoch(key) === epoch ? gatewayInput(key, title, prefill) : Promise.resolve(undefined),
|
|
196
294
|
notify: (message) => {
|
|
197
|
-
void sendGatewayNotice(key, message).catch(() => { });
|
|
295
|
+
void sendGatewayNotice(key, message, "notice", epoch).catch(() => { });
|
|
198
296
|
},
|
|
199
297
|
custom: async () => {
|
|
200
298
|
throw new Error("This command requires the terminal interface. Use the gateway's text commands or button pickers.");
|
|
201
299
|
},
|
|
202
300
|
pasteToEditor: (text) => {
|
|
203
|
-
void sendGatewayNotice(key, text).catch(() => { });
|
|
301
|
+
void sendGatewayNotice(key, text, "notice", epoch).catch(() => { });
|
|
204
302
|
},
|
|
205
303
|
setEditorText: (text) => {
|
|
206
|
-
void sendGatewayNotice(key, text).catch(() => { });
|
|
304
|
+
void sendGatewayNotice(key, text, "notice", epoch).catch(() => { });
|
|
207
305
|
},
|
|
208
306
|
};
|
|
209
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenter.js","sourceRoot":"","sources":["../../src/gateway/presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;AACzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4E,CAAC;AACnG,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;AACnD,IAAI,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;AAClD,IAAI,KAAiD,CAAC;AACtD,IAAI,OAAO,GAAG,KAAK,CAAC;AAOpB,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,qBAAqB,CAAC,CAAC;AAC9D,IAAI,MAAM,GAAe,EAAE,CAAC;AAE5B,SAAS,UAAU,CAAC,GAAW,EAAW;IACzC,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrH,OAAO,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAAA,CAC/D;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAgE;IAC7G,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACvF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAAA,CAC3C;AAED,MAAM,UAAU,uBAAuB,CAAI,GAAW,EAAE,GAAqB,EAAc;IAC1F,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpD,OAAO,sBAAsB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;AAAA,CAC7D;AAED,MAAM,UAAU,qBAAqB,CAAC,SAAuC,EAAQ;IACpF,QAAQ,GAAG,SAAS,CAAC;IACrB,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC;YACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAe,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IACD,IAAI,KAAK;QAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACzB,KAAK,WAAW,EAAE,CAAC;IAAA,CACnB,EAAE,IAAI,CAAC,CAAC;IACT,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,KAAK,WAAW,EAAE,CAAC;AAAA,CACnB;AAED,MAAM,UAAU,oBAAoB,GAAS;IAC5C,IAAI,KAAK;QAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,GAAG,SAAS,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE;QAAE,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAChE,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;AAAA,CACrB;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAY,EAAiB;IACjF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChE,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO;IACvC,MAAM,EAAE,OAAO,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IAC3B,MAAM,WAAW,EAAE,CAAC;AAAA,CACpB;AAED,KAAK,UAAU,WAAW,GAAkB;IAC3C,IAAI,OAAO;QAAE,OAAO;IACpB,OAAO,GAAG,IAAI,CAAC;IACf,IAAI,CAAC;QACJ,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChD,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;gBAC3B,SAAS;YACV,CAAC;YACD,IAAI,MAA6C,CAAC;YAClD,IAAI,CAAC;gBACJ,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACR,SAAS;YACV,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO;iBACjC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;iBAC3E,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YACpC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChD,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;YAC5B,CAAC;;gBAAM,MAAM;QACd,CAAC;IACF,CAAC;YAAS,CAAC;QACV,OAAO,GAAG,KAAK,CAAC;IACjB,CAAC;AAAA,CACD;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAW,EAAQ;IAC3D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,CACnB;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,KAAmB,EAAW;IAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACrF,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACzE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,MAAM,CACd,GAAW,EACX,IAA0C,EAC1C,IAAwF,EAC1D;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACvC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,KAAyB,EAAE,KAAe,EAAE,EAAE,CAAC;YAC9D,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACnD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBAClC,OAAO,CAAC,KAAK,CAAC,CAAC;QAAA,CACpB,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,KAAyB,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QAC1C,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtB,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC3B,MAAM,EAAE,CAAC;YACT,OAAO;QACR,CAAC;QACD,KAAK,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YACvG,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAAA,CACzB,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,aAAa,CAC5B,GAAW,EACX,KAAa,EACb,OAAiB,EACjB,IAA+B,EACD;IAC9B,OAAO,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,YAAY,CAChC,OAAO,EACP,MAAM,EACN;YACC,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,GAAG;YACf,SAAS,EAAE,MAAM,CAAC,MAAM;YACxB,KAAK;YACL,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACxD,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;YAC/B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YAAA,CACvD;SACD,EACD,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAC7B,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAC;IAAA,CAC/E,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,YAAY,CAC3B,GAAW,EACX,KAAa,EACb,WAAoB,EACpB,IAA+B,EACD;IAC9B,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC,CAAC;IACvG,OAAO,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,iBAAiB,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,gCAAgC,CAAC,CAAC;IAAA,CAC/G,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,eAAe,CAAC,GAAW,EAAsB;IAChE,OAAO;QACN,GAAG,aAAa;QAChB,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;QAC1E,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CACvC,CAAC,MAAM,aAAa,CAAC,GAAG,EAAE,GAAG,KAAK,KAAK,OAAO,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,SAAS;QAC/F,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC;QAChF,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC;QAC7D,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,KAAK,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAAA,CACrD;QACD,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACd,kGAAkG,CAClG,CAAC;QAAA,CACF;QACD,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,KAAK,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAAA,CAClD;QACD,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,KAAK,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAAA,CAClD;KACD,CAAC;AAAA,CACF","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { noOpUIContext } from \"../core/extensions/runner.ts\";\nimport type { ExtensionUIContext, ExtensionUIDialogOptions } from \"../core/extensions/types.ts\";\nimport { runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized, isGatewayOwner } from \"./authz.ts\";\nimport { createPicker } from \"./buttons.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { atomicJson } from \"./service.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nconst epochs = new Map<string, number>();\nconst inputs = new Map<string, { userId: string; resolve: (value: string | undefined) => void }>();\nconst dialogs = new Map<string, Set<() => void>>();\nlet adapters = new Map<string, PlatformAdapter>();\nlet timer: ReturnType<typeof setInterval> | undefined;\nlet sending = false;\ninterface Outbound {\n\tid: string;\n\tkey: string;\n\tepoch: number;\n\ttext: string;\n}\nconst path = () => join(getAgentDir(), \"gateway-outbox.json\");\nlet outbox: Outbound[] = [];\n\nfunction canDeliver(key: string): boolean {\n\tconst binding = conversationBinding(key);\n\tif (!binding) return false;\n\tconst cfg = loadGatewayConfig();\n\tif (binding.owner && (binding.owner !== binding.source.userId || !isGatewayOwner(binding.source, cfg))) return false;\n\treturn isAuthorized(binding.source, cfg, createPairingStore());\n}\n\nexport function gatewayDestination(key: string): { adapter: PlatformAdapter; source: MessageEvent[\"source\"] } {\n\tconst binding = conversationBinding(key);\n\tconst adapter = binding && adapters.get(binding.source.platform);\n\tif (!binding || !adapter || !canDeliver(key))\n\t\tthrow new Error(\"This chat is no longer authorized or its platform is disconnected.\");\n\treturn { adapter, source: binding.source };\n}\n\nexport function withGatewayPresentation<T>(key: string, run: () => Promise<T>): Promise<T> {\n\tconst { adapter, source } = gatewayDestination(key);\n\treturn runWithApprovalContext({ adapter, source, key }, run);\n}\n\nexport function startGatewayPresenter(connected: Map<string, PlatformAdapter>): void {\n\tadapters = connected;\n\tif (existsSync(path())) {\n\t\ttry {\n\t\t\toutbox = JSON.parse(readFileSync(path(), \"utf8\")) as Outbound[];\n\t\t} catch {\n\t\t\tthrow new Error(\"Gateway outbox is invalid. Restore it before starting.\");\n\t\t}\n\t}\n\tif (timer) clearInterval(timer);\n\ttimer = setInterval(() => {\n\t\tvoid flushOutbox();\n\t}, 3000);\n\ttimer.unref();\n\tvoid flushOutbox();\n}\n\nexport function stopGatewayPresenter(): void {\n\tif (timer) clearInterval(timer);\n\ttimer = undefined;\n\tfor (const key of dialogs.keys()) invalidateGatewayDialogs(key);\n\tadapters = new Map();\n}\n\nexport async function sendGatewayNotice(key: string, text: string): Promise<void> {\n\tconst clean = text.replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, \"\").trim();\n\tif (!clean || !canDeliver(key)) return;\n\tconst { adapter } = gatewayDestination(key);\n\tfor (const chunk of splitMessage(clean, adapter.maxMessageLength))\n\t\toutbox.push({ id: randomUUID(), key, epoch: epochs.get(key) ?? 0, text: chunk });\n\tatomicJson(path(), outbox);\n\tawait flushOutbox();\n}\n\nasync function flushOutbox(): Promise<void> {\n\tif (sending) return;\n\tsending = true;\n\ttry {\n\t\tfor (const item of [...outbox]) {\n\t\t\tif (!canDeliver(item.key)) {\n\t\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\t\tatomicJson(path(), outbox);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet target: ReturnType<typeof gatewayDestination>;\n\t\t\ttry {\n\t\t\t\ttarget = gatewayDestination(item.key);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst result = await target.adapter\n\t\t\t\t.send(target.source.chatId, item.text, { threadId: target.source.threadId })\n\t\t\t\t.catch(() => ({ success: false }));\n\t\t\tif (result.success) {\n\t\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\t\tatomicJson(path(), outbox);\n\t\t\t} else break;\n\t\t}\n\t} finally {\n\t\tsending = false;\n\t}\n}\n\nexport function invalidateGatewayDialogs(key: string): void {\n\tepochs.set(key, (epochs.get(key) ?? 0) + 1);\n\tfor (const cancel of dialogs.get(key) ?? []) cancel();\n\tdialogs.delete(key);\n\tinputs.delete(key);\n}\n\nexport function acceptGatewayInput(key: string, event: MessageEvent): boolean {\n\tconst input = inputs.get(key);\n\tif (!input || input.userId !== event.source.userId || !canDeliver(key)) return false;\n\tif (event.text.startsWith(\"/\") && event.text !== \"/cancel\") return false;\n\tinput.resolve(event.text === \"/cancel\" ? undefined : event.text);\n\treturn true;\n}\n\nfunction dialog(\n\tkey: string,\n\topts: ExtensionUIDialogOptions | undefined,\n\tshow: (done: (value: string | undefined) => void, valid: () => boolean) => Promise<void>,\n): Promise<string | undefined> {\n\tconst epoch = epochs.get(key) ?? 0;\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: string | undefined, error?: unknown) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\tinputs.delete(key);\n\t\t\tdialogs.get(key)?.delete(cancel);\n\t\t\tif (error !== undefined) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tconst done = (value: string | undefined) => finish(value);\n\t\tconst cancel = () => done(undefined);\n\t\tconst timeout = setTimeout(cancel, opts?.timeout ?? 300_000);\n\t\tconst set = dialogs.get(key) ?? new Set();\n\t\tset.add(cancel);\n\t\tdialogs.set(key, set);\n\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\tif (opts?.signal?.aborted) {\n\t\t\tcancel();\n\t\t\treturn;\n\t\t}\n\t\tvoid show(done, () => !settled && (epochs.get(key) ?? 0) === epoch && canDeliver(key)).catch((error) => {\n\t\t\tfinish(undefined, error);\n\t\t});\n\t});\n}\n\nexport function gatewaySelect(\n\tkey: string,\n\ttitle: string,\n\toptions: string[],\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\treturn dialog(key, opts, async (done, valid) => {\n\t\tconst { adapter, source } = gatewayDestination(key);\n\t\tconst result = await createPicker(\n\t\t\tadapter,\n\t\t\tsource,\n\t\t\t{\n\t\t\t\tkind: \"dialog\",\n\t\t\t\tsessionKey: key,\n\t\t\t\tinvokerId: source.userId,\n\t\t\t\ttitle,\n\t\t\t\titems: options.map((value) => ({ label: value, value })),\n\t\t\t\tvalidate: valid,\n\t\t\t\tonCancel: () => done(undefined),\n\t\t\t\tresolve: async (item) => {\n\t\t\t\t\tdone(item.value);\n\t\t\t\t\treturn { done: true, text: `${title}\\n${item.value}` };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ threadId: source.threadId },\n\t\t);\n\t\tif (!result.success) throw new Error(result.error ?? \"Could not send picker.\");\n\t});\n}\n\nexport function gatewayInput(\n\tkey: string,\n\ttitle: string,\n\tplaceholder?: string,\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\tif (inputs.has(key)) return Promise.reject(new Error(\"Answer or cancel the existing question first.\"));\n\treturn dialog(key, opts, async (done) => {\n\t\tconst { source } = gatewayDestination(key);\n\t\tinputs.set(key, { userId: source.userId, resolve: done });\n\t\tawait sendGatewayNotice(key, `${title}${placeholder ? `\\n${placeholder}` : \"\"}\\nReply with text, or /cancel.`);\n\t});\n}\n\nexport function createGatewayUI(key: string): ExtensionUIContext {\n\treturn {\n\t\t...noOpUIContext,\n\t\tselect: (title, options, opts) => gatewaySelect(key, title, options, opts),\n\t\tconfirm: async (title, message, opts) =>\n\t\t\t(await gatewaySelect(key, `${title}\\n${message}`, [\"Approve\", \"Decline\"], opts)) === \"Approve\",\n\t\tinput: (title, placeholder, opts) => gatewayInput(key, title, placeholder, opts),\n\t\teditor: (title, prefill) => gatewayInput(key, title, prefill),\n\t\tnotify: (message) => {\n\t\t\tvoid sendGatewayNotice(key, message).catch(() => {});\n\t\t},\n\t\tcustom: async () => {\n\t\t\tthrow new Error(\n\t\t\t\t\"This command requires the terminal interface. Use the gateway's text commands or button pickers.\",\n\t\t\t);\n\t\t},\n\t\tpasteToEditor: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text).catch(() => {});\n\t\t},\n\t\tsetEditorText: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text).catch(() => {});\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"presenter.js","sourceRoot":"","sources":["../../src/gateway/presenter.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { noOpUIContext } from \"../core/extensions/runner.ts\";\nimport type { ExtensionUIContext, ExtensionUIDialogOptions } from \"../core/extensions/types.ts\";\nimport { runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized } from \"./authz.ts\";\nimport { createPicker } from \"./buttons.ts\";\nimport { type GatewayConfig, gatewayConfigPath, loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { atomicJson } from \"./service.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter, SendOptions } from \"./types.ts\";\n\nconst epochs = new Map<string, number>();\nconst inputs = new Map<string, { userId: string; resolve: (value: string | undefined) => void }>();\nconst dialogs = new Map<string, Set<() => void>>();\nlet adapters = new Map<string, PlatformAdapter>();\nconst deliveryConfigs = new Map<string, GatewayConfig>();\nconst roleGrants = new Map<string, { userId: string; chatId: string; threadId?: string }>();\nlet timer: ReturnType<typeof setInterval> | undefined;\nconst activeDeliveries = new Map<string, Promise<void>>();\ninterface Outbound {\n\tid: string;\n\tkey: string;\n\tepoch: number;\n\ttext: string;\n\tsource?: MessageEvent[\"source\"];\n\treplyTo?: string;\n\tkind?: \"result\" | \"notice\";\n\tattempts?: number;\n\tnextAttempt?: number;\n\tfailed?: boolean;\n\tbatchId?: string;\n}\nconst MAX_ATTEMPTS = 5;\nconst path = () => join(getAgentDir(), \"gateway-outbox.json\");\nlet outbox: Outbound[] = [];\n\nfunction sourceWithCurrentRole(key: string, source: MessageEvent[\"source\"]): MessageEvent[\"source\"] {\n\tconst grant = roleGrants.get(key);\n\treturn {\n\t\t...source,\n\t\troleAuthorized:\n\t\t\tgrant?.userId === source.userId && grant.chatId === source.chatId && grant.threadId === source.threadId,\n\t};\n}\n\nexport function rememberGatewayRole(key: string, source: MessageEvent[\"source\"]): void {\n\tif (source.roleAuthorized === true)\n\t\troleGrants.set(key, { userId: source.userId, chatId: source.chatId, threadId: source.threadId });\n\telse roleGrants.delete(key);\n}\n\nfunction canDeliver(key: string): boolean {\n\tconst binding = conversationBinding(key);\n\tif (!binding) return false;\n\tconst cfg = loadGatewayConfig();\n\tif (binding.owner && binding.owner !== binding.source.userId) return false;\n\treturn isAuthorized(sourceWithCurrentRole(key, binding.source), cfg, createPairingStore());\n}\n\nexport function gatewayDestination(key: string): { adapter: PlatformAdapter; source: MessageEvent[\"source\"] } {\n\tconst binding = conversationBinding(key);\n\tconst adapter = binding && adapters.get(binding.source.platform);\n\tif (!binding || !adapter || !canDeliver(key))\n\t\tthrow new Error(\"This chat is no longer authorized or its platform is disconnected.\");\n\treturn { adapter, source: binding.source };\n}\n\nexport function withGatewayPresentation<T>(key: string, run: () => Promise<T>): Promise<T> {\n\tconst { adapter, source } = gatewayDestination(key);\n\treturn runWithApprovalContext({ adapter, source, key }, run);\n}\n\nexport function startGatewayPresenter(connected: Map<string, PlatformAdapter>): void {\n\tadapters = connected;\n\tif (existsSync(path())) {\n\t\ttry {\n\t\t\tconst raw: unknown = JSON.parse(readFileSync(path(), \"utf8\"));\n\t\t\tif (\n\t\t\t\t!Array.isArray(raw) ||\n\t\t\t\t!raw.every(\n\t\t\t\t\t(item) =>\n\t\t\t\t\t\titem && typeof item.id === \"string\" && typeof item.key === \"string\" && typeof item.text === \"string\",\n\t\t\t\t)\n\t\t\t)\n\t\t\t\tthrow new Error(\"Invalid outbox\");\n\t\t\toutbox = raw as Outbound[];\n\t\t} catch {\n\t\t\tthrow new Error(\"Gateway outbox is invalid. Restore it before starting.\");\n\t\t}\n\t} else outbox = [];\n\tfor (const item of outbox) {\n\t\tif (item.kind === \"notice\") epochs.set(item.key, Math.max(epochs.get(item.key) ?? 0, item.epoch ?? 0));\n\t}\n\tif (timer) clearInterval(timer);\n\ttimer = setInterval(() => {\n\t\tvoid flushOutbox().catch((error) => console.error(\"[gateway] outbox flush failed:\", error));\n\t}, 3000);\n\ttimer.unref();\n\tvoid flushOutbox().catch((error) => console.error(\"[gateway] outbox flush failed:\", error));\n}\n\nexport function stopGatewayPresenter(): void {\n\tif (timer) clearInterval(timer);\n\ttimer = undefined;\n\tfor (const key of dialogs.keys()) invalidateGatewayDialogs(key);\n\tadapters = new Map();\n\tdeliveryConfigs.clear();\n\troleGrants.clear();\n}\n\nexport function gatewayEpoch(key: string): number {\n\treturn epochs.get(key) ?? 0;\n}\n\nexport async function sendGatewayNotice(\n\tkey: string,\n\ttext: string,\n\tkind: \"notice\" | \"result\" = \"notice\",\n\tepoch = gatewayEpoch(key),\n): Promise<void> {\n\tconst clean = text.replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, \"\").trim();\n\tif (!clean || !canDeliver(key)) return;\n\tconst { adapter, source } = gatewayDestination(key);\n\tawait queueGatewayText(key, source, adapter, clean, { kind, epoch });\n}\n\nexport async function queueGatewayText(\n\tkey: string,\n\tsource: MessageEvent[\"source\"],\n\tadapter: PlatformAdapter,\n\ttext: string,\n\toptions: { kind: \"result\" | \"notice\"; replyTo?: string; cfg?: GatewayConfig; chunks?: string[]; epoch?: number },\n): Promise<void> {\n\tif (!text || (options.kind === \"notice\" && options.epoch !== undefined && options.epoch !== gatewayEpoch(key)))\n\t\treturn;\n\tadapters.set(source.platform, adapter);\n\tif (options.cfg) deliveryConfigs.set(key, options.cfg);\n\tconst chunks = options.chunks ?? splitMessage(text, adapter.maxMessageLength);\n\tconst batchId = randomUUID();\n\tfor (const [index, chunk] of chunks.entries())\n\t\toutbox.push({\n\t\t\tid: randomUUID(),\n\t\t\tbatchId,\n\t\t\tkey,\n\t\t\tsource: { ...source, roleAuthorized: undefined },\n\t\t\tepoch: options.epoch ?? gatewayEpoch(key),\n\t\t\ttext: chunk,\n\t\t\tkind: options.kind,\n\t\t\treplyTo: index === 0 ? options.replyTo : undefined,\n\t\t});\n\tatomicJson(path(), outbox);\n\tif (activeDeliveries.has(key)) await activeDeliveries.get(key);\n\tawait flushKey(key);\n}\n\nexport function flushGatewayOutbox(): Promise<void> {\n\treturn flushOutbox();\n}\n\nfunction flushOutbox(): Promise<void> {\n\treturn Promise.all([...new Set(outbox.map((item) => item.key))].map(flushKey)).then(() => {});\n}\n\nfunction flushKey(key: string): Promise<void> {\n\tconst current = activeDeliveries.get(key);\n\tif (current) return current;\n\tconst running = drainKey(key).finally(() => {\n\t\tactiveDeliveries.delete(key);\n\t});\n\tactiveDeliveries.set(key, running);\n\treturn running;\n}\n\nasync function drainKey(key: string): Promise<void> {\n\tconst failedBatches = new Set(outbox.filter((item) => item.failed && item.batchId).map((item) => item.batchId));\n\tfor (const item of [...outbox].filter((item) => item.key === key)) {\n\t\tif (item.failed || (item.batchId && failedBatches.has(item.batchId))) continue;\n\t\tif (item.kind === \"notice\" && item.epoch !== (epochs.get(item.key) ?? 0)) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t\tcontinue;\n\t\t}\n\t\tconst binding = conversationBinding(item.key);\n\t\tconst source = item.source ?? binding?.source;\n\t\tif (!source) break;\n\t\tconst cfg = existsSync(gatewayConfigPath())\n\t\t\t? loadGatewayConfig()\n\t\t\t: (deliveryConfigs.get(item.key) ?? loadGatewayConfig());\n\t\tif (\n\t\t\t(binding &&\n\t\t\t\t(binding.source.platform !== source.platform ||\n\t\t\t\t\tbinding.source.chatId !== source.chatId ||\n\t\t\t\t\tbinding.source.userId !== source.userId ||\n\t\t\t\t\tbinding.source.threadId !== source.threadId)) ||\n\t\t\t!isAuthorized(sourceWithCurrentRole(key, source), cfg, createPairingStore()) ||\n\t\t\t(binding?.owner && binding.owner !== source.userId)\n\t\t) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t\tcontinue;\n\t\t}\n\t\tconst adapter = adapters.get(source.platform);\n\t\tif (!adapter || (item.nextAttempt ?? 0) > Date.now()) break;\n\t\tconst opts: SendOptions = { threadId: source.threadId, replyTo: item.replyTo };\n\t\tconst result = await adapter\n\t\t\t.send(source.chatId, item.text, opts)\n\t\t\t.catch((error: unknown) => ({ success: false as const, error: String(error), retryable: true }));\n\t\tif (result.success) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t} else {\n\t\t\titem.attempts = (item.attempts ?? 0) + 1;\n\t\t\titem.failed = result.retryable === false || item.attempts >= MAX_ATTEMPTS;\n\t\t\titem.nextAttempt = Date.now() + Math.min(60_000, 3000 * 2 ** (item.attempts - 1));\n\t\t\tif (item.failed && item.batchId) {\n\t\t\t\tfailedBatches.add(item.batchId);\n\t\t\t\tfor (const pending of outbox) if (pending.batchId === item.batchId) pending.failed = true;\n\t\t\t}\n\t\t\tatomicJson(path(), outbox);\n\t\t\tconsole.error(\n\t\t\t\t`[gateway] delivery ${item.failed ? \"failed permanently\" : \"retry scheduled\"} for ${source.platform}: ${result.error ?? \"unknown error\"}`,\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport function invalidateGatewayDialogs(key: string): void {\n\tepochs.set(key, (epochs.get(key) ?? 0) + 1);\n\tconst remaining = outbox.filter((item) => item.key !== key || item.kind !== \"notice\");\n\tif (remaining.length !== outbox.length) {\n\t\toutbox = remaining;\n\t\tatomicJson(path(), outbox);\n\t}\n\tfor (const cancel of dialogs.get(key) ?? []) cancel();\n\tdialogs.delete(key);\n\tinputs.delete(key);\n}\n\nexport function acceptGatewayInput(key: string, event: MessageEvent): boolean {\n\tconst input = inputs.get(key);\n\tif (!input || input.userId !== event.source.userId || !canDeliver(key)) return false;\n\tif (event.text.startsWith(\"/\") && event.text !== \"/cancel\") return false;\n\tinput.resolve(event.text === \"/cancel\" ? undefined : event.text);\n\treturn true;\n}\n\nfunction dialog(\n\tkey: string,\n\topts: ExtensionUIDialogOptions | undefined,\n\tshow: (done: (value: string | undefined) => void, valid: () => boolean) => Promise<void>,\n): Promise<string | undefined> {\n\tconst epoch = epochs.get(key) ?? 0;\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: string | undefined, error?: unknown) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\tinputs.delete(key);\n\t\t\tdialogs.get(key)?.delete(cancel);\n\t\t\tif (error !== undefined) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tconst done = (value: string | undefined) => finish(value);\n\t\tconst cancel = () => done(undefined);\n\t\tconst timeout = setTimeout(cancel, opts?.timeout ?? 300_000);\n\t\tconst set = dialogs.get(key) ?? new Set();\n\t\tset.add(cancel);\n\t\tdialogs.set(key, set);\n\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\tif (opts?.signal?.aborted) {\n\t\t\tcancel();\n\t\t\treturn;\n\t\t}\n\t\tvoid show(done, () => !settled && (epochs.get(key) ?? 0) === epoch && canDeliver(key)).catch((error) => {\n\t\t\tfinish(undefined, error);\n\t\t});\n\t});\n}\n\nexport function gatewaySelect(\n\tkey: string,\n\ttitle: string,\n\toptions: string[],\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\treturn dialog(key, opts, async (done, valid) => {\n\t\tconst { adapter, source } = gatewayDestination(key);\n\t\tconst result = await createPicker(\n\t\t\tadapter,\n\t\t\tsource,\n\t\t\t{\n\t\t\t\tkind: \"dialog\",\n\t\t\t\tsessionKey: key,\n\t\t\t\tinvokerId: source.userId,\n\t\t\t\ttitle,\n\t\t\t\titems: options.map((value) => ({ label: value, value })),\n\t\t\t\tvalidate: valid,\n\t\t\t\tonCancel: () => done(undefined),\n\t\t\t\tresolve: async (item) => {\n\t\t\t\t\tdone(item.value);\n\t\t\t\t\treturn { done: true, text: `${title}\\n${item.value}` };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ threadId: source.threadId },\n\t\t);\n\t\tif (!result.success) throw new Error(result.error ?? \"Could not send picker.\");\n\t});\n}\n\nexport function gatewayInput(\n\tkey: string,\n\ttitle: string,\n\tplaceholder?: string,\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\tif (inputs.has(key)) return Promise.reject(new Error(\"Answer or cancel the existing question first.\"));\n\tconst epoch = gatewayEpoch(key);\n\treturn dialog(key, opts, async (done) => {\n\t\tconst { source } = gatewayDestination(key);\n\t\tinputs.set(key, { userId: source.userId, resolve: done });\n\t\tawait sendGatewayNotice(\n\t\t\tkey,\n\t\t\t`${title}${placeholder ? `\\n${placeholder}` : \"\"}\\nReply with text, or /cancel.`,\n\t\t\t\"notice\",\n\t\t\tepoch,\n\t\t);\n\t});\n}\n\nexport function createGatewayUI(key: string): ExtensionUIContext {\n\tconst epoch = gatewayEpoch(key);\n\treturn {\n\t\t...noOpUIContext,\n\t\tselect: (title, options, opts) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewaySelect(key, title, options, opts) : Promise.resolve(undefined),\n\t\tconfirm: async (title, message, opts) =>\n\t\t\tgatewayEpoch(key) === epoch &&\n\t\t\t(await gatewaySelect(key, `${title}\\n${message}`, [\"Approve\", \"Decline\"], opts)) === \"Approve\",\n\t\tinput: (title, placeholder, opts) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewayInput(key, title, placeholder, opts) : Promise.resolve(undefined),\n\t\teditor: (title, prefill) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewayInput(key, title, prefill) : Promise.resolve(undefined),\n\t\tnotify: (message) => {\n\t\t\tvoid sendGatewayNotice(key, message, \"notice\", epoch).catch(() => {});\n\t\t},\n\t\tcustom: async () => {\n\t\t\tthrow new Error(\n\t\t\t\t\"This command requires the terminal interface. Use the gateway's text commands or button pickers.\",\n\t\t\t);\n\t\t},\n\t\tpasteToEditor: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text, \"notice\", epoch).catch(() => {});\n\t\t},\n\t\tsetEditorText: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text, \"notice\", epoch).catch(() => {});\n\t\t},\n\t};\n}\n"]}
|
package/dist/gateway/router.d.ts
CHANGED
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
* /context, /compact, /thinking, /stop, /status, /help, /whoami)
|
|
11
11
|
* 4. normal path: bridge.runTurn with a StreamConsumer when streaming is
|
|
12
12
|
* enabled and the adapter supports edit; the final text is
|
|
13
|
-
* silence-filtered and folded
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* sequentially, the first chunk replying to the triggering message.
|
|
13
|
+
* silence-filtered and folded into a successful streaming preview edit.
|
|
14
|
+
* Failed final edits and ordinary answers enter the durable outbox, which
|
|
15
|
+
* acknowledges chunks in order and retries by destination.
|
|
17
16
|
* Errors surface as a compact "⚠ <one-line>" — never a stack trace.
|
|
18
17
|
*/
|
|
19
18
|
import { type BridgeSession, type BridgeSessionStatus, type TurnCallbacks } from "./agent-bridge.ts";
|
|
@@ -26,7 +25,8 @@ export interface BridgeLike {
|
|
|
26
25
|
abort(key: string): Promise<void> | void;
|
|
27
26
|
reset(key: string): void | Promise<void>;
|
|
28
27
|
getStatus(key: string): BridgeSessionStatus;
|
|
29
|
-
getSession(key: string): Promise<BridgeSession | null>;
|
|
28
|
+
getSession(key: string, create?: boolean): Promise<BridgeSession | null>;
|
|
29
|
+
peekSession?(key: string): BridgeSession | undefined;
|
|
30
30
|
switchSession(key: string, sessionFile: string): Promise<void>;
|
|
31
31
|
undo(key: string): Promise<{
|
|
32
32
|
userText: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/gateway/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAU,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAI7G,OAAO,EAAE,KAAK,aAAa,EAA2D,MAAM,aAAa,CAAC;AAE1G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,yEAAyE;AACzE,MAAM,WAAW,UAAU;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC5C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACvD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,MAAM;IACtB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAcD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAsNrD","sourcesContent":["/**\n * lunR: gateway router.\n *\n * Every inbound MessageEvent flows through handleEvent:\n * 1. group gating (allowedChats / freeResponseChats / requireMention,\n * with adapter-supplied metadata.mentionedBot)\n * 2. authorization (authz.ts); denied DMs get a pairing code when the\n * behavior is \"pair\" and the user isn't rate-limited\n * 3. command registry (/new, /undo, /redo, /model, /sessions, /title,\n * /context, /compact, /thinking, /stop, /status, /help, /whoami)\n * 4. normal path: bridge.runTurn with a StreamConsumer when streaming is\n * enabled and the adapter supports edit; the final text is\n * silence-filtered and folded INTO the streaming preview (edit, plus\n * continuation chunks when the preview was truncated) — no preview +\n * final double-send. Without streaming it is split (text.ts) and sent\n * sequentially, the first chunk replying to the triggering message.\n * Errors surface as a compact \"⚠ <one-line>\" — never a stack trace.\n */\n\nimport { existsSync } from \"node:fs\";\n\nimport { type BridgeSession, type BridgeSessionStatus, QUEUED, type TurnCallbacks } from \"./agent-bridge.ts\";\nimport { registerGatewayApprovalHandler, runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized, requireGatewayOwner } from \"./authz.ts\";\nimport { CHAT_COMMANDS, runChatCommand, sendCommandReply } from \"./commands.ts\";\nimport { type GatewayConfig, gatewayConfigPath, loadGatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport { acceptGatewayInput, invalidateGatewayDialogs } from \"./presenter.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { applySilenceFilter, StreamConsumer } from \"./stream.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\n/** Structural bridge shape (AgentBridge satisfies it; tests fake it). */\nexport interface BridgeLike {\n\trunTurn(key: string, event: MessageEvent, callbacks: TurnCallbacks): Promise<string>;\n\tabort(key: string): Promise<void> | void;\n\treset(key: string): void | Promise<void>;\n\tgetStatus(key: string): BridgeSessionStatus;\n\tgetSession(key: string): Promise<BridgeSession | null>;\n\tswitchSession(key: string, sessionFile: string): Promise<void>;\n\tundo(key: string): Promise<{ userText: string }>;\n\tredo(key: string): Promise<void>;\n}\n\nexport interface RouterDeps {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\tpairing: PairingStore;\n\tbridge: BridgeLike;\n\t/** Reload gateway.json on every inbound event so CLI config edits (e.g. pair approve) are picked up by the running daemon. */\n\treloadConfig?: boolean;\n\tremoteControls?: boolean;\n}\n\nexport interface Router {\n\thandleEvent(event: MessageEvent): Promise<void>;\n}\n\nfunction oneLine(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nfunction formatPairingCode(code: string): string {\n\treturn code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;\n}\n\nfunction mentionedBot(event: MessageEvent): boolean {\n\treturn event.metadata?.mentionedBot === true;\n}\n\nexport function createRouter(deps: RouterDeps): Router {\n\tconst { adapters, cfg: initialCfg, pairing, bridge, reloadConfig } = deps;\n\n\t// Headless gateway sessions still prompt for large subagent launches in yolo mode.\n\tregisterGatewayApprovalHandler();\n\n\t/** Reload gateway.json on every inbound event so CLI edits (e.g. pair approve) take effect immediately. */\n\tfunction freshCfg(): GatewayConfig {\n\t\tif (!reloadConfig) return initialCfg;\n\t\ttry {\n\t\t\tif (existsSync(gatewayConfigPath())) {\n\t\t\t\treturn loadGatewayConfig();\n\t\t\t}\n\t\t} catch {\n\t\t\t// fall through to initial config\n\t\t}\n\t\treturn initialCfg;\n\t}\n\n\tasync function sendError(adapter: PlatformAdapter, event: MessageEvent, message: string): Promise<void> {\n\t\tawait adapter.send(event.source.chatId, `⚠ ${oneLine(message)}`, {\n\t\t\treplyTo: event.messageId,\n\t\t\tthreadId: event.source.threadId,\n\t\t});\n\t}\n\n\t/** Step 1: group-chat early gating. Returns true when the event must be dropped. */\n\tfunction isGroupGated(event: MessageEvent, cfg: GatewayConfig): boolean {\n\t\tconst { source } = event;\n\t\tif (source.chatType === \"dm\") return false;\n\t\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\t\tif (!platformCfg) return true;\n\t\tconst freeResponse = platformCfg.freeResponseChats.includes(source.chatId);\n\t\tif (\n\t\t\tplatformCfg.allowedChats.length > 0 &&\n\t\t\t!platformCfg.allowedChats.includes(source.chatId) &&\n\t\t\t!freeResponse &&\n\t\t\t!mentionedBot(event)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tif (platformCfg.requireMention && !freeResponse && !mentionedBot(event)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Step 2: authorization; handles the denied-DM pairing flow. Returns true when denied. */\n\tasync function isDenied(adapter: PlatformAdapter, event: MessageEvent, cfg: GatewayConfig): Promise<boolean> {\n\t\tif (isAuthorized(event.source, cfg, pairing)) return false;\n\t\tconst { source } = event;\n\t\tif (source.chatType !== \"dm\") return true; // denied group: silent\n\t\tif (cfg.unauthorizedDmBehavior !== \"pair\") return true;\n\t\tconst code = pairing.issueCode(source.platform, source.userId);\n\t\tif (code === null) return true; // rate-limited or pending list full: stay silent\n\t\tawait adapter.send(\n\t\t\tsource.chatId,\n\t\t\t`Your lunR pairing code: ${formatPairingCode(code)} — ask the owner to run: lunr gateway pair approve ${source.platform} ${formatPairingCode(code)}`,\n\t\t\t{ replyTo: event.messageId, threadId: source.threadId },\n\t\t);\n\t\treturn true;\n\t}\n\n\t/** Step 3: command registry. Returns true when the event was consumed. */\n\tasync function handleSlash(adapter: PlatformAdapter, event: MessageEvent, key: string): Promise<boolean> {\n\t\tconst text = event.text.trim();\n\t\tif (!text.startsWith(\"/\")) return false;\n\t\tconst firstToken = text.split(/\\s+/, 1)[0].toLowerCase();\n\t\tconst commandWord = firstToken.split(\"@\")[0].slice(1);\n\t\tconst args = text.slice(firstToken.length).trim();\n\t\tif (deps.remoteControls) {\n\t\t\tconst { handleMobileCommand, cancelMobileTransfer } = await import(\"./mobile-commands.ts\");\n\t\t\tif ([\"stop\", \"stopall\", \"new\", \"cancel\"].includes(commandWord)) cancelMobileTransfer(key);\n\t\t\tif (commandWord === \"cancel\") {\n\t\t\t\tinvalidateGatewayDialogs(key);\n\t\t\t\tawait adapter.send(event.source.chatId, \"Cancelled the pending selection or transfer.\", {\n\t\t\t\t\tthreadId: event.source.threadId,\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst consumed = await runWithApprovalContext({ key, adapter, source: event.source }, () =>\n\t\t\t\thandleMobileCommand({ key, event, adapter, bridge, cfg: freshCfg() }, commandWord, args),\n\t\t\t);\n\t\t\tif (consumed) return true;\n\t\t\tif (!event.text.startsWith(\"/\")) return false;\n\t\t}\n\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === commandWord || c.aliases?.includes(commandWord));\n\t\tif (!cmd) {\n\t\t\treturn event.source.chatType !== \"dm\";\n\t\t}\n\t\tconst ctx = {\n\t\t\tevent,\n\t\t\tkey,\n\t\t\tadapter,\n\t\t\tbridge,\n\t\t\targs,\n\t\t\treply: (message: string) => sendCommandReply(adapter, event, message),\n\t\t};\n\t\treturn runWithApprovalContext({ key, adapter, source: event.source }, () => runChatCommand(cmd, ctx));\n\t}\n\n\t/** Step 4 delivery: silence filter → split → sequential send.\n\t *\n\t * When a streaming preview message exists (editMessageId), the final text\n\t * is folded INTO it instead of sent as a duplicate: non-truncated previews\n\t * get a final edit (covers silence-marker stripping), truncated previews\n\t * are edited to chunk 1 and the remaining chunks are sent as follow-ups. */\n\tasync function deliver(\n\t\tadapter: PlatformAdapter,\n\t\tevent: MessageEvent,\n\t\ttext: string,\n\t\topts: { reply: boolean; editMessageId?: string; previewTruncated?: boolean },\n\t): Promise<void> {\n\t\tconst filtered = applySilenceFilter(text);\n\t\tif (filtered === null) return;\n\t\tconst chunks = splitMessage(filtered, adapter.maxMessageLength);\n\t\tlet startIndex = 0;\n\t\tif (opts.editMessageId) {\n\t\t\tif (!opts.previewTruncated) {\n\t\t\t\t// Preview already holds the whole text: one final edit, no new message.\n\t\t\t\tawait adapter.editMessage(event.source.chatId, opts.editMessageId, filtered);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Preview was truncated: upgrade it to the first full chunk.\n\t\t\tawait adapter.editMessage(event.source.chatId, opts.editMessageId, chunks[0]);\n\t\t\tstartIndex = 1;\n\t\t}\n\t\tfor (let index = startIndex; index < chunks.length; index++) {\n\t\t\tconst result = await adapter.send(event.source.chatId, chunks[index], {\n\t\t\t\treplyTo: opts.reply && index === 0 ? event.messageId : undefined,\n\t\t\t\tthreadId: event.source.threadId,\n\t\t\t});\n\t\t\tif (!result.success) {\n\t\t\t\tconsole.error(`[gateway] send failed on ${adapter.platform}: ${result.error ?? \"unknown error\"}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function runTurn(\n\t\tadapter: PlatformAdapter,\n\t\tevent: MessageEvent,\n\t\tkey: string,\n\t\tcfg: GatewayConfig,\n\t): Promise<void> {\n\t\tconst streaming = cfg.streaming.enabled && typeof adapter.editMessage === \"function\";\n\t\tlet consumer: StreamConsumer | undefined;\n\t\tif (streaming) {\n\t\t\tconsumer = new StreamConsumer({\n\t\t\t\tsendInitial: async (text) => {\n\t\t\t\t\tconst result = await adapter.send(event.source.chatId, text, {\n\t\t\t\t\t\treplyTo: event.messageId,\n\t\t\t\t\t\tthreadId: event.source.threadId,\n\t\t\t\t\t});\n\t\t\t\t\treturn result.success ? (result.messageId ?? null) : null;\n\t\t\t\t},\n\t\t\t\tedit: async (messageId, text) => {\n\t\t\t\t\tawait adapter.editMessage(event.source.chatId, messageId, text);\n\t\t\t\t},\n\t\t\t\tintervalMs: cfg.streaming.editIntervalMs,\n\t\t\t\tthreshold: cfg.streaming.bufferThreshold,\n\t\t\t\tmaxPreview: adapter.maxMessageLength,\n\t\t\t});\n\t\t}\n\n\t\tconst callbacks: TurnCallbacks = {\n\t\t\tonDelta: consumer ? (delta) => consumer.push(delta) : undefined,\n\t\t\tonFollowUpResult: (text) => {\n\t\t\t\tvoid deliver(adapter, event, text, { reply: false });\n\t\t\t},\n\t\t\tonError: (message) => {\n\t\t\t\tvoid sendError(adapter, event, message);\n\t\t\t},\n\t\t};\n\n\t\tconst result = await runWithApprovalContext({ key, adapter, source: event.source }, () =>\n\t\t\tbridge.runTurn(key, event, callbacks),\n\t\t);\n\t\tif (result === QUEUED) return; // queued behind a running turn: no reply\n\t\tif (consumer) await consumer.finalize();\n\t\tawait deliver(adapter, event, result, {\n\t\t\treply: !consumer,\n\t\t\teditMessageId: consumer?.sentMessageId ?? undefined,\n\t\t\tpreviewTruncated: consumer?.truncated ?? false,\n\t\t});\n\t}\n\n\treturn {\n\t\tasync handleEvent(event: MessageEvent): Promise<void> {\n\t\t\tconst adapter = adapters.get(event.source.platform);\n\t\t\tif (!adapter) return;\n\t\t\tconst cfg = freshCfg();\n\t\t\ttry {\n\t\t\t\tif (isGroupGated(event, cfg)) return;\n\t\t\t\tif (await isDenied(adapter, event, cfg)) return;\n\t\t\t\tconst key = buildSessionKey(event.source, { groupSessionsPerUser: cfg.groupSessionsPerUser });\n\t\t\t\tif (deps.remoteControls) {\n\t\t\t\t\tconst binding = conversationBinding(key);\n\t\t\t\t\tif (binding?.owner) {\n\t\t\t\t\t\trequireGatewayOwner(event.source, cfg);\n\t\t\t\t\t\tif (binding.owner !== event.source.userId)\n\t\t\t\t\t\t\tthrow new Error(\"This session belongs to a different owner.\");\n\t\t\t\t\t}\n\t\t\t\t\tbindConversation(key, event.source);\n\t\t\t\t\tif (acceptGatewayInput(key, event)) return;\n\t\t\t\t}\n\t\t\t\tif (await handleSlash(adapter, event, key)) return;\n\t\t\t\tawait adapter.sendTyping(event.source.chatId, event.source.threadId).catch(() => {});\n\t\t\t\tawait runTurn(adapter, event, key, cfg);\n\t\t\t} catch (err) {\n\t\t\t\tawait sendError(adapter, event, err instanceof Error ? err.message : String(err)).catch(() => {});\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/gateway/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAU,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAI7G,OAAO,EAAE,KAAK,aAAa,EAA2D,MAAM,aAAa,CAAC;AAG1G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAYjD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,yEAAyE;AACzE,MAAM,WAAW,UAAU;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAAC;IAC5C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACzE,WAAW,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IACrD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,8HAA8H;IAC9H,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,MAAM;IACtB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAcD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiSrD","sourcesContent":["/**\n * lunR: gateway router.\n *\n * Every inbound MessageEvent flows through handleEvent:\n * 1. group gating (allowedChats / freeResponseChats / requireMention,\n * with adapter-supplied metadata.mentionedBot)\n * 2. authorization (authz.ts); denied DMs get a pairing code when the\n * behavior is \"pair\" and the user isn't rate-limited\n * 3. command registry (/new, /undo, /redo, /model, /sessions, /title,\n * /context, /compact, /thinking, /stop, /status, /help, /whoami)\n * 4. normal path: bridge.runTurn with a StreamConsumer when streaming is\n * enabled and the adapter supports edit; the final text is\n * silence-filtered and folded into a successful streaming preview edit.\n * Failed final edits and ordinary answers enter the durable outbox, which\n * acknowledges chunks in order and retries by destination.\n * Errors surface as a compact \"⚠ <one-line>\" — never a stack trace.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { type BridgeSession, type BridgeSessionStatus, QUEUED, type TurnCallbacks } from \"./agent-bridge.ts\";\nimport { registerGatewayApprovalHandler, runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized, requireAuthorized } from \"./authz.ts\";\nimport { CHAT_COMMANDS, runChatCommand, sendCommandReply } from \"./commands.ts\";\nimport { type GatewayConfig, gatewayConfigPath, loadGatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport { resolveWithinRoots } from \"./mobile-commands.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport {\n\tacceptGatewayInput,\n\tgatewayEpoch,\n\tinvalidateGatewayDialogs,\n\tqueueGatewayText,\n\trememberGatewayRole,\n} from \"./presenter.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { getSession as getStoredSession } from \"./store.ts\";\nimport { applySilenceFilter, StreamConsumer } from \"./stream.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\n/** Structural bridge shape (AgentBridge satisfies it; tests fake it). */\nexport interface BridgeLike {\n\trunTurn(key: string, event: MessageEvent, callbacks: TurnCallbacks): Promise<string>;\n\tabort(key: string): Promise<void> | void;\n\treset(key: string): void | Promise<void>;\n\tgetStatus(key: string): BridgeSessionStatus;\n\tgetSession(key: string, create?: boolean): Promise<BridgeSession | null>;\n\tpeekSession?(key: string): BridgeSession | undefined;\n\tswitchSession(key: string, sessionFile: string): Promise<void>;\n\tundo(key: string): Promise<{ userText: string }>;\n\tredo(key: string): Promise<void>;\n}\n\nexport interface RouterDeps {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\tpairing: PairingStore;\n\tbridge: BridgeLike;\n\t/** Reload gateway.json on every inbound event so CLI config edits (e.g. pair approve) are picked up by the running daemon. */\n\treloadConfig?: boolean;\n\tremoteControls?: boolean;\n}\n\nexport interface Router {\n\thandleEvent(event: MessageEvent): Promise<void>;\n}\n\nfunction oneLine(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nfunction formatPairingCode(code: string): string {\n\treturn code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;\n}\n\nfunction mentionedBot(event: MessageEvent): boolean {\n\treturn event.metadata?.mentionedBot === true;\n}\n\nexport function createRouter(deps: RouterDeps): Router {\n\tconst { adapters, cfg: initialCfg, pairing, bridge, reloadConfig } = deps;\n\n\t// Headless gateway sessions still prompt for large subagent launches in yolo mode.\n\tregisterGatewayApprovalHandler();\n\n\t/** Reload gateway.json on every inbound event so CLI edits (e.g. pair approve) take effect immediately. */\n\tfunction freshCfg(): GatewayConfig {\n\t\tif (!reloadConfig) return initialCfg;\n\t\ttry {\n\t\t\tif (existsSync(gatewayConfigPath())) {\n\t\t\t\treturn loadGatewayConfig();\n\t\t\t}\n\t\t} catch {\n\t\t\t// fall through to initial config\n\t\t}\n\t\treturn initialCfg;\n\t}\n\n\tasync function sendError(\n\t\tadapter: PlatformAdapter,\n\t\tevent: MessageEvent,\n\t\tkey: string,\n\t\tcfg: GatewayConfig,\n\t\tmessage: string,\n\t\tepoch: number,\n\t): Promise<void> {\n\t\tawait queueGatewayText(key, event.source, adapter, `⚠ ${oneLine(message)}`, {\n\t\t\tkind: \"notice\",\n\t\t\treplyTo: event.messageId,\n\t\t\tcfg,\n\t\t\tepoch,\n\t\t});\n\t}\n\n\t/** Step 1: group-chat early gating. Returns true when the event must be dropped. */\n\tfunction isGroupGated(event: MessageEvent, cfg: GatewayConfig): boolean {\n\t\tconst { source } = event;\n\t\tif (source.chatType === \"dm\") return false;\n\t\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\t\tif (!platformCfg) return true;\n\t\tconst freeResponse = platformCfg.freeResponseChats.includes(source.chatId);\n\t\tif (\n\t\t\tplatformCfg.allowedChats.length > 0 &&\n\t\t\t!platformCfg.allowedChats.includes(source.chatId) &&\n\t\t\t!freeResponse &&\n\t\t\t!mentionedBot(event)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tif (platformCfg.requireMention && !freeResponse && !mentionedBot(event)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Step 2: authorization; handles the denied-DM pairing flow. Returns true when denied. */\n\tasync function isDenied(adapter: PlatformAdapter, event: MessageEvent, cfg: GatewayConfig): Promise<boolean> {\n\t\tif (isAuthorized(event.source, cfg, pairing)) return false;\n\t\tconst { source } = event;\n\t\tif (source.chatType !== \"dm\") return true; // denied group: silent\n\t\tif (cfg.unauthorizedDmBehavior !== \"pair\") return true;\n\t\tconst code = pairing.issueCode(source.platform, source.userId);\n\t\tif (code === null) return true; // rate-limited or pending list full: stay silent\n\t\tawait adapter.send(\n\t\t\tsource.chatId,\n\t\t\t`Your lunR pairing code: ${formatPairingCode(code)}. On the computer running lunr, approve access with: lunr gateway pair approve ${source.platform} ${formatPairingCode(code)}. Approval grants full gateway access, including local projects and saved sessions.`,\n\t\t\t{ replyTo: event.messageId, threadId: source.threadId },\n\t\t);\n\t\treturn true;\n\t}\n\n\tfunction workspaceReadiness(event: MessageEvent, key: string, cfg: GatewayConfig): string | undefined {\n\t\tif (getStoredSession(key) || conversationBinding(key)?.cwd) return undefined;\n\t\tconst root = cfg.defaultProject;\n\t\tif (!root) return \"No default project selected. Use /project to choose an approved folder.\";\n\t\ttry {\n\t\t\tconst cwd = resolveWithinRoots(root, cfg.projectRoots ?? []);\n\t\t\tbindConversation(key, event.source, { cwd, owner: event.source.userId });\n\t\t\treturn undefined;\n\t\t} catch {\n\t\t\treturn \"Default project is no longer an approved folder. Use /project to choose an approved folder.\";\n\t\t}\n\t}\n\n\t/** Step 3: command registry. Returns true when the event was consumed. */\n\tasync function handleSlash(adapter: PlatformAdapter, event: MessageEvent, key: string): Promise<boolean> {\n\t\tconst text = event.text.trim();\n\t\tif (!text.startsWith(\"/\")) return false;\n\t\tconst firstToken = text.split(/\\s+/, 1)[0].toLowerCase();\n\t\tconst commandWord = firstToken.split(\"@\")[0].slice(1);\n\t\tconst args = text.slice(firstToken.length).trim();\n\t\tif (deps.remoteControls) {\n\t\t\tconst { handleMobileCommand, cancelMobileTransfer } = await import(\"./mobile-commands.ts\");\n\t\t\tif ([\"stop\", \"stopall\", \"new\", \"cancel\"].includes(commandWord)) cancelMobileTransfer(key);\n\t\t\tif (commandWord === \"cancel\") {\n\t\t\t\tinvalidateGatewayDialogs(key);\n\t\t\t\tawait adapter.send(event.source.chatId, \"Cancelled the pending selection or transfer.\", {\n\t\t\t\t\tthreadId: event.source.threadId,\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst consumed = await runWithApprovalContext({ key, adapter, source: event.source }, () =>\n\t\t\t\thandleMobileCommand({ key, event, adapter, bridge, cfg: freshCfg() }, commandWord, args),\n\t\t\t);\n\t\t\tif (consumed) return true;\n\t\t\tif (!event.text.startsWith(\"/\")) return false;\n\t\t}\n\t\tif (commandWord === \"start\") {\n\t\t\tconst issue = deps.remoteControls ? workspaceReadiness(event, key, freshCfg()) : undefined;\n\t\t\tif (issue) {\n\t\t\t\tawait sendCommandReply(adapter, event, issue);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst session = await bridge.getSession(key, true);\n\t\t\t\tconst models = await session?.modelRuntime.getAvailable();\n\t\t\t\tif (!models?.length)\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tadapter,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\"No authenticated model available. Use /login on your computer, then try /start again.\",\n\t\t\t\t\t);\n\t\t\t\telse if (\n\t\t\t\t\t!session?.model ||\n\t\t\t\t\t!models.some((model) => model.id === session.model?.id && model.provider === session.model.provider)\n\t\t\t\t)\n\t\t\t\t\tawait sendCommandReply(adapter, event, \"No available model selected. Use /model here to choose one.\");\n\t\t\t\telse\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tadapter,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\"Ready. Send a message to begin, or use /model to choose a model.\",\n\t\t\t\t\t);\n\t\t\t} catch {\n\t\t\t\tawait sendCommandReply(\n\t\t\t\t\tadapter,\n\t\t\t\t\tevent,\n\t\t\t\t\t\"Session setup failed. Run lunr gateway doctor on your computer, then try /start again.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tif (deps.remoteControls && commandWord === \"model\") {\n\t\t\tconst issue = workspaceReadiness(event, key, freshCfg());\n\t\t\tif (issue) {\n\t\t\t\tawait sendCommandReply(adapter, event, issue);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === commandWord || c.aliases?.includes(commandWord));\n\t\tif (!cmd) {\n\t\t\treturn event.source.chatType !== \"dm\";\n\t\t}\n\t\tconst ctx = {\n\t\t\tevent,\n\t\t\tkey,\n\t\t\tadapter,\n\t\t\tbridge,\n\t\t\tcfg: freshCfg(),\n\t\t\targs,\n\t\t\treply: (message: string) => sendCommandReply(adapter, event, message),\n\t\t};\n\t\treturn runWithApprovalContext({ key, adapter, source: event.source }, () => runChatCommand(cmd, ctx));\n\t}\n\n\t/** A successful preview is finalized by edit; failed edits fall back to a durable full reply. */\n\tasync function deliver(\n\t\tadapter: PlatformAdapter,\n\t\tevent: MessageEvent,\n\t\ttext: string,\n\t\tkey: string,\n\t\tcfg: GatewayConfig,\n\t\topts: { reply: boolean; editMessageId?: string; previewTruncated?: boolean },\n\t): Promise<void> {\n\t\tconst filtered = applySilenceFilter(text);\n\t\tif (filtered === null) return;\n\t\tif (opts.editMessageId) {\n\t\t\tconst chunks = splitMessage(filtered, adapter.maxMessageLength);\n\t\t\tconst edit = await adapter\n\t\t\t\t.editMessage(event.source.chatId, opts.editMessageId, opts.previewTruncated ? chunks[0] : filtered)\n\t\t\t\t.catch(() => ({ success: false }));\n\t\t\tif (edit.success) {\n\t\t\t\tif (!opts.previewTruncated) return;\n\t\t\t\tawait queueGatewayText(key, event.source, adapter, chunks.slice(1).join(\"\"), {\n\t\t\t\t\tkind: \"result\",\n\t\t\t\t\tcfg,\n\t\t\t\t\tchunks: chunks.slice(1),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tawait queueGatewayText(key, event.source, adapter, filtered, {\n\t\t\tkind: \"result\",\n\t\t\treplyTo: opts.reply ? event.messageId : undefined,\n\t\t\tcfg,\n\t\t});\n\t}\n\n\tasync function runTurn(\n\t\tadapter: PlatformAdapter,\n\t\tevent: MessageEvent,\n\t\tkey: string,\n\t\tcfg: GatewayConfig,\n\t): Promise<void> {\n\t\tconst epoch = gatewayEpoch(key);\n\t\tconst streaming = cfg.streaming.enabled && typeof adapter.editMessage === \"function\";\n\t\tlet consumer: StreamConsumer | undefined;\n\t\tif (streaming) {\n\t\t\tconsumer = new StreamConsumer({\n\t\t\t\tsendInitial: async (text) => {\n\t\t\t\t\tconst result = await adapter.send(event.source.chatId, text, {\n\t\t\t\t\t\treplyTo: event.messageId,\n\t\t\t\t\t\tthreadId: event.source.threadId,\n\t\t\t\t\t});\n\t\t\t\t\treturn result.success ? (result.messageId ?? null) : null;\n\t\t\t\t},\n\t\t\t\tedit: async (messageId, text) => {\n\t\t\t\t\tconst result = await adapter.editMessage(event.source.chatId, messageId, text);\n\t\t\t\t\tif (!result.success) throw new Error(result.error ?? \"Preview edit failed\");\n\t\t\t\t},\n\t\t\t\tintervalMs: cfg.streaming.editIntervalMs,\n\t\t\t\tthreshold: cfg.streaming.bufferThreshold,\n\t\t\t\tmaxPreview: adapter.maxMessageLength,\n\t\t\t});\n\t\t}\n\n\t\tconst callbacks: TurnCallbacks = {\n\t\t\tonDelta: consumer ? (delta) => consumer.push(delta) : undefined,\n\t\t\tonFollowUpResult: (text) => {\n\t\t\t\tvoid deliver(adapter, event, text, key, cfg, { reply: false }).catch((error) =>\n\t\t\t\t\tconsole.error(\"[gateway] follow-up delivery failed:\", error),\n\t\t\t\t);\n\t\t\t},\n\t\t\tonError: (message) => {\n\t\t\t\tvoid sendError(adapter, event, key, cfg, message, epoch).catch((error) =>\n\t\t\t\t\tconsole.error(\"[gateway] error delivery failed:\", error),\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\n\t\tconst result = await runWithApprovalContext({ key, adapter, source: event.source }, () =>\n\t\t\tbridge.runTurn(key, event, callbacks),\n\t\t);\n\t\tif (result === QUEUED) return; // queued behind a running turn: no reply\n\t\tif (consumer) await consumer.finalize();\n\t\tawait deliver(adapter, event, result, key, cfg, {\n\t\t\treply: !consumer,\n\t\t\teditMessageId: consumer?.sentMessageId ?? undefined,\n\t\t\tpreviewTruncated: consumer?.truncated ?? false,\n\t\t});\n\t}\n\n\treturn {\n\t\tasync handleEvent(event: MessageEvent): Promise<void> {\n\t\t\tconst adapter = adapters.get(event.source.platform);\n\t\t\tif (!adapter) return;\n\t\t\tconst cfg = freshCfg();\n\t\t\tconst key = buildSessionKey(event.source, { groupSessionsPerUser: cfg.groupSessionsPerUser });\n\t\t\tconst epoch = gatewayEpoch(key);\n\t\t\ttry {\n\t\t\t\tif (isGroupGated(event, cfg)) return;\n\t\t\t\trememberGatewayRole(key, event.source);\n\t\t\t\tif (await isDenied(adapter, event, cfg)) return;\n\t\t\t\tif (deps.remoteControls) {\n\t\t\t\t\tconst binding = conversationBinding(key);\n\t\t\t\t\tif (binding?.owner) {\n\t\t\t\t\t\trequireAuthorized(event.source, cfg);\n\t\t\t\t\t\tif (binding.owner !== event.source.userId)\n\t\t\t\t\t\t\tthrow new Error(\"This session belongs to a different user.\");\n\t\t\t\t\t}\n\t\t\t\t\tbindConversation(key, event.source);\n\t\t\t\t\tif (acceptGatewayInput(key, event)) return;\n\t\t\t\t}\n\t\t\t\tif (await handleSlash(adapter, event, key)) return;\n\t\t\t\tconst issue = deps.remoteControls ? workspaceReadiness(event, key, cfg) : undefined;\n\t\t\t\tif (issue) {\n\t\t\t\t\tawait sendCommandReply(adapter, event, issue);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait adapter.sendTyping(event.source.chatId, event.source.threadId).catch(() => {});\n\t\t\t\tawait runTurn(adapter, event, key, cfg);\n\t\t\t} catch (err) {\n\t\t\t\tawait sendError(adapter, event, key, cfg, err instanceof Error ? err.message : String(err), epoch).catch(\n\t\t\t\t\t() => {},\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
|