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 +97 -1
- package/dist/index.js +272 -6
- package/package.json +1 -1
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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,
|