@weavetab/mcp 2.5.0-beta.1 → 2.5.0-beta.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.
Files changed (156) hide show
  1. package/AGENTS.md +2 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +164 -802
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +3 -3
  7. package/dist/cdp/bridge.d.ts +10 -0
  8. package/dist/cdp/bridge.js +285 -78
  9. package/dist/cdp/connector.d.ts +7 -3
  10. package/dist/cdp/connector.js +196 -75
  11. package/dist/cdp/helpers.d.ts +4 -0
  12. package/dist/cdp/helpers.js +181 -0
  13. package/dist/cdp/lock.d.ts +9 -6
  14. package/dist/cdp/lock.js +72 -59
  15. package/dist/cdp/omni_broker.d.ts +2 -1
  16. package/dist/cdp/omni_broker.js +13 -7
  17. package/dist/cdp/profiles.d.ts +10 -0
  18. package/dist/cdp/profiles.js +65 -0
  19. package/dist/cdp/walker.d.ts +25 -1
  20. package/dist/cdp/walker.js +354 -7
  21. package/dist/cli/plugin.d.ts +0 -2
  22. package/dist/cli/plugin.js +51 -696
  23. package/dist/cli/updater.d.ts +1 -1
  24. package/dist/cli/updater.js +27 -7
  25. package/dist/config/loader.d.ts +33 -6
  26. package/dist/config/loader.js +174 -22
  27. package/dist/desktop/discovery.d.ts +44 -0
  28. package/dist/desktop/discovery.js +364 -0
  29. package/dist/index.js +140 -64
  30. package/dist/intelligence/marks.d.ts +36 -0
  31. package/dist/intelligence/marks.js +30 -0
  32. package/dist/intelligence/patternCompiler.d.ts +118 -0
  33. package/dist/intelligence/patternCompiler.js +259 -0
  34. package/dist/intelligence/sessionMemory.d.ts +2 -0
  35. package/dist/intelligence/sessionMemory.js +18 -8
  36. package/dist/overlay/ghost.js +2 -2
  37. package/dist/overlay/injector.js +3 -5
  38. package/dist/plugin/extension_bridge.d.ts +40 -0
  39. package/dist/plugin/extension_bridge.js +158 -0
  40. package/dist/plugin/interface.d.ts +151 -15
  41. package/dist/plugin/loader.d.ts +19 -1
  42. package/dist/plugin/loader.js +331 -63
  43. package/dist/plugin/pipeline.d.ts +75 -0
  44. package/dist/plugin/pipeline.js +183 -0
  45. package/dist/plugin/registry.js +12 -1
  46. package/dist/plugin/storage.d.ts +51 -0
  47. package/dist/plugin/storage.js +164 -0
  48. package/dist/security/secrets.d.ts +1 -0
  49. package/dist/security/secrets.js +13 -0
  50. package/dist/sensors/actionability.js +1 -2
  51. package/dist/sensors/guard.js +4 -10
  52. package/dist/sensors/mutations.js +12 -0
  53. package/dist/sensors/operation_monitor.js +1 -0
  54. package/dist/server.d.ts +1 -1
  55. package/dist/server.js +600 -236
  56. package/dist/state/agent.d.ts +1 -0
  57. package/dist/state/agent.js +3 -0
  58. package/dist/state/delta.d.ts +12 -4
  59. package/dist/state/delta.js +67 -14
  60. package/dist/state/session.d.ts +8 -2
  61. package/dist/state/session.js +60 -13
  62. package/dist/tools/browser_detect.d.ts +16 -2
  63. package/dist/tools/browser_detect.js +54 -6
  64. package/dist/tools/burst.d.ts +17 -7
  65. package/dist/tools/burst.js +240 -83
  66. package/dist/tools/checkpoint.d.ts +38 -0
  67. package/dist/tools/checkpoint.js +271 -0
  68. package/dist/tools/click.d.ts +1 -0
  69. package/dist/tools/click.js +97 -98
  70. package/dist/tools/clipboard.js +31 -24
  71. package/dist/tools/design_extract.js +1 -1
  72. package/dist/tools/eval.js +1 -1
  73. package/dist/tools/fill.d.ts +20 -1
  74. package/dist/tools/fill.js +199 -40
  75. package/dist/tools/find.d.ts +16 -0
  76. package/dist/tools/find.js +67 -44
  77. package/dist/tools/github/index.d.ts +19 -4
  78. package/dist/tools/github/index.js +40 -4
  79. package/dist/tools/highlight.d.ts +6 -2
  80. package/dist/tools/highlight.js +158 -72
  81. package/dist/tools/inspect.js +1 -1
  82. package/dist/tools/key.js +65 -56
  83. package/dist/tools/navigate.d.ts +1 -1
  84. package/dist/tools/navigate.js +57 -44
  85. package/dist/tools/pdf.js +21 -13
  86. package/dist/tools/pointer.d.ts +18 -3
  87. package/dist/tools/pointer.js +89 -1
  88. package/dist/tools/read.d.ts +28 -8
  89. package/dist/tools/read.js +230 -56
  90. package/dist/tools/scrape.js +67 -13
  91. package/dist/tools/screenshot.js +21 -16
  92. package/dist/tools/scroll.js +6 -11
  93. package/dist/tools/select.js +8 -2
  94. package/dist/tools/snapshot.js +3 -34
  95. package/dist/tools/tabs.d.ts +10 -10
  96. package/dist/tools/tabs.js +55 -39
  97. package/dist/tools/thought.d.ts +2 -0
  98. package/dist/tools/thought.js +13 -14
  99. package/dist/tools/type.d.ts +2 -0
  100. package/dist/tools/type.js +109 -60
  101. package/dist/tools/type_secret.js +42 -76
  102. package/dist/tools/upload.js +1 -1
  103. package/dist/tools/video_compiler.d.ts +1 -1
  104. package/dist/tools/video_compiler.js +11 -12
  105. package/dist/version.d.ts +5 -0
  106. package/dist/version.js +5 -0
  107. package/docs/DOCUMENTATION.md +82 -36
  108. package/docs/PLUGINS.md +82 -499
  109. package/docs/TOOLS.md +51 -99
  110. package/docs/architecture/blind-injection.md +83 -0
  111. package/docs/architecture/dual-injection.md +32 -0
  112. package/docs/architecture/overview.md +49 -0
  113. package/docs/architecture/security.md +35 -0
  114. package/docs/architecture/transport.md +37 -0
  115. package/docs/captcha-stealth/captcha-solver.md +75 -0
  116. package/docs/captcha-stealth/stealth-physics.md +60 -0
  117. package/docs/cli/cli-commands.md +44 -0
  118. package/docs/cli/cli-flags.md +20 -0
  119. package/docs/cli/cli-introduction.md +20 -0
  120. package/docs/cli/cli-scripting.md +32 -0
  121. package/docs/cli/g-ide/ide-antigravity.md +24 -0
  122. package/docs/cli/g-ide/ide-claude-code.md +24 -0
  123. package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
  124. package/docs/cli/g-ide/ide-cursor.md +31 -0
  125. package/docs/cli/g-ide/ide-generic.md +34 -0
  126. package/docs/cli/g-ide/ide-opencode.md +24 -0
  127. package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
  128. package/docs/cli/g-ide/ide-windsurf.md +24 -0
  129. package/docs/configuration/config-locations.md +24 -0
  130. package/docs/configuration/configuration.md +73 -0
  131. package/docs/configuration/env-variables.md +19 -0
  132. package/docs/configuration/folders.md +55 -0
  133. package/docs/desktop-automation/electron-apps.md +217 -0
  134. package/docs/get-started/installation.md +47 -0
  135. package/docs/get-started/introduction.md +60 -0
  136. package/docs/get-started/quickstart.md +54 -0
  137. package/docs/get-started/setup-wizard.md +26 -0
  138. package/docs/intelligence/site-profiles.md +151 -0
  139. package/docs/skills-ecosystem/skills-overview.md +35 -0
  140. package/docs/skills-ecosystem/skills-recipes.md +36 -0
  141. package/docs/tools/tools.md +631 -0
  142. package/docs/troubleshooting/trouble-browser.md +27 -0
  143. package/docs/troubleshooting/trouble-config.md +20 -0
  144. package/docs/troubleshooting/trouble-loop.md +31 -0
  145. package/ext-dist/background.js +10 -62
  146. package/ext-dist/content.js +333 -921
  147. package/ext-dist/hud/hud.js +9 -9
  148. package/ext-dist/manifest.json +2 -6
  149. package/ext-dist/styles.css +6 -1
  150. package/mcp.json +108 -72
  151. package/package.json +14 -9
  152. package/server.json +3 -3
  153. package/docs/DEV_MODE.md +0 -68
  154. package/docs/dev_plugin.md +0 -310
  155. package/ext-dist/sidepanel/sidepanel.js +0 -25
  156. package/ext-dist/sidepanel.html +0 -547
