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.
@@ -37,13 +37,7 @@ const MIGRATION_PATH = join(__dirname, "..", "migrations", "023_skill_registry.s
37
37
  function setupTestDb(): Database {
38
38
  const db = new Database(":memory:");
39
39
  const migrationSql = readFileSync(MIGRATION_PATH, "utf-8");
40
- const statements = migrationSql
41
- .split(";")
42
- .map((s) => s.trim())
43
- .filter((s) => s.length > 0);
44
- for (const stmt of statements) {
45
- db.exec(stmt);
46
- }
40
+ db.run(migrationSql);
47
41
  return db;
48
42
  }
49
43
 
@@ -1,219 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
- import { Database } from "bun:sqlite";
3
- import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "fs";
4
- import { join } from "path";
5
- import { tmpdir } from "os";
6
- import { exportDatabase, importDatabase } from "../../src/db/export";
7
-
8
- function createTestDb(): Database {
9
- const db = new Database(":memory:");
10
-
11
- // Create the tables that EXPORT_TABLES references
12
- db.run(`
13
- CREATE TABLE IF NOT EXISTS sessions (
14
- id TEXT PRIMARY KEY,
15
- channel TEXT NOT NULL,
16
- user_id TEXT NOT NULL,
17
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
19
- )
20
- `);
21
- db.run(`
22
- CREATE TABLE IF NOT EXISTS memory_chunks (
23
- id INTEGER PRIMARY KEY AUTOINCREMENT,
24
- source_file TEXT NOT NULL,
25
- chunk_index INTEGER NOT NULL,
26
- content TEXT NOT NULL,
27
- embedding BLOB,
28
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
29
- UNIQUE(source_file, chunk_index)
30
- )
31
- `);
32
- db.run(`
33
- CREATE TABLE IF NOT EXISTS cron_jobs (
34
- id INTEGER PRIMARY KEY AUTOINCREMENT,
35
- name TEXT NOT NULL UNIQUE,
36
- schedule TEXT NOT NULL,
37
- task TEXT NOT NULL,
38
- enabled INTEGER NOT NULL DEFAULT 1,
39
- last_run TEXT,
40
- next_run TEXT,
41
- retry_count INTEGER NOT NULL DEFAULT 0,
42
- max_retries INTEGER NOT NULL DEFAULT 3,
43
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
44
- )
45
- `);
46
- db.run(`
47
- CREATE TABLE IF NOT EXISTS cron_logs (
48
- id INTEGER PRIMARY KEY AUTOINCREMENT,
49
- job_id INTEGER NOT NULL REFERENCES cron_jobs(id),
50
- status TEXT NOT NULL,
51
- output TEXT,
52
- error TEXT,
53
- started_at TEXT NOT NULL DEFAULT (datetime('now')),
54
- finished_at TEXT
55
- )
56
- `);
57
- db.run(`
58
- CREATE TABLE IF NOT EXISTS usage (
59
- id INTEGER PRIMARY KEY AUTOINCREMENT,
60
- session_id TEXT,
61
- provider TEXT NOT NULL,
62
- model TEXT NOT NULL,
63
- input_tokens INTEGER NOT NULL DEFAULT 0,
64
- output_tokens INTEGER NOT NULL DEFAULT 0,
65
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
66
- )
67
- `);
68
- db.run(`
69
- CREATE TABLE IF NOT EXISTS uploads (
70
- id INTEGER PRIMARY KEY AUTOINCREMENT,
71
- filename TEXT NOT NULL,
72
- original_name TEXT NOT NULL,
73
- mime_type TEXT NOT NULL,
74
- size_bytes INTEGER NOT NULL,
75
- chunk_count INTEGER NOT NULL DEFAULT 0,
76
- uploaded_at TEXT NOT NULL DEFAULT (datetime('now'))
77
- )
78
- `);
79
- db.run(`
80
- CREATE TABLE IF NOT EXISTS tool_metrics (
81
- id INTEGER PRIMARY KEY AUTOINCREMENT,
82
- tool_name TEXT NOT NULL,
83
- session_id TEXT,
84
- duration_ms INTEGER NOT NULL,
85
- success INTEGER NOT NULL DEFAULT 1,
86
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
87
- )
88
- `);
89
- db.run(`
90
- CREATE TABLE IF NOT EXISTS perf_snapshots (
91
- id INTEGER PRIMARY KEY AUTOINCREMENT,
92
- rss_mb REAL,
93
- heap_mb REAL,
94
- db_size_mb REAL,
95
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
96
- )
97
- `);
98
-
99
- return db;
100
- }
101
-
102
- describe("importDatabase", () => {
103
- let tempDir: string;
104
-
105
- beforeEach(() => {
106
- tempDir = mkdtempSync(join(tmpdir(), "orba-export-test-"));
107
- });
108
-
109
- afterEach(() => {
110
- rmSync(tempDir, { recursive: true, force: true });
111
- });
112
-
113
- test("rejects unknown table names by skipping them", () => {
114
- const db = createTestDb();
115
- const importFile = join(tempDir, "import.json");
116
-
117
- const data = {
118
- version: 1,
119
- exportedAt: new Date().toISOString(),
120
- tables: {
121
- evil_table: [{ id: 1, payload: "malicious" }],
122
- },
123
- };
124
- writeFileSync(importFile, JSON.stringify(data));
125
-
126
- const result = importDatabase(db, importFile);
127
- // The unknown table should be skipped entirely (not in EXPORT_TABLES)
128
- expect(result.imported).toBe(0);
129
- });
130
-
131
- test("handles empty data gracefully", () => {
132
- const db = createTestDb();
133
- const importFile = join(tempDir, "empty.json");
134
-
135
- const data = {
136
- version: 1,
137
- exportedAt: new Date().toISOString(),
138
- tables: {},
139
- };
140
- writeFileSync(importFile, JSON.stringify(data));
141
-
142
- const result = importDatabase(db, importFile);
143
- expect(result.imported).toBe(0);
144
- expect(result.skipped).toBe(0);
145
- });
146
-
147
- test("rejects unsupported export version", () => {
148
- const db = createTestDb();
149
- const importFile = join(tempDir, "bad-version.json");
150
-
151
- const data = {
152
- version: 99,
153
- exportedAt: new Date().toISOString(),
154
- tables: {},
155
- };
156
- writeFileSync(importFile, JSON.stringify(data));
157
-
158
- expect(() => importDatabase(db, importFile)).toThrow("Unsupported export version");
159
- });
160
- });
161
-
162
- describe("export/import round-trip", () => {
163
- let tempDir: string;
164
-
165
- beforeEach(() => {
166
- tempDir = mkdtempSync(join(tmpdir(), "orba-roundtrip-test-"));
167
- });
168
-
169
- afterEach(() => {
170
- rmSync(tempDir, { recursive: true, force: true });
171
- });
172
-
173
- test("preserves data through export and import", () => {
174
- const sourceDb = createTestDb();
175
-
176
- // Insert test data into sessions
177
- sourceDb
178
- .prepare("INSERT INTO sessions (id, channel, user_id) VALUES (?, ?, ?)")
179
- .run("sess-1", "discord", "user-42");
180
- sourceDb
181
- .prepare("INSERT INTO sessions (id, channel, user_id) VALUES (?, ?, ?)")
182
- .run("sess-2", "telegram", "user-99");
183
-
184
- // Insert test data into memory_chunks
185
- sourceDb
186
- .prepare(
187
- "INSERT INTO memory_chunks (source_file, chunk_index, content) VALUES (?, ?, ?)"
188
- )
189
- .run("notes.md", 0, "Remember this fact");
190
-
191
- // Export
192
- const exportPath = join(tempDir, "export.json");
193
- exportDatabase(sourceDb, exportPath);
194
-
195
- // Verify the export file was created
196
- const exportContent = JSON.parse(readFileSync(exportPath, "utf-8"));
197
- expect(exportContent.version).toBe(1);
198
- expect(exportContent.tables.sessions).toHaveLength(2);
199
- expect(exportContent.tables.memory_chunks).toHaveLength(1);
200
-
201
- // Import into a fresh database
202
- const targetDb = createTestDb();
203
- const result = importDatabase(targetDb, exportPath);
204
-
205
- expect(result.imported).toBe(3); // 2 sessions + 1 memory chunk
206
-
207
- // Verify the data
208
- const sessions = targetDb.query("SELECT * FROM sessions ORDER BY id").all() as any[];
209
- expect(sessions).toHaveLength(2);
210
- expect(sessions[0].id).toBe("sess-1");
211
- expect(sessions[0].channel).toBe("discord");
212
- expect(sessions[1].id).toBe("sess-2");
213
- expect(sessions[1].channel).toBe("telegram");
214
-
215
- const chunks = targetDb.query("SELECT * FROM memory_chunks").all() as any[];
216
- expect(chunks).toHaveLength(1);
217
- expect(chunks[0].content).toBe("Remember this fact");
218
- });
219
- });
@@ -1,58 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { mkdirSync, rmSync, existsSync } from "fs";
3
- import { join } from "path";
4
-
5
- const TEST_SESSION_DIR = join(import.meta.dir, ".test-sessions");
6
-
7
- describe("Session", () => {
8
- beforeEach(() => {
9
- mkdirSync(TEST_SESSION_DIR, { recursive: true });
10
- });
11
-
12
- afterEach(() => {
13
- if (existsSync(TEST_SESSION_DIR)) {
14
- rmSync(TEST_SESSION_DIR, { recursive: true });
15
- }
16
- });
17
-
18
- it("should create session file on first append", async () => {
19
- const { writeFileSync, readFileSync } = await import("fs");
20
- const sessionPath = join(TEST_SESSION_DIR, "test-session.jsonl");
21
-
22
- const msg = { role: "user", content: [{ type: "text", text: "hello" }], timestamp: new Date().toISOString() };
23
- writeFileSync(sessionPath, JSON.stringify(msg) + "\n");
24
-
25
- expect(existsSync(sessionPath)).toBe(true);
26
- const content = readFileSync(sessionPath, "utf-8").trim();
27
- const parsed = JSON.parse(content);
28
- expect(parsed.role).toBe("user");
29
- });
30
-
31
- it("should append multiple messages", async () => {
32
- const { writeFileSync, readFileSync } = await import("fs");
33
- const sessionPath = join(TEST_SESSION_DIR, "multi.jsonl");
34
-
35
- const msgs = [
36
- { role: "user", content: [{ type: "text", text: "hi" }] },
37
- { role: "assistant", content: [{ type: "text", text: "hello" }] },
38
- ];
39
-
40
- let content = "";
41
- for (const msg of msgs) {
42
- content += JSON.stringify(msg) + "\n";
43
- }
44
- writeFileSync(sessionPath, content);
45
-
46
- const lines = readFileSync(sessionPath, "utf-8").trim().split("\n");
47
- expect(lines.length).toBe(2);
48
- expect(JSON.parse(lines[0]).role).toBe("user");
49
- expect(JSON.parse(lines[1]).role).toBe("assistant");
50
- });
51
-
52
- it("should reject path traversal in session IDs", () => {
53
- const dangerous = "../../../etc/passwd";
54
- const safe = dangerous.replace(/[^a-zA-Z0-9_:-]/g, "_");
55
- expect(safe).not.toContain("..");
56
- expect(safe).not.toContain("/");
57
- });
58
- });
@@ -1,150 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
- import { executeTool, type ToolResult } from "../../src/tools/executor";
3
- import { registerTool, unregisterTool } from "../../src/tools/registry";
4
-
5
- describe("executeTool", () => {
6
- test("returns an error result for an unknown tool", async () => {
7
- const result = await executeTool(
8
- "nonexistent_tool",
9
- "call-123",
10
- {}
11
- );
12
-
13
- expect(result.is_error).toBe(true);
14
- expect(result.tool_use_id).toBe("call-123");
15
- expect(result.content).toContain("Unknown tool");
16
- expect(result.content).toContain("nonexistent_tool");
17
- });
18
-
19
- test("blocks a tool not in the allowedTools list", async () => {
20
- const result = await executeTool(
21
- "some_tool",
22
- "call-456",
23
- {},
24
- ["other_tool"]
25
- );
26
-
27
- expect(result.is_error).toBe(true);
28
- expect(result.content).toContain("not available");
29
- });
30
-
31
- test("allows a tool that is in the allowedTools list", async () => {
32
- const testToolName = "__test_allowed_tool__";
33
-
34
- registerTool({
35
- definition: {
36
- name: testToolName,
37
- description: "A test tool",
38
- input_schema: { type: "object", properties: {} },
39
- },
40
- execute: async () => "success",
41
- });
42
-
43
- try {
44
- // Unknown tools default to "confirm" — first call returns confirmation token
45
- const confirmResult = await executeTool(
46
- testToolName,
47
- "call-789",
48
- {},
49
- [testToolName]
50
- );
51
- expect(confirmResult.content).toContain("Confirmation Token:");
52
- const tokenMatch = confirmResult.content.match(/Confirmation Token: ([a-f0-9-]+)/);
53
- expect(tokenMatch).toBeTruthy();
54
-
55
- // Second call with token executes the tool
56
- const result = await executeTool(
57
- testToolName,
58
- "call-789b",
59
- { _confirmToken: tokenMatch![1] },
60
- [testToolName]
61
- );
62
- expect(result.is_error).toBe(false);
63
- expect(result.content).toBe("success");
64
- } finally {
65
- unregisterTool(testToolName);
66
- }
67
- });
68
-
69
- test("returns a denied error for tools with deny permission", async () => {
70
- const result = await executeTool(
71
- "shell",
72
- "call-deny",
73
- {},
74
- [] // Empty allowedTools means nothing is allowed
75
- );
76
-
77
- expect(result.is_error).toBe(true);
78
- expect(result.content).toContain("not available");
79
- });
80
-
81
- test("executes a registered tool and returns its output", async () => {
82
- const testToolName = "__test_executor_tool__";
83
-
84
- registerTool({
85
- definition: {
86
- name: testToolName,
87
- description: "Echoes input back",
88
- input_schema: {
89
- type: "object",
90
- properties: { message: { type: "string" } },
91
- },
92
- },
93
- execute: async (input) => `echo: ${input.message}`,
94
- });
95
-
96
- try {
97
- // First call: get confirmation token (unknown tools default to "confirm")
98
- const confirmResult = await executeTool(testToolName, "call-exec", {
99
- message: "hello",
100
- });
101
- expect(confirmResult.content).toContain("Confirmation Token:");
102
- const tokenMatch = confirmResult.content.match(/Confirmation Token: ([a-f0-9-]+)/);
103
- expect(tokenMatch).toBeTruthy();
104
-
105
- // Second call with token: actually execute
106
- const result = await executeTool(testToolName, "call-exec2", {
107
- message: "hello",
108
- _confirmToken: tokenMatch![1],
109
- });
110
-
111
- expect(result.is_error).toBe(false);
112
- expect(result.content).toBe("echo: hello");
113
- expect(result.tool_use_id).toBe("call-exec2");
114
- } finally {
115
- unregisterTool(testToolName);
116
- }
117
- });
118
-
119
- test("returns an error result when a tool throws", async () => {
120
- const testToolName = "__test_throwing_tool__";
121
-
122
- registerTool({
123
- definition: {
124
- name: testToolName,
125
- description: "Always throws",
126
- input_schema: { type: "object", properties: {} },
127
- },
128
- execute: async () => {
129
- throw new Error("Something went wrong");
130
- },
131
- });
132
-
133
- try {
134
- // First call: get confirmation token
135
- const confirmResult = await executeTool(testToolName, "call-throw", {});
136
- const tokenMatch = confirmResult.content.match(/Confirmation Token: ([a-f0-9-]+)/);
137
- expect(tokenMatch).toBeTruthy();
138
-
139
- // Second call with token: tool throws
140
- const result = await executeTool(testToolName, "call-throw2", {
141
- _confirmToken: tokenMatch![1],
142
- });
143
-
144
- expect(result.is_error).toBe(true);
145
- expect(result.content).toContain("Something went wrong");
146
- } finally {
147
- unregisterTool(testToolName);
148
- }
149
- });
150
- });