botinabox 2.16.5 → 2.16.7
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/core/chat/chat-pipeline-v2.d.ts +21 -1
- package/dist/index.js +151 -138
- package/package.json +2 -2
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { DataStore } from '../data/data-store.js';
|
|
16
16
|
import type { HookBus } from '../hooks/hook-bus.js';
|
|
17
17
|
import type { InboundMessage } from './types.js';
|
|
18
|
-
import type { ToolDefinition, ToolHandler } from '../orchestrator/execution-engine.js';
|
|
18
|
+
import type { ToolDefinition, ToolHandler, ContextFile } from '../orchestrator/execution-engine.js';
|
|
19
19
|
import type { SystemContextOptions } from '../data/context-builder.js';
|
|
20
20
|
import type { Extractor } from './message-interpreter.js';
|
|
21
21
|
import { MessageStore } from './message-store.js';
|
|
@@ -97,6 +97,26 @@ export interface ChatPipelineV2Config {
|
|
|
97
97
|
subAgentResultMode?: 'passthrough' | 'synthesize';
|
|
98
98
|
/** Resolve file paths from DB-relative to absolute */
|
|
99
99
|
resolveFilePath?: (path: string) => string;
|
|
100
|
+
/**
|
|
101
|
+
* Optional per-turn context resolver. Called once per inbound message with
|
|
102
|
+
* the resolved conversation coordinates. Returned files are wrapped in
|
|
103
|
+
* `<file path="...">...</file>` XML tags and appended to the system prompt
|
|
104
|
+
* AFTER the static `buildSystemContext` block. Intended for apps that want
|
|
105
|
+
* to inject per-conversation rendered context (e.g. reward-ranked entity or
|
|
106
|
+
* memory files) that is not already covered by `buildSystemContext`.
|
|
107
|
+
*
|
|
108
|
+
* The resolver owns all filesystem / database lookups — the pipeline does
|
|
109
|
+
* not touch the disk. If the resolver throws, the error propagates into the
|
|
110
|
+
* Phase-1 try/catch, which logs loudly and emits `pipeline.error`; there is
|
|
111
|
+
* no silent fallback to an empty context.
|
|
112
|
+
*/
|
|
113
|
+
resolveContextFiles?: (ctx: {
|
|
114
|
+
channelId: string;
|
|
115
|
+
threadId: string;
|
|
116
|
+
userId?: string;
|
|
117
|
+
messageText: string;
|
|
118
|
+
channel: string;
|
|
119
|
+
}) => Promise<ContextFile[]> | ContextFile[];
|
|
100
120
|
}
|
|
101
121
|
export declare class ChatPipelineV2 {
|
|
102
122
|
private db;
|
package/dist/index.js
CHANGED
|
@@ -2101,6 +2101,144 @@ ${list}`);
|
|
|
2101
2101
|
return sections.join("\n\n");
|
|
2102
2102
|
}
|
|
2103
2103
|
|
|
2104
|
+
// src/core/orchestrator/execution-engine.ts
|
|
2105
|
+
function formatContextFilesBlock(files) {
|
|
2106
|
+
if (!files || files.length === 0) return "";
|
|
2107
|
+
return files.map((f) => `<file path="${f.path}">
|
|
2108
|
+
${f.content}
|
|
2109
|
+
</file>`).join("\n\n");
|
|
2110
|
+
}
|
|
2111
|
+
async function registerExecutionEngine(opts) {
|
|
2112
|
+
const { db, hooks, runs, config } = opts;
|
|
2113
|
+
const model = config.model ?? "claude-sonnet-4-20250514";
|
|
2114
|
+
const maxIterations = config.maxIterations ?? 5;
|
|
2115
|
+
const includeContext = config.includeSystemContext ?? true;
|
|
2116
|
+
const systemContext = includeContext ? await buildSystemContext(db) : "";
|
|
2117
|
+
const toolDefs = (config.tools ?? []).map((t) => t.definition);
|
|
2118
|
+
const toolHandlers = new Map(
|
|
2119
|
+
(config.tools ?? []).map((t) => [t.definition.name, t.handler])
|
|
2120
|
+
);
|
|
2121
|
+
async function tryExecuteTask(taskId, hintAgentId) {
|
|
2122
|
+
const task = await db.get("tasks", { id: taskId });
|
|
2123
|
+
if (!task || task.status !== "todo") return;
|
|
2124
|
+
const nextRetryAt = task["next_retry_at"];
|
|
2125
|
+
if (nextRetryAt && new Date(nextRetryAt) > /* @__PURE__ */ new Date()) return;
|
|
2126
|
+
const assigneeId = hintAgentId ?? task.assignee_id;
|
|
2127
|
+
if (!assigneeId) return;
|
|
2128
|
+
if (runs.isLocked(assigneeId)) return;
|
|
2129
|
+
const agent = await db.get("agents", { id: assigneeId });
|
|
2130
|
+
if (!agent) return;
|
|
2131
|
+
let runId;
|
|
2132
|
+
try {
|
|
2133
|
+
runId = await runs.startRun(assigneeId, taskId, "api");
|
|
2134
|
+
} catch {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
const prompt = task.description ?? task.title ?? "";
|
|
2138
|
+
try {
|
|
2139
|
+
const taskModel = config.resolveModel ? config.resolveModel({ agent, task }) ?? model : model;
|
|
2140
|
+
const contextFiles = config.resolveContextFiles ? await config.resolveContextFiles({ agent, task }) : [];
|
|
2141
|
+
const contextFilesBlock = formatContextFilesBlock(contextFiles);
|
|
2142
|
+
const toolListing = toolDefs.length > 0 ? `
|
|
2143
|
+
## Available Tools
|
|
2144
|
+
${toolDefs.map((t) => `- **${t.name}**: ${t.description}`).join("\n")}
|
|
2145
|
+
|
|
2146
|
+
Use your tools to take action. Do NOT describe what you would do \u2014 call the tool.` : "";
|
|
2147
|
+
const systemPrompt = [
|
|
2148
|
+
`You are ${agent.name}, an AI agent with role: ${agent.role}.`,
|
|
2149
|
+
systemContext ? `
|
|
2150
|
+
${systemContext}` : "",
|
|
2151
|
+
contextFilesBlock ? `
|
|
2152
|
+
${contextFilesBlock}` : "",
|
|
2153
|
+
toolListing,
|
|
2154
|
+
config.systemPromptSuffix ?? ""
|
|
2155
|
+
].filter(Boolean).join("\n");
|
|
2156
|
+
const messages = [{ role: "user", content: prompt }];
|
|
2157
|
+
let finalOutput = "";
|
|
2158
|
+
let totalInput = 0;
|
|
2159
|
+
let totalOutput = 0;
|
|
2160
|
+
const systemField = config.cacheSystemPrompt ? [
|
|
2161
|
+
{
|
|
2162
|
+
type: "text",
|
|
2163
|
+
text: systemPrompt,
|
|
2164
|
+
cache_control: { type: "ephemeral" }
|
|
2165
|
+
}
|
|
2166
|
+
] : systemPrompt;
|
|
2167
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
2168
|
+
const createParams = {
|
|
2169
|
+
model: taskModel,
|
|
2170
|
+
max_tokens: 4096,
|
|
2171
|
+
system: systemField,
|
|
2172
|
+
messages
|
|
2173
|
+
};
|
|
2174
|
+
if (toolDefs.length > 0) {
|
|
2175
|
+
createParams.tools = toolDefs;
|
|
2176
|
+
createParams.tool_choice = i === 0 ? { type: "any" } : { type: "auto" };
|
|
2177
|
+
}
|
|
2178
|
+
const response = await config.client.messages.create(createParams);
|
|
2179
|
+
totalInput += response.usage.input_tokens;
|
|
2180
|
+
totalOutput += response.usage.output_tokens;
|
|
2181
|
+
const textBlocks = response.content.filter((b) => b.type === "text").map((b) => b.text ?? "");
|
|
2182
|
+
if (textBlocks.length > 0) finalOutput += textBlocks.join("");
|
|
2183
|
+
if (response.stop_reason !== "tool_use") break;
|
|
2184
|
+
const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
|
|
2185
|
+
const toolResults = [];
|
|
2186
|
+
for (const toolUse of toolUseBlocks) {
|
|
2187
|
+
const handler = toolHandlers.get(toolUse.name);
|
|
2188
|
+
if (handler) {
|
|
2189
|
+
try {
|
|
2190
|
+
const result = await handler(
|
|
2191
|
+
toolUse.input,
|
|
2192
|
+
{ taskId, agentId: assigneeId, hooks, db, resolveFilePath: config.resolveFilePath }
|
|
2193
|
+
);
|
|
2194
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result });
|
|
2195
|
+
} catch (err) {
|
|
2196
|
+
toolResults.push({
|
|
2197
|
+
type: "tool_result",
|
|
2198
|
+
tool_use_id: toolUse.id,
|
|
2199
|
+
content: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
messages.push({ role: "assistant", content: response.content });
|
|
2205
|
+
messages.push({ role: "user", content: toolResults });
|
|
2206
|
+
}
|
|
2207
|
+
const costCents = Math.round((totalInput * 0.3 + totalOutput * 1.5) / 100);
|
|
2208
|
+
await runs.finishRun(runId, {
|
|
2209
|
+
exitCode: 0,
|
|
2210
|
+
output: finalOutput,
|
|
2211
|
+
costCents,
|
|
2212
|
+
usage: { inputTokens: totalInput, outputTokens: totalOutput },
|
|
2213
|
+
model: taskModel,
|
|
2214
|
+
provider: "anthropic"
|
|
2215
|
+
});
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2218
|
+
await runs.finishRun(runId, { exitCode: 1, output: `Execution error: ${msg}` });
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
hooks.register("task.created", async (ctx) => {
|
|
2222
|
+
const taskId = ctx.id ?? ctx.taskId;
|
|
2223
|
+
if (!taskId) return;
|
|
2224
|
+
await tryExecuteTask(taskId);
|
|
2225
|
+
});
|
|
2226
|
+
hooks.register("agent.wakeup", async (ctx) => {
|
|
2227
|
+
const taskId = ctx.taskId;
|
|
2228
|
+
const agentId = ctx.agentId;
|
|
2229
|
+
if (!taskId) return;
|
|
2230
|
+
await tryExecuteTask(taskId, agentId);
|
|
2231
|
+
});
|
|
2232
|
+
hooks.register("run.completed", async (ctx) => {
|
|
2233
|
+
const agentId = ctx.agentId;
|
|
2234
|
+
if (!agentId) return;
|
|
2235
|
+
const pendingTasks = (await db.query("tasks", { where: { status: "todo" } })).filter((t) => t["assignee_id"] === agentId).sort((a, b) => a["priority"] - b["priority"]);
|
|
2236
|
+
if (pendingTasks.length > 0) {
|
|
2237
|
+
await tryExecuteTask(pendingTasks[0]["id"], agentId);
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2104
2242
|
// src/core/chat/chat-pipeline-v2.ts
|
|
2105
2243
|
var DEFAULT_DEDUP_WINDOW_MS2 = 5 * 60 * 1e3;
|
|
2106
2244
|
var DEFAULT_MAX_ITERATIONS = 5;
|
|
@@ -2193,6 +2331,19 @@ var ChatPipelineV2 = class {
|
|
|
2193
2331
|
if (ctx2) systemPrompt += `
|
|
2194
2332
|
|
|
2195
2333
|
${ctx2}`;
|
|
2334
|
+
}
|
|
2335
|
+
if (this.config.resolveContextFiles) {
|
|
2336
|
+
const contextFiles = await this.config.resolveContextFiles({
|
|
2337
|
+
channelId,
|
|
2338
|
+
threadId: threadTs,
|
|
2339
|
+
userId: msg.from,
|
|
2340
|
+
messageText: msg.body,
|
|
2341
|
+
channel: this.channel
|
|
2342
|
+
});
|
|
2343
|
+
const contextFilesBlock = formatContextFilesBlock(contextFiles);
|
|
2344
|
+
if (contextFilesBlock) systemPrompt += `
|
|
2345
|
+
|
|
2346
|
+
${contextFilesBlock}`;
|
|
2196
2347
|
}
|
|
2197
2348
|
const { text, tasksDispatched } = await this.think(
|
|
2198
2349
|
systemPrompt,
|
|
@@ -6592,144 +6743,6 @@ var GateRunner = class {
|
|
|
6592
6743
|
}
|
|
6593
6744
|
};
|
|
6594
6745
|
|
|
6595
|
-
// src/core/orchestrator/execution-engine.ts
|
|
6596
|
-
function formatContextFilesBlock(files) {
|
|
6597
|
-
if (!files || files.length === 0) return "";
|
|
6598
|
-
return files.map((f) => `<file path="${f.path}">
|
|
6599
|
-
${f.content}
|
|
6600
|
-
</file>`).join("\n\n");
|
|
6601
|
-
}
|
|
6602
|
-
async function registerExecutionEngine(opts) {
|
|
6603
|
-
const { db, hooks, runs, config } = opts;
|
|
6604
|
-
const model = config.model ?? "claude-sonnet-4-20250514";
|
|
6605
|
-
const maxIterations = config.maxIterations ?? 5;
|
|
6606
|
-
const includeContext = config.includeSystemContext ?? true;
|
|
6607
|
-
const systemContext = includeContext ? await buildSystemContext(db) : "";
|
|
6608
|
-
const toolDefs = (config.tools ?? []).map((t) => t.definition);
|
|
6609
|
-
const toolHandlers = new Map(
|
|
6610
|
-
(config.tools ?? []).map((t) => [t.definition.name, t.handler])
|
|
6611
|
-
);
|
|
6612
|
-
async function tryExecuteTask(taskId, hintAgentId) {
|
|
6613
|
-
const task = await db.get("tasks", { id: taskId });
|
|
6614
|
-
if (!task || task.status !== "todo") return;
|
|
6615
|
-
const nextRetryAt = task["next_retry_at"];
|
|
6616
|
-
if (nextRetryAt && new Date(nextRetryAt) > /* @__PURE__ */ new Date()) return;
|
|
6617
|
-
const assigneeId = hintAgentId ?? task.assignee_id;
|
|
6618
|
-
if (!assigneeId) return;
|
|
6619
|
-
if (runs.isLocked(assigneeId)) return;
|
|
6620
|
-
const agent = await db.get("agents", { id: assigneeId });
|
|
6621
|
-
if (!agent) return;
|
|
6622
|
-
let runId;
|
|
6623
|
-
try {
|
|
6624
|
-
runId = await runs.startRun(assigneeId, taskId, "api");
|
|
6625
|
-
} catch {
|
|
6626
|
-
return;
|
|
6627
|
-
}
|
|
6628
|
-
const prompt = task.description ?? task.title ?? "";
|
|
6629
|
-
try {
|
|
6630
|
-
const taskModel = config.resolveModel ? config.resolveModel({ agent, task }) ?? model : model;
|
|
6631
|
-
const contextFiles = config.resolveContextFiles ? await config.resolveContextFiles({ agent, task }) : [];
|
|
6632
|
-
const contextFilesBlock = formatContextFilesBlock(contextFiles);
|
|
6633
|
-
const toolListing = toolDefs.length > 0 ? `
|
|
6634
|
-
## Available Tools
|
|
6635
|
-
${toolDefs.map((t) => `- **${t.name}**: ${t.description}`).join("\n")}
|
|
6636
|
-
|
|
6637
|
-
Use your tools to take action. Do NOT describe what you would do \u2014 call the tool.` : "";
|
|
6638
|
-
const systemPrompt = [
|
|
6639
|
-
`You are ${agent.name}, an AI agent with role: ${agent.role}.`,
|
|
6640
|
-
systemContext ? `
|
|
6641
|
-
${systemContext}` : "",
|
|
6642
|
-
contextFilesBlock ? `
|
|
6643
|
-
${contextFilesBlock}` : "",
|
|
6644
|
-
toolListing,
|
|
6645
|
-
config.systemPromptSuffix ?? ""
|
|
6646
|
-
].filter(Boolean).join("\n");
|
|
6647
|
-
const messages = [{ role: "user", content: prompt }];
|
|
6648
|
-
let finalOutput = "";
|
|
6649
|
-
let totalInput = 0;
|
|
6650
|
-
let totalOutput = 0;
|
|
6651
|
-
const systemField = config.cacheSystemPrompt ? [
|
|
6652
|
-
{
|
|
6653
|
-
type: "text",
|
|
6654
|
-
text: systemPrompt,
|
|
6655
|
-
cache_control: { type: "ephemeral" }
|
|
6656
|
-
}
|
|
6657
|
-
] : systemPrompt;
|
|
6658
|
-
for (let i = 0; i < maxIterations; i++) {
|
|
6659
|
-
const createParams = {
|
|
6660
|
-
model: taskModel,
|
|
6661
|
-
max_tokens: 4096,
|
|
6662
|
-
system: systemField,
|
|
6663
|
-
messages
|
|
6664
|
-
};
|
|
6665
|
-
if (toolDefs.length > 0) {
|
|
6666
|
-
createParams.tools = toolDefs;
|
|
6667
|
-
createParams.tool_choice = i === 0 ? { type: "any" } : { type: "auto" };
|
|
6668
|
-
}
|
|
6669
|
-
const response = await config.client.messages.create(createParams);
|
|
6670
|
-
totalInput += response.usage.input_tokens;
|
|
6671
|
-
totalOutput += response.usage.output_tokens;
|
|
6672
|
-
const textBlocks = response.content.filter((b) => b.type === "text").map((b) => b.text ?? "");
|
|
6673
|
-
if (textBlocks.length > 0) finalOutput += textBlocks.join("");
|
|
6674
|
-
if (response.stop_reason !== "tool_use") break;
|
|
6675
|
-
const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
|
|
6676
|
-
const toolResults = [];
|
|
6677
|
-
for (const toolUse of toolUseBlocks) {
|
|
6678
|
-
const handler = toolHandlers.get(toolUse.name);
|
|
6679
|
-
if (handler) {
|
|
6680
|
-
try {
|
|
6681
|
-
const result = await handler(
|
|
6682
|
-
toolUse.input,
|
|
6683
|
-
{ taskId, agentId: assigneeId, hooks, db, resolveFilePath: config.resolveFilePath }
|
|
6684
|
-
);
|
|
6685
|
-
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result });
|
|
6686
|
-
} catch (err) {
|
|
6687
|
-
toolResults.push({
|
|
6688
|
-
type: "tool_result",
|
|
6689
|
-
tool_use_id: toolUse.id,
|
|
6690
|
-
content: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
6691
|
-
});
|
|
6692
|
-
}
|
|
6693
|
-
}
|
|
6694
|
-
}
|
|
6695
|
-
messages.push({ role: "assistant", content: response.content });
|
|
6696
|
-
messages.push({ role: "user", content: toolResults });
|
|
6697
|
-
}
|
|
6698
|
-
const costCents = Math.round((totalInput * 0.3 + totalOutput * 1.5) / 100);
|
|
6699
|
-
await runs.finishRun(runId, {
|
|
6700
|
-
exitCode: 0,
|
|
6701
|
-
output: finalOutput,
|
|
6702
|
-
costCents,
|
|
6703
|
-
usage: { inputTokens: totalInput, outputTokens: totalOutput },
|
|
6704
|
-
model: taskModel,
|
|
6705
|
-
provider: "anthropic"
|
|
6706
|
-
});
|
|
6707
|
-
} catch (err) {
|
|
6708
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6709
|
-
await runs.finishRun(runId, { exitCode: 1, output: `Execution error: ${msg}` });
|
|
6710
|
-
}
|
|
6711
|
-
}
|
|
6712
|
-
hooks.register("task.created", async (ctx) => {
|
|
6713
|
-
const taskId = ctx.id ?? ctx.taskId;
|
|
6714
|
-
if (!taskId) return;
|
|
6715
|
-
await tryExecuteTask(taskId);
|
|
6716
|
-
});
|
|
6717
|
-
hooks.register("agent.wakeup", async (ctx) => {
|
|
6718
|
-
const taskId = ctx.taskId;
|
|
6719
|
-
const agentId = ctx.agentId;
|
|
6720
|
-
if (!taskId) return;
|
|
6721
|
-
await tryExecuteTask(taskId, agentId);
|
|
6722
|
-
});
|
|
6723
|
-
hooks.register("run.completed", async (ctx) => {
|
|
6724
|
-
const agentId = ctx.agentId;
|
|
6725
|
-
if (!agentId) return;
|
|
6726
|
-
const pendingTasks = (await db.query("tasks", { where: { status: "todo" } })).filter((t) => t["assignee_id"] === agentId).sort((a, b) => a["priority"] - b["priority"]);
|
|
6727
|
-
if (pendingTasks.length > 0) {
|
|
6728
|
-
await tryExecuteTask(pendingTasks[0]["id"], agentId);
|
|
6729
|
-
}
|
|
6730
|
-
});
|
|
6731
|
-
}
|
|
6732
|
-
|
|
6733
6746
|
// src/core/orchestrator/tools/send-file.ts
|
|
6734
6747
|
import { existsSync as existsSync3 } from "fs";
|
|
6735
6748
|
import { basename } from "path";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botinabox",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.7",
|
|
4
4
|
"description": "Bot in a Box — framework for building multi-agent bots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@types/uuid": "^10.0.0",
|
|
61
61
|
"ajv": "^8.17.1",
|
|
62
62
|
"cron-parser": "^4.9.0",
|
|
63
|
-
"latticesql": "^1.13.
|
|
63
|
+
"latticesql": "^1.13.10",
|
|
64
64
|
"uuid": "^13.0.0",
|
|
65
65
|
"yaml": "^2.7.0"
|
|
66
66
|
},
|