@adhdev/daemon-core 0.6.76 → 0.6.79

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.
@@ -0,0 +1 @@
1
+ export { x as ManagedStatus, N as isManagedStatusWaiting, O as isManagedStatusWorking, V as normalizeActiveChatData, X as normalizeManagedStatus } from '../normalize-igHDb198.mjs';
@@ -0,0 +1 @@
1
+ export { x as ManagedStatus, N as isManagedStatusWaiting, O as isManagedStatusWorking, V as normalizeActiveChatData, X as normalizeManagedStatus } from '../normalize-igHDb198.js';
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/status/normalize.ts
21
+ var normalize_exports = {};
22
+ __export(normalize_exports, {
23
+ isManagedStatusWaiting: () => isManagedStatusWaiting,
24
+ isManagedStatusWorking: () => isManagedStatusWorking,
25
+ normalizeActiveChatData: () => normalizeActiveChatData,
26
+ normalizeManagedStatus: () => normalizeManagedStatus
27
+ });
28
+ module.exports = __toCommonJS(normalize_exports);
29
+ var WORKING_STATUSES = /* @__PURE__ */ new Set([
30
+ "generating",
31
+ "streaming",
32
+ "loading",
33
+ "loading_reference",
34
+ "thinking",
35
+ "active"
36
+ ]);
37
+ function hasApprovalButtons(activeModal) {
38
+ return (activeModal?.buttons?.length ?? 0) > 0;
39
+ }
40
+ function normalizeManagedStatus(status, opts) {
41
+ if (hasApprovalButtons(opts?.activeModal)) return "waiting_approval";
42
+ const normalized = String(status || "idle").trim().toLowerCase();
43
+ if (normalized === "waiting_approval") return "waiting_approval";
44
+ if (WORKING_STATUSES.has(normalized)) return "generating";
45
+ if (normalized === "error") return "error";
46
+ if (normalized === "stopped") return "stopped";
47
+ if (normalized === "starting") return "starting";
48
+ if (normalized === "panel_hidden") return "panel_hidden";
49
+ if (normalized === "not_monitored") return "not_monitored";
50
+ if (normalized === "disconnected") return "disconnected";
51
+ return "idle";
52
+ }
53
+ function isManagedStatusWorking(status) {
54
+ return normalizeManagedStatus(status) === "generating";
55
+ }
56
+ function isManagedStatusWaiting(status, opts) {
57
+ return normalizeManagedStatus(status, opts) === "waiting_approval";
58
+ }
59
+ function normalizeActiveChatData(activeChat) {
60
+ if (!activeChat) return activeChat;
61
+ return {
62
+ ...activeChat,
63
+ status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal })
64
+ };
65
+ }
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ isManagedStatusWaiting,
69
+ isManagedStatusWorking,
70
+ normalizeActiveChatData,
71
+ normalizeManagedStatus
72
+ });
73
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n): T {\n if (!activeChat) return activeChat;\n return {\n ...activeChat,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n } as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,CAAC;AAED,SAAS,mBAAmB,aAA8D;AACtF,UAAQ,aAAa,SAAS,UAAU,KAAK;AACjD;AAEO,SAAS,uBACZ,QACA,MACa;AACb,MAAI,mBAAmB,MAAM,WAAW,EAAG,QAAO;AAElD,QAAM,aAAa,OAAO,UAAU,MAAM,EAAE,KAAK,EAAE,YAAY;AAC/D,MAAI,eAAe,mBAAoB,QAAO;AAC9C,MAAI,iBAAiB,IAAI,UAAU,EAAG,QAAO;AAC7C,MAAI,eAAe,QAAS,QAAO;AACnC,MAAI,eAAe,UAAW,QAAO;AACrC,MAAI,eAAe,WAAY,QAAO;AACtC,MAAI,eAAe,eAAgB,QAAO;AAC1C,MAAI,eAAe,gBAAiB,QAAO;AAC3C,MAAI,eAAe,eAAgB,QAAO;AAC1C,SAAO;AACX;AAEO,SAAS,uBAAuB,QAAiC;AACpE,SAAO,uBAAuB,MAAM,MAAM;AAC9C;AAEO,SAAS,uBACZ,QACA,MACO;AACP,SAAO,uBAAuB,QAAQ,IAAI,MAAM;AACpD;AAEO,SAAS,wBACZ,YACC;AACD,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO;AAAA,IACH,GAAG;AAAA,IACH,QAAQ,uBAAuB,WAAW,QAAQ,EAAE,aAAa,WAAW,YAAY,CAAC;AAAA,EAC7F;AACJ;","names":[]}
@@ -0,0 +1,45 @@
1
+ // src/status/normalize.ts
2
+ var WORKING_STATUSES = /* @__PURE__ */ new Set([
3
+ "generating",
4
+ "streaming",
5
+ "loading",
6
+ "loading_reference",
7
+ "thinking",
8
+ "active"
9
+ ]);
10
+ function hasApprovalButtons(activeModal) {
11
+ return (activeModal?.buttons?.length ?? 0) > 0;
12
+ }
13
+ function normalizeManagedStatus(status, opts) {
14
+ if (hasApprovalButtons(opts?.activeModal)) return "waiting_approval";
15
+ const normalized = String(status || "idle").trim().toLowerCase();
16
+ if (normalized === "waiting_approval") return "waiting_approval";
17
+ if (WORKING_STATUSES.has(normalized)) return "generating";
18
+ if (normalized === "error") return "error";
19
+ if (normalized === "stopped") return "stopped";
20
+ if (normalized === "starting") return "starting";
21
+ if (normalized === "panel_hidden") return "panel_hidden";
22
+ if (normalized === "not_monitored") return "not_monitored";
23
+ if (normalized === "disconnected") return "disconnected";
24
+ return "idle";
25
+ }
26
+ function isManagedStatusWorking(status) {
27
+ return normalizeManagedStatus(status) === "generating";
28
+ }
29
+ function isManagedStatusWaiting(status, opts) {
30
+ return normalizeManagedStatus(status, opts) === "waiting_approval";
31
+ }
32
+ function normalizeActiveChatData(activeChat) {
33
+ if (!activeChat) return activeChat;
34
+ return {
35
+ ...activeChat,
36
+ status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal })
37
+ };
38
+ }
39
+ export {
40
+ isManagedStatusWaiting,
41
+ isManagedStatusWorking,
42
+ normalizeActiveChatData,
43
+ normalizeManagedStatus
44
+ };
45
+ //# sourceMappingURL=normalize.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/status/normalize.ts"],"sourcesContent":["import type { ActiveChatData } from '../providers/provider-instance.js';\n\nexport type ManagedStatus =\n | 'idle'\n | 'generating'\n | 'waiting_approval'\n | 'error'\n | 'stopped'\n | 'starting'\n | 'panel_hidden'\n | 'not_monitored'\n | 'disconnected';\n\nconst WORKING_STATUSES = new Set([\n 'generating',\n 'streaming',\n 'loading',\n 'loading_reference',\n 'thinking',\n 'active',\n]);\n\nfunction hasApprovalButtons(activeModal?: { buttons?: unknown[] | null } | null): boolean {\n return (activeModal?.buttons?.length ?? 0) > 0;\n}\n\nexport function normalizeManagedStatus(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): ManagedStatus {\n if (hasApprovalButtons(opts?.activeModal)) return 'waiting_approval';\n\n const normalized = String(status || 'idle').trim().toLowerCase();\n if (normalized === 'waiting_approval') return 'waiting_approval';\n if (WORKING_STATUSES.has(normalized)) return 'generating';\n if (normalized === 'error') return 'error';\n if (normalized === 'stopped') return 'stopped';\n if (normalized === 'starting') return 'starting';\n if (normalized === 'panel_hidden') return 'panel_hidden';\n if (normalized === 'not_monitored') return 'not_monitored';\n if (normalized === 'disconnected') return 'disconnected';\n return 'idle';\n}\n\nexport function isManagedStatusWorking(status?: string | null): boolean {\n return normalizeManagedStatus(status) === 'generating';\n}\n\nexport function isManagedStatusWaiting(\n status?: string | null,\n opts?: { activeModal?: { buttons?: unknown[] | null } | null },\n): boolean {\n return normalizeManagedStatus(status, opts) === 'waiting_approval';\n}\n\nexport function normalizeActiveChatData<T extends ActiveChatData | null | undefined>(\n activeChat: T,\n): T {\n if (!activeChat) return activeChat;\n return {\n ...activeChat,\n status: normalizeManagedStatus(activeChat.status, { activeModal: activeChat.activeModal }),\n } as T;\n}\n"],"mappings":";AAaA,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,CAAC;AAED,SAAS,mBAAmB,aAA8D;AACtF,UAAQ,aAAa,SAAS,UAAU,KAAK;AACjD;AAEO,SAAS,uBACZ,QACA,MACa;AACb,MAAI,mBAAmB,MAAM,WAAW,EAAG,QAAO;AAElD,QAAM,aAAa,OAAO,UAAU,MAAM,EAAE,KAAK,EAAE,YAAY;AAC/D,MAAI,eAAe,mBAAoB,QAAO;AAC9C,MAAI,iBAAiB,IAAI,UAAU,EAAG,QAAO;AAC7C,MAAI,eAAe,QAAS,QAAO;AACnC,MAAI,eAAe,UAAW,QAAO;AACrC,MAAI,eAAe,WAAY,QAAO;AACtC,MAAI,eAAe,eAAgB,QAAO;AAC1C,MAAI,eAAe,gBAAiB,QAAO;AAC3C,MAAI,eAAe,eAAgB,QAAO;AAC1C,SAAO;AACX;AAEO,SAAS,uBAAuB,QAAiC;AACpE,SAAO,uBAAuB,MAAM,MAAM;AAC9C;AAEO,SAAS,uBACZ,QACA,MACO;AACP,SAAO,uBAAuB,QAAQ,IAAI,MAAM;AACpD;AAEO,SAAS,wBACZ,YACC;AACD,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO;AAAA,IACH,GAAG;AAAA,IACH,QAAQ,uBAAuB,WAAW,QAAQ,EAAE,aAAa,WAAW,YAAY,CAAC;AAAA,EAC7F;AACJ;","names":[]}
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.6.76",
3
+ "version": "0.6.79",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
10
11
  "require": "./dist/index.js",
