wave-code 0.19.2 → 0.19.4

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.
@@ -0,0 +1,562 @@
1
+ /**
2
+ * AgentBridge — wraps the SDK Agent and translates between the JSON-RPC-like
3
+ * stdio protocol and Agent method calls / callbacks.
4
+ *
5
+ * Multi-tenant: maintains a Map<sessionId, {agent, storedConfig}> so a single
6
+ * `wave --stdio` process can host multiple sessions. Session-scoped requests
7
+ * carry `sessionId` on the JSON-RPC envelope for routing; global requests
8
+ * (listSessions/searchFiles/auth/plugins) don't require it but may use it
9
+ * for workdir fallback.
10
+ *
11
+ * Responsibilities:
12
+ * - Route incoming requests to the appropriate Agent method (by sessionId)
13
+ * - Translate AgentCallbacks into outgoing notifications (with sessionId)
14
+ * - Implement the canUseTool permission flow over the stdio protocol
15
+ * - Handle config updates by destroying and recreating the Agent
16
+ */
17
+ import { Agent, listSessions, searchFiles, PromptHistoryManager, AuthService, PluginCore, } from "wave-agent-sdk";
18
+ import { INTERNAL_ERROR as PROTOCOL_INTERNAL_ERROR, METHOD_NOT_FOUND as PROTOCOL_METHOD_NOT_FOUND, } from "./protocol.js";
19
+ import { readFileSync } from "fs";
20
+ import path from "path";
21
+ import { fileURLToPath } from "url";
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const CLI_VERSION = (() => {
24
+ try {
25
+ const pkg = JSON.parse(readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8"));
26
+ return pkg.version ?? "";
27
+ }
28
+ catch {
29
+ return "";
30
+ }
31
+ })();
32
+ export class AgentBridge {
33
+ constructor(options) {
34
+ this.sessions = new Map();
35
+ this.pendingPermissions = new Map();
36
+ this.permissionCounter = 0;
37
+ this.emit = options.emit;
38
+ }
39
+ // ── Public API ────────────────────────────────────────────────
40
+ async handleRequest(method, params, sessionId) {
41
+ const p = (params ?? {});
42
+ switch (method) {
43
+ // ── Lifecycle ──
44
+ case "initialize":
45
+ return this.initialize(p);
46
+ case "destroy":
47
+ return this.destroy(sessionId);
48
+ case "restoreSession":
49
+ return this.restoreSession(p.sessionId, sessionId);
50
+ case "listSessions":
51
+ return this.listSessions(p.workdir, sessionId);
52
+ case "getSessionInfo":
53
+ return this.getSessionInfo(sessionId);
54
+ case "updateConfig":
55
+ return this.updateConfig(p, sessionId);
56
+ // ── Messages ──
57
+ case "sendMessage":
58
+ return this.sendMessage(p, sessionId);
59
+ case "bang":
60
+ return this.bang(p.command, sessionId);
61
+ case "abortMessage":
62
+ return this.abortMessage(sessionId);
63
+ case "clearMessages":
64
+ return this.clearMessages(sessionId);
65
+ case "rewindToMessage":
66
+ return this.rewindToMessage(p.messageId, sessionId);
67
+ case "deleteQueuedMessage":
68
+ return this.deleteQueuedMessage(p.index, sessionId);
69
+ case "getMessages":
70
+ return this.getMessages(sessionId);
71
+ case "getFullMessageThread":
72
+ return this.getFullMessageThread(sessionId);
73
+ // ── Permissions ──
74
+ case "setPermissionMode":
75
+ return this.setPermissionMode(p.mode, sessionId);
76
+ case "getPermissionMode":
77
+ return this.getPermissionMode(sessionId);
78
+ // ── MCP ──
79
+ case "getMcpServers":
80
+ return this.getMcpServers(sessionId);
81
+ case "connectMcpServer":
82
+ return this.connectMcpServer(p.serverName, sessionId);
83
+ case "disconnectMcpServer":
84
+ return this.disconnectMcpServer(p.serverName, sessionId);
85
+ // ── Commands ──
86
+ case "getSlashCommands":
87
+ return this.getSlashCommands(sessionId);
88
+ // ── File / History (global — no session required) ──
89
+ case "searchFiles":
90
+ return this.searchFiles(p, sessionId);
91
+ case "getPromptHistory":
92
+ return this.getPromptHistory(p.workdir, sessionId);
93
+ case "searchPromptHistory":
94
+ return this.searchPromptHistory(p.query, p.workdir, sessionId);
95
+ // ── Auth (global — no session required) ──
96
+ case "getAuthStatus":
97
+ return this.getAuthStatus();
98
+ case "login":
99
+ return this.login(p.serverUrl);
100
+ case "logout":
101
+ return this.logout();
102
+ // ── Plugins (global — no session required) ──
103
+ case "listPlugins":
104
+ return this.listPlugins(p.workdir, sessionId);
105
+ case "installPlugin":
106
+ return this.installPlugin(p.pluginId, p.scope, p.workdir, sessionId);
107
+ case "uninstallPlugin":
108
+ return this.uninstallPlugin(p.pluginId, p.workdir, sessionId);
109
+ case "enablePlugin":
110
+ return this.enablePlugin(p.pluginId, p.scope, p.workdir, sessionId);
111
+ case "disablePlugin":
112
+ return this.disablePlugin(p.pluginId, p.scope, p.workdir, sessionId);
113
+ case "updatePlugin":
114
+ return this.updatePlugin(p.pluginId, p.workdir, sessionId);
115
+ case "listMarketplaces":
116
+ return this.listMarketplaces(p.workdir, sessionId);
117
+ case "addMarketplace":
118
+ return this.addMarketplace(p.input, p.scope, p.workdir, sessionId);
119
+ case "removeMarketplace":
120
+ return this.removeMarketplace(p.name, p.scope, p.workdir, sessionId);
121
+ case "updateMarketplace":
122
+ return this.updateMarketplace(p.name, p.workdir, sessionId);
123
+ default:
124
+ throw new RpcError(PROTOCOL_METHOD_NOT_FOUND, `Method not found: ${method}`);
125
+ }
126
+ }
127
+ handleNotification(method, params) {
128
+ if (method === "permissionResponse") {
129
+ const p = params;
130
+ // requestId is process-level unique; lookup doesn't need sessionId
131
+ const resolve = this.pendingPermissions.get(p.requestId);
132
+ if (resolve) {
133
+ this.pendingPermissions.delete(p.requestId);
134
+ resolve(p.decision);
135
+ }
136
+ }
137
+ }
138
+ // ── Lifecycle ─────────────────────────────────────────────────
139
+ async initialize(params) {
140
+ const ctx = {};
141
+ const callbacks = this.createCallbacks(ctx);
142
+ const options = {
143
+ callbacks,
144
+ workdir: params.workdir,
145
+ restoreSessionId: params.restoreSessionId,
146
+ apiKey: params.apiKey,
147
+ baseURL: params.baseURL,
148
+ defaultHeaders: params.defaultHeaders,
149
+ model: params.model,
150
+ fastModel: params.fastModel,
151
+ language: params.language,
152
+ permissionMode: params.permissionMode,
153
+ tools: params.tools,
154
+ allowedTools: params.allowedTools,
155
+ disallowedTools: params.disallowedTools,
156
+ plugins: params.pluginDirs?.map((path) => ({ type: "local", path })),
157
+ mcpServers: params.mcpServers,
158
+ canUseTool: (context) => this.canUseTool(context, ctx),
159
+ };
160
+ const agent = await Agent.create(options);
161
+ ctx.agent = agent;
162
+ ctx.registeredSessionId = agent.sessionId;
163
+ this.sessions.set(agent.sessionId, {
164
+ agent,
165
+ storedConfig: { ...params },
166
+ });
167
+ if (params.clientVersion) {
168
+ console.debug(`[agentBridge] clientVersion=${params.clientVersion} serverVersion=${CLI_VERSION}`);
169
+ }
170
+ return {
171
+ sessionId: agent.sessionId,
172
+ workingDirectory: agent.workingDirectory,
173
+ permissionMode: agent.getPermissionMode(),
174
+ latestTotalTokens: agent.latestTotalTokens,
175
+ serverVersion: CLI_VERSION,
176
+ };
177
+ }
178
+ async destroy(sessionId) {
179
+ if (sessionId) {
180
+ const entry = this.sessions.get(sessionId);
181
+ if (entry) {
182
+ await entry.agent.destroy();
183
+ this.sessions.delete(sessionId);
184
+ }
185
+ }
186
+ return null;
187
+ }
188
+ async restoreSession(restoreId, sessionId) {
189
+ const entry = this.requireSession(sessionId);
190
+ await entry.agent.restoreSession(restoreId);
191
+ return null;
192
+ }
193
+ async listSessions(workdir, sessionId) {
194
+ const sessions = await listSessions(workdir || this.getSessionWorkdir(sessionId) || process.cwd());
195
+ return { sessions };
196
+ }
197
+ getSessionInfo(sessionId) {
198
+ const entry = this.requireSession(sessionId);
199
+ return {
200
+ sessionId: entry.agent.sessionId,
201
+ workingDirectory: entry.agent.workingDirectory,
202
+ latestTotalTokens: entry.agent.latestTotalTokens,
203
+ permissionMode: entry.agent.getPermissionMode(),
204
+ availableTools: entry.agent.getAvailableToolNames(),
205
+ };
206
+ }
207
+ async updateConfig(params, sessionId) {
208
+ const entry = this.requireSession(sessionId);
209
+ const currentSessionId = entry.agent.sessionId;
210
+ // Merge new config into stored config
211
+ entry.storedConfig = { ...entry.storedConfig, ...params };
212
+ // Destroy and recreate within the same session slot
213
+ await entry.agent.destroy();
214
+ this.sessions.delete(currentSessionId);
215
+ const ctx = {};
216
+ const callbacks = this.createCallbacks(ctx);
217
+ const options = {
218
+ callbacks,
219
+ workdir: entry.storedConfig.workdir,
220
+ restoreSessionId: currentSessionId,
221
+ apiKey: entry.storedConfig.apiKey,
222
+ baseURL: entry.storedConfig.baseURL,
223
+ defaultHeaders: entry.storedConfig.defaultHeaders,
224
+ model: entry.storedConfig.model,
225
+ fastModel: entry.storedConfig.fastModel,
226
+ language: entry.storedConfig.language,
227
+ permissionMode: entry.storedConfig.permissionMode,
228
+ tools: entry.storedConfig.tools,
229
+ allowedTools: entry.storedConfig.allowedTools,
230
+ disallowedTools: entry.storedConfig.disallowedTools,
231
+ plugins: entry.storedConfig.pluginDirs?.map((p) => ({
232
+ type: "local",
233
+ path: p,
234
+ })),
235
+ mcpServers: entry.storedConfig.mcpServers,
236
+ canUseTool: (context) => this.canUseTool(context, ctx),
237
+ };
238
+ const agent = await Agent.create(options);
239
+ ctx.agent = agent;
240
+ ctx.registeredSessionId = agent.sessionId;
241
+ this.sessions.set(agent.sessionId, {
242
+ agent,
243
+ storedConfig: { ...entry.storedConfig },
244
+ });
245
+ return { sessionId: agent.sessionId };
246
+ }
247
+ // ── Messages ──────────────────────────────────────────────────
248
+ async sendMessage(params, sessionId) {
249
+ const entry = this.requireSession(sessionId);
250
+ if (params.force) {
251
+ entry.agent.abortMessage();
252
+ }
253
+ // Save prompt to history (mirrors VSCE chatSession.ts:236-242)
254
+ try {
255
+ await PromptHistoryManager.addEntry(params.text, entry.agent.sessionId, {}, entry.agent.workingDirectory);
256
+ }
257
+ catch {
258
+ // Best-effort; don't block message sending on history save failure
259
+ }
260
+ await entry.agent.sendMessage(params.text, params.images);
261
+ return null;
262
+ }
263
+ async bang(command, sessionId) {
264
+ const entry = this.requireSession(sessionId);
265
+ await entry.agent.bang(command);
266
+ return null;
267
+ }
268
+ async abortMessage(sessionId) {
269
+ const entry = this.requireSession(sessionId);
270
+ entry.agent.abortMessage();
271
+ return null;
272
+ }
273
+ async clearMessages(sessionId) {
274
+ const entry = this.requireSession(sessionId);
275
+ entry.agent.clearMessages();
276
+ return null;
277
+ }
278
+ async rewindToMessage(messageId, sessionId) {
279
+ const entry = this.requireSession(sessionId);
280
+ const { messages } = await entry.agent.getFullMessageThread();
281
+ const index = messages.findIndex((m) => m.id === messageId);
282
+ if (index === -1) {
283
+ throw new RpcError(PROTOCOL_INTERNAL_ERROR, `Message not found: ${messageId}`);
284
+ }
285
+ const message = messages[index];
286
+ const textBlock = message.blocks.find((b) => b.type === "text");
287
+ await entry.agent.truncateHistory(index);
288
+ return { inputContent: textBlock?.content || "" };
289
+ }
290
+ deleteQueuedMessage(index, sessionId) {
291
+ const entry = this.requireSession(sessionId);
292
+ entry.agent.removeQueuedMessage(index);
293
+ return null;
294
+ }
295
+ getMessages(sessionId) {
296
+ const entry = this.requireSession(sessionId);
297
+ return { messages: entry.agent.messages };
298
+ }
299
+ async getFullMessageThread(sessionId) {
300
+ const entry = this.requireSession(sessionId);
301
+ return entry.agent.getFullMessageThread();
302
+ }
303
+ // ── Permissions ───────────────────────────────────────────────
304
+ async setPermissionMode(mode, sessionId) {
305
+ const entry = this.requireSession(sessionId);
306
+ await entry.agent.setPermissionMode(mode);
307
+ return null;
308
+ }
309
+ getPermissionMode(sessionId) {
310
+ const entry = this.requireSession(sessionId);
311
+ return { mode: entry.agent.getPermissionMode() };
312
+ }
313
+ // ── MCP ───────────────────────────────────────────────────────
314
+ getMcpServers(sessionId) {
315
+ const entry = this.requireSession(sessionId);
316
+ return { servers: entry.agent.getMcpServers() };
317
+ }
318
+ async connectMcpServer(serverName, sessionId) {
319
+ const entry = this.requireSession(sessionId);
320
+ const success = await entry.agent.connectMcpServer(serverName);
321
+ return { success };
322
+ }
323
+ async disconnectMcpServer(serverName, sessionId) {
324
+ const entry = this.requireSession(sessionId);
325
+ const success = await entry.agent.disconnectMcpServer(serverName);
326
+ return { success };
327
+ }
328
+ // ── Commands ──────────────────────────────────────────────────
329
+ getSlashCommands(sessionId) {
330
+ const entry = this.requireSession(sessionId);
331
+ return { commands: entry.agent.getSlashCommands() };
332
+ }
333
+ // ── File / History (global) ───────────────────────────────────
334
+ async searchFiles(params, sessionId) {
335
+ const files = await searchFiles(params.query, {
336
+ maxResults: params.maxResults,
337
+ workingDirectory: params.workdir || this.getSessionWorkdir(sessionId) || process.cwd(),
338
+ });
339
+ return { files };
340
+ }
341
+ async getPromptHistory(workdir, sessionId) {
342
+ const history = await PromptHistoryManager.getHistory({
343
+ workdir: workdir || this.getSessionWorkdir(sessionId),
344
+ });
345
+ return { history };
346
+ }
347
+ async searchPromptHistory(query, workdir, sessionId) {
348
+ const history = await PromptHistoryManager.searchHistory(query, {
349
+ workdir: workdir || this.getSessionWorkdir(sessionId),
350
+ });
351
+ return { history };
352
+ }
353
+ // ── canUseTool flow ───────────────────────────────────────────
354
+ canUseTool(context, ctx) {
355
+ const requestId = `perm_${++this.permissionCounter}`;
356
+ return new Promise((resolve) => {
357
+ this.pendingPermissions.set(requestId, resolve);
358
+ this.emit("permissionRequest", { requestId, context }, ctx.registeredSessionId);
359
+ // 5-minute timeout → auto-deny
360
+ setTimeout(() => {
361
+ if (this.pendingPermissions.has(requestId)) {
362
+ this.pendingPermissions.delete(requestId);
363
+ resolve({
364
+ behavior: "deny",
365
+ message: "Permission request timed out",
366
+ });
367
+ }
368
+ }, 5 * 60 * 1000);
369
+ });
370
+ }
371
+ // ── Auth (global) ────────────────────────────────────────────
372
+ async getAuthStatus() {
373
+ const authService = AuthService.getInstance();
374
+ return {
375
+ isAuthenticated: authService.isSSOAuthenticated(),
376
+ user: authService.getAuthUser(),
377
+ };
378
+ }
379
+ async login(serverUrl) {
380
+ const authService = AuthService.getInstance();
381
+ await authService.login({
382
+ onAuthUrl: (url) => {
383
+ this.emit("authUrl", { url });
384
+ },
385
+ serverUrl,
386
+ });
387
+ return { user: authService.getAuthUser() };
388
+ }
389
+ async logout() {
390
+ const authService = AuthService.getInstance();
391
+ await authService.clearAuth();
392
+ return null;
393
+ }
394
+ // ── Plugins (global) ─────────────────────────────────────────
395
+ getPluginCore(workdir, sessionId) {
396
+ const resolvedWorkdir = workdir || this.getSessionWorkdir(sessionId) || process.cwd();
397
+ if (!this.pluginCore || this.pluginCoreWorkdir !== resolvedWorkdir) {
398
+ this.pluginCore = new PluginCore(resolvedWorkdir);
399
+ this.pluginCoreWorkdir = resolvedWorkdir;
400
+ }
401
+ return this.pluginCore;
402
+ }
403
+ async listPlugins(workdir, sessionId) {
404
+ const core = this.getPluginCore(workdir, sessionId);
405
+ const { plugins, mergedEnabled } = await core.listPlugins();
406
+ return {
407
+ plugins: plugins.map((p) => {
408
+ const pluginId = `${p.name}@${p.marketplace}`;
409
+ return {
410
+ id: pluginId,
411
+ name: p.name,
412
+ description: p.description,
413
+ marketplace: p.marketplace,
414
+ installed: p.installed,
415
+ version: p.version,
416
+ enabled: mergedEnabled[pluginId] !== false,
417
+ scope: p.scope,
418
+ };
419
+ }),
420
+ };
421
+ }
422
+ async installPlugin(pluginId, scope, workdir, sessionId) {
423
+ return this.getPluginCore(workdir, sessionId).installPlugin(pluginId, scope);
424
+ }
425
+ async uninstallPlugin(pluginId, workdir, sessionId) {
426
+ await this.getPluginCore(workdir, sessionId).uninstallPlugin(pluginId);
427
+ return null;
428
+ }
429
+ async enablePlugin(pluginId, scope, workdir, sessionId) {
430
+ return this.getPluginCore(workdir, sessionId).enablePlugin(pluginId, scope);
431
+ }
432
+ async disablePlugin(pluginId, scope, workdir, sessionId) {
433
+ return this.getPluginCore(workdir, sessionId).disablePlugin(pluginId, scope);
434
+ }
435
+ async updatePlugin(pluginId, workdir, sessionId) {
436
+ return this.getPluginCore(workdir, sessionId).updatePlugin(pluginId);
437
+ }
438
+ async listMarketplaces(workdir, sessionId) {
439
+ return this.getPluginCore(workdir, sessionId).listMarketplaces();
440
+ }
441
+ async addMarketplace(input, scope, workdir, sessionId) {
442
+ return this.getPluginCore(workdir, sessionId).addMarketplace(input, scope);
443
+ }
444
+ async removeMarketplace(name, scope, workdir, sessionId) {
445
+ await this.getPluginCore(workdir, sessionId).removeMarketplace(name, scope);
446
+ return null;
447
+ }
448
+ async updateMarketplace(name, workdir, sessionId) {
449
+ await this.getPluginCore(workdir, sessionId).updateMarketplace(name);
450
+ return null;
451
+ }
452
+ // ── Callbacks → Notifications ─────────────────────────────────
453
+ createCallbacks(ctx) {
454
+ return {
455
+ onMessagesChange: (messages) => {
456
+ this.emit("messagesChange", { messages }, ctx.registeredSessionId);
457
+ },
458
+ onUserMessageAdded: () => {
459
+ const msg = this.findLastUserMessage(ctx.agent);
460
+ if (msg)
461
+ this.emit("userMessageAdded", { message: msg }, ctx.registeredSessionId);
462
+ },
463
+ onAssistantMessageAdded: (messageId) => {
464
+ const msg = ctx.agent?.messages.find((m) => m.id === messageId);
465
+ if (msg)
466
+ this.emit("assistantMessageAdded", { message: msg }, ctx.registeredSessionId);
467
+ },
468
+ onAssistantContentUpdated: (params) => {
469
+ this.emit("assistantContentUpdated", params, ctx.registeredSessionId);
470
+ },
471
+ onAssistantReasoningUpdated: (params) => {
472
+ this.emit("assistantReasoningUpdated", params, ctx.registeredSessionId);
473
+ },
474
+ onToolBlockUpdated: (params) => {
475
+ this.emit("toolBlockUpdated", params, ctx.registeredSessionId);
476
+ },
477
+ onErrorBlockAdded: (error) => {
478
+ this.emit("errorBlockAdded", { error }, ctx.registeredSessionId);
479
+ },
480
+ onLoadingChange: (loading) => {
481
+ this.emit("loadingChange", {
482
+ loading,
483
+ latestTotalTokens: ctx.agent?.latestTotalTokens,
484
+ }, ctx.registeredSessionId);
485
+ },
486
+ onCommandRunningChange: (running) => {
487
+ this.emit("commandRunningChange", { running }, ctx.registeredSessionId);
488
+ },
489
+ onQueuedMessagesChange: (messages) => {
490
+ this.emit("queuedMessagesChange", { messages }, ctx.registeredSessionId);
491
+ },
492
+ onTasksChange: (tasks) => {
493
+ this.emit("tasksChange", { tasks }, ctx.registeredSessionId);
494
+ },
495
+ onSessionIdChange: (newSessionId) => {
496
+ const oldSessionId = ctx.registeredSessionId;
497
+ // Emit with the OLD sessionId so the client's router can deliver it
498
+ this.emit("sessionIdChange", { sessionId: newSessionId }, oldSessionId);
499
+ // Update the sessions Map key atomically (single-threaded, no await)
500
+ if (oldSessionId && oldSessionId !== newSessionId) {
501
+ const entry = this.sessions.get(oldSessionId);
502
+ if (entry) {
503
+ this.sessions.delete(oldSessionId);
504
+ this.sessions.set(newSessionId, entry);
505
+ }
506
+ }
507
+ ctx.registeredSessionId = newSessionId;
508
+ },
509
+ onPermissionModeChange: (mode) => {
510
+ this.emit("permissionModeChange", { mode }, ctx.registeredSessionId);
511
+ },
512
+ onMcpServersChange: (servers) => {
513
+ this.emit("mcpServersChange", { servers }, ctx.registeredSessionId);
514
+ },
515
+ onAddBangMessage: () => {
516
+ this.emit("bangMessageAdded", {}, ctx.registeredSessionId);
517
+ },
518
+ onUpdateBangMessage: () => {
519
+ this.emit("bangMessageUpdated", {}, ctx.registeredSessionId);
520
+ },
521
+ onCompleteBangMessage: () => {
522
+ this.emit("bangMessageCompleted", {}, ctx.registeredSessionId);
523
+ },
524
+ onNotificationMessageAdded: (params) => {
525
+ const msg = ctx.agent?.messages.find((m) => m.role === "user" &&
526
+ m.blocks.some((b) => b.type === "task_notification" &&
527
+ b.taskId === params.taskId));
528
+ this.emit("notificationMessageAdded", { ...params, message: msg }, ctx.registeredSessionId);
529
+ },
530
+ };
531
+ }
532
+ findLastUserMessage(agent) {
533
+ const userMessages = agent?.messages.filter((m) => m.role === "user") ?? [];
534
+ return userMessages[userMessages.length - 1];
535
+ }
536
+ // ── Utils ─────────────────────────────────────────────────────
537
+ requireSession(sessionId) {
538
+ if (!sessionId) {
539
+ throw new RpcError(PROTOCOL_INTERNAL_ERROR, "sessionId is required for this request");
540
+ }
541
+ const entry = this.sessions.get(sessionId);
542
+ if (!entry) {
543
+ throw new RpcError(PROTOCOL_INTERNAL_ERROR, `Session not found: ${sessionId}`);
544
+ }
545
+ return entry;
546
+ }
547
+ getSessionWorkdir(sessionId) {
548
+ if (!sessionId)
549
+ return undefined;
550
+ return this.sessions.get(sessionId)?.agent.workingDirectory;
551
+ }
552
+ }
553
+ // ── Error class for protocol errors ─────────────────────────────
554
+ export class RpcError extends Error {
555
+ constructor(code, message) {
556
+ super(message);
557
+ this.code = code;
558
+ }
559
+ toJsonRpcError() {
560
+ return { code: this.code, message: this.message };
561
+ }
562
+ }
@@ -0,0 +1,4 @@
1
+ export { StdioServer, type StdioServerOptions } from "./stdioServer.js";
2
+ export { AgentBridge, type AgentBridgeOptions, RpcError, } from "./agentBridge.js";
3
+ export * from "./protocol.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stdio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EACL,WAAW,EACX,KAAK,kBAAkB,EACvB,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAC1B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { StdioServer } from "./stdioServer.js";
2
+ export { AgentBridge, RpcError, } from "./agentBridge.js";
3
+ export * from "./protocol.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * JSON-RPC 2.0-compatible protocol types for stdio communication.
3
+ *
4
+ * Each line on stdin/stdout is one JSON object.
5
+ * - Requests have `id` (number|string) and expect a response.
6
+ * - Responses match the request `id` with either `result` or `error`.
7
+ * - Notifications have no `id` and expect no response.
8
+ */
9
+ export interface JsonRpcRequest {
10
+ id: number | string;
11
+ method: string;
12
+ params?: unknown;
13
+ /** Session-scoped requests carry sessionId for routing to the right Agent. */
14
+ sessionId?: string;
15
+ }
16
+ export interface JsonRpcResponse {
17
+ id: number | string | null;
18
+ result?: unknown;
19
+ error?: JsonRpcError;
20
+ }
21
+ export interface JsonRpcError {
22
+ code: number;
23
+ message: string;
24
+ data?: unknown;
25
+ }
26
+ export interface JsonRpcNotification {
27
+ method: string;
28
+ params?: unknown;
29
+ /** Session-scoped notifications carry sessionId for demultiplexing on the client. */
30
+ sessionId?: string;
31
+ }
32
+ export declare const PARSE_ERROR = -32700;
33
+ export declare const INVALID_REQUEST = -32600;
34
+ export declare const METHOD_NOT_FOUND = -32601;
35
+ export declare const INVALID_PARAMS = -32602;
36
+ export declare const INTERNAL_ERROR = -32603;
37
+ export type RequestMethod = "initialize" | "destroy" | "restoreSession" | "listSessions" | "getSessionInfo" | "sendMessage" | "bang" | "abortMessage" | "clearMessages" | "rewindToMessage" | "deleteQueuedMessage" | "getMessages" | "getFullMessageThread" | "setPermissionMode" | "getPermissionMode" | "getMcpServers" | "connectMcpServer" | "disconnectMcpServer" | "getSlashCommands" | "searchFiles" | "getPromptHistory" | "searchPromptHistory" | "updateConfig" | "getAuthStatus" | "login" | "logout" | "listPlugins" | "installPlugin" | "uninstallPlugin" | "enablePlugin" | "disablePlugin" | "updatePlugin" | "listMarketplaces" | "addMarketplace" | "removeMarketplace" | "updateMarketplace";
38
+ export type ClientNotificationMethod = "permissionResponse";
39
+ export type ServerNotificationMethod = "messagesChange" | "userMessageAdded" | "assistantMessageAdded" | "assistantContentUpdated" | "assistantReasoningUpdated" | "toolBlockUpdated" | "errorBlockAdded" | "loadingChange" | "commandRunningChange" | "queuedMessagesChange" | "tasksChange" | "sessionIdChange" | "permissionModeChange" | "mcpServersChange" | "bangMessageAdded" | "bangMessageUpdated" | "bangMessageCompleted" | "notificationMessageAdded" | "permissionRequest" | "authUrl";
40
+ export declare function isRequest(msg: unknown): msg is JsonRpcRequest;
41
+ export declare function isNotification(msg: unknown): msg is JsonRpcNotification;
42
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/stdio/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,eAAO,MAAM,WAAW,SAAS,CAAC;AAClC,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC,eAAO,MAAM,gBAAgB,SAAS,CAAC;AACvC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,cAAc,SAAS,CAAC;AAIrC,MAAM,MAAM,aAAa,GACrB,YAAY,GACZ,SAAS,GACT,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAChB,aAAa,GACb,MAAM,GACN,cAAc,GACd,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,aAAa,GACb,sBAAsB,GACtB,mBAAmB,GACnB,mBAAmB,GACnB,eAAe,GACf,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,qBAAqB,GACrB,cAAc,GAEd,eAAe,GACf,OAAO,GACP,QAAQ,GAER,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,cAAc,GACd,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,CAAC;AAIxB,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAI5D,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,kBAAkB,GAClB,uBAAuB,GACvB,yBAAyB,GACzB,2BAA2B,GAC3B,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,sBAAsB,GACtB,sBAAsB,GACtB,aAAa,GACb,iBAAiB,GACjB,sBAAsB,GACtB,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,GACpB,sBAAsB,GACtB,0BAA0B,GAC1B,mBAAmB,GACnB,SAAS,CAAC;AAId,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc,CAS7D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,mBAAmB,CAIvE"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * JSON-RPC 2.0-compatible protocol types for stdio communication.
3
+ *
4
+ * Each line on stdin/stdout is one JSON object.
5
+ * - Requests have `id` (number|string) and expect a response.
6
+ * - Responses match the request `id` with either `result` or `error`.
7
+ * - Notifications have no `id` and expect no response.
8
+ */
9
+ // ── Error codes (JSON-RPC 2.0 standard) ────────────────────────
10
+ export const PARSE_ERROR = -32700;
11
+ export const INVALID_REQUEST = -32600;
12
+ export const METHOD_NOT_FOUND = -32601;
13
+ export const INVALID_PARAMS = -32602;
14
+ export const INTERNAL_ERROR = -32603;
15
+ // ── Helper: is this a request (has id)? ─────────────────────────
16
+ export function isRequest(msg) {
17
+ return (typeof msg === "object" &&
18
+ msg !== null &&
19
+ "method" in msg &&
20
+ "id" in msg &&
21
+ (typeof msg.id === "number" ||
22
+ typeof msg.id === "string"));
23
+ }
24
+ export function isNotification(msg) {
25
+ return (typeof msg === "object" && msg !== null && "method" in msg && !("id" in msg));
26
+ }