@cydm/happy-elves 0.1.0-beta.31 → 0.1.0-beta.310
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/apps/cli/dist/commands/app.js +10 -0
- package/apps/cli/dist/commands/daemon.js +92 -4
- package/apps/cli/dist/commands/gateway.d.ts +2 -0
- package/apps/cli/dist/commands/gateway.js +429 -0
- package/apps/cli/dist/commands/lib/args.js +49 -0
- package/apps/cli/dist/commands/lib/bootstrap-daemon.d.ts +2 -2
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +2 -2
- package/apps/cli/dist/commands/lib/doctor.d.ts +11 -0
- package/apps/cli/dist/commands/lib/doctor.js +276 -11
- package/apps/cli/dist/commands/lib/gateway-store.d.ts +38 -0
- package/apps/cli/dist/commands/lib/gateway-store.js +33 -0
- package/apps/cli/dist/commands/lib/index.d.ts +4 -2
- package/apps/cli/dist/commands/lib/index.js +2 -0
- package/apps/cli/dist/commands/lib/json.d.ts +2 -2
- package/apps/cli/dist/commands/lib/json.js +5 -2
- package/apps/cli/dist/commands/lib/local-daemon.d.ts +1 -0
- package/apps/cli/dist/commands/lib/local-daemon.js +14 -0
- package/apps/cli/dist/commands/lib/paths.d.ts +2 -0
- package/apps/cli/dist/commands/lib/paths.js +2 -0
- package/apps/cli/dist/commands/lib/relay-db-audit.d.ts +37 -0
- package/apps/cli/dist/commands/lib/relay-db-audit.js +300 -0
- package/apps/cli/dist/commands/lib/relay-http.d.ts +1 -1
- package/apps/cli/dist/commands/lib/scope.d.ts +1 -1
- package/apps/cli/dist/commands/lib/session-output.d.ts +15 -1
- package/apps/cli/dist/commands/lib/session-output.js +214 -6
- package/apps/cli/dist/commands/lib/session-view.d.ts +2 -2
- package/apps/cli/dist/commands/lib/status.js +5 -1
- package/apps/cli/dist/commands/lib/types.d.ts +4 -2
- package/apps/cli/dist/commands/lib/usage.js +123 -10
- package/apps/cli/dist/commands/lib/workspace.d.ts +2 -2
- package/apps/cli/dist/commands/lib/workspace.js +63 -10
- package/apps/cli/dist/commands/machine.js +50 -9
- package/apps/cli/dist/commands/memory.d.ts +2 -0
- package/apps/cli/dist/commands/memory.js +179 -0
- package/apps/cli/dist/commands/relay.js +29 -2
- package/apps/cli/dist/commands/release.d.ts +2 -0
- package/apps/cli/dist/commands/release.js +197 -0
- package/apps/cli/dist/commands/remote.js +10 -2
- package/apps/cli/dist/commands/schedule.d.ts +2 -0
- package/apps/cli/dist/commands/schedule.js +273 -0
- package/apps/cli/dist/commands/session.js +517 -5
- package/apps/cli/dist/commands/turn.js +225 -9
- package/apps/cli/dist/commands/workspace.d.ts +2 -0
- package/apps/cli/dist/commands/workspace.js +68 -0
- package/apps/cli/package.json +6 -0
- package/apps/daemon/dist/gateway/adapters.d.ts +38 -0
- package/apps/daemon/dist/gateway/adapters.js +2 -0
- package/apps/daemon/dist/gateway/feishu.d.ts +65 -0
- package/apps/daemon/dist/gateway/feishu.js +493 -0
- package/apps/daemon/dist/gateway/format.d.ts +2 -0
- package/apps/daemon/dist/gateway/format.js +19 -0
- package/apps/daemon/dist/gateway/handler.d.ts +7 -0
- package/apps/daemon/dist/gateway/handler.js +151 -0
- package/apps/daemon/dist/gateway/runtime.d.ts +33 -0
- package/apps/daemon/dist/gateway/runtime.js +850 -0
- package/apps/daemon/dist/gateway/store.d.ts +92 -0
- package/apps/daemon/dist/gateway/store.js +268 -0
- package/apps/daemon/dist/memory/handler.d.ts +7 -0
- package/apps/daemon/dist/memory/handler.js +105 -0
- package/apps/daemon/dist/memory/manager.d.ts +44 -0
- package/apps/daemon/dist/memory/manager.js +359 -0
- package/apps/daemon/dist/paths.d.ts +3 -0
- package/apps/daemon/dist/paths.js +3 -0
- package/apps/daemon/dist/relay/admission.d.ts +30 -0
- package/apps/daemon/dist/relay/admission.js +31 -0
- package/apps/daemon/dist/relay/connection.js +37 -1
- package/apps/daemon/dist/relay/devtools.d.ts +1 -1
- package/apps/daemon/dist/relay/register.js +2 -0
- package/apps/daemon/dist/relay/send.d.ts +2 -2
- package/apps/daemon/dist/relay/send.js +2 -0
- package/apps/daemon/dist/relay-http.d.ts +1 -1
- package/apps/daemon/dist/runtime/external-provider.d.ts +1 -1
- package/apps/daemon/dist/runtime/external-provider.js +187 -17
- package/apps/daemon/dist/runtime.d.ts +1 -2
- package/apps/daemon/dist/runtime.js +0 -3
- package/apps/daemon/dist/schedule/handler.d.ts +9 -0
- package/apps/daemon/dist/schedule/handler.js +148 -0
- package/apps/daemon/dist/schedule/runner.d.ts +21 -0
- package/apps/daemon/dist/schedule/runner.js +547 -0
- package/apps/daemon/dist/schedule/store.d.ts +9 -0
- package/apps/daemon/dist/schedule/store.js +143 -0
- package/apps/daemon/dist/session/create-session.d.ts +1 -1
- package/apps/daemon/dist/session/directory.d.ts +1 -1
- package/apps/daemon/dist/session/events.d.ts +15 -2
- package/apps/daemon/dist/session/events.js +76 -24
- package/apps/daemon/dist/session/file-preview.d.ts +1 -1
- package/apps/daemon/dist/session/lifecycle.d.ts +26 -3
- package/apps/daemon/dist/session/lifecycle.js +161 -22
- package/apps/daemon/dist/session/metadata.d.ts +2 -2
- package/apps/daemon/dist/session/metadata.js +42 -1
- package/apps/daemon/dist/session/primitives.d.ts +1 -1
- package/apps/daemon/dist/session/primitives.js +37 -6
- package/apps/daemon/dist/session/prompt.d.ts +28 -1
- package/apps/daemon/dist/session/prompt.js +340 -5
- package/apps/daemon/dist/session/reconcile.d.ts +3 -0
- package/apps/daemon/dist/session/reconcile.js +235 -0
- package/apps/daemon/dist/session/runtime-handle.d.ts +1 -1
- package/apps/daemon/dist/start.js +5 -1
- package/apps/daemon/dist/state.d.ts +2 -2
- package/apps/daemon/dist/turn-coordinator.d.ts +1 -0
- package/apps/daemon/dist/turn-coordinator.js +4 -0
- package/apps/daemon/dist/types.d.ts +16 -1
- package/apps/daemon/package.json +3 -25
- package/apps/relay/dist/connections.d.ts +2 -0
- package/apps/relay/dist/controller-handlers.d.ts +42 -2
- package/apps/relay/dist/controller-handlers.js +349 -37
- package/apps/relay/dist/db.js +296 -2
- package/apps/relay/dist/errors.d.ts +2 -1
- package/apps/relay/dist/errors.js +3 -1
- package/apps/relay/dist/http-routes.js +659 -33
- package/apps/relay/dist/http-schemas.d.ts +156 -0
- package/apps/relay/dist/http-schemas.js +85 -0
- package/apps/relay/dist/index.js +31 -1
- package/apps/relay/dist/machine-handlers.d.ts +15 -7
- package/apps/relay/dist/machine-handlers.js +567 -27
- package/apps/relay/dist/projections.d.ts +1 -1
- package/apps/relay/dist/projections.js +13 -0
- package/apps/relay/dist/relay-context.d.ts +41 -3
- package/apps/relay/dist/relay-context.js +244 -19
- package/apps/relay/dist/repositories.d.ts +2 -2
- package/apps/relay/dist/repositories.js +28 -1
- package/apps/relay/dist/runtime-bindings.d.ts +25 -0
- package/apps/relay/dist/runtime-bindings.js +49 -0
- package/apps/relay/dist/scope.d.ts +1 -1
- package/apps/relay/dist/types.d.ts +71 -1
- package/apps/relay/dist/websocket.js +2 -0
- package/package.json +5 -11
- package/packages/agent-sdk/dist/index.d.ts +80 -0
- package/packages/agent-sdk/dist/index.js +16 -0
- package/packages/client/dist/account.d.ts +35 -2
- package/packages/client/dist/account.js +98 -1
- package/packages/client/dist/client.d.ts +59 -6
- package/packages/client/dist/client.js +312 -58
- package/packages/client/dist/index.d.ts +1 -1
- package/packages/client/dist/parsers.d.ts +28 -2
- package/packages/client/dist/parsers.js +167 -0
- package/packages/client/dist/transport.d.ts +21 -1
- package/packages/client/dist/transport.js +69 -6
- package/packages/client/dist/types.d.ts +177 -1
- package/packages/client/dist/validation.d.ts +1 -1
- package/packages/runtime/dist/index.d.ts +66 -0
- package/packages/runtime-acpx/dist/index.d.ts +1 -1
- package/packages/runtime-acpx/dist/index.js +8 -0
- package/packages/runtime-cli/dist/claude-history.d.ts +1 -2
- package/packages/runtime-cli/dist/claude-history.js +0 -3
- package/packages/runtime-cli/dist/claude-session-store.d.ts +0 -1
- package/packages/runtime-cli/dist/claude-session-store.js +1 -1
- package/packages/runtime-cli/dist/claude-turn.d.ts +2 -1
- package/packages/runtime-cli/dist/claude-turn.js +29 -9
- package/packages/runtime-cli/dist/codex-app-server.d.ts +8 -3
- package/packages/runtime-cli/dist/codex-app-server.js +122 -25
- package/packages/runtime-cli/dist/codex-history.d.ts +1 -2
- package/packages/runtime-cli/dist/codex-history.js +11 -5
- package/packages/runtime-cli/dist/codex-protocol.d.ts +2 -2
- package/packages/runtime-cli/dist/codex-protocol.js +4 -4
- package/packages/runtime-cli/dist/history-merge.d.ts +1 -1
- package/packages/runtime-cli/dist/index.d.ts +1 -1
- package/packages/runtime-cli/dist/index.js +108 -2
- package/packages/runtime-cli/dist/permissions.d.ts +1 -1
- package/packages/runtime-cli/dist/session-store.d.ts +2 -1
- package/packages/runtime-cli/dist/session-store.js +9 -8
- package/packages/shared/dist/index.d.ts +2 -0
- package/packages/shared/dist/index.js +2 -0
- package/packages/shared/dist/protocol-schemas.d.ts +1077 -0
- package/packages/shared/dist/protocol-schemas.js +273 -0
- package/packages/shared/dist/protocol-types.d.ts +371 -3
- package/packages/shared/dist/protocol.d.ts +181 -2
- package/packages/shared/dist/protocol.js +197 -1
- package/packages/shared/dist/session-tags.d.ts +12 -0
- package/packages/shared/dist/session-tags.js +50 -0
- package/apps/cli/dist/commands/account.js.map +0 -1
- package/apps/cli/dist/commands/app.js.map +0 -1
- package/apps/cli/dist/commands/collect.js.map +0 -1
- package/apps/cli/dist/commands/command.js.map +0 -1
- package/apps/cli/dist/commands/config.js.map +0 -1
- package/apps/cli/dist/commands/daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/args.js.map +0 -1
- package/apps/cli/dist/commands/lib/audit.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-config.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap-output.js.map +0 -1
- package/apps/cli/dist/commands/lib/bootstrap.js.map +0 -1
- package/apps/cli/dist/commands/lib/config.js.map +0 -1
- package/apps/cli/dist/commands/lib/doctor.js.map +0 -1
- package/apps/cli/dist/commands/lib/exit.js.map +0 -1
- package/apps/cli/dist/commands/lib/index.js.map +0 -1
- package/apps/cli/dist/commands/lib/json.js.map +0 -1
- package/apps/cli/dist/commands/lib/local-daemon.js.map +0 -1
- package/apps/cli/dist/commands/lib/loop-store.js.map +0 -1
- package/apps/cli/dist/commands/lib/paths.js.map +0 -1
- package/apps/cli/dist/commands/lib/relay-http.js.map +0 -1
- package/apps/cli/dist/commands/lib/scope.js.map +0 -1
- package/apps/cli/dist/commands/lib/session-output.js.map +0 -1
- package/apps/cli/dist/commands/lib/session-view.js.map +0 -1
- package/apps/cli/dist/commands/lib/status.js.map +0 -1
- package/apps/cli/dist/commands/lib/types.js.map +0 -1
- package/apps/cli/dist/commands/lib/usage.js.map +0 -1
- package/apps/cli/dist/commands/lib/workspace.js.map +0 -1
- package/apps/cli/dist/commands/loop.js.map +0 -1
- package/apps/cli/dist/commands/machine.js.map +0 -1
- package/apps/cli/dist/commands/relay.js.map +0 -1
- package/apps/cli/dist/commands/remote.js.map +0 -1
- package/apps/cli/dist/commands/session.js.map +0 -1
- package/apps/cli/dist/commands/token.js.map +0 -1
- package/apps/cli/dist/commands/turn.js.map +0 -1
- package/apps/cli/dist/errors.js.map +0 -1
- package/apps/cli/dist/index.js.map +0 -1
- package/apps/daemon/dist/app.js.map +0 -1
- package/apps/daemon/dist/args.js.map +0 -1
- package/apps/daemon/dist/audit.js.map +0 -1
- package/apps/daemon/dist/cli.js.map +0 -1
- package/apps/daemon/dist/config.js.map +0 -1
- package/apps/daemon/dist/errors.js.map +0 -1
- package/apps/daemon/dist/loop/runner.js.map +0 -1
- package/apps/daemon/dist/loop/store.js.map +0 -1
- package/apps/daemon/dist/pair.js.map +0 -1
- package/apps/daemon/dist/paths.js.map +0 -1
- package/apps/daemon/dist/process.js.map +0 -1
- package/apps/daemon/dist/relay/connection.js.map +0 -1
- package/apps/daemon/dist/relay/devtools.js.map +0 -1
- package/apps/daemon/dist/relay/register.js.map +0 -1
- package/apps/daemon/dist/relay/send.js.map +0 -1
- package/apps/daemon/dist/relay-http.js.map +0 -1
- package/apps/daemon/dist/runtime/external-provider.js.map +0 -1
- package/apps/daemon/dist/runtime.js.map +0 -1
- package/apps/daemon/dist/session/create-session.js.map +0 -1
- package/apps/daemon/dist/session/directory.js.map +0 -1
- package/apps/daemon/dist/session/events.js.map +0 -1
- package/apps/daemon/dist/session/file-preview.js.map +0 -1
- package/apps/daemon/dist/session/lifecycle.js.map +0 -1
- package/apps/daemon/dist/session/metadata.js.map +0 -1
- package/apps/daemon/dist/session/primitives.js.map +0 -1
- package/apps/daemon/dist/session/prompt.js.map +0 -1
- package/apps/daemon/dist/session/runtime-handle.js.map +0 -1
- package/apps/daemon/dist/start.js.map +0 -1
- package/apps/daemon/dist/state.js.map +0 -1
- package/apps/daemon/dist/turn-coordinator.js.map +0 -1
- package/apps/daemon/dist/types.js.map +0 -1
- package/apps/daemon/dist/utils.js.map +0 -1
- package/apps/relay/dist/auth.js.map +0 -1
- package/apps/relay/dist/connections.js.map +0 -1
- package/apps/relay/dist/controller-handlers.js.map +0 -1
- package/apps/relay/dist/db.js.map +0 -1
- package/apps/relay/dist/errors.js.map +0 -1
- package/apps/relay/dist/http-routes.js.map +0 -1
- package/apps/relay/dist/http-schemas.js.map +0 -1
- package/apps/relay/dist/index.js.map +0 -1
- package/apps/relay/dist/machine-handlers.js.map +0 -1
- package/apps/relay/dist/projections.js.map +0 -1
- package/apps/relay/dist/relay-context.js.map +0 -1
- package/apps/relay/dist/repositories.js.map +0 -1
- package/apps/relay/dist/retention.js.map +0 -1
- package/apps/relay/dist/scope.js.map +0 -1
- package/apps/relay/dist/security.js.map +0 -1
- package/apps/relay/dist/types.js.map +0 -1
- package/apps/relay/dist/websocket.js.map +0 -1
- package/apps/relay/package.json +0 -29
- package/packages/agent-sdk/dist/index.d.ts.map +0 -1
- package/packages/agent-sdk/dist/index.js.map +0 -1
- package/packages/agent-sdk/package.json +0 -20
- package/packages/client/dist/account.d.ts.map +0 -1
- package/packages/client/dist/account.js.map +0 -1
- package/packages/client/dist/client.d.ts.map +0 -1
- package/packages/client/dist/client.js.map +0 -1
- package/packages/client/dist/errors.d.ts.map +0 -1
- package/packages/client/dist/errors.js.map +0 -1
- package/packages/client/dist/http.d.ts.map +0 -1
- package/packages/client/dist/http.js.map +0 -1
- package/packages/client/dist/index.d.ts.map +0 -1
- package/packages/client/dist/index.js.map +0 -1
- package/packages/client/dist/parsers.d.ts.map +0 -1
- package/packages/client/dist/parsers.js.map +0 -1
- package/packages/client/dist/transport.d.ts.map +0 -1
- package/packages/client/dist/transport.js.map +0 -1
- package/packages/client/dist/types.d.ts.map +0 -1
- package/packages/client/dist/types.js.map +0 -1
- package/packages/client/dist/validation.d.ts.map +0 -1
- package/packages/client/dist/validation.js.map +0 -1
- package/packages/client/package.json +0 -22
- package/packages/pie-provider/dist/index.d.ts.map +0 -1
- package/packages/pie-provider/dist/index.js.map +0 -1
- package/packages/pie-provider/package.json +0 -26
- package/packages/runtime/dist/index.d.ts.map +0 -1
- package/packages/runtime/dist/index.js.map +0 -1
- package/packages/runtime/package.json +0 -19
- package/packages/runtime-acpx/dist/index.d.ts.map +0 -1
- package/packages/runtime-acpx/dist/index.js.map +0 -1
- package/packages/runtime-acpx/package.json +0 -23
- package/packages/runtime-cli/dist/async-queue.d.ts.map +0 -1
- package/packages/runtime-cli/dist/async-queue.js.map +0 -1
- package/packages/runtime-cli/dist/claude-history.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-history.js.map +0 -1
- package/packages/runtime-cli/dist/claude-json.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-json.js.map +0 -1
- package/packages/runtime-cli/dist/claude-rename.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-rename.js.map +0 -1
- package/packages/runtime-cli/dist/claude-session-store.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-session-store.js.map +0 -1
- package/packages/runtime-cli/dist/claude-turn.d.ts.map +0 -1
- package/packages/runtime-cli/dist/claude-turn.js.map +0 -1
- package/packages/runtime-cli/dist/codex-app-server.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-app-server.js.map +0 -1
- package/packages/runtime-cli/dist/codex-approvals.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-approvals.js.map +0 -1
- package/packages/runtime-cli/dist/codex-history.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-history.js.map +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-json-rpc.js.map +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-lifecycle.js.map +0 -1
- package/packages/runtime-cli/dist/codex-protocol.d.ts.map +0 -1
- package/packages/runtime-cli/dist/codex-protocol.js.map +0 -1
- package/packages/runtime-cli/dist/command-resolver.d.ts.map +0 -1
- package/packages/runtime-cli/dist/command-resolver.js.map +0 -1
- package/packages/runtime-cli/dist/history-merge.d.ts.map +0 -1
- package/packages/runtime-cli/dist/history-merge.js.map +0 -1
- package/packages/runtime-cli/dist/index.d.ts.map +0 -1
- package/packages/runtime-cli/dist/index.js.map +0 -1
- package/packages/runtime-cli/dist/permissions.d.ts.map +0 -1
- package/packages/runtime-cli/dist/permissions.js.map +0 -1
- package/packages/runtime-cli/dist/session-store.d.ts.map +0 -1
- package/packages/runtime-cli/dist/session-store.js.map +0 -1
- package/packages/runtime-cli/package.json +0 -22
- package/packages/shared/dist/args.d.ts.map +0 -1
- package/packages/shared/dist/args.js.map +0 -1
- package/packages/shared/dist/crypto.d.ts.map +0 -1
- package/packages/shared/dist/crypto.js.map +0 -1
- package/packages/shared/dist/ids.d.ts.map +0 -1
- package/packages/shared/dist/ids.js.map +0 -1
- package/packages/shared/dist/index.d.ts.map +0 -1
- package/packages/shared/dist/index.js.map +0 -1
- package/packages/shared/dist/protocol-schemas.d.ts.map +0 -1
- package/packages/shared/dist/protocol-schemas.js.map +0 -1
- package/packages/shared/dist/protocol-types.d.ts.map +0 -1
- package/packages/shared/dist/protocol-types.js.map +0 -1
- package/packages/shared/dist/protocol.d.ts.map +0 -1
- package/packages/shared/dist/protocol.js.map +0 -1
- package/packages/shared/dist/session-name.d.ts.map +0 -1
- package/packages/shared/dist/session-name.js.map +0 -1
- package/packages/shared/dist/session-state.d.ts.map +0 -1
- package/packages/shared/dist/session-state.js.map +0 -1
- package/packages/shared/package.json +0 -24
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { encryptJson, } from "../../../../packages/shared/dist/index.js";
|
|
4
|
+
import { admitMachineCreateSession, admitMachinePrompt } from "../relay/admission.js";
|
|
5
|
+
import { sessions, sessionTurns } from "../state.js";
|
|
6
|
+
import { createFeishuGatewayAdapter } from "./feishu.js";
|
|
7
|
+
import { publicGatewayChannel, readGatewayStore, updateGatewayStore, } from "./store.js";
|
|
8
|
+
const gatewayReplyHistoryLimit = 500;
|
|
9
|
+
let gatewayServer;
|
|
10
|
+
let gatewayRuntimeConfig;
|
|
11
|
+
const gatewayAdapters = new Map();
|
|
12
|
+
let gatewaySyncChain = Promise.resolve();
|
|
13
|
+
function gatewayRuntimeEndpoint() {
|
|
14
|
+
return {
|
|
15
|
+
host: process.env.HAPPY_ELVES_GATEWAY_HOST || "127.0.0.1",
|
|
16
|
+
port: parseGatewayPort(process.env.HAPPY_ELVES_GATEWAY_PORT),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function startGatewayRuntime(config) {
|
|
20
|
+
gatewayRuntimeConfig = config;
|
|
21
|
+
await startGatewayDebugServer().catch((error) => {
|
|
22
|
+
gatewayServer = undefined;
|
|
23
|
+
console.error(`Gateway debug runtime failed to start: ${errorMessage(error)}`);
|
|
24
|
+
});
|
|
25
|
+
await syncGatewayRuntime(config);
|
|
26
|
+
}
|
|
27
|
+
async function startGatewayDebugServer() {
|
|
28
|
+
if (gatewayServer?.listening)
|
|
29
|
+
return;
|
|
30
|
+
const { host, port } = gatewayRuntimeEndpoint();
|
|
31
|
+
const server = createServer((request, response) => {
|
|
32
|
+
void handleGatewayHttpRequest(request, response).catch((error) => {
|
|
33
|
+
writeGatewayHttpJson(response, errorStatus(error), {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: {
|
|
36
|
+
code: errorCode(error),
|
|
37
|
+
message: error instanceof Error ? error.message : String(error),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
gatewayServer = server;
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
server.once("error", reject);
|
|
45
|
+
server.listen(port, host, () => {
|
|
46
|
+
server.off("error", reject);
|
|
47
|
+
resolve();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
console.log(`Gateway debug runtime listening on http://${host}:${port}`);
|
|
51
|
+
}
|
|
52
|
+
export async function stopGatewayRuntime() {
|
|
53
|
+
const server = gatewayServer;
|
|
54
|
+
gatewayServer = undefined;
|
|
55
|
+
gatewayRuntimeConfig = undefined;
|
|
56
|
+
const adapters = Array.from(gatewayAdapters.values());
|
|
57
|
+
gatewayAdapters.clear();
|
|
58
|
+
await Promise.allSettled(adapters.map((entry) => entry.adapter.stop()));
|
|
59
|
+
if (!server?.listening)
|
|
60
|
+
return;
|
|
61
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
62
|
+
}
|
|
63
|
+
export async function syncGatewayRuntime(config = gatewayRuntimeConfig) {
|
|
64
|
+
if (!config)
|
|
65
|
+
return;
|
|
66
|
+
gatewayRuntimeConfig = config;
|
|
67
|
+
const run = gatewaySyncChain.then(async () => {
|
|
68
|
+
const store = await readGatewayStore();
|
|
69
|
+
const desired = new Set();
|
|
70
|
+
for (const channel of store.channels) {
|
|
71
|
+
if (channel.type !== "lark" || !channel.enabled)
|
|
72
|
+
continue;
|
|
73
|
+
desired.add(channel.id);
|
|
74
|
+
const key = gatewayAdapterKey(channel);
|
|
75
|
+
const existing = gatewayAdapters.get(channel.id);
|
|
76
|
+
if (existing?.key === key)
|
|
77
|
+
continue;
|
|
78
|
+
if (existing) {
|
|
79
|
+
gatewayAdapters.delete(channel.id);
|
|
80
|
+
await existing.adapter.stop().catch((error) => setGatewayLastError(channel.id, error));
|
|
81
|
+
}
|
|
82
|
+
if (!channel.appId || !channel.appSecret) {
|
|
83
|
+
await setGatewayLastError(channel.id, new Error("Lark gateway requires appId and appSecret"));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const adapter = await createFeishuGatewayAdapter({
|
|
88
|
+
config,
|
|
89
|
+
channel,
|
|
90
|
+
readChannel: async () => {
|
|
91
|
+
const current = await readGatewayStore();
|
|
92
|
+
return current.channels.find((item) => item.id === channel.id);
|
|
93
|
+
},
|
|
94
|
+
sendInbound: async (input) => {
|
|
95
|
+
const currentStore = await readGatewayStore();
|
|
96
|
+
const currentChannel = currentStore.channels.find((item) => item.id === input.gatewayId);
|
|
97
|
+
if (!currentChannel?.enabled)
|
|
98
|
+
return { accepted: false };
|
|
99
|
+
if (!gatewayPolicyAllows(currentChannel, input)) {
|
|
100
|
+
await setGatewayLastError(currentChannel.id, new Error("Gateway sender/chat is not allowed by channel policy"));
|
|
101
|
+
return { accepted: false };
|
|
102
|
+
}
|
|
103
|
+
const delivery = await deliverGatewayMessage(config, currentChannel, {
|
|
104
|
+
externalConversationId: gatewayConversationId(input.destination),
|
|
105
|
+
externalMessageId: input.platformMessageId,
|
|
106
|
+
text: input.text,
|
|
107
|
+
destination: input.destination,
|
|
108
|
+
senderPlatformUserId: input.senderPlatformUserId,
|
|
109
|
+
senderDisplayName: input.senderDisplayName,
|
|
110
|
+
});
|
|
111
|
+
return { accepted: true, duplicate: delivery.duplicate, queued: delivery.queued };
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
await adapter.start();
|
|
115
|
+
gatewayAdapters.set(channel.id, { key, adapter });
|
|
116
|
+
await clearGatewayLastError(channel.id);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
await setGatewayLastError(channel.id, error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [channelId, entry] of Array.from(gatewayAdapters.entries())) {
|
|
123
|
+
if (desired.has(channelId))
|
|
124
|
+
continue;
|
|
125
|
+
gatewayAdapters.delete(channelId);
|
|
126
|
+
await entry.adapter.stop().catch((error) => setGatewayLastError(channelId, error));
|
|
127
|
+
}
|
|
128
|
+
await retryPendingGatewayReplies();
|
|
129
|
+
});
|
|
130
|
+
gatewaySyncChain = run.catch(() => undefined);
|
|
131
|
+
await run;
|
|
132
|
+
}
|
|
133
|
+
async function handleGatewayHttpRequest(request, response) {
|
|
134
|
+
if (request.method === "GET" && request.url === "/health") {
|
|
135
|
+
writeGatewayHttpJson(response, 200, { ok: true, service: "happy-elves-gateway-daemon" });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const { host, port } = gatewayRuntimeEndpoint();
|
|
139
|
+
const url = new URL(request.url ?? "/", `http://${host}:${port}`);
|
|
140
|
+
const match = /^\/gateway\/([^/]+)$/.exec(url.pathname);
|
|
141
|
+
if (request.method !== "POST" || !match) {
|
|
142
|
+
writeGatewayHttpJson(response, 404, { ok: false, error: { code: "NOT_FOUND", message: "Gateway route not found" } });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!gatewayRuntimeConfig)
|
|
146
|
+
throw gatewayError("Gateway runtime is not configured", "GATEWAY_NOT_READY", 503);
|
|
147
|
+
const channelId = decodeURIComponent(match[1]);
|
|
148
|
+
const store = await readGatewayStore();
|
|
149
|
+
const channel = store.channels.find((item) => item.id === channelId);
|
|
150
|
+
if (!channel?.enabled)
|
|
151
|
+
throw gatewayError("Gateway channel is disabled or missing", "GATEWAY_DISABLED", 404);
|
|
152
|
+
const body = await readJsonRequestBody(request);
|
|
153
|
+
if (channel.type === "lark" && isLarkChallenge(body)) {
|
|
154
|
+
writeGatewayHttpJson(response, 200, { challenge: body.challenge });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
assertGatewayToken(channel, request, body);
|
|
158
|
+
const message = channel.type === "lark" ? larkMessageFromBody(body) : fakeGatewayMessageFromBody(body);
|
|
159
|
+
const delivery = await deliverGatewayMessage(gatewayRuntimeConfig, channel, message);
|
|
160
|
+
writeGatewayHttpJson(response, 200, { ok: true, type: "gateway.deliver", data: delivery });
|
|
161
|
+
}
|
|
162
|
+
export async function deliverGatewayMessage(config, channel, message) {
|
|
163
|
+
const externalConversationId = message.externalConversationId;
|
|
164
|
+
const externalMessageId = message.externalMessageId;
|
|
165
|
+
const requestId = stableGatewayId("gw_msg", channel.id, externalConversationId, externalMessageId);
|
|
166
|
+
const turnId = stableGatewayId("turn_gw", channel.id, externalConversationId, externalMessageId);
|
|
167
|
+
const receivedAt = new Date().toISOString();
|
|
168
|
+
await updateGatewayStore((current) => ({
|
|
169
|
+
...current,
|
|
170
|
+
channels: current.channels.map((item) => item.id === channel.id
|
|
171
|
+
? {
|
|
172
|
+
...item,
|
|
173
|
+
lastReceived: {
|
|
174
|
+
externalConversationId,
|
|
175
|
+
externalMessageId,
|
|
176
|
+
requestId,
|
|
177
|
+
turnId,
|
|
178
|
+
at: receivedAt,
|
|
179
|
+
},
|
|
180
|
+
updatedAt: receivedAt,
|
|
181
|
+
}
|
|
182
|
+
: item),
|
|
183
|
+
}));
|
|
184
|
+
let store = await readGatewayStore();
|
|
185
|
+
let mapping = store.mappings.find((item) => item.channelId === channel.id && item.externalConversationId === externalConversationId);
|
|
186
|
+
const createRequestId = stableGatewayId("gw_create", channel.id, externalConversationId);
|
|
187
|
+
if (!mapping) {
|
|
188
|
+
mapping = await createGatewayMapping(config, channel, externalConversationId, createRequestId);
|
|
189
|
+
store = await readGatewayStore();
|
|
190
|
+
}
|
|
191
|
+
const common = {
|
|
192
|
+
channelId: channel.id,
|
|
193
|
+
externalConversationId,
|
|
194
|
+
externalMessageId,
|
|
195
|
+
sessionId: mapping.sessionId,
|
|
196
|
+
requestId,
|
|
197
|
+
turnId,
|
|
198
|
+
};
|
|
199
|
+
if (store.replyHistory.some((item) => item.channelId === channel.id && item.requestId === requestId)) {
|
|
200
|
+
return { ...common, admission: "replayed", dispatch: "queued", duplicate: true };
|
|
201
|
+
}
|
|
202
|
+
if (store.pendingReplies.some((item) => item.channelId === channel.id && item.requestId === requestId)) {
|
|
203
|
+
return { ...common, admission: "replayed", dispatch: "queued", duplicate: true };
|
|
204
|
+
}
|
|
205
|
+
if (store.queue.some((item) => item.channelId === channel.id && item.requestId === requestId)) {
|
|
206
|
+
return { ...common, dispatch: "queued", queued: true, duplicate: true };
|
|
207
|
+
}
|
|
208
|
+
if (sessionTurns.activeTurnId(mapping.sessionId)) {
|
|
209
|
+
await enqueueGatewayMessage({
|
|
210
|
+
channel,
|
|
211
|
+
mapping,
|
|
212
|
+
externalConversationId,
|
|
213
|
+
externalMessageId,
|
|
214
|
+
requestId,
|
|
215
|
+
turnId,
|
|
216
|
+
text: message.text,
|
|
217
|
+
destination: message.destination,
|
|
218
|
+
senderPlatformUserId: message.senderPlatformUserId,
|
|
219
|
+
senderDisplayName: message.senderDisplayName,
|
|
220
|
+
at: receivedAt,
|
|
221
|
+
});
|
|
222
|
+
return { ...common, dispatch: "queued", queued: true };
|
|
223
|
+
}
|
|
224
|
+
return await admitGatewayPromptWithMappingRecovery(config, channel, mapping, {
|
|
225
|
+
...common,
|
|
226
|
+
text: message.text,
|
|
227
|
+
destination: message.destination,
|
|
228
|
+
senderPlatformUserId: message.senderPlatformUserId,
|
|
229
|
+
senderDisplayName: message.senderDisplayName,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
export async function notifyGatewayConversation(channel, input) {
|
|
233
|
+
if (!channel.enabled)
|
|
234
|
+
throw gatewayError("Gateway channel is disabled", "GATEWAY_DISABLED", 400);
|
|
235
|
+
const adapter = gatewayAdapters.get(channel.id)?.adapter;
|
|
236
|
+
if (!adapter)
|
|
237
|
+
throw gatewayError("Gateway adapter is not listening", "GATEWAY_NOT_LISTENING", 503);
|
|
238
|
+
const destination = destinationFromGatewayConversationId(input.externalConversationId);
|
|
239
|
+
const deliveryId = stableGatewayId("gw_notify", channel.id, input.externalConversationId, input.externalMessageId);
|
|
240
|
+
const result = await adapter.send({
|
|
241
|
+
deliveryId,
|
|
242
|
+
destination,
|
|
243
|
+
payload: {
|
|
244
|
+
type: "notice",
|
|
245
|
+
title: "Happy Elves",
|
|
246
|
+
text: input.text,
|
|
247
|
+
severity: "info",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
const repliedAt = new Date().toISOString();
|
|
251
|
+
const lastReplied = {
|
|
252
|
+
externalConversationId: input.externalConversationId,
|
|
253
|
+
externalMessageId: input.externalMessageId,
|
|
254
|
+
deliveryId,
|
|
255
|
+
platformMessageId: result.platformMessageId,
|
|
256
|
+
at: repliedAt,
|
|
257
|
+
};
|
|
258
|
+
await updateGatewayStore((current) => ({
|
|
259
|
+
...current,
|
|
260
|
+
channels: current.channels.map((item) => item.id === channel.id
|
|
261
|
+
? { ...item, lastError: undefined, lastReplied, updatedAt: repliedAt }
|
|
262
|
+
: item),
|
|
263
|
+
replyHistory: [
|
|
264
|
+
{
|
|
265
|
+
channelId: channel.id,
|
|
266
|
+
externalConversationId: input.externalConversationId,
|
|
267
|
+
externalMessageId: input.externalMessageId,
|
|
268
|
+
deliveryId,
|
|
269
|
+
platformMessageId: result.platformMessageId,
|
|
270
|
+
repliedAt,
|
|
271
|
+
},
|
|
272
|
+
...current.replyHistory.filter((item) => !(item.channelId === channel.id && item.deliveryId === deliveryId)),
|
|
273
|
+
].slice(0, gatewayReplyHistoryLimit),
|
|
274
|
+
}));
|
|
275
|
+
return {
|
|
276
|
+
channelId: channel.id,
|
|
277
|
+
externalConversationId: input.externalConversationId,
|
|
278
|
+
externalMessageId: input.externalMessageId,
|
|
279
|
+
deliveryId,
|
|
280
|
+
platformMessageId: result.platformMessageId,
|
|
281
|
+
dispatch: "delivered",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export async function handleGatewayTurnTerminal(config, input) {
|
|
285
|
+
await deliverPendingGatewayReply(config, input);
|
|
286
|
+
await drainGatewayQueue(config, input.sessionId);
|
|
287
|
+
}
|
|
288
|
+
async function admitGatewayPromptWithMappingRecovery(config, channel, mapping, input) {
|
|
289
|
+
try {
|
|
290
|
+
return await admitGatewayPromptNow(config, channel, input);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
if (!canRecoverGatewayMapping(channel, error))
|
|
294
|
+
throw error;
|
|
295
|
+
const recovered = await recoverGatewayMapping(config, channel, mapping, input.externalConversationId, input.requestId);
|
|
296
|
+
return await admitGatewayPromptNow(config, channel, {
|
|
297
|
+
...input,
|
|
298
|
+
sessionId: recovered.sessionId,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function recoverGatewayMapping(config, channel, staleMapping, externalConversationId, recoverySeed) {
|
|
303
|
+
const createRequestId = stableGatewayId("gw_create", channel.id, externalConversationId, "recover", recoverySeed);
|
|
304
|
+
const sessionId = stableGatewayId("ses_gw", channel.id, externalConversationId, "recover", recoverySeed);
|
|
305
|
+
const mapping = await createGatewayMapping(config, channel, externalConversationId, createRequestId, sessionId);
|
|
306
|
+
if (mapping.sessionId === staleMapping.sessionId)
|
|
307
|
+
return mapping;
|
|
308
|
+
const now = new Date().toISOString();
|
|
309
|
+
await updateGatewayStore((current) => ({
|
|
310
|
+
...current,
|
|
311
|
+
queue: current.queue.map((item) => item.channelId === channel.id && item.externalConversationId === externalConversationId
|
|
312
|
+
? { ...item, sessionId: mapping.sessionId, updatedAt: now }
|
|
313
|
+
: item),
|
|
314
|
+
}));
|
|
315
|
+
return mapping;
|
|
316
|
+
}
|
|
317
|
+
function canRecoverGatewayMapping(channel, error) {
|
|
318
|
+
if (channel.targetSessionId)
|
|
319
|
+
return false;
|
|
320
|
+
if (!channel.targetCwd)
|
|
321
|
+
return false;
|
|
322
|
+
return errorCode(error) === "SESSION_NOT_FOUND" || errorCode(error) === "SESSION_CLOSED";
|
|
323
|
+
}
|
|
324
|
+
async function createGatewayMapping(config, channel, externalConversationId, createRequestId, recoveredSessionId) {
|
|
325
|
+
if (channel.targetSessionId) {
|
|
326
|
+
const now = new Date().toISOString();
|
|
327
|
+
const mapping = {
|
|
328
|
+
channelId: channel.id,
|
|
329
|
+
externalConversationId,
|
|
330
|
+
sessionId: channel.targetSessionId,
|
|
331
|
+
createRequestId,
|
|
332
|
+
createdAt: now,
|
|
333
|
+
updatedAt: now,
|
|
334
|
+
};
|
|
335
|
+
await updateGatewayStore((current) => ({
|
|
336
|
+
...current,
|
|
337
|
+
mappings: [mapping, ...current.mappings.filter((item) => !(item.channelId === channel.id && item.externalConversationId === externalConversationId))],
|
|
338
|
+
}));
|
|
339
|
+
return mapping;
|
|
340
|
+
}
|
|
341
|
+
if (!channel.targetCwd)
|
|
342
|
+
throw gatewayError("Gateway channel needs target cwd or target session", "GATEWAY_TARGET_MISSING", 400);
|
|
343
|
+
const sessionId = recoveredSessionId ?? stableGatewayId("ses_gw", channel.id, externalConversationId);
|
|
344
|
+
const create = await admitMachineCreateSession(config, {
|
|
345
|
+
requestId: createRequestId,
|
|
346
|
+
sessionId,
|
|
347
|
+
agent: channel.defaultAgent,
|
|
348
|
+
cwd: channel.targetCwd,
|
|
349
|
+
name: `Gateway ${channel.type}: ${externalConversationId}`,
|
|
350
|
+
...(channel.defaultModel ? { model: channel.defaultModel } : {}),
|
|
351
|
+
encryptedMetadata: await encryptJson(config.accountSecret, `gateway:create:${createRequestId}`, {
|
|
352
|
+
createdFrom: "gateway",
|
|
353
|
+
gateway: {
|
|
354
|
+
channelId: channel.id,
|
|
355
|
+
channelType: channel.type,
|
|
356
|
+
externalConversationId,
|
|
357
|
+
},
|
|
358
|
+
agent: channel.defaultAgent,
|
|
359
|
+
cwd: channel.targetCwd,
|
|
360
|
+
name: `Gateway ${channel.type}: ${externalConversationId}`,
|
|
361
|
+
...(channel.defaultModel ? { model: channel.defaultModel } : {}),
|
|
362
|
+
}),
|
|
363
|
+
});
|
|
364
|
+
if (!create.sessionId)
|
|
365
|
+
throw gatewayError("Gateway session admission did not return a sessionId", "GATEWAY_CREATE_FAILED", 502);
|
|
366
|
+
const now = new Date().toISOString();
|
|
367
|
+
const mapping = {
|
|
368
|
+
channelId: channel.id,
|
|
369
|
+
externalConversationId,
|
|
370
|
+
sessionId: create.sessionId,
|
|
371
|
+
createRequestId,
|
|
372
|
+
createdAt: now,
|
|
373
|
+
updatedAt: now,
|
|
374
|
+
};
|
|
375
|
+
await updateGatewayStore((current) => ({
|
|
376
|
+
...current,
|
|
377
|
+
mappings: [mapping, ...current.mappings.filter((item) => !(item.channelId === channel.id && item.externalConversationId === externalConversationId))],
|
|
378
|
+
}));
|
|
379
|
+
return mapping;
|
|
380
|
+
}
|
|
381
|
+
async function admitGatewayPromptNow(config, channel, input) {
|
|
382
|
+
const deliveryId = stableGatewayId("gw_delivery", channel.id, input.externalConversationId, input.externalMessageId, input.requestId);
|
|
383
|
+
const admittedAt = new Date().toISOString();
|
|
384
|
+
if (input.destination) {
|
|
385
|
+
await upsertPendingReply({
|
|
386
|
+
channelId: channel.id,
|
|
387
|
+
externalConversationId: input.externalConversationId,
|
|
388
|
+
externalMessageId: input.externalMessageId,
|
|
389
|
+
sessionId: input.sessionId,
|
|
390
|
+
requestId: input.requestId,
|
|
391
|
+
turnId: input.turnId,
|
|
392
|
+
deliveryId,
|
|
393
|
+
destination: input.destination,
|
|
394
|
+
status: "pending",
|
|
395
|
+
attempts: 0,
|
|
396
|
+
createdAt: admittedAt,
|
|
397
|
+
updatedAt: admittedAt,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const sent = await admitMachinePrompt(config, {
|
|
402
|
+
requestId: input.requestId,
|
|
403
|
+
sessionId: input.sessionId,
|
|
404
|
+
turnId: input.turnId,
|
|
405
|
+
encryptedPrompt: await encryptJson(config.accountSecret, `gateway:prompt:${input.sessionId}:${input.turnId}`, {
|
|
406
|
+
type: "user_prompt",
|
|
407
|
+
text: input.text,
|
|
408
|
+
}),
|
|
409
|
+
permissionMode: "approve-reads",
|
|
410
|
+
...(channel.defaultModel ? { model: channel.defaultModel } : {}),
|
|
411
|
+
});
|
|
412
|
+
const turnId = sent.turnId ?? input.turnId;
|
|
413
|
+
const lastAdmitted = {
|
|
414
|
+
externalConversationId: input.externalConversationId,
|
|
415
|
+
externalMessageId: input.externalMessageId,
|
|
416
|
+
requestId: input.requestId,
|
|
417
|
+
sessionId: input.sessionId,
|
|
418
|
+
turnId,
|
|
419
|
+
deliveryId,
|
|
420
|
+
at: admittedAt,
|
|
421
|
+
};
|
|
422
|
+
await updateGatewayStore((current) => ({
|
|
423
|
+
...current,
|
|
424
|
+
channels: current.channels.map((item) => item.id === channel.id ? {
|
|
425
|
+
...item,
|
|
426
|
+
lastError: undefined,
|
|
427
|
+
lastAdmitted,
|
|
428
|
+
lastDeliveredMessage: {
|
|
429
|
+
externalConversationId: input.externalConversationId,
|
|
430
|
+
externalMessageId: input.externalMessageId,
|
|
431
|
+
requestId: input.requestId,
|
|
432
|
+
sessionId: input.sessionId,
|
|
433
|
+
turnId,
|
|
434
|
+
deliveredAt: admittedAt,
|
|
435
|
+
},
|
|
436
|
+
updatedAt: admittedAt,
|
|
437
|
+
} : item),
|
|
438
|
+
mappings: current.mappings.map((item) => item.channelId === channel.id && item.externalConversationId === input.externalConversationId
|
|
439
|
+
? { ...item, sessionId: input.sessionId, updatedAt: admittedAt }
|
|
440
|
+
: item),
|
|
441
|
+
queue: current.queue.filter((item) => !(item.channelId === channel.id && item.requestId === input.requestId)),
|
|
442
|
+
pendingReplies: sent.admission === "replayed" && !current.pendingReplies.some((item) => item.channelId === channel.id && item.requestId === input.requestId)
|
|
443
|
+
? current.pendingReplies.filter((item) => !(item.channelId === channel.id && item.requestId === input.requestId))
|
|
444
|
+
: current.pendingReplies,
|
|
445
|
+
}));
|
|
446
|
+
return {
|
|
447
|
+
channelId: channel.id,
|
|
448
|
+
externalConversationId: input.externalConversationId,
|
|
449
|
+
externalMessageId: input.externalMessageId,
|
|
450
|
+
sessionId: input.sessionId,
|
|
451
|
+
requestId: input.requestId,
|
|
452
|
+
turnId,
|
|
453
|
+
admission: sent.admission,
|
|
454
|
+
dispatch: sent.dispatch,
|
|
455
|
+
duplicate: sent.admission === "replayed",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
await updateGatewayStore((current) => ({
|
|
460
|
+
...current,
|
|
461
|
+
pendingReplies: current.pendingReplies.filter((item) => !(item.channelId === channel.id && item.requestId === input.requestId && !item.payload)),
|
|
462
|
+
channels: current.channels.map((item) => item.id === channel.id
|
|
463
|
+
? { ...item, lastError: errorMessage(error), updatedAt: new Date().toISOString() }
|
|
464
|
+
: item),
|
|
465
|
+
}));
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async function upsertPendingReply(reply) {
|
|
470
|
+
await updateGatewayStore((current) => ({
|
|
471
|
+
...current,
|
|
472
|
+
pendingReplies: [
|
|
473
|
+
{
|
|
474
|
+
...reply,
|
|
475
|
+
...(current.pendingReplies.find((item) => item.channelId === reply.channelId && item.requestId === reply.requestId) ?? {}),
|
|
476
|
+
...reply,
|
|
477
|
+
},
|
|
478
|
+
...current.pendingReplies.filter((item) => !(item.channelId === reply.channelId && item.requestId === reply.requestId)),
|
|
479
|
+
],
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
async function enqueueGatewayMessage(input) {
|
|
483
|
+
const queued = {
|
|
484
|
+
channelId: input.channel.id,
|
|
485
|
+
externalConversationId: input.externalConversationId,
|
|
486
|
+
externalMessageId: input.externalMessageId,
|
|
487
|
+
sessionId: input.mapping.sessionId,
|
|
488
|
+
requestId: input.requestId,
|
|
489
|
+
turnId: input.turnId,
|
|
490
|
+
text: input.text,
|
|
491
|
+
destination: input.destination,
|
|
492
|
+
senderPlatformUserId: input.senderPlatformUserId,
|
|
493
|
+
senderDisplayName: input.senderDisplayName,
|
|
494
|
+
queuedAt: input.at,
|
|
495
|
+
updatedAt: input.at,
|
|
496
|
+
};
|
|
497
|
+
await updateGatewayStore((current) => ({
|
|
498
|
+
...current,
|
|
499
|
+
queue: [queued, ...current.queue.filter((item) => !(item.channelId === input.channel.id && item.requestId === input.requestId))],
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
async function drainGatewayQueue(config, sessionId) {
|
|
503
|
+
const store = await readGatewayStore();
|
|
504
|
+
const admittedSessions = new Set();
|
|
505
|
+
const queued = [...store.queue].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt));
|
|
506
|
+
for (const item of queued) {
|
|
507
|
+
if (sessionId && item.sessionId !== sessionId)
|
|
508
|
+
continue;
|
|
509
|
+
if (admittedSessions.has(item.sessionId))
|
|
510
|
+
continue;
|
|
511
|
+
if (sessionTurns.activeTurnId(item.sessionId))
|
|
512
|
+
continue;
|
|
513
|
+
const channel = store.channels.find((candidate) => candidate.id === item.channelId);
|
|
514
|
+
if (!channel?.enabled)
|
|
515
|
+
continue;
|
|
516
|
+
try {
|
|
517
|
+
const mapping = {
|
|
518
|
+
channelId: channel.id,
|
|
519
|
+
externalConversationId: item.externalConversationId,
|
|
520
|
+
sessionId: item.sessionId,
|
|
521
|
+
createRequestId: stableGatewayId("gw_create", channel.id, item.externalConversationId),
|
|
522
|
+
createdAt: item.queuedAt,
|
|
523
|
+
updatedAt: item.updatedAt,
|
|
524
|
+
};
|
|
525
|
+
await admitGatewayPromptWithMappingRecovery(config, channel, mapping, {
|
|
526
|
+
externalConversationId: item.externalConversationId,
|
|
527
|
+
externalMessageId: item.externalMessageId,
|
|
528
|
+
sessionId: item.sessionId,
|
|
529
|
+
requestId: item.requestId,
|
|
530
|
+
turnId: item.turnId,
|
|
531
|
+
text: item.text,
|
|
532
|
+
destination: item.destination,
|
|
533
|
+
senderPlatformUserId: item.senderPlatformUserId,
|
|
534
|
+
senderDisplayName: item.senderDisplayName,
|
|
535
|
+
});
|
|
536
|
+
admittedSessions.add(item.sessionId);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
await setGatewayLastError(item.channelId, error);
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function deliverPendingGatewayReply(config, input) {
|
|
545
|
+
const store = await readGatewayStore();
|
|
546
|
+
const pending = store.pendingReplies.find((item) => item.sessionId === input.sessionId
|
|
547
|
+
&& (item.turnId === input.turnId || (input.requestId && item.requestId === input.requestId)));
|
|
548
|
+
if (!pending)
|
|
549
|
+
return;
|
|
550
|
+
const payload = gatewayTerminalPayload(config, input);
|
|
551
|
+
await updateGatewayStore((current) => ({
|
|
552
|
+
...current,
|
|
553
|
+
pendingReplies: current.pendingReplies.map((item) => item.channelId === pending.channelId && item.requestId === pending.requestId
|
|
554
|
+
? { ...item, payload, updatedAt: new Date().toISOString() }
|
|
555
|
+
: item),
|
|
556
|
+
}));
|
|
557
|
+
await sendPendingGatewayReply({ ...pending, payload });
|
|
558
|
+
}
|
|
559
|
+
async function retryPendingGatewayReplies() {
|
|
560
|
+
const store = await readGatewayStore();
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
for (const pending of store.pendingReplies) {
|
|
563
|
+
if (!pending.payload || pending.status !== "pending")
|
|
564
|
+
continue;
|
|
565
|
+
if (pending.nextAttemptAt && Date.parse(pending.nextAttemptAt) > now)
|
|
566
|
+
continue;
|
|
567
|
+
await sendPendingGatewayReply(pending);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async function sendPendingGatewayReply(pending) {
|
|
571
|
+
if (!pending.destination || !pending.payload)
|
|
572
|
+
return;
|
|
573
|
+
const channel = (await readGatewayStore()).channels.find((item) => item.id === pending.channelId);
|
|
574
|
+
if (!channel?.enabled)
|
|
575
|
+
return;
|
|
576
|
+
const adapter = gatewayAdapters.get(channel.id)?.adapter;
|
|
577
|
+
if (!adapter) {
|
|
578
|
+
await markPendingReplyFailed(pending, new Error("Gateway adapter is not listening"));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const result = await adapter.send({
|
|
583
|
+
deliveryId: pending.deliveryId,
|
|
584
|
+
destination: pending.destination,
|
|
585
|
+
payload: pending.payload,
|
|
586
|
+
});
|
|
587
|
+
const repliedAt = new Date().toISOString();
|
|
588
|
+
const lastReplied = {
|
|
589
|
+
externalConversationId: pending.externalConversationId,
|
|
590
|
+
externalMessageId: pending.externalMessageId,
|
|
591
|
+
requestId: pending.requestId,
|
|
592
|
+
sessionId: pending.sessionId,
|
|
593
|
+
turnId: pending.turnId,
|
|
594
|
+
deliveryId: pending.deliveryId,
|
|
595
|
+
platformMessageId: result.platformMessageId,
|
|
596
|
+
at: repliedAt,
|
|
597
|
+
};
|
|
598
|
+
await updateGatewayStore((current) => ({
|
|
599
|
+
...current,
|
|
600
|
+
channels: current.channels.map((item) => item.id === pending.channelId
|
|
601
|
+
? { ...item, lastError: undefined, lastReplied, updatedAt: repliedAt }
|
|
602
|
+
: item),
|
|
603
|
+
pendingReplies: current.pendingReplies.filter((item) => !(item.channelId === pending.channelId && item.requestId === pending.requestId)),
|
|
604
|
+
replyHistory: [
|
|
605
|
+
{
|
|
606
|
+
channelId: pending.channelId,
|
|
607
|
+
externalConversationId: pending.externalConversationId,
|
|
608
|
+
externalMessageId: pending.externalMessageId,
|
|
609
|
+
sessionId: pending.sessionId,
|
|
610
|
+
requestId: pending.requestId,
|
|
611
|
+
turnId: pending.turnId,
|
|
612
|
+
deliveryId: pending.deliveryId,
|
|
613
|
+
platformMessageId: result.platformMessageId,
|
|
614
|
+
repliedAt,
|
|
615
|
+
},
|
|
616
|
+
...current.replyHistory.filter((item) => !(item.channelId === pending.channelId && item.requestId === pending.requestId)),
|
|
617
|
+
].slice(0, gatewayReplyHistoryLimit),
|
|
618
|
+
}));
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
await markPendingReplyFailed(pending, error);
|
|
622
|
+
console.error(`Gateway reply failed for ${pending.channelId}/${pending.turnId}: ${errorMessage(error)}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async function markPendingReplyFailed(pending, error) {
|
|
626
|
+
const now = new Date();
|
|
627
|
+
const attempts = pending.attempts + 1;
|
|
628
|
+
const nextAttemptAt = new Date(now.getTime() + Math.min(60_000, 2 ** Math.min(attempts, 6) * 1000)).toISOString();
|
|
629
|
+
await updateGatewayStore((current) => ({
|
|
630
|
+
...current,
|
|
631
|
+
channels: current.channels.map((item) => item.id === pending.channelId
|
|
632
|
+
? { ...item, lastError: errorMessage(error), updatedAt: now.toISOString() }
|
|
633
|
+
: item),
|
|
634
|
+
pendingReplies: current.pendingReplies.map((item) => item.channelId === pending.channelId && item.requestId === pending.requestId
|
|
635
|
+
? { ...item, attempts, lastError: errorMessage(error), nextAttemptAt, updatedAt: now.toISOString() }
|
|
636
|
+
: item),
|
|
637
|
+
}));
|
|
638
|
+
}
|
|
639
|
+
function gatewayTerminalPayload(config, input) {
|
|
640
|
+
const session = sessions.get(input.sessionId);
|
|
641
|
+
return {
|
|
642
|
+
type: "turn_stopped",
|
|
643
|
+
sessionId: input.sessionId,
|
|
644
|
+
machineId: config.machineId,
|
|
645
|
+
turnId: input.turnId,
|
|
646
|
+
status: input.status,
|
|
647
|
+
stopReason: input.stopReason,
|
|
648
|
+
error: input.error,
|
|
649
|
+
outputPreview: input.outputPreview ? truncateGatewayText(input.outputPreview, 4000) : undefined,
|
|
650
|
+
sessionName: session?.name,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
export async function setGatewayLastError(channelId, error) {
|
|
654
|
+
const message = errorMessage(error);
|
|
655
|
+
await updateGatewayStore((current) => ({
|
|
656
|
+
...current,
|
|
657
|
+
channels: current.channels.map((channel) => channel.id === channelId
|
|
658
|
+
? { ...channel, lastError: message, updatedAt: new Date().toISOString() }
|
|
659
|
+
: channel),
|
|
660
|
+
}));
|
|
661
|
+
}
|
|
662
|
+
async function clearGatewayLastError(channelId) {
|
|
663
|
+
await updateGatewayStore((current) => ({
|
|
664
|
+
...current,
|
|
665
|
+
channels: current.channels.map((channel) => channel.id === channelId
|
|
666
|
+
? { ...channel, lastError: undefined, updatedAt: new Date().toISOString() }
|
|
667
|
+
: channel),
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
670
|
+
export function publicChannelsForStore(store) {
|
|
671
|
+
return store.channels.map((channel) => publicGatewayChannel(channel, gatewayChannelListening(channel)));
|
|
672
|
+
}
|
|
673
|
+
function gatewayChannelListening(channel) {
|
|
674
|
+
if (!channel.enabled)
|
|
675
|
+
return false;
|
|
676
|
+
if (channel.type === "lark")
|
|
677
|
+
return gatewayAdapters.has(channel.id);
|
|
678
|
+
return Boolean(gatewayServer?.listening);
|
|
679
|
+
}
|
|
680
|
+
function gatewayConversationId(destination) {
|
|
681
|
+
if (destination.contextKind === "thread")
|
|
682
|
+
return `${destination.platformChatId}:thread:${destination.contextKey}`;
|
|
683
|
+
return `${destination.platformChatId}:main`;
|
|
684
|
+
}
|
|
685
|
+
function destinationFromGatewayConversationId(externalConversationId) {
|
|
686
|
+
const threadMatch = /^(.+):thread:(.+)$/.exec(externalConversationId);
|
|
687
|
+
if (threadMatch) {
|
|
688
|
+
return { platformChatId: threadMatch[1], contextKind: "thread", contextKey: threadMatch[2] };
|
|
689
|
+
}
|
|
690
|
+
const mainMatch = /^(.+):main$/.exec(externalConversationId);
|
|
691
|
+
if (mainMatch)
|
|
692
|
+
return { platformChatId: mainMatch[1], contextKind: "main" };
|
|
693
|
+
throw gatewayError("Gateway notify requires a conversation id like <chatId>:main or <chatId>:thread:<threadId>", "GATEWAY_CONVERSATION_INVALID", 400);
|
|
694
|
+
}
|
|
695
|
+
function gatewayAdapterKey(channel) {
|
|
696
|
+
return JSON.stringify({
|
|
697
|
+
type: channel.type,
|
|
698
|
+
enabled: channel.enabled,
|
|
699
|
+
appId: channel.appId,
|
|
700
|
+
appSecretHash: channel.appSecret ? createHash("sha256").update(channel.appSecret).digest("base64url") : "",
|
|
701
|
+
domain: channel.domain ?? "feishu",
|
|
702
|
+
inboundMode: channel.inboundMode,
|
|
703
|
+
processingReaction: channel.processingReaction,
|
|
704
|
+
allowAnySender: channel.allowAnySender ?? true,
|
|
705
|
+
allowedChatIds: channel.allowedChatIds ?? [],
|
|
706
|
+
allowedSenderIds: channel.allowedSenderIds ?? [],
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
function gatewayPolicyAllows(channel, input) {
|
|
710
|
+
if (channel.allowAnySender ?? true)
|
|
711
|
+
return true;
|
|
712
|
+
const chatId = input.destination?.platformChatId;
|
|
713
|
+
if (chatId && (channel.allowedChatIds ?? []).includes(chatId))
|
|
714
|
+
return true;
|
|
715
|
+
if (input.senderPlatformUserId && (channel.allowedSenderIds ?? []).includes(input.senderPlatformUserId))
|
|
716
|
+
return true;
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
function stableGatewayId(prefix, ...parts) {
|
|
720
|
+
const hash = createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 32);
|
|
721
|
+
return `${prefix}_${hash}`;
|
|
722
|
+
}
|
|
723
|
+
function parseGatewayPort(value) {
|
|
724
|
+
const port = Number(value || "8789");
|
|
725
|
+
if (Number.isInteger(port) && port >= 1 && port <= 65_535)
|
|
726
|
+
return port;
|
|
727
|
+
return 8789;
|
|
728
|
+
}
|
|
729
|
+
async function readJsonRequestBody(request) {
|
|
730
|
+
const chunks = [];
|
|
731
|
+
let size = 0;
|
|
732
|
+
for await (const chunk of request) {
|
|
733
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
734
|
+
size += buffer.length;
|
|
735
|
+
if (size > 1024 * 1024)
|
|
736
|
+
throw gatewayError("Gateway request body is too large", "REQUEST_TOO_LARGE", 413);
|
|
737
|
+
chunks.push(buffer);
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
throw gatewayError("Gateway request body must be JSON", "INVALID_JSON", 400);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function writeGatewayHttpJson(response, statusCode, body) {
|
|
747
|
+
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
|
|
748
|
+
response.end(`${JSON.stringify(body)}\n`);
|
|
749
|
+
}
|
|
750
|
+
function isRecord(value) {
|
|
751
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
752
|
+
}
|
|
753
|
+
function isLarkChallenge(value) {
|
|
754
|
+
return isRecord(value) && typeof value.challenge === "string";
|
|
755
|
+
}
|
|
756
|
+
function assertGatewayToken(channel, request, body) {
|
|
757
|
+
if (!channel.localToken)
|
|
758
|
+
return;
|
|
759
|
+
const auth = request.headers.authorization;
|
|
760
|
+
const bearer = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : undefined;
|
|
761
|
+
const record = isRecord(body) ? body : {};
|
|
762
|
+
const header = isRecord(record.header) ? record.header : {};
|
|
763
|
+
const token = bearer ?? stringField(record, "token") ?? stringField(header, "token") ?? headerValue(request, "x-happy-elves-token");
|
|
764
|
+
if (token !== channel.localToken)
|
|
765
|
+
throw gatewayError("Gateway token is invalid", "GATEWAY_TOKEN_INVALID", 401);
|
|
766
|
+
}
|
|
767
|
+
function headerValue(request, key) {
|
|
768
|
+
const value = request.headers[key.toLowerCase()];
|
|
769
|
+
return typeof value === "string" ? value : undefined;
|
|
770
|
+
}
|
|
771
|
+
function fakeGatewayMessageFromBody(body) {
|
|
772
|
+
const record = isRecord(body) ? body : {};
|
|
773
|
+
const externalConversationId = stringField(record, "externalConversationId") ?? stringField(record, "conversationId");
|
|
774
|
+
const externalMessageId = stringField(record, "externalMessageId") ?? stringField(record, "messageId");
|
|
775
|
+
const text = stringField(record, "text");
|
|
776
|
+
if (!externalConversationId || !externalMessageId || !text) {
|
|
777
|
+
throw gatewayError("Fake gateway body requires conversationId, messageId, and text", "GATEWAY_MESSAGE_INVALID", 400);
|
|
778
|
+
}
|
|
779
|
+
return { externalConversationId, externalMessageId, text };
|
|
780
|
+
}
|
|
781
|
+
function larkMessageFromBody(body) {
|
|
782
|
+
const record = isRecord(body) ? body : {};
|
|
783
|
+
const event = isRecord(record.event) ? record.event : record;
|
|
784
|
+
const message = isRecord(event.message) ? event.message : event;
|
|
785
|
+
const sender = isRecord(event.sender) ? event.sender : {};
|
|
786
|
+
const senderId = isRecord(sender.sender_id) ? sender.sender_id : {};
|
|
787
|
+
const externalMessageId = stringField(message, "message_id") ?? stringField(event, "message_id") ?? stringField(record, "uuid");
|
|
788
|
+
const chatId = stringField(message, "chat_id") ??
|
|
789
|
+
stringField(event, "chat_id") ??
|
|
790
|
+
stringField(event, "open_chat_id") ??
|
|
791
|
+
stringField(senderId, "open_id");
|
|
792
|
+
const text = larkTextFromContent(message.content) ?? stringField(message, "text");
|
|
793
|
+
if (!chatId || !externalMessageId || !text) {
|
|
794
|
+
throw gatewayError("Lark gateway event is not a supported text message", "GATEWAY_MESSAGE_INVALID", 400);
|
|
795
|
+
}
|
|
796
|
+
const threadId = stringField(message, "thread_id");
|
|
797
|
+
const destination = threadId
|
|
798
|
+
? { platformChatId: chatId, contextKind: "thread", contextKey: threadId, replyToMessageId: externalMessageId }
|
|
799
|
+
: { platformChatId: chatId, contextKind: "main", replyToMessageId: externalMessageId };
|
|
800
|
+
return {
|
|
801
|
+
externalConversationId: gatewayConversationId(destination),
|
|
802
|
+
externalMessageId,
|
|
803
|
+
text,
|
|
804
|
+
destination,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function larkTextFromContent(content) {
|
|
808
|
+
if (typeof content === "string") {
|
|
809
|
+
try {
|
|
810
|
+
const parsed = JSON.parse(content);
|
|
811
|
+
if (isRecord(parsed))
|
|
812
|
+
return stringField(parsed, "text");
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
return content;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (isRecord(content))
|
|
819
|
+
return stringField(content, "text");
|
|
820
|
+
return undefined;
|
|
821
|
+
}
|
|
822
|
+
function stringField(record, key) {
|
|
823
|
+
const value = record[key];
|
|
824
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
825
|
+
}
|
|
826
|
+
function truncateGatewayText(value, maxChars) {
|
|
827
|
+
if (value.length <= maxChars)
|
|
828
|
+
return value;
|
|
829
|
+
return `${value.slice(0, maxChars - 1)}…`;
|
|
830
|
+
}
|
|
831
|
+
function gatewayError(message, code, statusCode) {
|
|
832
|
+
const error = new Error(message);
|
|
833
|
+
error.code = code;
|
|
834
|
+
error.statusCode = statusCode;
|
|
835
|
+
return error;
|
|
836
|
+
}
|
|
837
|
+
function errorMessage(error) {
|
|
838
|
+
return error instanceof Error ? error.message : String(error);
|
|
839
|
+
}
|
|
840
|
+
function errorCode(error) {
|
|
841
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
842
|
+
? error.code
|
|
843
|
+
: "GATEWAY_REQUEST_FAILED";
|
|
844
|
+
}
|
|
845
|
+
function errorStatus(error) {
|
|
846
|
+
return typeof error === "object" && error !== null && "statusCode" in error && typeof error.statusCode === "number"
|
|
847
|
+
? error.statusCode
|
|
848
|
+
: 500;
|
|
849
|
+
}
|
|
850
|
+
//# sourceMappingURL=runtime.js.map
|