@@ -15,6 +15,7 @@ declare class AgentStateManager {
15
15
  constructor(idleTimeoutMs?: number);
16
16
  setCallbacks(cb: AgentStateCallbacks): void;
17
17
  setSession(session: CDP.Client): void;
18
+ getSession(): CDP.Client | null;
18
19
  setIdleTimeout(ms: number): void;
19
20
  reportToolCall(): void;
20
21
  reportToolSuccess(): void;
@@ -15,6 +15,9 @@ class AgentStateManager {
15
15
  setSession(session) {
16
16
  this.session = session;
17
17
  }
18
+ getSession() {
19
+ return this.session;
20
+ }
18
21
  setIdleTimeout(ms) {
19
22
  this.idleTimeoutMs = ms;
20
23
  }
@@ -46,19 +46,19 @@ export interface RichDelta {
46
46
  /**
47
47
  * Store a full snapshot for a tab target.
48
48
  */
49
- export declare function storeSnapshot(targetId: string, url: string, refs: RefEntry[]): void;
49
+ export declare function storeSnapshot(targetId: string, urlOrRefs: string | RefEntry[], maybeRefs?: RefEntry[]): void;
50
50
  /**
51
51
  * Eagerly initialize the property snapshot cache for a tab.
52
52
  */
53
- export declare function seedRichSnapshot(targetId: string, url: string, elements: ActionElement[]): void;
53
+ export declare function seedRichSnapshot(targetId: string, urlOrElements: string | ActionElement[], maybeElements?: ActionElement[]): void;
54
54
  /**
55
55
  * Compute basic delta.
56
56
  */
57
- export declare function computeDelta(targetId: string, url: string, newRefs: RefEntry[]): SnapshotDiff | null;
57
+ export declare function computeDelta(targetId: string, urlOrNewRefs: string | RefEntry[], maybeNewRefs?: RefEntry[]): SnapshotDiff | null;
58
58
  /**
59
59
  * Rich delta: wraps computeDelta with property-level change details.
60
60
  */
61
- export declare function computeRichDelta(targetId: string, url: string, newElements: ActionElement[]): Promise<RichDelta | {
61
+ export declare function computeRichDelta(targetId: string, urlOrNewElements: string | ActionElement[], maybeNewElements?: ActionElement[]): Promise<RichDelta | {
62
62
  delta_timeout: true;
63
63
  } | null>;
64
64
  /**
@@ -69,3 +69,11 @@ export declare function clearSnapshot(targetId: string): void;
69
69
  * Clear all snapshots (called on CDP disconnect).
70
70
  */
71
71
  export declare function clearAllSnapshots(): void;
72
+ /**
73
+ * Get stored ref entry from snapshot for recovery of detached nodes.
74
+ */
75
+ export declare function getStoredRef(targetId: string, refId: string): RefEntry | undefined;
76
+ /**
77
+ * Get stored element properties from rich snapshot for recovery.
78
+ */
79
+ export declare function getStoredProps(targetId: string, refId: string): Record<string, string> | undefined;
@@ -3,6 +3,7 @@
3
3
  // weave_read first visit → full snapshot stored here.
4
4
  // Subsequent calls → diff against stored snapshot → return only changed refs.
5
5
  import { SessionSecretTracker } from "../security/secrets.js";
6
+ import { hookPipeline } from "../plugin/pipeline.js";
6
7
  const snapshots = new Map();
7
8
  const propSnapshots = new Map();
8
9
  const frameSnapshots = new Map();
@@ -13,22 +14,27 @@ const INTERACTIVE_ROLES = new Set([
13
14
  function serializeProps(el, targetId) {
14
15
  const isSecret = (el.backendNodeId !== undefined && targetId &&
15
16
  SessionSecretTracker.isNodeSecret(targetId, el.backendNodeId)) ||
16
- el.label === '[REDACTED]';
17
+ el.label === '[REDACTED]' ||
18
+ el.value === '[REDACTED]' ||
19
+ el.role === 'password' ||
20
+ el.type === 'password';
17
21
  return {
18
- label: isSecret ? '[REDACTED]' : (el.label ?? ''),
19
- value: isSecret ? '[REDACTED]' : (el.value ?? ''),
22
+ label: isSecret ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.label ?? '') : (el.label ?? '')),
23
+ value: isSecret ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.value ?? '') : (el.value ?? '')),
20
24
  disabled: String(!!el.disabled),
21
25
  role: el.role,
22
26
  href: el.href ?? '',
23
- placeholder: el.placeholder ?? '',
24
- textContent: isSecret ? '[REDACTED]' : (el.textContent ?? ''),
27
+ placeholder: isSecret ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.placeholder ?? '') : (el.placeholder ?? '')),
28
+ textContent: isSecret ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.textContent ?? '') : (el.textContent ?? '')),
25
29
  className: (el.className ?? '').split(' ').filter(Boolean).sort().join(' '),
