bare-agent 0.10.4 → 0.12.0

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 (65) hide show
  1. package/bin/cli.d.ts +4 -0
  2. package/bin/cli.js +70 -12
  3. package/bin/test-provider.d.ts +2 -0
  4. package/bin/test-provider.js +5 -1
  5. package/index.d.ts +20 -0
  6. package/package.json +44 -10
  7. package/src/bareguard-adapter.d.ts +118 -0
  8. package/src/bareguard-adapter.js +75 -3
  9. package/src/checkpoint.d.ts +61 -0
  10. package/src/checkpoint.js +17 -8
  11. package/src/circuit-breaker.d.ts +70 -0
  12. package/src/circuit-breaker.js +20 -4
  13. package/src/errors.d.ts +106 -0
  14. package/src/errors.js +50 -1
  15. package/src/loop.d.ts +135 -0
  16. package/src/loop.js +80 -18
  17. package/src/mcp-bridge.d.ts +133 -0
  18. package/src/mcp-bridge.js +199 -26
  19. package/src/mcp.d.ts +4 -0
  20. package/src/memory.d.ts +50 -0
  21. package/src/memory.js +22 -2
  22. package/src/planner.d.ts +62 -0
  23. package/src/planner.js +26 -7
  24. package/src/provider-anthropic.d.ts +55 -0
  25. package/src/provider-anthropic.js +34 -10
  26. package/src/provider-clipipe.d.ts +86 -0
  27. package/src/provider-clipipe.js +28 -18
  28. package/src/provider-fallback.d.ts +44 -0
  29. package/src/provider-fallback.js +18 -8
  30. package/src/provider-ollama.d.ts +41 -0
  31. package/src/provider-ollama.js +29 -7
  32. package/src/provider-openai.d.ts +57 -0
  33. package/src/provider-openai.js +34 -7
  34. package/src/providers.d.ts +6 -0
  35. package/src/retry.d.ts +44 -0
  36. package/src/retry.js +15 -1
  37. package/src/run-plan.d.ts +126 -0
  38. package/src/run-plan.js +46 -13
  39. package/src/scheduler.d.ts +102 -0
  40. package/src/scheduler.js +32 -4
  41. package/src/state.d.ts +45 -0
  42. package/src/state.js +18 -2
  43. package/src/store-jsonfile.d.ts +85 -0
  44. package/src/store-jsonfile.js +50 -8
  45. package/src/store-sqlite.d.ts +90 -0
  46. package/src/store-sqlite.js +31 -7
  47. package/src/stores.d.ts +3 -0
  48. package/src/stream.d.ts +79 -0
  49. package/src/stream.js +32 -0
  50. package/src/tools.d.ts +8 -0
  51. package/src/transport-jsonl.d.ts +30 -0
  52. package/src/transport-jsonl.js +13 -0
  53. package/src/transports.d.ts +2 -0
  54. package/tools/browse.d.ts +10 -0
  55. package/tools/browse.js +2 -0
  56. package/tools/defer.d.ts +33 -0
  57. package/tools/defer.js +12 -3
  58. package/tools/mobile.d.ts +34 -0
  59. package/tools/mobile.js +28 -15
  60. package/tools/shell.d.ts +31 -0
  61. package/tools/shell.js +83 -6
  62. package/tools/spawn.d.ts +107 -0
  63. package/tools/spawn.js +24 -5
  64. package/types/index.d.ts +66 -0
  65. package/types/shims.d.ts +16 -0
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
+ }
@@ -2,60 +2,102 @@
2
2
 
3
3
  const { readFileSync, writeFileSync, existsSync } = require('node:fs');
4
4
 
5
+ // Past this many entries, the O(n) scan + whole-file rewrite per write becomes a
6
+ // real latency/availability concern. Warn once (per instance) and point at SQLiteStore.
7
+ const LARGE_STORE_THRESHOLD = 10000;
8
+
5
9
  /**
6
10
  * JSON file memory store. Zero deps, case-insensitive substring search.
7
11
  *
12
+ * SCALING: intended for small memory sets. `search()` is an O(n) substring scan
13
+ * (no index), and every `store()`/`delete()` re-serializes and rewrites the entire
14
+ * file synchronously. Fine for hundreds–low-thousands of entries; for larger or
15
+ * write-heavy memory use SQLiteStore (FTS5 index, parameterized, incremental writes).
16
+ *
8
17
  * Interface (implements Memory store contract):
9
18
  * store(content, metadata) → id
10
19
  * search(query, options) → [{ id, content, metadata, score }]
11
20
  * get(id) → { content, metadata }
12
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
13
28
  */
