wave-code 0.19.3 → 0.19.5
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 +261 -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 +384 -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,12 +368,13 @@ 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 {
|
|
317
375
|
isAuthenticated: authService.isSSOAuthenticated(),
|
|
318
376
|
user: authService.getAuthUser(),
|
|
377
|
+
serverUrl: authService.getServerUrl(),
|
|
319
378
|
};
|
|
320
379
|
}
|
|
321
380
|
async login(serverUrl) {
|
|
@@ -333,17 +392,17 @@ export class AgentBridge {
|
|
|
333
392
|
await authService.clearAuth();
|
|
334
393
|
return null;
|
|
335
394
|
}
|
|
336
|
-
// ── Plugins
|
|
337
|
-
getPluginCore(workdir) {
|
|
338
|
-
const resolvedWorkdir = workdir || this.
|
|
395
|
+
// ── Plugins (global) ─────────────────────────────────────────
|
|
396
|
+
getPluginCore(workdir, sessionId) {
|
|
397
|
+
const resolvedWorkdir = workdir || this.getSessionWorkdir(sessionId) || process.cwd();
|
|
339
398
|
if (!this.pluginCore || this.pluginCoreWorkdir !== resolvedWorkdir) {
|
|
340
399
|
this.pluginCore = new PluginCore(resolvedWorkdir);
|
|
341
400
|
this.pluginCoreWorkdir = resolvedWorkdir;
|
|
342
401
|
}
|
|
343
402
|
return this.pluginCore;
|
|
344
403
|
}
|
|
345
|
-
async listPlugins(workdir) {
|
|
346
|
-
const core = this.getPluginCore(workdir);
|
|
404
|
+
async listPlugins(workdir, sessionId) {
|
|
405
|
+
const core = this.getPluginCore(workdir, sessionId);
|
|
347
406
|
const { plugins, mergedEnabled } = await core.listPlugins();
|
|
348
407
|
return {
|
|
349
408
|
plugins: plugins.map((p) => {
|
|
@@ -361,117 +420,135 @@ export class AgentBridge {
|
|
|
361
420
|
}),
|
|
362
421
|
};
|
|
363
422
|
}
|
|
364
|
-
async installPlugin(pluginId, scope, workdir) {
|
|
365
|
-
return this.getPluginCore(workdir).installPlugin(pluginId, scope);
|
|
423
|
+
async installPlugin(pluginId, scope, workdir, sessionId) {
|
|
424
|
+
return this.getPluginCore(workdir, sessionId).installPlugin(pluginId, scope);
|
|
366
425
|
}
|
|
367
|
-
async uninstallPlugin(pluginId, workdir) {
|
|
368
|
-
await this.getPluginCore(workdir).uninstallPlugin(pluginId);
|
|
426
|
+
async uninstallPlugin(pluginId, workdir, sessionId) {
|
|
427
|
+
await this.getPluginCore(workdir, sessionId).uninstallPlugin(pluginId);
|
|
369
428
|
return null;
|
|
370
429
|
}
|
|
371
|
-
async enablePlugin(pluginId, scope, workdir) {
|
|
372
|
-
return this.getPluginCore(workdir).enablePlugin(pluginId, scope);
|
|
430
|
+
async enablePlugin(pluginId, scope, workdir, sessionId) {
|
|
431
|
+
return this.getPluginCore(workdir, sessionId).enablePlugin(pluginId, scope);
|
|
373
432
|
}
|
|
374
|
-
async disablePlugin(pluginId, scope, workdir) {
|
|
375
|
-
return this.getPluginCore(workdir).disablePlugin(pluginId, scope);
|
|
433
|
+
async disablePlugin(pluginId, scope, workdir, sessionId) {
|
|
434
|
+
return this.getPluginCore(workdir, sessionId).disablePlugin(pluginId, scope);
|
|
376
435
|
}
|
|
377
|
-
async updatePlugin(pluginId, workdir) {
|
|
378
|
-
return this.getPluginCore(workdir).updatePlugin(pluginId);
|
|
436
|
+
async updatePlugin(pluginId, workdir, sessionId) {
|
|
437
|
+
return this.getPluginCore(workdir, sessionId).updatePlugin(pluginId);
|
|
379
438
|
}
|
|
380
|
-
async listMarketplaces(workdir) {
|
|
381
|
-
return this.getPluginCore(workdir).listMarketplaces();
|
|
439
|
+
async listMarketplaces(workdir, sessionId) {
|
|
440
|
+
return this.getPluginCore(workdir, sessionId).listMarketplaces();
|
|
382
441
|
}
|
|
383
|
-
async addMarketplace(input, scope, workdir) {
|
|
384
|
-
return this.getPluginCore(workdir).addMarketplace(input, scope);
|
|
442
|
+
async addMarketplace(input, scope, workdir, sessionId) {
|
|
443
|
+
return this.getPluginCore(workdir, sessionId).addMarketplace(input, scope);
|
|
385
444
|
}
|
|
386
|
-
async removeMarketplace(name, scope, workdir) {
|
|
387
|
-
await this.getPluginCore(workdir).removeMarketplace(name, scope);
|
|
445
|
+
async removeMarketplace(name, scope, workdir, sessionId) {
|
|
446
|
+
await this.getPluginCore(workdir, sessionId).removeMarketplace(name, scope);
|
|
388
447
|
return null;
|
|
389
448
|
}
|
|
390
|
-
async updateMarketplace(name, workdir) {
|
|
391
|
-
await this.getPluginCore(workdir).updateMarketplace(name);
|
|
449
|
+
async updateMarketplace(name, workdir, sessionId) {
|
|
450
|
+
await this.getPluginCore(workdir, sessionId).updateMarketplace(name);
|
|
392
451
|
return null;
|
|
393
452
|
}
|
|
394
453
|
// ── Callbacks → Notifications ─────────────────────────────────
|
|
395
|
-
createCallbacks() {
|
|
454
|
+
createCallbacks(ctx) {
|
|
396
455
|
return {
|
|
397
456
|
onMessagesChange: (messages) => {
|
|
398
|
-
this.emit("messagesChange", { messages });
|
|
457
|
+
this.emit("messagesChange", { messages }, ctx.registeredSessionId);
|
|
399
458
|
},
|
|
400
459
|
onUserMessageAdded: () => {
|
|
401
|
-
const msg = this.findLastUserMessage();
|
|
460
|
+
const msg = this.findLastUserMessage(ctx.agent);
|
|
402
461
|
if (msg)
|
|
403
|
-
this.emit("userMessageAdded", { message: msg });
|
|
462
|
+
this.emit("userMessageAdded", { message: msg }, ctx.registeredSessionId);
|
|
404
463
|
},
|
|
405
464
|
onAssistantMessageAdded: (messageId) => {
|
|
406
|
-
const msg =
|
|
465
|
+
const msg = ctx.agent?.messages.find((m) => m.id === messageId);
|
|
407
466
|
if (msg)
|
|
408
|
-
this.emit("assistantMessageAdded", { message: msg });
|
|
467
|
+
this.emit("assistantMessageAdded", { message: msg }, ctx.registeredSessionId);
|
|
409
468
|
},
|
|
410
469
|
onAssistantContentUpdated: (params) => {
|
|
411
|
-
this.emit("assistantContentUpdated", params);
|
|
470
|
+
this.emit("assistantContentUpdated", params, ctx.registeredSessionId);
|
|
412
471
|
},
|
|
413
472
|
onAssistantReasoningUpdated: (params) => {
|
|
414
|
-
this.emit("assistantReasoningUpdated", params);
|
|
473
|
+
this.emit("assistantReasoningUpdated", params, ctx.registeredSessionId);
|
|
415
474
|
},
|
|
416
475
|
onToolBlockUpdated: (params) => {
|
|
417
|
-
this.emit("toolBlockUpdated", params);
|
|
476
|
+
this.emit("toolBlockUpdated", params, ctx.registeredSessionId);
|
|
418
477
|
},
|
|
419
478
|
onErrorBlockAdded: (error) => {
|
|
420
|
-
this.emit("errorBlockAdded", { error });
|
|
479
|
+
this.emit("errorBlockAdded", { error }, ctx.registeredSessionId);
|
|
421
480
|
},
|
|
422
481
|
onLoadingChange: (loading) => {
|
|
423
482
|
this.emit("loadingChange", {
|
|
424
483
|
loading,
|
|
425
|
-
latestTotalTokens:
|
|
426
|
-
});
|
|
484
|
+
latestTotalTokens: ctx.agent?.latestTotalTokens,
|
|
485
|
+
}, ctx.registeredSessionId);
|
|
427
486
|
},
|
|
428
487
|
onCommandRunningChange: (running) => {
|
|
429
|
-
this.emit("commandRunningChange", { running });
|
|
488
|
+
this.emit("commandRunningChange", { running }, ctx.registeredSessionId);
|
|
430
489
|
},
|
|
431
490
|
onQueuedMessagesChange: (messages) => {
|
|
432
|
-
this.emit("queuedMessagesChange", { messages });
|
|
491
|
+
this.emit("queuedMessagesChange", { messages }, ctx.registeredSessionId);
|
|
433
492
|
},
|
|
434
493
|
onTasksChange: (tasks) => {
|
|
435
|
-
this.emit("tasksChange", { tasks });
|
|
494
|
+
this.emit("tasksChange", { tasks }, ctx.registeredSessionId);
|
|
436
495
|
},
|
|
437
|
-
onSessionIdChange: (
|
|
438
|
-
|
|
496
|
+
onSessionIdChange: (newSessionId) => {
|
|
497
|
+
const oldSessionId = ctx.registeredSessionId;
|
|
498
|
+
// Emit with the OLD sessionId so the client's router can deliver it
|
|
499
|
+
this.emit("sessionIdChange", { sessionId: newSessionId }, oldSessionId);
|
|
500
|
+
// Update the sessions Map key atomically (single-threaded, no await)
|
|
501
|
+
if (oldSessionId && oldSessionId !== newSessionId) {
|
|
502
|
+
const entry = this.sessions.get(oldSessionId);
|
|
503
|
+
if (entry) {
|
|
504
|
+
this.sessions.delete(oldSessionId);
|
|
505
|
+
this.sessions.set(newSessionId, entry);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
ctx.registeredSessionId = newSessionId;
|
|
439
509
|
},
|
|
440
510
|
onPermissionModeChange: (mode) => {
|
|
441
|
-
this.emit("permissionModeChange", { mode });
|
|
511
|
+
this.emit("permissionModeChange", { mode }, ctx.registeredSessionId);
|
|
442
512
|
},
|
|
443
513
|
onMcpServersChange: (servers) => {
|
|
444
|
-
this.emit("mcpServersChange", { servers });
|
|
514
|
+
this.emit("mcpServersChange", { servers }, ctx.registeredSessionId);
|
|
445
515
|
},
|
|
446
516
|
onAddBangMessage: () => {
|
|
447
|
-
this.emit("bangMessageAdded", {});
|
|
517
|
+
this.emit("bangMessageAdded", {}, ctx.registeredSessionId);
|
|
448
518
|
},
|
|
449
519
|
onUpdateBangMessage: () => {
|
|
450
|
-
this.emit("bangMessageUpdated", {});
|
|
520
|
+
this.emit("bangMessageUpdated", {}, ctx.registeredSessionId);
|
|
451
521
|
},
|
|
452
522
|
onCompleteBangMessage: () => {
|
|
453
|
-
this.emit("bangMessageCompleted", {});
|
|
523
|
+
this.emit("bangMessageCompleted", {}, ctx.registeredSessionId);
|
|
454
524
|
},
|
|
455
525
|
onNotificationMessageAdded: (params) => {
|
|
456
|
-
const msg =
|
|
526
|
+
const msg = ctx.agent?.messages.find((m) => m.role === "user" &&
|
|
457
527
|
m.blocks.some((b) => b.type === "task_notification" &&
|
|
458
528
|
b.taskId === params.taskId));
|
|
459
|
-
this.emit("notificationMessageAdded", {
|
|
460
|
-
...params,
|
|
461
|
-
message: msg,
|
|
462
|
-
});
|
|
529
|
+
this.emit("notificationMessageAdded", { ...params, message: msg }, ctx.registeredSessionId);
|
|
463
530
|
},
|
|
464
531
|
};
|
|
465
532
|
}
|
|
466
|
-
findLastUserMessage() {
|
|
467
|
-
const userMessages =
|
|
533
|
+
findLastUserMessage(agent) {
|
|
534
|
+
const userMessages = agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
468
535
|
return userMessages[userMessages.length - 1];
|
|
469
536
|
}
|
|
470
537
|
// ── Utils ─────────────────────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
if (!
|
|
473
|
-
throw new RpcError(PROTOCOL_INTERNAL_ERROR, "
|
|
538
|
+
requireSession(sessionId) {
|
|
539
|
+
if (!sessionId) {
|
|
540
|
+
throw new RpcError(PROTOCOL_INTERNAL_ERROR, "sessionId is required for this request");
|
|
474
541
|
}
|
|
542
|
+
const entry = this.sessions.get(sessionId);
|
|
543
|
+
if (!entry) {
|
|
544
|
+
throw new RpcError(PROTOCOL_INTERNAL_ERROR, `Session not found: ${sessionId}`);
|
|
545
|
+
}
|
|
546
|
+
return entry;
|
|
547
|
+
}
|
|
548
|
+
getSessionWorkdir(sessionId) {
|
|
549
|
+
if (!sessionId)
|
|
550
|
+
return undefined;
|
|
551
|
+
return this.sessions.get(sessionId)?.agent.workingDirectory;
|
|
475
552
|
}
|
|
476
553
|
}
|
|
477
554
|
// ── Error class for protocol errors ─────────────────────────────
|