botinabox 1.8.3 → 1.8.4
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.
|
@@ -72,6 +72,8 @@ export declare class ChatPipeline {
|
|
|
72
72
|
private readonly tasks;
|
|
73
73
|
private readonly wakeups;
|
|
74
74
|
private readonly threadChannelMap;
|
|
75
|
+
/** Last dispatch promise — exposed for testing. */
|
|
76
|
+
lastDispatch: Promise<void>;
|
|
75
77
|
constructor(db: DataStore, hooks: HookBus, config: ChatPipelineConfig);
|
|
76
78
|
/**
|
|
77
79
|
* Resolve the Slack channel ID for a thread (for response delivery).
|
|
@@ -87,8 +89,15 @@ export declare class ChatPipeline {
|
|
|
87
89
|
private isDuplicate;
|
|
88
90
|
/**
|
|
89
91
|
* Async interpretation + task dispatch (Layers 3-5).
|
|
92
|
+
*
|
|
93
|
+
* ALWAYS creates a task programmatically — task creation does not depend
|
|
94
|
+
* on LLM classification. Interpretation enriches but never gates dispatch.
|
|
90
95
|
*/
|
|
91
96
|
private interpretAndDispatch;
|
|
97
|
+
/**
|
|
98
|
+
* Programmatic task creation — guaranteed, no LLM dependency.
|
|
99
|
+
*/
|
|
100
|
+
private guaranteedTaskDispatch;
|
|
92
101
|
/**
|
|
93
102
|
* Route and dispatch extracted tasks.
|
|
94
103
|
*/
|
|
@@ -36,6 +36,8 @@ export class ChatPipeline {
|
|
|
36
36
|
// In-memory thread → channel mapping for response routing
|
|
37
37
|
// (before thread_task_map exists)
|
|
38
38
|
threadChannelMap = new Map();
|
|
39
|
+
/** Last dispatch promise — exposed for testing. */
|
|
40
|
+
lastDispatch = Promise.resolve();
|
|
39
41
|
constructor(db, hooks, config) {
|
|
40
42
|
this.db = db;
|
|
41
43
|
this.hooks = hooks;
|
|
@@ -119,8 +121,12 @@ export class ChatPipeline {
|
|
|
119
121
|
skipFilter: true,
|
|
120
122
|
skipRedundancyCheck: true,
|
|
121
123
|
});
|
|
122
|
-
// ── Layer 3-5:
|
|
123
|
-
|
|
124
|
+
// ── Layer 3-5: Interpretation + guaranteed task dispatch ────
|
|
125
|
+
// ALWAYS create a task programmatically. Interpretation enriches
|
|
126
|
+
// it with classification, but task creation is not LLM-dependent.
|
|
127
|
+
const dispatchPromise = this.interpretAndDispatch(messageId, msg, threadTs, channelId);
|
|
128
|
+
this.lastDispatch = dispatchPromise;
|
|
129
|
+
void dispatchPromise;
|
|
124
130
|
});
|
|
125
131
|
// Layer 6: Task execution response
|
|
126
132
|
this.hooks.register('run.completed', async (ctx) => {
|
|
@@ -144,6 +150,7 @@ export class ChatPipeline {
|
|
|
144
150
|
agentId: ctx.agentId,
|
|
145
151
|
taskId,
|
|
146
152
|
source: 'agent',
|
|
153
|
+
skipRedundancyCheck: true, // Task results are always delivered
|
|
147
154
|
});
|
|
148
155
|
}, { priority: 90 });
|
|
149
156
|
}
|
|
@@ -169,32 +176,93 @@ export class ChatPipeline {
|
|
|
169
176
|
}
|
|
170
177
|
/**
|
|
171
178
|
* Async interpretation + task dispatch (Layers 3-5).
|
|
179
|
+
*
|
|
180
|
+
* ALWAYS creates a task programmatically — task creation does not depend
|
|
181
|
+
* on LLM classification. Interpretation enriches but never gates dispatch.
|
|
172
182
|
*/
|
|
173
183
|
async interpretAndDispatch(messageId, msg, threadTs, channelId) {
|
|
184
|
+
// Layer 5: ALWAYS create a task — this is programmatic, not LLM-dependent
|
|
185
|
+
await this.guaranteedTaskDispatch(msg, threadTs, channelId);
|
|
186
|
+
// Layer 3-4: Interpretation is best-effort enrichment (memories, user context)
|
|
174
187
|
try {
|
|
175
188
|
const result = await this.interpreter.interpret(messageId);
|
|
176
|
-
//
|
|
177
|
-
if (result.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
// Store any extracted memories (enrichment only — task already created above)
|
|
190
|
+
if (result.memories.length > 0 || result.userContext.length > 0) {
|
|
191
|
+
try {
|
|
192
|
+
const parts = [];
|
|
193
|
+
if (result.memories.length > 0) {
|
|
194
|
+
parts.push(`Noted ${result.memories.length} thing${result.memories.length > 1 ? 's' : ''} to remember.`);
|
|
195
|
+
}
|
|
196
|
+
if (parts.length > 0) {
|
|
197
|
+
await this.responder.sendResponse({
|
|
198
|
+
text: parts.join(' '),
|
|
199
|
+
channel: this.channel,
|
|
200
|
+
threadId: threadTs,
|
|
201
|
+
source: 'interpretation',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Non-fatal
|
|
207
|
+
}
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
catch (err) {
|
|
192
|
-
// Interpretation failure is non-fatal —
|
|
211
|
+
// Interpretation failure is non-fatal — task was already created above
|
|
212
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
193
213
|
await this.hooks.emit('interpretation.error', {
|
|
194
214
|
messageId,
|
|
195
|
-
error:
|
|
215
|
+
error: errMsg,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Programmatic task creation — guaranteed, no LLM dependency.
|
|
221
|
+
*/
|
|
222
|
+
async guaranteedTaskDispatch(msg, threadTs, channelId) {
|
|
223
|
+
// Route to best agent
|
|
224
|
+
const { agentSlug: targetSlug } = await this.router.route(msg);
|
|
225
|
+
if (!targetSlug)
|
|
226
|
+
return;
|
|
227
|
+
const agents = await this.db.query('agents', { where: { slug: targetSlug } });
|
|
228
|
+
const targetAgent = agents[0];
|
|
229
|
+
if (!targetAgent)
|
|
230
|
+
return;
|
|
231
|
+
const handlerAgentId = targetAgent.id;
|
|
232
|
+
// Follow-up in existing thread
|
|
233
|
+
if (threadTs) {
|
|
234
|
+
const existing = await this.db.query('thread_task_map', {
|
|
235
|
+
where: { thread_ts: threadTs, channel_id: channelId },
|
|
236
|
+
});
|
|
237
|
+
if (existing.length > 0) {
|
|
238
|
+
const taskId = existing[0].task_id;
|
|
239
|
+
const task = await this.tasks.get(taskId);
|
|
240
|
+
if (task && task.status !== 'done' && task.status !== 'cancelled') {
|
|
241
|
+
const updatedDesc = `${task.description ?? ''}\n\n---\n**Follow-up (${new Date().toISOString()}):**\n${msg.body}`;
|
|
242
|
+
await this.tasks.update(taskId, { description: updatedDesc });
|
|
243
|
+
await this.wakeups.enqueue(handlerAgentId, 'chat_followup', { taskId });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// New task — programmatic, guaranteed
|
|
249
|
+
const description = `## Chat Message\n\n**Channel:** ${channelId}\n**Thread:** ${threadTs}\n**From:** ${msg.from}\n**Time:** ${msg.receivedAt}\n\n---\n\n${msg.body}`;
|
|
250
|
+
const taskId = randomUUID();
|
|
251
|
+
if (threadTs) {
|
|
252
|
+
await this.db.insert('thread_task_map', {
|
|
253
|
+
thread_ts: threadTs,
|
|
254
|
+
channel_id: channelId,
|
|
255
|
+
task_id: taskId,
|
|
196
256
|
});
|
|
197
257
|
}
|
|
258
|
+
await this.tasks.create({
|
|
259
|
+
id: taskId,
|
|
260
|
+
title: msg.body.slice(0, 120),
|
|
261
|
+
description,
|
|
262
|
+
assignee_id: handlerAgentId,
|
|
263
|
+
priority: 5,
|
|
264
|
+
});
|
|
265
|
+
await this.wakeups.enqueue(handlerAgentId, 'chat_dispatch', { taskId });
|
|
198
266
|
}
|
|
199
267
|
/**
|
|
200
268
|
* Route and dispatch extracted tasks.
|
|
@@ -131,7 +131,9 @@ export class MessageInterpreter {
|
|
|
131
131
|
system: INTERPRET_SYSTEM,
|
|
132
132
|
maxTokens: 1000,
|
|
133
133
|
});
|
|
134
|
-
|
|
134
|
+
// Strip markdown fences if present (LLMs often wrap JSON in ```json...```)
|
|
135
|
+
const raw = result.content.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
|
|
136
|
+
const parsed = JSON.parse(raw);
|
|
135
137
|
return {
|
|
136
138
|
tasks: (parsed.tasks ?? []).map(t => ({
|
|
137
139
|
title: t.title,
|
package/dist/index.d.ts
CHANGED
|
@@ -1360,8 +1360,15 @@ declare class ChatPipeline {
|
|
|
1360
1360
|
private isDuplicate;
|
|
1361
1361
|
/**
|
|
1362
1362
|
* Async interpretation + task dispatch (Layers 3-5).
|
|
1363
|
+
*
|
|
1364
|
+
* ALWAYS creates a task programmatically — task creation does not depend
|
|
1365
|
+
* on LLM classification. Interpretation enriches but never gates dispatch.
|
|
1363
1366
|
*/
|
|
1364
1367
|
private interpretAndDispatch;
|
|
1368
|
+
/**
|
|
1369
|
+
* Programmatic task creation — guaranteed, no LLM dependency.
|
|
1370
|
+
*/
|
|
1371
|
+
private guaranteedTaskDispatch;
|
|
1365
1372
|
/**
|
|
1366
1373
|
* Route and dispatch extracted tasks.
|
|
1367
1374
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1576,7 +1576,8 @@ var MessageInterpreter = class {
|
|
|
1576
1576
|
system: INTERPRET_SYSTEM,
|
|
1577
1577
|
maxTokens: 1e3
|
|
1578
1578
|
});
|
|
1579
|
-
const
|
|
1579
|
+
const raw = result.content.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
1580
|
+
const parsed = JSON.parse(raw);
|
|
1580
1581
|
return {
|
|
1581
1582
|
tasks: (parsed.tasks ?? []).map((t) => ({
|
|
1582
1583
|
title: t.title,
|
|
@@ -1725,7 +1726,9 @@ ${historyContext}` : void 0
|
|
|
1725
1726
|
threadId: mapping.thread_ts,
|
|
1726
1727
|
agentId: ctx.agentId,
|
|
1727
1728
|
taskId,
|
|
1728
|
-
source: "agent"
|
|
1729
|
+
source: "agent",
|
|
1730
|
+
skipRedundancyCheck: true
|
|
1731
|
+
// Task results are always delivered
|
|
1729
1732
|
});
|
|
1730
1733
|
}, { priority: 90 });
|
|
1731
1734
|
}
|
|
@@ -1749,25 +1752,31 @@ ${historyContext}` : void 0
|
|
|
1749
1752
|
}
|
|
1750
1753
|
/**
|
|
1751
1754
|
* Async interpretation + task dispatch (Layers 3-5).
|
|
1755
|
+
*
|
|
1756
|
+
* ALWAYS creates a task programmatically — task creation does not depend
|
|
1757
|
+
* on LLM classification. Interpretation enriches but never gates dispatch.
|
|
1752
1758
|
*/
|
|
1753
1759
|
async interpretAndDispatch(messageId, msg, threadTs, channelId) {
|
|
1760
|
+
await this.guaranteedTaskDispatch(msg, threadTs, channelId);
|
|
1754
1761
|
try {
|
|
1755
1762
|
const result = await this.interpreter.interpret(messageId);
|
|
1756
|
-
if (result.
|
|
1763
|
+
if (result.memories.length > 0 || result.userContext.length > 0) {
|
|
1757
1764
|
try {
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
+
const parts = [];
|
|
1766
|
+
if (result.memories.length > 0) {
|
|
1767
|
+
parts.push(`Noted ${result.memories.length} thing${result.memories.length > 1 ? "s" : ""} to remember.`);
|
|
1768
|
+
}
|
|
1769
|
+
if (parts.length > 0) {
|
|
1770
|
+
await this.responder.sendResponse({
|
|
1771
|
+
text: parts.join(" "),
|
|
1772
|
+
channel: this.channel,
|
|
1773
|
+
threadId: threadTs,
|
|
1774
|
+
source: "interpretation"
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1765
1777
|
} catch {
|
|
1766
1778
|
}
|
|
1767
1779
|
}
|
|
1768
|
-
if (result.tasks.length > 0) {
|
|
1769
|
-
await this.dispatchTasks(result, msg, threadTs, channelId);
|
|
1770
|
-
}
|
|
1771
1780
|
} catch (err) {
|
|
1772
1781
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1773
1782
|
await this.hooks.emit("interpretation.error", {
|
|
@@ -1776,6 +1785,62 @@ ${historyContext}` : void 0
|
|
|
1776
1785
|
});
|
|
1777
1786
|
}
|
|
1778
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Programmatic task creation — guaranteed, no LLM dependency.
|
|
1790
|
+
*/
|
|
1791
|
+
async guaranteedTaskDispatch(msg, threadTs, channelId) {
|
|
1792
|
+
const { agentSlug: targetSlug } = await this.router.route(msg);
|
|
1793
|
+
if (!targetSlug) return;
|
|
1794
|
+
const agents = await this.db.query("agents", { where: { slug: targetSlug } });
|
|
1795
|
+
const targetAgent = agents[0];
|
|
1796
|
+
if (!targetAgent) return;
|
|
1797
|
+
const handlerAgentId = targetAgent.id;
|
|
1798
|
+
if (threadTs) {
|
|
1799
|
+
const existing = await this.db.query("thread_task_map", {
|
|
1800
|
+
where: { thread_ts: threadTs, channel_id: channelId }
|
|
1801
|
+
});
|
|
1802
|
+
if (existing.length > 0) {
|
|
1803
|
+
const taskId2 = existing[0].task_id;
|
|
1804
|
+
const task = await this.tasks.get(taskId2);
|
|
1805
|
+
if (task && task.status !== "done" && task.status !== "cancelled") {
|
|
1806
|
+
const updatedDesc = `${task.description ?? ""}
|
|
1807
|
+
|
|
1808
|
+
---
|
|
1809
|
+
**Follow-up (${(/* @__PURE__ */ new Date()).toISOString()}):**
|
|
1810
|
+
${msg.body}`;
|
|
1811
|
+
await this.tasks.update(taskId2, { description: updatedDesc });
|
|
1812
|
+
await this.wakeups.enqueue(handlerAgentId, "chat_followup", { taskId: taskId2 });
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
const description = `## Chat Message
|
|
1818
|
+
|
|
1819
|
+
**Channel:** ${channelId}
|
|
1820
|
+
**Thread:** ${threadTs}
|
|
1821
|
+
**From:** ${msg.from}
|
|
1822
|
+
**Time:** ${msg.receivedAt}
|
|
1823
|
+
|
|
1824
|
+
---
|
|
1825
|
+
|
|
1826
|
+
${msg.body}`;
|
|
1827
|
+
const taskId = randomUUID();
|
|
1828
|
+
if (threadTs) {
|
|
1829
|
+
await this.db.insert("thread_task_map", {
|
|
1830
|
+
thread_ts: threadTs,
|
|
1831
|
+
channel_id: channelId,
|
|
1832
|
+
task_id: taskId
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
await this.tasks.create({
|
|
1836
|
+
id: taskId,
|
|
1837
|
+
title: msg.body.slice(0, 120),
|
|
1838
|
+
description,
|
|
1839
|
+
assignee_id: handlerAgentId,
|
|
1840
|
+
priority: 5
|
|
1841
|
+
});
|
|
1842
|
+
await this.wakeups.enqueue(handlerAgentId, "chat_dispatch", { taskId });
|
|
1843
|
+
}
|
|
1779
1844
|
/**
|
|
1780
1845
|
* Route and dispatch extracted tasks.
|
|
1781
1846
|
*/
|