29
+
14
30
  class JsonFileStore {
15
31
  /**
16
- * @param {object} options
17
- * @param {string} options.path - Path to JSON file (required).
32
+ * @param {{ path?: string }} [options]
18
33
  * @throws {Error} `[JsonFileStore] requires options.path` — when path is missing.
19
34
  */
20
35
  constructor(options = {}) {
21
36
  if (!options.path) throw new Error('[JsonFileStore] requires options.path');
22
37
  this._path = options.path;
38
+ /** @type {JsonRecord[]} */
23
39
  this._data = existsSync(this._path)
24
40
  ? JSON.parse(readFileSync(this._path, 'utf8'))
25
41
  : [];
26
42
  this._nextId = this._data.length
27
- ? 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
28
44
  : 1;
45
+ this._warnedLarge = false;
29
46
  }
30
47
 
31
48
  _save() {
32
49
  writeFileSync(this._path, JSON.stringify(this._data, null, 2));
33
50
  }
34
51
 
52
+ /**
53
+ * @param {any} content
54
+ * @param {Record<string, any>} [metadata]
55
+ * @returns {number} id
56
+ */
35
57
  store(content, metadata = {}) {
36
58
  const id = this._nextId++;
37
59
  this._data.push({ id, content, metadata, createdAt: new Date().toISOString() });
38
60
  this._save();
61
+ if (!this._warnedLarge && this._data.length > LARGE_STORE_THRESHOLD) {
62
+ this._warnedLarge = true;
63
+ console.warn(
64
+ `[JsonFileStore] ${this._data.length} entries — search is O(n) and every write rewrites ` +
65
+ `the whole file. Switch to SQLiteStore for memory this size.`,
66
+ );
67
+ }
39
68
  return id;
40
69
  }
41
70
 
71
+ /**
72
+ * @param {string} query
73
+ * @param {{ limit?: number }} [options]
74
+ * @returns {Array<JsonRecord & { score: number }>}
75
+ */
42
76
  search(query, options = {}) {
43
77
  const limit = options.limit || 10;
44
78
  const q = (query || '').toLowerCase();
45
- 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 }));
46
80
  return this._data
47
- .filter(d => d.content.toLowerCase().includes(q))
81
+ .filter((/** @type {JsonRecord} */ d) => d.content.toLowerCase().includes(q))
48
82
  .slice(0, limit)
49
- .map(d => ({ ...d, score: 1 }));
83
+ .map((/** @type {JsonRecord} */ d) => ({ ...d, score: 1 }));
50
84
  }
51
85
 
86
+ /**
87
+ * @param {number} id
88
+ * @returns {{ id: number, content: any, metadata: Record<string, any> } | null}
89
+ */
52
90
  get(id) {
53
- const item = this._data.find(d => d.id === id);
91
+ const item = this._data.find((/** @type {JsonRecord} */ d) => d.id === id);
54
92
  return item ? { id: item.id, content: item.content, metadata: item.metadata } : null;
55
93
  }
56
94
 
95
+ /**
96
+ * @param {number} id
97
+ * @returns {void}
98
+ */
57
99
  delete(id) {
58
- this._data = this._data.filter(d => d.id !== id);
100
+ this._data = this._data.filter((/** @type {JsonRecord} */ d) => d.id !== id);
59
101
  this._save();
60
102
  }
61
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 };
@@ -0,0 +1,30 @@
1
+ export type JsonlTransportOptions = {
2
+ /**
3
+ * - Writable stream to write JSONL lines to. Defaults to process.stdout.
4
+ */
5
+ output?: NodeJS.WritableStream | undefined;
6
+ };
7
+ /**
8
+ * JSONL transport: one JSON object per line to a writable stream.
9
+ * Default: process.stdout. Pipe-friendly, parseable by any language.
10
+ *
11
+ * Debug output goes to stderr (never pollutes stdout).
12
+ */
13
+ /**
14
+ * @typedef {object} JsonlTransportOptions
15
+ * @property {NodeJS.WritableStream} [output] - Writable stream to write JSONL lines to. Defaults to process.stdout.
16
+ */
17
+ export class JsonlTransport {
18
+ /**
19
+ * @param {JsonlTransportOptions} [options={}]
20
+ */
21
+ constructor(options?: JsonlTransportOptions);
22
+ _output: NodeJS.WritableStream | (NodeJS.WriteStream & {
23
+ fd: 1;
24
+ });
25
+ /**
26
+ * @param {*} event - Event object to serialize as one JSON line.
27
+ * @returns {void}
28
+ */
29
+ write(event: any): void;
30
+ }
@@ -6,11 +6,24 @@
6
6
  *
7
7
  * Debug output goes to stderr (never pollutes stdout).
8
8
  */
9
+
10
+ /**
11
+ * @typedef {object} JsonlTransportOptions
12
+ * @property {NodeJS.WritableStream} [output] - Writable stream to write JSONL lines to. Defaults to process.stdout.
13
+ */
14
+
9
15
  class JsonlTransport {
16
+ /**
17
+ * @param {JsonlTransportOptions} [options={}]
18
+ */
10
19
  constructor(options = {}) {
11
20
  this._output = options.output || process.stdout;
12
21
  }
13
22
 
23
+ /**
24
+ * @param {*} event - Event object to serialize as one JSON line.
25
+ * @returns {void}
26
+ */
14
27
  write(event) {
15
28
  this._output.write(JSON.stringify(event) + '\n');
16
29
  }
@@ -0,0 +1,2 @@
1
+ export { JsonlTransport };
2
+ import { JsonlTransport } from "./transport-jsonl";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Creates browsing tools via barebrowse (optional dep).
3
+ * Returns { tools, close } or null if barebrowse is not installed.
4
+ * @param {object} [opts] - Options passed to barebrowse createBrowseTools
5
+ * @returns {Promise<{tools: Array, close: Function}|null>}
6
+ */
7
+ export function createBrowsingTools(opts?: object): Promise<{
8
+ tools: any[];
9
+ close: Function;
10
+ } | null>;
package/tools/browse.js CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
  async function createBrowsingTools(opts = {}) {
10
10
  try {
11
+ // barebrowse is an optional dep; the subpath is declared as an ambient `any`
12
+ // module in types/shims.d.ts and resolves to `any` at runtime.
11
13
  const { createBrowseTools } = await import('barebrowse/bareagent');
12
14
  return createBrowseTools(opts);
13
15
  } catch {