botholomew 0.16.4 → 0.18.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 +4 -9
- package/src/chat/agent.ts +37 -40
- package/src/chat/session.ts +10 -10
- package/src/cli.ts +0 -2
- package/src/commands/capabilities.ts +35 -33
- package/src/commands/context.ts +133 -221
- package/src/commands/init.ts +22 -1
- package/src/commands/mcpx.ts +21 -8
- package/src/commands/nuke.ts +52 -15
- package/src/commands/prepare.ts +16 -13
- package/src/config/loader.ts +1 -8
- package/src/config/schemas.ts +6 -0
- package/src/constants.ts +16 -32
- package/src/init/index.ts +52 -27
- package/src/mcpx/client.ts +21 -5
- package/src/mem/client.ts +33 -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/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 +13 -13
- 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/README.md
CHANGED
|
@@ -14,12 +14,14 @@ documents, researching topics, organizing notes, and maintaining context
|
|
|
14
14
|
over time — while you sleep, work, or chat with it.
|
|
15
15
|
|
|
16
16
|
Botholomew has **no shell and no access to your real filesystem**. The
|
|
17
|
-
agent's world is a
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
agent's world is a per-project knowledge store managed by
|
|
18
|
+
[`membot`](https://github.com/evantahler/membot) — every read, write,
|
|
19
|
+
search, and delete is addressed by `logical_path` (a DB key, not a
|
|
20
|
+
filesystem path), so a prompt-injected attempt to reach `~/.ssh/id_rsa`
|
|
21
|
+
has nowhere to land. Local files and URLs are brought in through
|
|
22
|
+
`botholomew context add`. External capabilities (email, Slack, the web,
|
|
23
|
+
and hundreds of other services) are granted deliberately, per project,
|
|
24
|
+
through MCP servers wired up via
|
|
23
25
|
[MCPX](https://github.com/evantahler/mcpx).
|
|
24
26
|
|
|
25
27
|
---
|
|
@@ -32,12 +34,13 @@ deliberately, per project, through MCP servers wired up via
|
|
|
32
34
|
- **Portable.** A project is just a directory of files — markdown for
|
|
33
35
|
prompts, tasks, schedules, and context; CSVs for conversation history.
|
|
34
36
|
Copy it, share it, `git diff` it, check it in (or `.gitignore` it).
|
|
35
|
-
- **Your data, your disk.** Tasks, schedules, threads, and
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
- **Your data, your disk.** Tasks, schedules, threads, prompts, and
|
|
38
|
+
skills are all real files you can `vim`, `grep`, and `git`. The
|
|
39
|
+
knowledge store is a single local DuckDB file (`index.duckdb`,
|
|
40
|
+
managed by [`membot`](https://github.com/evantahler/membot)) — append-only,
|
|
41
|
+
versioned, queryable with the DuckDB CLI if you ever want to. Model
|
|
42
|
+
calls go direct to Anthropic; any further reach is scoped to the MCP
|
|
43
|
+
servers you add.
|
|
41
44
|
- **Extensible.** External tools come from MCP servers via
|
|
42
45
|
[MCPX](https://github.com/evantahler/mcpx) — run them locally (Gmail,
|
|
43
46
|
Slack, GitHub) or connect through an MCP gateway like
|
|
@@ -46,9 +49,12 @@ deliberately, per project, through MCP servers wired up via
|
|
|
46
49
|
Reusable workflows are defined as markdown "skills" (slash commands)
|
|
47
50
|
that the chat agent can also create, edit, and search at runtime.
|
|
48
51
|
- **Safe by default.** The agent has no shell and no direct filesystem
|
|
49
|
-
access.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
access. The knowledge store is keyed by `logical_path` (an opaque DB
|
|
53
|
+
string, not a filesystem path); every external capability is an MCP
|
|
54
|
+
server you explicitly add. The remaining file-system paths the agent
|
|
55
|
+
touches (`tasks/`, `schedules/`, `prompts/`, `skills/`) all route
|
|
56
|
+
through a single sandbox helper (NFC normalization + lstat-walk to
|
|
57
|
+
reject symlinks at any level).
|
|
52
58
|
- **Concurrent.** Many workers can run at once. Each writes a pidfile
|
|
53
59
|
and heartbeats; tasks and schedules are claimed via `O_EXCL` lockfiles
|
|
54
60
|
and crashed workers get reaped automatically.
|
|
@@ -133,8 +139,8 @@ my-project/
|
|
|
133
139
|
standup.md
|
|
134
140
|
capabilities.md
|
|
135
141
|
mcpx/servers.json # external MCP servers (Gmail, Slack, …)
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
index.duckdb # knowledge store (managed by membot)
|
|
143
|
+
config.json # membot config (separate from config/config.json)
|
|
138
144
|
tasks/ # one markdown file per task
|
|
139
145
|
<id>.md # status & metadata in frontmatter
|
|
140
146
|
.locks/<id>.lock # O_EXCL claim file (held by a worker)
|
|
@@ -144,12 +150,14 @@ my-project/
|
|
|
144
150
|
threads/<YYYY-MM-DD>/<id>.csv # full conversation history
|
|
145
151
|
workers/<id>.json # worker pidfile + heartbeat
|
|
146
152
|
logs/<YYYY-MM-DD>/<id>.log # per-worker logs
|
|
147
|
-
index.duckdb # search index sidecar (rebuildable; safe to delete)
|
|
148
153
|
```
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
Tasks, schedules, threads, prompts, and skills are plain text — `vim`,
|
|
156
|
+
`grep`, and `git` work without ceremony. The agent's knowledge store
|
|
157
|
+
lives in `index.duckdb`, managed end-to-end by the
|
|
158
|
+
[`membot`](https://github.com/evantahler/membot) library: ingestion
|
|
159
|
+
(PDF/DOCX/HTML → markdown), local WASM embeddings, hybrid BM25 +
|
|
160
|
+
semantic search, append-only versioning, and URL refresh all live there.
|
|
153
161
|
|
|
154
162
|
---
|
|
155
163
|
|
|
@@ -157,8 +165,8 @@ from `context/`.
|
|
|
157
165
|
|
|
158
166
|

|
|
159
167
|
|
|
160
|
-
Pulling a remote document straight into
|
|
161
|
-
fetcher (`mcp_search` → `mcp_exec` →
|
|
168
|
+
Pulling a remote document straight into the knowledge store via an
|
|
169
|
+
LLM-driven fetcher (`mcp_search` → `mcp_exec` → `membot_pipe`):
|
|
162
170
|
|
|
163
171
|

|
|
164
172
|
|
|
@@ -170,14 +178,14 @@ fetcher (`mcp_search` → `mcp_exec` → save):
|
|
|
170
178
|
| `botholomew chat` | Interactive Ink/React TUI |
|
|
171
179
|
| `botholomew task list\|add\|view\|update\|reset\|delete` | Manage the task queue (markdown files in `tasks/`) |
|
|
172
180
|
| `botholomew schedule list\|add\|view\|enable\|disable\|trigger\|delete` | Recurring work (markdown files in `schedules/`) |
|
|
173
|
-
| `botholomew context add\|
|
|
181
|
+
| `botholomew context add\|ls\|tree\|read\|write\|search\|info\|versions\|diff\|refresh\|…` | Knowledge-store passthrough to [`membot`](https://github.com/evantahler/membot) — `--config` is set to the project dir automatically |
|
|
182
|
+
| `botholomew context import-global` | Seed the project from `~/.membot` (copies `index.duckdb` + `config.json` in) |
|
|
174
183
|
| `botholomew capabilities` | Rescan built-in + MCPX tools and rewrite `prompts/capabilities.md` |
|
|
175
184
|
| `botholomew prompts list\|show\|create\|edit\|delete\|validate` | CRUD over the markdown files in `prompts/` (with strict frontmatter validation) |
|
|
176
185
|
| `botholomew mcpx servers\|list\|add\|remove\|info\|search\|exec\|ping\|auth\|deauth\|import-global\|…` | Configure external MCP servers (passthrough to `mcpx`) |
|
|
177
186
|
| `botholomew skill list\|show\|create\|validate` | Manage slash-command skills |
|
|
178
187
|
| `botholomew thread list\|view` | Browse the agent's conversation history (CSVs in `threads/`) |
|
|
179
|
-
| `botholomew nuke
|
|
180
|
-
| `botholomew db doctor [--repair]` | Probe the search-index DB; rebuild via EXPORT/IMPORT |
|
|
188
|
+
| `botholomew nuke knowledge\|tasks\|schedules\|threads\|all` | Bulk-erase project state |
|
|
181
189
|
| `botholomew upgrade` | Self-update |
|
|
182
190
|
|
|
183
191
|
All `list` subcommands support `-l, --limit <n>` and `-o, --offset <n>` for pagination.
|
|
@@ -206,8 +214,9 @@ All `list` subcommands support `-l, --limit <n>` and `-o, --offset <n>` for pagi
|
|
|
206
214
|
│ schedules/<id>.md │
|
|
207
215
|
│ threads/<date>/<id>.csv │
|
|
208
216
|
│ workers/<id>.json │
|
|
209
|
-
│
|
|
210
|
-
│
|
|
217
|
+
│ prompts/, skills/, mcpx/ │
|
|
218
|
+
│ index.duckdb ◄─ membot │
|
|
219
|
+
│ (knowledge store) │
|
|
211
220
|
└──────────────────┬────────────────────┘
|
|
212
221
|
│
|
|
213
222
|
▼
|
|
@@ -232,12 +241,11 @@ Topics worth understanding in detail:
|
|
|
232
241
|
- **[The TUI](docs/tui.md)** — the `botholomew chat` Ink/React terminal UI:
|
|
233
242
|
eight tabs, slash-command autocomplete, message queue, tool-call
|
|
234
243
|
visualization, and a live workers panel.
|
|
235
|
-
- **[Files & the
|
|
236
|
-
|
|
237
|
-
`
|
|
238
|
-
- **[Context &
|
|
239
|
-
chunking,
|
|
240
|
-
search merged with reciprocal rank fusion.
|
|
244
|
+
- **[Files & the knowledge store](docs/files.md)** — the membot store,
|
|
245
|
+
the path sandbox (NFC + lstat-walk) for non-knowledge files, and how
|
|
246
|
+
`membot_read`/`membot_write`/`membot_edit` work.
|
|
247
|
+
- **[Context & search](docs/context-and-search.md)** — pointer to
|
|
248
|
+
membot for ingestion, chunking, embeddings, and hybrid search.
|
|
241
249
|
- **[Tasks & schedules](docs/tasks-and-schedules.md)** — markdown
|
|
242
250
|
frontmatter as the source of truth, lockfile-based claim, DAG
|
|
243
251
|
validation, and natural-language recurring schedules.
|
|
@@ -260,15 +268,12 @@ Topics worth understanding in detail:
|
|
|
260
268
|
## Tech stack
|
|
261
269
|
|
|
262
270
|
- **[Bun](https://bun.sh)** + TypeScript
|
|
263
|
-
- **[
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
- **[`membot`](https://github.com/evantahler/membot)** — owns the
|
|
272
|
+
knowledge store: ingestion (PDF/DOCX/HTML → markdown), local WASM
|
|
273
|
+
embeddings, hybrid BM25 + semantic search over DuckDB, append-only
|
|
274
|
+
versioning, URL refresh. Botholomew consumes it as an SDK.
|
|
267
275
|
- **[Anthropic SDK](https://docs.anthropic.com/en/api/client-sdks)** for
|
|
268
276
|
Claude — the reasoning model
|
|
269
|
-
- **[`@huggingface/transformers`](https://huggingface.co/docs/transformers.js)**
|
|
270
|
-
for local embeddings (default `Xenova/bge-small-en-v1.5`, 384-dim) —
|
|
271
|
-
no API key, weights cached on first run
|
|
272
277
|
- **[MCPX](https://github.com/evantahler/mcpx)** for external tools
|
|
273
278
|
- **[Ink 6](https://github.com/vadimdemedes/ink)** + **React 19** for the
|
|
274
279
|
terminal UI
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botholomew",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "An autonomous AI agent for knowledge work — works your task queue while you sleep.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,9 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
31
|
-
"@
|
|
32
|
-
"@evantahler/mcpx": "0.21.3",
|
|
33
|
-
"@huggingface/transformers": "^4.2.0",
|
|
31
|
+
"@evantahler/mcpx": "0.21.6",
|
|
34
32
|
"ansis": "^4.2.0",
|
|
35
33
|
"commander": "^14.0.0",
|
|
36
34
|
"gray-matter": "^4.0.3",
|
|
@@ -38,8 +36,8 @@
|
|
|
38
36
|
"ink-spinner": "^5.0.0",
|
|
39
37
|
"ink-text-input": "^6.0.0",
|
|
40
38
|
"istextorbinary": "^9.5.0",
|
|
39
|
+
"membot": "^0.14.0",
|
|
41
40
|
"nanospinner": "^1.2.2",
|
|
42
|
-
"onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
|
|
43
41
|
"react": "^19.2.0",
|
|
44
42
|
"uuid": "^14.0.0",
|
|
45
43
|
"wrap-ansi": "^10.0.0",
|
|
@@ -57,8 +55,5 @@
|
|
|
57
55
|
},
|
|
58
56
|
"trustedDependencies": [
|
|
59
57
|
"protobufjs"
|
|
60
|
-
]
|
|
61
|
-
"patchedDependencies": {
|
|
62
|
-
"@huggingface/transformers@4.2.0": "patches/@huggingface%2Ftransformers@4.2.0.patch"
|
|
63
|
-
}
|
|
58
|
+
]
|
|
64
59
|
}
|
package/src/chat/agent.ts
CHANGED
|
@@ -7,8 +7,8 @@ import type {
|
|
|
7
7
|
ToolUseBlock,
|
|
8
8
|
} from "@anthropic-ai/sdk/resources/messages";
|
|
9
9
|
import type { McpxClient } from "@evantahler/mcpx";
|
|
10
|
+
import type { MembotClient } from "membot";
|
|
10
11
|
import type { BotholomewConfig } from "../config/schemas.ts";
|
|
11
|
-
import { withDb } from "../db/connection.ts";
|
|
12
12
|
import { logInteraction } from "../threads/store.ts";
|
|
13
13
|
import { registerAllTools } from "../tools/registry.ts";
|
|
14
14
|
import {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
buildMetaHeader,
|
|
25
25
|
extractKeywords,
|
|
26
26
|
loadPersistentContext,
|
|
27
|
+
MEMBOT_PROMPT_SECTION,
|
|
27
28
|
STYLE_RULES,
|
|
28
29
|
} from "../worker/prompt.ts";
|
|
29
30
|
import type { ChatSession } from "./session.ts";
|
|
@@ -35,19 +36,29 @@ import {
|
|
|
35
36
|
|
|
36
37
|
registerAllTools();
|
|
37
38
|
|
|
38
|
-
/** Tools available in chat mode — no worker terminal tools (complete/fail/wait),
|
|
39
|
+
/** Tools available in chat mode — no worker terminal tools (complete/fail/wait), and the destructive `membot_prune` is omitted (chat shouldn't permanently GC history). */
|
|
39
40
|
const CHAT_TOOL_NAMES = new Set([
|
|
40
41
|
"create_task",
|
|
41
42
|
"list_tasks",
|
|
42
43
|
"view_task",
|
|
43
44
|
"update_task",
|
|
44
45
|
"delete_task",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
46
|
+
"membot_add",
|
|
47
|
+
"membot_list",
|
|
48
|
+
"membot_tree",
|
|
49
|
+
"membot_read",
|
|
50
|
+
"membot_write",
|
|
51
|
+
"membot_edit",
|
|
52
|
+
"membot_search",
|
|
53
|
+
"membot_info",
|
|
54
|
+
"membot_stats",
|
|
55
|
+
"membot_versions",
|
|
56
|
+
"membot_diff",
|
|
57
|
+
"membot_refresh",
|
|
58
|
+
"membot_exists",
|
|
59
|
+
"membot_count_lines",
|
|
60
|
+
"membot_copy",
|
|
61
|
+
"membot_pipe",
|
|
51
62
|
"list_threads",
|
|
52
63
|
"view_thread",
|
|
53
64
|
"search_threads",
|
|
@@ -62,8 +73,6 @@ const CHAT_TOOL_NAMES = new Set([
|
|
|
62
73
|
"mcp_search",
|
|
63
74
|
"mcp_info",
|
|
64
75
|
"mcp_exec",
|
|
65
|
-
"read_large_result",
|
|
66
|
-
"pipe_to_context",
|
|
67
76
|
"spawn_worker",
|
|
68
77
|
"skill_list",
|
|
69
78
|
"skill_read",
|
|
@@ -84,7 +93,6 @@ export async function buildChatSystemPrompt(
|
|
|
84
93
|
projectDir: string,
|
|
85
94
|
options?: {
|
|
86
95
|
keywordSource?: string;
|
|
87
|
-
dbPath?: string;
|
|
88
96
|
config?: Required<BotholomewConfig>;
|
|
89
97
|
hasMcpTools?: boolean;
|
|
90
98
|
},
|
|
@@ -97,9 +105,9 @@ export async function buildChatSystemPrompt(
|
|
|
97
105
|
prompt += await loadPersistentContext(projectDir, taskKeywords);
|
|
98
106
|
|
|
99
107
|
prompt += `## Instructions
|
|
100
|
-
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
|
|
108
|
+
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 the knowledge store, and answer questions.
|
|
101
109
|
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.
|
|
102
|
-
Use the available tools to look up tasks, threads, schedules, and
|
|
110
|
+
Use the available tools to look up tasks, threads, schedules, and the knowledge store when the user asks about them. The agent's knowledge lives in the membot store, keyed by \`logical_path\` (e.g. \`notes/foo.md\`). Use \`membot_tree\` to see what's there, \`membot_search\` (hybrid semantic + BM25) to find content, then \`membot_read\` / \`membot_info\` to drill in. Every write creates a new version — use \`membot_versions\` / \`membot_diff\` to inspect history.
|
|
103
111
|
Past conversations live in CSV files under \`threads/\`; use \`list_threads\`, \`search_threads\`, and \`view_thread\` to find and page through them.
|
|
104
112
|
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.
|
|
105
113
|
You can manage the agent's prompt files (always-on or keyword-loaded notes the agent sees in every turn) under \`prompts/\` via \`prompt_list\`, \`prompt_read\`, \`prompt_create\`, \`prompt_edit\` (git-style line-range patches), and \`prompt_delete\`. Files marked \`agent-modification: false\` are read-only — \`prompt_edit\` and \`prompt_delete\` will refuse them.
|
|
@@ -107,26 +115,28 @@ You can author and refine slash-command skills (reusable prompt templates stored
|
|
|
107
115
|
Format your responses using Markdown. Use headings, bold, italic, lists, and code blocks to make your responses clear and well-structured.
|
|
108
116
|
`;
|
|
109
117
|
|
|
118
|
+
prompt += `\n${MEMBOT_PROMPT_SECTION}`;
|
|
119
|
+
|
|
110
120
|
if (options?.hasMcpTools) {
|
|
111
121
|
prompt += `
|
|
112
122
|
## External Tools (MCP)
|
|
113
123
|
|
|
114
|
-
### Local
|
|
124
|
+
### Local knowledge store first
|
|
115
125
|
|
|
116
|
-
**Before any MCP read, search
|
|
126
|
+
**Before any MCP read, search the membot knowledge store.** Prior ingests (Gmail dumps, GitHub fetches, URL captures, prior agent outputs) are usually already there — refetching is slower, costs tokens, and risks rate limits.
|
|
117
127
|
|
|
118
128
|
Workflow for any "look up / find / read" intent:
|
|
119
129
|
|
|
120
|
-
1. \`
|
|
121
|
-
2. If freshness matters, call \`
|
|
130
|
+
1. \`membot_search\` (hybrid semantic + BM25) over the store, then \`membot_read\` / \`membot_tree\` to drill in.
|
|
131
|
+
2. If freshness matters, call \`membot_info\` and check the source mtime / refresh status. To re-pull stale content, call \`membot_refresh\` for URL-backed entries, or \`membot_pipe\` an \`mcp_exec\` call to capture a fresh snapshot.
|
|
122
132
|
3. Only call \`mcp_exec\` for reads when the data is genuinely missing locally **or** must be real-time (e.g., "what's on my calendar right now").
|
|
123
133
|
|
|
124
|
-
Writes always go through MCP — sending an email, creating an issue, posting to Slack. Don't search
|
|
134
|
+
Writes to external systems always go through MCP — sending an email, creating an issue, posting to Slack. Don't search membot first for those.
|
|
125
135
|
|
|
126
136
|
Examples:
|
|
127
|
-
- "What does doc X say?" → \`
|
|
128
|
-
- "Any new emails from Y?" → \`
|
|
129
|
-
- "Send an email to Y" → MCP write directly; no
|
|
137
|
+
- "What does doc X say?" → \`membot_search\` first.
|
|
138
|
+
- "Any new emails from Y?" → \`membot_search\` for the sender's name before hitting Gmail MCP.
|
|
139
|
+
- "Send an email to Y" → MCP write directly; no membot lookup.
|
|
130
140
|
|
|
131
141
|
### Calling MCP tools
|
|
132
142
|
|
|
@@ -201,7 +211,7 @@ export async function runChatTurn(input: {
|
|
|
201
211
|
messages: MessageParam[];
|
|
202
212
|
projectDir: string;
|
|
203
213
|
config: Required<BotholomewConfig>;
|
|
204
|
-
|
|
214
|
+
mem: MembotClient;
|
|
205
215
|
threadId: string;
|
|
206
216
|
mcpxClient: McpxClient | null;
|
|
207
217
|
callbacks: ChatTurnCallbacks;
|
|
@@ -218,7 +228,7 @@ export async function runChatTurn(input: {
|
|
|
218
228
|
messages,
|
|
219
229
|
projectDir,
|
|
220
230
|
config,
|
|
221
|
-
|
|
231
|
+
mem,
|
|
222
232
|
threadId,
|
|
223
233
|
mcpxClient,
|
|
224
234
|
callbacks,
|
|
@@ -259,7 +269,6 @@ export async function runChatTurn(input: {
|
|
|
259
269
|
const keywordSource = findLastUserText(messages);
|
|
260
270
|
const systemPrompt = await buildChatSystemPrompt(projectDir, {
|
|
261
271
|
keywordSource,
|
|
262
|
-
dbPath,
|
|
263
272
|
config,
|
|
264
273
|
hasMcpTools: mcpxClient != null,
|
|
265
274
|
});
|
|
@@ -410,7 +419,7 @@ export async function runChatTurn(input: {
|
|
|
410
419
|
toolUseBlocks.map(async (toolUse) => {
|
|
411
420
|
const start = Date.now();
|
|
412
421
|
const result = await executeChatToolCall(toolUse, {
|
|
413
|
-
|
|
422
|
+
mem,
|
|
414
423
|
projectDir,
|
|
415
424
|
config,
|
|
416
425
|
mcpxClient,
|
|
@@ -461,7 +470,7 @@ export async function runChatTurn(input: {
|
|
|
461
470
|
}
|
|
462
471
|
|
|
463
472
|
interface ChatToolCallCtx {
|
|
464
|
-
|
|
473
|
+
mem: MembotClient;
|
|
465
474
|
projectDir: string;
|
|
466
475
|
config: Required<BotholomewConfig>;
|
|
467
476
|
mcpxClient: McpxClient | null;
|
|
@@ -490,20 +499,8 @@ async function executeChatToolCall(
|
|
|
490
499
|
}
|
|
491
500
|
|
|
492
501
|
try {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// lock and block any worker that also wants the DB. Run it without a
|
|
496
|
-
// connection — the tool doesn't touch the DB.
|
|
497
|
-
const runWithoutDb = tool.name === "sleep";
|
|
498
|
-
const result = runWithoutDb
|
|
499
|
-
? await tool.execute(parsed.data, {
|
|
500
|
-
...baseCtx,
|
|
501
|
-
conn: undefined as unknown as ToolContext["conn"],
|
|
502
|
-
})
|
|
503
|
-
: await withDb(baseCtx.dbPath, (conn) => {
|
|
504
|
-
const ctx: ToolContext = { ...baseCtx, conn };
|
|
505
|
-
return tool.execute(parsed.data, ctx);
|
|
506
|
-
});
|
|
502
|
+
const ctx: ToolContext = baseCtx;
|
|
503
|
+
const result = await tool.execute(parsed.data, ctx);
|
|
507
504
|
const isError =
|
|
508
505
|
typeof result === "object" && result !== null && "is_error" in result
|
|
509
506
|
? (result as { is_error: boolean }).is_error
|
package/src/chat/session.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { MessageStream } from "@anthropic-ai/sdk/lib/MessageStream";
|
|
2
2
|
import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";
|
|
3
|
+
import type { MembotClient } from "membot";
|
|
3
4
|
import { loadConfig } from "../config/loader.ts";
|
|
4
5
|
import type { BotholomewConfig } from "../config/schemas.ts";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { migrate } from "../db/schema.ts";
|
|
8
|
-
import { createMcpxClient } from "../mcpx/client.ts";
|
|
6
|
+
import { createMcpxClient, resolveMcpxDir } from "../mcpx/client.ts";
|
|
7
|
+
import { openMembot, resolveMembotDir } from "../mem/client.ts";
|
|
9
8
|
import { loadSkills } from "../skills/loader.ts";
|
|
10
9
|
import type { SkillDefinition } from "../skills/parser.ts";
|
|
11
10
|
import {
|
|
@@ -20,7 +19,7 @@ import { generateThreadTitle } from "../utils/title.ts";
|
|
|
20
19
|
import { type ChatTurnCallbacks, runChatTurn } from "./agent.ts";
|
|
21
20
|
|
|
22
21
|
export interface ChatSession {
|
|
23
|
-
|
|
22
|
+
mem: MembotClient;
|
|
24
23
|
threadId: string;
|
|
25
24
|
projectDir: string;
|
|
26
25
|
config: Required<BotholomewConfig>;
|
|
@@ -61,8 +60,8 @@ export async function startChatSession(
|
|
|
61
60
|
);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
const
|
|
65
|
-
await
|
|
63
|
+
const mem = openMembot(resolveMembotDir(projectDir, config));
|
|
64
|
+
await mem.connect();
|
|
66
65
|
await ensureThreadsDir(projectDir);
|
|
67
66
|
|
|
68
67
|
let threadId: string;
|
|
@@ -102,15 +101,16 @@ export async function startChatSession(
|
|
|
102
101
|
);
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
const mcpxClient = await createMcpxClient(projectDir);
|
|
104
|
+
const mcpxClient = await createMcpxClient(resolveMcpxDir(projectDir, config));
|
|
106
105
|
const skills = await loadSkills(projectDir);
|
|
107
106
|
|
|
108
107
|
const cleanup = async () => {
|
|
109
108
|
await mcpxClient?.close();
|
|
109
|
+
await mem.close();
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
return {
|
|
113
|
-
|
|
113
|
+
mem,
|
|
114
114
|
threadId,
|
|
115
115
|
projectDir,
|
|
116
116
|
config,
|
|
@@ -158,7 +158,7 @@ export async function sendMessage(
|
|
|
158
158
|
messages: session.messages,
|
|
159
159
|
projectDir: session.projectDir,
|
|
160
160
|
config: session.config,
|
|
161
|
-
|
|
161
|
+
mem: session.mem,
|
|
162
162
|
threadId: session.threadId,
|
|
163
163
|
mcpxClient: session.mcpxClient,
|
|
164
164
|
callbacks,
|
package/src/cli.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { registerCapabilitiesCommand } from "./commands/capabilities.ts";
|
|
|
6
6
|
import { registerChatCommand } from "./commands/chat.ts";
|
|
7
7
|
import { registerCheckUpdateCommand } from "./commands/check-update.ts";
|
|
8
8
|
import { registerContextCommand } from "./commands/context.ts";
|
|
9
|
-
import { registerDbCommand } from "./commands/db.ts";
|
|
10
9
|
import { registerInitCommand } from "./commands/init.ts";
|
|
11
10
|
import { registerMcpxCommand } from "./commands/mcpx.ts";
|
|
12
11
|
import { registerNukeCommand } from "./commands/nuke.ts";
|
|
@@ -42,7 +41,6 @@ registerThreadCommand(program);
|
|
|
42
41
|
registerScheduleCommand(program);
|
|
43
42
|
registerChatCommand(program);
|
|
44
43
|
registerContextCommand(program);
|
|
45
|
-
registerDbCommand(program);
|
|
46
44
|
registerCapabilitiesCommand(program);
|
|
47
45
|
registerPromptsCommand(program);
|
|
48
46
|
registerMcpxCommand(program);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import { createSpinner } from "nanospinner";
|
|
3
3
|
import { loadConfig } from "../config/loader.ts";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { createMcpxClient, resolveMcpxDir } from "../mcpx/client.ts";
|
|
5
|
+
import { writeCapabilitiesFile } from "../prompts/capabilities.ts";
|
|
6
|
+
import { registerAllTools } from "../tools/registry.ts";
|
|
7
7
|
|
|
8
8
|
export function registerCapabilitiesCommand(program: Command) {
|
|
9
9
|
program
|
|
@@ -12,35 +12,37 @@ export function registerCapabilitiesCommand(program: Command) {
|
|
|
12
12
|
"Regenerate prompts/capabilities.md by scanning built-in tools and MCPX tools",
|
|
13
13
|
)
|
|
14
14
|
.option("--no-mcp", "Skip MCPX tool enumeration (built-in tools only)")
|
|
15
|
-
.action((opts: { mcp?: boolean }) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
15
|
+
.action(async (opts: { mcp?: boolean }) => {
|
|
16
|
+
const dir = program.opts().dir as string;
|
|
17
|
+
const includeMcp = opts.mcp !== false;
|
|
18
|
+
registerAllTools();
|
|
19
|
+
const spinner = createSpinner("Loading config").start();
|
|
20
|
+
const config = await loadConfig(dir);
|
|
21
|
+
spinner.update({ text: "Connecting to MCPX servers" });
|
|
22
|
+
const mcpxClient = includeMcp
|
|
23
|
+
? await createMcpxClient(resolveMcpxDir(dir, config))
|
|
24
|
+
: null;
|
|
25
|
+
try {
|
|
26
|
+
const result = await writeCapabilitiesFile(
|
|
27
|
+
dir,
|
|
28
|
+
mcpxClient,
|
|
29
|
+
config,
|
|
30
|
+
(phase) => spinner.update({ text: phase }),
|
|
31
|
+
);
|
|
32
|
+
const bits = [
|
|
33
|
+
`${result.counts.internal} built-in`,
|
|
34
|
+
`${result.counts.mcp} MCPX`,
|
|
35
|
+
];
|
|
36
|
+
if (!includeMcp) bits.push("MCPX skipped");
|
|
37
|
+
spinner.success({
|
|
38
|
+
text: `Wrote ${result.path} (${bits.join(", ")})`,
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
spinner.error({ text: `Failed: ${(err as Error).message}` });
|
|
42
42
|
await mcpxClient?.close();
|
|
43
|
-
process.exit(
|
|
44
|
-
}
|
|
45
|
-
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
await mcpxClient?.close();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
});
|
|
46
48
|
}
|