11
12
  "default": "./dist/index.js"
13
+ },
14
+ "./status/normalize": {
15
+ "types": "./dist/status/normalize.d.ts",
16
+ "import": "./dist/status/normalize.mjs",
17
+ "require": "./dist/status/normalize.js",
18
+ "default": "./dist/status/normalize.js"
12
19
  }
13
20
  },
14
21
  "scripts": {
@@ -30,14 +30,12 @@ export interface ManagedAgent {
30
30
 
31
31
  export class DaemonAgentStreamManager {
32
32
  private allAdapters: IAgentStreamAdapter[] = [];
33
- private managed = new Map<string, ManagedAgent>();
33
+ private managedByScope = new Map<string, Map<string, ManagedAgent>>();
34
34
  private enabled = true;
35
35
  private logFn: (msg: string) => void;
36
- private lastDiscoveryTime = 0;
37
- private discoveryIntervalMs = 10_000;
38
-
39
-
40
- private _activeAgentType: string | null = null;
36
+ private lastDiscoveryTimeByScope = new Map<string, number>();
37
+ private discoveryIntervalMsByScope = new Map<string, number>();
38
+ private activeAgentTypeByScope = new Map<string, string | null>();
41
39
 
42
40
  constructor(logFn?: (msg: string) => void, providerLoader?: ProviderLoader) {
43
41
  this.logFn = logFn || LOG.forComponent('AgentStream').asLogFn();
@@ -58,7 +56,25 @@ export class DaemonAgentStreamManager {
58
56
 
59
57
  setEnabled(enabled: boolean) { this.enabled = enabled; }
60
58
  get isEnabled() { return this.enabled; }
61
- get activeAgentType(): string | null { return this._activeAgentType; }
59
+ getActiveAgentType(scopeKey: string): string | null {
60
+ return this.activeAgentTypeByScope.get(scopeKey) || null;
61
+ }
62
+
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
+ }
71
+
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);
77
+ }
62
78
 
63
79
  /** Panel focus based on provider.js focusPanel or extensionId (currently no-op) */
64
80
  async ensureAgentPanelOpen(agentType: string, targetIdeType?: string): Promise<void> {
@@ -66,43 +82,52 @@ export class DaemonAgentStreamManager {
66
82
  // Can be replaced with CDP-based focus (future implementation)
67
83
  }
68
84
 
69
- async switchActiveAgent(cdp: DaemonCdpManager, agentType: string | null): Promise<void> {
70
- if (this._activeAgentType === agentType) return;
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;
71
89
 
72
- if (this._activeAgentType) {
73
- const prev = this.managed.get(this._activeAgentType);
90
+ if (previousAgentType) {
91
+ const prev = managed.get(previousAgentType);
74
92
  if (prev) {
75
93
  try { await cdp.detachAgent(prev.sessionId); } catch { }
76
- this.managed.delete(this._activeAgentType);
77
- this.logFn(`[AgentStream] Deactivated: ${prev.adapter.agentName}`);
94
+ managed.delete(previousAgentType);
95
+ this.logFn(`[AgentStream] Deactivated: ${prev.adapter.agentName} (${scopeKey})`);
78
96
  }
79
97
  }
80
98
 
81
- this._activeAgentType = agentType;
82
- this.lastDiscoveryTime = 0;
83
- this.logFn(`[AgentStream] Active agent: ${agentType || 'none'}`);
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'}`);
84
105
  }
85
106
 
86
107
  /** Agent webview discovery + session connection */
87
- async syncAgentSessions(cdp: DaemonCdpManager): Promise<void> {
88
- if (!this.enabled || !this._activeAgentType) return;
108
+ async syncAgentSessions(cdp: DaemonCdpManager, scopeKey: string): Promise<void> {
109
+ const activeAgentType = this.getActiveAgentType(scopeKey);
110
+ if (!this.enabled || !activeAgentType) return;
89
111
 
90
112
  const now = Date.now();
91
- if (this.managed.has(this._activeAgentType) && (now - this.lastDiscoveryTime) < this.discoveryIntervalMs) {
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) {
92
117
  return;
93
118
  }
94
- this.lastDiscoveryTime = now;
119
+ this.lastDiscoveryTimeByScope.set(scopeKey, now);
95
120
 
96
121
  try {
97
122
  const targets = await cdp.discoverAgentWebviews();
98
- const activeTarget = targets.find(t => t.agentType === this._activeAgentType);
123
+ const activeTarget = targets.find(t => t.agentType === activeAgentType);
99
124
 
100
- if (activeTarget && !this.managed.has(this._activeAgentType)) {
101
- const adapter = this.allAdapters.find(a => a.agentType === this._activeAgentType);
125
+ if (activeTarget && !managed.has(activeAgentType)) {
126
+ const adapter = this.allAdapters.find(a => a.agentType === activeAgentType);
102
127
  if (adapter) {
103
128
  const sessionId = await cdp.attachToAgent(activeTarget);
104
129
  if (sessionId) {
105
- this.managed.set(this._activeAgentType, {
130
+ managed.set(activeAgentType, {
106
131
  adapter,
107
132
  sessionId,
108
133
  target: activeTarget,
@@ -110,34 +135,36 @@ export class DaemonAgentStreamManager {
110
135
  lastError: null,
111
136
  lastHiddenCheckTime: 0,
112
137
  });
113
- this.logFn(`[AgentStream] Connected: ${adapter.agentName}`);
138
+ this.logFn(`[AgentStream] Connected: ${adapter.agentName} (${scopeKey})`);
114
139
  }
115
140
  }
116
141
  }
117
142
 
118
143
  // Cleanup inactive agents
119
- for (const [type, agent] of this.managed) {
120
- if (type !== this._activeAgentType) {
144
+ for (const [type, agent] of managed) {
145
+ if (type !== activeAgentType) {
121
146
  await cdp.detachAgent(agent.sessionId);
122
- this.managed.delete(type);
147
+ managed.delete(type);
123
148
  }
124
149
  }
125
150
 
126
- this.discoveryIntervalMs = this.managed.has(this._activeAgentType) ? 30_000 : 10_000;
151
+ this.discoveryIntervalMsByScope.set(scopeKey, managed.has(activeAgentType) ? 30_000 : 10_000);
127
152
  } catch (e) {
128
- this.logFn(`[AgentStream] sync error: ${(e as Error).message}`);
153
+ this.logFn(`[AgentStream] sync error (${scopeKey}): ${(e as Error).message}`);
129
154
  }
130
155
  }
131
156
 
132
157
  /** Collect active agent status */
133
- async collectAgentStreams(cdp: DaemonCdpManager): Promise<AgentStreamState[]> {
158
+ async collectAgentStreams(cdp: DaemonCdpManager, scopeKey: string): Promise<AgentStreamState[]> {
134
159
  if (!this.enabled) return [];
135
160
 
136
161
  const results: AgentStreamState[] = [];
162
+ const activeAgentType = this.getActiveAgentType(scopeKey);
163
+ const managed = this.managedByScope.get(scopeKey);
137
164
 
138
- if (this._activeAgentType && this.managed.has(this._activeAgentType)) {
139
- const agent = this.managed.get(this._activeAgentType)!;
140
- const type = this._activeAgentType;
165
+ if (activeAgentType && managed?.has(activeAgentType)) {
166
+ const agent = managed.get(activeAgentType)!;
167
+ const type = activeAgentType;
141
168
 
142
169
  const isHidden = agent.lastState?.status === 'panel_hidden';
143
170
  const hiddenCacheFresh = isHidden && (Date.now() - agent.lastHiddenCheckTime < 30000);
@@ -170,8 +197,8 @@ export class DaemonAgentStreamManager {
170
197
  });
171
198
  if (errorMsg.includes('timeout') || errorMsg.includes('not connected') || errorMsg.includes('Session')) {
172
199
  try { await cdp.detachAgent(agent.sessionId); } catch { }
173
- this.managed.delete(type);
174
- this.lastDiscoveryTime = 0;
200
+ managed.delete(type);
201
+ this.lastDiscoveryTimeByScope.set(scopeKey, 0);
175
202
  }
176
203
  }
177
204
  }
@@ -180,9 +207,9 @@ export class DaemonAgentStreamManager {
180
207
  return results;
181
208
  }
182
209
 
183
- async sendToAgent(cdp: DaemonCdpManager, agentType: string, text: string, targetIdeType?: string): Promise<boolean> {
210
+ async sendToAgent(cdp: DaemonCdpManager, scopeKey: string, agentType: string, text: string, targetIdeType?: string): Promise<boolean> {
184
211
  await this.ensureAgentPanelOpen(agentType, targetIdeType);
185
- const agent = this.managed.get(agentType);
212
+ const agent = this.getManagedAgent(agentType, scopeKey);
186
213
  if (!agent) return false;
187
214
  try {
188
215
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
@@ -195,9 +222,9 @@ export class DaemonAgentStreamManager {
195
222
  }
196
223
  }
197
224
 
198
- async resolveAgentAction(cdp: DaemonCdpManager, agentType: string, action: 'approve' | 'reject', targetIdeType?: string): Promise<boolean> {
225
+ async resolveAgentAction(cdp: DaemonCdpManager, scopeKey: string, agentType: string, action: 'approve' | 'reject', targetIdeType?: string): Promise<boolean> {
199
226
  await this.ensureAgentPanelOpen(agentType, targetIdeType);
200
- const agent = this.managed.get(agentType);
227
+ const agent = this.getManagedAgent(agentType, scopeKey);
201
228
  if (!agent) return false;
202
229
  try {
203
230
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
@@ -209,9 +236,9 @@ export class DaemonAgentStreamManager {
209
236
  }
210
237
  }
211
238
 
212
- async newAgentSession(cdp: DaemonCdpManager, agentType: string, targetIdeType?: string): Promise<boolean> {
239
+ async newAgentSession(cdp: DaemonCdpManager, scopeKey: string, agentType: string, targetIdeType?: string): Promise<boolean> {
213
240
  await this.ensureAgentPanelOpen(agentType, targetIdeType);
214
- const agent = this.managed.get(agentType);
241
+ const agent = this.getManagedAgent(agentType, scopeKey);
215
242
  if (!agent) return false;
216
243
  try {
217
244
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
@@ -224,14 +251,14 @@ export class DaemonAgentStreamManager {
224
251
  }
225
252
  }
226
253
 
227
- async listAgentChats(cdp: DaemonCdpManager, agentType: string): Promise<AgentChatListItem[]> {
228
- let agent = this.managed.get(agentType);
254
+ async listAgentChats(cdp: DaemonCdpManager, scopeKey: string, agentType: string): Promise<AgentChatListItem[]> {
255
+ let agent = this.getManagedAgent(agentType, scopeKey);
229
256
  // on-demand: try activate+sync if not in managed list
230
257
  if (!agent) {
231
- this.logFn(`[AgentStream] listChats: ${agentType} not managed, trying on-demand activation`);
232
- await this.switchActiveAgent(cdp, agentType);
233
- await this.syncAgentSessions(cdp);
234
- agent = this.managed.get(agentType);
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);
235
262
  }
236
263
  if (!agent || typeof agent.adapter.listChats !== 'function') return [];
237
264
  try {
@@ -244,13 +271,13 @@ export class DaemonAgentStreamManager {
244
271
  }
245
272
  }
246
273
 
247
- async switchAgentSession(cdp: DaemonCdpManager, agentType: string, sessionId: string): Promise<boolean> {
248
- let agent = this.managed.get(agentType);
274
+ async switchAgentSession(cdp: DaemonCdpManager, scopeKey: string, agentType: string, sessionId: string): Promise<boolean> {
275
+ let agent = this.getManagedAgent(agentType, scopeKey);
249
276
  if (!agent) {
250
- this.logFn(`[AgentStream] switchSession: ${agentType} not managed, trying on-demand activation`);
251
- await this.switchActiveAgent(cdp, agentType);
252
- await this.syncAgentSessions(cdp);
253
- agent = this.managed.get(agentType);
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);
254
281
  }
255
282
  if (!agent || typeof agent.adapter.switchSession !== 'function') return false;
256
283
  try {
@@ -263,8 +290,8 @@ export class DaemonAgentStreamManager {
263
290
  }
264
291
  }
265
292
 
266
- async focusAgentEditor(cdp: DaemonCdpManager, agentType: string): Promise<boolean> {
267
- const agent = this.managed.get(agentType);
293
+ async focusAgentEditor(cdp: DaemonCdpManager, scopeKey: string, agentType: string): Promise<boolean> {
294
+ const agent = this.getManagedAgent(agentType, scopeKey);
268
295
  if (!agent || typeof agent.adapter.focusEditor !== 'function') return false;
269
296
  try {
270
297
  const evaluate: AgentEvaluateFn = (expr, timeout) =>
@@ -277,13 +304,26 @@ export class DaemonAgentStreamManager {
277
304
  }
278
305
  }
279
306
 
280
- getConnectedAgents(): string[] { return Array.from(this.managed.keys()); }
281
- getManagedAgent(agentType: string): ManagedAgent | undefined { return this.managed.get(agentType); }
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()));
310
+ }
311
+
312
+ getManagedAgent(agentType: string, scopeKey: string): ManagedAgent | undefined {
313
+ return this.managedByScope.get(scopeKey)?.get(agentType);
314
+ }
282
315
 
283
- async dispose(cdp: DaemonCdpManager): Promise<void> {
284
- for (const [, agent] of this.managed) {
285
- try { await cdp.detachAgent(agent.sessionId); } catch { }
316
+ async dispose(cdpManagers: Map<string, DaemonCdpManager>): Promise<void> {
317
+ for (const [scopeKey, managed] of this.managedByScope) {
318
+ const cdp = cdpManagers.get(scopeKey);
319
+ if (!cdp) continue;
320
+ for (const [, agent] of managed) {
321
+ try { await cdp.detachAgent(agent.sessionId); } catch { }
322
+ }
286
323
  }
287
- this.managed.clear();
324
+ this.managedByScope.clear();
325
+ this.activeAgentTypeByScope.clear();
326
+ this.lastDiscoveryTimeByScope.clear();
327
+ this.discoveryIntervalMsByScope.clear();
288
328
  }
289
329
  }
@@ -31,7 +31,6 @@ export interface AgentStreamPollerDeps {
31
31
 
32
32
  export class AgentStreamPoller {
33
33
  private deps: AgentStreamPollerDeps;
34
- private _activeIdeType: string | null = null;
35
34
  private timer: NodeJS.Timeout | null = null;
36
35
 
37
36
  constructor(deps: AgentStreamPollerDeps) {
@@ -40,14 +39,12 @@ export class AgentStreamPoller {
40
39
 
41
40
  /** Currently active IDE type for agent streaming */
42
41
  get activeIde(): string | null {
43
- return this._activeIdeType;
42
+ return null;
44
43
  }
45
44
 
46
45
  /** Reset active IDE tracking (e.g., when IDE is stopped) */
47
46
  resetActiveIde(ideType: string): void {
48
- if (this._activeIdeType === ideType) {
49
- this._activeIdeType = null;
50
- }
47
+ this.deps.agentStreamManager.resetScope(ideType);
51
48
  }
52
49
 
53
50
  /** Start polling (idempotent — ignored if already started) */
@@ -113,56 +110,48 @@ export class AgentStreamPoller {
113
110
  }
114
111
 
115
112
  // 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;
113
+ const activeType = agentStreamManager.getActiveAgentType(ideType);
114
+ if (activeType) {
118
115
  const enabledExtTypes = new Set(
119
116
  providerLoader.getEnabledExtensionProviders(ideType).map((p: any) => p.type)
120
117
  );
121
118
  if (!enabledExtTypes.has(activeType)) {
122
119
  LOG.info('AgentStream', `Active agent ${activeType} was disabled for ${ideType} — detaching`);
123
- await agentStreamManager.switchActiveAgent(cdp, null);
124
- this._activeIdeType = null;
120
+ await agentStreamManager.switchActiveAgent(cdp, ideType, null);
125
121
  // Report empty streams so dashboard removes the tab
126
122
  this.deps.onStreamsUpdated?.(ideType, []);
127
123
  }
128
124
  }
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;
125
+ if (!cdp.isConnected) {
126
+ if (activeType) {
127
+ agentStreamManager.resetScope(ideType);
128
+ this.deps.onStreamsUpdated?.(ideType, []);
129
+ }
130
+ continue;
143
131
  }
144
- // CDP lost — reset
145
- this._activeIdeType = null;
146
- }
147
132
 
148
- // ─── Phase 3: Auto-discover agents ───
149
- if (!agentStreamManager.activeAgentType) {
150
- for (const [ideType, cdp] of cdpManagers) {
151
- if (!cdp.isConnected) continue;
133
+ // ─── Phase 2: Agent session sync + collect ───
134
+ let resolvedActiveType = activeType;
135
+
136
+ // ─── Phase 3: Auto-discover agents ───
137
+ if (!resolvedActiveType) {
152
138
  try {
153
139
  const discovered = await cdp.discoverAgentWebviews();
154
140
  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;
141
+ resolvedActiveType = discovered[0].agentType;
142
+ await agentStreamManager.switchActiveAgent(cdp, ideType, resolvedActiveType);
143
+ LOG.info('AgentStream', `Auto-activated: ${resolvedActiveType} (${ideType})`);
163
144
  }
164
145
  } catch { }
165
146
  }
147
+
148
+ if (!resolvedActiveType) continue;
149
+
150
+ try {
151
+ await agentStreamManager.syncAgentSessions(cdp, ideType);
152
+ const streams = await agentStreamManager.collectAgentStreams(cdp, ideType);
153
+ this.deps.onStreamsUpdated?.(ideType, streams);
154
+ } catch { }
166
155
  }
167
156
  }
168
157
  }
@@ -288,9 +288,8 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
288
288
 
289
289
  // 2. Dispose agent stream
290
290
  try {
291
- const anyCdp = [...cdpManagers.values()].find(m => m.isConnected);
292
- if (agentStreamManager && anyCdp) {
293
- await agentStreamManager.dispose(anyCdp);
291
+ if (agentStreamManager) {
292
+ await agentStreamManager.dispose(cdpManagers);
294
293
  }
295
294
  } catch (e: any) { LOG.warn('Shutdown', `AgentStream dispose: ${e?.message}`); }
296
295
 
@@ -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)) {
@@ -963,6 +969,23 @@ export class DaemonCdpManager {
963
969
  return this.agentSessions;
964
970
  }
965
971
 
972
+ private async getCurrentPageWebviewUrls(): Promise<Set<string>> {
973
+ if (!this.isConnected) return new Set();
974
+ try {
975
+ const raw = await this.evaluate(
976
+ `JSON.stringify(Array.from(document.querySelectorAll('iframe,webview'))
977
+ .map((el) => el.src || el.getAttribute('src') || '')
978
+ .filter((src) => typeof src === 'string' && src.includes('vscode-webview')))`,
979
+ 5000,
980
+ );
981
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
982
+ if (!Array.isArray(parsed)) return new Set();
983
+ return new Set(parsed.filter((src: unknown): src is string => typeof src === 'string' && src.length > 0));
984
+ } catch {
985
+ return new Set();
986
+ }
987
+ }
988
+
966
989
  // ─── Screenshot ──────────────────────────────────────────
967
990
 
968
991
  async captureScreenshot(opts?: { quality?: number }): Promise<Buffer | null> {