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
package/src/stdio/agentBridge.ts
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
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
|
*/
|
|
@@ -35,8 +41,27 @@ import {
|
|
|
35
41
|
INTERNAL_ERROR as PROTOCOL_INTERNAL_ERROR,
|
|
36
42
|
METHOD_NOT_FOUND as PROTOCOL_METHOD_NOT_FOUND,
|
|
37
43
|
} from "./protocol.js";
|
|
44
|
+
import { readFileSync } from "fs";
|
|
45
|
+
import path from "path";
|
|
46
|
+
import { fileURLToPath } from "url";
|
|
47
|
+
|
|
48
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
const CLI_VERSION: string = (() => {
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(
|
|
52
|
+
readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8"),
|
|
53
|
+
);
|
|
54
|
+
return pkg.version ?? "";
|
|
55
|
+
} catch {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
38
59
|
|
|
39
|
-
export type NotificationEmitter = (
|
|
60
|
+
export type NotificationEmitter = (
|
|
61
|
+
method: string,
|
|
62
|
+
params: unknown,
|
|
63
|
+
sessionId?: string,
|
|
64
|
+
) => void;
|
|
40
65
|
|
|
41
66
|
export interface AgentBridgeOptions {
|
|
42
67
|
emit: NotificationEmitter;
|
|
@@ -58,6 +83,7 @@ interface InitializeParams {
|
|
|
58
83
|
disallowedTools?: string[];
|
|
59
84
|
pluginDirs?: string[];
|
|
60
85
|
mcpServers?: Record<string, McpServerConfig>;
|
|
86
|
+
clientVersion?: string;
|
|
61
87
|
}
|
|
62
88
|
|
|
63
89
|
interface UpdateConfigParams {
|
|
@@ -76,14 +102,30 @@ interface SearchFilesParams {
|
|
|
76
102
|
workdir?: string;
|
|
77
103
|
}
|
|
78
104
|
|
|
105
|
+
interface SessionEntry {
|
|
106
|
+
agent: Agent;
|
|
107
|
+
storedConfig: Partial<InitializeParams>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Mutable holder so callbacks/canUseTool (created before Agent.create resolves)
|
|
112
|
+
* can reference the agent and its registered sessionId after creation.
|
|
113
|
+
* `registeredSessionId` is the sessionId the client knows about; it's used as
|
|
114
|
+
* the envelope sessionId for outgoing notifications so the client's router can
|
|
115
|
+
* demultiplex correctly. It's updated atomically when onSessionIdChange fires.
|
|
116
|
+
*/
|
|
117
|
+
interface SessionContext {
|
|
118
|
+
agent?: Agent;
|
|
119
|
+
registeredSessionId?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
79
122
|
export class AgentBridge {
|
|
80
|
-
private
|
|
123
|
+
private sessions = new Map<string, SessionEntry>();
|
|
81
124
|
private pendingPermissions = new Map<
|
|
82
125
|
string,
|
|
83
126
|
(decision: PermissionDecision) => void
|
|
84
127
|
>();
|
|
85
128
|
private permissionCounter = 0;
|
|
86
|
-
private storedConfig: Partial<InitializeParams> = {};
|
|
87
129
|
private emit: NotificationEmitter;
|
|
88
130
|
private pluginCore: PluginCore | undefined;
|
|
89
131
|
private pluginCoreWorkdir: string | undefined;
|
|
@@ -94,22 +136,26 @@ export class AgentBridge {
|
|
|
94
136
|
|
|
95
137
|
// ── Public API ────────────────────────────────────────────────
|
|
96
138
|
|
|
97
|
-
async handleRequest(
|
|
139
|
+
async handleRequest(
|
|
140
|
+
method: string,
|
|
141
|
+
params: unknown,
|
|
142
|
+
sessionId?: string,
|
|
143
|
+
): Promise<unknown> {
|
|
98
144
|
const p = (params ?? {}) as Record<string, unknown>;
|
|
99
145
|
switch (method) {
|
|
100
146
|
// ── Lifecycle ──
|
|
101
147
|
case "initialize":
|
|
102
148
|
return this.initialize(p as unknown as InitializeParams);
|
|
103
149
|
case "destroy":
|
|
104
|
-
return this.destroy();
|
|
150
|
+
return this.destroy(sessionId);
|
|
105
151
|
case "restoreSession":
|
|
106
|
-
return this.restoreSession(p.sessionId as string);
|
|
152
|
+
return this.restoreSession(p.sessionId as string, sessionId);
|
|
107
153
|
case "listSessions":
|
|
108
|
-
return this.listSessions(p.workdir as string | undefined);
|
|
154
|
+
return this.listSessions(p.workdir as string | undefined, sessionId);
|
|
109
155
|
case "getSessionInfo":
|
|
110
|
-
return this.getSessionInfo();
|
|
156
|
+
return this.getSessionInfo(sessionId);
|
|
111
157
|
case "updateConfig":
|
|
112
|
-
return this.updateConfig(p as unknown as UpdateConfigParams);
|
|
158
|
+
return this.updateConfig(p as unknown as UpdateConfigParams, sessionId);
|
|
113
159
|
|
|
114
160
|
// ── Messages ──
|
|
115
161
|
case "sendMessage":
|
|
@@ -119,52 +165,57 @@ export class AgentBridge {
|
|
|
119
165
|
images?: Array<{ path: string; mimeType: string }>;
|
|
120
166
|
force?: boolean;
|
|
121
167
|
},
|
|
168
|
+
sessionId,
|
|
122
169
|
);
|
|
123
170
|
case "bang":
|
|
124
|
-
return this.bang(p.command as string);
|
|
171
|
+
return this.bang(p.command as string, sessionId);
|
|
125
172
|
case "abortMessage":
|
|
126
|
-
return this.abortMessage();
|
|
173
|
+
return this.abortMessage(sessionId);
|
|
127
174
|
case "clearMessages":
|
|
128
|
-
return this.clearMessages();
|
|
175
|
+
return this.clearMessages(sessionId);
|
|
129
176
|
case "rewindToMessage":
|
|
130
|
-
return this.rewindToMessage(p.messageId as string);
|
|
177
|
+
return this.rewindToMessage(p.messageId as string, sessionId);
|
|
131
178
|
case "deleteQueuedMessage":
|
|
132
|
-
return this.deleteQueuedMessage(p.index as number);
|
|
179
|
+
return this.deleteQueuedMessage(p.index as number, sessionId);
|
|
133
180
|
case "getMessages":
|
|
134
|
-
return this.getMessages();
|
|
181
|
+
return this.getMessages(sessionId);
|
|
135
182
|
case "getFullMessageThread":
|
|
136
|
-
return this.getFullMessageThread();
|
|
183
|
+
return this.getFullMessageThread(sessionId);
|
|
137
184
|
|
|
138
185
|
// ── Permissions ──
|
|
139
186
|
case "setPermissionMode":
|
|
140
|
-
return this.setPermissionMode(p.mode as PermissionMode);
|
|
187
|
+
return this.setPermissionMode(p.mode as PermissionMode, sessionId);
|
|
141
188
|
case "getPermissionMode":
|
|
142
|
-
return this.getPermissionMode();
|
|
189
|
+
return this.getPermissionMode(sessionId);
|
|
143
190
|
|
|
144
191
|
// ── MCP ──
|
|
145
192
|
case "getMcpServers":
|
|
146
|
-
return this.getMcpServers();
|
|
193
|
+
return this.getMcpServers(sessionId);
|
|
147
194
|
case "connectMcpServer":
|
|
148
|
-
return this.connectMcpServer(p.serverName as string);
|
|
195
|
+
return this.connectMcpServer(p.serverName as string, sessionId);
|
|
149
196
|
case "disconnectMcpServer":
|
|
150
|
-
return this.disconnectMcpServer(p.serverName as string);
|
|
197
|
+
return this.disconnectMcpServer(p.serverName as string, sessionId);
|
|
151
198
|
|
|
152
199
|
// ── Commands ──
|
|
153
200
|
case "getSlashCommands":
|
|
154
|
-
return this.getSlashCommands();
|
|
201
|
+
return this.getSlashCommands(sessionId);
|
|
155
202
|
|
|
156
|
-
// ── File / History ──
|
|
203
|
+
// ── File / History (global — no session required) ──
|
|
157
204
|
case "searchFiles":
|
|
158
|
-
return this.searchFiles(p as unknown as SearchFilesParams);
|
|
205
|
+
return this.searchFiles(p as unknown as SearchFilesParams, sessionId);
|
|
159
206
|
case "getPromptHistory":
|
|
160
|
-
return this.getPromptHistory(
|
|
207
|
+
return this.getPromptHistory(
|
|
208
|
+
p.workdir as string | undefined,
|
|
209
|
+
sessionId,
|
|
210
|
+
);
|
|
161
211
|
case "searchPromptHistory":
|
|
162
212
|
return this.searchPromptHistory(
|
|
163
213
|
p.query as string,
|
|
164
214
|
p.workdir as string | undefined,
|
|
215
|
+
sessionId,
|
|
165
216
|
);
|
|
166
217
|
|
|
167
|
-
// ── Auth ──
|
|
218
|
+
// ── Auth (global — no session required) ──
|
|
168
219
|
case "getAuthStatus":
|
|
169
220
|
return this.getAuthStatus();
|
|
170
221
|
case "login":
|
|
@@ -172,55 +223,66 @@ export class AgentBridge {
|
|
|
172
223
|
case "logout":
|
|
173
224
|
return this.logout();
|
|
174
225
|
|
|
175
|
-
// ── Plugins ──
|
|
226
|
+
// ── Plugins (global — no session required) ──
|
|
176
227
|
case "listPlugins":
|
|
177
|
-
return this.listPlugins(p.workdir as string | undefined);
|
|
228
|
+
return this.listPlugins(p.workdir as string | undefined, sessionId);
|
|
178
229
|
case "installPlugin":
|
|
179
230
|
return this.installPlugin(
|
|
180
231
|
p.pluginId as string,
|
|
181
232
|
p.scope as Scope | undefined,
|
|
182
233
|
p.workdir as string | undefined,
|
|
234
|
+
sessionId,
|
|
183
235
|
);
|
|
184
236
|
case "uninstallPlugin":
|
|
185
237
|
return this.uninstallPlugin(
|
|
186
238
|
p.pluginId as string,
|
|
187
239
|
p.workdir as string | undefined,
|
|
240
|
+
sessionId,
|
|
188
241
|
);
|
|
189
242
|
case "enablePlugin":
|
|
190
243
|
return this.enablePlugin(
|
|
191
244
|
p.pluginId as string,
|
|
192
245
|
p.scope as Scope | undefined,
|
|
193
246
|
p.workdir as string | undefined,
|
|
247
|
+
sessionId,
|
|
194
248
|
);
|
|
195
249
|
case "disablePlugin":
|
|
196
250
|
return this.disablePlugin(
|
|
197
251
|
p.pluginId as string,
|
|
198
252
|
p.scope as Scope | undefined,
|
|
199
253
|
p.workdir as string | undefined,
|
|
254
|
+
sessionId,
|
|
200
255
|
);
|
|
201
256
|
case "updatePlugin":
|
|
202
257
|
return this.updatePlugin(
|
|
203
258
|
p.pluginId as string,
|
|
204
259
|
p.workdir as string | undefined,
|
|
260
|
+
sessionId,
|
|
205
261
|
);
|
|
206
262
|
case "listMarketplaces":
|
|
207
|
-
return this.listMarketplaces(
|
|
263
|
+
return this.listMarketplaces(
|
|
264
|
+
p.workdir as string | undefined,
|
|
265
|
+
sessionId,
|
|
266
|
+
);
|
|
208
267
|
case "addMarketplace":
|
|
209
268
|
return this.addMarketplace(
|
|
210
269
|
p.input as string,
|
|
211
270
|
p.scope as Scope | undefined,
|
|
212
271
|
p.workdir as string | undefined,
|
|
272
|
+
sessionId,
|
|
213
273
|
);
|
|
214
274
|
case "removeMarketplace":
|
|
215
275
|
return this.removeMarketplace(
|
|
216
276
|
p.name as string,
|
|
217
277
|
p.scope as Scope | undefined,
|
|
218
278
|
p.workdir as string | undefined,
|
|
279
|
+
sessionId,
|
|
219
280
|
);
|
|
220
281
|
case "updateMarketplace":
|
|
221
282
|
return this.updateMarketplace(
|
|
222
283
|
p.name as string | undefined,
|
|
223
284
|
p.workdir as string | undefined,
|
|
285
|
+
sessionId,
|
|
224
286
|
);
|
|
225
287
|
|
|
226
288
|
default:
|
|
@@ -237,6 +299,7 @@ export class AgentBridge {
|
|
|
237
299
|
requestId: string;
|
|
238
300
|
decision: PermissionDecision;
|
|
239
301
|
};
|
|
302
|
+
// requestId is process-level unique; lookup doesn't need sessionId
|
|
240
303
|
const resolve = this.pendingPermissions.get(p.requestId);
|
|
241
304
|
if (resolve) {
|
|
242
305
|
this.pendingPermissions.delete(p.requestId);
|
|
@@ -252,11 +315,11 @@ export class AgentBridge {
|
|
|
252
315
|
workingDirectory: string;
|
|
253
316
|
permissionMode: PermissionMode;
|
|
254
317
|
latestTotalTokens: number;
|
|
318
|
+
serverVersion: string;
|
|
255
319
|
}> {
|
|
256
|
-
|
|
257
|
-
|
|
320
|
+
const ctx: SessionContext = {};
|
|
321
|
+
const callbacks = this.createCallbacks(ctx);
|
|
258
322
|
|
|
259
|
-
const callbacks = this.createCallbacks();
|
|
260
323
|
const options: AgentOptions = {
|
|
261
324
|
callbacks,
|
|
262
325
|
workdir: params.workdir,
|
|
@@ -273,125 +336,184 @@ export class AgentBridge {
|
|
|
273
336
|
disallowedTools: params.disallowedTools,
|
|
274
337
|
plugins: params.pluginDirs?.map((path) => ({ type: "local", path })),
|
|
275
338
|
mcpServers: params.mcpServers,
|
|
276
|
-
canUseTool: (context: ToolPermissionContext) =>
|
|
339
|
+
canUseTool: (context: ToolPermissionContext) =>
|
|
340
|
+
this.canUseTool(context, ctx),
|
|
277
341
|
};
|
|
278
342
|
|
|
279
|
-
|
|
343
|
+
const agent = await Agent.create(options);
|
|
344
|
+
ctx.agent = agent;
|
|
345
|
+
ctx.registeredSessionId = agent.sessionId;
|
|
346
|
+
|
|
347
|
+
this.sessions.set(agent.sessionId, {
|
|
348
|
+
agent,
|
|
349
|
+
storedConfig: { ...params },
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (params.clientVersion) {
|
|
353
|
+
console.debug(
|
|
354
|
+
`[agentBridge] clientVersion=${params.clientVersion} serverVersion=${CLI_VERSION}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
280
357
|
|
|
281
358
|
return {
|
|
282
|
-
sessionId:
|
|
283
|
-
workingDirectory:
|
|
284
|
-
permissionMode:
|
|
285
|
-
latestTotalTokens:
|
|
359
|
+
sessionId: agent.sessionId,
|
|
360
|
+
workingDirectory: agent.workingDirectory,
|
|
361
|
+
permissionMode: agent.getPermissionMode(),
|
|
362
|
+
latestTotalTokens: agent.latestTotalTokens,
|
|
363
|
+
serverVersion: CLI_VERSION,
|
|
286
364
|
};
|
|
287
365
|
}
|
|
288
366
|
|
|
289
|
-
private async destroy(): Promise<null> {
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
367
|
+
private async destroy(sessionId?: string): Promise<null> {
|
|
368
|
+
if (sessionId) {
|
|
369
|
+
const entry = this.sessions.get(sessionId);
|
|
370
|
+
if (entry) {
|
|
371
|
+
await entry.agent.destroy();
|
|
372
|
+
this.sessions.delete(sessionId);
|
|
373
|
+
}
|
|
293
374
|
}
|
|
294
375
|
return null;
|
|
295
376
|
}
|
|
296
377
|
|
|
297
|
-
private async restoreSession(
|
|
298
|
-
|
|
299
|
-
|
|
378
|
+
private async restoreSession(
|
|
379
|
+
restoreId: string,
|
|
380
|
+
sessionId?: string,
|
|
381
|
+
): Promise<null> {
|
|
382
|
+
const entry = this.requireSession(sessionId);
|
|
383
|
+
await entry.agent.restoreSession(restoreId);
|
|
300
384
|
return null;
|
|
301
385
|
}
|
|
302
386
|
|
|
303
387
|
private async listSessions(
|
|
304
388
|
workdir?: string,
|
|
389
|
+
sessionId?: string,
|
|
305
390
|
): Promise<{ sessions: SessionMetadata[] }> {
|
|
306
391
|
const sessions = await listSessions(
|
|
307
|
-
workdir || this.
|
|
392
|
+
workdir || this.getSessionWorkdir(sessionId) || process.cwd(),
|
|
308
393
|
);
|
|
309
394
|
return { sessions };
|
|
310
395
|
}
|
|
311
396
|
|
|
312
|
-
private getSessionInfo(): {
|
|
397
|
+
private getSessionInfo(sessionId?: string): {
|
|
313
398
|
sessionId: string;
|
|
314
399
|
workingDirectory: string;
|
|
315
400
|
latestTotalTokens: number;
|
|
316
401
|
permissionMode: PermissionMode;
|
|
317
402
|
availableTools: string[];
|
|
318
403
|
} {
|
|
319
|
-
this.
|
|
404
|
+
const entry = this.requireSession(sessionId);
|
|
320
405
|
return {
|
|
321
|
-
sessionId:
|
|
322
|
-
workingDirectory:
|
|
323
|
-
latestTotalTokens:
|
|
324
|
-
permissionMode:
|
|
325
|
-
availableTools:
|
|
406
|
+
sessionId: entry.agent.sessionId,
|
|
407
|
+
workingDirectory: entry.agent.workingDirectory,
|
|
408
|
+
latestTotalTokens: entry.agent.latestTotalTokens,
|
|
409
|
+
permissionMode: entry.agent.getPermissionMode(),
|
|
410
|
+
availableTools: entry.agent.getAvailableToolNames(),
|
|
326
411
|
};
|
|
327
412
|
}
|
|
328
413
|
|
|
329
414
|
private async updateConfig(
|
|
330
415
|
params: UpdateConfigParams,
|
|
416
|
+
sessionId?: string,
|
|
331
417
|
): Promise<{ sessionId: string }> {
|
|
332
|
-
this.
|
|
333
|
-
const currentSessionId =
|
|
418
|
+
const entry = this.requireSession(sessionId);
|
|
419
|
+
const currentSessionId = entry.agent.sessionId;
|
|
334
420
|
// Merge new config into stored config
|
|
335
|
-
|
|
336
|
-
// Destroy and recreate
|
|
337
|
-
await
|
|
338
|
-
this.
|
|
339
|
-
|
|
340
|
-
|
|
421
|
+
entry.storedConfig = { ...entry.storedConfig, ...params };
|
|
422
|
+
// Destroy and recreate within the same session slot
|
|
423
|
+
await entry.agent.destroy();
|
|
424
|
+
this.sessions.delete(currentSessionId);
|
|
425
|
+
|
|
426
|
+
const ctx: SessionContext = {};
|
|
427
|
+
const callbacks = this.createCallbacks(ctx);
|
|
428
|
+
const options: AgentOptions = {
|
|
429
|
+
callbacks,
|
|
430
|
+
workdir: entry.storedConfig.workdir,
|
|
341
431
|
restoreSessionId: currentSessionId,
|
|
432
|
+
apiKey: entry.storedConfig.apiKey,
|
|
433
|
+
baseURL: entry.storedConfig.baseURL,
|
|
434
|
+
defaultHeaders: entry.storedConfig.defaultHeaders,
|
|
435
|
+
model: entry.storedConfig.model,
|
|
436
|
+
fastModel: entry.storedConfig.fastModel,
|
|
437
|
+
language: entry.storedConfig.language,
|
|
438
|
+
permissionMode: entry.storedConfig.permissionMode,
|
|
439
|
+
tools: entry.storedConfig.tools,
|
|
440
|
+
allowedTools: entry.storedConfig.allowedTools,
|
|
441
|
+
disallowedTools: entry.storedConfig.disallowedTools,
|
|
442
|
+
plugins: entry.storedConfig.pluginDirs?.map((p) => ({
|
|
443
|
+
type: "local",
|
|
444
|
+
path: p,
|
|
445
|
+
})),
|
|
446
|
+
mcpServers: entry.storedConfig.mcpServers,
|
|
447
|
+
canUseTool: (context: ToolPermissionContext) =>
|
|
448
|
+
this.canUseTool(context, ctx),
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const agent = await Agent.create(options);
|
|
452
|
+
ctx.agent = agent;
|
|
453
|
+
ctx.registeredSessionId = agent.sessionId;
|
|
454
|
+
this.sessions.set(agent.sessionId, {
|
|
455
|
+
agent,
|
|
456
|
+
storedConfig: { ...entry.storedConfig },
|
|
342
457
|
});
|
|
343
|
-
|
|
458
|
+
|
|
459
|
+
return { sessionId: agent.sessionId };
|
|
344
460
|
}
|
|
345
461
|
|
|
346
462
|
// ── Messages ──────────────────────────────────────────────────
|
|
347
463
|
|
|
348
|
-
private async sendMessage(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
464
|
+
private async sendMessage(
|
|
465
|
+
params: {
|
|
466
|
+
text: string;
|
|
467
|
+
images?: Array<{ path: string; mimeType: string }>;
|
|
468
|
+
force?: boolean;
|
|
469
|
+
},
|
|
470
|
+
sessionId?: string,
|
|
471
|
+
): Promise<null> {
|
|
472
|
+
const entry = this.requireSession(sessionId);
|
|
354
473
|
if (params.force) {
|
|
355
|
-
|
|
474
|
+
entry.agent.abortMessage();
|
|
356
475
|
}
|
|
357
476
|
// Save prompt to history (mirrors VSCE chatSession.ts:236-242)
|
|
358
477
|
try {
|
|
359
478
|
await PromptHistoryManager.addEntry(
|
|
360
479
|
params.text,
|
|
361
|
-
|
|
480
|
+
entry.agent.sessionId,
|
|
362
481
|
{},
|
|
363
|
-
|
|
482
|
+
entry.agent.workingDirectory,
|
|
364
483
|
);
|
|
365
484
|
} catch {
|
|
366
485
|
// Best-effort; don't block message sending on history save failure
|
|
367
486
|
}
|
|
368
|
-
await
|
|
487
|
+
await entry.agent.sendMessage(params.text, params.images);
|
|
369
488
|
return null;
|
|
370
489
|
}
|
|
371
490
|
|
|
372
|
-
private async bang(command: string): Promise<null> {
|
|
373
|
-
this.
|
|
374
|
-
await
|
|
491
|
+
private async bang(command: string, sessionId?: string): Promise<null> {
|
|
492
|
+
const entry = this.requireSession(sessionId);
|
|
493
|
+
await entry.agent.bang(command);
|
|
375
494
|
return null;
|
|
376
495
|
}
|
|
377
496
|
|
|
378
|
-
private async abortMessage(): Promise<null> {
|
|
379
|
-
this.
|
|
380
|
-
|
|
497
|
+
private async abortMessage(sessionId?: string): Promise<null> {
|
|
498
|
+
const entry = this.requireSession(sessionId);
|
|
499
|
+
entry.agent.abortMessage();
|
|
381
500
|
return null;
|
|
382
501
|
}
|
|
383
502
|
|
|
384
|
-
private async clearMessages(): Promise<null> {
|
|
385
|
-
this.
|
|
386
|
-
|
|
503
|
+
private async clearMessages(sessionId?: string): Promise<null> {
|
|
504
|
+
const entry = this.requireSession(sessionId);
|
|
505
|
+
entry.agent.clearMessages();
|
|
387
506
|
return null;
|
|
388
507
|
}
|
|
389
508
|
|
|
390
|
-
private async rewindToMessage(
|
|
509
|
+
private async rewindToMessage(
|
|
510
|
+
messageId: string,
|
|
511
|
+
sessionId?: string,
|
|
512
|
+
): Promise<{
|
|
391
513
|
inputContent: string;
|
|
392
514
|
}> {
|
|
393
|
-
this.
|
|
394
|
-
const { messages } = await
|
|
515
|
+
const entry = this.requireSession(sessionId);
|
|
516
|
+
const { messages } = await entry.agent.getFullMessageThread();
|
|
395
517
|
const index = messages.findIndex((m) => m.id === messageId);
|
|
396
518
|
if (index === -1) {
|
|
397
519
|
throw new RpcError(
|
|
@@ -403,90 +525,99 @@ export class AgentBridge {
|
|
|
403
525
|
const textBlock = message.blocks.find((b) => b.type === "text") as
|
|
404
526
|
| { content?: string }
|
|
405
527
|
| undefined;
|
|
406
|
-
await
|
|
528
|
+
await entry.agent.truncateHistory(index);
|
|
407
529
|
return { inputContent: textBlock?.content || "" };
|
|
408
530
|
}
|
|
409
531
|
|
|
410
|
-
private deleteQueuedMessage(index: number): null {
|
|
411
|
-
this.
|
|
412
|
-
|
|
532
|
+
private deleteQueuedMessage(index: number, sessionId?: string): null {
|
|
533
|
+
const entry = this.requireSession(sessionId);
|
|
534
|
+
entry.agent.removeQueuedMessage(index);
|
|
413
535
|
return null;
|
|
414
536
|
}
|
|
415
537
|
|
|
416
|
-
private getMessages(): { messages: Message[] } {
|
|
417
|
-
this.
|
|
418
|
-
return { messages:
|
|
538
|
+
private getMessages(sessionId?: string): { messages: Message[] } {
|
|
539
|
+
const entry = this.requireSession(sessionId);
|
|
540
|
+
return { messages: entry.agent.messages };
|
|
419
541
|
}
|
|
420
542
|
|
|
421
|
-
private async getFullMessageThread(): Promise<{
|
|
543
|
+
private async getFullMessageThread(sessionId?: string): Promise<{
|
|
422
544
|
messages: Message[];
|
|
423
545
|
sessionIds: string[];
|
|
424
546
|
}> {
|
|
425
|
-
this.
|
|
426
|
-
return
|
|
547
|
+
const entry = this.requireSession(sessionId);
|
|
548
|
+
return entry.agent.getFullMessageThread();
|
|
427
549
|
}
|
|
428
550
|
|
|
429
551
|
// ── Permissions ───────────────────────────────────────────────
|
|
430
552
|
|
|
431
|
-
private async setPermissionMode(
|
|
432
|
-
|
|
433
|
-
|
|
553
|
+
private async setPermissionMode(
|
|
554
|
+
mode: PermissionMode,
|
|
555
|
+
sessionId?: string,
|
|
556
|
+
): Promise<null> {
|
|
557
|
+
const entry = this.requireSession(sessionId);
|
|
558
|
+
await entry.agent.setPermissionMode(mode);
|
|
434
559
|
return null;
|
|
435
560
|
}
|
|
436
561
|
|
|
437
|
-
private getPermissionMode(): { mode: PermissionMode } {
|
|
438
|
-
this.
|
|
439
|
-
return { mode:
|
|
562
|
+
private getPermissionMode(sessionId?: string): { mode: PermissionMode } {
|
|
563
|
+
const entry = this.requireSession(sessionId);
|
|
564
|
+
return { mode: entry.agent.getPermissionMode() };
|
|
440
565
|
}
|
|
441
566
|
|
|
442
567
|
// ── MCP ───────────────────────────────────────────────────────
|
|
443
568
|
|
|
444
|
-
private getMcpServers(): { servers: McpServerStatus[] } {
|
|
445
|
-
this.
|
|
446
|
-
return { servers:
|
|
569
|
+
private getMcpServers(sessionId?: string): { servers: McpServerStatus[] } {
|
|
570
|
+
const entry = this.requireSession(sessionId);
|
|
571
|
+
return { servers: entry.agent.getMcpServers() };
|
|
447
572
|
}
|
|
448
573
|
|
|
449
574
|
private async connectMcpServer(
|
|
450
575
|
serverName: string,
|
|
576
|
+
sessionId?: string,
|
|
451
577
|
): Promise<{ success: boolean }> {
|
|
452
|
-
this.
|
|
453
|
-
const success = await
|
|
578
|
+
const entry = this.requireSession(sessionId);
|
|
579
|
+
const success = await entry.agent.connectMcpServer(serverName);
|
|
454
580
|
return { success };
|
|
455
581
|
}
|
|
456
582
|
|
|
457
583
|
private async disconnectMcpServer(
|
|
458
584
|
serverName: string,
|
|
585
|
+
sessionId?: string,
|
|
459
586
|
): Promise<{ success: boolean }> {
|
|
460
|
-
this.
|
|
461
|
-
const success = await
|
|
587
|
+
const entry = this.requireSession(sessionId);
|
|
588
|
+
const success = await entry.agent.disconnectMcpServer(serverName);
|
|
462
589
|
return { success };
|
|
463
590
|
}
|
|
464
591
|
|
|
465
592
|
// ── Commands ──────────────────────────────────────────────────
|
|
466
593
|
|
|
467
|
-
private getSlashCommands(): { commands: SlashCommand[] } {
|
|
468
|
-
this.
|
|
469
|
-
return { commands:
|
|
594
|
+
private getSlashCommands(sessionId?: string): { commands: SlashCommand[] } {
|
|
595
|
+
const entry = this.requireSession(sessionId);
|
|
596
|
+
return { commands: entry.agent.getSlashCommands() };
|
|
470
597
|
}
|
|
471
598
|
|
|
472
|
-
// ── File / History
|
|
599
|
+
// ── File / History (global) ───────────────────────────────────
|
|
473
600
|
|
|
474
601
|
private async searchFiles(
|
|
475
602
|
params: SearchFilesParams,
|
|
603
|
+
sessionId?: string,
|
|
476
604
|
): Promise<{ files: Awaited<ReturnType<typeof searchFiles>> }> {
|
|
477
605
|
const files = await searchFiles(params.query, {
|
|
478
606
|
maxResults: params.maxResults,
|
|
479
607
|
workingDirectory:
|
|
480
|
-
params.workdir || this.
|
|
608
|
+
params.workdir || this.getSessionWorkdir(sessionId) || process.cwd(),
|
|
481
609
|
});
|
|
482
610
|
return { files };
|
|
483
611
|
}
|
|
484
612
|
|
|
485
|
-
private async getPromptHistory(
|
|
613
|
+
private async getPromptHistory(
|
|
614
|
+
workdir?: string,
|
|
615
|
+
sessionId?: string,
|
|
616
|
+
): Promise<{
|
|
486
617
|
history: Awaited<ReturnType<typeof PromptHistoryManager.getHistory>>;
|
|
487
618
|
}> {
|
|
488
619
|
const history = await PromptHistoryManager.getHistory({
|
|
489
|
-
workdir: workdir || this.
|
|
620
|
+
workdir: workdir || this.getSessionWorkdir(sessionId),
|
|
490
621
|
});
|
|
491
622
|
return { history };
|
|
492
623
|
}
|
|
@@ -494,11 +625,12 @@ export class AgentBridge {
|
|
|
494
625
|
private async searchPromptHistory(
|
|
495
626
|
query: string,
|
|
496
627
|
workdir?: string,
|
|
628
|
+
sessionId?: string,
|
|
497
629
|
): Promise<{
|
|
498
630
|
history: Awaited<ReturnType<typeof PromptHistoryManager.searchHistory>>;
|
|
499
631
|
}> {
|
|
500
632
|
const history = await PromptHistoryManager.searchHistory(query, {
|
|
501
|
-
workdir: workdir || this.
|
|
633
|
+
workdir: workdir || this.getSessionWorkdir(sessionId),
|
|
502
634
|
});
|
|
503
635
|
return { history };
|
|
504
636
|
}
|
|
@@ -507,11 +639,16 @@ export class AgentBridge {
|
|
|
507
639
|
|
|
508
640
|
private canUseTool(
|
|
509
641
|
context: ToolPermissionContext,
|
|
642
|
+
ctx: SessionContext,
|
|
510
643
|
): Promise<PermissionDecision> {
|
|
511
644
|
const requestId = `perm_${++this.permissionCounter}`;
|
|
512
645
|
return new Promise<PermissionDecision>((resolve) => {
|
|
513
646
|
this.pendingPermissions.set(requestId, resolve);
|
|
514
|
-
this.emit(
|
|
647
|
+
this.emit(
|
|
648
|
+
"permissionRequest",
|
|
649
|
+
{ requestId, context },
|
|
650
|
+
ctx.registeredSessionId,
|
|
651
|
+
);
|
|
515
652
|
|
|
516
653
|
// 5-minute timeout → auto-deny
|
|
517
654
|
setTimeout(
|
|
@@ -529,16 +666,18 @@ export class AgentBridge {
|
|
|
529
666
|
});
|
|
530
667
|
}
|
|
531
668
|
|
|
532
|
-
// ── Auth
|
|
669
|
+
// ── Auth (global) ────────────────────────────────────────────
|
|
533
670
|
|
|
534
671
|
private async getAuthStatus(): Promise<{
|
|
535
672
|
isAuthenticated: boolean;
|
|
536
673
|
user: { id: string; email?: string } | undefined;
|
|
674
|
+
serverUrl: string;
|
|
537
675
|
}> {
|
|
538
676
|
const authService = AuthService.getInstance();
|
|
539
677
|
return {
|
|
540
678
|
isAuthenticated: authService.isSSOAuthenticated(),
|
|
541
679
|
user: authService.getAuthUser(),
|
|
680
|
+
serverUrl: authService.getServerUrl(),
|
|
542
681
|
};
|
|
543
682
|
}
|
|
544
683
|
|
|
@@ -561,11 +700,11 @@ export class AgentBridge {
|
|
|
561
700
|
return null;
|
|
562
701
|
}
|
|
563
702
|
|
|
564
|
-
// ── Plugins
|
|
703
|
+
// ── Plugins (global) ─────────────────────────────────────────
|
|
565
704
|
|
|
566
|
-
private getPluginCore(workdir?: string): PluginCore {
|
|
705
|
+
private getPluginCore(workdir?: string, sessionId?: string): PluginCore {
|
|
567
706
|
const resolvedWorkdir =
|
|
568
|
-
workdir || this.
|
|
707
|
+
workdir || this.getSessionWorkdir(sessionId) || process.cwd();
|
|
569
708
|
if (!this.pluginCore || this.pluginCoreWorkdir !== resolvedWorkdir) {
|
|
570
709
|
this.pluginCore = new PluginCore(resolvedWorkdir);
|
|
571
710
|
this.pluginCoreWorkdir = resolvedWorkdir;
|
|
@@ -573,8 +712,8 @@ export class AgentBridge {
|
|
|
573
712
|
return this.pluginCore;
|
|
574
713
|
}
|
|
575
714
|
|
|
576
|
-
private async listPlugins(workdir?: string) {
|
|
577
|
-
const core = this.getPluginCore(workdir);
|
|
715
|
+
private async listPlugins(workdir?: string, sessionId?: string) {
|
|
716
|
+
const core = this.getPluginCore(workdir, sessionId);
|
|
578
717
|
const { plugins, mergedEnabled } = await core.listPlugins();
|
|
579
718
|
return {
|
|
580
719
|
plugins: plugins.map((p) => {
|
|
@@ -597,12 +736,20 @@ export class AgentBridge {
|
|
|
597
736
|
pluginId: string,
|
|
598
737
|
scope?: Scope,
|
|
599
738
|
workdir?: string,
|
|
739
|
+
sessionId?: string,
|
|
600
740
|
) {
|
|
601
|
-
return this.getPluginCore(workdir).installPlugin(
|
|
741
|
+
return this.getPluginCore(workdir, sessionId).installPlugin(
|
|
742
|
+
pluginId,
|
|
743
|
+
scope,
|
|
744
|
+
);
|
|
602
745
|
}
|
|
603
746
|
|
|
604
|
-
private async uninstallPlugin(
|
|
605
|
-
|
|
747
|
+
private async uninstallPlugin(
|
|
748
|
+
pluginId: string,
|
|
749
|
+
workdir?: string,
|
|
750
|
+
sessionId?: string,
|
|
751
|
+
) {
|
|
752
|
+
await this.getPluginCore(workdir, sessionId).uninstallPlugin(pluginId);
|
|
606
753
|
return null;
|
|
607
754
|
}
|
|
608
755
|
|
|
@@ -610,106 +757,154 @@ export class AgentBridge {
|
|
|
610
757
|
pluginId: string,
|
|
611
758
|
scope?: Scope,
|
|
612
759
|
workdir?: string,
|
|
760
|
+
sessionId?: string,
|
|
613
761
|
) {
|
|
614
|
-
return this.getPluginCore(workdir).enablePlugin(pluginId, scope);
|
|
762
|
+
return this.getPluginCore(workdir, sessionId).enablePlugin(pluginId, scope);
|
|
615
763
|
}
|
|
616
764
|
|
|
617
765
|
private async disablePlugin(
|
|
618
766
|
pluginId: string,
|
|
619
767
|
scope?: Scope,
|
|
620
768
|
workdir?: string,
|
|
769
|
+
sessionId?: string,
|
|
621
770
|
) {
|
|
622
|
-
return this.getPluginCore(workdir).disablePlugin(
|
|
771
|
+
return this.getPluginCore(workdir, sessionId).disablePlugin(
|
|
772
|
+
pluginId,
|
|
773
|
+
scope,
|
|
774
|
+
);
|
|
623
775
|
}
|
|
624
776
|
|
|
625
|
-
private async updatePlugin(
|
|
626
|
-
|
|
777
|
+
private async updatePlugin(
|
|
778
|
+
pluginId: string,
|
|
779
|
+
workdir?: string,
|
|
780
|
+
sessionId?: string,
|
|
781
|
+
) {
|
|
782
|
+
return this.getPluginCore(workdir, sessionId).updatePlugin(pluginId);
|
|
627
783
|
}
|
|
628
784
|
|
|
629
|
-
private async listMarketplaces(workdir?: string) {
|
|
630
|
-
return this.getPluginCore(workdir).listMarketplaces();
|
|
785
|
+
private async listMarketplaces(workdir?: string, sessionId?: string) {
|
|
786
|
+
return this.getPluginCore(workdir, sessionId).listMarketplaces();
|
|
631
787
|
}
|
|
632
788
|
|
|
633
|
-
private async addMarketplace(
|
|
634
|
-
|
|
789
|
+
private async addMarketplace(
|
|
790
|
+
input: string,
|
|
791
|
+
scope?: Scope,
|
|
792
|
+
workdir?: string,
|
|
793
|
+
sessionId?: string,
|
|
794
|
+
) {
|
|
795
|
+
return this.getPluginCore(workdir, sessionId).addMarketplace(input, scope);
|
|
635
796
|
}
|
|
636
797
|
|
|
637
798
|
private async removeMarketplace(
|
|
638
799
|
name: string,
|
|
639
800
|
scope?: Scope,
|
|
640
801
|
workdir?: string,
|
|
802
|
+
sessionId?: string,
|
|
641
803
|
) {
|
|
642
|
-
await this.getPluginCore(workdir).removeMarketplace(name, scope);
|
|
804
|
+
await this.getPluginCore(workdir, sessionId).removeMarketplace(name, scope);
|
|
643
805
|
return null;
|
|
644
806
|
}
|
|
645
807
|
|
|
646
|
-
private async updateMarketplace(
|
|
647
|
-
|
|
808
|
+
private async updateMarketplace(
|
|
809
|
+
name?: string,
|
|
810
|
+
workdir?: string,
|
|
811
|
+
sessionId?: string,
|
|
812
|
+
) {
|
|
813
|
+
await this.getPluginCore(workdir, sessionId).updateMarketplace(name);
|
|
648
814
|
return null;
|
|
649
815
|
}
|
|
650
816
|
|
|
651
817
|
// ── Callbacks → Notifications ─────────────────────────────────
|
|
652
818
|
|
|
653
|
-
private createCallbacks(): AgentCallbacks {
|
|
819
|
+
private createCallbacks(ctx: SessionContext): AgentCallbacks {
|
|
654
820
|
return {
|
|
655
821
|
onMessagesChange: (messages: Message[]) => {
|
|
656
|
-
this.emit("messagesChange", { messages });
|
|
822
|
+
this.emit("messagesChange", { messages }, ctx.registeredSessionId);
|
|
657
823
|
},
|
|
658
824
|
onUserMessageAdded: () => {
|
|
659
|
-
const msg = this.findLastUserMessage();
|
|
660
|
-
if (msg)
|
|
825
|
+
const msg = this.findLastUserMessage(ctx.agent);
|
|
826
|
+
if (msg)
|
|
827
|
+
this.emit(
|
|
828
|
+
"userMessageAdded",
|
|
829
|
+
{ message: msg },
|
|
830
|
+
ctx.registeredSessionId,
|
|
831
|
+
);
|
|
661
832
|
},
|
|
662
833
|
onAssistantMessageAdded: (messageId: string) => {
|
|
663
|
-
const msg =
|
|
664
|
-
if (msg)
|
|
834
|
+
const msg = ctx.agent?.messages.find((m) => m.id === messageId);
|
|
835
|
+
if (msg)
|
|
836
|
+
this.emit(
|
|
837
|
+
"assistantMessageAdded",
|
|
838
|
+
{ message: msg },
|
|
839
|
+
ctx.registeredSessionId,
|
|
840
|
+
);
|
|
665
841
|
},
|
|
666
842
|
onAssistantContentUpdated: (params) => {
|
|
667
|
-
this.emit("assistantContentUpdated", params);
|
|
843
|
+
this.emit("assistantContentUpdated", params, ctx.registeredSessionId);
|
|
668
844
|
},
|
|
669
845
|
onAssistantReasoningUpdated: (params) => {
|
|
670
|
-
this.emit("assistantReasoningUpdated", params);
|
|
846
|
+
this.emit("assistantReasoningUpdated", params, ctx.registeredSessionId);
|
|
671
847
|
},
|
|
672
848
|
onToolBlockUpdated: (params) => {
|
|
673
|
-
this.emit("toolBlockUpdated", params);
|
|
849
|
+
this.emit("toolBlockUpdated", params, ctx.registeredSessionId);
|
|
674
850
|
},
|
|
675
851
|
onErrorBlockAdded: (error: string) => {
|
|
676
|
-
this.emit("errorBlockAdded", { error });
|
|
852
|
+
this.emit("errorBlockAdded", { error }, ctx.registeredSessionId);
|
|
677
853
|
},
|
|
678
854
|
onLoadingChange: (loading: boolean) => {
|
|
679
|
-
this.emit(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
855
|
+
this.emit(
|
|
856
|
+
"loadingChange",
|
|
857
|
+
{
|
|
858
|
+
loading,
|
|
859
|
+
latestTotalTokens: ctx.agent?.latestTotalTokens,
|
|
860
|
+
},
|
|
861
|
+
ctx.registeredSessionId,
|
|
862
|
+
);
|
|
683
863
|
},
|
|
684
864
|
onCommandRunningChange: (running: boolean) => {
|
|
685
|
-
this.emit("commandRunningChange", { running });
|
|
865
|
+
this.emit("commandRunningChange", { running }, ctx.registeredSessionId);
|
|
686
866
|
},
|
|
687
867
|
onQueuedMessagesChange: (messages: QueuedMessage[]) => {
|
|
688
|
-
this.emit(
|
|
868
|
+
this.emit(
|
|
869
|
+
"queuedMessagesChange",
|
|
870
|
+
{ messages },
|
|
871
|
+
ctx.registeredSessionId,
|
|
872
|
+
);
|
|
689
873
|
},
|
|
690
874
|
onTasksChange: (tasks: Task[]) => {
|
|
691
|
-
this.emit("tasksChange", { tasks });
|
|
875
|
+
this.emit("tasksChange", { tasks }, ctx.registeredSessionId);
|
|
692
876
|
},
|
|
693
|
-
onSessionIdChange: (
|
|
694
|
-
|
|
877
|
+
onSessionIdChange: (newSessionId: string) => {
|
|
878
|
+
const oldSessionId = ctx.registeredSessionId;
|
|
879
|
+
// Emit with the OLD sessionId so the client's router can deliver it
|
|
880
|
+
this.emit("sessionIdChange", { sessionId: newSessionId }, oldSessionId);
|
|
881
|
+
// Update the sessions Map key atomically (single-threaded, no await)
|
|
882
|
+
if (oldSessionId && oldSessionId !== newSessionId) {
|
|
883
|
+
const entry = this.sessions.get(oldSessionId);
|
|
884
|
+
if (entry) {
|
|
885
|
+
this.sessions.delete(oldSessionId);
|
|
886
|
+
this.sessions.set(newSessionId, entry);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
ctx.registeredSessionId = newSessionId;
|
|
695
890
|
},
|
|
696
891
|
onPermissionModeChange: (mode: PermissionMode) => {
|
|
697
|
-
this.emit("permissionModeChange", { mode });
|
|
892
|
+
this.emit("permissionModeChange", { mode }, ctx.registeredSessionId);
|
|
698
893
|
},
|
|
699
894
|
onMcpServersChange: (servers: McpServerStatus[]) => {
|
|
700
|
-
this.emit("mcpServersChange", { servers });
|
|
895
|
+
this.emit("mcpServersChange", { servers }, ctx.registeredSessionId);
|
|
701
896
|
},
|
|
702
897
|
onAddBangMessage: () => {
|
|
703
|
-
this.emit("bangMessageAdded", {});
|
|
898
|
+
this.emit("bangMessageAdded", {}, ctx.registeredSessionId);
|
|
704
899
|
},
|
|
705
900
|
onUpdateBangMessage: () => {
|
|
706
|
-
this.emit("bangMessageUpdated", {});
|
|
901
|
+
this.emit("bangMessageUpdated", {}, ctx.registeredSessionId);
|
|
707
902
|
},
|
|
708
903
|
onCompleteBangMessage: () => {
|
|
709
|
-
this.emit("bangMessageCompleted", {});
|
|
904
|
+
this.emit("bangMessageCompleted", {}, ctx.registeredSessionId);
|
|
710
905
|
},
|
|
711
906
|
onNotificationMessageAdded: (params) => {
|
|
712
|
-
const msg =
|
|
907
|
+
const msg = ctx.agent?.messages.find(
|
|
713
908
|
(m) =>
|
|
714
909
|
m.role === "user" &&
|
|
715
910
|
m.blocks.some(
|
|
@@ -718,26 +913,42 @@ export class AgentBridge {
|
|
|
718
913
|
(b as { taskId: string }).taskId === params.taskId,
|
|
719
914
|
),
|
|
720
915
|
);
|
|
721
|
-
this.emit(
|
|
722
|
-
|
|
723
|
-
message: msg,
|
|
724
|
-
|
|
916
|
+
this.emit(
|
|
917
|
+
"notificationMessageAdded",
|
|
918
|
+
{ ...params, message: msg },
|
|
919
|
+
ctx.registeredSessionId,
|
|
920
|
+
);
|
|
725
921
|
},
|
|
726
922
|
};
|
|
727
923
|
}
|
|
728
924
|
|
|
729
|
-
private findLastUserMessage(): Message | undefined {
|
|
730
|
-
const userMessages =
|
|
731
|
-
this.agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
925
|
+
private findLastUserMessage(agent?: Agent): Message | undefined {
|
|
926
|
+
const userMessages = agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
732
927
|
return userMessages[userMessages.length - 1];
|
|
733
928
|
}
|
|
734
929
|
|
|
735
930
|
// ── Utils ─────────────────────────────────────────────────────
|
|
736
931
|
|
|
737
|
-
private
|
|
738
|
-
if (!
|
|
739
|
-
throw new RpcError(
|
|
932
|
+
private requireSession(sessionId?: string): SessionEntry {
|
|
933
|
+
if (!sessionId) {
|
|
934
|
+
throw new RpcError(
|
|
935
|
+
PROTOCOL_INTERNAL_ERROR,
|
|
936
|
+
"sessionId is required for this request",
|
|
937
|
+
);
|
|
740
938
|
}
|
|
939
|
+
const entry = this.sessions.get(sessionId);
|
|
940
|
+
if (!entry) {
|
|
941
|
+
throw new RpcError(
|
|
942
|
+
PROTOCOL_INTERNAL_ERROR,
|
|
943
|
+
`Session not found: ${sessionId}`,
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
return entry;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private getSessionWorkdir(sessionId?: string): string | undefined {
|
|
950
|
+
if (!sessionId) return undefined;
|
|
951
|
+
return this.sessions.get(sessionId)?.agent.workingDirectory;
|
|
741
952
|
}
|
|
742
953
|
}
|
|
743
954
|
|