@adhdev/daemon-core 0.6.79 → 0.7.1

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.
@@ -11,6 +11,7 @@
11
11
  import { DaemonCdpManager, AgentWebviewTarget } from '../cdp/manager.js';
12
12
  import { ProviderLoader } from '../providers/provider-loader.js';
13
13
  import { ProviderStreamAdapter } from './provider-adapter.js';
14
+ import { SessionRegistry } from '../sessions/registry.js';
14
15
  import { LOG } from '../logging/logger.js';
15
16
  import type {
16
17
  IAgentStreamAdapter,
@@ -21,7 +22,9 @@ import type {
21
22
 
22
23
  export interface ManagedAgent {
23
24
  adapter: IAgentStreamAdapter;
24
- sessionId: string;
25
+ runtimeSessionId: string;
26
+ parentSessionId: string;
27
+ cdpSessionId: string;
25
28
  target: AgentWebviewTarget;
26
29
  lastState: AgentStreamState | null;
27
30
  lastError: string | null;
@@ -29,15 +32,19 @@ export interface ManagedAgent {
29
32
  }
30
33
 
31
34
  export class DaemonAgentStreamManager {
32
- private allAdapters: IAgentStreamAdapter[] = [];
33
- private managedByScope = new Map<string, Map<string, ManagedAgent>>();
35
+ private adaptersByType = new Map<string, IAgentStreamAdapter>();
36
+ private managedBySessionId = new Map<string, ManagedAgent>();
34
37
  private enabled = true;
35
38
  private logFn: (msg: string) => void;
36
- private lastDiscoveryTimeByScope = new Map<string, number>();
37
- private discoveryIntervalMsByScope = new Map<string, number>();
38
- private activeAgentTypeByScope = new Map<string, string | null>();
39
-
40
- constructor(logFn?: (msg: string) => void, providerLoader?: ProviderLoader) {
39
+ private lastDiscoveryTimeByParent = new Map<string, number>();
40
+ private discoveryIntervalMsByParent = new Map<string, number>();
41
+ private activeSessionIdByParent = new Map<string, string | null>();
42
+
43
+ constructor(
44
+ logFn?: (msg: string) => void,
45
+ providerLoader?: ProviderLoader,
46
+ private readonly sessionRegistry?: SessionRegistry,
47
+ ) {
41
48
  this.logFn = logFn || LOG.forComponent('AgentStream').asLogFn();
42
49
 
43
50
  // Create adapter for all extension providers
@@ -48,7 +55,7 @@ export class DaemonAgentStreamManager {
48
55
  const resolved = providerLoader.resolve(p.type);
49
56
  if (!resolved) continue;
50
57
  const adapter = new ProviderStreamAdapter(resolved);
51
- this.allAdapters.push(adapter);
58
+ this.adaptersByType.set(p.type, adapter);
52
59
  this.logFn(`[AgentStream] Adapter created: ${p.type} (${p.name}) scripts=${Object.keys(resolved.scripts || {}).join(',') || 'none'}`);
53
60
  }
54
61
  }
@@ -56,274 +63,290 @@ export class DaemonAgentStreamManager {
56
63
 
57
64
  setEnabled(enabled: boolean) { this.enabled = enabled; }
58
65
  get isEnabled() { return this.enabled; }
59
- getActiveAgentType(scopeKey: string): string | null {
60
- return this.activeAgentTypeByScope.get(scopeKey) || null;
66
+ getActiveSessionId(parentSessionId: string): string | null {
67
+ return this.activeSessionIdByParent.get(parentSessionId) || null;
61
68
  }
62
69
 
63
- private getManagedScope(scopeKey: string): Map<string, ManagedAgent> {
64
- let managed = this.managedByScope.get(scopeKey);
65
- if (!managed) {
66
- managed = new Map<string, ManagedAgent>();
67
- this.managedByScope.set(scopeKey, managed);
68
- }
69
- return managed;
70
+ private getSessionTarget(sessionId: string) {
71
+ return this.sessionRegistry?.get(sessionId);
70
72
  }
71
73
 
72
- resetScope(scopeKey: string): void {
73
- this.managedByScope.delete(scopeKey);
74
- this.activeAgentTypeByScope.delete(scopeKey);
75
- this.lastDiscoveryTimeByScope.delete(scopeKey);
76
- this.discoveryIntervalMsByScope.delete(scopeKey);
74
+ resetParentSession(parentSessionId: string): void {
75
+ const activeSessionId = this.activeSessionIdByParent.get(parentSessionId);
76
+ if (activeSessionId) this.managedBySessionId.delete(activeSessionId);
77
+ for (const child of this.sessionRegistry?.listChildren(parentSessionId) || []) {
78
+ this.managedBySessionId.delete(child.sessionId);
79
+ }
80
+ this.activeSessionIdByParent.delete(parentSessionId);
81
+ this.lastDiscoveryTimeByParent.delete(parentSessionId);
82
+ this.discoveryIntervalMsByParent.delete(parentSessionId);
77
83
  }
78
84
 
79
85
  /** Panel focus based on provider.js focusPanel or extensionId (currently no-op) */
80
- async ensureAgentPanelOpen(agentType: string, targetIdeType?: string): Promise<void> {
86
+ async ensureSessionPanelOpen(_sessionId: string): Promise<void> {
81
87
  // Extension was removed, so localServer-based panel focus no longer works
82
88
  // Can be replaced with CDP-based focus (future implementation)
83
89
  }
84
90
 
85
- async switchActiveAgent(cdp: DaemonCdpManager, scopeKey: string, agentType: string | null): Promise<void> {
86
- const managed = this.getManagedScope(scopeKey);
87
- const previousAgentType = this.getActiveAgentType(scopeKey);
88
- if (previousAgentType === agentType) return;
91
+ async setActiveSession(cdp: DaemonCdpManager, parentSessionId: string, sessionId: string | null): Promise<void> {
92
+ const previousSessionId = this.getActiveSessionId(parentSessionId);
93
+ if (previousSessionId === sessionId) return;
89
94
 
90
- if (previousAgentType) {
91
- const prev = managed.get(previousAgentType);
95
+ if (previousSessionId) {
96
+ const prev = this.managedBySessionId.get(previousSessionId);
92
97
  if (prev) {
93
- try { await cdp.detachAgent(prev.sessionId); } catch { }
94
- managed.delete(previousAgentType);
95
- this.logFn(`[AgentStream] Deactivated: ${prev.adapter.agentName} (${scopeKey})`);
98
+ try { await cdp.detachAgent(prev.cdpSessionId); } catch { }
99
+ this.managedBySessionId.delete(previousSessionId);
100
+ this.logFn(`[AgentStream] Deactivated: ${prev.adapter.agentName} (${parentSessionId})`);
96
101
  }
97
102
  }
98
103
 
99
- this.activeAgentTypeByScope.set(scopeKey, agentType);
100
- this.lastDiscoveryTimeByScope.set(scopeKey, 0);
101
- if (!agentType && managed.size === 0) {
102
- this.managedByScope.delete(scopeKey);
103
- }
104
- this.logFn(`[AgentStream] Active agent (${scopeKey}): ${agentType || 'none'}`);
104
+ this.activeSessionIdByParent.set(parentSessionId, sessionId);
105
+ this.lastDiscoveryTimeByParent.set(parentSessionId, 0);
106
+ this.logFn(`[AgentStream] Active session (${parentSessionId}): ${sessionId || 'none'}`);
107
+ }
108
+
109
+ private resolveSessionIdForTarget(parentSessionId: string, agentType: string): string | null {
110
+ const child = (this.sessionRegistry?.listChildren(parentSessionId) || [])
111
+ .find((entry) => entry.providerCategory === 'extension' && entry.providerType === agentType);
112
+ return child?.sessionId || null;
113
+ }
114
+
115
+ private async connectManagedSession(
116
+ cdp: DaemonCdpManager,
117
+ parentSessionId: string,
118
+ runtimeSessionId: string,
119
+ ): Promise<ManagedAgent | null> {
120
+ const target = this.getSessionTarget(runtimeSessionId);
121
+ if (!target || target.providerCategory !== 'extension') return null;
122
+ const adapter = this.adaptersByType.get(target.providerType);
123
+ if (!adapter) return null;
124
+ const targets = await cdp.discoverAgentWebviews();
125
+ const activeTarget = targets.find((entry) => entry.agentType === target.providerType);
126
+ if (!activeTarget) return null;
127
+ const cdpSessionId = await cdp.attachToAgent(activeTarget);
128
+ if (!cdpSessionId) return null;
129
+ const managed: ManagedAgent = {
130
+ adapter,
131
+ runtimeSessionId,
132
+ parentSessionId,
133
+ cdpSessionId,
134
+ target: activeTarget,
135
+ lastState: null,
136
+ lastError: null,
137
+ lastHiddenCheckTime: 0,
138
+ };
139
+ this.managedBySessionId.set(runtimeSessionId, managed);
140
+ this.logFn(`[AgentStream] Connected: ${adapter.agentName} (${parentSessionId})`);
141
+ return managed;
105
142
  }
106
143
 
107
- /** Agent webview discovery + session connection */
108
- async syncAgentSessions(cdp: DaemonCdpManager, scopeKey: string): Promise<void> {
109
- const activeAgentType = this.getActiveAgentType(scopeKey);
110
- if (!this.enabled || !activeAgentType) return;
144
+ /** Agent webview discovery + session connection */
145
+ async syncActiveSession(cdp: DaemonCdpManager, parentSessionId: string): Promise<void> {
146
+ const activeSessionId = this.getActiveSessionId(parentSessionId);
147
+ if (!this.enabled || !activeSessionId) return;
111
148
 
112
149
  const now = Date.now();
113
- const managed = this.getManagedScope(scopeKey);
114
- const lastDiscoveryTime = this.lastDiscoveryTimeByScope.get(scopeKey) || 0;
115
- const discoveryIntervalMs = this.discoveryIntervalMsByScope.get(scopeKey) || 10_000;
116
- if (managed.has(activeAgentType) && (now - lastDiscoveryTime) < discoveryIntervalMs) {
150
+ const managed = this.managedBySessionId.get(activeSessionId);
151
+ const lastDiscoveryTime = this.lastDiscoveryTimeByParent.get(parentSessionId) || 0;
152
+ const discoveryIntervalMs = this.discoveryIntervalMsByParent.get(parentSessionId) || 10_000;
153
+ if (managed && (now - lastDiscoveryTime) < discoveryIntervalMs) {
117
154
  return;
118
155
  }
119
- this.lastDiscoveryTimeByScope.set(scopeKey, now);
156
+ this.lastDiscoveryTimeByParent.set(parentSessionId, now);
120
157
 
121
158
  try {
122
- const targets = await cdp.discoverAgentWebviews();
123
- const activeTarget = targets.find(t => t.agentType === activeAgentType);
124
-
125
- if (activeTarget && !managed.has(activeAgentType)) {
126
- const adapter = this.allAdapters.find(a => a.agentType === activeAgentType);
127
- if (adapter) {
128
- const sessionId = await cdp.attachToAgent(activeTarget);
129
- if (sessionId) {
130
- managed.set(activeAgentType, {
131
- adapter,
132
- sessionId,
133
- target: activeTarget,
134
- lastState: null,
135
- lastError: null,
136
- lastHiddenCheckTime: 0,
137
- });
138
- this.logFn(`[AgentStream] Connected: ${adapter.agentName} (${scopeKey})`);
139
- }
140
- }
141
- }
142
-
143
- // Cleanup inactive agents
144
- for (const [type, agent] of managed) {
145
- if (type !== activeAgentType) {
146
- await cdp.detachAgent(agent.sessionId);
147
- managed.delete(type);
148
- }
159
+ if (!managed) {
160
+ await this.connectManagedSession(cdp, parentSessionId, activeSessionId);
149
161
  }
150
-
151
- this.discoveryIntervalMsByScope.set(scopeKey, managed.has(activeAgentType) ? 30_000 : 10_000);
162
+ this.discoveryIntervalMsByParent.set(parentSessionId, this.managedBySessionId.has(activeSessionId) ? 30_000 : 10_000);
152
163
  } catch (e) {
153
- this.logFn(`[AgentStream] sync error (${scopeKey}): ${(e as Error).message}`);
164
+ this.logFn(`[AgentStream] sync error (${parentSessionId}): ${(e as Error).message}`);
154
165
  }
155
166
  }
156
167
 
157
- /** Collect active agent status */
158
- async collectAgentStreams(cdp: DaemonCdpManager, scopeKey: string): Promise<AgentStreamState[]> {
159
- if (!this.enabled) return [];
160
-
161
- const results: AgentStreamState[] = [];
162
- const activeAgentType = this.getActiveAgentType(scopeKey);
163
- const managed = this.managedByScope.get(scopeKey);
164
-
165
- if (activeAgentType && managed?.has(activeAgentType)) {
166
- const agent = managed.get(activeAgentType)!;
167
- const type = activeAgentType;
168
+ /** Collect active extension session state */
169
+ async collectActiveSession(cdp: DaemonCdpManager, parentSessionId: string): Promise<AgentStreamState | null> {
170
+ if (!this.enabled) return null;
171
+ const activeSessionId = this.getActiveSessionId(parentSessionId);
172
+ if (!activeSessionId) return null;
173
+ let agent = this.managedBySessionId.get(activeSessionId);
174
+ if (!agent) {
175
+ agent = await this.connectManagedSession(cdp, parentSessionId, activeSessionId) || undefined;
176
+ }
177
+ if (!agent) return null;
178
+ const type = agent.adapter.agentType;
179
+ const isHidden = agent.lastState?.status === 'panel_hidden';
180
+ const hiddenCacheFresh = isHidden && (Date.now() - agent.lastHiddenCheckTime < 30000);
168
181
 
169
- const isHidden = agent.lastState?.status === 'panel_hidden';
170
- const hiddenCacheFresh = isHidden && (Date.now() - agent.lastHiddenCheckTime < 30000);
182
+ if (hiddenCacheFresh) return agent.lastState!;
171
183
 
172
- if (hiddenCacheFresh) {
173
- results.push(agent.lastState!);
174
- } else {
175
- try {
176
- const evaluate: AgentEvaluateFn = (expr, timeout) =>
177
- cdp.evaluateInSessionFrame(agent.sessionId, expr, timeout);
178
- const state = await agent.adapter.readChat(evaluate);
179
- LOG.debug('AgentStream', `[AgentStream] readChat(${type}) result: status=${state.status} msgs=${state.messages?.length || 0} model=${state.model || ''}${state.status === 'error' ? ' error=' + JSON.stringify((state as any).error || (state as any)._error || 'unknown') : ''}`);
180
- agent.lastState = state;
181
- agent.lastError = null;
182
- if (state.status === 'panel_hidden') {
183
- agent.lastHiddenCheckTime = Date.now();
184
- }
185
- results.push(state);
186
- } catch (e) {
187
- const errorMsg = (e as Error)?.message || String(e);
188
- this.logFn(`[AgentStream] readChat(${type}) error: ${errorMsg.slice(0, 200)}`);
189
- agent.lastError = errorMsg;
190
- results.push({
191
- agentType: type,
192
- agentName: agent.adapter.agentName,
193
- extensionId: agent.adapter.extensionId,
194
- status: 'disconnected',
195
- messages: agent.lastState?.messages || [],
196
- inputContent: '',
197
- });
198
- if (errorMsg.includes('timeout') || errorMsg.includes('not connected') || errorMsg.includes('Session')) {
199
- try { await cdp.detachAgent(agent.sessionId); } catch { }
200
- managed.delete(type);
201
- this.lastDiscoveryTimeByScope.set(scopeKey, 0);
202
- }
203
- }
184
+ try {
185
+ const evaluate: AgentEvaluateFn = (expr, timeout) =>
186
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
187
+ const state = await agent.adapter.readChat(evaluate);
188
+ LOG.debug('AgentStream', `[AgentStream] readChat(${type}) result: status=${state.status} msgs=${state.messages?.length || 0} model=${state.model || ''}${state.status === 'error' ? ' error=' + JSON.stringify((state as any).error || (state as any)._error || 'unknown') : ''}`);
189
+ agent.lastState = state;
190
+ agent.lastError = null;
191
+ if (state.status === 'panel_hidden') {
192
+ agent.lastHiddenCheckTime = Date.now();
204
193
  }
194
+ return state;
195
+ } catch (e) {
196
+ const errorMsg = (e as Error)?.message || String(e);
197
+ this.logFn(`[AgentStream] readChat(${type}) error: ${errorMsg.slice(0, 200)}`);
198
+ agent.lastError = errorMsg;
199
+ if (errorMsg.includes('timeout') || errorMsg.includes('not connected') || errorMsg.includes('Session')) {
200
+ try { await cdp.detachAgent(agent.cdpSessionId); } catch { }
201
+ this.managedBySessionId.delete(activeSessionId);
202
+ this.lastDiscoveryTimeByParent.set(parentSessionId, 0);
203
+ }
204
+ return {
205
+ agentType: type,
206
+ agentName: agent.adapter.agentName,
207
+ extensionId: agent.adapter.extensionId,
208
+ status: 'disconnected',
209
+ messages: agent.lastState?.messages || [],
210
+ inputContent: '',
211
+ };
205
212
  }
206
-
207
- return results;
208
213
  }
209
214
 
210
- async sendToAgent(cdp: DaemonCdpManager, scopeKey: string, agentType: string, text: string, targetIdeType?: string): Promise<boolean> {
211
- await this.ensureAgentPanelOpen(agentType, targetIdeType);
212
- const agent = this.getManagedAgent(agentType, scopeKey);
215
+ async sendToSession(cdp: DaemonCdpManager, sessionId: string, text: string): Promise<boolean> {
216
+ await this.ensureSessionPanelOpen(sessionId);
217
+ const target = this.getSessionTarget(sessionId);
218
+ if (!target?.parentSessionId) return false;
219
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
220
+ await this.syncActiveSession(cdp, target.parentSessionId);
221
+ const agent = this.managedBySessionId.get(sessionId);
213
222
  if (!agent) return false;
214
223
  try {
215
224
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
216
- cdp.evaluateInSessionFrame(agent.sessionId, expr, timeout);
225
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
217
226
  await agent.adapter.sendMessage(evaluate, text);
218
227
  return true;
219
228
  } catch (e) {
220
- this.logFn(`[AgentStream] sendToAgent(${agentType}) error: ${(e as Error).message}`);
229
+ this.logFn(`[AgentStream] sendToSession(${sessionId}) error: ${(e as Error).message}`);
221
230
  return false;
222
231
  }
223
232
  }
224
233
 
225
- async resolveAgentAction(cdp: DaemonCdpManager, scopeKey: string, agentType: string, action: 'approve' | 'reject', targetIdeType?: string): Promise<boolean> {
226
- await this.ensureAgentPanelOpen(agentType, targetIdeType);
227
- const agent = this.getManagedAgent(agentType, scopeKey);
234
+ async resolveSessionAction(cdp: DaemonCdpManager, sessionId: string, action: 'approve' | 'reject'): Promise<boolean> {
235
+ await this.ensureSessionPanelOpen(sessionId);
236
+ const target = this.getSessionTarget(sessionId);
237
+ if (!target?.parentSessionId) return false;
238
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
239
+ await this.syncActiveSession(cdp, target.parentSessionId);
240
+ const agent = this.managedBySessionId.get(sessionId);
228
241
  if (!agent) return false;
229
242
  try {
230
243
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
231
- cdp.evaluateInSessionFrame(agent.sessionId, expr, timeout);
244
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
232
245
  return await agent.adapter.resolveAction(evaluate, action);
233
246
  } catch (e) {
234
- this.logFn(`[AgentStream] resolveAction(${agentType}) error: ${(e as Error).message}`);
247
+ this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${(e as Error).message}`);
235
248
  return false;
236
249
  }
237
250
  }
238
251
 
239
- async newAgentSession(cdp: DaemonCdpManager, scopeKey: string, agentType: string, targetIdeType?: string): Promise<boolean> {
240
- await this.ensureAgentPanelOpen(agentType, targetIdeType);
241
- const agent = this.getManagedAgent(agentType, scopeKey);
252
+ async newSession(cdp: DaemonCdpManager, sessionId: string): Promise<boolean> {
253
+ await this.ensureSessionPanelOpen(sessionId);
254
+ const target = this.getSessionTarget(sessionId);
255
+ if (!target?.parentSessionId) return false;
256
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
257
+ await this.syncActiveSession(cdp, target.parentSessionId);
258
+ const agent = this.managedBySessionId.get(sessionId);
242
259
  if (!agent) return false;
243
260
  try {
244
261
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
245
- cdp.evaluateInSessionFrame(agent.sessionId, expr, timeout);
262
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
246
263
  await agent.adapter.newSession(evaluate);
247
264
  return true;
248
265
  } catch (e) {
249
- this.logFn(`[AgentStream] newSession(${agentType}) error: ${(e as Error).message}`);
266
+ this.logFn(`[AgentStream] newSession(${sessionId}) error: ${(e as Error).message}`);
250
267
  return false;
251
268
  }
252
269
  }
253
270
 
254
- async listAgentChats(cdp: DaemonCdpManager, scopeKey: string, agentType: string): Promise<AgentChatListItem[]> {
255
- let agent = this.getManagedAgent(agentType, scopeKey);
256
- // on-demand: try activate+sync if not in managed list
257
- if (!agent) {
258
- this.logFn(`[AgentStream] listChats: ${agentType} not managed in ${scopeKey}, trying on-demand activation`);
259
- await this.switchActiveAgent(cdp, scopeKey, agentType);
260
- await this.syncAgentSessions(cdp, scopeKey);
261
- agent = this.getManagedAgent(agentType, scopeKey);
262
- }
271
+ async listSessionChats(cdp: DaemonCdpManager, sessionId: string): Promise<AgentChatListItem[]> {
272
+ const target = this.getSessionTarget(sessionId);
273
+ if (!target?.parentSessionId) return [];
274
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
275
+ await this.syncActiveSession(cdp, target.parentSessionId);
276
+ const agent = this.managedBySessionId.get(sessionId);
263
277
  if (!agent || typeof agent.adapter.listChats !== 'function') return [];
264
278
  try {
265
279
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
266
- cdp.evaluateInSessionFrame(agent!.sessionId, expr, timeout);
280
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
267
281
  return await agent.adapter.listChats(evaluate);
268
282
  } catch (e) {
269
- this.logFn(`[AgentStream] listChats(${agentType}) error: ${(e as Error).message}`);
283
+ this.logFn(`[AgentStream] listChats(${sessionId}) error: ${(e as Error).message}`);
270
284
  return [];
271
285
  }
272
286
  }
273
287
 
274
- async switchAgentSession(cdp: DaemonCdpManager, scopeKey: string, agentType: string, sessionId: string): Promise<boolean> {
275
- let agent = this.getManagedAgent(agentType, scopeKey);
276
- if (!agent) {
277
- this.logFn(`[AgentStream] switchSession: ${agentType} not managed in ${scopeKey}, trying on-demand activation`);
278
- await this.switchActiveAgent(cdp, scopeKey, agentType);
279
- await this.syncAgentSessions(cdp, scopeKey);
280
- agent = this.getManagedAgent(agentType, scopeKey);
281
- }
288
+ async switchConversation(cdp: DaemonCdpManager, sessionId: string, conversationId: string): Promise<boolean> {
289
+ const target = this.getSessionTarget(sessionId);
290
+ if (!target?.parentSessionId) return false;
291
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
292
+ await this.syncActiveSession(cdp, target.parentSessionId);
293
+ const agent = this.managedBySessionId.get(sessionId);
282
294
  if (!agent || typeof agent.adapter.switchSession !== 'function') return false;
283
295
  try {
284
296
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
285
- cdp.evaluateInSessionFrame(agent!.sessionId, expr, timeout);
286
- return await agent.adapter.switchSession(evaluate, sessionId);
297
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
298
+ return await agent.adapter.switchSession(evaluate, conversationId);
287
299
  } catch (e) {
288
- this.logFn(`[AgentStream] switchSession(${agentType}) error: ${(e as Error).message}`);
300
+ this.logFn(`[AgentStream] switchSession(${sessionId}) error: ${(e as Error).message}`);
289
301
  return false;
290
302
  }
291
303
  }
292
304
 
293
- async focusAgentEditor(cdp: DaemonCdpManager, scopeKey: string, agentType: string): Promise<boolean> {
294
- const agent = this.getManagedAgent(agentType, scopeKey);
305
+ async focusSession(cdp: DaemonCdpManager, sessionId: string): Promise<boolean> {
306
+ const target = this.getSessionTarget(sessionId);
307
+ if (!target?.parentSessionId) return false;
308
+ await this.setActiveSession(cdp, target.parentSessionId, sessionId);
309
+ await this.syncActiveSession(cdp, target.parentSessionId);
310
+ const agent = this.managedBySessionId.get(sessionId);
295
311
  if (!agent || typeof agent.adapter.focusEditor !== 'function') return false;
296
312
  try {
297
313
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
298
- cdp.evaluateInSessionFrame(agent.sessionId, expr, timeout);
314
+ cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
299
315
  await agent.adapter.focusEditor(evaluate);
300
316
  return true;
301
317
  } catch (e) {
302
- this.logFn(`[AgentStream] focusEditor(${agentType}) error: ${(e as Error).message}`);
318
+ this.logFn(`[AgentStream] focusEditor(${sessionId}) error: ${(e as Error).message}`);
303
319
  return false;
304
320
  }
305
321
  }
306
322
 
307
- getConnectedAgents(scopeKey?: string): string[] {
308
- if (scopeKey) return Array.from((this.managedByScope.get(scopeKey) || new Map()).keys());
309
- return Array.from(this.managedByScope.values()).flatMap(scope => Array.from(scope.keys()));
323
+ getConnectedSessions(parentSessionId?: string): string[] {
324
+ if (parentSessionId) {
325
+ return [...this.managedBySessionId.values()]
326
+ .filter((entry) => entry.parentSessionId === parentSessionId)
327
+ .map((entry) => entry.runtimeSessionId);
328
+ }
329
+ return [...this.managedBySessionId.keys()];
310
330
  }
311
331
 
312
- getManagedAgent(agentType: string, scopeKey: string): ManagedAgent | undefined {
313
- return this.managedByScope.get(scopeKey)?.get(agentType);
332
+ getManagedSession(sessionId: string): ManagedAgent | undefined {
333
+ return this.managedBySessionId.get(sessionId);
314
334
  }
315
335
 
316
336
  async dispose(cdpManagers: Map<string, DaemonCdpManager>): Promise<void> {
317
- for (const [scopeKey, managed] of this.managedByScope) {
318
- const cdp = cdpManagers.get(scopeKey);
337
+ for (const managed of this.managedBySessionId.values()) {
338
+ const managerKey = this.getSessionTarget(managed.runtimeSessionId)?.cdpManagerKey;
339
+ const cdp = managerKey ? cdpManagers.get(managerKey) : null;
319
340
  if (!cdp) continue;
320
- for (const [, agent] of managed) {
321
- try { await cdp.detachAgent(agent.sessionId); } catch { }
322
- }
341
+ try { await cdp.detachAgent(managed.cdpSessionId); } catch { }
323
342
  }
324
- this.managedByScope.clear();
325
- this.activeAgentTypeByScope.clear();
326
- this.lastDiscoveryTimeByScope.clear();
327
- this.discoveryIntervalMsByScope.clear();
343
+ this.managedBySessionId.clear();
344
+ this.activeSessionIdByParent.clear();
345
+ this.lastDiscoveryTimeByParent.clear();
346
+ this.discoveryIntervalMsByParent.clear();
347
+ }
348
+
349
+ resolveSessionForAgent(parentSessionId: string, agentType: string): string | null {
350
+ return this.resolveSessionIdForTarget(parentSessionId, agentType);
328
351
  }
329
352
  }