botinabox 1.7.0 → 1.8.1

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/dist/index.d.ts CHANGED
@@ -1273,6 +1273,102 @@ declare class MessageInterpreter {
1273
1273
  private extractWithLLM;
1274
1274
  }
1275
1275
 
1276
+ /**
1277
+ * ChatPipeline — configurable 6-layer chat orchestration.
1278
+ * Story 7.4
1279
+ *
1280
+ * Replaces duplicated handler code across apps with a single configurable
1281
+ * pipeline. Apps provide: system prompt, routing rules, LLM call function,
1282
+ * and optional message filter. Everything else is framework-level.
1283
+ *
1284
+ * Layers:
1285
+ * 1. Dedup + Storage (MessageStore)
1286
+ * 2. Fast Response (ChatResponder)
1287
+ * 3. Interpretation (MessageInterpreter)
1288
+ * 4. Post-Interpretation Response
1289
+ * 5. Task Dispatch (TriageRouter)
1290
+ * 6. Task Execution Response
1291
+ */
1292
+
1293
+ interface ChatPipelineConfig {
1294
+ /** LLM call function for chat responses and interpretation */
1295
+ llmCall: ChatResponderConfig['llmCall'];
1296
+ /** System prompt for the conversational responder */
1297
+ systemPrompt: string;
1298
+ /** Agent routing rules for task dispatch */
1299
+ routingRules: RoutingRule[];
1300
+ /** Default agent when no rule matches */
1301
+ fallbackAgent: string;
1302
+ /** Optional message filter — return false to ignore a message */
1303
+ messageFilter?: (msg: InboundMessage) => boolean;
1304
+ /** Optional capabilities description for the responder */
1305
+ capabilities?: string;
1306
+ /** Channel this pipeline handles (default: 'slack') */
1307
+ channel?: string;
1308
+ /** Custom extractors for MessageInterpreter */
1309
+ extractors?: Extractor[];
1310
+ /** Dedup window in ms (default: 300_000 = 5 min) */
1311
+ dedupWindowMs?: number;
1312
+ /** Model for fast responses (default: 'fast') */
1313
+ model?: string;
1314
+ /** Enable LLM fallback routing (default: false) */
1315
+ llmRouting?: boolean;
1316
+ /** TaskQueue instance — required for task dispatch */
1317
+ tasks: {
1318
+ create(task: Record<string, unknown>): Promise<string>;
1319
+ update(id: string, changes: Record<string, unknown>): Promise<void>;
1320
+ get(id: string): Promise<Record<string, unknown> | undefined>;
1321
+ };
1322
+ /** WakeupQueue instance — required for agent waking */
1323
+ wakeups: {
1324
+ enqueue(agentId: string, source: string, context?: Record<string, unknown>): Promise<string>;
1325
+ };
1326
+ }
1327
+ declare class ChatPipeline {
1328
+ private db;
1329
+ private hooks;
1330
+ readonly messageStore: MessageStore;
1331
+ readonly responder: ChatResponder;
1332
+ readonly interpreter: MessageInterpreter;
1333
+ readonly router: TriageRouter;
1334
+ private readonly channel;
1335
+ private readonly messageFilter?;
1336
+ private readonly capabilities?;
1337
+ private readonly dedupWindowMs;
1338
+ private readonly tasks;
1339
+ private readonly wakeups;
1340
+ private readonly threadChannelMap;
1341
+ constructor(db: DataStore, hooks: HookBus, config: ChatPipelineConfig);
1342
+ /**
1343
+ * Resolve the Slack channel ID for a thread (for response delivery).
1344
+ */
1345
+ resolveChannel(threadId: string, taskId?: string): Promise<string | undefined>;
1346
+ /**
1347
+ * Register the 6-layer pipeline on the HookBus.
1348
+ */
1349
+ private registerHandlers;
1350
+ /**
1351
+ * Check and record message dedup (SHA256 hash, configurable window).
1352
+ */
1353
+ private isDuplicate;
1354
+ /**
1355
+ * Async interpretation + task dispatch (Layers 3-5).
1356
+ */
1357
+ private interpretAndDispatch;
1358
+ /**
1359
+ * Route and dispatch extracted tasks.
1360
+ */
1361
+ private dispatchTasks;
1362
+ /**
1363
+ * Resolve Slack channel ID from thread_task_map or in-memory fallback.
1364
+ */
1365
+ private resolveChannelId;
1366
+ /**
1367
+ * Build human-readable interpretation summary.
1368
+ */
1369
+ private buildSummary;
1370
+ }
1371
+
1276
1372
  /**
1277
1373
  * Text chunker — splits long text into chunks at natural boundaries.
1278
1374
  * Story 4.4
@@ -2366,4 +2462,4 @@ declare function isLoginRequired(stdout: string): boolean;
2366
2462
  /** Rewrite local image paths to prevent CLI auto-embedding as vision content. */
