botinabox 1.8.2 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/dist/channels/discord/adapter.d.ts +32 -0
  2. package/dist/channels/discord/adapter.js +70 -0
  3. package/dist/channels/discord/inbound.d.ts +25 -0
  4. package/dist/channels/discord/inbound.js +24 -0
  5. package/dist/channels/discord/models.d.ts +8 -0
  6. package/dist/channels/discord/models.js +5 -0
  7. package/dist/channels/discord/outbound.d.ts +14 -0
  8. package/dist/channels/discord/outbound.js +38 -0
  9. package/dist/channels/slack/adapter.d.ts +33 -0
  10. package/dist/channels/slack/adapter.js +74 -0
  11. package/dist/channels/slack/inbound.d.ts +59 -0
  12. package/dist/channels/slack/inbound.js +96 -0
  13. package/dist/channels/slack/models.d.ts +9 -0
  14. package/dist/channels/slack/models.js +5 -0
  15. package/dist/channels/slack/outbound.d.ts +12 -0
  16. package/dist/channels/slack/outbound.js +18 -0
  17. package/dist/channels/slack/transcribe.d.ts +41 -0
  18. package/dist/channels/slack/transcribe.js +106 -0
  19. package/dist/channels/webhook/adapter.d.ts +23 -0
  20. package/dist/channels/webhook/adapter.js +86 -0
  21. package/dist/channels/webhook/hmac.d.ts +13 -0
  22. package/dist/channels/webhook/hmac.js +26 -0
  23. package/dist/channels/webhook/models.d.ts +9 -0
  24. package/dist/channels/webhook/models.js +5 -0
  25. package/dist/channels/webhook/server.d.ts +20 -0
  26. package/dist/channels/webhook/server.js +91 -0
  27. package/dist/cli/templates/config.yml.d.ts +7 -0
  28. package/dist/cli/templates/config.yml.js +61 -0
  29. package/dist/cli/templates/env.d.ts +1 -0
  30. package/dist/cli/templates/env.js +30 -0
  31. package/dist/cli/templates/index.ts.d.ts +2 -0
  32. package/dist/cli/templates/index.ts.js +30 -0
  33. package/dist/cli/templates/package.json.d.ts +5 -0
  34. package/dist/cli/templates/package.json.js +28 -0
  35. package/dist/connectors/google/calendar-connector.d.ts +40 -0
  36. package/dist/connectors/google/calendar-connector.js +243 -0
  37. package/dist/connectors/google/gmail-connector.d.ts +42 -0
  38. package/dist/connectors/google/gmail-connector.js +345 -0
  39. package/dist/connectors/google/oauth.d.ts +48 -0
  40. package/dist/connectors/google/oauth.js +112 -0
  41. package/dist/connectors/google/types.d.ts +78 -0
  42. package/dist/connectors/google/types.js +2 -0
  43. package/dist/core/chat/auto-discovery.d.ts +16 -0
  44. package/dist/core/chat/auto-discovery.js +54 -0
  45. package/dist/core/chat/channel-registry.d.ts +45 -0
  46. package/dist/core/chat/channel-registry.js +96 -0
  47. package/dist/core/chat/chat-pipeline.d.ts +104 -0
  48. package/dist/core/chat/chat-pipeline.js +282 -0
  49. package/dist/core/chat/chat-responder.d.ts +90 -0
  50. package/dist/core/chat/chat-responder.js +185 -0
  51. package/dist/core/chat/formatter.d.ts +11 -0
  52. package/dist/core/chat/formatter.js +60 -0
  53. package/dist/core/chat/index.d.ts +24 -0
  54. package/dist/core/chat/index.js +18 -0
  55. package/dist/core/chat/message-interpreter.d.ts +91 -0
  56. package/dist/core/chat/message-interpreter.js +164 -0
  57. package/dist/core/chat/message-store.d.ts +66 -0
  58. package/dist/core/chat/message-store.js +131 -0
  59. package/dist/core/chat/notification-queue.d.ts +34 -0
  60. package/dist/core/chat/notification-queue.js +111 -0
  61. package/dist/core/chat/pipeline.d.ts +38 -0
  62. package/dist/core/chat/pipeline.js +89 -0
  63. package/dist/core/chat/policies.d.ts +16 -0
  64. package/dist/core/chat/policies.js +25 -0
  65. package/dist/core/chat/routing.d.ts +17 -0
  66. package/dist/core/chat/routing.js +36 -0
  67. package/dist/core/chat/session-key.d.ts +30 -0
  68. package/dist/core/chat/session-key.js +65 -0
  69. package/dist/core/chat/session-manager.d.ts +17 -0
  70. package/dist/core/chat/session-manager.js +23 -0
  71. package/dist/core/chat/text-chunker.d.ts +9 -0
  72. package/dist/core/chat/text-chunker.js +48 -0
  73. package/dist/core/chat/triage-router.d.ts +75 -0
  74. package/dist/core/chat/triage-router.js +142 -0
  75. package/dist/core/chat/types.d.ts +5 -0
  76. package/dist/core/chat/types.js +5 -0
  77. package/dist/core/config/defaults.d.ts +2 -0
  78. package/dist/core/config/defaults.js +38 -0
  79. package/dist/core/config/index.d.ts +6 -0
  80. package/dist/core/config/index.js +4 -0
  81. package/dist/core/config/interpolate.d.ts +5 -0
  82. package/dist/core/config/interpolate.js +27 -0
  83. package/dist/core/config/loader.d.ts +24 -0
  84. package/dist/core/config/loader.js +59 -0
  85. package/dist/core/config/schema.d.ts +5 -0
  86. package/dist/core/config/schema.js +119 -0
  87. package/dist/core/data/core-entity-contexts.d.ts +14 -0
  88. package/dist/core/data/core-entity-contexts.js +197 -0
  89. package/dist/core/data/core-migrations.d.ts +5 -0
  90. package/dist/core/data/core-migrations.js +45 -0
  91. package/dist/core/data/core-schema.d.ts +6 -0
  92. package/dist/core/data/core-schema.js +454 -0
  93. package/dist/core/data/data-store.d.ts +67 -0
  94. package/dist/core/data/data-store.js +218 -0
  95. package/dist/core/data/domain-entity-contexts.d.ts +29 -0
  96. package/dist/core/data/domain-entity-contexts.js +321 -0
  97. package/dist/core/data/domain-schema.d.ts +36 -0
  98. package/dist/core/data/domain-schema.js +323 -0
  99. package/dist/core/data/index.d.ts +7 -0
  100. package/dist/core/data/index.js +7 -0
  101. package/dist/core/data/types.d.ts +111 -0
  102. package/dist/core/data/types.js +1 -0
  103. package/dist/core/hooks/hook-bus.d.ts +18 -0
  104. package/dist/core/hooks/hook-bus.js +120 -0
  105. package/dist/core/hooks/index.d.ts +2 -0
  106. package/dist/core/hooks/index.js +1 -0
  107. package/dist/core/hooks/types.d.ts +19 -0
  108. package/dist/core/hooks/types.js +1 -0
  109. package/dist/core/index.d.ts +4 -0
  110. package/dist/core/index.js +4 -0
  111. package/dist/core/llm/auto-discovery.d.ts +11 -0
  112. package/dist/core/llm/auto-discovery.js +49 -0
  113. package/dist/core/llm/cost-tracker.d.ts +6 -0
  114. package/dist/core/llm/cost-tracker.js +38 -0
  115. package/dist/core/llm/index.d.ts +4 -0
  116. package/dist/core/llm/index.js +3 -0
  117. package/dist/core/llm/model-router.d.ts +25 -0
  118. package/dist/core/llm/model-router.js +49 -0
  119. package/dist/core/llm/provider-registry.d.ts +9 -0
  120. package/dist/core/llm/provider-registry.js +25 -0
  121. package/dist/core/llm/types.d.ts +2 -0
  122. package/dist/core/llm/types.js +2 -0
  123. package/dist/core/orchestrator/adapters/api-adapter.d.ts +34 -0
  124. package/dist/core/orchestrator/adapters/api-adapter.js +88 -0
  125. package/dist/core/orchestrator/adapters/cli-adapter.d.ts +22 -0
  126. package/dist/core/orchestrator/adapters/cli-adapter.js +69 -0
  127. package/dist/core/orchestrator/adapters/deterministic-adapter.d.ts +35 -0
  128. package/dist/core/orchestrator/adapters/deterministic-adapter.js +75 -0
  129. package/dist/core/orchestrator/adapters/env-whitelist.d.ts +4 -0
  130. package/dist/core/orchestrator/adapters/env-whitelist.js +27 -0
  131. package/dist/core/orchestrator/adapters/output-extractor.d.ts +11 -0
  132. package/dist/core/orchestrator/adapters/output-extractor.js +59 -0
  133. package/dist/core/orchestrator/adapters/process-manager.d.ts +15 -0
  134. package/dist/core/orchestrator/adapters/process-manager.js +26 -0
  135. package/dist/core/orchestrator/adapters/tool-loop.d.ts +22 -0
  136. package/dist/core/orchestrator/adapters/tool-loop.js +66 -0
  137. package/dist/core/orchestrator/agent-registry.d.ts +31 -0
  138. package/dist/core/orchestrator/agent-registry.js +135 -0
  139. package/dist/core/orchestrator/budget-controller.d.ts +19 -0
  140. package/dist/core/orchestrator/budget-controller.js +73 -0
  141. package/dist/core/orchestrator/chain-guard.d.ts +14 -0
  142. package/dist/core/orchestrator/chain-guard.js +23 -0
  143. package/dist/core/orchestrator/circuit-breaker.d.ts +65 -0
  144. package/dist/core/orchestrator/circuit-breaker.js +159 -0
  145. package/dist/core/orchestrator/claude-stream-parser.d.ts +31 -0
  146. package/dist/core/orchestrator/claude-stream-parser.js +99 -0
  147. package/dist/core/orchestrator/config-revisions.d.ts +6 -0
  148. package/dist/core/orchestrator/config-revisions.js +17 -0
  149. package/dist/core/orchestrator/dependency-resolver.d.ts +20 -0
  150. package/dist/core/orchestrator/dependency-resolver.js +78 -0
  151. package/dist/core/orchestrator/governance-gate.d.ts +110 -0
  152. package/dist/core/orchestrator/governance-gate.js +170 -0
  153. package/dist/core/orchestrator/learning-pipeline.d.ts +109 -0
  154. package/dist/core/orchestrator/learning-pipeline.js +249 -0
  155. package/dist/core/orchestrator/loop-detector.d.ts +51 -0
  156. package/dist/core/orchestrator/loop-detector.js +133 -0
  157. package/dist/core/orchestrator/ndjson-logger.d.ts +6 -0
  158. package/dist/core/orchestrator/ndjson-logger.js +18 -0
  159. package/dist/core/orchestrator/permission-relay.d.ts +72 -0
  160. package/dist/core/orchestrator/permission-relay.js +164 -0
  161. package/dist/core/orchestrator/run-manager.d.ts +31 -0
  162. package/dist/core/orchestrator/run-manager.js +178 -0
  163. package/dist/core/orchestrator/scheduler.d.ts +70 -0
  164. package/dist/core/orchestrator/scheduler.js +198 -0
  165. package/dist/core/orchestrator/secret-store.d.ts +57 -0
  166. package/dist/core/orchestrator/secret-store.js +171 -0
  167. package/dist/core/orchestrator/session-manager.d.ts +13 -0
  168. package/dist/core/orchestrator/session-manager.js +66 -0
  169. package/dist/core/orchestrator/task-queue.d.ts +34 -0
  170. package/dist/core/orchestrator/task-queue.js +83 -0
  171. package/dist/core/orchestrator/template-interpolate.d.ts +5 -0
  172. package/dist/core/orchestrator/template-interpolate.js +18 -0
  173. package/dist/core/orchestrator/user-registry.d.ts +47 -0
  174. package/dist/core/orchestrator/user-registry.js +76 -0
  175. package/dist/core/orchestrator/wakeup-queue.d.ts +9 -0
  176. package/dist/core/orchestrator/wakeup-queue.js +45 -0
  177. package/dist/core/orchestrator/workflow-engine.d.ts +47 -0
  178. package/dist/core/orchestrator/workflow-engine.js +204 -0
  179. package/dist/core/security/audit.d.ts +20 -0
  180. package/dist/core/security/audit.js +33 -0
  181. package/dist/core/security/column-validator.d.ts +20 -0
  182. package/dist/core/security/column-validator.js +37 -0
  183. package/dist/core/security/index.d.ts +5 -0
  184. package/dist/core/security/index.js +5 -0
  185. package/dist/core/security/process-env.d.ts +13 -0
  186. package/dist/core/security/process-env.js +49 -0
  187. package/dist/core/security/sanitizer.d.ts +11 -0
  188. package/dist/core/security/sanitizer.js +39 -0
  189. package/dist/core/security/types.d.ts +11 -0
  190. package/dist/core/security/types.js +1 -0
  191. package/dist/core/update/auto-update.d.ts +21 -0
  192. package/dist/core/update/auto-update.js +102 -0
  193. package/dist/core/update/backup-manager.d.ts +7 -0
  194. package/dist/core/update/backup-manager.js +24 -0
  195. package/dist/core/update/index.d.ts +8 -0
  196. package/dist/core/update/index.js +6 -0
  197. package/dist/core/update/migration-hooks.d.ts +11 -0
  198. package/dist/core/update/migration-hooks.js +10 -0
  199. package/dist/core/update/types.d.ts +11 -0
  200. package/dist/core/update/types.js +1 -0
  201. package/dist/core/update/update-checker.d.ts +11 -0
  202. package/dist/core/update/update-checker.js +63 -0
  203. package/dist/core/update/update-manager.d.ts +25 -0
  204. package/dist/core/update/update-manager.js +101 -0
  205. package/dist/core/update/version-utils.d.ts +6 -0
  206. package/dist/core/update/version-utils.js +34 -0
  207. package/dist/index.d.ts +2 -0
  208. package/dist/index.js +20 -13
  209. package/dist/providers/anthropic/models.d.ts +2 -0
  210. package/dist/providers/anthropic/models.js +29 -0
  211. package/dist/providers/anthropic/provider.d.ts +13 -0
  212. package/dist/providers/anthropic/provider.js +119 -0
  213. package/dist/providers/anthropic/tool-converter.d.ts +10 -0
  214. package/dist/providers/anthropic/tool-converter.js +7 -0
  215. package/dist/providers/ollama/provider.d.ts +17 -0
  216. package/dist/providers/ollama/provider.js +185 -0
  217. package/dist/providers/openai/models.d.ts +2 -0
  218. package/dist/providers/openai/models.js +29 -0
  219. package/dist/providers/openai/provider.d.ts +13 -0
  220. package/dist/providers/openai/provider.js +163 -0
  221. package/dist/providers/openai/tool-converter.d.ts +10 -0
  222. package/dist/providers/openai/tool-converter.js +10 -0
  223. package/dist/shared/constants.d.ts +50 -0
  224. package/dist/shared/constants.js +64 -0
  225. package/dist/shared/index.d.ts +14 -0
  226. package/dist/shared/index.js +14 -0
  227. package/dist/shared/types/agent.d.ts +36 -0
  228. package/dist/shared/types/agent.js +2 -0
  229. package/dist/shared/types/channel.d.ts +70 -0
  230. package/dist/shared/types/channel.js +2 -0
  231. package/dist/shared/types/config.d.ts +111 -0
  232. package/dist/shared/types/config.js +2 -0
  233. package/dist/shared/types/connector.d.ts +77 -0
  234. package/dist/shared/types/connector.js +2 -0
  235. package/dist/shared/types/execution.d.ts +29 -0
  236. package/dist/shared/types/execution.js +2 -0
  237. package/dist/shared/types/provider.d.ts +73 -0
  238. package/dist/shared/types/provider.js +2 -0
  239. package/dist/shared/types/task.d.ts +47 -0
  240. package/dist/shared/types/task.js +2 -0
  241. package/dist/shared/types/workflow.d.ts +39 -0
  242. package/dist/shared/types/workflow.js +2 -0
  243. package/dist/shared/utils.d.ts +6 -0
  244. package/dist/shared/utils.js +13 -0
  245. package/dist/update-check.d.ts +5 -0
  246. package/dist/update-check.js +56 -0
  247. package/package.json +1 -1
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MessagePipeline — routes inbound messages to the task queue.
3
+ * Story 4.2
4
+ */
5
+ import { buildAgentBindings } from "./routing.js";
6
+ import { checkAllowlist, checkMentionGate } from "./policies.js";
7
+ export class MessagePipeline {
8
+ hooks;
9
+ agentRegistry;
10
+ taskQueue;
11
+ config;
12
+ agentBindings;
13
+ userRegistry;
14
+ constructor(hooks, agentRegistry, taskQueue, config, userRegistry) {
15
+ this.hooks = hooks;
16
+ this.agentRegistry = agentRegistry;
17
+ this.taskQueue = taskQueue;
18
+ this.config = config;
19
+ this.agentBindings = buildAgentBindings(config.agents);
20
+ this.userRegistry = userRegistry;
21
+ }
22
+ /**
23
+ * Process an inbound message end-to-end.
24
+ * 1. Emit 'message.inbound'
25
+ * 2. Resolve agent
26
+ * 3. Check policy (allowlist / mention gate)
27
+ * 4. Create task
28
+ * 5. Emit 'message.processed'
29
+ */
30
+ async processInbound(msg) {
31
+ await this.hooks.emit("message.inbound", { message: msg, channel: msg.channel });
32
+ // Resolve sender to a user record (auto-creates if UserRegistry is available)
33
+ if (this.userRegistry && !msg.userId) {
34
+ const user = await this.userRegistry.resolveOrCreate(msg.from, msg.channel);
35
+ msg.userId = user.id;
36
+ }
37
+ const agentId = this.resolveAgent(msg);
38
+ if (agentId !== undefined) {
39
+ const allowed = this.evaluatePolicy(msg, agentId);
40
+ if (allowed) {
41
+ await this.taskQueue.create({
42
+ title: `Message from ${msg.from} on ${msg.channel}`,
43
+ description: msg.body,
44
+ assignee_id: agentId,
45
+ context: JSON.stringify({ message: msg, userId: msg.userId }),
46
+ });
47
+ }
48
+ }
49
+ await this.hooks.emit("message.processed", {
50
+ message: msg,
51
+ channel: msg.channel,
52
+ agentId: agentId ?? null,
53
+ userId: msg.userId ?? null,
54
+ });
55
+ }
56
+ /**
57
+ * Resolve the best agent for a given inbound message.
58
+ * Returns agentId (slug) or undefined if no match.
59
+ */
60
+ resolveAgent(msg) {
61
+ // Try direct channel binding first
62
+ const bound = this.agentBindings.get(msg.channel);
63
+ if (bound)
64
+ return bound;
65
+ // Fallback: first agent in config
66
+ const first = this.config.agents[0];
67
+ return first?.slug;
68
+ }
69
+ /**
70
+ * Evaluate messaging policy for the given agent.
71
+ * Returns false if the message is blocked.
72
+ */
73
+ evaluatePolicy(msg, agentId) {
74
+ const channelConfig = this.config.channels[msg.channel];
75
+ // Check allowlist
76
+ const allowFrom = channelConfig?.["allowFrom"] ?? [];
77
+ if (!checkAllowlist(allowFrom, msg.from)) {
78
+ return false;
79
+ }
80
+ // Check mention gate
81
+ const mentionGated = channelConfig?.["requireMention"] ?? false;
82
+ if (mentionGated) {
83
+ if (!checkMentionGate(msg, agentId)) {
84
+ return false;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Chat policy helpers — allowlist and mention gate checks.
3
+ * Story 4.2
4
+ */
5
+ import type { InboundMessage } from "./types.js";
6
+ /**
7
+ * Returns true if the sender is in the allowlist.
8
+ * If allowFrom is empty, all senders are allowed.
9
+ */
10
+ export declare function checkAllowlist(allowFrom: string[], senderId: string): boolean;
11
+ /**
12
+ * Returns true if the message passes the mention gate.
13
+ * In group/channel contexts, the bot must be mentioned (body contains @botId or similar).
14
+ * In direct contexts, always passes.
15
+ */
16
+ export declare function checkMentionGate(msg: InboundMessage, botId: string): boolean;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Chat policy helpers — allowlist and mention gate checks.
3
+ * Story 4.2
4
+ */
5
+ /**
6
+ * Returns true if the sender is in the allowlist.
7
+ * If allowFrom is empty, all senders are allowed.
8
+ */
9
+ export function checkAllowlist(allowFrom, senderId) {
10
+ if (allowFrom.length === 0)
11
+ return true;
12
+ return allowFrom.includes(senderId);
13
+ }
14
+ /**
15
+ * Returns true if the message passes the mention gate.
16
+ * In group/channel contexts, the bot must be mentioned (body contains @botId or similar).
17
+ * In direct contexts, always passes.
18
+ */
19
+ export function checkMentionGate(msg, botId) {
20
+ // For direct messages, no mention required
21
+ // We check via a simple heuristic: if the message contains the botId mention
22
+ // This is called only when mention gating is enabled
23
+ const body = msg.body ?? "";
24
+ return body.includes(`@${botId}`) || body.includes(botId);
25
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Chat routing — maps channel/scope to agentId.
3
+ * Story 4.2
4
+ *
5
+ * @deprecated Use TriageRouter for content-aware routing with keyword/regex
6
+ * matching and LLM fallback. This static channel→agent binding is kept for
7
+ * backward compatibility only. See `./triage-router.ts`.
8
+ */
9
+ import type { AgentConfig } from "../../shared/index.js";
10
+ /**
11
+ * Build a map from channel identifier to agentId.
12
+ * Each agent may have a config.channel or config.channels binding.
13
+ *
14
+ * @deprecated Use TriageRouter instead — it supports keyword, regex,
15
+ * and LLM-based routing with ownership chain logging.
16
+ */
17
+ export declare function buildAgentBindings(agents: AgentConfig[]): Map<string, string>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Chat routing — maps channel/scope to agentId.
3
+ * Story 4.2
4
+ *
5
+ * @deprecated Use TriageRouter for content-aware routing with keyword/regex
6
+ * matching and LLM fallback. This static channel→agent binding is kept for
7
+ * backward compatibility only. See `./triage-router.ts`.
8
+ */
9
+ /**
10
+ * Build a map from channel identifier to agentId.
11
+ * Each agent may have a config.channel or config.channels binding.
12
+ *
13
+ * @deprecated Use TriageRouter instead — it supports keyword, regex,
14
+ * and LLM-based routing with ownership chain logging.
15
+ */
16
+ export function buildAgentBindings(agents) {
17
+ const bindings = new Map();
18
+ for (const agent of agents) {
19
+ const agentId = agent.slug;
20
+ const cfg = agent.config ?? {};
21
+ // Support config.channel (single) or config.channels (array)
22
+ const channels = [];
23
+ if (typeof cfg["channel"] === "string") {
24
+ channels.push(cfg["channel"]);
25
+ }
26
+ if (Array.isArray(cfg["channels"])) {
27
+ for (const ch of cfg["channels"]) {
28
+ channels.push(ch);
29
+ }
30
+ }
31
+ for (const ch of channels) {
32
+ bindings.set(ch, agentId);
33
+ }
34
+ }
35
+ return bindings;
36
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SessionKey — structured key for chat sessions.
3
+ * Story 4.3
4
+ */
5
+ export declare class SessionKey {
6
+ readonly agentId: string;
7
+ readonly channel: string;
8
+ readonly scope: string;
9
+ constructor(agentId: string, channel: string, scope: string);
10
+ toString(): string;
11
+ toJSON(): {
12
+ agentId: string;
13
+ channel: string;
14
+ scope: string;
15
+ };
16
+ static fromString(key: string): SessionKey;
17
+ /**
18
+ * Build a session key from structured parameters.
19
+ *
20
+ * @param agentId - The agent identifier
21
+ * @param channel - The channel identifier
22
+ * @param chatType - 'dm' or 'group'
23
+ * @param peerId - The peer/user ID
24
+ * @param dmScope - DM scoping strategy
25
+ * - 'main' → single session per agent+channel regardless of peer
26
+ * - 'per-peer' → one session per (agent, peer)
27
+ * - 'per-channel-peer' → one session per (agent, channel, peer)
28
+ */
29
+ static build(agentId: string, channel: string, chatType: "dm" | "group", peerId: string, dmScope: "main" | "per-peer" | "per-channel-peer"): SessionKey;
30
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * SessionKey — structured key for chat sessions.
3
+ * Story 4.3
4
+ */
5
+ export class SessionKey {
6
+ agentId;
7
+ channel;
8
+ scope;
9
+ constructor(agentId, channel, scope) {
10
+ this.agentId = agentId;
11
+ this.channel = channel;
12
+ this.scope = scope;
13
+ }
14
+ toString() {
15
+ return `agent:${this.agentId}:${this.channel}:${this.scope}`;
16
+ }
17
+ toJSON() {
18
+ return {
19
+ agentId: this.agentId,
20
+ channel: this.channel,
21
+ scope: this.scope,
22
+ };
23
+ }
24
+ static fromString(key) {
25
+ const parts = key.split(":");
26
+ if (parts.length < 4 || parts[0] !== "agent") {
27
+ throw new Error(`Invalid SessionKey format: ${key}`);
28
+ }
29
+ // agentId might contain ':', so reconstruct carefully
30
+ // Format: "agent:{agentId}:{channel}:{scope}"
31
+ // We assume agentId, channel, scope do NOT contain ':'
32
+ const [, agentId, channel, scope] = parts;
33
+ if (!agentId || !channel || !scope) {
34
+ throw new Error(`Invalid SessionKey format: ${key}`);
35
+ }
36
+ return new SessionKey(agentId, channel, scope);
37
+ }
38
+ /**
39
+ * Build a session key from structured parameters.
40
+ *
41
+ * @param agentId - The agent identifier
42
+ * @param channel - The channel identifier
43
+ * @param chatType - 'dm' or 'group'
44
+ * @param peerId - The peer/user ID
45
+ * @param dmScope - DM scoping strategy
46
+ * - 'main' → single session per agent+channel regardless of peer
47
+ * - 'per-peer' → one session per (agent, peer)
48
+ * - 'per-channel-peer' → one session per (agent, channel, peer)
49
+ */
50
+ static build(agentId, channel, chatType, peerId, dmScope) {
51
+ if (chatType === "group") {
52
+ // Groups are always scoped to the channel
53
+ return new SessionKey(agentId, channel, channel);
54
+ }
55
+ // DM scoping
56
+ switch (dmScope) {
57
+ case "main":
58
+ return new SessionKey(agentId, channel, "main");
59
+ case "per-peer":
60
+ return new SessionKey(agentId, channel, peerId);
61
+ case "per-channel-peer":
62
+ return new SessionKey(agentId, channel, `${channel}:${peerId}`);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ChatSessionManager — wraps orchestrator SessionManager with SessionKey-based lookup.
3
+ * Story 4.3
4
+ */
5
+ import type { DataStore } from "../data/data-store.js";
6
+ import { SessionKey } from "./session-key.js";
7
+ export declare class ChatSessionManager {
8
+ private readonly inner;
9
+ constructor(db: DataStore);
10
+ save(key: SessionKey, params: Record<string, unknown>): Promise<string>;
11
+ load(key: SessionKey): Promise<Record<string, unknown> | undefined>;
12
+ clear(key: SessionKey): Promise<void>;
13
+ shouldClear(session: Record<string, unknown>, opts: {
14
+ maxRuns?: number;
15
+ maxAgeHours?: number;
16
+ }): Promise<boolean>;
17
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ChatSessionManager — wraps orchestrator SessionManager with SessionKey-based lookup.
3
+ * Story 4.3
4
+ */
5
+ import { SessionManager } from "../orchestrator/session-manager.js";
6
+ export class ChatSessionManager {
7
+ inner;
8
+ constructor(db) {
9
+ this.inner = new SessionManager(db);
10
+ }
11
+ async save(key, params) {
12
+ return this.inner.save(key.agentId, key.channel, key.scope, params);
13
+ }
14
+ async load(key) {
15
+ return this.inner.load(key.agentId, key.channel, key.scope);
16
+ }
17
+ async clear(key) {
18
+ return this.inner.clear(key.agentId, key.channel, key.scope);
19
+ }
20
+ async shouldClear(session, opts) {
21
+ return this.inner.shouldClear(session, opts);
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Text chunker — splits long text into chunks at natural boundaries.
3
+ * Story 4.4
4
+ */
5
+ /**
6
+ * Split text into chunks of at most maxLen characters.
7
+ * Splits at paragraph boundaries first, then sentence, then word, then hard-cuts.
8
+ */
9
+ export declare function chunkText(text: string, maxLen: number): string[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Text chunker — splits long text into chunks at natural boundaries.
3
+ * Story 4.4
4
+ */
5
+ /**
6
+ * Split text into chunks of at most maxLen characters.
7
+ * Splits at paragraph boundaries first, then sentence, then word, then hard-cuts.
8
+ */
9
+ export function chunkText(text, maxLen) {
10
+ if (maxLen <= 0)
11
+ throw new Error("maxLen must be > 0");
12
+ if (text.length <= maxLen)
13
+ return [text];
14
+ const chunks = [];
15
+ function splitInto(segment) {
16
+ if (segment.length <= maxLen) {
17
+ if (segment.length > 0)
18
+ chunks.push(segment);
19
+ return;
20
+ }
21
+ // Try paragraph split (\n\n)
22
+ const paraIdx = segment.lastIndexOf("\n\n", maxLen);
23
+ if (paraIdx > 0) {
24
+ chunks.push(segment.slice(0, paraIdx));
25
+ splitInto(segment.slice(paraIdx + 2).trimStart());
26
+ return;
27
+ }
28
+ // Try sentence split (". ")
29
+ const sentIdx = segment.lastIndexOf(". ", maxLen);
30
+ if (sentIdx > 0) {
31
+ chunks.push(segment.slice(0, sentIdx + 1));
32
+ splitInto(segment.slice(sentIdx + 2).trimStart());
33
+ return;
34
+ }
35
+ // Try word split (" ")
36
+ const wordIdx = segment.lastIndexOf(" ", maxLen);
37
+ if (wordIdx > 0) {
38
+ chunks.push(segment.slice(0, wordIdx));
39
+ splitInto(segment.slice(wordIdx + 1));
40
+ return;
41
+ }
42
+ // Hard cut
43
+ chunks.push(segment.slice(0, maxLen));
44
+ splitInto(segment.slice(maxLen));
45
+ }
46
+ splitInto(text);
47
+ return chunks;
48
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * TriageRouter — content-based routing with deterministic-first resolution.
3
+ * Story 6.3
4
+ *
5
+ * Replaces the simple channel→agent binding with intelligent routing:
6
+ * 1. Keyword/regex rules evaluated first (deterministic, ~4ms)
7
+ * 2. LLM classification only for ambiguous messages (async, ~2-4s)
8
+ * 3. Ownership chain logged for every routing decision
9
+ *
10
+ * Key constraint: specialists return to triage, never to another specialist.
11
+ */
12
+ import type { DataStore } from '../data/data-store.js';
13
+ import type { HookBus } from '../hooks/hook-bus.js';
14
+ import type { InboundMessage } from './types.js';
15
+ export interface RoutingRule {
16
+ /** Target agent slug */
17
+ agentSlug: string;
18
+ /** Keywords that trigger this rule (case-insensitive) */
19
+ keywords?: string[];
20
+ /** Regex patterns that trigger this rule */
21
+ patterns?: string[];
22
+ /** Priority — lower number wins ties. Default: 50 */
23
+ priority?: number;
24
+ }
25
+ export interface RoutingDecision {
26
+ timestamp: string;
27
+ source: string;
28
+ target: string;
29
+ reason: string;
30
+ method: 'deterministic' | 'llm';
31
+ messageId?: string;
32
+ channel?: string;
33
+ }
34
+ export interface TriageRouterConfig {
35
+ /** Static routing rules evaluated deterministically */
36
+ rules: RoutingRule[];
37
+ /** Fallback agent if no rule matches and LLM is unavailable */
38
+ fallbackAgent?: string;
39
+ /** Whether to use LLM for ambiguous messages. Default: true */
40
+ llmFallback?: boolean;
41
+ /** Log decisions to the database. Default: true */
42
+ persist?: boolean;
43
+ }
44
+ export declare class TriageRouter {
45
+ private db;
46
+ private hooks;
47
+ private readonly rules;
48
+ private readonly fallbackAgent?;
49
+ private readonly llmFallback;
50
+ private readonly persist;
51
+ private readonly compiledRules;
52
+ constructor(db: DataStore, hooks: HookBus, config: TriageRouterConfig);
53
+ /**
54
+ * Route an inbound message to the best agent.
55
+ * Returns the agent slug and the routing decision.
56
+ */
57
+ route(msg: InboundMessage): Promise<{
58
+ agentSlug: string | undefined;
59
+ decision: RoutingDecision;
60
+ }>;
61
+ /**
62
+ * Query the ownership chain for a given message or channel.
63
+ */
64
+ getDecisionHistory(filter?: {
65
+ channel?: string;
66
+ limit?: number;
67
+ }): Promise<RoutingDecision[]>;
68
+ /**
69
+ * LLM classification — emits a hook for external LLM integration.
70
+ * Returns agent slug + reason, or undefined if LLM is unavailable.
71
+ */
72
+ private classifyWithLLM;
73
+ private buildDecision;
74
+ private logDecision;
75
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * TriageRouter — content-based routing with deterministic-first resolution.
3
+ * Story 6.3
4
+ *
5
+ * Replaces the simple channel→agent binding with intelligent routing:
6
+ * 1. Keyword/regex rules evaluated first (deterministic, ~4ms)
7
+ * 2. LLM classification only for ambiguous messages (async, ~2-4s)
8
+ * 3. Ownership chain logged for every routing decision
9
+ *
10
+ * Key constraint: specialists return to triage, never to another specialist.
11
+ */
12
+ export class TriageRouter {
13
+ db;
14
+ hooks;
15
+ rules;
16
+ fallbackAgent;
17
+ llmFallback;
18
+ persist;
19
+ compiledRules;
20
+ constructor(db, hooks, config) {
21
+ this.db = db;
22
+ this.hooks = hooks;
23
+ this.rules = config.rules;
24
+ this.fallbackAgent = config.fallbackAgent;
25
+ this.llmFallback = config.llmFallback ?? true;
26
+ this.persist = config.persist ?? true;
27
+ // Pre-compile patterns for fast matching
28
+ this.compiledRules = this.rules
29
+ .sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50))
30
+ .map((rule) => ({
31
+ rule,
32
+ regexes: (rule.patterns ?? []).map((p) => new RegExp(p, 'i')),
33
+ keywordSet: new Set((rule.keywords ?? []).map((k) => k.toLowerCase())),
34
+ }));
35
+ }
36
+ /**
37
+ * Route an inbound message to the best agent.
38
+ * Returns the agent slug and the routing decision.
39
+ */
40
+ async route(msg) {
41
+ const body = msg.body.toLowerCase();
42
+ const words = new Set(body.split(/\s+/));
43
+ // Phase 1: Deterministic — keyword + regex matching
44
+ for (const { rule, regexes, keywordSet } of this.compiledRules) {
45
+ // Keyword match
46
+ for (const keyword of keywordSet) {
47
+ if (words.has(keyword) || body.includes(keyword)) {
48
+ const decision = this.buildDecision(rule.agentSlug, `keyword: '${keyword}'`, 'deterministic', msg);
49
+ await this.logDecision(decision);
50
+ return { agentSlug: rule.agentSlug, decision };
51
+ }
52
+ }
53
+ // Regex match
54
+ for (const regex of regexes) {
55
+ if (regex.test(msg.body)) {
56
+ const decision = this.buildDecision(rule.agentSlug, `pattern: ${regex.source}`, 'deterministic', msg);
57
+ await this.logDecision(decision);
58
+ return { agentSlug: rule.agentSlug, decision };
59
+ }
60
+ }
61
+ }
62
+ // Phase 2: LLM classification (if enabled)
63
+ if (this.llmFallback) {
64
+ const agentSlugs = this.rules.map((r) => r.agentSlug);
65
+ const classified = await this.classifyWithLLM(msg, agentSlugs);
66
+ if (classified) {
67
+ const decision = this.buildDecision(classified.agentSlug, `llm: ${classified.reason}`, 'llm', msg);
68
+ await this.logDecision(decision);
69
+ return { agentSlug: classified.agentSlug, decision };
70
+ }
71
+ }
72
+ // Phase 3: Fallback
73
+ const decision = this.buildDecision(this.fallbackAgent, 'fallback: no rule matched', 'deterministic', msg);
74
+ await this.logDecision(decision);
75
+ return { agentSlug: this.fallbackAgent, decision };
76
+ }
77
+ /**
78
+ * Query the ownership chain for a given message or channel.
79
+ */
80
+ async getDecisionHistory(filter) {
81
+ const rows = await this.db.query('activity_log', {
82
+ where: { event_type: 'triage_decision' },
83
+ });
84
+ let decisions = rows.map((r) => {
85
+ try {
86
+ return JSON.parse(r['payload']);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }).filter((d) => d !== null);
92
+ if (filter?.channel) {
93
+ decisions = decisions.filter((d) => d.channel === filter.channel);
94
+ }
95
+ // Sort by timestamp descending
96
+ decisions.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
97
+ if (filter?.limit) {
98
+ decisions = decisions.slice(0, filter.limit);
99
+ }
100
+ return decisions;
101
+ }
102
+ /**
103
+ * LLM classification — emits a hook for external LLM integration.
104
+ * Returns agent slug + reason, or undefined if LLM is unavailable.
105
+ */
106
+ async classifyWithLLM(msg, agentSlugs) {
107
+ // Emit classification request — listeners provide the result
108
+ const result = {};
109
+ await this.hooks.emit('triage.classify', {
110
+ message: msg,
111
+ candidates: agentSlugs,
112
+ respond: (slug, reason) => {
113
+ result.agentSlug = slug;
114
+ result.reason = reason;
115
+ },
116
+ });
117
+ if (result.agentSlug && result.reason) {
118
+ return { agentSlug: result.agentSlug, reason: result.reason };
119
+ }
120
+ return undefined;
121
+ }
122
+ buildDecision(target, reason, method, msg) {
123
+ return {
124
+ timestamp: new Date().toISOString(),
125
+ source: 'triage',
126
+ target: target ?? 'none',
127
+ reason,
128
+ method,
129
+ messageId: msg.id,
130
+ channel: msg.channel,
131
+ };
132
+ }
133
+ async logDecision(decision) {
134
+ if (!this.persist)
135
+ return;
136
+ await this.db.insert('activity_log', {
137
+ event_type: 'triage_decision',
138
+ payload: JSON.stringify(decision),
139
+ });
140
+ await this.hooks.emit('triage.routed', { decision });
141
+ }
142
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Chat types — re-exports from @botinabox/shared channel types.
3
+ * Story 4.1
4
+ */
5
+ export type { ChatType, FormattingMode, ChannelCapabilities, ChannelMeta, InboundMessage, Attachment, OutboundPayload, SendResult, HealthStatus, ChannelConfig, ChannelAdapter, } from "../../shared/index.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Chat types — re-exports from @botinabox/shared channel types.
3
+ * Story 4.1
4
+ */
5
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { BotConfig } from "../../shared/index.js";
2
+ export declare const DEFAULT_CONFIG: BotConfig;
@@ -0,0 +1,38 @@
1
+ export const DEFAULT_CONFIG = {
2
+ data: {
3
+ path: "./data/bot.db",
4
+ walMode: true,
5
+ },
6
+ channels: {},
7
+ agents: [],
8
+ providers: {},
9
+ models: {
10
+ aliases: {
11
+ fast: "claude-haiku-4-5",
12
+ smart: "claude-opus-4-6",
13
+ balanced: "claude-sonnet-4-6",
14
+ },
15
+ default: "smart",
16
+ routing: {
17
+ conversation: "fast",
18
+ task_execution: "smart",
19
+ classification: "fast",
20
+ },
21
+ fallbackChain: [],
22
+ },
23
+ entities: {},
24
+ security: {
25
+ fieldLengthLimits: { default: 65535 },
26
+ },
27
+ render: {
28
+ outputDir: "./context",
29
+ watchIntervalMs: 30_000,
30
+ },
31
+ updates: {
32
+ policy: "auto-compatible",
33
+ checkIntervalMs: 86_400_000,
34
+ },
35
+ budget: {
36
+ warnPercent: 80,
37
+ },
38
+ };
@@ -0,0 +1,6 @@
1
+ export { loadConfig, getConfig, initConfig, _resetConfig } from "./loader.js";
2
+ export type { ConfigLoadError, ConfigLoadResult } from "./loader.js";
3
+ export { interpolateEnv } from "./interpolate.js";
4
+ export { validateConfig } from "./schema.js";
5
+ export type { SchemaError } from "./schema.js";
6
+ export { DEFAULT_CONFIG } from "./defaults.js";
@@ -0,0 +1,4 @@
1
+ export { loadConfig, getConfig, initConfig, _resetConfig } from "./loader.js";
2
+ export { interpolateEnv } from "./interpolate.js";
3
+ export { validateConfig } from "./schema.js";
4
+ export { DEFAULT_CONFIG } from "./defaults.js";