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.
Files changed (68) hide show
  1. package/README.md +1 -0
  2. package/bareagent.context.md +1149 -0
  3. package/bin/cli.d.ts +4 -0
  4. package/bin/cli.js +40 -10
  5. package/bin/test-provider.d.ts +2 -0
  6. package/bin/test-provider.js +5 -1
  7. package/index.d.ts +20 -0
  8. package/package.json +46 -10
  9. package/src/bareguard-adapter.d.ts +118 -0
  10. package/src/bareguard-adapter.js +75 -3
  11. package/src/checkpoint.d.ts +61 -0
  12. package/src/checkpoint.js +17 -8
  13. package/src/circuit-breaker.d.ts +70 -0
  14. package/src/circuit-breaker.js +20 -4
  15. package/src/errors.d.ts +106 -0
  16. package/src/errors.js +50 -1
  17. package/src/loop.d.ts +135 -0
  18. package/src/loop.js +73 -17
  19. package/src/mcp-bridge.d.ts +133 -0
  20. package/src/mcp-bridge.js +179 -27
  21. package/src/mcp.d.ts +4 -0
  22. package/src/memory.d.ts +50 -0
  23. package/src/memory.js +22 -2
  24. package/src/planner.d.ts +62 -0
  25. package/src/planner.js +26 -7
  26. package/src/provider-anthropic.d.ts +55 -0
  27. package/src/provider-anthropic.js +32 -11
  28. package/src/provider-clipipe.d.ts +86 -0
  29. package/src/provider-clipipe.js +28 -18
  30. package/src/provider-fallback.d.ts +44 -0
  31. package/src/provider-fallback.js +18 -8
  32. package/src/provider-ollama.d.ts +41 -0
  33. package/src/provider-ollama.js +27 -7
  34. package/src/provider-openai.d.ts +57 -0
  35. package/src/provider-openai.js +31 -16
  36. package/src/providers.d.ts +6 -0
  37. package/src/providers.js +8 -0
  38. package/src/retry.d.ts +44 -0
  39. package/src/retry.js +15 -1
  40. package/src/run-plan.d.ts +126 -0
  41. package/src/run-plan.js +46 -13
  42. package/src/scheduler.d.ts +102 -0
  43. package/src/scheduler.js +32 -4
  44. package/src/state.d.ts +45 -0
  45. package/src/state.js +18 -2
  46. package/src/store-jsonfile.d.ts +85 -0
  47. package/src/store-jsonfile.js +33 -8
  48. package/src/store-sqlite.d.ts +90 -0
  49. package/src/store-sqlite.js +31 -7
  50. package/src/stores.d.ts +3 -0
  51. package/src/stream.d.ts +79 -0
  52. package/src/stream.js +32 -0
  53. package/src/tools.d.ts +8 -0
  54. package/src/transport-jsonl.d.ts +30 -0
  55. package/src/transport-jsonl.js +13 -0
  56. package/src/transports.d.ts +2 -0
  57. package/tools/browse.d.ts +10 -0
  58. package/tools/browse.js +2 -0
  59. package/tools/defer.d.ts +33 -0
  60. package/tools/defer.js +12 -3
  61. package/tools/mobile.d.ts +34 -0
  62. package/tools/mobile.js +28 -15
  63. package/tools/shell.d.ts +31 -0
  64. package/tools/shell.js +55 -6
  65. package/tools/spawn.d.ts +107 -0
  66. package/tools/spawn.js +24 -5
  67. package/types/index.d.ts +66 -0
  68. 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
- const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[unit];
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
+ }
@@ -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 {object} options
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
+ }
@@ -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 {object} options
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
  }
@@ -0,0 +1,3 @@
1
+ import { SQLiteStore } from "./store-sqlite";
2
+ import { JsonFileStore } from "./store-jsonfile";
3
+ export { SQLiteStore as SQLite, JsonFileStore as JsonFile };
@@ -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 };