2367
2463
  declare function deactivateLocalImagePaths(prompt: string): string;
2368
2464
 
2369
- export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, type ApprovalResponse, type ApprovalStatus, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, BreakerState, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatResponder, type ChatResponderConfig, ChatSessionManager, CircuitBreaker, type CircuitBreakerConfig, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, ConnectorConfig, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, DeterministicAdapter, type DeterministicConfig, type DomainEntityContextOptions, type DomainSchemaOptions, DriftGate, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type ExtractedFile, type ExtractedMemory, type ExtractedTask, type ExtractedUserContext, type Extractor, type FeedbackEntry, type Filter, type GateFinding, type GateInput, type GateResult, GateRunner, type GateVerdict, GovernanceGate, HealthStatus, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, type InterpretationResult, type LLMCallFn, LLMProvider, LearningPipeline, type LearningPipelineConfig, type LoopDetection, LoopDetector, type LoopDetectorConfig, LoopType, MAX_CHAIN_DEPTH, MessageInterpreter, type MessageInterpreterConfig, MessagePipeline, MessageStore, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type ParsedStream, type PermissionPrompt, type PermissionProvider, PermissionRelay, type PermissionRelayConfig, type PkLookup, type PlaybookEntry, ProviderRegistry, QAGate, QualityGate, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type RoutingDecision, type RoutingRule, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type Schedule, type ScheduleDef, Scheduler, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SkillEntry, type SqliteAdapter, type StepRef, type StoreResult, type StoredAttachment, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, TriageRouter, type TriageRouterConfig, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, type UsageSummary, type User, type UserInput, UserRegistry, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, areDependenciesMet, autoUpdate, buildAgentBindings, buildChainOrigin, buildProcessEnv, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, deactivateLocalImagePaths, defineCoreEntityContexts, defineCoreTables, defineDomainEntityContexts, defineDomainTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, isLoginRequired, isMaxTurns, loadConfig, parseClaudeStream, parseVersion, runPackageMigrations, sanitize, topologicalSort, truncateAtWord, validateConfig };
2465
+ export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, type ApprovalResponse, type ApprovalStatus, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, BreakerState, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatPipeline, type ChatPipelineConfig, ChatResponder, type ChatResponderConfig, ChatSessionManager, CircuitBreaker, type CircuitBreakerConfig, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, ConnectorConfig, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, DeterministicAdapter, type DeterministicConfig, type DomainEntityContextOptions, type DomainSchemaOptions, DriftGate, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type ExtractedFile, type ExtractedMemory, type ExtractedTask, type ExtractedUserContext, type Extractor, type FeedbackEntry, type Filter, type GateFinding, type GateInput, type GateResult, GateRunner, type GateVerdict, GovernanceGate, HealthStatus, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, type InterpretationResult, type LLMCallFn, LLMProvider, LearningPipeline, type LearningPipelineConfig, type LoopDetection, LoopDetector, type LoopDetectorConfig, LoopType, MAX_CHAIN_DEPTH, MessageInterpreter, type MessageInterpreterConfig, MessagePipeline, MessageStore, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type ParsedStream, type PermissionPrompt, type PermissionProvider, PermissionRelay, type PermissionRelayConfig, type PkLookup, type PlaybookEntry, ProviderRegistry, QAGate, QualityGate, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type RoutingDecision, type RoutingRule, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type Schedule, type ScheduleDef, Scheduler, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SkillEntry, type SqliteAdapter, type StepRef, type StoreResult, type StoredAttachment, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, TriageRouter, type TriageRouterConfig, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, type UsageSummary, type User, type UserInput, UserRegistry, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, areDependenciesMet, autoUpdate, buildAgentBindings, buildChainOrigin, buildProcessEnv, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, deactivateLocalImagePaths, defineCoreEntityContexts, defineCoreTables, defineDomainEntityContexts, defineDomainTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, isLoginRequired, isMaxTurns, loadConfig, parseClaudeStream, parseVersion, runPackageMigrations, sanitize, topologicalSort, truncateAtWord, validateConfig };
package/dist/index.js CHANGED
@@ -1594,6 +1594,247 @@ var MessageInterpreter = class {
1594
1594
  }
