botholomew 0.7.1 → 0.7.3
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 +1 -0
- package/package.json +1 -1
- package/src/chat/agent.ts +70 -4
- package/src/chat/session.ts +2 -10
- package/src/cli.ts +2 -0
- package/src/commands/nuke.ts +149 -0
- package/src/commands/skill.ts +62 -1
- package/src/daemon/prompt.ts +16 -6
- package/src/db/context.ts +11 -0
- package/src/db/daemon-state.ts +6 -0
- package/src/db/schedules.ts +5 -0
- package/src/db/tasks.ts +5 -0
- package/src/db/threads.ts +11 -0
- package/src/tools/dir/tree.ts +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,7 @@ Everything the agent can touch is here. No surprises.
|
|
|
125
125
|
| `botholomew skill list\|show\|create` | Manage slash-command skills |
|
|
126
126
|
| `botholomew file\|dir\|search ...` | Direct access to the agent's virtual filesystem |
|
|
127
127
|
| `botholomew thread list\|view` | Browse the agent's interaction history |
|
|
128
|
+
| `botholomew nuke context\|tasks\|schedules\|threads\|all` | Bulk-erase sections of the database |
|
|
128
129
|
| `botholomew upgrade` | Self-update |
|
|
129
130
|
|
|
130
131
|
---
|
package/package.json
CHANGED
package/src/chat/agent.ts
CHANGED
|
@@ -5,10 +5,16 @@ import type {
|
|
|
5
5
|
ToolUseBlock,
|
|
6
6
|
} from "@anthropic-ai/sdk/resources/messages";
|
|
7
7
|
import type { BotholomewConfig } from "../config/schemas.ts";
|
|
8
|
+
import { embedSingle } from "../context/embedder.ts";
|
|
8
9
|
import { fitToContextWindow, getMaxInputTokens } from "../daemon/context.ts";
|
|
9
10
|
import { maybeStoreResult } from "../daemon/large-results.ts";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
buildMetaHeader,
|
|
13
|
+
extractKeywords,
|
|
14
|
+
loadPersistentContext,
|
|
15
|
+
} from "../daemon/prompt.ts";
|
|
11
16
|
import type { DbConnection } from "../db/connection.ts";
|
|
17
|
+
import { hybridSearch } from "../db/embeddings.ts";
|
|
12
18
|
import { logInteraction } from "../db/threads.ts";
|
|
13
19
|
import { registerAllTools } from "../tools/registry.ts";
|
|
14
20
|
import {
|
|
@@ -17,6 +23,7 @@ import {
|
|
|
17
23
|
type ToolContext,
|
|
18
24
|
toAnthropicTool,
|
|
19
25
|
} from "../tools/tool.ts";
|
|
26
|
+
import { logger } from "../utils/logger.ts";
|
|
20
27
|
|
|
21
28
|
registerAllTools();
|
|
22
29
|
|
|
@@ -49,11 +56,44 @@ export function getChatTools() {
|
|
|
49
56
|
|
|
50
57
|
export async function buildChatSystemPrompt(
|
|
51
58
|
projectDir: string,
|
|
59
|
+
options?: {
|
|
60
|
+
keywordSource?: string;
|
|
61
|
+
conn?: DbConnection;
|
|
62
|
+
config?: Required<BotholomewConfig>;
|
|
63
|
+
},
|
|
52
64
|
): Promise<string> {
|
|
53
65
|
const parts: string[] = [];
|
|
54
66
|
|
|
55
67
|
parts.push(...buildMetaHeader(projectDir));
|
|
56
|
-
|
|
68
|
+
|
|
69
|
+
const keywordSource = options?.keywordSource?.trim();
|
|
70
|
+
const taskKeywords = keywordSource ? extractKeywords(keywordSource) : null;
|
|
71
|
+
|
|
72
|
+
parts.push(...(await loadPersistentContext(projectDir, taskKeywords)));
|
|
73
|
+
|
|
74
|
+
// Relevant context from embeddings search
|
|
75
|
+
const conn = options?.conn;
|
|
76
|
+
const config = options?.config;
|
|
77
|
+
if (conn && config?.openai_api_key && keywordSource) {
|
|
78
|
+
try {
|
|
79
|
+
const queryVec = await embedSingle(keywordSource, config);
|
|
80
|
+
const results = await hybridSearch(conn, keywordSource, queryVec, 5);
|
|
81
|
+
|
|
82
|
+
if (results.length > 0) {
|
|
83
|
+
parts.push("## Relevant Context");
|
|
84
|
+
for (const r of results) {
|
|
85
|
+
const path = r.source_path || r.context_item_id;
|
|
86
|
+
parts.push(`### ${r.title} (${path})`);
|
|
87
|
+
if (r.chunk_content) {
|
|
88
|
+
parts.push(r.chunk_content.slice(0, 1000));
|
|
89
|
+
}
|
|
90
|
+
parts.push("");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
logger.debug(`Failed to load contextual embeddings: ${err}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
57
97
|
|
|
58
98
|
parts.push("## Instructions");
|
|
59
99
|
parts.push(
|
|
@@ -95,6 +135,20 @@ export interface ChatTurnCallbacks {
|
|
|
95
135
|
) => void;
|
|
96
136
|
}
|
|
97
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Walk messages backward to find the most recent human-authored user message.
|
|
140
|
+
* After tool turns, `messages[messages.length - 1]` is a user entry whose
|
|
141
|
+
* content is a `ToolResultBlockParam[]` — we want the string content from the
|
|
142
|
+
* actual user, not tool output, as the keyword source.
|
|
143
|
+
*/
|
|
144
|
+
function findLastUserText(messages: MessageParam[]): string {
|
|
145
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
146
|
+
const m = messages[i];
|
|
147
|
+
if (m?.role === "user" && typeof m.content === "string") return m.content;
|
|
148
|
+
}
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
|
|
98
152
|
/**
|
|
99
153
|
* Run a single chat turn: stream the assistant response, execute any tool calls,
|
|
100
154
|
* and loop until the model produces end_turn with no tool calls.
|
|
@@ -102,14 +156,14 @@ export interface ChatTurnCallbacks {
|
|
|
102
156
|
*/
|
|
103
157
|
export async function runChatTurn(input: {
|
|
104
158
|
messages: MessageParam[];
|
|
105
|
-
|
|
159
|
+
projectDir: string;
|
|
106
160
|
config: Required<BotholomewConfig>;
|
|
107
161
|
conn: DbConnection;
|
|
108
162
|
threadId: string;
|
|
109
163
|
toolCtx: ToolContext;
|
|
110
164
|
callbacks: ChatTurnCallbacks;
|
|
111
165
|
}): Promise<void> {
|
|
112
|
-
const { messages,
|
|
166
|
+
const { messages, projectDir, config, conn, threadId, toolCtx, callbacks } =
|
|
113
167
|
input;
|
|
114
168
|
|
|
115
169
|
const client = new Anthropic({
|
|
@@ -126,6 +180,18 @@ export async function runChatTurn(input: {
|
|
|
126
180
|
for (let turn = 0; !maxTurns || turn < maxTurns; turn++) {
|
|
127
181
|
const startTime = Date.now();
|
|
128
182
|
|
|
183
|
+
// Rebuild the system prompt every iteration so that:
|
|
184
|
+
// (1) `loading: contextual` files get matched against the latest user
|
|
185
|
+
// message, and
|
|
186
|
+
// (2) any update_beliefs / update_goals tool call in the previous
|
|
187
|
+
// iteration is reflected in the next LLM call.
|
|
188
|
+
const keywordSource = findLastUserText(messages);
|
|
189
|
+
const systemPrompt = await buildChatSystemPrompt(projectDir, {
|
|
190
|
+
keywordSource,
|
|
191
|
+
conn,
|
|
192
|
+
config,
|
|
193
|
+
});
|
|
194
|
+
|
|
129
195
|
fitToContextWindow(messages, systemPrompt, maxInputTokens);
|
|
130
196
|
const stream = client.messages.stream({
|
|
131
197
|
model: config.model,
|
package/src/chat/session.ts
CHANGED
|
@@ -17,11 +17,7 @@ import { loadSkills } from "../skills/loader.ts";
|
|
|
17
17
|
import type { SkillDefinition } from "../skills/parser.ts";
|
|
18
18
|
import type { ToolContext } from "../tools/tool.ts";
|
|
19
19
|
import { generateThreadTitle } from "../utils/title.ts";
|
|
20
|
-
import {
|
|
21
|
-
buildChatSystemPrompt,
|
|
22
|
-
type ChatTurnCallbacks,
|
|
23
|
-
runChatTurn,
|
|
24
|
-
} from "./agent.ts";
|
|
20
|
+
import { type ChatTurnCallbacks, runChatTurn } from "./agent.ts";
|
|
25
21
|
|
|
26
22
|
export interface ChatSession {
|
|
27
23
|
conn: DbConnection;
|
|
@@ -29,7 +25,6 @@ export interface ChatSession {
|
|
|
29
25
|
projectDir: string;
|
|
30
26
|
config: Required<BotholomewConfig>;
|
|
31
27
|
messages: MessageParam[];
|
|
32
|
-
systemPrompt: string;
|
|
33
28
|
toolCtx: ToolContext;
|
|
34
29
|
skills: Map<string, SkillDefinition>;
|
|
35
30
|
cleanup: () => Promise<void>;
|
|
@@ -83,8 +78,6 @@ export async function startChatSession(
|
|
|
83
78
|
threadId = await createThread(conn, "chat_session", undefined, "New chat");
|
|
84
79
|
}
|
|
85
80
|
|
|
86
|
-
const systemPrompt = await buildChatSystemPrompt(projectDir);
|
|
87
|
-
|
|
88
81
|
const mcpxClient = await createMcpxClient(projectDir);
|
|
89
82
|
const skills = await loadSkills(projectDir);
|
|
90
83
|
|
|
@@ -105,7 +98,6 @@ export async function startChatSession(
|
|
|
105
98
|
projectDir,
|
|
106
99
|
config,
|
|
107
100
|
messages,
|
|
108
|
-
systemPrompt,
|
|
109
101
|
toolCtx,
|
|
110
102
|
skills,
|
|
111
103
|
cleanup,
|
|
@@ -138,7 +130,7 @@ export async function sendMessage(
|
|
|
138
130
|
|
|
139
131
|
await runChatTurn({
|
|
140
132
|
messages: session.messages,
|
|
141
|
-
|
|
133
|
+
projectDir: session.projectDir,
|
|
142
134
|
config: session.config,
|
|
143
135
|
conn: session.conn,
|
|
144
136
|
threadId: session.threadId,
|
package/src/cli.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { registerContextCommand } from "./commands/context.ts";
|
|
|
8
8
|
import { registerDaemonCommand } from "./commands/daemon.ts";
|
|
9
9
|
import { registerInitCommand } from "./commands/init.ts";
|
|
10
10
|
import { registerMcpxCommand } from "./commands/mcpx.ts";
|
|
11
|
+
import { registerNukeCommand } from "./commands/nuke.ts";
|
|
11
12
|
import { registerPrepareCommand } from "./commands/prepare.ts";
|
|
12
13
|
import { registerScheduleCommand } from "./commands/schedule.ts";
|
|
13
14
|
import { registerSkillCommand } from "./commands/skill.ts";
|
|
@@ -40,6 +41,7 @@ registerChatCommand(program);
|
|
|
40
41
|
registerContextCommand(program);
|
|
41
42
|
registerMcpxCommand(program);
|
|
42
43
|
registerSkillCommand(program);
|
|
44
|
+
registerNukeCommand(program);
|
|
43
45
|
registerPrepareCommand(program);
|
|
44
46
|
registerCheckUpdateCommand(program);
|
|
45
47
|
registerUpgradeCommand(program);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import type { DbConnection } from "../db/connection.ts";
|
|
4
|
+
import { deleteAllContextItems } from "../db/context.ts";
|
|
5
|
+
import { deleteAllDaemonState } from "../db/daemon-state.ts";
|
|
6
|
+
import { deleteAllSchedules } from "../db/schedules.ts";
|
|
7
|
+
import { deleteAllTasks } from "../db/tasks.ts";
|
|
8
|
+
import { deleteAllThreads } from "../db/threads.ts";
|
|
9
|
+
import { logger } from "../utils/logger.ts";
|
|
10
|
+
import { getDaemonStatus } from "../utils/pid.ts";
|
|
11
|
+
import { withDb } from "./with-db.ts";
|
|
12
|
+
|
|
13
|
+
type NukeScope = "context" | "tasks" | "schedules" | "threads" | "all";
|
|
14
|
+
|
|
15
|
+
const TABLES_BY_SCOPE: Record<NukeScope, string[]> = {
|
|
16
|
+
context: ["context_items", "embeddings"],
|
|
17
|
+
tasks: ["tasks"],
|
|
18
|
+
schedules: ["schedules"],
|
|
19
|
+
threads: ["threads", "interactions"],
|
|
20
|
+
all: [
|
|
21
|
+
"context_items",
|
|
22
|
+
"embeddings",
|
|
23
|
+
"tasks",
|
|
24
|
+
"schedules",
|
|
25
|
+
"threads",
|
|
26
|
+
"interactions",
|
|
27
|
+
"daemon_state",
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
async function countRows(conn: DbConnection, table: string): Promise<number> {
|
|
32
|
+
const row = await conn.queryGet<{ cnt: number }>(
|
|
33
|
+
`SELECT COUNT(*) AS cnt FROM ${table}`,
|
|
34
|
+
);
|
|
35
|
+
return row ? Number(row.cnt) : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printDryRun(scope: NukeScope, counts: Record<string, number>) {
|
|
39
|
+
console.log(ansis.red.bold(`Nuke scope: ${scope}`));
|
|
40
|
+
console.log("Would delete:");
|
|
41
|
+
const nameWidth = Math.max(...Object.keys(counts).map((k) => k.length));
|
|
42
|
+
for (const [table, count] of Object.entries(counts)) {
|
|
43
|
+
const padded = table.padEnd(nameWidth + 2);
|
|
44
|
+
console.log(` ${padded}${ansis.dim(`${count} rows`)}`);
|
|
45
|
+
}
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(
|
|
48
|
+
ansis.yellow("Re-run with --yes to confirm. This cannot be undone."),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function ensureDaemonStopped(dir: string): Promise<boolean> {
|
|
53
|
+
const status = await getDaemonStatus(dir);
|
|
54
|
+
if (status) {
|
|
55
|
+
logger.error(
|
|
56
|
+
`Daemon is running (PID ${status.pid}). Stop it first: botholomew daemon stop`,
|
|
57
|
+
);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runNuke(conn: DbConnection, scope: NukeScope): Promise<void> {
|
|
64
|
+
// Not wrapped in a transaction: DuckDB's FK index checks on DELETE FROM
|
|
65
|
+
// threads inside a transaction see stale interactions rows even after
|
|
66
|
+
// DELETE FROM interactions ran in the same transaction. Each helper is
|
|
67
|
+
// already a small sequence of statements, so auto-commit is fine for a
|
|
68
|
+
// destructive dev-time tool.
|
|
69
|
+
if (scope === "context" || scope === "all") {
|
|
70
|
+
const { contextItems, embeddings } = await deleteAllContextItems(conn);
|
|
71
|
+
logger.success(
|
|
72
|
+
`Deleted ${contextItems} context_items, ${embeddings} embeddings`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (scope === "tasks" || scope === "all") {
|
|
76
|
+
const n = await deleteAllTasks(conn);
|
|
77
|
+
logger.success(`Deleted ${n} tasks`);
|
|
78
|
+
}
|
|
79
|
+
if (scope === "schedules" || scope === "all") {
|
|
80
|
+
const n = await deleteAllSchedules(conn);
|
|
81
|
+
logger.success(`Deleted ${n} schedules`);
|
|
82
|
+
}
|
|
83
|
+
if (scope === "threads" || scope === "all") {
|
|
84
|
+
const { threads, interactions } = await deleteAllThreads(conn);
|
|
85
|
+
logger.success(`Deleted ${threads} threads, ${interactions} interactions`);
|
|
86
|
+
}
|
|
87
|
+
if (scope === "all") {
|
|
88
|
+
const n = await deleteAllDaemonState(conn);
|
|
89
|
+
logger.success(`Deleted ${n} daemon_state entries`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function registerScope(
|
|
94
|
+
program: Command,
|
|
95
|
+
parent: Command,
|
|
96
|
+
scope: NukeScope,
|
|
97
|
+
description: string,
|
|
98
|
+
) {
|
|
99
|
+
parent
|
|
100
|
+
.command(scope)
|
|
101
|
+
.description(description)
|
|
102
|
+
.option("-y, --yes", "confirm the deletion (required)")
|
|
103
|
+
.action((opts) =>
|
|
104
|
+
withDb(program, async (conn, dir) => {
|
|
105
|
+
if (!(await ensureDaemonStopped(dir))) {
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const tables = TABLES_BY_SCOPE[scope];
|
|
109
|
+
const counts: Record<string, number> = {};
|
|
110
|
+
for (const t of tables) {
|
|
111
|
+
counts[t] = await countRows(conn, t);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!opts.yes) {
|
|
115
|
+
printDryRun(scope, counts);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await runNuke(conn, scope);
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function registerNukeCommand(program: Command) {
|
|
125
|
+
const nuke = program
|
|
126
|
+
.command("nuke")
|
|
127
|
+
.description("Bulk-erase sections of the database");
|
|
128
|
+
|
|
129
|
+
registerScope(
|
|
130
|
+
program,
|
|
131
|
+
nuke,
|
|
132
|
+
"context",
|
|
133
|
+
"Erase all context_items and embeddings",
|
|
134
|
+
);
|
|
135
|
+
registerScope(program, nuke, "tasks", "Erase all tasks");
|
|
136
|
+
registerScope(program, nuke, "schedules", "Erase all schedules");
|
|
137
|
+
registerScope(
|
|
138
|
+
program,
|
|
139
|
+
nuke,
|
|
140
|
+
"threads",
|
|
141
|
+
"Erase all threads and interactions (daemon + chat history)",
|
|
142
|
+
);
|
|
143
|
+
registerScope(
|
|
144
|
+
program,
|
|
145
|
+
nuke,
|
|
146
|
+
"all",
|
|
147
|
+
"Erase everything in the database (preserves schema, skills, and on-disk soul/beliefs/goals)",
|
|
148
|
+
);
|
|
149
|
+
}
|
package/src/commands/skill.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
1
|
+
import { join, relative } from "node:path";
|
|
2
2
|
import ansis from "ansis";
|
|
3
3
|
import type { Command } from "commander";
|
|
4
4
|
import { getSkillsDir } from "../constants.ts";
|
|
@@ -24,6 +24,67 @@ export function registerSkillCommand(program: Command) {
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
skill
|
|
28
|
+
.command("list")
|
|
29
|
+
.description("List all skills loaded from .botholomew/skills/")
|
|
30
|
+
.action(async () => {
|
|
31
|
+
const dir = program.opts().dir;
|
|
32
|
+
const skills = await loadSkills(dir);
|
|
33
|
+
|
|
34
|
+
if (skills.size === 0) {
|
|
35
|
+
logger.dim("No skill files found.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sorted = [...skills.values()].sort((a, b) =>
|
|
40
|
+
a.name.localeCompare(b.name),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const header = `${ansis.bold("Name".padEnd(20))} ${ansis.bold("Description".padEnd(40))} ${ansis.bold("Args".padEnd(20))} ${ansis.bold("Path")}`;
|
|
44
|
+
console.log(header);
|
|
45
|
+
console.log("-".repeat(header.length));
|
|
46
|
+
|
|
47
|
+
for (const s of sorted) {
|
|
48
|
+
const name = s.name.padEnd(20);
|
|
49
|
+
const desc = s.description
|
|
50
|
+
? s.description.slice(0, 39).padEnd(40)
|
|
51
|
+
: ansis.dim("(no description)".padEnd(40));
|
|
52
|
+
const args =
|
|
53
|
+
s.arguments.length > 0
|
|
54
|
+
? s.arguments
|
|
55
|
+
.map((a) => a.name)
|
|
56
|
+
.join(",")
|
|
57
|
+
.slice(0, 19)
|
|
58
|
+
.padEnd(20)
|
|
59
|
+
: ansis.dim("none".padEnd(20));
|
|
60
|
+
const path = relative(dir, s.filePath);
|
|
61
|
+
console.log(`${name} ${desc} ${args} ${path}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`\n${ansis.dim(`${sorted.length} skill(s)`)}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
skill
|
|
68
|
+
.command("show <name>")
|
|
69
|
+
.description("Print the raw contents of a skill file")
|
|
70
|
+
.action(async (name: string) => {
|
|
71
|
+
const dir = program.opts().dir;
|
|
72
|
+
const skills = await loadSkills(dir);
|
|
73
|
+
const s = skills.get(name.toLowerCase());
|
|
74
|
+
|
|
75
|
+
if (!s) {
|
|
76
|
+
logger.error(`Skill not found: ${name}`);
|
|
77
|
+
if (skills.size > 0) {
|
|
78
|
+
const available = [...skills.keys()].sort().join(", ");
|
|
79
|
+
console.error(ansis.dim(`Available: ${available}`));
|
|
80
|
+
}
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const raw = await Bun.file(s.filePath).text();
|
|
85
|
+
process.stdout.write(raw);
|
|
86
|
+
});
|
|
87
|
+
|
|
27
88
|
skill
|
|
28
89
|
.command("create <name>")
|
|
29
90
|
.description("Create a new skill file from a template")
|
package/src/daemon/prompt.ts
CHANGED
|
@@ -13,6 +13,21 @@ const pkg = await Bun.file(
|
|
|
13
13
|
new URL("../../package.json", import.meta.url),
|
|
14
14
|
).json();
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Extract keyword set from free-form text: lowercase, split on whitespace,
|
|
18
|
+
* keep words longer than 3 chars. Used to match `loading: contextual` files
|
|
19
|
+
* against the agent's current intent (task text for the daemon, latest user
|
|
20
|
+
* message for the chat).
|
|
21
|
+
*/
|
|
22
|
+
export function extractKeywords(text: string): Set<string> {
|
|
23
|
+
return new Set(
|
|
24
|
+
text
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.split(/\s+/)
|
|
27
|
+
.filter((w) => w.length > 3),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
/**
|
|
17
32
|
* Load persistent context files from .botholomew/ directory.
|
|
18
33
|
* Returns an array of formatted string sections for "always" loaded files.
|
|
@@ -85,12 +100,7 @@ export async function buildSystemPrompt(
|
|
|
85
100
|
|
|
86
101
|
// Build keyword set from task for contextual loading
|
|
87
102
|
const taskKeywords = task
|
|
88
|
-
?
|
|
89
|
-
`${task.name} ${task.description}`
|
|
90
|
-
.toLowerCase()
|
|
91
|
-
.split(/\s+/)
|
|
92
|
-
.filter((w) => w.length > 3),
|
|
93
|
-
)
|
|
103
|
+
? extractKeywords(`${task.name} ${task.description}`)
|
|
94
104
|
: null;
|
|
95
105
|
|
|
96
106
|
// Load context files from .botholomew/
|
package/src/db/context.ts
CHANGED
|
@@ -428,6 +428,17 @@ export async function deleteContextItemByPath(
|
|
|
428
428
|
return deleteContextItem(db, item.id);
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
export async function deleteAllContextItems(
|
|
432
|
+
db: DbConnection,
|
|
433
|
+
): Promise<{ contextItems: number; embeddings: number }> {
|
|
434
|
+
const embeddings = await db.queryRun("DELETE FROM embeddings");
|
|
435
|
+
const contextItems = await db.queryRun("DELETE FROM context_items");
|
|
436
|
+
return {
|
|
437
|
+
contextItems: contextItems.changes,
|
|
438
|
+
embeddings: embeddings.changes,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
431
442
|
export async function deleteContextItemsByPrefix(
|
|
432
443
|
db: DbConnection,
|
|
433
444
|
prefix: string,
|
package/src/db/schedules.ts
CHANGED
|
@@ -127,6 +127,11 @@ export async function deleteSchedule(
|
|
|
127
127
|
return result.changes > 0;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export async function deleteAllSchedules(db: DbConnection): Promise<number> {
|
|
131
|
+
const result = await db.queryRun("DELETE FROM schedules");
|
|
132
|
+
return result.changes;
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
export async function markScheduleRun(
|
|
131
136
|
db: DbConnection,
|
|
132
137
|
id: string,
|
package/src/db/tasks.ts
CHANGED
|
@@ -229,6 +229,11 @@ export async function deleteTask(
|
|
|
229
229
|
return result.changes > 0;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
export async function deleteAllTasks(db: DbConnection): Promise<number> {
|
|
233
|
+
const result = await db.queryRun("DELETE FROM tasks");
|
|
234
|
+
return result.changes;
|
|
235
|
+
}
|
|
236
|
+
|
|
232
237
|
export async function resetTask(
|
|
233
238
|
db: DbConnection,
|
|
234
239
|
id: string,
|
package/src/db/threads.ts
CHANGED
|
@@ -205,6 +205,17 @@ export async function deleteThread(
|
|
|
205
205
|
return result.changes > 0;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
export async function deleteAllThreads(
|
|
209
|
+
db: DbConnection,
|
|
210
|
+
): Promise<{ threads: number; interactions: number }> {
|
|
211
|
+
const interactions = await db.queryRun("DELETE FROM interactions");
|
|
212
|
+
const threads = await db.queryRun("DELETE FROM threads");
|
|
213
|
+
return {
|
|
214
|
+
threads: threads.changes,
|
|
215
|
+
interactions: interactions.changes,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
export async function getInteractionsAfter(
|
|
209
220
|
db: DbConnection,
|
|
210
221
|
threadId: string,
|
package/src/tools/dir/tree.ts
CHANGED
|
@@ -66,7 +66,7 @@ type TreeEntry = DirNode | FileNode;
|
|
|
66
66
|
export const contextTreeTool = {
|
|
67
67
|
name: "context_tree",
|
|
68
68
|
description:
|
|
69
|
-
"
|
|
69
|
+
"Explore your context filesystem with a bird's-eye view — shows many paths across nested directories in one call. Reach for this first when you need to discover what content exists before reading a specific file (context_read) or running a keyword search (context_search). Returns a markdown-style tree; tune max_depth and items_per_dir to bound output, or pass a deeper path to drill into a subtree.",
|
|
70
70
|
group: "context",
|
|
71
71
|
inputSchema,
|
|
72
72
|
outputSchema,
|