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
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,7 +666,7 @@ 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;
|
|
@@ -561,11 +698,11 @@ export class AgentBridge {
|
|
|
561
698
|
return null;
|
|
562
699
|
}
|
|
563
700
|
|
|
564
|
-
// ── Plugins
|
|
701
|
+
// ── Plugins (global) ─────────────────────────────────────────
|
|
565
702
|
|
|
566
|
-
private getPluginCore(workdir?: string): PluginCore {
|
|
703
|
+
private getPluginCore(workdir?: string, sessionId?: string): PluginCore {
|
|
567
704
|
const resolvedWorkdir =
|
|
568
|
-
workdir || this.
|
|
705
|
+
workdir || this.getSessionWorkdir(sessionId) || process.cwd();
|
|
569
706
|
if (!this.pluginCore || this.pluginCoreWorkdir !== resolvedWorkdir) {
|
|
570
707
|
this.pluginCore = new PluginCore(resolvedWorkdir);
|
|
571
708
|
this.pluginCoreWorkdir = resolvedWorkdir;
|
|
@@ -573,8 +710,8 @@ export class AgentBridge {
|
|
|
573
710
|
return this.pluginCore;
|
|
574
711
|
}
|
|
575
712
|
|
|
576
|
-
private async listPlugins(workdir?: string) {
|
|
577
|
-
const core = this.getPluginCore(workdir);
|
|
713
|
+
private async listPlugins(workdir?: string, sessionId?: string) {
|
|
714
|
+
const core = this.getPluginCore(workdir, sessionId);
|
|
578
715
|
const { plugins, mergedEnabled } = await core.listPlugins();
|
|
579
716
|
return {
|
|
580
717
|
plugins: plugins.map((p) => {
|
|
@@ -597,12 +734,20 @@ export class AgentBridge {
|
|
|
597
734
|
pluginId: string,
|
|
598
735
|
scope?: Scope,
|
|
599
736
|
workdir?: string,
|
|
737
|
+
sessionId?: string,
|
|
600
738
|
) {
|
|
601
|
-
return this.getPluginCore(workdir).installPlugin(
|
|
739
|
+
return this.getPluginCore(workdir, sessionId).installPlugin(
|
|
740
|
+
pluginId,
|
|
741
|
+
scope,
|
|
742
|
+
);
|
|
602
743
|
}
|
|
603
744
|
|
|
604
|
-
private async uninstallPlugin(
|
|
605
|
-
|
|
745
|
+
private async uninstallPlugin(
|
|
746
|
+
pluginId: string,
|
|
747
|
+
workdir?: string,
|
|
748
|
+
sessionId?: string,
|
|
749
|
+
) {
|
|
750
|
+
await this.getPluginCore(workdir, sessionId).uninstallPlugin(pluginId);
|
|
606
751
|
return null;
|
|
607
752
|
}
|
|
608
753
|
|
|
@@ -610,106 +755,154 @@ export class AgentBridge {
|
|
|
610
755
|
pluginId: string,
|
|
611
756
|
scope?: Scope,
|
|
612
757
|
workdir?: string,
|
|
758
|
+
sessionId?: string,
|
|
613
759
|
) {
|
|
614
|
-
return this.getPluginCore(workdir).enablePlugin(pluginId, scope);
|
|
760
|
+
return this.getPluginCore(workdir, sessionId).enablePlugin(pluginId, scope);
|
|
615
761
|
}
|
|
616
762
|
|
|
617
763
|
private async disablePlugin(
|
|
618
764
|
pluginId: string,
|
|
619
765
|
scope?: Scope,
|
|
620
766
|
workdir?: string,
|
|
767
|
+
sessionId?: string,
|
|
621
768
|
) {
|
|
622
|
-
return this.getPluginCore(workdir).disablePlugin(
|
|
769
|
+
return this.getPluginCore(workdir, sessionId).disablePlugin(
|
|
770
|
+
pluginId,
|
|
771
|
+
scope,
|
|
772
|
+
);
|
|
623
773
|
}
|
|
624
774
|
|
|
625
|
-
private async updatePlugin(
|
|
626
|
-
|
|
775
|
+
private async updatePlugin(
|
|
776
|
+
pluginId: string,
|
|
777
|
+
workdir?: string,
|
|
778
|
+
sessionId?: string,
|
|
779
|
+
) {
|
|
780
|
+
return this.getPluginCore(workdir, sessionId).updatePlugin(pluginId);
|
|
627
781
|
}
|
|
628
782
|
|
|
629
|
-
private async listMarketplaces(workdir?: string) {
|
|
630
|
-
return this.getPluginCore(workdir).listMarketplaces();
|
|
783
|
+
private async listMarketplaces(workdir?: string, sessionId?: string) {
|
|
784
|
+
return this.getPluginCore(workdir, sessionId).listMarketplaces();
|
|
631
785
|
}
|
|
632
786
|
|
|
633
|
-
private async addMarketplace(
|
|
634
|
-
|
|
787
|
+
private async addMarketplace(
|
|
788
|
+
input: string,
|
|
789
|
+
scope?: Scope,
|
|
790
|
+
workdir?: string,
|
|
791
|
+
sessionId?: string,
|
|
792
|
+
) {
|
|
793
|
+
return this.getPluginCore(workdir, sessionId).addMarketplace(input, scope);
|
|
635
794
|
}
|
|
636
795
|
|
|
637
796
|
private async removeMarketplace(
|
|
638
797
|
name: string,
|
|
639
798
|
scope?: Scope,
|
|
640
799
|
workdir?: string,
|
|
800
|
+
sessionId?: string,
|
|
641
801
|
) {
|
|
642
|
-
await this.getPluginCore(workdir).removeMarketplace(name, scope);
|
|
802
|
+
await this.getPluginCore(workdir, sessionId).removeMarketplace(name, scope);
|
|
643
803
|
return null;
|
|
644
804
|
}
|
|
645
805
|
|
|
646
|
-
private async updateMarketplace(
|
|
647
|
-
|
|
806
|
+
private async updateMarketplace(
|
|
807
|
+
name?: string,
|
|
808
|
+
workdir?: string,
|
|
809
|
+
sessionId?: string,
|
|
810
|
+
) {
|
|
811
|
+
await this.getPluginCore(workdir, sessionId).updateMarketplace(name);
|
|
648
812
|
return null;
|
|
649
813
|
}
|
|
650
814
|
|
|
651
815
|
// ── Callbacks → Notifications ─────────────────────────────────
|
|
652
816
|
|
|
653
|
-
private createCallbacks(): AgentCallbacks {
|
|
817
|
+
private createCallbacks(ctx: SessionContext): AgentCallbacks {
|
|
654
818
|
return {
|
|
655
819
|
onMessagesChange: (messages: Message[]) => {
|
|
656
|
-
this.emit("messagesChange", { messages });
|
|
820
|
+
this.emit("messagesChange", { messages }, ctx.registeredSessionId);
|
|
657
821
|
},
|
|
658
822
|
onUserMessageAdded: () => {
|
|
659
|
-
const msg = this.findLastUserMessage();
|
|
660
|
-
if (msg)
|
|
823
|
+
const msg = this.findLastUserMessage(ctx.agent);
|
|
824
|
+
if (msg)
|
|
825
|
+
this.emit(
|
|
826
|
+
"userMessageAdded",
|
|
827
|
+
{ message: msg },
|
|
828
|
+
ctx.registeredSessionId,
|
|
829
|
+
);
|
|
661
830
|
},
|
|
662
831
|
onAssistantMessageAdded: (messageId: string) => {
|
|
663
|
-
const msg =
|
|
664
|
-
if (msg)
|
|
832
|
+
const msg = ctx.agent?.messages.find((m) => m.id === messageId);
|
|
833
|
+
if (msg)
|
|
834
|
+
this.emit(
|
|
835
|
+
"assistantMessageAdded",
|
|
836
|
+
{ message: msg },
|
|
837
|
+
ctx.registeredSessionId,
|
|
838
|
+
);
|
|
665
839
|
},
|
|
666
840
|
onAssistantContentUpdated: (params) => {
|
|
667
|
-
this.emit("assistantContentUpdated", params);
|
|
841
|
+
this.emit("assistantContentUpdated", params, ctx.registeredSessionId);
|
|
668
842
|
},
|
|
669
843
|
onAssistantReasoningUpdated: (params) => {
|
|
670
|
-
this.emit("assistantReasoningUpdated", params);
|
|
844
|
+
this.emit("assistantReasoningUpdated", params, ctx.registeredSessionId);
|
|
671
845
|
},
|
|
672
846
|
onToolBlockUpdated: (params) => {
|
|
673
|
-
this.emit("toolBlockUpdated", params);
|
|
847
|
+
this.emit("toolBlockUpdated", params, ctx.registeredSessionId);
|
|
674
848
|
},
|
|
675
849
|
onErrorBlockAdded: (error: string) => {
|
|
676
|
-
this.emit("errorBlockAdded", { error });
|
|
850
|
+
this.emit("errorBlockAdded", { error }, ctx.registeredSessionId);
|
|
677
851
|
},
|
|
678
852
|
onLoadingChange: (loading: boolean) => {
|
|
679
|
-
this.emit(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
853
|
+
this.emit(
|
|
854
|
+
"loadingChange",
|
|
855
|
+
{
|
|
856
|
+
loading,
|
|
857
|
+
latestTotalTokens: ctx.agent?.latestTotalTokens,
|
|
858
|
+
},
|
|
859
|
+
ctx.registeredSessionId,
|
|
860
|
+
);
|
|
683
861
|
},
|
|
684
862
|
onCommandRunningChange: (running: boolean) => {
|
|
685
|
-
this.emit("commandRunningChange", { running });
|
|
863
|
+
this.emit("commandRunningChange", { running }, ctx.registeredSessionId);
|
|
686
864
|
},
|
|
687
865
|
onQueuedMessagesChange: (messages: QueuedMessage[]) => {
|
|
688
|
-
this.emit(
|
|
866
|
+
this.emit(
|
|
867
|
+
"queuedMessagesChange",
|
|
868
|
+
{ messages },
|
|
869
|
+
ctx.registeredSessionId,
|
|
870
|
+
);
|
|
689
871
|
},
|
|
690
872
|
onTasksChange: (tasks: Task[]) => {
|
|
691
|
-
this.emit("tasksChange", { tasks });
|
|
873
|
+
this.emit("tasksChange", { tasks }, ctx.registeredSessionId);
|
|
692
874
|
},
|
|
693
|
-
onSessionIdChange: (
|
|
694
|
-
|
|
875
|
+
onSessionIdChange: (newSessionId: string) => {
|
|
876
|
+
const oldSessionId = ctx.registeredSessionId;
|
|
877
|
+
// Emit with the OLD sessionId so the client's router can deliver it
|
|
878
|
+
this.emit("sessionIdChange", { sessionId: newSessionId }, oldSessionId);
|
|
879
|
+
// Update the sessions Map key atomically (single-threaded, no await)
|
|
880
|
+
if (oldSessionId && oldSessionId !== newSessionId) {
|
|
881
|
+
const entry = this.sessions.get(oldSessionId);
|
|
882
|
+
if (entry) {
|
|
883
|
+
this.sessions.delete(oldSessionId);
|
|
884
|
+
this.sessions.set(newSessionId, entry);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
ctx.registeredSessionId = newSessionId;
|
|
695
888
|
},
|
|
696
889
|
onPermissionModeChange: (mode: PermissionMode) => {
|
|
697
|
-
this.emit("permissionModeChange", { mode });
|
|
890
|
+
this.emit("permissionModeChange", { mode }, ctx.registeredSessionId);
|
|
698
891
|
},
|
|
699
892
|
onMcpServersChange: (servers: McpServerStatus[]) => {
|
|
700
|
-
this.emit("mcpServersChange", { servers });
|
|
893
|
+
this.emit("mcpServersChange", { servers }, ctx.registeredSessionId);
|
|
701
894
|
},
|
|
702
895
|
onAddBangMessage: () => {
|
|
703
|
-
this.emit("bangMessageAdded", {});
|
|
896
|
+
this.emit("bangMessageAdded", {}, ctx.registeredSessionId);
|
|
704
897
|
},
|
|
705
898
|
onUpdateBangMessage: () => {
|
|
706
|
-
this.emit("bangMessageUpdated", {});
|
|
899
|
+
this.emit("bangMessageUpdated", {}, ctx.registeredSessionId);
|
|
707
900
|
},
|
|
708
901
|
onCompleteBangMessage: () => {
|
|
709
|
-
this.emit("bangMessageCompleted", {});
|
|
902
|
+
this.emit("bangMessageCompleted", {}, ctx.registeredSessionId);
|
|
710
903
|
},
|
|
711
904
|
onNotificationMessageAdded: (params) => {
|
|
712
|
-
const msg =
|
|
905
|
+
const msg = ctx.agent?.messages.find(
|
|
713
906
|
(m) =>
|
|
714
907
|
m.role === "user" &&
|
|
715
908
|
m.blocks.some(
|
|
@@ -718,26 +911,42 @@ export class AgentBridge {
|
|
|
718
911
|
(b as { taskId: string }).taskId === params.taskId,
|
|
719
912
|
),
|
|
720
913
|
);
|
|
721
|
-
this.emit(
|
|
722
|
-
|
|
723
|
-
message: msg,
|
|
724
|
-
|
|
914
|
+
this.emit(
|
|
915
|
+
"notificationMessageAdded",
|
|
916
|
+
{ ...params, message: msg },
|
|
917
|
+
ctx.registeredSessionId,
|
|
918
|
+
);
|
|
725
919
|
},
|
|
726
920
|
};
|
|
727
921
|
}
|
|
728
922
|
|
|
729
|
-
private findLastUserMessage(): Message | undefined {
|
|
730
|
-
const userMessages =
|
|
731
|
-
this.agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
923
|
+
private findLastUserMessage(agent?: Agent): Message | undefined {
|
|
924
|
+
const userMessages = agent?.messages.filter((m) => m.role === "user") ?? [];
|
|
732
925
|
return userMessages[userMessages.length - 1];
|
|
733
926
|
}
|
|
734
927
|
|
|
735
928
|
// ── Utils ─────────────────────────────────────────────────────
|
|
736
929
|
|
|
737
|
-
private
|
|
738
|
-
if (!
|
|
739
|
-
throw new RpcError(
|
|
930
|
+
private requireSession(sessionId?: string): SessionEntry {
|
|
931
|
+
if (!sessionId) {
|
|
932
|
+
throw new RpcError(
|
|
933
|
+
PROTOCOL_INTERNAL_ERROR,
|
|
934
|
+
"sessionId is required for this request",
|
|
935
|
+
);
|
|
740
936
|
}
|
|
937
|
+
const entry = this.sessions.get(sessionId);
|
|
938
|
+
if (!entry) {
|
|
939
|
+
throw new RpcError(
|
|
940
|
+
PROTOCOL_INTERNAL_ERROR,
|
|
941
|
+
`Session not found: ${sessionId}`,
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return entry;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private getSessionWorkdir(sessionId?: string): string | undefined {
|
|
948
|
+
if (!sessionId) return undefined;
|
|
949
|
+
return this.sessions.get(sessionId)?.agent.workingDirectory;
|
|
741
950
|
}
|
|
742
951
|
}
|
|
743
952
|
|