botholomew 0.8.8 → 0.8.10
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/package.json +1 -1
- package/src/chat/agent.ts +31 -30
- package/src/commands/capabilities.ts +3 -2
- package/src/context/capabilities.ts +5 -10
- package/src/worker/prompt.ts +40 -45
package/package.json
CHANGED
package/src/chat/agent.ts
CHANGED
|
@@ -65,18 +65,16 @@ export async function buildChatSystemPrompt(
|
|
|
65
65
|
keywordSource?: string;
|
|
66
66
|
dbPath?: string;
|
|
67
67
|
config?: Required<BotholomewConfig>;
|
|
68
|
+
hasMcpTools?: boolean;
|
|
68
69
|
},
|
|
69
70
|
): Promise<string> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
parts.push(...buildMetaHeader(projectDir));
|
|
71
|
+
let prompt = buildMetaHeader(projectDir);
|
|
73
72
|
|
|
74
73
|
const keywordSource = options?.keywordSource?.trim();
|
|
75
74
|
const taskKeywords = keywordSource ? extractKeywords(keywordSource) : null;
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
prompt += await loadPersistentContext(projectDir, taskKeywords);
|
|
78
77
|
|
|
79
|
-
// Relevant context from embeddings search
|
|
80
78
|
const dbPath = options?.dbPath;
|
|
81
79
|
const config = options?.config;
|
|
82
80
|
if (dbPath && config?.openai_api_key && keywordSource) {
|
|
@@ -87,14 +85,14 @@ export async function buildChatSystemPrompt(
|
|
|
87
85
|
);
|
|
88
86
|
|
|
89
87
|
if (results.length > 0) {
|
|
90
|
-
|
|
88
|
+
prompt += "## Relevant Context\n";
|
|
91
89
|
for (const r of results) {
|
|
92
90
|
const path = r.source_path || r.context_item_id;
|
|
93
|
-
|
|
91
|
+
prompt += `### ${r.title} (${path})\n`;
|
|
94
92
|
if (r.chunk_content) {
|
|
95
|
-
|
|
93
|
+
prompt += `${r.chunk_content.slice(0, 1000)}\n`;
|
|
96
94
|
}
|
|
97
|
-
|
|
95
|
+
prompt += "\n";
|
|
98
96
|
}
|
|
99
97
|
}
|
|
100
98
|
} catch (err) {
|
|
@@ -102,28 +100,30 @@ export async function buildChatSystemPrompt(
|
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
103
|
+
prompt += `## Instructions
|
|
104
|
+
You are Botholomew, an AI agent personified by a wise owl. This is your interactive chat interface. Help the user manage tasks, review results from background worker activity, search context, and answer questions.
|
|
105
|
+
You do NOT execute long-running work directly — enqueue tasks for a background worker instead using create_task, and spawn a worker via spawn_worker when the user wants the task run now.
|
|
106
|
+
Use the available tools to look up tasks, threads, schedules, and context when the user asks about them. Context items can be looked up by virtual path or by UUID via \`context_info\` and refreshed via \`context_refresh\`.
|
|
107
|
+
When multiple tool calls are independent of each other (i.e., one does not depend on the result of another), call them all in a single response. They will be executed in parallel, which is faster than calling them one at a time.
|
|
108
|
+
You can update the agent's beliefs and goals files when the user asks you to.
|
|
109
|
+
Format your responses using Markdown. Use headings, bold, italic, lists, and code blocks to make your responses clear and well-structured.
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
if (options?.hasMcpTools) {
|
|
113
|
+
prompt += `
|
|
114
|
+
## External Tools (MCP)
|
|
115
|
+
|
|
116
|
+
You have access to external tools via MCP servers. Before calling any MCP tool you haven't used yet this session, you MUST fetch its schema first:
|
|
117
|
+
|
|
118
|
+
1. Discover tools with \`mcp_search\` (preferred — semantic) or \`mcp_list_tools\`.
|
|
119
|
+
2. Call \`mcp_info\` with the exact \`server\` and \`tool\` to read the tool's input schema, required fields, and types.
|
|
120
|
+
3. Only then call \`mcp_exec\` with arguments that conform to that schema.
|
|
121
|
+
|
|
122
|
+
Skip step 2 only if you already called \`mcp_info\` for that exact server+tool earlier in this conversation. Do not guess arguments from the tool's description alone — descriptions omit types and required/optional markers.
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
125
|
|
|
126
|
-
return
|
|
126
|
+
return prompt;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export interface ToolEndMeta {
|
|
@@ -202,6 +202,7 @@ export async function runChatTurn(input: {
|
|
|
202
202
|
keywordSource,
|
|
203
203
|
dbPath,
|
|
204
204
|
config,
|
|
205
|
+
hasMcpTools: mcpxClient != null,
|
|
205
206
|
});
|
|
206
207
|
|
|
207
208
|
fitToContextWindow(messages, systemPrompt, maxInputTokens);
|
|
@@ -36,10 +36,11 @@ export function registerCapabilitiesCommand(program: Command) {
|
|
|
36
36
|
});
|
|
37
37
|
} catch (err) {
|
|
38
38
|
spinner.error({ text: `Failed: ${(err as Error).message}` });
|
|
39
|
-
process.exit(1);
|
|
40
|
-
} finally {
|
|
41
39
|
await mcpxClient?.close();
|
|
40
|
+
process.exit(1);
|
|
42
41
|
}
|
|
42
|
+
await mcpxClient?.close();
|
|
43
|
+
process.exit(0);
|
|
43
44
|
}),
|
|
44
45
|
);
|
|
45
46
|
}
|
|
@@ -268,22 +268,17 @@ async function summarizeViaLLM(
|
|
|
268
268
|
const userPrompt = `Summarize this tool inventory. Return via the \`${SUMMARIZE_TOOL_NAME}\` tool.\n\n${renderInventoryForPrompt(inv)}`;
|
|
269
269
|
|
|
270
270
|
try {
|
|
271
|
-
const response = await
|
|
272
|
-
|
|
271
|
+
const response = await client.messages.create(
|
|
272
|
+
{
|
|
273
273
|
model: config.chunker_model,
|
|
274
274
|
max_tokens: SUMMARIZE_MAX_TOKENS,
|
|
275
275
|
system: SUMMARIZE_SYSTEM,
|
|
276
276
|
tools: [SUMMARIZE_TOOL],
|
|
277
277
|
tool_choice: { type: "tool", name: SUMMARIZE_TOOL_NAME },
|
|
278
278
|
messages: [{ role: "user", content: userPrompt }],
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
() => reject(new Error("Capability summarization timeout")),
|
|
283
|
-
SUMMARIZE_TIMEOUT_MS,
|
|
284
|
-
),
|
|
285
|
-
),
|
|
286
|
-
]);
|
|
279
|
+
},
|
|
280
|
+
{ timeout: SUMMARIZE_TIMEOUT_MS },
|
|
281
|
+
);
|
|
287
282
|
|
|
288
283
|
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
289
284
|
if (!toolBlock || toolBlock.type !== "tool_use") return null;
|
package/src/worker/prompt.ts
CHANGED
|
@@ -29,16 +29,16 @@ export function extractKeywords(text: string): Set<string> {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Load persistent context files from .botholomew/ directory
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Load persistent context files from .botholomew/ directory as a single
|
|
33
|
+
* formatted string. Includes "always" files unconditionally and "contextual"
|
|
34
|
+
* files whose content overlaps the provided taskKeywords.
|
|
35
35
|
*/
|
|
36
36
|
export async function loadPersistentContext(
|
|
37
37
|
projectDir: string,
|
|
38
38
|
taskKeywords?: Set<string> | null,
|
|
39
|
-
): Promise<string
|
|
39
|
+
): Promise<string> {
|
|
40
40
|
const dotDir = getBotholomewDir(projectDir);
|
|
41
|
-
|
|
41
|
+
let out = "";
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
44
|
const files = await readdir(dotDir);
|
|
@@ -50,18 +50,14 @@ export async function loadPersistentContext(
|
|
|
50
50
|
const { meta, content } = parseContextFile(raw);
|
|
51
51
|
|
|
52
52
|
if (meta.loading === "always") {
|
|
53
|
-
|
|
54
|
-
parts.push(content);
|
|
55
|
-
parts.push("");
|
|
53
|
+
out += `## ${filename}\n${content}\n\n`;
|
|
56
54
|
} else if (meta.loading === "contextual" && taskKeywords) {
|
|
57
55
|
const contentLower = content.toLowerCase();
|
|
58
56
|
const hasOverlap = [...taskKeywords].some((kw) =>
|
|
59
57
|
contentLower.includes(kw),
|
|
60
58
|
);
|
|
61
59
|
if (hasOverlap) {
|
|
62
|
-
|
|
63
|
-
parts.push(content);
|
|
64
|
-
parts.push("");
|
|
60
|
+
out += `## ${filename} (contextual)\n${content}\n\n`;
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
}
|
|
@@ -69,21 +65,20 @@ export async function loadPersistentContext(
|
|
|
69
65
|
// .botholomew dir might not have md files yet
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
return
|
|
68
|
+
return out;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
/**
|
|
76
72
|
* Build common meta header (version, time, OS, user).
|
|
77
73
|
*/
|
|
78
|
-
export function buildMetaHeader(projectDir: string): string
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
];
|
|
74
|
+
export function buildMetaHeader(projectDir: string): string {
|
|
75
|
+
return `# Botholomew v${pkg.version}
|
|
76
|
+
Current time: ${new Date().toISOString()}
|
|
77
|
+
Project directory: ${projectDir}
|
|
78
|
+
OS: ${process.platform} ${process.arch}
|
|
79
|
+
User: ${process.env.USER || process.env.USERNAME || "unknown"}
|
|
80
|
+
|
|
81
|
+
`;
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
export async function buildSystemPrompt(
|
|
@@ -93,20 +88,14 @@ export async function buildSystemPrompt(
|
|
|
93
88
|
_config?: Required<BotholomewConfig>,
|
|
94
89
|
options?: { hasMcpTools?: boolean },
|
|
95
90
|
): Promise<string> {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// Meta information
|
|
99
|
-
parts.push(...buildMetaHeader(projectDir));
|
|
91
|
+
let prompt = buildMetaHeader(projectDir);
|
|
100
92
|
|
|
101
|
-
// Build keyword set from task for contextual loading
|
|
102
93
|
const taskKeywords = task
|
|
103
94
|
? extractKeywords(`${task.name} ${task.description}`)
|
|
104
95
|
: null;
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
parts.push(...(await loadPersistentContext(projectDir, taskKeywords)));
|
|
97
|
+
prompt += await loadPersistentContext(projectDir, taskKeywords);
|
|
108
98
|
|
|
109
|
-
// Relevant context from embeddings search
|
|
110
99
|
if (task && dbPath && _config?.openai_api_key) {
|
|
111
100
|
try {
|
|
112
101
|
const query = `${task.name} ${task.description}`;
|
|
@@ -116,14 +105,14 @@ export async function buildSystemPrompt(
|
|
|
116
105
|
);
|
|
117
106
|
|
|
118
107
|
if (results.length > 0) {
|
|
119
|
-
|
|
108
|
+
prompt += "## Relevant Context\n";
|
|
120
109
|
for (const r of results) {
|
|
121
110
|
const path = r.source_path || r.context_item_id;
|
|
122
|
-
|
|
111
|
+
prompt += `### ${r.title} (${path})\n`;
|
|
123
112
|
if (r.chunk_content) {
|
|
124
|
-
|
|
113
|
+
prompt += `${r.chunk_content.slice(0, 1000)}\n`;
|
|
125
114
|
}
|
|
126
|
-
|
|
115
|
+
prompt += "\n";
|
|
127
116
|
}
|
|
128
117
|
}
|
|
129
118
|
} catch (err) {
|
|
@@ -131,19 +120,25 @@ export async function buildSystemPrompt(
|
|
|
131
120
|
}
|
|
132
121
|
}
|
|
133
122
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
123
|
+
prompt += `## Instructions
|
|
124
|
+
You are Botholomew, a wise-owl worker that works through tasks. Use available tools to complete your assigned task, then call complete_task, fail_task, or wait_task. Use create_task for subtasks and update_task to refine pending tasks. Batch independent tool calls in a single response for parallel execution.
|
|
125
|
+
|
|
126
|
+
When calling complete_task, write a summary that captures your key findings, decisions, and outputs. This summary becomes the task's output and is provided to any downstream tasks that depend on this one. Include specific results (data, names, paths, conclusions) rather than vague descriptions of what you did — downstream tasks will rely on this information to do their work.
|
|
127
|
+
`;
|
|
128
|
+
|
|
139
129
|
if (options?.hasMcpTools) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
130
|
+
prompt += `
|
|
131
|
+
## External Tools (MCP)
|
|
132
|
+
|
|
133
|
+
You have access to external tools via MCP servers. Before calling any MCP tool you haven't used yet this session, you MUST fetch its schema first:
|
|
134
|
+
|
|
135
|
+
1. Discover tools with \`mcp_search\` (preferred — semantic) or \`mcp_list_tools\`.
|
|
136
|
+
2. Call \`mcp_info\` with the exact \`server\` and \`tool\` to read the tool's input schema, required fields, and types.
|
|
137
|
+
3. Only then call \`mcp_exec\` with arguments that conform to that schema.
|
|
138
|
+
|
|
139
|
+
Skip step 2 only if you already called \`mcp_info\` for that exact server+tool earlier in this conversation. Do not guess arguments from the tool's description alone — descriptions omit types and required/optional markers.
|
|
140
|
+
`;
|
|
145
141
|
}
|
|
146
|
-
parts.push("");
|
|
147
142
|
|
|
148
|
-
return
|
|
143
|
+
return prompt;
|
|
149
144
|
}
|