bare-agent 0.1.0 → 0.1.1
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 +229 -0
- package/bin/cli.js +71 -0
- package/index.js +21 -1
- package/package.json +40 -5
- package/src/checkpoint.js +33 -0
- package/src/loop.js +163 -0
- package/src/memory.js +46 -0
- package/src/planner.js +81 -0
- package/src/provider-anthropic.js +144 -0
- package/src/provider-ollama.js +87 -0
- package/src/provider-openai.js +91 -0
- package/src/providers.js +11 -0
- package/src/retry.js +53 -0
- package/src/scheduler.js +129 -0
- package/src/state.js +86 -0
- package/src/store-jsonfile.js +63 -0
- package/src/store-sqlite.js +133 -0
- package/src/stores.js +9 -0
- package/src/stream.js +41 -0
- package/src/transport-jsonl.js +19 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { readFileSync, writeFileSync, existsSync } = require('node:fs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* JSON file memory store. Zero deps, case-insensitive substring search.
|
|
7
|
+
*
|
|
8
|
+
* Interface (implements Memory store contract):
|
|
9
|
+
* store(content, metadata) → id
|
|
10
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
11
|
+
* get(id) → { content, metadata }
|
|
12
|
+
* delete(id) → void
|
|
13
|
+
*/
|
|
14
|
+
class JsonFileStore {
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.path - Path to JSON file (required).
|
|
18
|
+
* @throws {Error} `[JsonFileStore] requires options.path` — when path is missing.
|
|
19
|
+
*/
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
if (!options.path) throw new Error('[JsonFileStore] requires options.path');
|
|
22
|
+
this._path = options.path;
|
|
23
|
+
this._data = existsSync(this._path)
|
|
24
|
+
? JSON.parse(readFileSync(this._path, 'utf8'))
|
|
25
|
+
: [];
|
|
26
|
+
this._nextId = this._data.length
|
|
27
|
+
? Math.max(...this._data.map(d => d.id)) + 1
|
|
28
|
+
: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_save() {
|
|
32
|
+
writeFileSync(this._path, JSON.stringify(this._data, null, 2));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
store(content, metadata = {}) {
|
|
36
|
+
const id = this._nextId++;
|
|
37
|
+
this._data.push({ id, content, metadata, createdAt: new Date().toISOString() });
|
|
38
|
+
this._save();
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
search(query, options = {}) {
|
|
43
|
+
const limit = options.limit || 10;
|
|
44
|
+
const q = (query || '').toLowerCase();
|
|
45
|
+
if (!q) return this._data.slice(0, limit).map(d => ({ ...d, score: 1 }));
|
|
46
|
+
return this._data
|
|
47
|
+
.filter(d => d.content.toLowerCase().includes(q))
|
|
48
|
+
.slice(0, limit)
|
|
49
|
+
.map(d => ({ ...d, score: 1 }));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get(id) {
|
|
53
|
+
const item = this._data.find(d => d.id === id);
|
|
54
|
+
return item ? { id: item.id, content: item.content, metadata: item.metadata } : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
delete(id) {
|
|
58
|
+
this._data = this._data.filter(d => d.id !== id);
|
|
59
|
+
this._save();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { JsonFileStore };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SQLite FTS5 memory store. Full-text search with BM25 ranking.
|
|
5
|
+
*
|
|
6
|
+
* Interface (implements Memory store contract):
|
|
7
|
+
* store(content, metadata) → id
|
|
8
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
9
|
+
* get(id) → { content, metadata }
|
|
10
|
+
* delete(id) → void
|
|
11
|
+
*
|
|
12
|
+
* Requires peer dep: better-sqlite3
|
|
13
|
+
*/
|
|
14
|
+
class SQLiteStore {
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.path - Path to SQLite database file (required).
|
|
18
|
+
* @throws {Error} `[SQLiteStore] requires options.path` — when path is missing.
|
|
19
|
+
* @throws {Error} `[SQLiteStore] requires better-sqlite3` — when peer dep is not installed.
|
|
20
|
+
*/
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
if (!options.path) throw new Error('[SQLiteStore] requires options.path');
|
|
23
|
+
|
|
24
|
+
let Database;
|
|
25
|
+
try {
|
|
26
|
+
Database = require('better-sqlite3');
|
|
27
|
+
} catch {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'[SQLiteStore] requires better-sqlite3. Install it: npm install better-sqlite3'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this._db = new Database(options.path);
|
|
34
|
+
this._db.pragma('journal_mode = WAL');
|
|
35
|
+
this._db.exec(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
content TEXT NOT NULL,
|
|
39
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
40
|
+
created_at TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
|
|
43
|
+
content,
|
|
44
|
+
content='chunks',
|
|
45
|
+
content_rowid='id',
|
|
46
|
+
tokenize='porter'
|
|
47
|
+
);
|
|
48
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
49
|
+
INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
|
|
50
|
+
END;
|
|
51
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
|
|
52
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES('delete', old.id, old.content);
|
|
53
|
+
END;
|
|
54
|
+
CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
|
|
55
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES('delete', old.id, old.content);
|
|
56
|
+
INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
|
|
57
|
+
END;
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
this._insertStmt = this._db.prepare(
|
|
61
|
+
'INSERT INTO chunks (content, metadata, created_at) VALUES (?, ?, ?)'
|
|
62
|
+
);
|
|
63
|
+
this._getStmt = this._db.prepare(
|
|
64
|
+
'SELECT id, content, metadata FROM chunks WHERE id = ?'
|
|
65
|
+
);
|
|
66
|
+
this._deleteStmt = this._db.prepare('DELETE FROM chunks WHERE id = ?');
|
|
67
|
+
this._searchStmt = this._db.prepare(`
|
|
68
|
+
SELECT chunks.id, chunks.content, chunks.metadata, rank
|
|
69
|
+
FROM chunks_fts
|
|
70
|
+
JOIN chunks ON chunks.id = chunks_fts.rowid
|
|
71
|
+
WHERE chunks_fts MATCH ?
|
|
72
|
+
ORDER BY rank
|
|
73
|
+
LIMIT ?
|
|
74
|
+
`);
|
|
75
|
+
this._allStmt = this._db.prepare(
|
|
76
|
+
'SELECT id, content, metadata FROM chunks ORDER BY id DESC LIMIT ?'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
store(content, metadata = {}) {
|
|
81
|
+
const result = this._insertStmt.run(
|
|
82
|
+
content,
|
|
83
|
+
JSON.stringify(metadata),
|
|
84
|
+
new Date().toISOString()
|
|
85
|
+
);
|
|
86
|
+
return Number(result.lastInsertRowid);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
search(query, options = {}) {
|
|
90
|
+
const limit = options.limit || 10;
|
|
91
|
+
if (!query) {
|
|
92
|
+
return this._allStmt.all(limit).map(row => ({
|
|
93
|
+
id: row.id,
|
|
94
|
+
content: row.content,
|
|
95
|
+
metadata: JSON.parse(row.metadata),
|
|
96
|
+
score: 1,
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
// FTS5 query: wrap each word in quotes for phrase-safe matching
|
|
100
|
+
const ftsQuery = query
|
|
101
|
+
.split(/\s+/)
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map(w => `"${w.replace(/"/g, '""')}"`)
|
|
104
|
+
.join(' OR ');
|
|
105
|
+
try {
|
|
106
|
+
return this._searchStmt.all(ftsQuery, limit).map(row => ({
|
|
107
|
+
id: row.id,
|
|
108
|
+
content: row.content,
|
|
109
|
+
metadata: JSON.parse(row.metadata),
|
|
110
|
+
score: -row.rank, // FTS5 rank is negative (closer to 0 = better)
|
|
111
|
+
}));
|
|
112
|
+
} catch {
|
|
113
|
+
// If FTS query fails (e.g. special characters), return empty
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get(id) {
|
|
119
|
+
const row = this._getStmt.get(id);
|
|
120
|
+
if (!row) return null;
|
|
121
|
+
return { id: row.id, content: row.content, metadata: JSON.parse(row.metadata) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
delete(id) {
|
|
125
|
+
this._deleteStmt.run(id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
close() {
|
|
129
|
+
this._db.close();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { SQLiteStore };
|
package/src/stores.js
ADDED
package/src/stream.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structured event streaming for observability and cross-process communication.
|
|
5
|
+
*
|
|
6
|
+
* Interface:
|
|
7
|
+
* emit(event) → void
|
|
8
|
+
* subscribe(callback) → unsubscribe function
|
|
9
|
+
*
|
|
10
|
+
* Event shape:
|
|
11
|
+
* { type, taskId, data, ts }
|
|
12
|
+
*
|
|
13
|
+
* Transport:
|
|
14
|
+
* Object with write(event) method — e.g. JsonlTransport
|
|
15
|
+
* null — disabled (subscribers only)
|
|
16
|
+
*/
|
|
17
|
+
class Stream {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this._transport = options.transport || null;
|
|
20
|
+
this._subscribers = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
emit(event) {
|
|
24
|
+
const full = { ...event, ts: event.ts || new Date().toISOString() };
|
|
25
|
+
for (const fn of this._subscribers) {
|
|
26
|
+
try { fn(full); } catch {}
|
|
27
|
+
}
|
|
28
|
+
if (this._transport) {
|
|
29
|
+
this._transport.write(full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
subscribe(callback) {
|
|
34
|
+
this._subscribers.push(callback);
|
|
35
|
+
return () => {
|
|
36
|
+
this._subscribers = this._subscribers.filter(fn => fn !== callback);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { Stream };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSONL transport: one JSON object per line to a writable stream.
|
|
5
|
+
* Default: process.stdout. Pipe-friendly, parseable by any language.
|
|
6
|
+
*
|
|
7
|
+
* Debug output goes to stderr (never pollutes stdout).
|
|
8
|
+
*/
|
|
9
|
+
class JsonlTransport {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this._output = options.output || process.stdout;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
write(event) {
|
|
15
|
+
this._output.write(JSON.stringify(event) + '\n');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { JsonlTransport };
|