@agentbus/agentbus-client 0.11.2

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 ADDED
@@ -0,0 +1,77 @@
1
+ # @agentbus/agentbus-client
2
+
3
+ TypeScript / Node.js EventEmitter client for [AgentBus](https://github.com/onicarps/agentbus).
4
+
5
+ - Resolves `events.db` from `AGENTBUS_WORKSPACE`
6
+ - Debounced `fs.watch` + slow fallback timer to trigger polls
7
+ - Spawns `agentbus mcp-serve` over stdio (or accepts an injected MCP client)
8
+ - `publish` / `poll` map to `agentbus_publish` / `agentbus_poll`
9
+ - Emits `event` and per-topic events (`bus.on('okf/handoff', ...)`)
10
+
11
+ ## Prerequisites
12
+
13
+ 1. Python AgentBus installed and on `PATH` (or set `AGENTBUS_BIN`):
14
+
15
+ ```bash
16
+ pip install 'okf-agentbus[devex]'
17
+ # or use a repo venv:
18
+ export AGENTBUS_BIN=/path/to/agentbus/.venv/bin/agentbus
19
+ ```
20
+
21
+ 2. Workspace initialized:
22
+
23
+ ```bash
24
+ export AGENTBUS_WORKSPACE=/path/to/project
25
+ agentbus token ensure --workspace "$AGENTBUS_WORKSPACE"
26
+ ```
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ cd packages/js/agentbus-client
32
+ npm install
33
+ npm test
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Auto stdio (recommended)
39
+
40
+ ```ts
41
+ import { AgentBus } from "@agentbus/agentbus-client";
42
+
43
+ const bus = new AgentBus({
44
+ workspace: process.env.AGENTBUS_WORKSPACE,
45
+ topics: ["okf/handoff"], // or ["*"] to expand via agentbus_status
46
+ });
47
+
48
+ bus.on("okf/handoff", (payload, meta) => {
49
+ console.log("handoff", payload, meta.event_id);
50
+ });
51
+
52
+ await bus.connect(); // spawns `agentbus mcp-serve`
53
+ await bus.publish("okf/handoff", {
54
+ from: "node-agent",
55
+ to: "swarm",
56
+ summary: "hello from TS",
57
+ });
58
+
59
+ // later
60
+ await bus.disconnect();
61
+ ```
62
+
63
+ ### Injected MCP client (tests / custom transports)
64
+
65
+ ```ts
66
+ import { AgentBus, createStdioMcpClient } from "@agentbus/agentbus-client";
67
+
68
+ const mcp = await createStdioMcpClient({
69
+ workspace: process.env.AGENTBUS_WORKSPACE!,
70
+ command: process.env.AGENTBUS_BIN, // optional
71
+ });
72
+
73
+ const bus = new AgentBus({ mcp, workspace: process.env.AGENTBUS_WORKSPACE });
74
+ await bus.connect();
75
+ ```
76
+
77
+ Set `AGENTBUS_WORKSPACE` to the project root that contains `.agentbus/events.db`.
@@ -0,0 +1,49 @@
1
+ import { EventEmitter } from "events";
2
+ import type { AgentBusOptions, BusEvent } from "./types";
3
+ export { getDatabasePath } from "./locator";
4
+ export { DatabaseWatcher } from "./watcher";
5
+ export { createStdioMcpClient } from "./stdio";
6
+ export type { AgentBusOptions, BusEvent, McpToolClient, } from "./types";
7
+ export type { StdioMcpOptions } from "./stdio";
8
+ /**
9
+ * Idiomatic Node client: EventEmitter + publish/poll over AgentBus MCP tools,
10
+ * with fs.watch-triggered polls and a slow timer fallback.
11
+ *
12
+ * When `options.mcp` is omitted, `connect()` spawns `agentbus mcp-serve` over
13
+ * stdio (requires `agentbus` on PATH or `AGENTBUS_BIN`).
14
+ */
15
+ export declare class AgentBus extends EventEmitter {
16
+ private watcher;
17
+ private options;
18
+ /** Global high-water (min of per-topic cursors after last poll). */
19
+ private lastId;
20
+ /** Exclusive since_id per topic — avoids skipping pages across topics. */
21
+ private topicCursors;
22
+ private initialSince;
23
+ private connected;
24
+ private polling;
25
+ private ownsMcp;
26
+ private mcp;
27
+ constructor(options?: AgentBusOptions);
28
+ connect(): Promise<void>;
29
+ publish(topic: string, payload: unknown, opts?: {
30
+ producerId?: string;
31
+ }): Promise<unknown>;
32
+ /** Force a poll (also used by watcher). Drains each topic fully. */
33
+ poll(): Promise<BusEvent[]>;
34
+ get cursor(): number;
35
+ /** Per-topic exclusive cursors (for diagnostics / multi-topic correctness). */
36
+ getTopicCursor(topic: string): number;
37
+ disconnect(): Promise<void>;
38
+ private reportBackgroundError;
39
+ private rollbackConnect;
40
+ private requireMcp;
41
+ private resolveTopics;
42
+ }
43
+ /** Pull JSON out of MCP CallToolResult envelopes or raw values. */
44
+ export declare function unwrapToolResult(raw: unknown): unknown;
45
+ export declare function parsePollPage(raw: unknown): {
46
+ events: BusEvent[];
47
+ hasMore: boolean;
48
+ };
49
+ export declare function normalizePollResult(raw: unknown): BusEvent[];
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentBus = exports.createStdioMcpClient = exports.DatabaseWatcher = exports.getDatabasePath = void 0;
4
+ exports.unwrapToolResult = unwrapToolResult;
5
+ exports.parsePollPage = parsePollPage;
6
+ exports.normalizePollResult = normalizePollResult;
7
+ const events_1 = require("events");
8
+ const watcher_1 = require("./watcher");
9
+ const locator_1 = require("./locator");
10
+ const stdio_1 = require("./stdio");
11
+ var locator_2 = require("./locator");
12
+ Object.defineProperty(exports, "getDatabasePath", { enumerable: true, get: function () { return locator_2.getDatabasePath; } });
13
+ var watcher_2 = require("./watcher");
14
+ Object.defineProperty(exports, "DatabaseWatcher", { enumerable: true, get: function () { return watcher_2.DatabaseWatcher; } });
15
+ var stdio_2 = require("./stdio");
16
+ Object.defineProperty(exports, "createStdioMcpClient", { enumerable: true, get: function () { return stdio_2.createStdioMcpClient; } });
17
+ const DEFAULT_POLL_LIMIT = 50;
18
+ /**
19
+ * Idiomatic Node client: EventEmitter + publish/poll over AgentBus MCP tools,
20
+ * with fs.watch-triggered polls and a slow timer fallback.
21
+ *
22
+ * When `options.mcp` is omitted, `connect()` spawns `agentbus mcp-serve` over
23
+ * stdio (requires `agentbus` on PATH or `AGENTBUS_BIN`).
24
+ */
25
+ class AgentBus extends events_1.EventEmitter {
26
+ watcher = null;
27
+ options;
28
+ /** Global high-water (min of per-topic cursors after last poll). */
29
+ lastId;
30
+ /** Exclusive since_id per topic — avoids skipping pages across topics. */
31
+ topicCursors = new Map();
32
+ initialSince;
33
+ connected = false;
34
+ polling = false;
35
+ ownsMcp = false;
36
+ mcp;
37
+ constructor(options = {}) {
38
+ super();
39
+ this.options = options;
40
+ this.initialSince = options.sinceId ?? 0;
41
+ this.lastId = this.initialSince;
42
+ this.mcp = options.mcp;
43
+ }
44
+ async connect() {
45
+ if (this.connected)
46
+ return;
47
+ const spawnedMcpHere = !this.mcp;
48
+ try {
49
+ if (!this.mcp) {
50
+ const producerId = this.options.producerId ?? process.env.AGENTBUS_PRODUCER_ID;
51
+ const env = {
52
+ ...(this.options.stdio?.env ?? {}),
53
+ };
54
+ if (producerId) {
55
+ env.AGENTBUS_PRODUCER_ID = producerId;
56
+ }
57
+ this.mcp = await (0, stdio_1.createStdioMcpClient)({
58
+ workspace: this.options.workspace,
59
+ command: this.options.stdio?.command,
60
+ args: this.options.stdio?.args,
61
+ cwd: this.options.stdio?.cwd,
62
+ env,
63
+ });
64
+ this.ownsMcp = true;
65
+ }
66
+ const dbPath = (0, locator_1.getDatabasePath)(this.options.workspace);
67
+ this.watcher = new watcher_1.DatabaseWatcher(dbPath, () => {
68
+ void this.poll().catch((err) => {
69
+ this.reportBackgroundError(err);
70
+ });
71
+ }, this.options.fallbackMs ?? 5000);
72
+ this.watcher.start();
73
+ // Initial catch-up poll — must succeed before we mark connected.
74
+ await this.poll();
75
+ this.connected = true;
76
+ }
77
+ catch (err) {
78
+ await this.rollbackConnect(spawnedMcpHere);
79
+ throw err;
80
+ }
81
+ }
82
+ async publish(topic, payload, opts) {
83
+ const mcp = this.requireMcp();
84
+ // Server expects payload as object (dict), not a JSON string.
85
+ const body = typeof payload === "string"
86
+ ? safeParseJson(payload) ?? { text: payload }
87
+ : payload;
88
+ const producerId = opts?.producerId ??
89
+ this.options.producerId ??
90
+ process.env.AGENTBUS_PRODUCER_ID;
91
+ const args = {
92
+ topic,
93
+ payload: body,
94
+ };
95
+ if (producerId) {
96
+ args.producer_id = producerId;
97
+ }
98
+ const result = await mcp.callTool("agentbus_publish", args);
99
+ return unwrapToolResult(result);
100
+ }
101
+ /** Force a poll (also used by watcher). Drains each topic fully. */
102
+ async poll() {
103
+ if (this.polling)
104
+ return [];
105
+ this.polling = true;
106
+ try {
107
+ const mcp = this.mcp;
108
+ if (!mcp) {
109
+ // Without MCP, watcher still runs; no events to emit.
110
+ return [];
111
+ }
112
+ const topics = await this.resolveTopics(mcp);
113
+ const merged = [];
114
+ const seen = new Set();
115
+ // Commit + emit per topic after that topic drains. If a later topic fails,
116
+ // earlier topics keep their events and cursors (no silent drops).
117
+ for (const topic of topics) {
118
+ let since = this.topicCursors.get(topic) ?? this.initialSince;
119
+ let hasMore = true;
120
+ const topicEvents = [];
121
+ while (hasMore) {
122
+ const raw = await mcp.callTool("agentbus_poll", {
123
+ topic,
124
+ since_id: since,
125
+ limit: DEFAULT_POLL_LIMIT,
126
+ });
127
+ const page = parsePollPage(raw);
128
+ hasMore = page.hasMore;
129
+ for (const ev of page.events) {
130
+ if (ev.event_id > since) {
131
+ since = ev.event_id;
132
+ }
133
+ if (seen.has(ev.event_id))
134
+ continue;
135
+ seen.add(ev.event_id);
136
+ topicEvents.push(ev);
137
+ }
138
+ // Safety: empty page ends the loop even if has_more is stale.
139
+ if (page.events.length === 0) {
140
+ hasMore = false;
141
+ }
142
+ }
143
+ this.topicCursors.set(topic, since);
144
+ // Preserve arrival order within a topic (server returns ASC by event_id).
145
+ for (const ev of topicEvents) {
146
+ this.emit("event", ev);
147
+ this.emit(ev.topic, ev.payload, ev);
148
+ }
149
+ merged.push(...topicEvents);
150
+ }
151
+ // Global cursor = min of topic cursors (never claims past unfetched pages).
152
+ if (topics.length > 0) {
153
+ let minCursor = Number.POSITIVE_INFINITY;
154
+ for (const topic of topics) {
155
+ const c = this.topicCursors.get(topic) ?? this.initialSince;
156
+ if (c < minCursor)
157
+ minCursor = c;
158
+ }
159
+ if (Number.isFinite(minCursor)) {
160
+ this.lastId = minCursor;
161
+ }
162
+ }
163
+ return merged;
164
+ }
165
+ finally {
166
+ this.polling = false;
167
+ }
168
+ }
169
+ get cursor() {
170
+ return this.lastId;
171
+ }
172
+ /** Per-topic exclusive cursors (for diagnostics / multi-topic correctness). */
173
+ getTopicCursor(topic) {
174
+ return this.topicCursors.get(topic) ?? this.initialSince;
175
+ }
176
+ async disconnect() {
177
+ if (this.watcher) {
178
+ this.watcher.stop();
179
+ this.watcher = null;
180
+ }
181
+ this.connected = false;
182
+ // Only close MCP clients we spawned; injected clients are caller-owned.
183
+ if (this.ownsMcp && this.mcp?.close) {
184
+ await this.mcp.close();
185
+ this.mcp = undefined;
186
+ this.ownsMcp = false;
187
+ }
188
+ }
189
+ reportBackgroundError(err) {
190
+ // EventEmitter "error" throws if no listeners — guard that path.
191
+ if (this.listenerCount("error") > 0) {
192
+ this.emit("error", err);
193
+ }
194
+ else {
195
+ this.emit("pollError", err);
196
+ }
197
+ }
198
+ async rollbackConnect(spawnedMcpHere) {
199
+ if (this.watcher) {
200
+ this.watcher.stop();
201
+ this.watcher = null;
202
+ }
203
+ this.connected = false;
204
+ if (spawnedMcpHere && this.ownsMcp && this.mcp?.close) {
205
+ try {
206
+ await this.mcp.close();
207
+ }
208
+ catch {
209
+ /* ignore close errors during rollback */
210
+ }
211
+ this.mcp = undefined;
212
+ this.ownsMcp = false;
213
+ }
214
+ }
215
+ requireMcp() {
216
+ if (!this.mcp) {
217
+ throw new Error("AgentBus.publish requires connect() first (or pass options.mcp)");
218
+ }
219
+ return this.mcp;
220
+ }
221
+ async resolveTopics(mcp) {
222
+ const configured = this.options.topics ?? ["okf/handoff"];
223
+ if (!configured.includes("*")) {
224
+ return configured;
225
+ }
226
+ const raw = await mcp.callTool("agentbus_status", {});
227
+ const status = unwrapToolResult(raw);
228
+ const listed = Array.isArray(status?.topics) ? status.topics : [];
229
+ // Keep any explicit topics alongside *, drop the star marker.
230
+ const explicit = configured.filter((t) => t !== "*");
231
+ const set = new Set([...explicit, ...listed]);
232
+ return set.size > 0 ? [...set] : ["okf/handoff"];
233
+ }
234
+ }
235
+ exports.AgentBus = AgentBus;
236
+ function safeParseJson(text) {
237
+ try {
238
+ return JSON.parse(text);
239
+ }
240
+ catch {
241
+ return null;
242
+ }
243
+ }
244
+ /** Pull JSON out of MCP CallToolResult envelopes or raw values. */
245
+ function unwrapToolResult(raw) {
246
+ if (raw == null)
247
+ return null;
248
+ if (typeof raw === "string") {
249
+ return safeParseJson(raw) ?? raw;
250
+ }
251
+ if (typeof raw === "object") {
252
+ const obj = raw;
253
+ if (Array.isArray(obj.content)) {
254
+ for (const part of obj.content) {
255
+ if (part.type === "text" && typeof part.text === "string") {
256
+ return safeParseJson(part.text) ?? part.text;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ return raw;
262
+ }
263
+ function parsePollPage(raw) {
264
+ const data = unwrapToolResult(raw);
265
+ if (data == null)
266
+ return { events: [], hasMore: false };
267
+ if (Array.isArray(data)) {
268
+ return {
269
+ events: data.map(normalizeEvent),
270
+ hasMore: data.length >= DEFAULT_POLL_LIMIT,
271
+ };
272
+ }
273
+ if (typeof data === "object") {
274
+ const obj = data;
275
+ if (Array.isArray(obj.events)) {
276
+ const events = obj.events.map(normalizeEvent);
277
+ const hasMore = typeof obj.has_more === "boolean"
278
+ ? obj.has_more
279
+ : events.length >= DEFAULT_POLL_LIMIT;
280
+ return { events, hasMore };
281
+ }
282
+ }
283
+ return { events: [], hasMore: false };
284
+ }
285
+ function normalizePollResult(raw) {
286
+ return parsePollPage(raw).events;
287
+ }
288
+ function normalizeEvent(ev) {
289
+ // Payload may still be a JSON string from older projections.
290
+ if (typeof ev.payload === "string") {
291
+ const parsed = safeParseJson(ev.payload);
292
+ if (parsed != null)
293
+ return { ...ev, payload: parsed };
294
+ }
295
+ return ev;
296
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Resolve the AgentBus SQLite events.db path from AGENTBUS_WORKSPACE
3
+ * (or an explicit workspace override).
4
+ */
5
+ export declare function getDatabasePath(workspace?: string): string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getDatabasePath = getDatabasePath;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ /**
10
+ * Resolve the AgentBus SQLite events.db path from AGENTBUS_WORKSPACE
11
+ * (or an explicit workspace override).
12
+ */
13
+ function getDatabasePath(workspace) {
14
+ const ws = workspace ?? process.env.AGENTBUS_WORKSPACE;
15
+ if (!ws) {
16
+ throw new Error("AGENTBUS_WORKSPACE environment variable must be set");
17
+ }
18
+ if (!fs_1.default.existsSync(ws)) {
19
+ throw new Error("Workspace directory not found: " + ws);
20
+ }
21
+ return path_1.default.join(ws, ".agentbus", "events.db");
22
+ }
@@ -0,0 +1,23 @@
1
+ import type { McpToolClient } from "./types";
2
+ export type StdioMcpOptions = {
3
+ /** Workspace root containing `.agentbus/` (defaults to AGENTBUS_WORKSPACE). */
4
+ workspace?: string;
5
+ /** Binary to spawn (default: AGENTBUS_BIN or `agentbus`). */
6
+ command?: string;
7
+ /** Args after command (default: `mcp-serve --workspace <ws>`). */
8
+ args?: string[];
9
+ cwd?: string;
10
+ /** Extra env merged on top of MCP-safe defaults. */
11
+ env?: Record<string, string>;
12
+ /** Client name reported to the MCP server. */
13
+ clientName?: string;
14
+ /** Client version reported to the MCP server. */
15
+ clientVersion?: string;
16
+ };
17
+ /**
18
+ * Spawn `agentbus mcp-serve` over stdio and return a minimal tool client.
19
+ *
20
+ * Requires `agentbus` on PATH (or `AGENTBUS_BIN` / `options.command`) and a
21
+ * workspace with a valid token (`agentbus init` / `token ensure`).
22
+ */
23
+ export declare function createStdioMcpClient(options?: StdioMcpOptions): Promise<McpToolClient>;
package/dist/stdio.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStdioMcpClient = createStdioMcpClient;
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
6
+ /**
7
+ * Spawn `agentbus mcp-serve` over stdio and return a minimal tool client.
8
+ *
9
+ * Requires `agentbus` on PATH (or `AGENTBUS_BIN` / `options.command`) and a
10
+ * workspace with a valid token (`agentbus init` / `token ensure`).
11
+ */
12
+ async function createStdioMcpClient(options = {}) {
13
+ const workspace = options.workspace ?? process.env.AGENTBUS_WORKSPACE;
14
+ if (!workspace) {
15
+ throw new Error("createStdioMcpClient: workspace or AGENTBUS_WORKSPACE is required");
16
+ }
17
+ const command = options.command ?? process.env.AGENTBUS_BIN ?? "agentbus";
18
+ const args = options.args ?? ["mcp-serve", "--workspace", workspace];
19
+ const env = {
20
+ ...(0, stdio_js_1.getDefaultEnvironment)(),
21
+ ...options.env,
22
+ AGENTBUS_WORKSPACE: workspace,
23
+ };
24
+ // Preserve PATH so the child can resolve `agentbus` / python when needed.
25
+ if (process.env.PATH && !env.PATH) {
26
+ env.PATH = process.env.PATH;
27
+ }
28
+ const transport = new stdio_js_1.StdioClientTransport({
29
+ command,
30
+ args,
31
+ env,
32
+ cwd: options.cwd,
33
+ stderr: "pipe",
34
+ });
35
+ const client = new index_js_1.Client({
36
+ name: options.clientName ?? "@agentbus/agentbus-client",
37
+ version: options.clientVersion ?? "0.1.0",
38
+ });
39
+ await client.connect(transport);
40
+ return {
41
+ async callTool(name, toolArgs) {
42
+ const result = await client.callTool({
43
+ name,
44
+ arguments: toolArgs,
45
+ });
46
+ if (result.isError) {
47
+ const text = extractText(result);
48
+ throw new Error(text || `MCP tool ${name} returned isError without detail`);
49
+ }
50
+ return result;
51
+ },
52
+ async close() {
53
+ await client.close();
54
+ },
55
+ };
56
+ }
57
+ function extractText(result) {
58
+ if (result == null || typeof result !== "object")
59
+ return "";
60
+ const content = result.content;
61
+ if (!Array.isArray(content))
62
+ return "";
63
+ return content
64
+ .filter((p) => !!p &&
65
+ typeof p === "object" &&
66
+ p.type === "text" &&
67
+ typeof p.text === "string")
68
+ .map((p) => p.text)
69
+ .join("\n");
70
+ }
@@ -0,0 +1,44 @@
1
+ /** Minimal MCP tool surface used by AgentBus (stdio or mock). */
2
+ export type McpToolClient = {
3
+ callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
4
+ close?(): Promise<void> | void;
5
+ };
6
+ export type BusEvent = {
7
+ event_id: number;
8
+ topic: string;
9
+ producer_id?: string;
10
+ timestamp?: string;
11
+ status?: string;
12
+ payload: unknown;
13
+ };
14
+ export type AgentBusOptions = {
15
+ /** Override workspace (defaults to AGENTBUS_WORKSPACE). */
16
+ workspace?: string;
17
+ /** Fallback poll interval when fs.watch is quiet (ms). */
18
+ fallbackMs?: number;
19
+ /**
20
+ * Injected MCP tool caller for tests / custom transports.
21
+ * If omitted, `connect()` spawns stdio via `createStdioMcpClient`.
22
+ */
23
+ mcp?: McpToolClient;
24
+ /** Initial poll cursor (exclusive). */
25
+ sinceId?: number;
26
+ /**
27
+ * Default producer id for publish (also set as AGENTBUS_PRODUCER_ID on stdio).
28
+ * Falls back to process.env.AGENTBUS_PRODUCER_ID.
29
+ */
30
+ producerId?: string;
31
+ /**
32
+ * Topics to poll each cycle.
33
+ * Default: `["okf/handoff"]`.
34
+ * Pass `["*"]` to expand via `agentbus_status` topics list each poll.
35
+ */
36
+ topics?: string[];
37
+ /** Options for auto stdio spawn when `mcp` is not injected. */
38
+ stdio?: {
39
+ command?: string;
40
+ args?: string[];
41
+ cwd?: string;
42
+ env?: Record<string, string>;
43
+ };
44
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Debounced fs.watch on the events.db (or parent dir) plus a slow fallback poll.
3
+ * WAL renames can make watching the file alone flaky — we prefer the .agentbus dir
4
+ * when the db path does not exist yet.
5
+ */
6
+ export declare class DatabaseWatcher {
7
+ private dbPath;
8
+ private onTrigger;
9
+ private fallbackMs;
10
+ private debounceMs;
11
+ private timer;
12
+ private watcher;
13
+ private debounceTimer;
14
+ constructor(dbPath: string, onTrigger: () => void, fallbackMs?: number, debounceMs?: number);
15
+ start(): void;
16
+ private trigger;
17
+ stop(): void;
18
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DatabaseWatcher = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Debounced fs.watch on the events.db (or parent dir) plus a slow fallback poll.
11
+ * WAL renames can make watching the file alone flaky — we prefer the .agentbus dir
12
+ * when the db path does not exist yet.
13
+ */
14
+ class DatabaseWatcher {
15
+ dbPath;
16
+ onTrigger;
17
+ fallbackMs;
18
+ debounceMs;
19
+ timer = null;
20
+ watcher = null;
21
+ debounceTimer = null;
22
+ constructor(dbPath, onTrigger, fallbackMs = 5000, debounceMs = 50) {
23
+ this.dbPath = dbPath;
24
+ this.onTrigger = onTrigger;
25
+ this.fallbackMs = fallbackMs;
26
+ this.debounceMs = debounceMs;
27
+ }
28
+ start() {
29
+ this.timer = setInterval(() => this.trigger(), this.fallbackMs);
30
+ const watchTarget = fs_1.default.existsSync(this.dbPath)
31
+ ? this.dbPath
32
+ : path_1.default.dirname(this.dbPath);
33
+ try {
34
+ this.watcher = fs_1.default.watch(watchTarget, () => {
35
+ if (this.debounceTimer)
36
+ clearTimeout(this.debounceTimer);
37
+ this.debounceTimer = setTimeout(() => this.trigger(), this.debounceMs);
38
+ });
39
+ // Async watch failures (deleted path, etc.) must not crash the process;
40
+ // fallback timer continues to drive polls.
41
+ this.watcher.on("error", () => {
42
+ if (this.watcher) {
43
+ try {
44
+ this.watcher.close();
45
+ }
46
+ catch {
47
+ /* ignore */
48
+ }
49
+ this.watcher = null;
50
+ }
51
+ });
52
+ }
53
+ catch {
54
+ // Ignore if path is not watchable yet; fallback timer still runs.
55
+ }
56
+ }
57
+ trigger() {
58
+ this.onTrigger();
59
+ }
60
+ stop() {
61
+ if (this.timer)
62
+ clearInterval(this.timer);
63
+ if (this.debounceTimer)
64
+ clearTimeout(this.debounceTimer);
65
+ if (this.watcher)
66
+ this.watcher.close();
67
+ this.timer = null;
68
+ this.debounceTimer = null;
69
+ this.watcher = null;
70
+ }
71
+ }
72
+ exports.DatabaseWatcher = DatabaseWatcher;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@agentbus/agentbus-client",
3
+ "version": "0.11.2",
4
+ "description": "TypeScript/Node EventEmitter client for AgentBus (MCP + fs.watch poll)",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest"
16
+ },
17
+ "keywords": [
18
+ "agentbus",
19
+ "okf",
20
+ "mcp",
21
+ "agents"
22
+ ],
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/onicarps/agentbus.git",
30
+ "directory": "packages/js/agentbus-client"
31
+ },
32
+ "homepage": "https://github.com/onicarps/agentbus/tree/main/packages/js/agentbus-client",
33
+ "bugs": {
34
+ "url": "https://github.com/onicarps/agentbus/issues"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.12.1"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.15.0",
45
+ "typescript": "^5.8.3",
46
+ "vitest": "^3.1.2"
47
+ }
48
+ }