{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/gateway/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAkB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEtG,qBAAqB;AACrB,MAAM,WAAW,aAAa;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,cAAc,CAAC;CAC/B;AAED,qBAAqB;AACrB,qBAAa,aAAa;IACzB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAiB;IAEvC,YAAY,OAAO,EAAE,aAAa,EAGjC;IAED;;;;;;;;;;OAUG;IACG,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAwC3E;IAED;;OAEG;IACH,OAAO,CAAC,WAAW;IAyCnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgCnB;;OAEG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG;QAC7B,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,YAAY,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACnB,CAQA;IAED;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjG;IAED;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC;QAClD,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,CAAC,EAAE,OAAO,CAAC;KAC1B,CAAC,CA2BD;IAED;;OAEG;IACG,kBAAkB,CAAC,OAAO,EAAE,cAAc,+CAI/C;IAED;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAGrC;IAED;;OAEG;IACH,eAAe,IAAI,KAAK,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC,CAQD;CACD","sourcesContent":["/**\n * Message Router - Routes inbound messages to the appropriate agent session\n *\n * Implements the routing resolution logic:\n * 1. peer matching - exact DM/group/channel ID\n * 2. guild matching - Discord server ID\n * 3. team matching - Slack team ID\n * 4. account matching - specific account instance\n * 5. channel matching - whole platform wildcard\n * 6. default - default agent\n */\n\nimport type { ConfigManager } from \"../config/manager.js\";\nimport type { ChannelConfig } from \"./channels/base.js\";\nimport type { SessionManager } from \"./sessions.js\";\nimport type { ChannelBinding, ChannelScope, InboundMessage, RouteResolution } from \"./types/index.js\";\n\n/** Router options */\nexport interface RouterOptions {\n\tconfigManager: ConfigManager;\n\tsessionManager: SessionManager;\n}\n\n/** Message router */\nexport class MessageRouter {\n\tprivate configManager: ConfigManager;\n\tprivate sessionManager: SessionManager;\n\n\tconstructor(options: RouterOptions) {\n\t\tthis.configManager = options.configManager;\n\t\tthis.sessionManager = options.sessionManager;\n\t}\n\n\t/**\n\t * Resolve route for an inbound message\n\t *\n\t * Routing priority (high to low):\n\t * 1. peer matching - exact DM/group/channel ID\n\t * 2. guild matching - Discord server ID\n\t * 3. team matching - Slack team ID\n\t * 4. account matching - specific account instance\n\t * 5. channel matching - whole platform wildcard\n\t * 6. default - default agent\n\t */\n\tasync resolveRoute(message: InboundMessage): Promise<RouteResolution | null> {\n\t\tconst config = this.configManager.getConfig();\n\t\tconst { agentId, channel, scope, identifier } = this.parseSessionKey(message.sessionKey);\n\n\t\t// Get all bindings for this channel\n\t\tconst bindings = config.bindings.filter((b) => b.channel === channel);\n\n\t\t// Priority order\n\t\tconst priority: Array<ChannelBinding[\"bindType\"]> = [\"peer\", \"guild\", \"team\", \"account\", \"channel\", \"default\"];\n\n\t\tfor (const bindType of priority) {\n\t\t\tconst binding = this.findBinding(bindings, bindType, {\n\t\t\t\tidentifier,\n\t\t\t\tguildId: message.sender.guildId,\n\t\t\t\tteamId: message.sender.teamId,\n\t\t\t\taccountId: message.sender.accountId,\n\t\t\t});\n\n\t\t\tif (binding) {\n\t\t\t\t// Check access control\n\t\t\t\tif (!this.checkAccess(binding, message)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst channelConfig = config.channels.find((c) => c.type === channel);\n\t\t\t\tif (!channelConfig) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tagentId: binding.agentId,\n\t\t\t\t\tsessionKey: message.sessionKey,\n\t\t\t\t\tchannelConfig,\n\t\t\t\t\tbinding,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// No route found\n\t\treturn null;\n\t}\n\n\t/**\n\t * Find matching binding\n\t */\n\tprivate findBinding(\n\t\tbindings: ChannelBinding[],\n\t\tbindType: ChannelBinding[\"bindType\"],\n\t\tcontext: {\n\t\t\tidentifier: string;\n\t\t\tguildId?: string;\n\t\t\tteamId?: string;\n\t\t\taccountId?: string;\n\t\t},\n\t): ChannelBinding | undefined {\n\t\treturn bindings.find((b) => {\n\t\t\tif (b.bindType !== bindType) return false;\n\n\t\t\tswitch (bindType) {\n\t\t\t\tcase \"peer\":\n\t\t\t\t\t// Match exact DM/group/channel ID\n\t\t\t\t\treturn b.bindValue === context.identifier;\n\n\t\t\t\tcase \"guild\":\n\t\t\t\t\t// Match Discord server ID\n\t\t\t\t\treturn b.bindValue === context.guildId;\n\n\t\t\t\tcase \"team\":\n\t\t\t\t\t// Match Slack team ID\n\t\t\t\t\treturn b.bindValue === context.teamId;\n\n\t\t\t\tcase \"account\":\n\t\t\t\t\t// Match specific account\n\t\t\t\t\treturn b.bindValue === context.accountId;\n\n\t\t\t\tcase \"channel\":\n\t\t\t\tcase \"default\":\n\t\t\t\t\t// Wildcard match\n\t\t\t\t\treturn b.bindValue === \"*\";\n\n\t\t\t\tdefault:\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Check if sender has access based on binding policy\n\t */\n\tprivate checkAccess(binding: ChannelBinding, message: InboundMessage): boolean {\n\t\tconst { scope } = this.parseSessionKey(message.sessionKey);\n\t\tconst sender = message.sender;\n\t\tconst access = binding.access;\n\n\t\tif (scope === \"dm\") {\n\t\t\t// DM access control\n\t\t\tswitch (access.dmPolicy) {\n\t\t\t\tcase \"open\":\n\t\t\t\t\treturn true;\n\t\t\t\tcase \"allowlist\":\n\t\t\t\t\treturn access.dmAllowlist?.includes(sender.id) ?? false;\n\t\t\t\tcase \"pairing\":\n\t\t\t\tdefault:\n\t\t\t\t\t// Pairing requires explicit approval (handled elsewhere)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t} else {\n\t\t\t// Group access control\n\t\t\tswitch (access.groupPolicy) {\n\t\t\t\tcase \"open\":\n\t\t\t\t\treturn true;\n\t\t\t\tcase \"allowlist\":\n\t\t\t\t\treturn access.groupAllowlist?.includes(sender.id) ?? false;\n\t\t\t\tcase \"mention\":\n\t\t\t\tdefault:\n\t\t\t\t\t// In groups, require mention (handled by channel adapter)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse session key into components\n\t */\n\tparseSessionKey(key: string): {\n\t\tagentId: string;\n\t\tchannel: string;\n\t\tscope: ChannelScope;\n\t\tidentifier: string;\n\t} {\n\t\tconst parts = key.split(\":\");\n\t\treturn {\n\t\t\tagentId: parts[1] ?? \"default\",\n\t\t\tchannel: parts[2] ?? \"unknown\",\n\t\t\tscope: (parts[3] as ChannelScope) ?? \"dm\",\n\t\t\tidentifier: parts[4] ?? \"unknown\",\n\t\t};\n\t}\n\n\t/**\n\t * Build session key from components\n\t */\n\tbuildSessionKey(agentId: string, channel: string, scope: ChannelScope, identifier: string): string {\n\t\treturn `agent:${agentId}:${channel}:${scope}:${identifier}`;\n\t}\n\n\t/**\n\t * Check if message should be processed (access control)\n\t */\n\tasync canProcess(message: InboundMessage): Promise<{\n\t\tallowed: boolean;\n\t\treason?: string;\n\t\trequiresPairing?: boolean;\n\t}> {\n\t\tconst route = await this.resolveRoute(message);\n\n\t\tif (!route) {\n\t\t\treturn {\n\t\t\t\tallowed: false,\n\t\t\t\treason: \"No agent configured for this channel/identifier\",\n\t\t\t};\n\t\t}\n\n\t\tconst { scope } = this.parseSessionKey(message.sessionKey);\n\t\tconst sender = message.sender;\n\t\tconst { binding } = route;\n\n\t\t// Check DM policy\n\t\tif (scope === \"dm\" && binding.access.dmPolicy === \"pairing\") {\n\t\t\t// TODO: Check if sender is paired\n\t\t\t// For now, allow all\n\t\t}\n\n\t\t// Check group policy\n\t\tif (scope !== \"dm\" && binding.access.groupPolicy === \"mention\") {\n\t\t\t// Channel adapter should handle mention detection\n\t\t\t// If message reaches here, it was either a mention or direct message\n\t\t}\n\n\t\treturn { allowed: true };\n\t}\n\n\t/**\n\t * Get or create session for message\n\t */\n\tasync getOrCreateSession(message: InboundMessage) {\n\t\tconst { agentId, channel, scope, identifier } = this.parseSessionKey(message.sessionKey);\n\n\t\treturn this.sessionManager.getOrCreateSession(agentId, channel, scope, identifier);\n\t}\n\n\t/**\n\t * Get agent configuration for message\n\t */\n\tgetAgentConfig(message: InboundMessage) {\n\t\tconst { agentId } = this.parseSessionKey(message.sessionKey);\n\t\treturn this.configManager.getAgent(agentId);\n\t}\n\n\t/**\n\t * Get all active routes\n\t */\n\tgetActiveRoutes(): Array<{\n\t\tchannel: string;\n\t\tagentId: string;\n\t\tbindType: string;\n\t\tbindValue: string;\n\t}> {\n\t\tconst config = this.configManager.getConfig();\n\t\treturn config.bindings.map((b) => ({\n\t\t\tchannel: b.channel,\n\t\t\tagentId: b.agentId,\n\t\t\tbindType: b.bindType,\n\t\t\tbindValue: b.bindValue,\n\t\t}));\n\t}\n}\n"]}