wave-code 0.19.3 → 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.
- package/dist/stdio/agentBridge.d.ts +13 -7
- package/dist/stdio/agentBridge.d.ts.map +1 -1
- package/dist/stdio/agentBridge.js +260 -184
- package/dist/stdio/protocol.d.ts +4 -0
- package/dist/stdio/protocol.d.ts.map +1 -1
- package/dist/stdio/stdioServer.d.ts +1 -1
- package/dist/stdio/stdioServer.d.ts.map +1 -1
- package/dist/stdio/stdioServer.js +5 -3
- package/package.json +2 -2
- package/src/stdio/agentBridge.ts +382 -173
- package/src/stdio/protocol.ts +4 -0
- package/src/stdio/stdioServer.ts +9 -3
|
@@ -2,105 +2,124 @@
|
|
|
2
2
|
* AgentBridge — wraps the SDK Agent and translates between the JSON-RPC-like
|
|
3
3
|
* stdio protocol and Agent method calls / callbacks.
|
|
4
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
|
+
*
|
|
5
11
|
* Responsibilities:
|
|
6
|
-
* - Route incoming requests to the appropriate Agent method
|
|
7
|
-
* - Translate AgentCallbacks into outgoing notifications
|
|
12
|
+
* - Route incoming requests to the appropriate Agent method (by sessionId)
|
|
13
|
+
* - Translate AgentCallbacks into outgoing notifications (with sessionId)
|
|
8
14
|
* - Implement the canUseTool permission flow over the stdio protocol
|
|
9
15
|
* - Handle config updates by destroying and recreating the Agent
|
|
10
16
|
*/
|
|
11
17
|
import { Agent, listSessions, searchFiles, PromptHistoryManager, AuthService, PluginCore, } from "wave-agent-sdk";
|
|
12
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
|
+
})();
|
|
13
32
|
export class AgentBridge {
|
|
14
33
|
constructor(options) {
|
|
34
|
+
this.sessions = new Map();
|
|
15
35
|
this.pendingPermissions = new Map();
|
|
16
36
|
this.permissionCounter = 0;
|
|
17
|
-
this.storedConfig = {};
|
|
18
37
|
this.emit = options.emit;
|
|
19
38
|
}
|
|
20
39
|
// ── Public API ────────────────────────────────────────────────
|
|
21
|
-
async handleRequest(method, params) {
|
|
40
|
+
async handleRequest(method, params, sessionId) {
|
|
22
41
|
const p = (params ?? {});
|
|
23
42
|
switch (method) {
|
|
24
43
|
// ── Lifecycle ──
|
|
25
44
|
case "initialize":
|
|
26
45
|
return this.initialize(p);
|
|
27
46
|
case "destroy":
|
|
28
|
-
return this.destroy();
|
|
47
|
+
return this.destroy(sessionId);
|
|
29
48
|
case "restoreSession":
|
|
30
|
-
return this.restoreSession(p.sessionId);
|
|
49
|
+
return this.restoreSession(p.sessionId, sessionId);
|
|
31
50
|
case "listSessions":
|
|
32
|
-
return this.listSessions(p.workdir);
|
|
51
|
+
return this.listSessions(p.workdir, sessionId);
|
|
33
52
|
case "getSessionInfo":
|
|
34
|
-
return this.getSessionInfo();
|
|
53
|
+
return this.getSessionInfo(sessionId);
|
|
35
54
|
case "updateConfig":
|
|
36
|
-
return this.updateConfig(p);
|
|
55
|
+
return this.updateConfig(p, sessionId);
|
|
37
56
|
// ── Messages ──
|
|
38
57
|
case "sendMessage":
|
|
39
|
-
return this.sendMessage(p);
|
|
58
|
+
return this.sendMessage(p, sessionId);
|
|
40
59
|
case "bang":
|
|
41
|
-
return this.bang(p.command);
|
|
60
|
+
return this.bang(p.command, sessionId);
|
|
42
61
|
case "abortMessage":
|
|
43
|
-
return this.abortMessage();
|
|
62
|
+
return this.abortMessage(sessionId);
|
|
44
63
|
case "clearMessages":
|
|
45
|
-
return this.clearMessages();
|
|
64
|
+
return this.clearMessages(sessionId);
|
|
46
65
|
case "rewindToMessage":
|
|
47
|
-
return this.rewindToMessage(p.messageId);
|
|
66
|
+
return this.rewindToMessage(p.messageId, sessionId);
|
|
48
67
|
case "deleteQueuedMessage":
|
|
49
|
-
return this.deleteQueuedMessage(p.index);
|
|
68
|
+
return this.deleteQueuedMessage(p.index, sessionId);
|
|
50
69
|
case "getMessages":
|
|
51
|
-
return this.getMessages();
|
|
70
|
+
return this.getMessages(sessionId);
|
|
52
71
|
case "getFullMessageThread":
|
|
53
|
-
return this.getFullMessageThread();
|
|
72
|
+
return this.getFullMessageThread(sessionId);
|
|
54
73
|
// ── Permissions ──
|
|
55
74
|
case "setPermissionMode":
|
|
56
|
-
return this.setPermissionMode(p.mode);
|
|
75
|
+
return this.setPermissionMode(p.mode, sessionId);
|
|
57
76
|
case "getPermissionMode":
|
|
58
|
-
return this.getPermissionMode();
|
|
77
|
+
return this.getPermissionMode(sessionId);
|
|
59
78
|
// ── MCP ──
|
|
60
79
|
case "getMcpServers":
|
|
61
|
-
return this.getMcpServers();
|
|
80
|
+
return this.getMcpServers(sessionId);
|
|
62
81
|
case "connectMcpServer":
|
|
63
|
-
return this.connectMcpServer(p.serverName);
|
|
82
|
+
return this.connectMcpServer(p.serverName, sessionId);
|
|
64
83
|
case "disconnectMcpServer":
|
|
65
|
-
return this.disconnectMcpServer(p.serverName);
|
|
84
|
+
return this.disconnectMcpServer(p.serverName, sessionId);
|
|
66
85
|
// ── Commands ──
|
|
67
86
|
case "getSlashCommands":
|
|
68
|
-
return this.getSlashCommands();
|
|
69
|
-
// ── File / History ──
|
|
87
|
+
return this.getSlashCommands(sessionId);
|
|
88
|
+
// ── File / History (global — no session required) ──
|
|
70
89
|
case "searchFiles":
|
|
71
|
-
return this.searchFiles(p);
|
|
90
|
+
return this.searchFiles(p, sessionId);
|
|
72
91
|
case "getPromptHistory":
|
|
73
|
-
return this.getPromptHistory(p.workdir);
|
|
92
|
+
return this.getPromptHistory(p.workdir, sessionId);
|
|
74
93
|
case "searchPromptHistory":
|
|
75
|
-
return this.searchPromptHistory(p.query, p.workdir);
|
|
76
|
-
// ── Auth ──
|
|
94
|
+
return this.searchPromptHistory(p.query, p.workdir, sessionId);
|
|
95
|
+
// ── Auth (global — no session required) ──
|
|
77
96
|
case "getAuthStatus":
|
|
78
97
|
return this.getAuthStatus();
|
|
79
98
|
case "login":
|
|
80
99
|
return this.login(p.serverUrl);
|
|
81
100
|
case "logout":
|
|
82
101
|
return this.logout();
|
|
83
|
-
// ── Plugins ──
|
|
102
|
+
// ── Plugins (global — no session required) ──
|
|
84
103
|
case "listPlugins":
|
|
85
|
-
return this.listPlugins(p.workdir);
|
|
104
|
+
return this.listPlugins(p.workdir, sessionId);
|
|
86
105
|
case "installPlugin":
|
|
87
|
-
return this.installPlugin(p.pluginId, p.scope, p.workdir);
|
|
106
|
+
return this.installPlugin(p.pluginId, p.scope, p.workdir, sessionId);
|
|
88
107
|
case "uninstallPlugin":
|
|
89
|
-
return this.uninstallPlugin(p.pluginId, p.workdir);
|
|
108
|
+
return this.uninstallPlugin(p.pluginId, p.workdir, sessionId);
|
|
90
109
|
case "enablePlugin":
|
|
91
|
-
return this.enablePlugin(p.pluginId, p.scope, p.workdir);
|
|
110
|
+
return this.enablePlugin(p.pluginId, p.scope, p.workdir, sessionId);
|
|
92
111
|
case "disablePlugin":
|
|
93
|
-
return this.disablePlugin(p.pluginId, p.scope, p.workdir);
|
|
112
|
+
return this.disablePlugin(p.pluginId, p.scope, p.workdir, sessionId);
|
|
94
113
|
case "updatePlugin":
|
|
95
|
-
return this.updatePlugin(p.pluginId, p.workdir);
|
|
114
|
+
return this.updatePlugin(p.pluginId, p.workdir, sessionId);
|
|
96
115
|
case "listMarketplaces":
|
|
97
|
-
return this.listMarketplaces(p.workdir);
|
|
116
|
+
return this.listMarketplaces(p.workdir, sessionId);
|
|
98
117
|
case "addMarketplace":
|
|
99
|
-
return this.addMarketplace(p.input, p.scope, p.workdir);
|
|
118
|
+
return this.addMarketplace(p.input, p.scope, p.workdir, sessionId);
|
|
100
119
|
case "removeMarketplace":
|
|
101
|
-
return this.removeMarketplace(p.name, p.scope, p.workdir);
|
|
120
|
+
return this.removeMarketplace(p.name, p.scope, p.workdir, sessionId);
|
|
102
121
|
case "updateMarketplace":
|
|
103
|
-
return this.updateMarketplace(p.name, p.workdir);
|
|
122
|
+
return this.updateMarketplace(p.name, p.workdir, sessionId);
|
|
104
123
|
default:
|
|
105
124
|
throw new RpcError(PROTOCOL_METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
106
125
|
}
|
|
@@ -108,6 +127,7 @@ export class AgentBridge {
|
|
|
108
127
|
handleNotification(method, params) {
|
|
109
128
|
if (method === "permissionResponse") {
|
|
110
129
|
const p = params;
|
|
130
|
+
// requestId is process-level unique; lookup doesn't need sessionId
|
|
111
131
|
const resolve = this.pendingPermissions.get(p.requestId);
|
|
112
132
|
if (resolve) {
|
|
113
133
|
this.pendingPermissions.delete(p.requestId);
|
|
@@ -117,9 +137,8 @@ export class AgentBridge {
|
|
|
117
137
|
}
|
|
118
138
|
// ── Lifecycle ─────────────────────────────────────────────────
|
|
119
139
|
async initialize(params) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const callbacks = this.createCallbacks();
|
|
140
|
+
const ctx = {};
|
|
141
|
+
const callbacks = this.createCallbacks(ctx);
|
|
123
142
|
const options = {
|
|
124
143
|
callbacks,
|
|
125
144
|
workdir: params.workdir,
|
|
@@ -136,168 +155,207 @@ export class AgentBridge {
|
|
|
136
155
|
disallowedTools: params.disallowedTools,
|
|
137
156
|
plugins: params.pluginDirs?.map((path) => ({ type: "local", path })),
|
|
138
157
|
mcpServers: params.mcpServers,
|
|
139
|
-
canUseTool: (context) => this.canUseTool(context),
|
|
158
|
+
canUseTool: (context) => this.canUseTool(context, ctx),
|
|
140
159
|
};
|
|
141
|
-
|
|
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
|
+
}
|
|
142
170
|
return {
|
|
143
|
-
sessionId:
|
|
144
|
-
workingDirectory:
|
|
145
|
-
permissionMode:
|
|
146
|
-
latestTotalTokens:
|
|
171
|
+
sessionId: agent.sessionId,
|
|
172
|
+
workingDirectory: agent.workingDirectory,
|
|
173
|
+
permissionMode: agent.getPermissionMode(),
|
|
174
|
+
latestTotalTokens: agent.latestTotalTokens,
|
|
175
|
+
serverVersion: CLI_VERSION,
|
|
147
176
|
};
|
|
148
177
|
}
|
|
149
|
-
async destroy() {
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
}
|
|
153
185
|
}
|
|
154
186
|
return null;
|
|
155
187
|
}
|
|
156
|
-
async restoreSession(sessionId) {
|
|
157
|
-
this.
|
|
158
|
-
await
|
|
188
|
+
async restoreSession(restoreId, sessionId) {
|
|
189
|
+
const entry = this.requireSession(sessionId);
|
|
190
|
+
await entry.agent.restoreSession(restoreId);
|
|
159
191
|
return null;
|
|
160
192
|
}
|
|
161
|
-
async listSessions(workdir) {
|
|
162
|
-
const sessions = await listSessions(workdir || this.
|
|
193
|
+
async listSessions(workdir, sessionId) {
|
|
194
|
+
const sessions = await listSessions(workdir || this.getSessionWorkdir(sessionId) || process.cwd());
|
|
163
195
|
return { sessions };
|
|
164
196
|
}
|
|
165
|
-
getSessionInfo() {
|
|
166
|
-
this.
|
|
197
|
+
getSessionInfo(sessionId) {
|
|
198
|
+
const entry = this.requireSession(sessionId);
|
|
167
199
|
return {
|
|
168
|
-
sessionId:
|
|
169
|
-
workingDirectory:
|
|
170
|
-
latestTotalTokens:
|
|
171
|
-
permissionMode:
|
|
172
|
-
availableTools:
|
|
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(),
|
|
173
205
|
};
|
|
174
206
|
}
|
|
175
|
-
async updateConfig(params) {
|
|
176
|
-
this.
|
|
177
|
-
const currentSessionId =
|
|
207
|
+
async updateConfig(params, sessionId) {
|
|
208
|
+
const entry = this.requireSession(sessionId);
|
|
209
|
+
const currentSessionId = entry.agent.sessionId;
|
|
178
210
|
// Merge new config into stored config
|
|
179
|
-
|
|
180
|
-
// Destroy and recreate
|
|
181
|
-
await
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
|
|
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,
|
|
185
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 },
|
|
186
244
|
});
|
|
187
|
-
return { sessionId:
|
|
245
|
+
return { sessionId: agent.sessionId };
|
|
188
246
|
}
|
|
189
247
|
// ── Messages ──────────────────────────────────────────────────
|
|
190
|
-
async sendMessage(params) {
|
|
191
|
-
this.
|
|
248
|
+
async sendMessage(params, sessionId) {
|
|
249
|
+
const entry = this.requireSession(sessionId);
|
|
192
250
|
if (params.force) {
|
|
193
|
-
|
|
251
|
+
entry.agent.abortMessage();
|
|
194
252
|
}
|
|
195
253
|
// Save prompt to history (mirrors VSCE chatSession.ts:236-242)
|
|
196
254
|
try {
|
|
197
|
-
await PromptHistoryManager.addEntry(params.text,
|
|
255
|
+
await PromptHistoryManager.addEntry(params.text, entry.agent.sessionId, {}, entry.agent.workingDirectory);
|
|
198
256
|
}
|
|
199
257
|
catch {
|
|
200
258
|
// Best-effort; don't block message sending on history save failure
|
|
201
259
|
}
|
|
202
|
-
await
|
|
260
|
+
await entry.agent.sendMessage(params.text, params.images);
|
|
203
261
|
return null;
|
|
204
262
|
}
|
|
205
|
-
async bang(command) {
|
|
206
|
-
this.
|
|
207
|
-
await
|
|
263
|
+
async bang(command, sessionId) {
|
|
264
|
+
const entry = this.requireSession(sessionId);
|
|
265
|
+
await entry.agent.bang(command);
|
|
208
266
|
return null;
|
|
209
267
|
}
|
|
210
|
-
async abortMessage() {
|
|
211
|
-
this.
|
|
212
|
-
|
|
268
|
+
async abortMessage(sessionId) {
|
|
269
|
+
const entry = this.requireSession(sessionId);
|
|
270
|
+
entry.agent.abortMessage();
|
|
213
271
|
return null;
|
|
214
272
|
}
|
|
215
|
-
async clearMessages() {
|
|
216
|
-
this.
|
|
217
|
-
|
|
273
|
+
async clearMessages(sessionId) {
|
|
274
|
+
const entry = this.requireSession(sessionId);
|
|
275
|
+
entry.agent.clearMessages();
|
|
218
276
|
return null;
|
|
219
277
|
}
|
|
220
|
-
async rewindToMessage(messageId) {
|
|
221
|
-
this.
|
|
222
|
-
const { messages } = await
|
|
278
|
+
async rewindToMessage(messageId, sessionId) {
|
|
279
|
+
const entry = this.requireSession(sessionId);
|
|
280
|
+
const { messages } = await entry.agent.getFullMessageThread();
|
|
223
281
|
const index = messages.findIndex((m) => m.id === messageId);
|
|
224
282
|
if (index === -1) {
|
|
225
283
|
throw new RpcError(PROTOCOL_INTERNAL_ERROR, `Message not found: ${messageId}`);
|
|
226
284
|
}
|
|
227
285
|
const message = messages[index];
|
|
228
286
|
const textBlock = message.blocks.find((b) => b.type === "text");
|
|
229
|
-
await
|
|
287
|
+
await entry.agent.truncateHistory(index);
|
|
230
288
|
return { inputContent: textBlock?.content || "" };
|
|
231
289
|
}
|
|
232
|
-
deleteQueuedMessage(index) {
|
|
233
|
-
this.
|
|
234
|
-
|
|
290
|
+
deleteQueuedMessage(index, sessionId) {
|
|
291
|
+
const entry = this.requireSession(sessionId);
|
|
292
|
+
entry.agent.removeQueuedMessage(index);
|
|
235
293
|
return null;
|
|
236
294
|
}
|
|
237
|
-
getMessages() {
|
|
238
|
-
this.
|
|
239
|
-
return { messages:
|
|
295
|
+
getMessages(sessionId) {
|
|
296
|
+
const entry = this.requireSession(sessionId);
|
|
297
|
+
return { messages: entry.agent.messages };
|
|
240
298
|
}
|
|
241
|
-
async getFullMessageThread() {
|
|
242
|
-
this.
|
|
243
|
-
return
|
|
299
|
+
async getFullMessageThread(sessionId) {
|
|
300
|
+
const entry = this.requireSession(sessionId);
|
|
301
|
+
return entry.agent.getFullMessageThread();
|
|
244
302
|
}
|
|
245
303
|
// ── Permissions ───────────────────────────────────────────────
|
|
246
|
-
async setPermissionMode(mode) {
|
|
247
|
-
this.
|
|
248
|
-
await
|
|
304
|
+
async setPermissionMode(mode, sessionId) {
|
|
305
|
+
const entry = this.requireSession(sessionId);
|
|
306
|
+
await entry.agent.setPermissionMode(mode);
|
|
249
307
|
return null;
|
|
250
308
|
}
|
|
251
|
-
getPermissionMode() {
|
|
252
|
-
this.
|
|
253
|
-
return { mode:
|
|
309
|
+
getPermissionMode(sessionId) {
|
|
310
|
+
const entry = this.requireSession(sessionId);
|
|
311
|
+
return { mode: entry.agent.getPermissionMode() };
|
|
254
312
|
}
|
|
255
313
|
// ── MCP ───────────────────────────────────────────────────────
|
|
256
|
-
getMcpServers() {
|
|
257
|
-
this.
|
|
258
|
-
return { servers:
|
|
314
|
+
getMcpServers(sessionId) {
|
|
315
|
+
const entry = this.requireSession(sessionId);
|
|
316
|
+
return { servers: entry.agent.getMcpServers() };
|
|
259
317
|
}
|
|
260
|
-
async connectMcpServer(serverName) {
|
|
261
|
-
this.
|
|
262
|
-
const success = await
|
|
318
|
+
async connectMcpServer(serverName, sessionId) {
|
|
319
|
+
const entry = this.requireSession(sessionId);
|
|
320
|
+
const success = await entry.agent.connectMcpServer(serverName);
|
|
263
321
|
return { success };
|
|
264
322
|
}
|
|
265
|
-
async disconnectMcpServer(serverName) {
|
|
266
|
-
this.
|
|
267
|
-
const success = await
|
|
323
|
+
async disconnectMcpServer(serverName, sessionId) {
|
|
324
|
+
const entry = this.requireSession(sessionId);
|
|
325
|
+
const success = await entry.agent.disconnectMcpServer(serverName);
|
|
268
326
|
return { success };
|
|
269
327
|
}
|
|
270
328
|
// ── Commands ──────────────────────────────────────────────────
|
|
271
|
-
getSlashCommands() {
|
|
272
|
-
this.
|
|
273
|
-
return { commands:
|
|
329
|
+
getSlashCommands(sessionId) {
|
|
330
|
+
const entry = this.requireSession(sessionId);
|
|
331
|
+
return { commands: entry.agent.getSlashCommands() };
|
|
274
332
|
}
|
|
275
|
-
// ── File / History
|
|
276
|
-
async searchFiles(params) {
|
|
333
|
+
// ── File / History (global) ───────────────────────────────────
|
|
334
|
+
async searchFiles(params, sessionId) {
|
|
277
335
|
const files = await searchFiles(params.query, {
|
|
278
336
|
maxResults: params.maxResults,
|
|
279
|
-
workingDirectory: params.workdir || this.
|
|
337
|
+
workingDirectory: params.workdir || this.getSessionWorkdir(sessionId) || process.cwd(),
|
|
280
338
|
});
|
|
281
339
|
return { files };
|
|
282
340
|
}
|
|
283
|
-
async getPromptHistory(workdir) {
|
|
341
|
+
async getPromptHistory(workdir, sessionId) {
|
|
284
342
|
const history = await PromptHistoryManager.getHistory({
|
|
285
|
-
workdir: workdir || this.
|
|
343
|
+
workdir: workdir || this.getSessionWorkdir(sessionId),
|
|
286
344
|
});
|
|
287
345
|
return { history };
|
|
288
346
|
}
|
|
289
|
-
async searchPromptHistory(query, workdir) {
|
|
347
|
+
async searchPromptHistory(query, workdir, sessionId) {
|
|
290
348
|
const history = await PromptHistoryManager.searchHistory(query, {
|
|
291
|
-
workdir: workdir || this.
|
|
349
|
+
workdir: workdir || this.getSessionWorkdir(sessionId),
|
|
292
350
|
});
|
|
293
351
|
return { history };
|
|
294
352
|
}
|
|
295
353
|
// ── canUseTool flow ───────────────────────────────────────────
|
|
296
|
-
canUseTool(context) {
|
|
354
|
+
canUseTool(context, ctx) {
|
|
297
355
|
const requestId = `perm_${++this.permissionCounter}`;
|
|
298
356
|
return new Promise((resolve) => {
|
|
299
357
|
this.pendingPermissions.set(requestId, resolve);
|
|
300
|
-
this.emit("permissionRequest", { requestId, context });
|
|
358
|
+
this.emit("permissionRequest", { requestId, context }, ctx.registeredSessionId);
|
|
301
359
|
// 5-minute timeout → auto-deny
|
|
302
360
|
setTimeout(() => {
|
|
303
361
|
if (this.pendingPermissions.has(requestId)) {
|
|
@@ -310,7 +368,7 @@ export class AgentBridge {
|
|
|
310
368
|
}, 5 * 60 * 1000);
|
|
311
369
|
});
|
|
312
370
|
}
|
|
313
|
-
// ── Auth
|
|
371
|
+
// ── Auth (global) ────────────────────────────────────────────
|
|
314
372
|
async getAuthStatus() {
|
|
315
373
|
const authService = AuthService.getInstance();
|
|
316
374
|
return {
|
|
@@ -333,17 +391,17 @@ export class AgentBridge {
|
|
|
333
391
|
await authService.clearAuth();
|
|
334
392
|
return null;
|
|
335
393
|
}
|
|
336
|
-
// ── Plugins
|
|
337
|
-
getPluginCore(workdir) {
|
|
338
|
-
const resolvedWorkdir = workdir || this.
|
|
394
|
+
// ── Plugins (global) ─────────────────────────────────────────
|
|
395
|
+
getPluginCore(workdir, sessionId) {
|
|
396
|
+
const resolvedWorkdir = workdir || this.getSessionWorkdir(sessionId) || process.cwd();
|
|
339
397
|
if (!this.pluginCore || this.pluginCoreWorkdir !== resolvedWorkdir) {
|
|
340
398
|
this.pluginCore = new PluginCore(resolvedWorkdir);
|
|
341
399
|
this.pluginCoreWorkdir = resolvedWorkdir;
|
|
342
400
|
}
|
|
343
401
|
return this.pluginCore;
|
|
344
402
|
}
|
|
345
|
-
async listPlugins(workdir) {
|
|
346
|
-
const core = this.getPluginCore(workdir);
|
|
403
|
+
async listPlugins(workdir, sessionId) {
|
|
404
|
+
const core = this.getPluginCore(workdir, sessionId);
|
|
347
405
|
const { plugins, mergedEnabled } = await core.listPlugins();
|
|
348
406
|
return {
|
|
349
407
|
plugins: plugins.map((p) => {
|
|
@@ -361,117 +419,135 @@ export class AgentBridge {
|
|
|
361
419
|
}),
|
|
362
420
|
};
|
|
363
421
|
}
|
|
364
|
-
async installPlugin(pluginId, scope, workdir) {
|
|
365
|
-
return this.getPluginCore(workdir).installPlugin(pluginId, scope);
|
|
422
|
+
async installPlugin(pluginId, scope, workdir, sessionId) {
|
|
423
|
+
return this.getPluginCore(workdir, sessionId).installPlugin(pluginId, scope);
|
|
366
424
|
}
|
|
367
|
-
async uninstallPlugin(pluginId, workdir) {
|
|
368
|
-
await this.getPluginCore(workdir).uninstallPlugin(pluginId);
|
|
425
|
+
async uninstallPlugin(pluginId, workdir, sessionId) {
|
|
426
|
+
await this.getPluginCore(workdir, sessionId).uninstallPlugin(pluginId);
|
|
369
427
|
return null;
|
|
370
428
|
}
|
|
371
|
-
async enablePlugin(pluginId, scope, workdir) {
|
|
372
|
-
return this.getPluginCore(workdir).enablePlugin(pluginId, scope);
|
|
429
|
+
async enablePlugin(pluginId, scope, workdir, sessionId) {
|
|
430
|
+
return this.getPluginCore(workdir, sessionId).enablePlugin(pluginId, scope);
|
|
373
431
|
}
|
|
374
|
-
async disablePlugin(pluginId, scope, workdir) {
|
|
375
|
-
return this.getPluginCore(workdir).disablePlugin(pluginId, scope);
|
|
432
|
+
async disablePlugin(pluginId, scope, workdir, sessionId) {
|
|
433
|
+
return this.getPluginCore(workdir, sessionId).disablePlugin(pluginId, scope);
|
|
376
434
|
}
|
|
377
|
-
async updatePlugin(pluginId, workdir) {
|
|
378
|
-
return this.getPluginCore(workdir).updatePlugin(pluginId);
|
|
435
|
+
async updatePlugin(pluginId, workdir, sessionId) {
|
|
436
|
+
return this.getPluginCore(workdir, sessionId).updatePlugin(pluginId);
|
|
379
437
|
}
|
|
380
|
-
async listMarketplaces(workdir) {
|
|
381
|
-
return this.getPluginCore(workdir).listMarketplaces();
|
|
438
|
+
async listMarketplaces(workdir, sessionId) {
|
|
439
|
+
return this.getPluginCore(workdir, sessionId).listMarketplaces();
|
|
382
440
|
}
|
|
383
|
-
async addMarketplace(input, scope, workdir) {
|
|
384
|
-
return this.getPluginCore(workdir).addMarketplace(input, scope);
|
|
441
|
+
async addMarketplace(input, scope, workdir, sessionId) {
|
|
442
|
+
return this.getPluginCore(workdir, sessionId).addMarketplace(input, scope);
|
|
385
443
|
}
|
|
386
|
-
async removeMarketplace(name, scope, workdir) {
|
|
387
|
-
await this.getPluginCore(workdir).removeMarketplace(name, scope);
|
|
444
|
+
async removeMarketplace(name, scope, workdir, sessionId) {
|
|
445
|
+
await this.getPluginCore(workdir, sessionId).removeMarketplace(name, scope);
|
|
388
446
|
return null;
|
|
389
447
|
}
|
|
390
|
-
async updateMarketplace(name, workdir) {
|
|
391
|
-
await this.getPluginCore(workdir).updateMarketplace(name);
|
|
448
|
+
async updateMarketplace(name, workdir, sessionId) {
|
|
449
|
+
await this.getPluginCore(workdir, sessionId).updateMarketplace(name);
|
|
392
450
|
return null;
|
|
393
451
|
}
|
|
394
452
|
// ── Callbacks → Notifications ─────────────────────────────────
|
|
395
|
-
createCallbacks() {
|
|
453
|
+
createCallbacks(ctx) {
|
|
396
454
|
return {
|
|
397
455
|
onMessagesChange: (messages) => {
|
|
398
|
-
this.emit("messagesChange", { messages });
|
|
456
|
+
this.emit("messagesChange", { messages }, ctx.registeredSessionId);
|
|
399
457
|
},
|
|
400
458
|
onUserMessageAdded: () => {
|
|
401
|
-
const msg = this.findLastUserMessage();
|
|
459
|
+
const msg = this.findLastUserMessage(ctx.agent);
|
|
402
460
|
if (msg)
|
|
403
|
-
this.emit("userMessageAdded", { message: msg });
|
|
461
|
+
this.emit("userMessageAdded", { message: msg }, ctx.registeredSessionId);
|
|
404
462
|
},
|
|
405
463
|
onAssistantMessageAdded: (messageId) => {
|
|
406
|
-
const msg =
|
|
464
|
+
const msg = ctx.agent?.messages.find((m) => m.id === messageId);
|
|
407
465
|
if (msg)
|
|
408
|
-
this.emit("assistantMessageAdded", { message: msg });
|
|
466
|
+
this.emit("assistantMessageAdded", { message: msg }, ctx.registeredSessionId);
|
|
409
467
|
},
|
|
410
468
|
onAssistantContentUpdated: (params) => {
|
|
411
|
-
this.emit("assistantContentUpdated", params);
|
|
469
|
+
this.emit("assistantContentUpdated", params, ctx.registeredSessionId);
|
|
412
470
|
},
|
|
413
471
|
onAssistantReasoningUpdated: (params) => {
|
|
414
|
-
this.emit("assistantReasoningUpdated", params);
|
|
472
|
+
this.emit("assistantReasoningUpdated", params, ctx.registeredSessionId);
|
|
415
473
|
},
|
|
416
474
|
onToolBlockUpdated: (params) => {
|
|
417
|
-
this.emit("toolBlockUpdated", params);
|
|
475
|
+
this.emit("toolBlockUpdated", params, ctx.registeredSessionId);
|
|
418
476
|
},
|
|
419
477
|
onErrorBlockAdded: (error) => {
|
|
420
|
-
this.emit("errorBlockAdded", { error });
|
|
478
|
+
this.emit("errorBlockAdded", { error }, ctx.registeredSessionId);
|
|
421
479
|
},
|
|
422
480
|
onLoadingChange: (loading) => {
|
|
423
481
|
this.emit("loadingChange", {
|
|
424
482
|
loading,
|
|
425
|
-
latestTotalTokens:
|
|
426
|
-
});
|
|
483
|
+
latestTotalTokens: ctx.agent?.latestTotalTokens,
|
|
484
|
+
}, ctx.registeredSessionId);
|
|
427
485
|
},
|
|
428
486
|
onCommandRunningChange: (running) => {
|
|
429
|
-
this.emit("commandRunningChange", { running });
|
|
487
|
+
this.emit("commandRunningChange", { running }, ctx.registeredSessionId);
|
|
430
488
|
},
|
|
431
489
|
onQueuedMessagesChange: (messages) => {
|
|
432
|
-
this.emit("queuedMessagesChange", { messages });
|
|
490
|
+
this.emit("queuedMessagesChange", { messages }, ctx.registeredSessionId);
|
|
433
491
|
},
|
|
434
492
|
onTasksChange: (tasks) => {
|
|
435
|
-
this.emit("tasksChange", { tasks });
|
|
493
|
+
this.emit("tasksChange", { tasks }, ctx.registeredSessionId);
|
|
436
494
|
},
|
|
437
|
-
onSessionIdChange: (
|
|
438
|
-
|
|
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;
|
|
439
508
|
},
|
|
440
509
|
onPermissionModeChange: (mode) => {
|
|
441
|
-
this.emit("permissionModeChange", { mode });
|
|
510
|
+
this.emit("permissionModeChange", { mode }, ctx.registeredSessionId);
|
|
442
511
|
},
|
|
443
512
|
onMcpServersChange: (servers) => {
|
|
444
|
-
this.emit("mcpServersChange", { servers });
|
|
513
|
+
this.emit("mcpServersChange", { servers }, ctx.registeredSessionId);
|
|
445
514
|
},
|
|
446
515
|
onAddBangMessage: () => {
|
|
447
|
-
this.emit("bangMessageAdded", {});
|
|
516
|
+
this.emit("bangMessageAdded", {}, ctx.registeredSessionId);
|
|
448
517
|
},
|
|
449
518
|
onUpdateBangMessage: () => {
|
|
450
|
-
this.emit("bangMessageUpdated", {});
|
|
519
|
+
this.emit("bangMessageUpdated", {}, ctx.registeredSessionId);
|
|
451
520
|
},
|
|
452
521
|
onCompleteBangMessage: () => {
|
|
453
|
-
this.emit("bangMessageCompleted", {});
|
|
522
|
+
this.emit("bangMessageCompleted", {}, ctx.registeredSessionId);
|
|
454
523
|
},
|
|
455
524
|
onNotificationMessageAdded: (params) => {
|
|
456
|
-
const msg =
|
|
525
|
+
const msg = ctx.agent?.messages.find((m) => m.role === "user" &&
|
|
457
526
|
m.blocks.some((b) => b.type === "task_notification" &&
|
|
458
527
|
b.taskId === params.taskId));
|
|
459
|
-
this.emit("notificationMessageAdded", {
|
|
460
|
-
...params,
|
|
461
|
-
message: msg,
|
|
462
|
-
});
|
|
528
|
+
this.emit("notificationMessageAdded", { ...params, message: msg }, ctx.registeredSessionId);
|
|
463
529
|
},
|
|
464
530
|
};
|
|
465
531
|
}
|
|
466
|
-
findLastUserMessage() {
|
|
467
|
-
const userMessages =
|
|
532
|
+
findLastUserMessage(agent) {
|
|
533
|
+
const userMessages = agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
468
534
|
return userMessages[userMessages.length - 1];
|
|
469
535
|
}
|
|
470
536
|
// ── Utils ─────────────────────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
if (!
|
|
473
|
-
throw new RpcError(PROTOCOL_INTERNAL_ERROR, "
|
|
537
|
+
requireSession(sessionId) {
|
|
538
|
+
if (!sessionId) {
|
|
539
|
+
throw new RpcError(PROTOCOL_INTERNAL_ERROR, "sessionId is required for this request");
|
|
474
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;
|
|
475
551
|
}
|
|
476
552
|
}
|
|
477
553
|
// ── Error class for protocol errors ─────────────────────────────
|