1595
1595
  };
1596
1596
 
1597
+ // src/core/chat/chat-pipeline.ts
1598
+ import { createHash, randomUUID } from "crypto";
1599
+ var DEFAULT_DEDUP_WINDOW_MS = 5 * 60 * 1e3;
1600
+ var ChatPipeline = class {
1601
+ constructor(db, hooks, config) {
1602
+ this.db = db;
1603
+ this.hooks = hooks;
1604
+ this.channel = config.channel ?? "slack";
1605
+ this.messageFilter = config.messageFilter;
1606
+ this.capabilities = config.capabilities;
1607
+ this.dedupWindowMs = config.dedupWindowMs ?? DEFAULT_DEDUP_WINDOW_MS;
1608
+ this.tasks = config.tasks;
1609
+ this.wakeups = config.wakeups;
1610
+ this.messageStore = new MessageStore(db, hooks);
1611
+ this.responder = new ChatResponder(db, hooks, this.messageStore, {
1612
+ llmCall: config.llmCall,
1613
+ model: config.model ?? "fast",
1614
+ systemPrompt: config.systemPrompt
1615
+ });
1616
+ this.interpreter = new MessageInterpreter(db, hooks, {
1617
+ llmCall: config.llmCall,
1618
+ model: config.model ?? "fast",
1619
+ extractors: config.extractors
1620
+ });
1621
+ this.router = new TriageRouter(db, hooks, {
1622
+ rules: config.routingRules,
1623
+ fallbackAgent: config.fallbackAgent,
1624
+ llmFallback: config.llmRouting ?? false,
1625
+ persist: true
1626
+ });
1627
+ this.registerHandlers();
1628
+ }
1629
+ db;
1630
+ hooks;
1631
+ messageStore;
1632
+ responder;
1633
+ interpreter;
1634
+ router;
1635
+ channel;
1636
+ messageFilter;
1637
+ capabilities;
1638
+ dedupWindowMs;
1639
+ tasks;
1640
+ wakeups;
1641
+ // In-memory thread → channel mapping for response routing
1642
+ // (before thread_task_map exists)
1643
+ threadChannelMap = /* @__PURE__ */ new Map();
1644
+ /**
1645
+ * Resolve the Slack channel ID for a thread (for response delivery).
1646
+ */
1647
+ resolveChannel(threadId, taskId) {
1648
+ return this.resolveChannelId(threadId, taskId);
1649
+ }
1650
+ /**
1651
+ * Register the 6-layer pipeline on the HookBus.
1652
+ */
1653
+ registerHandlers() {
1654
+ this.hooks.register("message.inbound", async (ctx) => {
1655
+ const msg = ctx;
1656
+ if (msg.channel !== this.channel) return;
1657
+ if (this.messageFilter && !this.messageFilter(msg)) return;
1658
+ if (await this.isDuplicate(msg)) return;
1659
+ const rawTs = msg.raw?.ts;
1660
+ const threadTs = msg.threadId ?? rawTs ?? msg.id;
1661
+ const channelId = msg.account ?? "";
1662
+ if (threadTs && channelId) {
1663
+ this.threadChannelMap.set(threadTs, channelId);
1664
+ }
1665
+ const { messageId } = await this.messageStore.storeInbound(msg);
1666
+ const ackResponse = await this.responder.respond({
1667
+ messageBody: msg.body,
1668
+ threadId: threadTs,
1669
+ channel: this.channel,
1670
+ capabilities: this.capabilities
1671
+ });
1672
+ await this.responder.sendResponse({
1673
+ text: ackResponse,
1674
+ channel: this.channel,
1675
+ threadId: threadTs,
1676
+ source: "responder",
1677
+ skipFilter: true,
1678
+ skipRedundancyCheck: true
1679
+ });
1680
+ void this.interpretAndDispatch(messageId, msg, threadTs, channelId);
1681
+ });
1682
+ this.hooks.register("run.completed", async (ctx) => {
1683
+ const taskId = ctx.taskId;
1684
+ if (!taskId) return;
1685
+ const task = await this.db.get("tasks", { id: taskId });
1686
+ const output = task?.result;
1687
+ if (!output) return;
1688
+ const mappings = await this.db.query("thread_task_map", {
1689
+ where: { task_id: taskId }
1690
+ });
1691
+ if (mappings.length === 0) return;
1692
+ const mapping = mappings[0];
1693
+ await this.responder.sendResponse({
1694
+ text: output,
1695
+ channel: this.channel,
1696
+ threadId: mapping.thread_ts,
1697
+ agentId: ctx.agentId,
1698
+ taskId,
1699
+ source: "agent"
1700
+ });
1701
+ }, { priority: 90 });
1702
+ }
1703
+ /**
1704
+ * Check and record message dedup (SHA256 hash, configurable window).
1705
+ */
1706
+ async isDuplicate(msg) {
1707
+ const hash = createHash("sha256").update(`${msg.from}:${msg.body}`).digest("hex");
1708
+ const cutoff = new Date(Date.now() - this.dedupWindowMs).toISOString();
1709
+ const recent = await this.db.query("message_dedup", {
1710
+ where: { content_hash: hash }
1711
+ });
1712
+ if (recent.some((r) => r.created_at > cutoff)) {
1713
+ return true;
1714
+ }
1715
+ await this.db.insert("message_dedup", {
1716
+ content_hash: hash,
1717
+ channel_id: msg.account ?? ""
1718
+ });
1719
+ return false;
1720
+ }
1721
+ /**
1722
+ * Async interpretation + task dispatch (Layers 3-5).
1723
+ */
1724
+ async interpretAndDispatch(messageId, msg, threadTs, channelId) {
1725
+ try {
1726
+ const result = await this.interpreter.interpret(messageId);
1727
+ if (result.tasks.length > 0 || result.memories.length > 0) {
1728
+ const summary = this.buildSummary(result);
1729
+ await this.responder.sendResponse({
1730
+ text: summary,
1731
+ channel: this.channel,
1732
+ threadId: threadTs,
1733
+ source: "interpretation"
1734
+ });
1735
+ }
1736
+ if (result.isTaskRequest && result.tasks.length > 0) {
1737
+ await this.dispatchTasks(result, msg, threadTs, channelId);
1738
+ }
1739
+ } catch (err) {
1740
+ await this.hooks.emit("interpretation.error", {
1741
+ messageId,
1742
+ error: err instanceof Error ? err.message : String(err)
1743
+ });
1744
+ }
1745
+ }
1746
+ /**
1747
+ * Route and dispatch extracted tasks.
1748
+ */
1749
+ async dispatchTasks(result, msg, threadTs, channelId) {
1750
+ for (const extractedTask of result.tasks) {
1751
+ const { agentSlug: targetSlug } = await this.router.route(msg);
1752
+ if (!targetSlug) continue;
1753
+ const agents = await this.db.query("agents", { where: { slug: targetSlug } });
1754
+ const targetAgent = agents[0];
1755
+ if (!targetAgent) continue;
1756
+ const handlerAgentId = targetAgent.id;
1757
+ if (threadTs) {
1758
+ const existing = await this.db.query("thread_task_map", {
1759
+ where: { thread_ts: threadTs, channel_id: channelId }
1760
+ });
1761
+ if (existing.length > 0) {
1762
+ const taskId2 = existing[0].task_id;
1763
+ const task = await this.tasks.get(taskId2);
1764
+ if (task && task.status !== "done" && task.status !== "cancelled") {
1765
+ const updatedDesc = `${task.description ?? ""}
1766
+
1767
+ ---
1768
+ **Follow-up (${(/* @__PURE__ */ new Date()).toISOString()}):**
1769
+ ${msg.body}`;
1770
+ await this.tasks.update(taskId2, { description: updatedDesc });
1771
+ await this.wakeups.enqueue(handlerAgentId, "chat_followup", { taskId: taskId2 });
1772
+ return;
1773
+ }
1774
+ }
1775
+ }
1776
+ const description = `## Chat Message
1777
+
1778
+ **Channel:** ${channelId}
1779
+ **Thread:** ${threadTs}
1780
+ **From:** ${msg.from}
1781
+ **Time:** ${msg.receivedAt}
1782
+
1783
+ ---
1784
+
1785
+ ${extractedTask.description ?? msg.body}`;
1786
+ const taskId = randomUUID();
1787
+ if (threadTs) {
1788
+ await this.db.insert("thread_task_map", {
1789
+ thread_ts: threadTs,
1790
+ channel_id: channelId,
1791
+ task_id: taskId
1792
+ });
1793
+ }
1794
+ await this.tasks.create({
1795
+ id: taskId,
1796
+ title: extractedTask.title.slice(0, 120),
1797
+ description,
1798
+ assignee_id: handlerAgentId,
1799
+ priority: extractedTask.priority ?? 5
1800
+ });
1801
+ await this.wakeups.enqueue(handlerAgentId, "chat_dispatch", { taskId });
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Resolve Slack channel ID from thread_task_map or in-memory fallback.
1806
+ */
1807
+ async resolveChannelId(threadId, taskId) {
1808
+ if (taskId) {
1809
+ const mappings = await this.db.query("thread_task_map", {
1810
+ where: { task_id: taskId }
1811
+ });
1812
+ if (mappings.length > 0) return mappings[0].channel_id;
1813
+ }
1814
+ if (threadId) {
1815
+ const mappings = await this.db.query("thread_task_map", {
1816
+ where: { thread_ts: threadId }
1817
+ });
1818
+ if (mappings.length > 0) return mappings[0].channel_id;
1819
+ }
1820
+ return this.threadChannelMap.get(threadId);
1821
+ }
1822
+ /**
1823
+ * Build human-readable interpretation summary.
1824
+ */
1825
+ buildSummary(result) {
1826
+ const parts = [];
1827
+ if (result.tasks.length > 0) {
1828
+ const names = result.tasks.map((t) => t.title).join(", ");
1829
+ parts.push(`I've identified ${result.tasks.length} task${result.tasks.length > 1 ? "s" : ""}: ${names}. Working on ${result.tasks.length > 1 ? "them" : "it"} now.`);
1830
+ }
1831
+ if (result.memories.length > 0) {
1832
+ parts.push(`Noted ${result.memories.length} thing${result.memories.length > 1 ? "s" : ""} to remember.`);
1833
+ }
1834
+ return parts.join(" ");
1835
+ }
1836
+ };
1837
+
1597
1838
  // src/core/chat/text-chunker.ts
1598
1839
  function chunkText(text, maxLen) {
1599
1840
  if (maxLen <= 0) throw new Error("maxLen must be > 0");
@@ -2279,6 +2520,30 @@ function defineCoreTables(db) {
2279
2520
  "FOREIGN KEY (message_id) REFERENCES messages(id)"
2280
2521
  ]
2281
2522
  });
2523
+ db.define("thread_task_map", {
2524
+ columns: {
2525
+ id: "TEXT PRIMARY KEY",
2526
+ thread_ts: "TEXT NOT NULL",
2527
+ channel_id: "TEXT NOT NULL",
2528
+ task_id: "TEXT NOT NULL",
2529
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
2530
+ },
2531
+ tableConstraints: [
2532
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_thread_task_map_thread ON thread_task_map(thread_ts, channel_id)",
2533
+ "CREATE INDEX IF NOT EXISTS idx_thread_task_map_task ON thread_task_map(task_id)"
2534
+ ]
2535
+ });
2536
+ db.define("message_dedup", {
2537
+ columns: {
2538
+ id: "TEXT PRIMARY KEY",
2539
+ content_hash: "TEXT NOT NULL",
2540
+ channel_id: "TEXT NOT NULL",
2541
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
2542
+ },
2543
+ tableConstraints: [
2544
+ "CREATE INDEX IF NOT EXISTS idx_message_dedup_hash ON message_dedup(content_hash, created_at)"
2545
+ ]
2546
+ });
2282
2547
  db.define("memories", {
2283
2548
  columns: {
2284
2549
  id: "TEXT PRIMARY KEY",
@@ -2417,7 +2682,7 @@ function defineCoreEntityContexts(db) {
2417
2682
  db.defineEntityContext("users", {
2418
2683
  table: "users",
2419
2684
  directory: "users",
2420
- slugColumn: "name",
2685
+ slugColumn: "id",
2421
2686
  protected: true,
2422
2687
  indexFile: "users/USERS.md",
2423
2688
  files: {
@@ -2830,7 +3095,7 @@ function defineDomainEntityContexts(db, options = {}) {
2830
3095
  db.defineEntityContext("org", {
2831
3096
  table: "org",
2832
3097
  directory: "orgs",
2833
- slugColumn: "name",
3098
+ slugColumn: "id",
2834
3099
  indexFile: "orgs/ORGS.md",
2835
3100
  files: {
2836
3101
  "ORG.md": {
@@ -2856,7 +3121,7 @@ ${o.description}` : null,
2856
3121
  db.defineEntityContext("project", {
2857
3122
  table: "project",
2858
3123
  directory: "projects",
2859
- slugColumn: "name",
3124
+ slugColumn: "id",
2860
3125
  indexFile: "projects/PROJECTS.md",
2861
3126
  files: {
2862
3127
  "PROJECT.md": {
@@ -2980,7 +3245,7 @@ ${lines.join("\n")}
2980
3245
  db.defineEntityContext("client", {
2981
3246
  table: "client",
2982
3247
  directory: "clients",
2983
- slugColumn: "name",
3248
+ slugColumn: "id",
2984
3249
  indexFile: "clients/CLIENTS.md",
2985
3250
  files: {
2986
3251
  "CLIENT.md": {
@@ -3067,7 +3332,7 @@ ${lines.join("\n")}
3067
3332
  db.defineEntityContext("file", {
3068
3333
  table: "file",
3069
3334
  directory: "files",
3070
- slugColumn: "name",
3335
+ slugColumn: "id",
3071
3336
  indexFile: "files/FILES.md",
3072
3337
  files: {
3073
3338
  "FILE.md": {
@@ -3094,7 +3359,7 @@ ${f.description}` : null,
3094
3359
  db.defineEntityContext("channel", {
3095
3360
  table: "channel",
3096
3361
  directory: "channels",
3097
- slugColumn: "name",
3362
+ slugColumn: "id",
3098
3363
  indexFile: "channels/CHANNELS.md",
3099
3364
  files: {
3100
3365
  "CHANNEL.md": {
@@ -5944,6 +6209,7 @@ export {
5944
6209
  CORE_MIGRATIONS,
5945
6210
  ChannelRegistry,
5946
6211
  ChannelRegistryError,
6212
+ ChatPipeline,
5947
6213
  ChatResponder,
5948
6214
  ChatSessionManager,
5949
6215
  CircuitBreaker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",