zubo 0.1.21 → 0.1.22
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/migrations/024_personal_features.sql +96 -0
- package/package.json +1 -1
- package/site/docs/index.html +11 -0
- package/site/docs/skills.html +107 -0
- package/site/index.html +9 -1
- package/src/agent/context.ts +3 -3
- package/src/agent/loop.ts +1 -1
- package/src/agent/prompts.ts +42 -1
- package/src/channels/dashboard.html.ts +2 -2
- package/src/channels/webchat.ts +51 -27
- package/src/start.ts +12 -0
- package/src/tools/builtin/follow-ups.ts +189 -0
- package/src/tools/builtin/notes.ts +207 -0
- package/src/tools/builtin/preferences.ts +173 -0
- package/src/tools/builtin/todos.ts +270 -0
- package/src/tools/builtin/topics.ts +166 -0
- package/src/tools/permissions.ts +7 -0
- package/tests/agent/session.test.ts +43 -45
- package/tests/mcp-registry.test.ts +32 -35
- package/tests/personal-features.test.ts +1251 -0
- package/tests/skill-registry.test.ts +1 -7
- package/tests/db/export.test.ts +0 -219
- package/tests/session.test.ts +0 -58
- package/tests/tools/executor.test.ts +0 -150
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "bun:test";
|
|
1
|
+
import { describe, test, expect, beforeEach, afterAll } from "bun:test";
|
|
2
2
|
import { mock } from "bun:test";
|
|
3
|
+
import { writeFileSync, mkdtempSync, rmSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Tests for src/tools/mcp-registry.ts
|
|
6
9
|
*
|
|
7
|
-
* We mock `fetch` to avoid real network calls and mock the
|
|
8
|
-
* client modules
|
|
10
|
+
* We mock `fetch` to avoid real network calls and mock the config paths +
|
|
11
|
+
* MCP client modules. We do NOT mock `fs` — instead we use a real temp config file.
|
|
9
12
|
*/
|
|
10
13
|
|
|
14
|
+
// Create a temp directory for the config file
|
|
15
|
+
const testTmpDir = mkdtempSync(join(tmpdir(), "mcp-registry-test-"));
|
|
16
|
+
const testConfigPath = join(testTmpDir, "config.json");
|
|
17
|
+
|
|
11
18
|
// Track fetch calls for assertions
|
|
12
19
|
let fetchCalls: { url: string; options?: any }[] = [];
|
|
13
20
|
let fetchResponse: { ok: boolean; status: number; statusText: string; json: () => any } = {
|
|
@@ -17,10 +24,6 @@ let fetchResponse: { ok: boolean; status: number; statusText: string; json: () =
|
|
|
17
24
|
json: () => [],
|
|
18
25
|
};
|
|
19
26
|
|
|
20
|
-
// Track file system operations
|
|
21
|
-
let writtenConfig: string | null = null;
|
|
22
|
-
let mockConfigData: any = { mcp: { servers: [] } };
|
|
23
|
-
|
|
24
27
|
// Track MCP client operations
|
|
25
28
|
let connectedServers: string[] = [];
|
|
26
29
|
let disconnectedServers: string[] = [];
|
|
@@ -33,7 +36,7 @@ globalThis.fetch = async (input: any, init?: any) => {
|
|
|
33
36
|
return fetchResponse as any;
|
|
34
37
|
};
|
|
35
38
|
|
|
36
|
-
// Mock dependencies
|
|
39
|
+
// Mock dependencies — but NOT `fs`!
|
|
37
40
|
mock.module("../src/util/logger", () => ({
|
|
38
41
|
logger: {
|
|
39
42
|
debug: () => {},
|
|
@@ -45,19 +48,8 @@ mock.module("../src/util/logger", () => ({
|
|
|
45
48
|
|
|
46
49
|
mock.module("../src/config/paths", () => ({
|
|
47
50
|
paths: {
|
|
48
|
-
config:
|
|
49
|
-
},
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
mock.module("fs", () => ({
|
|
53
|
-
readFileSync: (path: string) => JSON.stringify(mockConfigData),
|
|
54
|
-
writeFileSync: (path: string, content: string) => {
|
|
55
|
-
writtenConfig = content;
|
|
51
|
+
config: testConfigPath,
|
|
56
52
|
},
|
|
57
|
-
existsSync: () => true,
|
|
58
|
-
readdirSync: () => [],
|
|
59
|
-
mkdirSync: () => {},
|
|
60
|
-
unlinkSync: () => {},
|
|
61
53
|
}));
|
|
62
54
|
|
|
63
55
|
mock.module("../src/tools/mcp-client", () => ({
|
|
@@ -79,13 +71,21 @@ const {
|
|
|
79
71
|
uninstallMcpServer,
|
|
80
72
|
} = await import("../src/tools/mcp-registry");
|
|
81
73
|
|
|
74
|
+
afterAll(() => {
|
|
75
|
+
globalThis.fetch = originalFetch;
|
|
76
|
+
rmSync(testTmpDir, { recursive: true, force: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Helper to write config file for install/uninstall tests
|
|
80
|
+
function writeTestConfig(data: any) {
|
|
81
|
+
writeFileSync(testConfigPath, JSON.stringify(data, null, 2));
|
|
82
|
+
}
|
|
83
|
+
|
|
82
84
|
// ─── searchRegistry ─────────────────────────────────────────────────────────
|
|
83
85
|
|
|
84
86
|
describe("mcp-registry: searchRegistry", () => {
|
|
85
87
|
beforeEach(() => {
|
|
86
88
|
fetchCalls = [];
|
|
87
|
-
// Clear the module's internal cache by importing fresh — but since we
|
|
88
|
-
// can't easily do that, we use unique queries to avoid stale cache hits.
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
test("calls registry API with encoded query", async () => {
|
|
@@ -234,8 +234,7 @@ describe("mcp-registry: installFromRegistry", () => {
|
|
|
234
234
|
beforeEach(() => {
|
|
235
235
|
fetchCalls = [];
|
|
236
236
|
connectedServers = [];
|
|
237
|
-
|
|
238
|
-
mockConfigData = { mcp: { servers: [] } };
|
|
237
|
+
writeTestConfig({ mcp: { servers: [] } });
|
|
239
238
|
});
|
|
240
239
|
|
|
241
240
|
test("installs a server from registry into config", async () => {
|
|
@@ -257,9 +256,9 @@ describe("mcp-registry: installFromRegistry", () => {
|
|
|
257
256
|
expect(result.name).toBe(uniqueName);
|
|
258
257
|
expect(result.tools).toBe(3); // from our mock getMcpStatus
|
|
259
258
|
expect(connectedServers).toContain(uniqueName);
|
|
260
|
-
expect(writtenConfig).not.toBeNull();
|
|
261
259
|
|
|
262
|
-
|
|
260
|
+
// Verify config was written to disk
|
|
261
|
+
const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
|
|
263
262
|
const server = savedConfig.mcp.servers.find((s: any) => s.name === uniqueName);
|
|
264
263
|
expect(server).toBeTruthy();
|
|
265
264
|
expect(server.command).toBe("npx");
|
|
@@ -280,11 +279,11 @@ describe("mcp-registry: installFromRegistry", () => {
|
|
|
280
279
|
|
|
281
280
|
test("replaces existing server entry with same name", async () => {
|
|
282
281
|
const uniqueName = `replace-server-${Date.now()}`;
|
|
283
|
-
|
|
282
|
+
writeTestConfig({
|
|
284
283
|
mcp: {
|
|
285
284
|
servers: [{ name: uniqueName, command: "old-cmd", args: [], enabled: true }],
|
|
286
285
|
},
|
|
287
|
-
};
|
|
286
|
+
});
|
|
288
287
|
|
|
289
288
|
fetchResponse = {
|
|
290
289
|
ok: true,
|
|
@@ -299,7 +298,7 @@ describe("mcp-registry: installFromRegistry", () => {
|
|
|
299
298
|
|
|
300
299
|
await installFromRegistry(uniqueName);
|
|
301
300
|
|
|
302
|
-
const savedConfig = JSON.parse(
|
|
301
|
+
const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
|
|
303
302
|
const servers = savedConfig.mcp.servers.filter((s: any) => s.name === uniqueName);
|
|
304
303
|
expect(servers).toHaveLength(1);
|
|
305
304
|
expect(servers[0].command).toBe("new-cmd");
|
|
@@ -311,32 +310,30 @@ describe("mcp-registry: installFromRegistry", () => {
|
|
|
311
310
|
describe("mcp-registry: uninstallMcpServer", () => {
|
|
312
311
|
beforeEach(() => {
|
|
313
312
|
disconnectedServers = [];
|
|
314
|
-
writtenConfig = null;
|
|
315
313
|
});
|
|
316
314
|
|
|
317
315
|
test("disconnects and removes server from config", async () => {
|
|
318
|
-
|
|
316
|
+
writeTestConfig({
|
|
319
317
|
mcp: {
|
|
320
318
|
servers: [
|
|
321
319
|
{ name: "keep-this", command: "cmd1" },
|
|
322
320
|
{ name: "remove-this", command: "cmd2" },
|
|
323
321
|
],
|
|
324
322
|
},
|
|
325
|
-
};
|
|
323
|
+
});
|
|
326
324
|
|
|
327
325
|
await uninstallMcpServer("remove-this");
|
|
328
326
|
|
|
329
327
|
expect(disconnectedServers).toContain("remove-this");
|
|
330
|
-
expect(writtenConfig).not.toBeNull();
|
|
331
328
|
|
|
332
|
-
const savedConfig = JSON.parse(
|
|
329
|
+
const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
|
|
333
330
|
const remaining = savedConfig.mcp.servers.map((s: any) => s.name);
|
|
334
331
|
expect(remaining).toContain("keep-this");
|
|
335
332
|
expect(remaining).not.toContain("remove-this");
|
|
336
333
|
});
|
|
337
334
|
|
|
338
335
|
test("handles server not in config gracefully", async () => {
|
|
339
|
-
|
|
336
|
+
writeTestConfig({ mcp: { servers: [] } });
|
|
340
337
|
|
|
341
338
|
await expect(uninstallMcpServer("nonexistent")).resolves.toBeUndefined();
|
|
342
339
|
expect(disconnectedServers).toContain("nonexistent");
|