26
30
  };
27
31
  }
28
32
  /**
29
33
  * Store a full snapshot for a tab target.
30
34
  */
31
- export function storeSnapshot(targetId, url, refs) {
35
+ export function storeSnapshot(targetId, urlOrRefs, maybeRefs) {
36
+ const url = typeof urlOrRefs === 'string' ? urlOrRefs : '';
37
+ const refs = Array.isArray(urlOrRefs) ? urlOrRefs : (maybeRefs || []);
32
38
  const map = new Map();
33
39
  for (const r of refs)
34
40
  map.set(r.id, r);
@@ -37,7 +43,9 @@ export function storeSnapshot(targetId, url, refs) {
37
43
  /**
38
44
  * Eagerly initialize the property snapshot cache for a tab.
39
45
  */
40
- export function seedRichSnapshot(targetId, url, elements) {
46
+ export function seedRichSnapshot(targetId, urlOrElements, maybeElements) {
47
+ const url = typeof urlOrElements === 'string' ? urlOrElements : '';
48
+ const elements = Array.isArray(urlOrElements) ? urlOrElements : (maybeElements || []);
41
49
  const propMap = new Map();
42
50
  const snapMap = new Map();
43
51
  const frameMap = new Map();
@@ -63,9 +71,13 @@ export function seedRichSnapshot(targetId, url, elements) {
63
71
  /**
64
72
  * Compute basic delta.
65
73
  */
66
- export function computeDelta(targetId, url, newRefs) {
74
+ export function computeDelta(targetId, urlOrNewRefs, maybeNewRefs) {
75
+ const url = typeof urlOrNewRefs === 'string' ? urlOrNewRefs : '';
76
+ const newRefs = Array.isArray(urlOrNewRefs) ? urlOrNewRefs : (maybeNewRefs || []);
67
77
  const prevWrapper = snapshots.get(targetId);
68
- if (!prevWrapper || prevWrapper.url !== url)
78
+ if (!prevWrapper)
79
+ return null;
80
+ if (url && prevWrapper.url && prevWrapper.url !== url)
69
81
  return null;
70
82
  const prev = prevWrapper.refs;
71
83
  const next = new Map();
@@ -103,15 +115,30 @@ export function computeDelta(targetId, url, newRefs) {
103
115
  removed.push(id);
104
116
  }
105
117
  snapshots.set(targetId, { url, refs: next });
106
- return { added, removed, changed, stable_count };
118
+ const diff = { added, removed, changed, stable_count };
119
+ if (added.length > 0 || removed.length > 0 || changed.length > 0) {
120
+ hookPipeline.dispatchDOMMutation({
121
+ type: "dom_diff",
122
+ timestamp: Date.now(),
123
+ url,
124
+ added,
125
+ removed,
126
+ changed,
127
+ stable_count,
128
+ significantChange: added.length > 0 || changed.length > 0,
129
+ }).catch(() => { });
130
+ }
131
+ return diff;
107
132
  }
108
133
  /**
109
134
  * Rich delta: wraps computeDelta with property-level change details.
110
135
  */
111
- export async function computeRichDelta(targetId, url, newElements) {
136
+ export async function computeRichDelta(targetId, urlOrNewElements, maybeNewElements) {
137
+ const url = typeof urlOrNewElements === 'string' ? urlOrNewElements : '';
138
+ const newElements = Array.isArray(urlOrNewElements) ? urlOrNewElements : (maybeNewElements || []);
112
139
  // Wrap computation in a 5-second race against a timeout to avoid O(n^2) hangs
113
140
  const computePromise = new Promise((resolve) => {
114
- if (!propSnapshots.has(targetId) || propSnapshots.get(targetId).url !== url) {
141
+ if (!propSnapshots.has(targetId) || (url && propSnapshots.get(targetId).url && propSnapshots.get(targetId).url !== url)) {
115
142
  seedRichSnapshot(targetId, url, newElements);
116
143
  resolve(null);
117
144
  return;
@@ -204,13 +231,27 @@ export async function computeRichDelta(targetId, url, newElements) {
204
231
  newPropMap.set(el.id, serializeProps(el, targetId));
205
232
  }
206
233
  propSnapshots.set(targetId, { url, props: newPropMap });
207
- resolve({
234
+ const richResult = {
208
235
  added: basicDiff.added,
209
236
  removed: basicDiff.removed,
210
237
  changed: richChanged,
211
238
  stable_count: basicDiff.stable_count,
212
239
  ...(iframeChanges.length > 0 ? { iframes: iframeChanges } : {}),
213
- });
240
+ };
241
+ if (basicDiff.added.length > 0 || basicDiff.removed.length > 0 || richChanged.length > 0) {
242
+ hookPipeline.dispatchDOMMutation({
243
+ type: "rich_delta",
244
+ timestamp: Date.now(),
245
+ url,
246
+ added: basicDiff.added,
247
+ removed: basicDiff.removed,
248
+ changed: richChanged,
249
+ stable_count: basicDiff.stable_count,
250
+ iframes: iframeChanges,
251
+ significantChange: basicDiff.added.length > 0 || richChanged.some(c => c.actionable),
252
+ }).catch(() => { });
253
+ }
254
+ resolve(richResult);
214
255
  });
215
256
  const timeoutPromise = new Promise((resolve) => {
216
257
  setTimeout(() => resolve({ delta_timeout: true }), 5000);
@@ -232,3 +273,15 @@ export function clearAllSnapshots() {
232
273
  propSnapshots.clear();
233
274
  frameSnapshots.clear();
234
275
  }
276
+ /**
277
+ * Get stored ref entry from snapshot for recovery of detached nodes.
278
+ */
279
+ export function getStoredRef(targetId, refId) {
280
+ return snapshots.get(targetId)?.refs.get(refId);
281
+ }
282
+ /**
283
+ * Get stored element properties from rich snapshot for recovery.
284
+ */
285
+ export function getStoredProps(targetId, refId) {
286
+ return propSnapshots.get(targetId)?.props.get(refId);
287
+ }
@@ -12,9 +12,15 @@ export declare class SessionRegistry {
12
12
  getBrowserContextIdForAgent(agentId: string): string | undefined;
13
13
  getAgentForBrowserContextId(contextId: string): string | undefined;
14
14
  setBrowserContextIdForAgent(agentId: string, contextId: string): void;
15
- getActive(agentId?: string): BrowserClient | null;
15
+ getActive(agentId?: string, appName?: string): BrowserClient | null;
16
16
  getMostRecent(agentId?: string): BrowserClient | null;
17
- setActive(targetId: string, agentId: string): void;
17
+ setActive(targetId: string, agentId: string, appName?: string): void;
18
+ private lastActiveApp;
19
+ getLastActiveApp(): string | null;
20
+ setLastActiveApp(app?: string | null): void;
21
+ findClientByTargetId(targetId: string): BrowserClient | null;
22
+ getActiveElectronClients(): BrowserClient[];
23
+ isBrowserSessionActive(): boolean;
18
24
  getAll(): BrowserClient[];
19
25
  getAllForAgent(agentId: string): BrowserClient[];
20
26
  register(targetId: string, client: BrowserClient): void;
@@ -50,23 +50,67 @@ export class SessionRegistry {
50
50
  this.browserContextsByAgent.set(agentId, contextId);
51
51
  this.savePersistence();
52
52
  }
53
- getActive(agentId) {
53
+ getActive(agentId, appName) {
54
54
  if (!agentId)
55
55
  return null;
56
- const targetId = this.activeTargetIdByAgent.get(agentId);
57
- if (!targetId)
58
- return null;
59
- return this.sessions.get(targetId) || null;
56
+ const key = appName ? `${agentId}:app:${appName}` : agentId;
57
+ const targetId = this.activeTargetIdByAgent.get(key);
58
+ if (targetId && this.sessions.has(targetId)) {
59
+ return this.sessions.get(targetId) || null;
60
+ }
61
+ if (appName) {
62
+ const match = Array.from(this.sessions.values()).find(c => c.agentId === agentId && c.appName === appName);
63
+ if (match)
64
+ return match;
65
+ }
66
+ else {
67
+ const browserTargetId = this.activeTargetIdByAgent.get(agentId);
68
+ if (browserTargetId) {
69
+ const client = this.sessions.get(browserTargetId);
70
+ if (client && !client.isElectron)
71
+ return client;
72
+ }
73
+ }
74
+ return null;
60
75
  }
61
76
  getMostRecent(agentId) {
62
77
  const values = Array.from(this.sessions.values()).filter(c => c.agentId === agentId);
63
78
  return values.length > 0 ? values[values.length - 1] : null;
64
79
  }
65
- setActive(targetId, agentId) {
80
+ setActive(targetId, agentId, appName) {
66
81
  if (this.sessions.has(targetId)) {
67
- this.activeTargetIdByAgent.set(agentId, targetId);
82
+ const client = this.sessions.get(targetId);
83
+ const targetApp = appName || client?.appName;
84
+ const key = targetApp ? `${agentId}:app:${targetApp}` : agentId;
85
+ this.activeTargetIdByAgent.set(key, targetId);
86
+ }
87
+ }
88
+ lastActiveApp = null;
89
+ getLastActiveApp() {
90
+ return this.lastActiveApp;
91
+ }
92
+ setLastActiveApp(app) {
93
+ if (!app) {
94
+ this.lastActiveApp = null;
95
+ return;
96
+ }
97
+ const low = app.toLowerCase();
98
+ if (low !== "browser" && low !== "chrome" && low !== "chromium") {
99
+ this.lastActiveApp = low;
100
+ }
101
+ else {
102
+ this.lastActiveApp = null;
68
103
  }
69
104
  }
105
+ findClientByTargetId(targetId) {
106
+ return this.sessions.get(targetId) || null;
107
+ }
108
+ getActiveElectronClients() {
109
+ return Array.from(this.sessions.values()).filter(c => c.isElectron);
110
+ }
111
+ isBrowserSessionActive() {
112
+ return Array.from(this.sessions.values()).some(c => !c.isElectron);
113
+ }
70
114
  getAll() {
71
115
  return Array.from(this.sessions.values());
72
116
  }
@@ -77,8 +121,9 @@ export class SessionRegistry {
77
121
  this.sessions.set(targetId, client);
78
122
  this.heartbeatTimestamps.set(targetId, Date.now());
79
123
  this.startHeartbeat(targetId, client.session);
80
- if (!this.activeTargetIdByAgent.has(client.agentId)) {
81
- this.activeTargetIdByAgent.set(client.agentId, targetId);
124
+ const key = client.appName ? `${client.agentId}:app:${client.appName}` : client.agentId;
125
+ if (!this.activeTargetIdByAgent.has(key)) {
126
+ this.activeTargetIdByAgent.set(key, targetId);
82
127
  }
83
128
  }
84
129
  unregister(targetId) {
@@ -87,14 +132,15 @@ export class SessionRegistry {
87
132
  if (client) {
88
133
  client.session.close().catch(() => { });
89
134
  this.sessions.delete(targetId);
90
- const activeForAgent = this.activeTargetIdByAgent.get(client.agentId);
135
+ const key = client.appName ? `${client.agentId}:app:${client.appName}` : client.agentId;
136
+ const activeForAgent = this.activeTargetIdByAgent.get(key);
91
137
  if (activeForAgent === targetId) {
92
- const agentTabs = Array.from(this.sessions.values()).filter(c => c.agentId === client.agentId);
138
+ const agentTabs = Array.from(this.sessions.values()).filter(c => c.agentId === client.agentId && c.appName === client.appName);
93
139
  if (agentTabs.length > 0) {
94
- this.activeTargetIdByAgent.set(client.agentId, agentTabs[agentTabs.length - 1].targetId);
140
+ this.activeTargetIdByAgent.set(key, agentTabs[agentTabs.length - 1].targetId);
95
141
  }
96
142
  else {
97
- this.activeTargetIdByAgent.delete(client.agentId);
143
+ this.activeTargetIdByAgent.delete(key);
98
144
  }
99
145
  }
100
146
  }
@@ -131,6 +177,7 @@ export class SessionRegistry {
131
177
  // heartbeat failed — tab may be dead, will be caught by getHealth()
132
178
  }
133
179
  }, HEARTBEAT_INTERVAL_MS);
180
+ interval.unref();
134
181
  this.heartbeatIntervals.set(targetId, interval);
135
182
  }
136
183
  stopHeartbeat(targetId) {
@@ -1,4 +1,16 @@
1
- import type { BrowserEngine } from "../config/loader.js";
1
+ import { type BrowserEngine } from "../config/loader.js";
2
+ import { type DiscoveredElectronApp } from "../desktop/discovery.js";
3
+ export interface ConfiguredAppStatus {
4
+ name: string;
5
+ status: "connected" | "running_no_debug" | "offline";
6
+ configuredPath?: string;
7
+ activeTabs?: {
8
+ id: string;
9
+ title: string;
10
+ url: string;
11
+ }[];
12
+ port?: number;
13
+ }
2
14
  export interface BrowserDetectResult {
3
15
  available: {
4
16
  engine: BrowserEngine;
@@ -6,8 +18,10 @@ export interface BrowserDetectResult {
6
18
  path: string;
7
19
  }[];
8
20
  current: BrowserEngine;
21
+ configuredApps?: ConfiguredAppStatus[];
22
+ runningUnconfiguredApps?: DiscoveredElectronApp[];
9
23
  toolCoverage: Record<string, number>;
10
24
  unsupportedTools: Record<string, string[]>;
11
25
  }
12
- export declare function detectBrowsers(currentEngine: BrowserEngine): BrowserDetectResult;
26
+ export declare function detectBrowsers(currentEngine: BrowserEngine): Promise<BrowserDetectResult>;
13
27
  export declare function isToolSupported(engine: BrowserEngine, toolName: string): boolean;
@@ -1,4 +1,5 @@
1
1
  import { findFirefox, probeAllBrowsers } from "../cdp/bridge.js";
2
+ import { getCachedConfig, getElectronApps } from "../config/loader.js";
2
3
  // Phase 8c: Per-engine tool coverage map
3
4
  // Firefox CDP is ~80% feature-complete; WebKit is ~60%
4
5
  const ENGINE_COVERAGE = {
@@ -39,7 +40,11 @@ const ENGINE_COVERAGE = {
39
40
  ],
40
41
  },
41
42
  };
42
- export function detectBrowsers(currentEngine) {
43
+ import { sessionRegistry } from "../state/session.js";
44
+ import { getTabList } from "../cdp/connector.js";
45
+ import { getConfigAgentId } from "../config/loader.js";
46
+ import { discoverElectronApps } from "../desktop/discovery.js";
47
+ export async function detectBrowsers(currentEngine) {
43
48
  const available = [];
44
49
  // Detect Chromium-based browsers
45
50
  const allBrowsers = probeAllBrowsers();
@@ -51,19 +56,62 @@ export function detectBrowsers(currentEngine) {
51
56
  if (ff) {
52
57
  available.push({ engine: "firefox", name: ff.name, path: ff.path });
53
58
  }
54
- // WebKit detection is best-effort (Safari Technology Preview)
55
- // We do not auto-detect WebKit — only report if manually configured
56
59
  const toolCoverage = {};
57
- const unsupportedTools = {};
60
+ const unsupportedMap = {};
58
61
  for (const engine of ["chromium", "firefox", "webkit"]) {
59
62
  toolCoverage[engine] = ENGINE_COVERAGE[engine].coverage;
60
- unsupportedTools[engine] = [...ENGINE_COVERAGE[engine].unsupported];
63
+ unsupportedMap[engine] = [...ENGINE_COVERAGE[engine].unsupported];
64
+ }
65
+ const config = getCachedConfig();
66
+ const electronApps = getElectronApps(config);
67
+ const runningElectronApps = await discoverElectronApps().catch(() => []);
68
+ const configuredApps = [];
69
+ const runningUnconfiguredApps = [];
70
+ // Identify configured app statuses
71
+ for (const [appName, appCfg] of Object.entries(electronApps)) {
72
+ const clients = sessionRegistry.getAllForAgent(getConfigAgentId(config));
73
+ const activeClient = clients.find(c => c.isElectron && c.appName === appName);
74
+ let status = "offline";
75
+ let activeTabs = [];
76
+ let port;
77
+ if (activeClient) {
78
+ status = "connected";
79
+ port = activeClient.port;
80
+ try {
81
+ const tabs = await getTabList(getConfigAgentId(config), appName);
82
+ activeTabs = tabs.map(t => ({ id: t.id, title: t.title, url: t.url }));
83
+ }
84
+ catch { /* ignore tab list failure */ }
85
+ }
86
+ else {
87
+ const running = runningElectronApps.find(r => r.name.toLowerCase() === appName.toLowerCase());
88
+ if (running) {
89
+ status = running.status === "debug_port_open" ? "connected" : "running_no_debug";
90
+ port = running.port;
91
+ }
92
+ }
93
+ configuredApps.push({
94
+ name: appName,
95
+ status,
96
+ configuredPath: appCfg.path,
97
+ activeTabs: activeTabs.length > 0 ? activeTabs : undefined,
98
+ port
99
+ });
100
+ }
101
+ // Find running apps that are not configured
102
+ for (const running of runningElectronApps) {
103
+ const isConfigured = Object.keys(electronApps).some(n => n.toLowerCase() === running.name.toLowerCase());
104
+ if (!isConfigured) {
105
+ runningUnconfiguredApps.push(running);
106
+ }
61
107
  }
62
108
  return {
63
109
  available,
64
110
  current: currentEngine,
111
+ configuredApps: configuredApps.length > 0 ? configuredApps : undefined,
112
+ runningUnconfiguredApps: runningUnconfiguredApps.length > 0 ? runningUnconfiguredApps : undefined,
65
113
  toolCoverage,
66
- unsupportedTools,
114
+ unsupportedTools: unsupportedMap,
67
115
  };
68
116
  }
69
117
  export function isToolSupported(engine, toolName) {
@@ -5,16 +5,26 @@
5
5
  */
6
6
  import CDP from "chrome-remote-interface";
7
7
  import type { Config } from "../config/loader.js";
8
+ import { type TrailEntry } from "../intelligence/trail.js";
8
9
  export interface BurstStep {
9
- tool: "browser_click" | "browser_type" | "browser_go" | "browser_navigate" | "browser_key" | "browser_scroll" | "browser_wait" | "browser_map" | "browser_pointer" | "browser_eval" | "browser_select" | "browser_console" | "browser_find" | "browser_fill" | "browser_type_secret" | "browser_screenshot" | "browser_inspect" | "browser_storage" | "browser_cookies" | "browser_highlight" | "browser_scrape" | "browser_canvas" | "browser_performance" | "browser_snapshot" | "browser_pdf" | "browser_dialog" | "browser_upload" | "browser_viewport" | "browser_clipboard" | "browser_detect" | "browser_recording" | "browser_automation";
10
+ tool: string;
10
11
  args: Record<string, unknown>;
11
12
  }
12
- /** Macro step types — expanded server-side into multiple primitive calls. */
13
13
  export interface BurstMacroStep {
14
- tool: "type_and_send" | "navigate_and_read" | "click_and_wait" | "scroll_and_read" | "fill_form" | "drag_and_drop" | "hover_and_read";
14
+ tool: "type_and_send" | "navigate_and_read" | "click_and_wait" | "scroll_and_read" | "fill_form" | "drag_and_drop" | "hover_and_read" | string;
15
15
  args: Record<string, unknown>;
16
16
  }
17
17
  export type AnyBurstStep = BurstStep | BurstMacroStep;
18
+ export interface BurstOptions {
19
+ steps?: AnyBurstStep[];
20
+ actions?: AnyBurstStep[];
21
+ compile_from_history?: boolean;
22
+ history_limit?: number;
23
+ dry_run?: boolean;
24
+ stop_on_error?: boolean;
25
+ batch?: boolean;
26
+ app?: string;
27
+ }
18
28
  export interface BurstStepResult {
19
29
  step: number;
20
30
  tool: string;
@@ -28,8 +38,8 @@ export interface BurstResult {
28
38
  results: BurstStepResult[];
29
39
  aborted: boolean;
30
40
  abort_reason?: string;
41
+ compiled_steps?: AnyBurstStep[];
42
+ message?: string;
31
43
  }
32
- export declare function browserBurst(session: CDP.Client, args: {
33
- steps: AnyBurstStep[];
34
- batch?: boolean;
35
- }, config: Config): Promise<BurstResult>;
44
+ export declare function compileTraceToMacro(steps: TrailEntry[]): AnyBurstStep[];
45
+ export declare function browserBurst(session: CDP.Client, args: BurstOptions, config: Config): Promise<BurstResult>;