botholomew 0.3.1 → 0.3.2
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 +2 -2
- package/src/chat/agent.ts +62 -16
- package/src/chat/session.ts +19 -6
- package/src/cli.ts +2 -0
- package/src/commands/thread.ts +180 -0
- package/src/config/schemas.ts +3 -1
- package/src/daemon/large-results.ts +15 -3
- package/src/daemon/llm.ts +22 -7
- package/src/daemon/prompt.ts +1 -9
- package/src/daemon/tick.ts +9 -0
- package/src/db/threads.ts +17 -0
- package/src/init/templates.ts +1 -0
- package/src/tools/context/read-large-result.ts +2 -1
- package/src/tools/context/search.ts +2 -0
- package/src/tools/context/update-beliefs.ts +2 -0
- package/src/tools/context/update-goals.ts +2 -0
- package/src/tools/dir/create.ts +3 -2
- package/src/tools/dir/list.ts +2 -1
- package/src/tools/dir/size.ts +2 -1
- package/src/tools/dir/tree.ts +3 -2
- package/src/tools/file/copy.ts +2 -1
- package/src/tools/file/count-lines.ts +2 -1
- package/src/tools/file/delete.ts +3 -2
- package/src/tools/file/edit.ts +2 -1
- package/src/tools/file/exists.ts +2 -1
- package/src/tools/file/info.ts +2 -0
- package/src/tools/file/move.ts +2 -1
- package/src/tools/file/read.ts +2 -1
- package/src/tools/file/write.ts +3 -2
- package/src/tools/mcp/exec.ts +70 -3
- package/src/tools/mcp/info.ts +8 -0
- package/src/tools/mcp/list-tools.ts +18 -6
- package/src/tools/mcp/search.ts +38 -10
- package/src/tools/registry.ts +2 -0
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/search/grep.ts +3 -2
- package/src/tools/search/semantic.ts +2 -0
- package/src/tools/task/complete.ts +2 -0
- package/src/tools/task/create.ts +17 -4
- package/src/tools/task/fail.ts +2 -0
- package/src/tools/task/list.ts +2 -0
- package/src/tools/task/update.ts +87 -0
- package/src/tools/task/view.ts +3 -1
- package/src/tools/task/wait.ts +2 -0
- package/src/tools/thread/list.ts +2 -0
- package/src/tools/thread/view.ts +3 -1
- package/src/tools/tool.ts +5 -3
- package/src/tui/App.tsx +209 -82
- package/src/tui/components/ContextPanel.tsx +6 -3
- package/src/tui/components/HelpPanel.tsx +52 -3
- package/src/tui/components/InputBar.tsx +125 -59
- package/src/tui/components/MessageList.tsx +40 -75
- package/src/tui/components/StatusBar.tsx +9 -8
- package/src/tui/components/TabBar.tsx +4 -2
- package/src/tui/components/TaskPanel.tsx +409 -0
- package/src/tui/components/ThreadPanel.tsx +541 -0
- package/src/tui/components/ToolCall.tsx +36 -3
- package/src/tui/components/ToolPanel.tsx +40 -31
- package/src/tui/theme.ts +20 -3
- package/src/utils/title.ts +47 -0
|
@@ -19,6 +19,7 @@ const inputSchema = z.object({
|
|
|
19
19
|
const outputSchema = z.object({
|
|
20
20
|
message: z.string(),
|
|
21
21
|
path: z.string(),
|
|
22
|
+
is_error: z.boolean(),
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const updateGoalsTool = {
|
|
@@ -49,6 +50,7 @@ export const updateGoalsTool = {
|
|
|
49
50
|
return {
|
|
50
51
|
message: "Updated goals.md",
|
|
51
52
|
path: filePath,
|
|
53
|
+
is_error: false,
|
|
52
54
|
};
|
|
53
55
|
},
|
|
54
56
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/dir/create.ts
CHANGED
|
@@ -13,6 +13,7 @@ const inputSchema = z.object({
|
|
|
13
13
|
const outputSchema = z.object({
|
|
14
14
|
created: z.boolean(),
|
|
15
15
|
path: z.string(),
|
|
16
|
+
is_error: z.boolean(),
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
export const dirCreateTool = {
|
|
@@ -24,7 +25,7 @@ export const dirCreateTool = {
|
|
|
24
25
|
execute: async (input, ctx) => {
|
|
25
26
|
const exists = await contextPathExists(ctx.conn, input.path);
|
|
26
27
|
if (exists) {
|
|
27
|
-
return { created: false, path: input.path };
|
|
28
|
+
return { created: false, path: input.path, is_error: false };
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
await createContextItem(ctx.conn, {
|
|
@@ -34,6 +35,6 @@ export const dirCreateTool = {
|
|
|
34
35
|
isTextual: false,
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
return { created: true, path: input.path };
|
|
38
|
+
return { created: true, path: input.path, is_error: false };
|
|
38
39
|
},
|
|
39
40
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/dir/list.ts
CHANGED
|
@@ -33,6 +33,7 @@ const inputSchema = z.object({
|
|
|
33
33
|
const outputSchema = z.object({
|
|
34
34
|
entries: z.array(DirEntrySchema),
|
|
35
35
|
total: z.number(),
|
|
36
|
+
is_error: z.boolean(),
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
export const dirListTool = {
|
|
@@ -82,6 +83,6 @@ export const dirListTool = {
|
|
|
82
83
|
const total = entries.length;
|
|
83
84
|
const paginated = entries.slice(offset, offset + limit);
|
|
84
85
|
|
|
85
|
-
return { entries: paginated, total };
|
|
86
|
+
return { entries: paginated, total, is_error: false };
|
|
86
87
|
},
|
|
87
88
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/dir/size.ts
CHANGED
|
@@ -21,6 +21,7 @@ const inputSchema = z.object({
|
|
|
21
21
|
const outputSchema = z.object({
|
|
22
22
|
bytes: z.number(),
|
|
23
23
|
formatted: z.string(),
|
|
24
|
+
is_error: z.boolean(),
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
export const dirSizeTool = {
|
|
@@ -40,6 +41,6 @@ export const dirSizeTool = {
|
|
|
40
41
|
if (item.content != null) bytes += item.content.length;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return { bytes, formatted: formatBytes(bytes) };
|
|
44
|
+
return { bytes, formatted: formatBytes(bytes), is_error: false };
|
|
44
45
|
},
|
|
45
46
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/dir/tree.ts
CHANGED
|
@@ -20,6 +20,7 @@ const inputSchema = z.object({
|
|
|
20
20
|
|
|
21
21
|
const outputSchema = z.object({
|
|
22
22
|
tree: z.string(),
|
|
23
|
+
is_error: z.boolean(),
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
export const dirTreeTool = {
|
|
@@ -38,7 +39,7 @@ export const dirTreeTool = {
|
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
if (items.length === 0) {
|
|
41
|
-
return { tree: `${path}\n (empty)
|
|
42
|
+
return { tree: `${path}\n (empty)`, is_error: false };
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const normalizedPath = path.endsWith("/") ? path : `${path}/`;
|
|
@@ -86,6 +87,6 @@ export const dirTreeTool = {
|
|
|
86
87
|
lines.push(`... (truncated at ${maxItems} items)`);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
return { tree: lines.join("\n") };
|
|
90
|
+
return { tree: lines.join("\n"), is_error: false };
|
|
90
91
|
},
|
|
91
92
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/copy.ts
CHANGED
|
@@ -15,6 +15,7 @@ const inputSchema = z.object({
|
|
|
15
15
|
const outputSchema = z.object({
|
|
16
16
|
id: z.string(),
|
|
17
17
|
path: z.string(),
|
|
18
|
+
is_error: z.boolean(),
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
export const fileCopyTool = {
|
|
@@ -33,6 +34,6 @@ export const fileCopyTool = {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const item = await copyContextItem(ctx.conn, input.src, input.dst);
|
|
36
|
-
return { id: item.id, path: item.context_path };
|
|
37
|
+
return { id: item.id, path: item.context_path, is_error: false };
|
|
37
38
|
},
|
|
38
39
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -8,6 +8,7 @@ const inputSchema = z.object({
|
|
|
8
8
|
|
|
9
9
|
const outputSchema = z.object({
|
|
10
10
|
lines: z.number(),
|
|
11
|
+
is_error: z.boolean(),
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
export const fileCountLinesTool = {
|
|
@@ -21,6 +22,6 @@ export const fileCountLinesTool = {
|
|
|
21
22
|
if (!item) throw new Error(`Not found: ${input.path}`);
|
|
22
23
|
if (item.content == null) throw new Error(`No text content: ${input.path}`);
|
|
23
24
|
|
|
24
|
-
return { lines: item.content.split("\n").length };
|
|
25
|
+
return { lines: item.content.split("\n").length, is_error: false };
|
|
25
26
|
},
|
|
26
27
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/delete.ts
CHANGED
|
@@ -19,6 +19,7 @@ const inputSchema = z.object({
|
|
|
19
19
|
|
|
20
20
|
const outputSchema = z.object({
|
|
21
21
|
deleted: z.number(),
|
|
22
|
+
is_error: z.boolean(),
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const fileDeleteTool = {
|
|
@@ -31,13 +32,13 @@ export const fileDeleteTool = {
|
|
|
31
32
|
if (input.recursive) {
|
|
32
33
|
const count = await deleteContextItemsByPrefix(ctx.conn, input.path);
|
|
33
34
|
const exact = await deleteContextItemByPath(ctx.conn, input.path);
|
|
34
|
-
return { deleted: count + (exact ? 1 : 0) };
|
|
35
|
+
return { deleted: count + (exact ? 1 : 0), is_error: false };
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const deleted = await deleteContextItemByPath(ctx.conn, input.path);
|
|
38
39
|
if (!deleted && !input.force) {
|
|
39
40
|
throw new Error(`Not found: ${input.path}`);
|
|
40
41
|
}
|
|
41
|
-
return { deleted: deleted ? 1 : 0 };
|
|
42
|
+
return { deleted: deleted ? 1 : 0, is_error: false };
|
|
42
43
|
},
|
|
43
44
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/edit.ts
CHANGED
|
@@ -21,6 +21,7 @@ const inputSchema = z.object({
|
|
|
21
21
|
const outputSchema = z.object({
|
|
22
22
|
applied: z.number(),
|
|
23
23
|
content: z.string(),
|
|
24
|
+
is_error: z.boolean(),
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
export const fileEditTool = {
|
|
@@ -38,6 +39,6 @@ export const fileEditTool = {
|
|
|
38
39
|
);
|
|
39
40
|
|
|
40
41
|
await ingestByPath(ctx.conn, input.path, ctx.config, ctx.embedFn);
|
|
41
|
-
return { applied, content: item.content ?? "" };
|
|
42
|
+
return { applied, content: item.content ?? "", is_error: false };
|
|
42
43
|
},
|
|
43
44
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/exists.ts
CHANGED
|
@@ -8,6 +8,7 @@ const inputSchema = z.object({
|
|
|
8
8
|
|
|
9
9
|
const outputSchema = z.object({
|
|
10
10
|
exists: z.boolean(),
|
|
11
|
+
is_error: z.boolean(),
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
export const fileExistsTool = {
|
|
@@ -18,6 +19,6 @@ export const fileExistsTool = {
|
|
|
18
19
|
outputSchema,
|
|
19
20
|
execute: async (input, ctx) => {
|
|
20
21
|
const exists = await contextPathExists(ctx.conn, input.path);
|
|
21
|
-
return { exists };
|
|
22
|
+
return { exists, is_error: false };
|
|
22
23
|
},
|
|
23
24
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/info.ts
CHANGED
|
@@ -19,6 +19,7 @@ const outputSchema = z.object({
|
|
|
19
19
|
indexed_at: z.string().nullable(),
|
|
20
20
|
created_at: z.string(),
|
|
21
21
|
updated_at: z.string(),
|
|
22
|
+
is_error: z.boolean(),
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const fileInfoTool = {
|
|
@@ -45,6 +46,7 @@ export const fileInfoTool = {
|
|
|
45
46
|
indexed_at: item.indexed_at?.toISOString() ?? null,
|
|
46
47
|
created_at: item.created_at.toISOString(),
|
|
47
48
|
updated_at: item.updated_at.toISOString(),
|
|
49
|
+
is_error: false,
|
|
48
50
|
};
|
|
49
51
|
},
|
|
50
52
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/move.ts
CHANGED
|
@@ -14,6 +14,7 @@ const inputSchema = z.object({
|
|
|
14
14
|
|
|
15
15
|
const outputSchema = z.object({
|
|
16
16
|
path: z.string(),
|
|
17
|
+
is_error: z.boolean(),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
export const fileMoveTool = {
|
|
@@ -38,6 +39,6 @@ export const fileMoveTool = {
|
|
|
38
39
|
.query("UPDATE embeddings SET source_path = ?1 WHERE source_path = ?2")
|
|
39
40
|
.run(input.dst, input.src);
|
|
40
41
|
|
|
41
|
-
return { path: input.dst };
|
|
42
|
+
return { path: input.dst, is_error: false };
|
|
42
43
|
},
|
|
43
44
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/read.ts
CHANGED
|
@@ -13,6 +13,7 @@ const inputSchema = z.object({
|
|
|
13
13
|
|
|
14
14
|
const outputSchema = z.object({
|
|
15
15
|
content: z.string(),
|
|
16
|
+
is_error: z.boolean(),
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
export const fileReadTool = {
|
|
@@ -35,6 +36,6 @@ export const fileReadTool = {
|
|
|
35
36
|
content = lines.slice(start, end).join("\n");
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
return { content };
|
|
39
|
+
return { content, is_error: false };
|
|
39
40
|
},
|
|
40
41
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/file/write.ts
CHANGED
|
@@ -40,6 +40,7 @@ const inputSchema = z.object({
|
|
|
40
40
|
const outputSchema = z.object({
|
|
41
41
|
id: z.string(),
|
|
42
42
|
path: z.string(),
|
|
43
|
+
is_error: z.boolean(),
|
|
43
44
|
});
|
|
44
45
|
|
|
45
46
|
export const fileWriteTool = {
|
|
@@ -72,7 +73,7 @@ export const fileWriteTool = {
|
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
75
|
await ingestByPath(ctx.conn, input.path, ctx.config, ctx.embedFn);
|
|
75
|
-
return { id: existing.id, path: input.path };
|
|
76
|
+
return { id: existing.id, path: input.path, is_error: false };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const title =
|
|
@@ -88,6 +89,6 @@ export const fileWriteTool = {
|
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
await ingestByPath(ctx.conn, input.path, ctx.config, ctx.embedFn);
|
|
91
|
-
return { id: item.id, path: item.context_path };
|
|
92
|
+
return { id: item.id, path: item.context_path, is_error: false };
|
|
92
93
|
},
|
|
93
94
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/mcp/exec.ts
CHANGED
|
@@ -11,15 +11,72 @@ const inputSchema = z.object({
|
|
|
11
11
|
.describe("Tool arguments as a JSON object"),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
const errorKindSchema = z
|
|
15
|
+
.enum(["retryable", "permanent", "input_error", "auth_error"])
|
|
16
|
+
.optional();
|
|
17
|
+
|
|
14
18
|
const outputSchema = z.object({
|
|
15
19
|
result: z.string(),
|
|
16
20
|
is_error: z.boolean(),
|
|
21
|
+
error_kind: errorKindSchema,
|
|
22
|
+
hint: z.string().optional(),
|
|
17
23
|
});
|
|
18
24
|
|
|
25
|
+
type ErrorKind = z.infer<typeof errorKindSchema>;
|
|
26
|
+
|
|
27
|
+
function classifyError(err: unknown): { error_kind: ErrorKind; hint: string } {
|
|
28
|
+
const msg = String(err).toLowerCase();
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
msg.includes("econnrefused") ||
|
|
32
|
+
msg.includes("etimedout") ||
|
|
33
|
+
msg.includes("enotfound") ||
|
|
34
|
+
msg.includes("rate limit") ||
|
|
35
|
+
msg.includes("429") ||
|
|
36
|
+
msg.includes("503")
|
|
37
|
+
) {
|
|
38
|
+
return {
|
|
39
|
+
error_kind: "retryable",
|
|
40
|
+
hint: "Transient network error. Retry after a pause.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
msg.includes("401") ||
|
|
46
|
+
msg.includes("403") ||
|
|
47
|
+
msg.includes("unauthorized") ||
|
|
48
|
+
msg.includes("forbidden") ||
|
|
49
|
+
msg.includes("authentication") ||
|
|
50
|
+
msg.includes("auth")
|
|
51
|
+
) {
|
|
52
|
+
return {
|
|
53
|
+
error_kind: "auth_error",
|
|
54
|
+
hint: "Authentication failed. Check MCP server credentials. Not retryable.",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
msg.includes("invalid") ||
|
|
60
|
+
msg.includes("validation") ||
|
|
61
|
+
msg.includes("required") ||
|
|
62
|
+
msg.includes("schema")
|
|
63
|
+
) {
|
|
64
|
+
return {
|
|
65
|
+
error_kind: "input_error",
|
|
66
|
+
hint: `Tool rejected input. Use mcp_info to check the expected schema for ${msg}, then retry with corrected arguments.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
error_kind: "permanent",
|
|
72
|
+
hint: "Unexpected error. Use mcp_search to find an alternative tool.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
19
76
|
export const mcpExecTool = {
|
|
20
77
|
name: "mcp_exec",
|
|
21
78
|
description:
|
|
22
|
-
"Execute a tool on an MCP server. Use mcp_list_tools or mcp_search to discover available tools first.",
|
|
79
|
+
"Execute a tool on an MCP server. Use mcp_list_tools or mcp_search to discover available tools first, and mcp_info to check the expected input schema.",
|
|
23
80
|
group: "mcp",
|
|
24
81
|
inputSchema,
|
|
25
82
|
outputSchema,
|
|
@@ -27,8 +84,10 @@ export const mcpExecTool = {
|
|
|
27
84
|
if (!ctx.mcpxClient) {
|
|
28
85
|
return {
|
|
29
86
|
result:
|
|
30
|
-
"No MCP servers configured. Add servers with `botholomew mcpx add`.",
|
|
87
|
+
"No MCP servers configured. This task requires external tool access. Add servers with `botholomew mcpx add`.",
|
|
31
88
|
is_error: true,
|
|
89
|
+
error_kind: "permanent" as const,
|
|
90
|
+
hint: "Consider calling fail_task noting that MCP servers need to be configured.",
|
|
32
91
|
};
|
|
33
92
|
}
|
|
34
93
|
|
|
@@ -38,14 +97,22 @@ export const mcpExecTool = {
|
|
|
38
97
|
input.tool,
|
|
39
98
|
input.args,
|
|
40
99
|
);
|
|
100
|
+
const isError = callResult.isError ?? false;
|
|
41
101
|
return {
|
|
42
102
|
result: formatCallToolResult(callResult),
|
|
43
|
-
is_error:
|
|
103
|
+
is_error: isError,
|
|
104
|
+
error_kind: undefined,
|
|
105
|
+
hint: isError
|
|
106
|
+
? "The tool returned an error. Check the error message and use mcp_info to verify you're passing the correct arguments."
|
|
107
|
+
: undefined,
|
|
44
108
|
};
|
|
45
109
|
} catch (err) {
|
|
110
|
+
const { error_kind, hint } = classifyError(err);
|
|
46
111
|
return {
|
|
47
112
|
result: `MCP tool error: ${err}`,
|
|
48
113
|
is_error: true,
|
|
114
|
+
error_kind,
|
|
115
|
+
hint,
|
|
49
116
|
};
|
|
50
117
|
}
|
|
51
118
|
},
|
package/src/tools/mcp/info.ts
CHANGED
|
@@ -11,6 +11,8 @@ const outputSchema = z.object({
|
|
|
11
11
|
name: z.string(),
|
|
12
12
|
description: z.string(),
|
|
13
13
|
input_schema: z.string(),
|
|
14
|
+
is_error: z.boolean(),
|
|
15
|
+
hint: z.string().optional(),
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
export const mcpInfoTool = {
|
|
@@ -27,6 +29,8 @@ export const mcpInfoTool = {
|
|
|
27
29
|
name: input.tool,
|
|
28
30
|
description: "No MCP servers configured.",
|
|
29
31
|
input_schema: "{}",
|
|
32
|
+
is_error: true,
|
|
33
|
+
hint: "Add MCP servers with `botholomew mcpx add` before using external tools.",
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -37,6 +41,8 @@ export const mcpInfoTool = {
|
|
|
37
41
|
name: input.tool,
|
|
38
42
|
description: `Tool "${input.tool}" not found on server "${input.server}".`,
|
|
39
43
|
input_schema: "{}",
|
|
44
|
+
is_error: true,
|
|
45
|
+
hint: "Tool not found. Use mcp_search or mcp_list_tools to find the correct server and tool name.",
|
|
40
46
|
};
|
|
41
47
|
}
|
|
42
48
|
|
|
@@ -45,6 +51,8 @@ export const mcpInfoTool = {
|
|
|
45
51
|
name: tool.name,
|
|
46
52
|
description: tool.description ?? "",
|
|
47
53
|
input_schema: JSON.stringify(tool.inputSchema ?? {}, null, 2),
|
|
54
|
+
is_error: false,
|
|
55
|
+
hint: `Call mcp_exec with server='${input.server}', tool='${tool.name}', and the required args.`,
|
|
48
56
|
};
|
|
49
57
|
},
|
|
50
58
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -16,6 +16,8 @@ const ToolEntrySchema = z.object({
|
|
|
16
16
|
|
|
17
17
|
const outputSchema = z.object({
|
|
18
18
|
tools: z.array(ToolEntrySchema),
|
|
19
|
+
is_error: z.boolean(),
|
|
20
|
+
hint: z.string().optional(),
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
export const mcpListToolsTool = {
|
|
@@ -27,16 +29,26 @@ export const mcpListToolsTool = {
|
|
|
27
29
|
outputSchema,
|
|
28
30
|
execute: async (input, ctx) => {
|
|
29
31
|
if (!ctx.mcpxClient) {
|
|
30
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
tools: [],
|
|
34
|
+
is_error: false,
|
|
35
|
+
hint: "No MCP servers configured. Add servers with `botholomew mcpx add`.",
|
|
36
|
+
};
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
const toolsWithServer = await ctx.mcpxClient.listTools(input.server);
|
|
40
|
+
const mapped = toolsWithServer.map((t) => ({
|
|
41
|
+
server: t.server,
|
|
42
|
+
name: t.tool.name,
|
|
43
|
+
description: t.tool.description ?? "",
|
|
44
|
+
}));
|
|
34
45
|
return {
|
|
35
|
-
tools:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
tools: mapped,
|
|
47
|
+
is_error: false,
|
|
48
|
+
hint:
|
|
49
|
+
mapped.length > 0
|
|
50
|
+
? "Use mcp_search to find tools by capability, or mcp_info to get the full schema for a specific tool."
|
|
51
|
+
: "No tools available. MCP servers may not be configured or may be offline.",
|
|
40
52
|
};
|
|
41
53
|
},
|
|
42
54
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/mcp/search.ts
CHANGED
|
@@ -23,6 +23,9 @@ const SearchResultSchema = z.object({
|
|
|
23
23
|
|
|
24
24
|
const outputSchema = z.object({
|
|
25
25
|
results: z.array(SearchResultSchema),
|
|
26
|
+
is_error: z.boolean(),
|
|
27
|
+
error_message: z.string().optional(),
|
|
28
|
+
hint: z.string().optional(),
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
export const mcpSearchTool = {
|
|
@@ -34,7 +37,11 @@ export const mcpSearchTool = {
|
|
|
34
37
|
outputSchema,
|
|
35
38
|
execute: async (input, ctx) => {
|
|
36
39
|
if (!ctx.mcpxClient) {
|
|
37
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
results: [],
|
|
42
|
+
is_error: false,
|
|
43
|
+
hint: "No MCP servers configured. Add servers with `botholomew mcpx add`.",
|
|
44
|
+
};
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
try {
|
|
@@ -42,17 +49,38 @@ export const mcpSearchTool = {
|
|
|
42
49
|
keywordOnly: input.keyword_only,
|
|
43
50
|
semanticOnly: input.semantic_only,
|
|
44
51
|
});
|
|
52
|
+
const mapped = results.map((r) => ({
|
|
53
|
+
server: r.server,
|
|
54
|
+
tool: r.tool,
|
|
55
|
+
description: r.description ?? "",
|
|
56
|
+
score: r.score,
|
|
57
|
+
match_type: r.matchType ?? "keyword",
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
results: mapped,
|
|
61
|
+
is_error: false,
|
|
62
|
+
hint:
|
|
63
|
+
mapped.length > 0
|
|
64
|
+
? "Use mcp_info with server and tool name to see the full input schema before calling mcp_exec."
|
|
65
|
+
: "No matches. Try broader search terms, or use mcp_list_tools to browse all available tools.",
|
|
66
|
+
};
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const msg = String(err).toLowerCase();
|
|
69
|
+
const isIndexMissing =
|
|
70
|
+
msg.includes("index") ||
|
|
71
|
+
msg.includes("not found") ||
|
|
72
|
+
msg.includes("no such file");
|
|
73
|
+
|
|
45
74
|
return {
|
|
46
|
-
results:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
results: [],
|
|
76
|
+
is_error: true,
|
|
77
|
+
error_message: isIndexMissing
|
|
78
|
+
? "Search index not built. Run 'botholomew mcpx index' to build it."
|
|
79
|
+
: `Search failed: ${err}`,
|
|
80
|
+
hint: isIndexMissing
|
|
81
|
+
? "Use mcp_list_tools to browse available tools instead."
|
|
82
|
+
: "Search temporarily unavailable. Use mcp_list_tools as a fallback.",
|
|
53
83
|
};
|
|
54
|
-
} catch {
|
|
55
|
-
return { results: [] };
|
|
56
84
|
}
|
|
57
85
|
},
|
|
58
86
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/registry.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { completeTaskTool } from "./task/complete.ts";
|
|
|
34
34
|
import { createTaskTool } from "./task/create.ts";
|
|
35
35
|
import { failTaskTool } from "./task/fail.ts";
|
|
36
36
|
import { listTasksTool } from "./task/list.ts";
|
|
37
|
+
import { updateTaskTool } from "./task/update.ts";
|
|
37
38
|
import { viewTaskTool } from "./task/view.ts";
|
|
38
39
|
import { waitTaskTool } from "./task/wait.ts";
|
|
39
40
|
// Thread tools
|
|
@@ -47,6 +48,7 @@ export function registerAllTools(): void {
|
|
|
47
48
|
registerTool(failTaskTool);
|
|
48
49
|
registerTool(waitTaskTool);
|
|
49
50
|
registerTool(createTaskTool);
|
|
51
|
+
registerTool(updateTaskTool);
|
|
50
52
|
registerTool(listTasksTool);
|
|
51
53
|
registerTool(viewTaskTool);
|
|
52
54
|
|
|
@@ -17,6 +17,7 @@ const outputSchema = z.object({
|
|
|
17
17
|
id: z.string(),
|
|
18
18
|
name: z.string(),
|
|
19
19
|
message: z.string(),
|
|
20
|
+
is_error: z.boolean(),
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export const createScheduleTool = {
|
|
@@ -37,6 +38,7 @@ export const createScheduleTool = {
|
|
|
37
38
|
id: schedule.id,
|
|
38
39
|
name: schedule.name,
|
|
39
40
|
message: `Created schedule "${schedule.name}" with frequency "${schedule.frequency}"`,
|
|
41
|
+
is_error: false,
|
|
40
42
|
};
|
|
41
43
|
},
|
|
42
44
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -17,6 +17,7 @@ const outputSchema = z.object({
|
|
|
17
17
|
}),
|
|
18
18
|
),
|
|
19
19
|
count: z.number(),
|
|
20
|
+
is_error: z.boolean(),
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
export const listSchedulesTool = {
|
|
@@ -38,6 +39,7 @@ export const listSchedulesTool = {
|
|
|
38
39
|
last_run_at: s.last_run_at?.toISOString() ?? null,
|
|
39
40
|
})),
|
|
40
41
|
count: schedules.length,
|
|
42
|
+
is_error: false,
|
|
41
43
|
};
|
|
42
44
|
},
|
|
43
45
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/search/grep.ts
CHANGED
|
@@ -32,6 +32,7 @@ const inputSchema = z.object({
|
|
|
32
32
|
|
|
33
33
|
const outputSchema = z.object({
|
|
34
34
|
matches: z.array(GrepMatchSchema),
|
|
35
|
+
is_error: z.boolean(),
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
export const searchGrepTool = {
|
|
@@ -76,12 +77,12 @@ export const searchGrepTool = {
|
|
|
76
77
|
content: line,
|
|
77
78
|
context_lines: lines.slice(start, end),
|
|
78
79
|
});
|
|
79
|
-
if (matches.length >= maxResults) return { matches };
|
|
80
|
+
if (matches.length >= maxResults) return { matches, is_error: false };
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
return { matches };
|
|
85
|
+
return { matches, is_error: false };
|
|
85
86
|
},
|
|
86
87
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
87
88
|
|
|
@@ -25,6 +25,7 @@ const outputSchema = z.object({
|
|
|
25
25
|
snippet: z.string(),
|
|
26
26
|
}),
|
|
27
27
|
),
|
|
28
|
+
is_error: z.boolean(),
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
export const searchSemanticTool = {
|
|
@@ -55,6 +56,7 @@ export const searchSemanticTool = {
|
|
|
55
56
|
snippet: (r.chunk_content || "").slice(0, 300),
|
|
56
57
|
}))
|
|
57
58
|
.sort((a, b) => b.score - a.score),
|
|
59
|
+
is_error: false,
|
|
58
60
|
};
|
|
59
61
|
},
|
|
60
62
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -7,6 +7,7 @@ const inputSchema = z.object({
|
|
|
7
7
|
|
|
8
8
|
const outputSchema = z.object({
|
|
9
9
|
message: z.string(),
|
|
10
|
+
is_error: z.boolean(),
|
|
10
11
|
});
|
|
11
12
|
|
|
12
13
|
export const completeTaskTool = {
|
|
@@ -19,5 +20,6 @@ export const completeTaskTool = {
|
|
|
19
20
|
outputSchema,
|
|
20
21
|
execute: async (input) => ({
|
|
21
22
|
message: `Task completed: ${input.summary}`,
|
|
23
|
+
is_error: false,
|
|
22
24
|
}),
|
|
23
25
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
package/src/tools/task/create.ts
CHANGED
|
@@ -4,9 +4,19 @@ import { logger } from "../../utils/logger.ts";
|
|
|
4
4
|
import type { ToolDefinition } from "../tool.ts";
|
|
5
5
|
|
|
6
6
|
const inputSchema = z.object({
|
|
7
|
-
name: z
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
name: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("Concise but descriptive task name summarizing the goal"),
|
|
10
|
+
description: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(
|
|
14
|
+
"Detailed description including relevant file paths, what needs to change, why, and any constraints. Rich descriptions reduce redundant tool calls when the task is picked up later.",
|
|
15
|
+
),
|
|
16
|
+
priority: z
|
|
17
|
+
.enum(TASK_PRIORITIES)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Task priority (default: medium)"),
|
|
10
20
|
blocked_by: z
|
|
11
21
|
.array(z.string())
|
|
12
22
|
.optional()
|
|
@@ -17,11 +27,13 @@ const outputSchema = z.object({
|
|
|
17
27
|
id: z.string(),
|
|
18
28
|
name: z.string(),
|
|
19
29
|
message: z.string(),
|
|
30
|
+
is_error: z.boolean(),
|
|
20
31
|
});
|
|
21
32
|
|
|
22
33
|
export const createTaskTool = {
|
|
23
34
|
name: "create_task",
|
|
24
|
-
description:
|
|
35
|
+
description:
|
|
36
|
+
"Create a new task. Include as much context as possible in the description so the agent picking it up can start immediately without redundant lookups.",
|
|
25
37
|
group: "task",
|
|
26
38
|
inputSchema,
|
|
27
39
|
outputSchema,
|
|
@@ -37,6 +49,7 @@ export const createTaskTool = {
|
|
|
37
49
|
id: newTask.id,
|
|
38
50
|
name: newTask.name,
|
|
39
51
|
message: `Created task "${newTask.name}" with ID ${newTask.id}`,
|
|
52
|
+
is_error: false,
|
|
40
53
|
};
|
|
41
54
|
},
|
|
42
55
|
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|