botholomew 0.16.3 → 0.17.0
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/README.md +46 -41
- package/package.json +3 -8
- package/src/chat/agent.ts +37 -40
- package/src/chat/session.ts +8 -8
- package/src/cli.ts +0 -2
- package/src/commands/capabilities.ts +32 -32
- package/src/commands/context.ts +124 -223
- package/src/commands/mcpx.ts +1 -1
- package/src/commands/nuke.ts +44 -15
- package/src/commands/prepare.ts +17 -13
- package/src/config/loader.ts +1 -8
- package/src/constants.ts +16 -32
- package/src/init/index.ts +11 -14
- package/src/mem/client.ts +17 -0
- package/src/{context → prompts}/capabilities.ts +11 -7
- package/src/schedules/store.ts +1 -1
- package/src/tasks/store.ts +1 -1
- package/src/threads/store.ts +1 -1
- package/src/tools/capabilities/refresh.ts +1 -1
- package/src/tools/membot/adapter.ts +111 -0
- package/src/tools/membot/copy.ts +59 -0
- package/src/tools/membot/count_lines.ts +53 -0
- package/src/tools/membot/edit.ts +72 -0
- package/src/tools/membot/exists.ts +54 -0
- package/src/tools/membot/index.ts +26 -0
- package/src/tools/{context → membot}/pipe.ts +34 -32
- package/src/tools/registry.ts +6 -37
- package/src/tools/tool.ts +6 -8
- package/src/tui/App.tsx +3 -4
- package/src/tui/components/ContextPanel.tsx +109 -226
- package/src/tui/components/HelpPanel.tsx +2 -2
- package/src/tui/components/MessageList.tsx +0 -1
- package/src/tui/components/StatusBar.tsx +0 -6
- package/src/tui/components/ThreadPanel.tsx +8 -7
- package/src/tui/wrapDetail.ts +11 -0
- package/src/worker/heartbeat.ts +0 -20
- package/src/worker/index.ts +11 -11
- package/src/worker/llm.ts +7 -9
- package/src/worker/prompt.ts +25 -13
- package/src/worker/spawn.ts +1 -1
- package/src/worker/tick.ts +10 -9
- package/src/commands/db.ts +0 -119
- package/src/commands/with-db.ts +0 -22
- package/src/context/chunker.ts +0 -275
- package/src/context/embedder-impl.ts +0 -100
- package/src/context/embedder.ts +0 -9
- package/src/context/fetcher-errors.ts +0 -8
- package/src/context/fetcher.ts +0 -515
- package/src/context/locks.ts +0 -146
- package/src/context/markdown-converter.ts +0 -186
- package/src/context/reindex.ts +0 -198
- package/src/context/store.ts +0 -841
- package/src/context/url-utils.ts +0 -25
- package/src/db/connection.ts +0 -255
- package/src/db/doctor.ts +0 -235
- package/src/db/embeddings.ts +0 -317
- package/src/db/query.ts +0 -56
- package/src/db/schema.ts +0 -93
- package/src/db/sql/1-core_tables.sql +0 -53
- package/src/db/sql/10-dedupe_context_items.sql +0 -26
- package/src/db/sql/11-rebuild_hnsw.sql +0 -8
- package/src/db/sql/12-workers.sql +0 -66
- package/src/db/sql/13-drive-paths.sql +0 -47
- package/src/db/sql/14-drop_hnsw_index.sql +0 -8
- package/src/db/sql/15-fts_index.sql +0 -8
- package/src/db/sql/16-source_url.sql +0 -7
- package/src/db/sql/17-worker_log_path.sql +0 -3
- package/src/db/sql/18-reset_embeddings_for_local.sql +0 -39
- package/src/db/sql/19-disk_backed_index.sql +0 -36
- package/src/db/sql/2-logging_tables.sql +0 -24
- package/src/db/sql/20-drop_db_tables_for_files.sql +0 -19
- package/src/db/sql/3-daemon_state.sql +0 -5
- package/src/db/sql/4-unique_context_path.sql +0 -1
- package/src/db/sql/5-reset_embeddings_for_openai.sql +0 -1
- package/src/db/sql/6-vss_index.sql +0 -7
- package/src/db/sql/7-drop_embeddings_fk.sql +0 -23
- package/src/db/sql/8-task_output.sql +0 -1
- package/src/db/sql/9-source-type.sql +0 -1
- package/src/tools/context/read-large-result.ts +0 -33
- package/src/tools/dir/create.ts +0 -47
- package/src/tools/dir/size.ts +0 -77
- package/src/tools/dir/tree.ts +0 -124
- package/src/tools/file/copy.ts +0 -73
- package/src/tools/file/count-lines.ts +0 -54
- package/src/tools/file/delete.ts +0 -83
- package/src/tools/file/edit.ts +0 -76
- package/src/tools/file/exists.ts +0 -33
- package/src/tools/file/info.ts +0 -66
- package/src/tools/file/move.ts +0 -66
- package/src/tools/file/read.ts +0 -67
- package/src/tools/file/write.ts +0 -58
- package/src/tools/search/fuse.ts +0 -96
- package/src/tools/search/index.ts +0 -127
- package/src/tools/search/regexp.ts +0 -82
- package/src/tools/search/semantic.ts +0 -167
- /package/src/{db → utils}/uuid.ts +0 -0
package/src/init/index.ts
CHANGED
|
@@ -4,9 +4,7 @@ import { loadConfig } from "../config/loader.ts";
|
|
|
4
4
|
import {
|
|
5
5
|
CONFIG_DIR,
|
|
6
6
|
CONFIG_FILENAME,
|
|
7
|
-
CONTEXT_DIR,
|
|
8
7
|
getConfigPath,
|
|
9
|
-
getDbPath,
|
|
10
8
|
getMcpxDir,
|
|
11
9
|
getPromptsDir,
|
|
12
10
|
getSchedulesDir,
|
|
@@ -22,11 +20,10 @@ import {
|
|
|
22
20
|
SCHEDULES_DIR,
|
|
23
21
|
TASKS_DIR,
|
|
24
22
|
} from "../constants.ts";
|
|
25
|
-
import { writeCapabilitiesFile } from "../context/capabilities.ts";
|
|
26
|
-
import { getConnection } from "../db/connection.ts";
|
|
27
|
-
import { migrate } from "../db/schema.ts";
|
|
28
23
|
import { assertCompatibleFilesystem } from "../fs/compat.ts";
|
|
29
24
|
import { createMcpxClient } from "../mcpx/client.ts";
|
|
25
|
+
import { openMembot } from "../mem/client.ts";
|
|
26
|
+
import { writeCapabilitiesFile } from "../prompts/capabilities.ts";
|
|
30
27
|
import { registerAllTools } from "../tools/registry.ts";
|
|
31
28
|
import { logger } from "../utils/logger.ts";
|
|
32
29
|
import {
|
|
@@ -62,7 +59,6 @@ export async function initProject(
|
|
|
62
59
|
await mkdir(getPromptsDir(projectDir), { recursive: true });
|
|
63
60
|
await mkdir(getSkillsDir(projectDir), { recursive: true });
|
|
64
61
|
await mkdir(getMcpxDir(projectDir), { recursive: true });
|
|
65
|
-
await mkdir(join(projectDir, CONTEXT_DIR), { recursive: true });
|
|
66
62
|
await mkdir(getTasksDir(projectDir), { recursive: true });
|
|
67
63
|
await mkdir(getTasksLockDir(projectDir), { recursive: true });
|
|
68
64
|
await mkdir(getSchedulesDir(projectDir), { recursive: true });
|
|
@@ -92,11 +88,12 @@ export async function initProject(
|
|
|
92
88
|
`${JSON.stringify(DEFAULT_MCPX_SERVERS, null, 2)}\n`,
|
|
93
89
|
);
|
|
94
90
|
|
|
95
|
-
// Initialize the
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
// Initialize the membot knowledge store. Opening + closing the client
|
|
92
|
+
// triggers membot's first-run migration so the project ships with a
|
|
93
|
+
// ready-to-use index.duckdb.
|
|
94
|
+
const mem = openMembot(projectDir);
|
|
95
|
+
await mem.connect();
|
|
96
|
+
await mem.close();
|
|
100
97
|
|
|
101
98
|
// Populate capabilities.md with the real tool inventory.
|
|
102
99
|
registerAllTools();
|
|
@@ -111,20 +108,20 @@ export async function initProject(
|
|
|
111
108
|
logger.success("Initialized Botholomew project");
|
|
112
109
|
logger.dim(` Project root: ${projectDir}`);
|
|
113
110
|
logger.dim(` Config: ${CONFIG_DIR}/${CONFIG_FILENAME}`);
|
|
114
|
-
logger.dim(`
|
|
111
|
+
logger.dim(` Knowledge: index.duckdb (managed by membot)`);
|
|
115
112
|
logger.dim("");
|
|
116
113
|
logger.dim("Layout:");
|
|
117
114
|
logger.dim(` ${CONFIG_DIR}/ settings`);
|
|
118
115
|
logger.dim(
|
|
119
116
|
` prompts/ goals, beliefs, capabilities (and any you add)`,
|
|
120
117
|
);
|
|
121
|
-
logger.dim(`
|
|
118
|
+
logger.dim(` index.duckdb agent's knowledge store (membot)`);
|
|
122
119
|
logger.dim(` ${TASKS_DIR}/ one markdown file per task`);
|
|
123
120
|
logger.dim(` ${LOCKS_SUBDIR}/ worker claim lockfiles`);
|
|
124
121
|
logger.dim(` ${SCHEDULES_DIR}/ one markdown file per schedule`);
|
|
125
122
|
logger.dim(` threads/ one CSV per conversation, by UTC date`);
|
|
126
123
|
logger.dim(` workers/ one JSON pidfile per worker (heartbeats)`);
|
|
127
|
-
logger.dim(` skills/, mcpx/,
|
|
124
|
+
logger.dim(` skills/, mcpx/, logs/`);
|
|
128
125
|
logger.dim("");
|
|
129
126
|
logger.dim("Next steps:");
|
|
130
127
|
logger.dim(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MembotClient } from "membot";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Open a per-project membot client. Each Botholomew project gets its own
|
|
5
|
+
* membot data dir (`<projectDir>/config.json` + `<projectDir>/index.duckdb`)
|
|
6
|
+
* so projects don't share knowledge. The caller is responsible for `close()`
|
|
7
|
+
* on shutdown.
|
|
8
|
+
*
|
|
9
|
+
* Membot's `configFlag` doubles as its data-dir flag (see
|
|
10
|
+
* `membot/src/config/loader.ts::resolveDataDir`): an explicit value wins over
|
|
11
|
+
* `$MEMBOT_HOME` and the `~/.membot` default. We pass the project directory
|
|
12
|
+
* unconditionally so a stray `MEMBOT_HOME` in the user's environment cannot
|
|
13
|
+
* redirect Botholomew at a different store.
|
|
14
|
+
*/
|
|
15
|
+
export function openMembot(projectDir: string): MembotClient {
|
|
16
|
+
return new MembotClient({ configFlag: projectDir });
|
|
17
|
+
}
|
|
@@ -27,8 +27,9 @@ type AnyTool = ToolDefinition<any, any>;
|
|
|
27
27
|
const GROUP_ORDER = [
|
|
28
28
|
"task",
|
|
29
29
|
"schedule",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"membot",
|
|
31
|
+
"prompt",
|
|
32
|
+
"skill",
|
|
32
33
|
"thread",
|
|
33
34
|
"mcp",
|
|
34
35
|
"worker",
|
|
@@ -38,8 +39,9 @@ const GROUP_ORDER = [
|
|
|
38
39
|
const GROUP_HEADINGS: Record<string, string> = {
|
|
39
40
|
task: "Task management",
|
|
40
41
|
schedule: "Schedules",
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
membot: "Knowledge store (membot)",
|
|
43
|
+
prompt: "Prompts",
|
|
44
|
+
skill: "Skills",
|
|
43
45
|
thread: "Threads",
|
|
44
46
|
mcp: "MCPX meta-tools",
|
|
45
47
|
worker: "Workers",
|
|
@@ -367,9 +369,11 @@ function renderFallback(inv: RawInventory, now: Date): string {
|
|
|
367
369
|
task: "create, list, view, update, complete, fail, and wait on tasks in the agent's work queue",
|
|
368
370
|
schedule:
|
|
369
371
|
"create and list recurring schedules that automatically generate tasks",
|
|
370
|
-
|
|
371
|
-
"read, write, edit, move, copy, delete, and
|
|
372
|
-
|
|
372
|
+
membot:
|
|
373
|
+
"add, read, write, edit, move, copy, delete, and search content in the agent's knowledge store; track every version and refresh from URL sources",
|
|
374
|
+
prompt:
|
|
375
|
+
"list, read, create, edit, and delete the project's prompt files (goals, beliefs, capabilities, plus any agent-authored ones)",
|
|
376
|
+
skill: "list, read, write, edit, delete, and search slash-command skills",
|
|
373
377
|
thread: "list and view past conversation threads and tool interactions",
|
|
374
378
|
mcp: "search, list, inspect, and execute tools exposed by configured MCPX servers",
|
|
375
379
|
worker: "spawn background workers to run tasks asynchronously",
|
package/src/schedules/store.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { readdir, unlink } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
import { getSchedulesDir, getSchedulesLockDir } from "../constants.ts";
|
|
5
|
-
import { uuidv7 } from "../db/uuid.ts";
|
|
6
5
|
import {
|
|
7
6
|
acquireLock,
|
|
8
7
|
atomicWrite,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
releaseLock,
|
|
14
13
|
} from "../fs/atomic.ts";
|
|
15
14
|
import { logger } from "../utils/logger.ts";
|
|
15
|
+
import { uuidv7 } from "../utils/uuid.ts";
|
|
16
16
|
import {
|
|
17
17
|
type Schedule,
|
|
18
18
|
type ScheduleFrontmatter,
|
package/src/tasks/store.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { readdir, stat, unlink } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
import { getTasksDir, getTasksLockDir } from "../constants.ts";
|
|
5
|
-
import { uuidv7 } from "../db/uuid.ts";
|
|
6
5
|
import {
|
|
7
6
|
acquireLock,
|
|
8
7
|
atomicWrite,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
releaseLock,
|
|
14
13
|
} from "../fs/atomic.ts";
|
|
15
14
|
import { logger } from "../utils/logger.ts";
|
|
15
|
+
import { uuidv7 } from "../utils/uuid.ts";
|
|
16
16
|
import {
|
|
17
17
|
type Task,
|
|
18
18
|
type TaskFrontmatter,
|
package/src/threads/store.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { appendFile, readdir, readFile, rm, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getThreadsDir } from "../constants.ts";
|
|
4
|
-
import { uuidv7 } from "../db/uuid.ts";
|
|
5
4
|
import { atomicWrite } from "../fs/atomic.ts";
|
|
5
|
+
import { uuidv7 } from "../utils/uuid.ts";
|
|
6
6
|
import { DATE_DIR_RE, dateForId } from "../utils/v7-date.ts";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Operation } from "membot";
|
|
2
|
+
import { composeDescription, isHelpfulError } from "membot";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { ToolContext, ToolDefinition } from "../tool.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Common output envelope for every membot-backed tool. The membot operation's
|
|
8
|
+
* own result is parked under `data` so the LLM gets a single, predictable
|
|
9
|
+
* shape across all 14 verbs. Errors flatten the HelpfulError's `kind`/`hint`
|
|
10
|
+
* into `error_type` / `next_action_hint`, which is the recovery cue Botholomew
|
|
11
|
+
* agents already know from the rest of the tool surface.
|
|
12
|
+
*/
|
|
13
|
+
export const membotOutputSchema = z.object({
|
|
14
|
+
is_error: z.boolean(),
|
|
15
|
+
data: z.unknown().optional(),
|
|
16
|
+
error_type: z.string().optional(),
|
|
17
|
+
message: z.string().optional(),
|
|
18
|
+
next_action_hint: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type MembotOutput = z.infer<typeof membotOutputSchema>;
|
|
22
|
+
|
|
23
|
+
type MembotMethodName =
|
|
24
|
+
| "add"
|
|
25
|
+
| "list"
|
|
26
|
+
| "tree"
|
|
27
|
+
| "read"
|
|
28
|
+
| "search"
|
|
29
|
+
| "info"
|
|
30
|
+
| "stats"
|
|
31
|
+
| "versions"
|
|
32
|
+
| "diff"
|
|
33
|
+
| "write"
|
|
34
|
+
| "move"
|
|
35
|
+
| "remove"
|
|
36
|
+
| "refresh"
|
|
37
|
+
| "prune";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Map an Operation's exposed name (`membot_add`, `membot_delete`, …) to the
|
|
41
|
+
* `MembotClient` method that actually runs it. The two diverge in a couple
|
|
42
|
+
* of spots — `membot_delete` calls `client.remove`, `membot_move` calls
|
|
43
|
+
* `client.move` — so we keep the routing explicit rather than guessing.
|
|
44
|
+
*/
|
|
45
|
+
const METHOD_BY_OP_NAME: Record<string, MembotMethodName> = {
|
|
46
|
+
membot_add: "add",
|
|
47
|
+
membot_list: "list",
|
|
48
|
+
membot_tree: "tree",
|
|
49
|
+
membot_read: "read",
|
|
50
|
+
membot_search: "search",
|
|
51
|
+
membot_info: "info",
|
|
52
|
+
membot_stats: "stats",
|
|
53
|
+
membot_versions: "versions",
|
|
54
|
+
membot_diff: "diff",
|
|
55
|
+
membot_write: "write",
|
|
56
|
+
membot_move: "move",
|
|
57
|
+
membot_delete: "remove",
|
|
58
|
+
membot_refresh: "refresh",
|
|
59
|
+
membot_prune: "prune",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Adapt one membot {@link Operation} into a Botholomew {@link ToolDefinition}.
|
|
64
|
+
* The input schema passes through unchanged so the LLM sees membot's
|
|
65
|
+
* upstream prose/aliases verbatim. Success calls return `{ is_error: false,
|
|
66
|
+
* data: <op output> }`; HelpfulErrors return the flattened error envelope.
|
|
67
|
+
* Unknown errors are wrapped as `internal_error` so a thrown handler never
|
|
68
|
+
* crashes the agent loop.
|
|
69
|
+
*/
|
|
70
|
+
export function adaptOperation(
|
|
71
|
+
// biome-ignore lint/suspicious/noExplicitAny: Operation generic is heterogeneous across 14 verbs
|
|
72
|
+
op: Operation<any, any>,
|
|
73
|
+
): ToolDefinition<z.ZodObject<z.ZodRawShape>, typeof membotOutputSchema> {
|
|
74
|
+
const methodName = METHOD_BY_OP_NAME[op.name];
|
|
75
|
+
if (!methodName) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`adaptOperation: no MembotClient method registered for op '${op.name}'. ` +
|
|
78
|
+
`Add it to METHOD_BY_OP_NAME in src/tools/membot/adapter.ts.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
name: op.name,
|
|
83
|
+
description: composeDescription(op),
|
|
84
|
+
group: "membot",
|
|
85
|
+
inputSchema: op.inputSchema as z.ZodObject<z.ZodRawShape>,
|
|
86
|
+
outputSchema: membotOutputSchema,
|
|
87
|
+
execute: async (input, ctx: ToolContext) => {
|
|
88
|
+
try {
|
|
89
|
+
const method = ctx.mem[methodName] as (i: unknown) => Promise<unknown>;
|
|
90
|
+
const data = await method.call(ctx.mem, input);
|
|
91
|
+
return { is_error: false, data };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (isHelpfulError(err)) {
|
|
94
|
+
return {
|
|
95
|
+
is_error: true,
|
|
96
|
+
error_type: err.kind,
|
|
97
|
+
message: err.message,
|
|
98
|
+
next_action_hint: err.hint,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
is_error: true,
|
|
103
|
+
error_type: "internal_error",
|
|
104
|
+
message: err instanceof Error ? err.message : String(err),
|
|
105
|
+
next_action_hint:
|
|
106
|
+
"Check the project's membot store (run `botholomew context stats`) and try again. If this persists, file a bug.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isHelpfulError } from "membot";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ToolDefinition } from "../tool.ts";
|
|
4
|
+
|
|
5
|
+
const inputSchema = z.object({
|
|
6
|
+
from_logical_path: z.string().describe("Source path"),
|
|
7
|
+
to_logical_path: z.string().describe("Destination path"),
|
|
8
|
+
change_note: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const outputSchema = z.object({
|
|
12
|
+
is_error: z.boolean(),
|
|
13
|
+
from_logical_path: z.string().optional(),
|
|
14
|
+
to_logical_path: z.string().optional(),
|
|
15
|
+
new_version_id: z.string().optional(),
|
|
16
|
+
error_type: z.string().optional(),
|
|
17
|
+
message: z.string().optional(),
|
|
18
|
+
next_action_hint: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const membotCopyTool = {
|
|
22
|
+
name: "membot_copy",
|
|
23
|
+
description:
|
|
24
|
+
"[[ bash equivalent command: cp ]] Duplicate a file's current content under a new logical_path. The source is left untouched; the destination becomes a new inline-source version. Use membot_move to rename instead (the source is tombstoned in that case).",
|
|
25
|
+
group: "membot",
|
|
26
|
+
inputSchema,
|
|
27
|
+
outputSchema,
|
|
28
|
+
execute: async (input, ctx) => {
|
|
29
|
+
try {
|
|
30
|
+
const src = await ctx.mem.read({ logical_path: input.from_logical_path });
|
|
31
|
+
const written = await ctx.mem.write({
|
|
32
|
+
logical_path: input.to_logical_path,
|
|
33
|
+
content: src.content ?? "",
|
|
34
|
+
change_note:
|
|
35
|
+
input.change_note ?? `copied from ${input.from_logical_path}`,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
is_error: false,
|
|
39
|
+
from_logical_path: input.from_logical_path,
|
|
40
|
+
to_logical_path: written.logical_path,
|
|
41
|
+
new_version_id: written.version_id,
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (isHelpfulError(err)) {
|
|
45
|
+
return {
|
|
46
|
+
is_error: true,
|
|
47
|
+
error_type: err.kind,
|
|
48
|
+
message: err.message,
|
|
49
|
+
next_action_hint: err.hint,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
is_error: true,
|
|
54
|
+
error_type: "internal_error",
|
|
55
|
+
message: err instanceof Error ? err.message : String(err),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isHelpfulError } from "membot";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ToolDefinition } from "../tool.ts";
|
|
4
|
+
|
|
5
|
+
const inputSchema = z.object({
|
|
6
|
+
logical_path: z.string().describe("Logical path of the file to count."),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const outputSchema = z.object({
|
|
10
|
+
is_error: z.boolean(),
|
|
11
|
+
logical_path: z.string().optional(),
|
|
12
|
+
line_count: z.number().optional(),
|
|
13
|
+
size_bytes: z.number().nullable().optional(),
|
|
14
|
+
error_type: z.string().optional(),
|
|
15
|
+
message: z.string().optional(),
|
|
16
|
+
next_action_hint: z.string().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const membotCountLinesTool = {
|
|
20
|
+
name: "membot_count_lines",
|
|
21
|
+
description:
|
|
22
|
+
"[[ bash equivalent command: wc -l ]] Count lines in a stored file's markdown surrogate. Useful before a large membot_read or membot_edit to decide whether to fetch the whole body or page through it with `offset`/`limit`.",
|
|
23
|
+
group: "membot",
|
|
24
|
+
inputSchema,
|
|
25
|
+
outputSchema,
|
|
26
|
+
execute: async (input, ctx) => {
|
|
27
|
+
try {
|
|
28
|
+
const result = await ctx.mem.read({ logical_path: input.logical_path });
|
|
29
|
+
const content = result.content ?? "";
|
|
30
|
+
const lineCount = content === "" ? 0 : content.split("\n").length;
|
|
31
|
+
return {
|
|
32
|
+
is_error: false,
|
|
33
|
+
logical_path: result.logical_path,
|
|
34
|
+
line_count: lineCount,
|
|
35
|
+
size_bytes: result.size_bytes,
|
|
36
|
+
};
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (isHelpfulError(err)) {
|
|
39
|
+
return {
|
|
40
|
+
is_error: true,
|
|
41
|
+
error_type: err.kind,
|
|
42
|
+
message: err.message,
|
|
43
|
+
next_action_hint: err.hint,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
is_error: true,
|
|
48
|
+
error_type: "internal_error",
|
|
49
|
+
message: err instanceof Error ? err.message : String(err),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isHelpfulError } from "membot";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { applyLinePatches, LinePatchSchema } from "../../fs/patches.ts";
|
|
4
|
+
import type { ToolDefinition } from "../tool.ts";
|
|
5
|
+
|
|
6
|
+
const inputSchema = z.object({
|
|
7
|
+
logical_path: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("Logical path of the file to edit (e.g. 'notes/foo.md')."),
|
|
10
|
+
patches: z
|
|
11
|
+
.array(LinePatchSchema)
|
|
12
|
+
.min(1)
|
|
13
|
+
.describe(
|
|
14
|
+
"Git-hunk-style edits applied bottom-up. `end_line: 0` inserts; empty `content` deletes.",
|
|
15
|
+
),
|
|
16
|
+
change_note: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Free-text note attached to the new version."),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const outputSchema = z.object({
|
|
23
|
+
is_error: z.boolean(),
|
|
24
|
+
logical_path: z.string().optional(),
|
|
25
|
+
version_id: z.string().optional(),
|
|
26
|
+
size_bytes: z.number().optional(),
|
|
27
|
+
error_type: z.string().optional(),
|
|
28
|
+
message: z.string().optional(),
|
|
29
|
+
next_action_hint: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const membotEditTool = {
|
|
33
|
+
name: "membot_edit",
|
|
34
|
+
description:
|
|
35
|
+
"[[ bash equivalent command: patch ]] Apply line-range edits to a stored file: reads the current version, applies bottom-up patches, and writes the result back as a new version. Prefer this over membot_write when you only need to change part of a file — the diff is small and the change_note travels with the new version. To replace the whole body, use membot_write. To delete the file, use membot_delete.",
|
|
36
|
+
group: "membot",
|
|
37
|
+
inputSchema,
|
|
38
|
+
outputSchema,
|
|
39
|
+
execute: async (input, ctx) => {
|
|
40
|
+
try {
|
|
41
|
+
const current = await ctx.mem.read({ logical_path: input.logical_path });
|
|
42
|
+
const next = applyLinePatches(current.content ?? "", input.patches);
|
|
43
|
+
const result = await ctx.mem.write({
|
|
44
|
+
logical_path: input.logical_path,
|
|
45
|
+
content: next,
|
|
46
|
+
change_note: input.change_note,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
is_error: false,
|
|
50
|
+
logical_path: result.logical_path,
|
|
51
|
+
version_id: result.version_id,
|
|
52
|
+
size_bytes: result.size_bytes,
|
|
53
|
+
};
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (isHelpfulError(err)) {
|
|
56
|
+
return {
|
|
57
|
+
is_error: true,
|
|
58
|
+
error_type: err.kind,
|
|
59
|
+
message: err.message,
|
|
60
|
+
next_action_hint: err.hint,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
is_error: true,
|
|
65
|
+
error_type: "internal_error",
|
|
66
|
+
message: err instanceof Error ? err.message : String(err),
|
|
67
|
+
next_action_hint:
|
|
68
|
+
"Re-read the file with membot_read to confirm current line numbers, then retry.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isHelpfulError } from "membot";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ToolDefinition } from "../tool.ts";
|
|
4
|
+
|
|
5
|
+
const inputSchema = z.object({
|
|
6
|
+
logical_path: z.string().describe("Logical path to check."),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const outputSchema = z.object({
|
|
10
|
+
is_error: z.boolean(),
|
|
11
|
+
exists: z.boolean().optional(),
|
|
12
|
+
logical_path: z.string().optional(),
|
|
13
|
+
error_type: z.string().optional(),
|
|
14
|
+
message: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const membotExistsTool = {
|
|
18
|
+
name: "membot_exists",
|
|
19
|
+
description:
|
|
20
|
+
"[[ bash equivalent command: test -e ]] Check whether a logical_path has a current (non-tombstoned) version in the store. Returns `{ exists: true|false }` — never throws on absence. Use before membot_write when you want to avoid clobbering, or to disambiguate a not_found from a real error.",
|
|
21
|
+
group: "membot",
|
|
22
|
+
inputSchema,
|
|
23
|
+
outputSchema,
|
|
24
|
+
execute: async (input, ctx) => {
|
|
25
|
+
try {
|
|
26
|
+
await ctx.mem.info({ logical_path: input.logical_path });
|
|
27
|
+
return {
|
|
28
|
+
is_error: false,
|
|
29
|
+
exists: true,
|
|
30
|
+
logical_path: input.logical_path,
|
|
31
|
+
};
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (isHelpfulError(err) && err.kind === "not_found") {
|
|
34
|
+
return {
|
|
35
|
+
is_error: false,
|
|
36
|
+
exists: false,
|
|
37
|
+
logical_path: input.logical_path,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (isHelpfulError(err)) {
|
|
41
|
+
return {
|
|
42
|
+
is_error: true,
|
|
43
|
+
error_type: err.kind,
|
|
44
|
+
message: err.message,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
is_error: true,
|
|
49
|
+
error_type: "internal_error",
|
|
50
|
+
message: err instanceof Error ? err.message : String(err),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
} satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OPERATIONS } from "membot";
|
|
2
|
+
import { type AnyToolDefinition, registerTool } from "../tool.ts";
|
|
3
|
+
import { adaptOperation } from "./adapter.ts";
|
|
4
|
+
import { membotCopyTool } from "./copy.ts";
|
|
5
|
+
import { membotCountLinesTool } from "./count_lines.ts";
|
|
6
|
+
import { membotEditTool } from "./edit.ts";
|
|
7
|
+
import { membotExistsTool } from "./exists.ts";
|
|
8
|
+
import { membotPipeTool } from "./pipe.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register every membot operation as a Botholomew tool. The 14 verbs that
|
|
12
|
+
* have a direct membot Operation (add, list, tree, read, search, info,
|
|
13
|
+
* stats, versions, diff, write, move, delete, refresh, prune) are wired via
|
|
14
|
+
* `adaptOperation`; the five Botholomew-side wrappers (edit, copy, exists,
|
|
15
|
+
* count_lines, pipe) bolt on the file-shaped UX our agents already know.
|
|
16
|
+
*/
|
|
17
|
+
export function registerMembotTools(): void {
|
|
18
|
+
for (const op of OPERATIONS) {
|
|
19
|
+
registerTool(adaptOperation(op) as unknown as AnyToolDefinition);
|
|
20
|
+
}
|
|
21
|
+
registerTool(membotEditTool);
|
|
22
|
+
registerTool(membotCopyTool);
|
|
23
|
+
registerTool(membotExistsTool);
|
|
24
|
+
registerTool(membotCountLinesTool);
|
|
25
|
+
registerTool(membotPipeTool);
|
|
26
|
+
}
|