@adhdev/daemon-core 0.6.77 → 0.7.0

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.
@@ -15,6 +15,7 @@ import type { DaemonAgentStreamManager } from './manager.js';
15
15
  import type { ProviderLoader } from '../providers/provider-loader.js';
16
16
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
17
17
  import { registerExtensionProviders } from '../cdp/setup.js';
18
+ import type { SessionRegistry } from '../sessions/registry.js';
18
19
  import { LOG } from '../logging/logger.js';
19
20
  import type { AgentStreamState } from './types.js';
20
21
 
@@ -25,13 +26,13 @@ export interface AgentStreamPollerDeps {
25
26
  providerLoader: ProviderLoader;
26
27
  instanceManager: ProviderInstanceManager;
27
28
  cdpManagers: Map<string, DaemonCdpManager>;
29
+ sessionRegistry: SessionRegistry;
28
30
  /** Callback when agent streams are updated */
29
31
  onStreamsUpdated?: (ideType: string, streams: AgentStreamState[]) => void;
30
32
  }
31
33
 
32
34
  export class AgentStreamPoller {
33
35
  private deps: AgentStreamPollerDeps;
34
- private _activeIdeType: string | null = null;
35
36
  private timer: NodeJS.Timeout | null = null;
36
37
 
37
38
  constructor(deps: AgentStreamPollerDeps) {
@@ -40,14 +41,12 @@ export class AgentStreamPoller {
40
41
 
41
42
  /** Currently active IDE type for agent streaming */
42
43
  get activeIde(): string | null {
43
- return this._activeIdeType;
44
+ return null;
44
45
  }
45
46
 
46
47
  /** Reset active IDE tracking (e.g., when IDE is stopped) */
47
- resetActiveIde(ideType: string): void {
48
- if (this._activeIdeType === ideType) {
49
- this._activeIdeType = null;
50
- }
48
+ resetActiveIde(parentSessionId: string): void {
49
+ this.deps.agentStreamManager.resetParentSession(parentSessionId);
51
50
  }
52
51
 
53
52
  /** Start polling (idempotent — ignored if already started) */
@@ -74,6 +73,7 @@ export class AgentStreamPoller {
74
73
  providerLoader,
75
74
  instanceManager,
76
75
  cdpManagers,
76
+ sessionRegistry,
77
77
  } = this.deps;
78
78
 
79
79
  if (!agentStreamManager || cdpManagers.size === 0) return;
@@ -85,6 +85,7 @@ export class AgentStreamPoller {
85
85
 
86
86
  // 1b. Dynamically add/remove IDE instance extensions
87
87
  const ideInstance = instanceManager.getInstance(`ide:${ideType}`) as any;
88
+ const parentSessionId = ideInstance?.getInstanceId?.();
88
89
  if (ideInstance?.getExtensionTypes && ideInstance?.addExtension && ideInstance?.removeExtension) {
89
90
  const currentExtTypes = new Set(ideInstance.getExtensionTypes() as string[]);
90
91
  const enabledExtTypes = new Set(
@@ -94,6 +95,10 @@ export class AgentStreamPoller {
94
95
  // Remove disabled extensions
95
96
  for (const extType of currentExtTypes) {
96
97
  if (!enabledExtTypes.has(extType)) {
98
+ const extInstance = ideInstance.getExtension?.(extType);
99
+ if (extInstance?.getInstanceId) {
100
+ sessionRegistry.unregister(extInstance.getInstanceId());
101
+ }
97
102
  ideInstance.removeExtension(extType);
98
103
  LOG.info('AgentStream', `Extension removed: ${extType} (disabled for ${ideType})`);
99
104
  }
@@ -106,6 +111,18 @@ export class AgentStreamPoller {
106
111
  if (extProvider) {
107
112
  const extSettings = providerLoader.getSettings(extType);
108
113
  ideInstance.addExtension(extProvider, extSettings);
114
+ const extInstance = ideInstance.getExtension?.(extType);
115
+ if (parentSessionId && extInstance?.getInstanceId) {
116
+ sessionRegistry.register({
117
+ sessionId: extInstance.getInstanceId(),
118
+ parentSessionId,
119
+ providerType: extType,
120
+ providerCategory: 'extension',
121
+ transport: 'cdp-webview',
122
+ cdpManagerKey: ideType,
123
+ instanceKey: `ide:${ideType}`,
124
+ });
125
+ }
109
126
  LOG.info('AgentStream', `Extension added: ${extType} (enabled for ${ideType})`);
110
127
  }
111
128
  }
@@ -113,56 +130,51 @@ export class AgentStreamPoller {
113
130
  }
114
131
 
115
132
  // 1c. If the active agent stream belongs to a now-disabled extension, detach it
116
- if (this._activeIdeType === ideType && agentStreamManager.activeAgentType) {
117
- const activeType = agentStreamManager.activeAgentType;
118
- const enabledExtTypes = new Set(
119
- providerLoader.getEnabledExtensionProviders(ideType).map((p: any) => p.type)
120
- );
121
- if (!enabledExtTypes.has(activeType)) {
122
- LOG.info('AgentStream', `Active agent ${activeType} was disabled for ${ideType} — detaching`);
123
- await agentStreamManager.switchActiveAgent(cdp, null);
124
- this._activeIdeType = null;
133
+ const activeSessionId = parentSessionId ? agentStreamManager.getActiveSessionId(parentSessionId) : null;
134
+ if (activeSessionId) {
135
+ const activeTarget = sessionRegistry.get(activeSessionId);
136
+ const enabledExtTypes = new Set(providerLoader.getEnabledExtensionProviders(ideType).map((p: any) => p.type));
137
+ if (!activeTarget || !enabledExtTypes.has(activeTarget.providerType)) {
138
+ LOG.info('AgentStream', `Active agent ${activeTarget?.providerType || activeSessionId} was disabled for ${ideType} — detaching`);
139
+ await agentStreamManager.setActiveSession(cdp, parentSessionId!, null);
125
140
  // Report empty streams so dashboard removes the tab
126
141
  this.deps.onStreamsUpdated?.(ideType, []);
127
142
  }
128
143
  }
129
- }
130
-
131
- // ─── Phase 2: Agent session sync + collect ───
132
-
133
- // If agent already connected to a specific IDE, only operate on that CDP
134
- if (this._activeIdeType) {
135
- const cdp = cdpManagers.get(this._activeIdeType);
136
- if (cdp?.isConnected) {
137
- try {
138
- await agentStreamManager.syncAgentSessions(cdp);
139
- const streams = await agentStreamManager.collectAgentStreams(cdp);
140
- this.deps.onStreamsUpdated?.(this._activeIdeType, streams);
141
- } catch { }
142
- return;
144
+ if (!cdp.isConnected) {
145
+ if (parentSessionId && activeSessionId) {
146
+ agentStreamManager.resetParentSession(parentSessionId);
147
+ this.deps.onStreamsUpdated?.(ideType, []);
148
+ }
149
+ continue;
143
150
  }
144
- // CDP lost — reset
145
- this._activeIdeType = null;
146
- }
147
151
 
148
- // ─── Phase 3: Auto-discover agents ───
149
- if (!agentStreamManager.activeAgentType) {
150
- for (const [ideType, cdp] of cdpManagers) {
151
- if (!cdp.isConnected) continue;
152
+ // ─── Phase 2: Agent session sync + collect ───
153
+ let resolvedActiveSessionId = activeSessionId;
154
+
155
+ // ─── Phase 3: Auto-discover agents ───
156
+ if (!resolvedActiveSessionId && parentSessionId) {
152
157
  try {
153
158
  const discovered = await cdp.discoverAgentWebviews();
154
- if (discovered.length > 0) {
155
- this._activeIdeType = ideType;
156
- await agentStreamManager.switchActiveAgent(cdp, discovered[0].agentType);
157
- LOG.info('AgentStream', `Auto-activated: ${discovered[0].agentType} (${ideType})`);
158
- // sync+collect immediately
159
- await agentStreamManager.syncAgentSessions(cdp);
160
- const streams = await agentStreamManager.collectAgentStreams(cdp);
161
- this.deps.onStreamsUpdated?.(ideType, streams);
162
- return;
159
+ for (const target of discovered) {
160
+ const sessionId = agentStreamManager.resolveSessionForAgent(parentSessionId, target.agentType);
161
+ if (sessionId) {
162
+ resolvedActiveSessionId = sessionId;
163
+ await agentStreamManager.setActiveSession(cdp, parentSessionId, sessionId);
164
+ LOG.info('AgentStream', `Auto-activated: ${target.agentType} (${ideType})`);
165
+ break;
166
+ }
163
167
  }
164
168
  } catch { }
165
169
  }
170
+
171
+ if (!resolvedActiveSessionId || !parentSessionId) continue;
172
+
173
+ try {
174
+ await agentStreamManager.syncActiveSession(cdp, parentSessionId);
175
+ const stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
176
+ this.deps.onStreamsUpdated?.(ideType, stream ? [stream] : []);
177
+ } catch { }
166
178
  }
167
179
  }
168
180
  }
@@ -20,6 +20,7 @@ import { VersionArchive, detectAllVersions } from '../providers/version-archive.
20
20
  import { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
21
21
  import { DevServer } from '../daemon/dev-server.js';
22
22
  import { detectIDEs } from '../detection/ide-detector.js';
23
+ import { SessionRegistry } from '../sessions/registry.js';
23
24
  import { installGlobalInterceptor, LOG } from '../logging/logger.js';
24
25
  import { loadConfig } from '../config/config.js';
25
26
 
@@ -70,7 +71,7 @@ export interface DaemonComponents {
70
71
  poller: AgentStreamPoller;
71
72
  cdpInitializer: DaemonCdpInitializer;
72
73
  cdpManagers: Map<string, DaemonCdpManager>;
73
- instanceIdMap: Map<string, string>;
74
+ sessionRegistry: SessionRegistry;
74
75
  detectedIdes: { value: any[] };
75
76
  }
76
77
 
@@ -141,13 +142,16 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
141
142
  // 3. Shared state
142
143
  const instanceManager = new ProviderInstanceManager();
143
144
  const cdpManagers = new Map<string, DaemonCdpManager>();
144
- const instanceIdMap = new Map<string, string>();
145
+ const sessionRegistry = new SessionRegistry();
145
146
  const detectedIdesRef = { value: [] as any[] };
147
+ let agentStreamManager: DaemonAgentStreamManager | null = null;
148
+ let poller: AgentStreamPoller | null = null;
146
149
 
147
150
  // 4. CLI Manager
148
151
  const cliManager = new DaemonCliManager({
149
152
  ...config.cliManagerDeps,
150
153
  getInstanceManager: () => instanceManager,
154
+ getSessionRegistry: () => sessionRegistry,
151
155
  }, providerLoader);
152
156
 
153
157
  // 5. Detect IDEs
@@ -161,7 +165,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
161
165
  providerLoader,
162
166
  instanceManager,
163
167
  cdpManagers,
164
- instanceIdMap,
168
+ sessionRegistry,
165
169
  };
166
170
 
167
171
  const cdpInitializer = new DaemonCdpInitializer({
@@ -174,6 +178,21 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
174
178
  // Transport-specific extras
175
179
  await config.onCdpManagerSetup?.(ideType, manager, managerKey);
176
180
  },
181
+ onDisconnected: async (_ideType, _manager, managerKey) => {
182
+ sessionRegistry.unregisterByManagerKey(managerKey);
183
+ const instanceKey = `ide:${managerKey}`;
184
+ const ideInstance = instanceManager.getInstance(instanceKey) as any;
185
+
186
+ if (ideInstance) {
187
+ instanceManager.removeInstance(instanceKey);
188
+ LOG.info('CDP', `Instance removed after disconnect: ${instanceKey}`);
189
+ }
190
+
191
+ if (ideInstance?.getInstanceId) {
192
+ agentStreamManager?.resetParentSession(ideInstance.getInstanceId());
193
+ }
194
+ config.onStatusChange?.();
195
+ },
177
196
  });
178
197
  await cdpInitializer.connectAll(detectedIdesRef.value);
179
198
  cdpInitializer.startPeriodicScan(config.cdpScanIntervalMs ?? 30_000);
@@ -186,20 +205,19 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
186
205
  adapters: cliManager.adapters,
187
206
  providerLoader,
188
207
  instanceManager,
189
- instanceIdMap,
208
+ sessionRegistry,
190
209
  });
191
210
 
192
211
  // 8. AgentStreamManager
193
- const agentStreamManager = new DaemonAgentStreamManager(
212
+ agentStreamManager = new DaemonAgentStreamManager(
194
213
  LOG.forComponent('AgentStream').asLogFn(),
195
214
  providerLoader,
215
+ sessionRegistry,
196
216
  );
197
217
  commandHandler.setAgentStreamManager(agentStreamManager);
198
218
 
199
219
  // 9. Router + Poller (with internal cross-wiring)
200
220
  // Note: poller is declared first so router's onIdeConnected closure captures it
201
- let poller: AgentStreamPoller;
202
-
203
221
  const router = new DaemonCommandRouter({
204
222
  commandHandler,
205
223
  cliManager,
@@ -207,7 +225,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
207
225
  providerLoader,
208
226
  instanceManager,
209
227
  detectedIdes: detectedIdesRef,
210
- instanceIdMap,
228
+ sessionRegistry,
211
229
  onCdpManagerCreated: async (ideType: string, manager: DaemonCdpManager) => {
212
230
  // For launch_ide: register instance + extension providers
213
231
  await setupIdeInstance(cdpSetupContext, { ideType, manager });
@@ -224,6 +242,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
224
242
  providerLoader,
225
243
  instanceManager,
226
244
  cdpManagers,
245
+ sessionRegistry,
227
246
  onStreamsUpdated: config.onStreamsUpdated,
228
247
  });
229
248
  poller.start();
@@ -241,7 +260,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
241
260
  poller,
242
261
  cdpInitializer,
243
262
  cdpManagers,
244
- instanceIdMap,
263
+ sessionRegistry,
245
264
  detectedIdes: detectedIdesRef,
246
265
  };
247
266
  }
@@ -288,9 +307,8 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
288
307
 
289
308
  // 2. Dispose agent stream
290
309
  try {
291
- const anyCdp = [...cdpManagers.values()].find(m => m.isConnected);
292
- if (agentStreamManager && anyCdp) {
293
- await agentStreamManager.dispose(anyCdp);
310
+ if (agentStreamManager) {
311
+ await agentStreamManager.dispose(cdpManagers);
294
312
  }
295
313
  } catch (e: any) { LOG.warn('Shutdown', `AgentStream dispose: ${e?.message}`); }
296
314
 
@@ -24,6 +24,13 @@ export interface CdpInitializerConfig {
24
24
  enabledIdes?: string[];
25
25
  /** Callback when a new CDP manager is connected */
26
26
  onConnected?: (ideType: string, manager: DaemonCdpManager, managerKey: string) => void | Promise<void>;
27
+ /** Callback when a stale/disconnected CDP manager is removed */
28
+ onDisconnected?: (
29
+ ideType: string,
30
+ manager: DaemonCdpManager,
31
+ managerKey: string,
32
+ reason: 'ide_closed' | 'target_closed' | 'target_rekeyed',
33
+ ) => void | Promise<void>;
27
34
  }
28
35
 
29
36
  export class DaemonCdpInitializer {
@@ -88,6 +95,7 @@ export class DaemonCdpInitializer {
88
95
 
89
96
  // 1. Try multi-window: list all workbench pages on this port
90
97
  const targets = await DaemonCdpManager.listAllTargets(port);
98
+ await this.pruneStaleManagers(port, ide, targets);
91
99
 
92
100
  if (targets.length === 0) {
93
101
  // Prevent duplicate fallback connection
@@ -153,6 +161,45 @@ export class DaemonCdpInitializer {
153
161
  }
154
162
  }
155
163
 
164
+ private async pruneStaleManagers(
165
+ port: number,
166
+ ide: string,
167
+ targets: Array<{ id: string }>,
168
+ ): Promise<void> {
169
+ const trackedTargetIds = new Set(targets.map((target) => target.id));
170
+ const removals: Array<{
171
+ key: string;
172
+ manager: DaemonCdpManager;
173
+ reason: 'ide_closed' | 'target_closed' | 'target_rekeyed';
174
+ }> = [];
175
+
176
+ for (const [key, manager] of this.config.cdpManagers.entries()) {
177
+ if (!(key === ide || key.startsWith(`${ide}_`))) continue;
178
+ if (manager.getPort() !== port) continue;
179
+
180
+ if (targets.length === 0) {
181
+ removals.push({ key, manager, reason: 'ide_closed' });
182
+ continue;
183
+ }
184
+
185
+ if (manager.targetId && !trackedTargetIds.has(manager.targetId)) {
186
+ removals.push({ key, manager, reason: 'target_closed' });
187
+ continue;
188
+ }
189
+
190
+ if (key === ide && !manager.targetId && targets.length > 1) {
191
+ removals.push({ key, manager, reason: 'target_rekeyed' });
192
+ }
193
+ }
194
+
195
+ for (const { key, manager, reason } of removals) {
196
+ try { manager.disconnect(); } catch { /* noop */ }
197
+ this.config.cdpManagers.delete(key);
198
+ LOG.info('CDP', `Removed stale manager: ${key} (${reason})`);
199
+ await this.config.onDisconnected?.(ide, manager, key, reason);
200
+ }
201
+ }
202
+
156
203
  // ─── Periodic scanning ───
157
204
 
158
205
  /**
@@ -660,9 +660,12 @@ export class DaemonCdpManager {
660
660
  try {
661
661
  // 1. Find webview iframe targets
662
662
  const { targetInfos } = await sendWs('Target.getTargets');
663
- const webviewIframes = (targetInfos || []).filter(
664
- (t: any) => t.type === 'iframe' && (t.url || '').includes('vscode-webview')
665
- );
663
+ const pageWebviewUrls = await this.getCurrentPageWebviewUrls();
664
+ const webviewIframes = (targetInfos || []).filter((t: any) => {
665
+ if (t.type !== 'iframe' || !(t.url || '').includes('vscode-webview')) return false;
666
+ if (pageWebviewUrls.size === 0) return true;
667
+ return pageWebviewUrls.has(t.url || '');
668
+ });
666
669
 
667
670
  if (webviewIframes.length === 0) {
668
671
  this.log('[CDP] evaluateInWebviewFrame: no webview iframes found');
@@ -766,6 +769,8 @@ export class DaemonCdpManager {
766
769
  allTargets = result?.targetInfos || [];
767
770
  }
768
771
 
772
+ const pageWebviewUrls = await this.getCurrentPageWebviewUrls();
773
+
769
774
  const iframes = allTargets.filter((t: any) => t.type === 'iframe');
770
775
  const typeMap = new Map<string, number>();
771
776
  for (const t of allTargets) {
@@ -794,6 +799,7 @@ export class DaemonCdpManager {
794
799
  const url = target.url || '';
795
800
  const hasWebview = url.includes('vscode-webview');
796
801
  if (!hasWebview) continue;
802
+ if (pageWebviewUrls.size > 0 && !pageWebviewUrls.has(url)) continue;
797
803
 
798
804
  for (const known of this.extensionProviders) {
799
805
  if (known.extensionIdPattern.test(url)) {
@@ -822,7 +828,10 @@ export class DaemonCdpManager {
822
828
  async attachToAgent(target: AgentWebviewTarget): Promise<string | null> {
823
829
  if (!this.isConnected) return null;
824
830
  for (const [sid, t] of this.agentSessions) {
825
- if (t.agentType === target.agentType) return sid;
831
+ if (t.targetId === target.targetId) return sid;
832
+ if (t.agentType === target.agentType && t.targetId !== target.targetId) {
833
+ await this.detachAgent(sid).catch(() => { });
834
+ }
826
835
  }
827
836
  try {
828
837
  // Attach via Browser WS (iframes can only be attached from browser-level)
@@ -963,6 +972,38 @@ export class DaemonCdpManager {
963
972
  return this.agentSessions;
964
973
  }
965
974
 
975
+ private async getCurrentPageWebviewUrls(): Promise<Set<string>> {
976
+ if (!this.isConnected) return new Set();
977
+
978
+ try {
979
+ const urls = new Set<string>();
980
+ const { frameTree } = await this.sendInternal('Page.getFrameTree', {}, 5000);
981
+ const visit = (node: any) => {
982
+ const url = node?.frame?.url;
983
+ if (typeof url === 'string' && url.includes('vscode-webview')) {
984
+ urls.add(url);
985
+ }
986
+ for (const child of node?.childFrames || []) visit(child);
987
+ };
988
+ if (frameTree) visit(frameTree);
989
+ if (urls.size > 0) return urls;
990
+ } catch { /* fall through to DOM scan */ }
991
+
992
+ try {
993
+ const raw = await this.evaluate(
994
+ `JSON.stringify(Array.from(document.querySelectorAll('iframe,webview'))
995
+ .map((el) => el.src || el.getAttribute('src') || '')
996
+ .filter((src) => typeof src === 'string' && src.includes('vscode-webview')))`,
997
+ 5000,
998
+ );
999
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
1000
+ if (!Array.isArray(parsed)) return new Set();
1001
+ return new Set(parsed.filter((src: unknown): src is string => typeof src === 'string' && src.length > 0));
1002
+ } catch {
1003
+ return new Set();
1004
+ }
1005
+ }
1006
+
966
1007
  // ─── Screenshot ──────────────────────────────────────────
967
1008
 
968
1009
  async captureScreenshot(opts?: { quality?: number }): Promise<Buffer | null> {
package/src/cdp/setup.ts CHANGED
@@ -9,14 +9,13 @@ import { DaemonCdpManager } from './manager.js';
9
9
  import { ProviderLoader } from '../providers/provider-loader.js';
10
10
  import { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
11
11
  import { IdeProviderInstance } from '../providers/ide-provider-instance.js';
12
- import type { ProviderModule } from '../providers/contracts.js';
12
+ import { SessionRegistry } from '../sessions/registry.js';
13
13
 
14
14
  export interface CdpSetupContext {
15
15
  providerLoader: ProviderLoader;
16
16
  instanceManager: ProviderInstanceManager;
17
17
  cdpManagers: Map<string, DaemonCdpManager>;
18
- /** UUID instanceId → CDP manager key mapping */
19
- instanceIdMap: Map<string, string>;
18
+ sessionRegistry: SessionRegistry;
20
19
  /** Server connection (optional) */
21
20
  serverConn?: any;
22
21
  }
@@ -58,7 +57,7 @@ export function registerExtensionProviders(
58
57
  * 2. Create IdeProviderInstance
59
58
  * 3. Register in InstanceManager
60
59
  * 4. Register enabled extensions
61
- * 5. Update instanceIdMap (IDE + extension UUIDs)
60
+ * 5. Register runtime sessions (workspace + extension children)
62
61
  *
63
62
  * @returns The created IdeProviderInstance, or null if provider not found
64
63
  */
@@ -66,7 +65,7 @@ export async function setupIdeInstance(
66
65
  ctx: CdpSetupContext,
67
66
  opts: SetupIdeInstanceOptions,
68
67
  ): Promise<IdeProviderInstance | null> {
69
- const { providerLoader, instanceManager, instanceIdMap } = ctx;
68
+ const { providerLoader, instanceManager, sessionRegistry } = ctx;
70
69
  const { ideType, manager, settings } = opts;
71
70
  const managerKey = opts.managerKey || ideType;
72
71
 
@@ -91,18 +90,34 @@ export async function setupIdeInstance(
91
90
  settings: resolvedSettings,
92
91
  });
93
92
 
94
- // 5. Map IDE instance UUID → manager key
95
- instanceIdMap.set(ideInstance.getInstanceId(), managerKey);
93
+ // 5. Register workspace session
94
+ sessionRegistry.register({
95
+ sessionId: ideInstance.getInstanceId(),
96
+ parentSessionId: null,
97
+ providerType: ideType,
98
+ providerCategory: 'ide',
99
+ transport: 'cdp-page',
100
+ cdpManagerKey: managerKey,
101
+ instanceKey: `ide:${managerKey}`,
102
+ });
96
103
 
97
104
  // 6. Register enabled extensions
98
105
  const extensionProviders = providerLoader.getEnabledByCategory('extension', ideType);
99
106
  for (const extProvider of extensionProviders) {
100
107
  const extSettings = providerLoader.getSettings(extProvider.type);
101
108
  await ideInstance.addExtension(extProvider, extSettings);
102
- // Map extension UUIDs too (CDP uses parent IDE)
103
- for (const ext of ideInstance.getExtensionInstances()) {
104
- instanceIdMap.set(ext.getInstanceId(), managerKey);
105
- }
109
+ }
110
+
111
+ for (const ext of ideInstance.getExtensionInstances()) {
112
+ sessionRegistry.register({
113
+ sessionId: ext.getInstanceId(),
114
+ parentSessionId: ideInstance.getInstanceId(),
115
+ providerType: ext.type,
116
+ providerCategory: 'extension',
117
+ transport: 'cdp-webview',
118
+ cdpManagerKey: managerKey,
119
+ instanceKey: `ide:${managerKey}`,
120
+ });
106
121
  }
107
122
 
108
123
  return ideInstance;