bare-agent 0.11.0 → 0.12.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 +1 -0
- package/bareagent.context.md +1149 -0
- package/bin/cli.d.ts +4 -0
- package/bin/cli.js +40 -10
- package/bin/test-provider.d.ts +2 -0
- package/bin/test-provider.js +5 -1
- package/index.d.ts +20 -0
- package/package.json +46 -10
- package/src/bareguard-adapter.d.ts +118 -0
- package/src/bareguard-adapter.js +75 -3
- package/src/checkpoint.d.ts +61 -0
- package/src/checkpoint.js +17 -8
- package/src/circuit-breaker.d.ts +70 -0
- package/src/circuit-breaker.js +20 -4
- package/src/errors.d.ts +106 -0
- package/src/errors.js +50 -1
- package/src/loop.d.ts +135 -0
- package/src/loop.js +73 -17
- package/src/mcp-bridge.d.ts +133 -0
- package/src/mcp-bridge.js +179 -27
- package/src/mcp.d.ts +4 -0
- package/src/memory.d.ts +50 -0
- package/src/memory.js +22 -2
- package/src/planner.d.ts +62 -0
- package/src/planner.js +26 -7
- package/src/provider-anthropic.d.ts +55 -0
- package/src/provider-anthropic.js +32 -11
- package/src/provider-clipipe.d.ts +86 -0
- package/src/provider-clipipe.js +28 -18
- package/src/provider-fallback.d.ts +44 -0
- package/src/provider-fallback.js +18 -8
- package/src/provider-ollama.d.ts +41 -0
- package/src/provider-ollama.js +27 -7
- package/src/provider-openai.d.ts +57 -0
- package/src/provider-openai.js +31 -16
- package/src/providers.d.ts +6 -0
- package/src/providers.js +8 -0
- package/src/retry.d.ts +44 -0
- package/src/retry.js +15 -1
- package/src/run-plan.d.ts +126 -0
- package/src/run-plan.js +46 -13
- package/src/scheduler.d.ts +102 -0
- package/src/scheduler.js +32 -4
- package/src/state.d.ts +45 -0
- package/src/state.js +18 -2
- package/src/store-jsonfile.d.ts +85 -0
- package/src/store-jsonfile.js +33 -8
- package/src/store-sqlite.d.ts +90 -0
- package/src/store-sqlite.js +31 -7
- package/src/stores.d.ts +3 -0
- package/src/stream.d.ts +79 -0
- package/src/stream.js +32 -0
- package/src/tools.d.ts +8 -0
- package/src/transport-jsonl.d.ts +30 -0
- package/src/transport-jsonl.js +13 -0
- package/src/transports.d.ts +2 -0
- package/tools/browse.d.ts +10 -0
- package/tools/browse.js +2 -0
- package/tools/defer.d.ts +33 -0
- package/tools/defer.js +12 -3
- package/tools/mobile.d.ts +34 -0
- package/tools/mobile.js +28 -15
- package/tools/shell.d.ts +31 -0
- package/tools/shell.js +55 -6
- package/tools/spawn.d.ts +107 -0
- package/tools/spawn.js +24 -5
- package/types/index.d.ts +66 -0
- package/types/shims.d.ts +16 -0
package/src/scheduler.js
CHANGED
|
@@ -12,17 +12,41 @@ const { readFileSync, writeFileSync, existsSync } = require('node:fs');
|
|
|
12
12
|
* start(handler) → begin tick loop (handler receives due jobs)
|
|
13
13
|
* stop() → stop tick loop
|
|
14
14
|
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} Job
|
|
18
|
+
* @property {number} id
|
|
19
|
+
* @property {string} type
|
|
20
|
+
* @property {string} schedule
|
|
21
|
+
* @property {*} action
|
|
22
|
+
* @property {string} status
|
|
23
|
+
* @property {string} nextRun
|
|
24
|
+
* @property {string} [createdAt]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} SchedulerOptions
|
|
29
|
+
* @property {string|null} [file] - Path to JSON persistence file.
|
|
30
|
+
* @property {number} [interval=60000] - Tick interval in ms.
|
|
31
|
+
* @property {((err: any, job: Job) => void)|null} [onError] - Handler errors callback.
|
|
32
|
+
*/
|
|
33
|
+
|
|
15
34
|
class Scheduler {
|
|
35
|
+
/** @param {SchedulerOptions} [options={}] */
|
|
16
36
|
constructor(options = {}) {
|
|
17
37
|
this._file = options.file || null;
|
|
18
38
|
this._interval = options.interval || 60000;
|
|
19
39
|
this.onError = options.onError || null;
|
|
40
|
+
/** @type {Job[]} */
|
|
20
41
|
this._jobs = this._file && existsSync(this._file)
|
|
21
42
|
? JSON.parse(readFileSync(this._file, 'utf8'))
|
|
22
43
|
: [];
|
|
44
|
+
/** @type {NodeJS.Timeout|null} */
|
|
23
45
|
this._timer = null;
|
|
46
|
+
/** @type {Set<number>} */
|
|
47
|
+
this._running = new Set();
|
|
24
48
|
this._nextId = this._jobs.length
|
|
25
|
-
? this._jobs.reduce((max, j) => Math.max(max, j.id), 0) + 1
|
|
49
|
+
? this._jobs.reduce((/** @type {number} */ max, /** @type {Job} */ j) => Math.max(max, j.id), 0) + 1
|
|
26
50
|
: 1;
|
|
27
51
|
}
|
|
28
52
|
|
|
@@ -30,6 +54,7 @@ class Scheduler {
|
|
|
30
54
|
if (this._file) writeFileSync(this._file, JSON.stringify(this._jobs, null, 2));
|
|
31
55
|
}
|
|
32
56
|
|
|
57
|
+
/** @param {{ type?: string, schedule: string, action: * }} job */
|
|
33
58
|
add(job) {
|
|
34
59
|
const id = this._nextId++;
|
|
35
60
|
const nextRun = this._parseSchedule(job.schedule);
|
|
@@ -46,13 +71,14 @@ class Scheduler {
|
|
|
46
71
|
return id;
|
|
47
72
|
}
|
|
48
73
|
|
|
74
|
+
/** @param {number} jobId */
|
|
49
75
|
remove(jobId) {
|
|
50
|
-
this._jobs = this._jobs.filter(j => j.id !== jobId);
|
|
76
|
+
this._jobs = this._jobs.filter((/** @type {Job} */ j) => j.id !== jobId);
|
|
51
77
|
this._save();
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
list() {
|
|
55
|
-
return this._jobs.map(j => ({ ...j }));
|
|
81
|
+
return this._jobs.map((/** @type {Job} */ j) => ({ ...j }));
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
/**
|
|
@@ -114,7 +140,9 @@ class Scheduler {
|
|
|
114
140
|
const rel = schedule.match(/^(\d+)(s|m|h|d)$/);
|
|
115
141
|
if (rel) {
|
|
116
142
|
const [, n, unit] = rel;
|
|
117
|
-
|
|
143
|
+
/** @type {Record<string, number>} */
|
|
144
|
+
const units = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
145
|
+
const ms = units[unit];
|
|
118
146
|
return new Date(Date.now() + Number(n) * ms);
|
|
119
147
|
}
|
|
120
148
|
// Cron: try cron-parser
|
package/src/state.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Task = {
|
|
2
|
+
status: string;
|
|
3
|
+
data: any;
|
|
4
|
+
error: any;
|
|
5
|
+
updatedAt: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} Task
|
|
9
|
+
* @property {string} status
|
|
10
|
+
* @property {*} data
|
|
11
|
+
* @property {*} error
|
|
12
|
+
* @property {string} updatedAt
|
|
13
|
+
*/
|
|
14
|
+
export class StateMachine extends EventEmitter<[never]> {
|
|
15
|
+
/** @param {{ file?: string|null }} [options={}] */
|
|
16
|
+
constructor(options?: {
|
|
17
|
+
file?: string | null;
|
|
18
|
+
});
|
|
19
|
+
file: string | null;
|
|
20
|
+
/** @type {Map<string, Task>} */
|
|
21
|
+
tasks: Map<string, Task>;
|
|
22
|
+
/**
|
|
23
|
+
* Transition a task to a new state.
|
|
24
|
+
* @param {string} taskId - Task identifier.
|
|
25
|
+
* @param {string} event - Transition event (start, complete, fail, pause, resume, cancel, retry).
|
|
26
|
+
* @param {*} [data] - Optional data to attach to the task.
|
|
27
|
+
* @returns {string} The new status.
|
|
28
|
+
* @throws {Error} `[StateMachine] Invalid transition` — when the event is not valid for the current state.
|
|
29
|
+
*/
|
|
30
|
+
transition(taskId: string, event: string, data?: any): string;
|
|
31
|
+
/** @param {string} taskId */
|
|
32
|
+
getStatus(taskId: string): Task | null;
|
|
33
|
+
/** @param {(event: { taskId: string, from: string, to: string, event: string, data: * }) => void} callback */
|
|
34
|
+
onTransition(callback: (event: {
|
|
35
|
+
taskId: string;
|
|
36
|
+
from: string;
|
|
37
|
+
to: string;
|
|
38
|
+
event: string;
|
|
39
|
+
data: any;
|
|
40
|
+
}) => void): () => this;
|
|
41
|
+
getAll(): Record<string, Task>;
|
|
42
|
+
_load(): void;
|
|
43
|
+
_save(): void;
|
|
44
|
+
}
|
|
45
|
+
import { EventEmitter } from "events";
|
package/src/state.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { readFileSync, writeFileSync } = require('fs');
|
|
4
4
|
const { EventEmitter } = require('events');
|
|
5
5
|
|
|
6
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
6
7
|
const TRANSITIONS = {
|
|
7
8
|
pending: { start: 'running', cancel: 'cancelled' },
|
|
8
9
|
running: { complete: 'done', fail: 'failed', pause: 'waiting_for_input', cancel: 'cancelled' },
|
|
@@ -11,10 +12,20 @@ const TRANSITIONS = {
|
|
|
11
12
|
// done and cancelled are terminal
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} Task
|
|
17
|
+
* @property {string} status
|
|
18
|
+
* @property {*} data
|
|
19
|
+
* @property {*} error
|
|
20
|
+
* @property {string} updatedAt
|
|
21
|
+
*/
|
|
22
|
+
|
|
14
23
|
class StateMachine extends EventEmitter {
|
|
24
|
+
/** @param {{ file?: string|null }} [options={}] */
|
|
15
25
|
constructor(options = {}) {
|
|
16
26
|
super();
|
|
17
27
|
this.file = options.file || null;
|
|
28
|
+
/** @type {Map<string, Task>} */
|
|
18
29
|
this.tasks = new Map();
|
|
19
30
|
if (this.file) this._load();
|
|
20
31
|
}
|
|
@@ -51,35 +62,40 @@ class StateMachine extends EventEmitter {
|
|
|
51
62
|
return task.status;
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
/** @param {string} taskId */
|
|
54
66
|
getStatus(taskId) {
|
|
55
67
|
return this.tasks.get(taskId) || null;
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
/** @param {(event: { taskId: string, from: string, to: string, event: string, data: * }) => void} callback */
|
|
58
71
|
onTransition(callback) {
|
|
59
72
|
this.on('transition', callback);
|
|
60
73
|
return () => this.off('transition', callback);
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
getAll() {
|
|
77
|
+
/** @type {Record<string, Task>} */
|
|
64
78
|
const result = {};
|
|
65
79
|
for (const [id, task] of this.tasks) result[id] = { ...task };
|
|
66
80
|
return result;
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
_load() {
|
|
84
|
+
if (!this.file) return;
|
|
70
85
|
try {
|
|
71
86
|
const raw = readFileSync(this.file, 'utf8');
|
|
72
87
|
const data = JSON.parse(raw);
|
|
73
|
-
for (const [id, task] of Object.entries(data)) this.tasks.set(id, task);
|
|
88
|
+
for (const [id, task] of Object.entries(data)) this.tasks.set(id, /** @type {Task} */ (task));
|
|
74
89
|
} catch (e) {
|
|
75
90
|
if (e.code !== 'ENOENT') throw e;
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
_save() {
|
|
95
|
+
/** @type {Record<string, Task>} */
|
|
80
96
|
const obj = {};
|
|
81
97
|
for (const [id, task] of this.tasks) obj[id] = task;
|
|
82
|
-
writeFileSync(this.file, JSON.stringify(obj, null, 2) + '\n');
|
|
98
|
+
if (this.file) writeFileSync(this.file, JSON.stringify(obj, null, 2) + '\n');
|
|
83
99
|
}
|
|
84
100
|
}
|
|
85
101
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON file memory store. Zero deps, case-insensitive substring search.
|
|
3
|
+
*
|
|
4
|
+
* SCALING: intended for small memory sets. `search()` is an O(n) substring scan
|
|
5
|
+
* (no index), and every `store()`/`delete()` re-serializes and rewrites the entire
|
|
6
|
+
* file synchronously. Fine for hundreds–low-thousands of entries; for larger or
|
|
7
|
+
* write-heavy memory use SQLiteStore (FTS5 index, parameterized, incremental writes).
|
|
8
|
+
*
|
|
9
|
+
* Interface (implements Memory store contract):
|
|
10
|
+
* store(content, metadata) → id
|
|
11
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
12
|
+
* get(id) → { content, metadata }
|
|
13
|
+
* delete(id) → void
|
|
14
|
+
*/
|
|
15
|
+
export type JsonRecord = {
|
|
16
|
+
id: number;
|
|
17
|
+
content: any;
|
|
18
|
+
metadata: Record<string, any>;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* JSON file memory store. Zero deps, case-insensitive substring search.
|
|
23
|
+
*
|
|
24
|
+
* SCALING: intended for small memory sets. `search()` is an O(n) substring scan
|
|
25
|
+
* (no index), and every `store()`/`delete()` re-serializes and rewrites the entire
|
|
26
|
+
* file synchronously. Fine for hundreds–low-thousands of entries; for larger or
|
|
27
|
+
* write-heavy memory use SQLiteStore (FTS5 index, parameterized, incremental writes).
|
|
28
|
+
*
|
|
29
|
+
* Interface (implements Memory store contract):
|
|
30
|
+
* store(content, metadata) → id
|
|
31
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
32
|
+
* get(id) → { content, metadata }
|
|
33
|
+
* delete(id) → void
|
|
34
|
+
*
|
|
35
|
+
* @typedef {object} JsonRecord
|
|
36
|
+
* @property {number} id
|
|
37
|
+
* @property {any} content
|
|
38
|
+
* @property {Record<string, any>} metadata
|
|
39
|
+
* @property {string} createdAt
|
|
40
|
+
*/
|
|
41
|
+
export class JsonFileStore {
|
|
42
|
+
/**
|
|
43
|
+
* @param {{ path?: string }} [options]
|
|
44
|
+
* @throws {Error} `[JsonFileStore] requires options.path` — when path is missing.
|
|
45
|
+
*/
|
|
46
|
+
constructor(options?: {
|
|
47
|
+
path?: string;
|
|
48
|
+
});
|
|
49
|
+
_path: string;
|
|
50
|
+
/** @type {JsonRecord[]} */
|
|
51
|
+
_data: JsonRecord[];
|
|
52
|
+
_nextId: number;
|
|
53
|
+
_warnedLarge: boolean;
|
|
54
|
+
_save(): void;
|
|
55
|
+
/**
|
|
56
|
+
* @param {any} content
|
|
57
|
+
* @param {Record<string, any>} [metadata]
|
|
58
|
+
* @returns {number} id
|
|
59
|
+
*/
|
|
60
|
+
store(content: any, metadata?: Record<string, any>): number;
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} query
|
|
63
|
+
* @param {{ limit?: number }} [options]
|
|
64
|
+
* @returns {Array<JsonRecord & { score: number }>}
|
|
65
|
+
*/
|
|
66
|
+
search(query: string, options?: {
|
|
67
|
+
limit?: number;
|
|
68
|
+
}): Array<JsonRecord & {
|
|
69
|
+
score: number;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* @param {number} id
|
|
73
|
+
* @returns {{ id: number, content: any, metadata: Record<string, any> } | null}
|
|
74
|
+
*/
|
|
75
|
+
get(id: number): {
|
|
76
|
+
id: number;
|
|
77
|
+
content: any;
|
|
78
|
+
metadata: Record<string, any>;
|
|
79
|
+
} | null;
|
|
80
|
+
/**
|
|
81
|
+
* @param {number} id
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
delete(id: number): void;
|
|
85
|
+
}
|
package/src/store-jsonfile.js
CHANGED
|
@@ -19,21 +19,28 @@ const LARGE_STORE_THRESHOLD = 10000;
|
|
|
19
19
|
* search(query, options) → [{ id, content, metadata, score }]
|
|
20
20
|
* get(id) → { content, metadata }
|
|
21
21
|
* delete(id) → void
|
|
22
|
+
*
|
|
23
|
+
* @typedef {object} JsonRecord
|
|
24
|
+
* @property {number} id
|
|
25
|
+
* @property {any} content
|
|
26
|
+
* @property {Record<string, any>} metadata
|
|
27
|
+
* @property {string} createdAt
|
|
22
28
|
*/
|
|
29
|
+
|
|
23
30
|
class JsonFileStore {
|
|
24
31
|
/**
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {string} options.path - Path to JSON file (required).
|
|
32
|
+
* @param {{ path?: string }} [options]
|
|
27
33
|
* @throws {Error} `[JsonFileStore] requires options.path` — when path is missing.
|
|
28
34
|
*/
|
|
29
35
|
constructor(options = {}) {
|
|
30
36
|
if (!options.path) throw new Error('[JsonFileStore] requires options.path');
|
|
31
37
|
this._path = options.path;
|
|
38
|
+
/** @type {JsonRecord[]} */
|
|
32
39
|
this._data = existsSync(this._path)
|
|
33
40
|
? JSON.parse(readFileSync(this._path, 'utf8'))
|
|
34
41
|
: [];
|
|
35
42
|
this._nextId = this._data.length
|
|
36
|
-
? this._data.reduce((max, d) => Math.max(max, d.id), 0) + 1
|
|
43
|
+
? this._data.reduce((/** @type {number} */ max, /** @type {JsonRecord} */ d) => Math.max(max, d.id), 0) + 1
|
|
37
44
|
: 1;
|
|
38
45
|
this._warnedLarge = false;
|
|
39
46
|
}
|
|
@@ -42,6 +49,11 @@ class JsonFileStore {
|
|
|
42
49
|
writeFileSync(this._path, JSON.stringify(this._data, null, 2));
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {any} content
|
|
54
|
+
* @param {Record<string, any>} [metadata]
|
|
55
|
+
* @returns {number} id
|
|
56
|
+
*/
|
|
45
57
|
store(content, metadata = {}) {
|
|
46
58
|
const id = this._nextId++;
|
|
47
59
|
this._data.push({ id, content, metadata, createdAt: new Date().toISOString() });
|
|
@@ -56,23 +68,36 @@ class JsonFileStore {
|
|
|
56
68
|
return id;
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} query
|
|
73
|
+
* @param {{ limit?: number }} [options]
|
|
74
|
+
* @returns {Array<JsonRecord & { score: number }>}
|
|
75
|
+
*/
|
|
59
76
|
search(query, options = {}) {
|
|
60
77
|
const limit = options.limit || 10;
|
|
61
78
|
const q = (query || '').toLowerCase();
|
|
62
|
-
if (!q) return this._data.slice(0, limit).map(d => ({ ...d, score: 1 }));
|
|
79
|
+
if (!q) return this._data.slice(0, limit).map((/** @type {JsonRecord} */ d) => ({ ...d, score: 1 }));
|
|
63
80
|
return this._data
|
|
64
|
-
.filter(d => d.content.toLowerCase().includes(q))
|
|
81
|
+
.filter((/** @type {JsonRecord} */ d) => d.content.toLowerCase().includes(q))
|
|
65
82
|
.slice(0, limit)
|
|
66
|
-
.map(d => ({ ...d, score: 1 }));
|
|
83
|
+
.map((/** @type {JsonRecord} */ d) => ({ ...d, score: 1 }));
|
|
67
84
|
}
|
|
68
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {number} id
|
|
88
|
+
* @returns {{ id: number, content: any, metadata: Record<string, any> } | null}
|
|
89
|
+
*/
|
|
69
90
|
get(id) {
|
|
70
|
-
const item = this._data.find(d => d.id === id);
|
|
91
|
+
const item = this._data.find((/** @type {JsonRecord} */ d) => d.id === id);
|
|
71
92
|
return item ? { id: item.id, content: item.content, metadata: item.metadata } : null;
|
|
72
93
|
}
|
|
73
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {number} id
|
|
97
|
+
* @returns {void}
|
|
98
|
+
*/
|
|
74
99
|
delete(id) {
|
|
75
|
-
this._data = this._data.filter(d => d.id !== id);
|
|
100
|
+
this._data = this._data.filter((/** @type {JsonRecord} */ d) => d.id !== id);
|
|
76
101
|
this._save();
|
|
77
102
|
}
|
|
78
103
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite FTS5 memory store. Full-text search with BM25 ranking.
|
|
3
|
+
*
|
|
4
|
+
* Interface (implements Memory store contract):
|
|
5
|
+
* store(content, metadata) → id
|
|
6
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
7
|
+
* get(id) → { content, metadata }
|
|
8
|
+
* delete(id) → void
|
|
9
|
+
*
|
|
10
|
+
* Requires peer dep: better-sqlite3
|
|
11
|
+
*/
|
|
12
|
+
export type ChunkRow = {
|
|
13
|
+
id: number;
|
|
14
|
+
content: string;
|
|
15
|
+
/**
|
|
16
|
+
* - JSON-serialized metadata.
|
|
17
|
+
*/
|
|
18
|
+
metadata: string;
|
|
19
|
+
/**
|
|
20
|
+
* - FTS5 rank (search rows only).
|
|
21
|
+
*/
|
|
22
|
+
rank?: number | undefined;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* SQLite FTS5 memory store. Full-text search with BM25 ranking.
|
|
26
|
+
*
|
|
27
|
+
* Interface (implements Memory store contract):
|
|
28
|
+
* store(content, metadata) → id
|
|
29
|
+
* search(query, options) → [{ id, content, metadata, score }]
|
|
30
|
+
* get(id) → { content, metadata }
|
|
31
|
+
* delete(id) → void
|
|
32
|
+
*
|
|
33
|
+
* Requires peer dep: better-sqlite3
|
|
34
|
+
*
|
|
35
|
+
* @typedef {object} ChunkRow
|
|
36
|
+
* @property {number} id
|
|
37
|
+
* @property {string} content
|
|
38
|
+
* @property {string} metadata - JSON-serialized metadata.
|
|
39
|
+
* @property {number} [rank] - FTS5 rank (search rows only).
|
|
40
|
+
*/
|
|
41
|
+
export class SQLiteStore {
|
|
42
|
+
/**
|
|
43
|
+
* @param {{ path?: string }} [options]
|
|
44
|
+
* @throws {Error} `[SQLiteStore] requires options.path` — when path is missing.
|
|
45
|
+
* @throws {Error} `[SQLiteStore] requires better-sqlite3` — when peer dep is not installed.
|
|
46
|
+
*/
|
|
47
|
+
constructor(options?: {
|
|
48
|
+
path?: string;
|
|
49
|
+
});
|
|
50
|
+
_db: any;
|
|
51
|
+
_insertStmt: any;
|
|
52
|
+
_getStmt: any;
|
|
53
|
+
_deleteStmt: any;
|
|
54
|
+
_searchStmt: any;
|
|
55
|
+
_allStmt: any;
|
|
56
|
+
/**
|
|
57
|
+
* @param {any} content
|
|
58
|
+
* @param {Record<string, any>} [metadata]
|
|
59
|
+
* @returns {number} id
|
|
60
|
+
*/
|
|
61
|
+
store(content: any, metadata?: Record<string, any>): number;
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} query
|
|
64
|
+
* @param {{ limit?: number }} [options]
|
|
65
|
+
* @returns {Array<{ id: number, content: string, metadata: Record<string, any>, score: number }>}
|
|
66
|
+
*/
|
|
67
|
+
search(query: string, options?: {
|
|
68
|
+
limit?: number;
|
|
69
|
+
}): Array<{
|
|
70
|
+
id: number;
|
|
71
|
+
content: string;
|
|
72
|
+
metadata: Record<string, any>;
|
|
73
|
+
score: number;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* @param {number} id
|
|
77
|
+
* @returns {{ id: number, content: string, metadata: Record<string, any> } | null}
|
|
78
|
+
*/
|
|
79
|
+
get(id: number): {
|
|
80
|
+
id: number;
|
|
81
|
+
content: string;
|
|
82
|
+
metadata: Record<string, any>;
|
|
83
|
+
} | null;
|
|
84
|
+
/**
|
|
85
|
+
* @param {number} id
|
|
86
|
+
* @returns {void}
|
|
87
|
+
*/
|
|
88
|
+
delete(id: number): void;
|
|
89
|
+
close(): void;
|
|
90
|
+
}
|
package/src/store-sqlite.js
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* delete(id) → void
|
|
11
11
|
*
|
|
12
12
|
* Requires peer dep: better-sqlite3
|
|
13
|
+
*
|
|
14
|
+
* @typedef {object} ChunkRow
|
|
15
|
+
* @property {number} id
|
|
16
|
+
* @property {string} content
|
|
17
|
+
* @property {string} metadata - JSON-serialized metadata.
|
|
18
|
+
* @property {number} [rank] - FTS5 rank (search rows only).
|
|
13
19
|
*/
|
|
20
|
+
|
|
14
21
|
class SQLiteStore {
|
|
15
22
|
/**
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {string} options.path - Path to SQLite database file (required).
|
|
23
|
+
* @param {{ path?: string }} [options]
|
|
18
24
|
* @throws {Error} `[SQLiteStore] requires options.path` — when path is missing.
|
|
19
25
|
* @throws {Error} `[SQLiteStore] requires better-sqlite3` — when peer dep is not installed.
|
|
20
26
|
*/
|
|
@@ -77,6 +83,11 @@ class SQLiteStore {
|
|
|
77
83
|
);
|
|
78
84
|
}
|
|
79
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {any} content
|
|
88
|
+
* @param {Record<string, any>} [metadata]
|
|
89
|
+
* @returns {number} id
|
|
90
|
+
*/
|
|
80
91
|
store(content, metadata = {}) {
|
|
81
92
|
const result = this._insertStmt.run(
|
|
82
93
|
content,
|
|
@@ -86,10 +97,15 @@ class SQLiteStore {
|
|
|
86
97
|
return Number(result.lastInsertRowid);
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} query
|
|
102
|
+
* @param {{ limit?: number }} [options]
|
|
103
|
+
* @returns {Array<{ id: number, content: string, metadata: Record<string, any>, score: number }>}
|
|
104
|
+
*/
|
|
89
105
|
search(query, options = {}) {
|
|
90
106
|
const limit = options.limit || 10;
|
|
91
107
|
if (!query) {
|
|
92
|
-
return this._allStmt.all(limit).map(row => ({
|
|
108
|
+
return this._allStmt.all(limit).map((/** @type {ChunkRow} */ row) => ({
|
|
93
109
|
id: row.id,
|
|
94
110
|
content: row.content,
|
|
95
111
|
metadata: JSON.parse(row.metadata),
|
|
@@ -100,14 +116,14 @@ class SQLiteStore {
|
|
|
100
116
|
const ftsQuery = query
|
|
101
117
|
.split(/\s+/)
|
|
102
118
|
.filter(Boolean)
|
|
103
|
-
.map(w => `"${w.replace(/"/g, '""')}"`)
|
|
119
|
+
.map((/** @type {string} */ w) => `"${w.replace(/"/g, '""')}"`)
|
|
104
120
|
.join(' OR ');
|
|
105
121
|
try {
|
|
106
|
-
return this._searchStmt.all(ftsQuery, limit).map(row => ({
|
|
122
|
+
return this._searchStmt.all(ftsQuery, limit).map((/** @type {ChunkRow} */ row) => ({
|
|
107
123
|
id: row.id,
|
|
108
124
|
content: row.content,
|
|
109
125
|
metadata: JSON.parse(row.metadata),
|
|
110
|
-
score: -row.rank, // FTS5 rank is negative (closer to 0 = better)
|
|
126
|
+
score: -(row.rank ?? 0), // FTS5 rank is negative (closer to 0 = better)
|
|
111
127
|
}));
|
|
112
128
|
} catch {
|
|
113
129
|
// If FTS query fails (e.g. special characters), return empty
|
|
@@ -115,12 +131,20 @@ class SQLiteStore {
|
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @param {number} id
|
|
136
|
+
* @returns {{ id: number, content: string, metadata: Record<string, any> } | null}
|
|
137
|
+
*/
|
|
118
138
|
get(id) {
|
|
119
|
-
const row = this._getStmt.get(id);
|
|
139
|
+
const row = /** @type {ChunkRow | undefined} */ (this._getStmt.get(id));
|
|
120
140
|
if (!row) return null;
|
|
121
141
|
return { id: row.id, content: row.content, metadata: JSON.parse(row.metadata) };
|
|
122
142
|
}
|
|
123
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @param {number} id
|
|
146
|
+
* @returns {void}
|
|
147
|
+
*/
|
|
124
148
|
delete(id) {
|
|
125
149
|
this._deleteStmt.run(id);
|
|
126
150
|
}
|
package/src/stores.d.ts
ADDED
package/src/stream.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type StreamEvent = {
|
|
2
|
+
/**
|
|
3
|
+
* - Event type identifier.
|
|
4
|
+
*/
|
|
5
|
+
type: string;
|
|
6
|
+
/**
|
|
7
|
+
* - Associated task identifier.
|
|
8
|
+
*/
|
|
9
|
+
taskId?: string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* - Arbitrary event payload.
|
|
12
|
+
*/
|
|
13
|
+
data?: any;
|
|
14
|
+
/**
|
|
15
|
+
* - ISO timestamp; auto-filled on emit if absent.
|
|
16
|
+
*/
|
|
17
|
+
ts?: string | undefined;
|
|
18
|
+
};
|
|
19
|
+
export type Transport = {
|
|
20
|
+
/**
|
|
21
|
+
* - Sink for emitted events.
|
|
22
|
+
*/
|
|
23
|
+
write: (event: StreamEvent) => void;
|
|
24
|
+
};
|
|
25
|
+
export type StreamOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* - Optional transport sink for emitted events.
|
|
28
|
+
*/
|
|
29
|
+
transport?: Transport | null | undefined;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Structured event streaming for observability and cross-process communication.
|
|
33
|
+
*
|
|
34
|
+
* Interface:
|
|
35
|
+
* emit(event) → void
|
|
36
|
+
* subscribe(callback) → unsubscribe function
|
|
37
|
+
*
|
|
38
|
+
* Event shape:
|
|
39
|
+
* { type, taskId, data, ts }
|
|
40
|
+
*
|
|
41
|
+
* Transport:
|
|
42
|
+
* Object with write(event) method — e.g. JsonlTransport
|
|
43
|
+
* null — disabled (subscribers only)
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} StreamEvent
|
|
47
|
+
* @property {string} type - Event type identifier.
|
|
48
|
+
* @property {string} [taskId] - Associated task identifier.
|
|
49
|
+
* @property {*} [data] - Arbitrary event payload.
|
|
50
|
+
* @property {string} [ts] - ISO timestamp; auto-filled on emit if absent.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {object} Transport
|
|
54
|
+
* @property {(event: StreamEvent) => void} write - Sink for emitted events.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {object} StreamOptions
|
|
58
|
+
* @property {Transport|null} [transport] - Optional transport sink for emitted events.
|
|
59
|
+
*/
|
|
60
|
+
export class Stream {
|
|
61
|
+
/**
|
|
62
|
+
* @param {StreamOptions} [options={}]
|
|
63
|
+
*/
|
|
64
|
+
constructor(options?: StreamOptions);
|
|
65
|
+
/** @type {Transport|null} */
|
|
66
|
+
_transport: Transport | null;
|
|
67
|
+
/** @type {Array<(e: StreamEvent) => void>} */
|
|
68
|
+
_subscribers: Array<(e: StreamEvent) => void>;
|
|
69
|
+
/**
|
|
70
|
+
* @param {StreamEvent} event - Event to broadcast to subscribers and transport.
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
emit(event: StreamEvent): void;
|
|
74
|
+
/**
|
|
75
|
+
* @param {(e: StreamEvent) => void} callback - Invoked with each emitted event.
|
|
76
|
+
* @returns {() => void} Unsubscribe function.
|
|
77
|
+
*/
|
|
78
|
+
subscribe(callback: (e: StreamEvent) => void): () => void;
|
|
79
|
+
}
|
package/src/stream.js
CHANGED
|
@@ -14,12 +14,40 @@
|
|
|
14
14
|
* Object with write(event) method — e.g. JsonlTransport
|
|
15
15
|
* null — disabled (subscribers only)
|
|
16
16
|
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} StreamEvent
|
|
20
|
+
* @property {string} type - Event type identifier.
|
|
21
|
+
* @property {string} [taskId] - Associated task identifier.
|
|
22
|
+
* @property {*} [data] - Arbitrary event payload.
|
|
23
|
+
* @property {string} [ts] - ISO timestamp; auto-filled on emit if absent.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} Transport
|
|
28
|
+
* @property {(event: StreamEvent) => void} write - Sink for emitted events.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} StreamOptions
|
|
33
|
+
* @property {Transport|null} [transport] - Optional transport sink for emitted events.
|
|
34
|
+
*/
|
|
35
|
+
|
|
17
36
|
class Stream {
|
|
37
|
+
/**
|
|
38
|
+
* @param {StreamOptions} [options={}]
|
|
39
|
+
*/
|
|
18
40
|
constructor(options = {}) {
|
|
41
|
+
/** @type {Transport|null} */
|
|
19
42
|
this._transport = options.transport || null;
|
|
43
|
+
/** @type {Array<(e: StreamEvent) => void>} */
|
|
20
44
|
this._subscribers = [];
|
|
21
45
|
}
|
|
22
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @param {StreamEvent} event - Event to broadcast to subscribers and transport.
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
23
51
|
emit(event) {
|
|
24
52
|
const full = { ...event, ts: event.ts || new Date().toISOString() };
|
|
25
53
|
for (const fn of this._subscribers) {
|
|
@@ -30,6 +58,10 @@ class Stream {
|
|
|
30
58
|
}
|
|
31
59
|
}
|
|
32
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @param {(e: StreamEvent) => void} callback - Invoked with each emitted event.
|
|
63
|
+
* @returns {() => void} Unsubscribe function.
|
|
64
|
+
*/
|
|
33
65
|
subscribe(callback) {
|
|
34
66
|
this._subscribers.push(callback);
|
|
35
67
|
return () => {
|
package/src/tools.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createBrowsingTools } from "../tools/browse";
|
|
2
|
+
import { createMobileTools } from "../tools/mobile";
|
|
3
|
+
import { createShellTools } from "../tools/shell";
|
|
4
|
+
import { createSpawnTool } from "../tools/spawn";
|
|
5
|
+
import { spawnChild } from "../tools/spawn";
|
|
6
|
+
import { createDeferTool } from "../tools/defer";
|
|
7
|
+
import { readQueue as readDeferQueue } from "../tools/defer";
|
|
8
|
+
export { createBrowsingTools, createMobileTools, createShellTools, createSpawnTool, spawnChild, createDeferTool, readDeferQueue };
|