@aexol/spectral 0.9.257 → 0.9.259
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agents.d.ts +79 -3
- package/dist/agent/agents.d.ts.map +1 -1
- package/dist/agent/agents.js +109 -11
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +55 -13
- package/dist/extensions/browser/browser-service.d.ts +47 -0
- package/dist/extensions/browser/browser-service.d.ts.map +1 -1
- package/dist/extensions/browser/browser-service.js +245 -25
- package/dist/extensions/browser/index.d.ts +3 -3
- package/dist/extensions/browser/index.js +4 -4
- package/dist/extensions/browser/tools/core.d.ts.map +1 -1
- package/dist/extensions/browser/tools/core.js +118 -2
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +10 -0
- package/package.json +1 -1
package/dist/agent/agents.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export type AgentMode = "primary" | "subagent";
|
|
2
2
|
export interface AgentConfig {
|
|
3
|
+
/** Backend row id (cloud agents only — system agents have none). */
|
|
4
|
+
id?: string;
|
|
5
|
+
/** Parent PRIMARY agent id (cloud subagents only; null/undefined = root). */
|
|
6
|
+
parentId?: string | null;
|
|
3
7
|
name: string;
|
|
4
8
|
description: string;
|
|
5
9
|
mode: AgentMode;
|
|
@@ -33,9 +37,17 @@ export declare function refreshCloudAgents(opts: {
|
|
|
33
37
|
/**
|
|
34
38
|
* Discover all agents available to a session: built-in system agents
|
|
35
39
|
* (SYSTEM_AGENTS, read-only) plus cloud agents from the backend cache
|
|
36
|
-
* (refreshCloudAgents, managed in Aexol Studio).
|
|
37
|
-
*
|
|
38
|
-
*
|
|
40
|
+
* (refreshCloudAgents, managed in Aexol Studio).
|
|
41
|
+
*
|
|
42
|
+
* Discovery is name-agnostic: ALL agents are returned, including cloud
|
|
43
|
+
* agents that share a name with a system agent (subagent names are no
|
|
44
|
+
* longer globally unique per team). Name-collision precedence is applied
|
|
45
|
+
* at resolution time in `resolveAgentByName` (system agents win in
|
|
46
|
+
* team-wide mode; a primary's own subtree wins in scoped mode).
|
|
47
|
+
* Primaries remain unique per team by construction (the backend blocks
|
|
48
|
+
* reserved system names for cloud primaries).
|
|
49
|
+
* Never throws and never touches the network — offline / logged-out
|
|
50
|
+
* sessions simply get the system agents.
|
|
39
51
|
*/
|
|
40
52
|
export declare function discoverAgents(teamId?: string): AgentConfig[];
|
|
41
53
|
export declare function discoverPrimaryAgent(teamId?: string): AgentConfig | null;
|
|
@@ -49,6 +61,70 @@ export declare function discoverPrimaryAgent(teamId?: string): AgentConfig | nul
|
|
|
49
61
|
* silently injects an arbitrary primary agent.
|
|
50
62
|
*/
|
|
51
63
|
export declare function resolveSelectedPrimaryAgent(selectedName: string | null | undefined, teamId?: string): AgentConfig | null;
|
|
64
|
+
/**
|
|
65
|
+
* Minimal reference to an active PRIMARY agent used to anchor parent-aware
|
|
66
|
+
* subagent name resolution.
|
|
67
|
+
*/
|
|
68
|
+
export type AgentSubtreeRef = Pick<AgentConfig, "id" | "name">;
|
|
69
|
+
/**
|
|
70
|
+
* Filter a flat agent list down to the subtree of an active primary agent:
|
|
71
|
+
* the primary itself plus its direct children (subagents whose `parentId`
|
|
72
|
+
* points at it).
|
|
73
|
+
*
|
|
74
|
+
* Returns the list UNCHANGED when no primary ref is given — standalone
|
|
75
|
+
* sessions and system primaries (built-in ghost has no backend id) fall
|
|
76
|
+
* back to team-wide resolution.
|
|
77
|
+
*/
|
|
78
|
+
export declare function agentsInSubtree(agents: AgentConfig[], activePrimary?: AgentSubtreeRef | null): AgentConfig[];
|
|
79
|
+
/**
|
|
80
|
+
* The set of agents a session can actually reach:
|
|
81
|
+
*
|
|
82
|
+
* - Scoped (an `activePrimary` cloud primary): the primary's subtree plus
|
|
83
|
+
* the built-in system agents — a same-named cloud subagent in the
|
|
84
|
+
* subtree intentionally shadows the system agent, so system agents are
|
|
85
|
+
* only appended when their name is free in the subtree.
|
|
86
|
+
* - Team-wide (no active primary): system agents win over same-named
|
|
87
|
+
* cloud agents (dedupe keeps the system row, first).
|
|
88
|
+
*/
|
|
89
|
+
export declare function reachableAgents(agents: AgentConfig[], activePrimary?: AgentSubtreeRef | null): AgentConfig[];
|
|
90
|
+
/**
|
|
91
|
+
* Outcome of resolving an agent name against a discovered agent list,
|
|
92
|
+
* optionally scoped to an active primary's subtree.
|
|
93
|
+
*/
|
|
94
|
+
export type AgentResolution = {
|
|
95
|
+
status: "ok";
|
|
96
|
+
agent: AgentConfig;
|
|
97
|
+
} | {
|
|
98
|
+
status: "not-found";
|
|
99
|
+
name: string;
|
|
100
|
+
candidates: AgentConfig[];
|
|
101
|
+
} | {
|
|
102
|
+
status: "ambiguous";
|
|
103
|
+
name: string;
|
|
104
|
+
conflicting: AgentConfig[];
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Resolve an agent name to a single agent.
|
|
108
|
+
*
|
|
109
|
+
* - Team-wide (no `activePrimary`, e.g. the built-in ghost session):
|
|
110
|
+
* SYSTEM agents always win over same-named cloud agents, so a cloud
|
|
111
|
+
* subagent named "executor" can no longer hijack the system executor.
|
|
112
|
+
* Only when no system agent has that name does resolution fall through
|
|
113
|
+
* to the cloud first-match (historical behavior for team-wide
|
|
114
|
+
* duplicates — not an error in fallback mode).
|
|
115
|
+
* - Scoped (an `activePrimary` cloud primary with a backend id):
|
|
116
|
+
* candidates are scoped to the primary's subtree (the primary itself +
|
|
117
|
+
* its direct children, plus any other agent sharing the primary's name),
|
|
118
|
+
* so same-named subagents under different primaries resolve to the
|
|
119
|
+
* caller's own subtree. A same-named cloud subagent in the subtree
|
|
120
|
+
* intentionally shadows the system agent (that primary's own
|
|
121
|
+
* "executor"). If the name is NOT in the subtree, resolution falls back
|
|
122
|
+
* to SYSTEM agents (never to cloud agents of other trees). A name that
|
|
123
|
+
* matches multiple agents within the subtree (legacy data) is
|
|
124
|
+
* ambiguous — the caller must surface the conflict, never silently
|
|
125
|
+
* pick one.
|
|
126
|
+
*/
|
|
127
|
+
export declare function resolveAgentByName(name: string, agents: AgentConfig[], activePrimary?: AgentSubtreeRef | null): AgentResolution;
|
|
52
128
|
/**
|
|
53
129
|
* Prepend the primary agent's system prompt above the base system prompt.
|
|
54
130
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agent/agents.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAC/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6ED,eAAO,MAAM,aAAa,EAAE,WAAW,EAkDtC,CAAC;AAkBF,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAO3E;AACD;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,yEAAyE;IACzE,YAAY,EAAE,IAAI,CAAC;IACnB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG;IACF,YAAY,EAAE,KAAK,CAAC;IACpB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agent/agents.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/agents.js
CHANGED
|
@@ -155,6 +155,8 @@ export async function refreshCloudAgents(opts) {
|
|
|
155
155
|
agents: [
|
|
156
156
|
{ teamId },
|
|
157
157
|
{
|
|
158
|
+
id: true,
|
|
159
|
+
parentId: true,
|
|
158
160
|
name: true,
|
|
159
161
|
description: true,
|
|
160
162
|
systemPrompt: true,
|
|
@@ -172,6 +174,8 @@ export async function refreshCloudAgents(opts) {
|
|
|
172
174
|
typeof a.description === "string" &&
|
|
173
175
|
typeof a.systemPrompt === "string")
|
|
174
176
|
.map((a) => ({
|
|
177
|
+
id: typeof a.id === "string" ? a.id : undefined,
|
|
178
|
+
parentId: typeof a.parentId === "string" ? a.parentId : null,
|
|
175
179
|
name: a.name,
|
|
176
180
|
description: a.description,
|
|
177
181
|
mode: a.mode === "PRIMARY" ? "primary" : "subagent",
|
|
@@ -213,18 +217,21 @@ export async function refreshCloudAgents(opts) {
|
|
|
213
217
|
/**
|
|
214
218
|
* Discover all agents available to a session: built-in system agents
|
|
215
219
|
* (SYSTEM_AGENTS, read-only) plus cloud agents from the backend cache
|
|
216
|
-
* (refreshCloudAgents, managed in Aexol Studio).
|
|
217
|
-
*
|
|
218
|
-
*
|
|
220
|
+
* (refreshCloudAgents, managed in Aexol Studio).
|
|
221
|
+
*
|
|
222
|
+
* Discovery is name-agnostic: ALL agents are returned, including cloud
|
|
223
|
+
* agents that share a name with a system agent (subagent names are no
|
|
224
|
+
* longer globally unique per team). Name-collision precedence is applied
|
|
225
|
+
* at resolution time in `resolveAgentByName` (system agents win in
|
|
226
|
+
* team-wide mode; a primary's own subtree wins in scoped mode).
|
|
227
|
+
* Primaries remain unique per team by construction (the backend blocks
|
|
228
|
+
* reserved system names for cloud primaries).
|
|
229
|
+
* Never throws and never touches the network — offline / logged-out
|
|
230
|
+
* sessions simply get the system agents.
|
|
219
231
|
*/
|
|
220
232
|
export function discoverAgents(teamId) {
|
|
221
233
|
const cloudAgents = getCloudAgentCache(teamId ?? LEGACY_CLOUD_TEAM).agents;
|
|
222
|
-
|
|
223
|
-
for (const agent of SYSTEM_AGENTS)
|
|
224
|
-
agentMap.set(agent.name, agent);
|
|
225
|
-
for (const agent of cloudAgents)
|
|
226
|
-
agentMap.set(agent.name, agent);
|
|
227
|
-
return Array.from(agentMap.values());
|
|
234
|
+
return [...SYSTEM_AGENTS, ...cloudAgents];
|
|
228
235
|
}
|
|
229
236
|
export function discoverPrimaryAgent(teamId) {
|
|
230
237
|
const agents = discoverAgents(teamId);
|
|
@@ -232,8 +239,9 @@ export function discoverPrimaryAgent(teamId) {
|
|
|
232
239
|
if (primaries.length === 0)
|
|
233
240
|
return null;
|
|
234
241
|
if (primaries.length > 1) {
|
|
235
|
-
//
|
|
236
|
-
//
|
|
242
|
+
// A cloud primary always wins over the built-in ghost. (Cloud
|
|
243
|
+
// primaries cannot use reserved system names — the backend blocks
|
|
244
|
+
// it — so this only fires for a genuine primary-selection override.)
|
|
237
245
|
const cloudPrimary = primaries.find((a) => a.source === "cloud");
|
|
238
246
|
const chosen = cloudPrimary ?? primaries[0];
|
|
239
247
|
console.warn(chalk.yellow(`Multiple primary agents found (${primaries.map((a) => a.name).join(", ")}). Using "${chosen.name}".`));
|
|
@@ -261,6 +269,96 @@ export function resolveSelectedPrimaryAgent(selectedName, teamId) {
|
|
|
261
269
|
const agents = discoverAgents(teamId);
|
|
262
270
|
return (agents.find((agent) => agent.mode === "primary" && agent.name === selectedName) ?? null);
|
|
263
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Filter a flat agent list down to the subtree of an active primary agent:
|
|
274
|
+
* the primary itself plus its direct children (subagents whose `parentId`
|
|
275
|
+
* points at it).
|
|
276
|
+
*
|
|
277
|
+
* Returns the list UNCHANGED when no primary ref is given — standalone
|
|
278
|
+
* sessions and system primaries (built-in ghost has no backend id) fall
|
|
279
|
+
* back to team-wide resolution.
|
|
280
|
+
*/
|
|
281
|
+
export function agentsInSubtree(agents, activePrimary) {
|
|
282
|
+
if (!activePrimary)
|
|
283
|
+
return agents;
|
|
284
|
+
return agents.filter((a) => a.name === activePrimary.name ||
|
|
285
|
+
(activePrimary.id !== undefined &&
|
|
286
|
+
(a.id === activePrimary.id || a.parentId === activePrimary.id)));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The set of agents a session can actually reach:
|
|
290
|
+
*
|
|
291
|
+
* - Scoped (an `activePrimary` cloud primary): the primary's subtree plus
|
|
292
|
+
* the built-in system agents — a same-named cloud subagent in the
|
|
293
|
+
* subtree intentionally shadows the system agent, so system agents are
|
|
294
|
+
* only appended when their name is free in the subtree.
|
|
295
|
+
* - Team-wide (no active primary): system agents win over same-named
|
|
296
|
+
* cloud agents (dedupe keeps the system row, first).
|
|
297
|
+
*/
|
|
298
|
+
export function reachableAgents(agents, activePrimary) {
|
|
299
|
+
if (!activePrimary) {
|
|
300
|
+
const systemAgents = agents.filter((a) => a.source === "system");
|
|
301
|
+
const systemNames = new Set(systemAgents.map((a) => a.name));
|
|
302
|
+
return [
|
|
303
|
+
...systemAgents,
|
|
304
|
+
...agents.filter((a) => a.source !== "system" && !systemNames.has(a.name)),
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
const subtree = agentsInSubtree(agents, activePrimary);
|
|
308
|
+
const subtreeNames = new Set(subtree.map((a) => a.name));
|
|
309
|
+
return [
|
|
310
|
+
...subtree,
|
|
311
|
+
...agents.filter((a) => a.source === "system" && !subtreeNames.has(a.name)),
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Resolve an agent name to a single agent.
|
|
316
|
+
*
|
|
317
|
+
* - Team-wide (no `activePrimary`, e.g. the built-in ghost session):
|
|
318
|
+
* SYSTEM agents always win over same-named cloud agents, so a cloud
|
|
319
|
+
* subagent named "executor" can no longer hijack the system executor.
|
|
320
|
+
* Only when no system agent has that name does resolution fall through
|
|
321
|
+
* to the cloud first-match (historical behavior for team-wide
|
|
322
|
+
* duplicates — not an error in fallback mode).
|
|
323
|
+
* - Scoped (an `activePrimary` cloud primary with a backend id):
|
|
324
|
+
* candidates are scoped to the primary's subtree (the primary itself +
|
|
325
|
+
* its direct children, plus any other agent sharing the primary's name),
|
|
326
|
+
* so same-named subagents under different primaries resolve to the
|
|
327
|
+
* caller's own subtree. A same-named cloud subagent in the subtree
|
|
328
|
+
* intentionally shadows the system agent (that primary's own
|
|
329
|
+
* "executor"). If the name is NOT in the subtree, resolution falls back
|
|
330
|
+
* to SYSTEM agents (never to cloud agents of other trees). A name that
|
|
331
|
+
* matches multiple agents within the subtree (legacy data) is
|
|
332
|
+
* ambiguous — the caller must surface the conflict, never silently
|
|
333
|
+
* pick one.
|
|
334
|
+
*/
|
|
335
|
+
export function resolveAgentByName(name, agents, activePrimary) {
|
|
336
|
+
const scope = agentsInSubtree(agents, activePrimary);
|
|
337
|
+
const matches = scope.filter((a) => a.name === name);
|
|
338
|
+
if (matches.length === 1)
|
|
339
|
+
return { status: "ok", agent: matches[0] };
|
|
340
|
+
if (matches.length > 1 && !activePrimary) {
|
|
341
|
+
// Team-wide: a system agent always beats same-named cloud agents;
|
|
342
|
+
// otherwise keep the historical first-match. Duplicates across the
|
|
343
|
+
// team are only an error inside a scoped subtree.
|
|
344
|
+
const systemMatch = matches.find((a) => a.source === "system");
|
|
345
|
+
return { status: "ok", agent: systemMatch ?? matches[0] };
|
|
346
|
+
}
|
|
347
|
+
if (matches.length > 1)
|
|
348
|
+
return { status: "ambiguous", name, conflicting: matches };
|
|
349
|
+
if (!activePrimary)
|
|
350
|
+
return { status: "not-found", name, candidates: scope };
|
|
351
|
+
// Scoped: no match in the subtree — fall back to built-in system
|
|
352
|
+
// agents (never to cloud agents of other trees).
|
|
353
|
+
const systemMatch = agents.find((a) => a.source === "system" && a.name === name);
|
|
354
|
+
if (systemMatch)
|
|
355
|
+
return { status: "ok", agent: systemMatch };
|
|
356
|
+
const candidates = [
|
|
357
|
+
...scope,
|
|
358
|
+
...agents.filter((a) => a.source === "system"),
|
|
359
|
+
];
|
|
360
|
+
return { status: "not-found", name, candidates };
|
|
361
|
+
}
|
|
264
362
|
/**
|
|
265
363
|
* Prepend the primary agent's system prompt above the base system prompt.
|
|
266
364
|
*
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -95,5 +95,13 @@ export default function subagentExtension(ext: ExtensionAPI, opts?: {
|
|
|
95
95
|
disabledAgents?: string[];
|
|
96
96
|
emitSubagentEvent?: (event: ServerEvent) => void;
|
|
97
97
|
teamId?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the session's active primary-agent name on each tool call
|
|
100
|
+
* (mirrors the bridge's per-turn primary-injection logic). The
|
|
101
|
+
* subagent uses it to scope name resolution to the primary's
|
|
102
|
+
* subtree so same-named subagents under different primaries resolve
|
|
103
|
+
* to the caller's own subtree. Returns null when no primary applies.
|
|
104
|
+
*/
|
|
105
|
+
resolveActivePrimaryName?: () => string | null | undefined;
|
|
98
106
|
}): void;
|
|
99
107
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAEX,UAAU,EAEV,SAAS,EAET,MAAM,4BAA4B,CAAC;AAoBpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAKrB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACN,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAEX,UAAU,EAEV,SAAS,EAET,MAAM,4BAA4B,CAAC;AAoBpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAKrB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACN,KAAK,WAAW,EAQhB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,uCAAuC,CAAC;AAS/C,OAAO,KAAK,EAAE,sBAAsB,EAAgB,MAAM,6CAA6C,CAAC;AAyGxG;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GACpF,MAAM,CAYR;AAyND;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,sBAAsB,EAAE,WAAW,GAAG,cAAc,CAAC,CAe5D;AAgCD,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,EACrB,SAAS,CAAC,EAAE,MAAM,GAChB,SAAS,EAAE,CAkDb;AAyBD;;;;;;;;;;GAUG;AACH;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,6EAA6E;IAC7E,GAAG,EAAE,cAAc,CAAC;CACpB;AAED,8EAA8E;AAC9E,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,wBAAwB,CAAC;AAqF1E,2FAA2F;AAC3F,wBAAgB,+BAA+B,CAC9C,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,IAAI,CAEN;AAmBD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC9D,MAAM,CAiBR;AAwnBD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IACN,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,wBAAwB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC3D,GACC,IAAI,CAwbN"}
|
package/dist/agent/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import { RETRY_DEFAULT_BASE_DELAY_MS, RETRY_DEFAULT_MAX_RETRIES, RETRY_MAX_BACKO
|
|
|
37
37
|
import { mutateSystemPrompt } from "../sdk/coding-agent/core/system-prompt-mutator.js";
|
|
38
38
|
import { sleep } from "../sdk/coding-agent/utils/sleep.js";
|
|
39
39
|
import { Type } from "typebox";
|
|
40
|
-
import { discoverAgents, agentMetadataBlock, } from "./agents.js";
|
|
40
|
+
import { discoverAgents, agentMetadataBlock, resolveAgentByName, reachableAgents, resolveSelectedPrimaryAgent, } from "./agents.js";
|
|
41
41
|
import { loadAgentSettings } from "../server/handlers/agent-settings.js";
|
|
42
42
|
import { buildTpsUsageFields, computeTpsMetrics, } from "../server/agent-bridge/tps-metrics.js";
|
|
43
43
|
import { randomUUID } from "node:crypto";
|
|
@@ -388,7 +388,7 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
|
|
|
388
388
|
await Promise.all(workers);
|
|
389
389
|
return results;
|
|
390
390
|
}
|
|
391
|
-
async function runSingleAgentWithTimeout(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries) {
|
|
391
|
+
async function runSingleAgentWithTimeout(defaultCwd, agents, activePrimary, agentName, task, cwd, step, signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries) {
|
|
392
392
|
const timeoutMs = SUBAGENT_TIMEOUT_MS;
|
|
393
393
|
const timeoutSeconds = timeoutMs / 1000;
|
|
394
394
|
const controller = new AbortController();
|
|
@@ -402,7 +402,7 @@ async function runSingleAgentWithTimeout(defaultCwd, agents, agentName, task, cw
|
|
|
402
402
|
signal.addEventListener("abort", onParentAbort, { once: true });
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
|
-
const result = await runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, controller.signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries);
|
|
405
|
+
const result = await runSingleAgent(defaultCwd, agents, activePrimary, agentName, task, cwd, step, controller.signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries);
|
|
406
406
|
// If the timeout fired (and parent signal did not), surface a clean
|
|
407
407
|
// timeout error result rather than a bare "aborted" result.
|
|
408
408
|
if (controller.signal.aborted &&
|
|
@@ -479,12 +479,36 @@ export function resolveSubagentMaxAttempts(explicit, settings) {
|
|
|
479
479
|
// ---------------------------------------------------------------------------
|
|
480
480
|
// Core — run a single subagent in-process via agentLoop
|
|
481
481
|
// ---------------------------------------------------------------------------
|
|
482
|
-
async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries) {
|
|
482
|
+
async function runSingleAgent(defaultCwd, agents, activePrimary, agentName, task, cwd, step, signal, onUpdate, makeDetails, parentToolCallId, onInternalEvent, ext, ctx, maxRetries) {
|
|
483
483
|
const normalizedAgentName = agentName.replace(/^@/, "");
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
// Parent-aware resolution: with an active cloud primary, candidates are
|
|
485
|
+
// scoped to its subtree (the primary + its direct children), so
|
|
486
|
+
// same-named subagents under different primaries resolve to the
|
|
487
|
+
// caller's own subtree. Without one, this is the historical team-wide
|
|
488
|
+
// first-match. Ambiguity (legacy duplicate names in-scope) is surfaced,
|
|
489
|
+
// never silently resolved.
|
|
490
|
+
const resolution = resolveAgentByName(normalizedAgentName, agents, activePrimary);
|
|
491
|
+
if (resolution.status === "ambiguous") {
|
|
492
|
+
const conflicts = resolution.conflicting
|
|
493
|
+
.map((a) => `${a.name} (id: ${a.id ?? a.filePath})`)
|
|
494
|
+
.join(", ");
|
|
495
|
+
return {
|
|
496
|
+
agent: agentName,
|
|
497
|
+
agentSource: "unknown",
|
|
498
|
+
task,
|
|
499
|
+
exitCode: 1,
|
|
500
|
+
messages: [],
|
|
501
|
+
stderr: `Ambiguous agent name "${agentName}": multiple agents share this name in scope — ${conflicts}. Disambiguate by picking a unique name.`,
|
|
502
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
503
|
+
step,
|
|
504
|
+
errorMessage: `Ambiguous agent name: ${agentName}`,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (resolution.status === "not-found") {
|
|
508
|
+
const available = resolution.candidates
|
|
509
|
+
.map((a) => `${a.name} (${a.source})`)
|
|
510
|
+
.join(", ") || "none";
|
|
511
|
+
const suggestion = suggestAgentName(normalizedAgentName, resolution.candidates);
|
|
488
512
|
return {
|
|
489
513
|
agent: agentName,
|
|
490
514
|
agentSource: "unknown",
|
|
@@ -496,6 +520,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
496
520
|
step,
|
|
497
521
|
};
|
|
498
522
|
}
|
|
523
|
+
const agent = resolution.agent;
|
|
499
524
|
// Build combined system prompt without leaking an injected primary-agent
|
|
500
525
|
// block into the isolated subagent context.
|
|
501
526
|
const combinedPrompt = buildSubagentSystemPrompt(ctx.getBaseSystemPrompt(), agent);
|
|
@@ -1012,6 +1037,23 @@ export default function subagentExtension(ext, opts) {
|
|
|
1012
1037
|
...(liveSettings.disabledSubagents ?? []),
|
|
1013
1038
|
]);
|
|
1014
1039
|
const agents = discoverAgents(opts?.teamId).filter((a) => !disabledSet.has(a.name));
|
|
1040
|
+
// Parent-aware resolution scope: when the session has an active
|
|
1041
|
+
// CLOUD primary (one with a backend id), name resolution is scoped
|
|
1042
|
+
// to that primary's subtree. System primaries (built-in ghost) have
|
|
1043
|
+
// no id and anchor no subtree — team-wide resolution applies.
|
|
1044
|
+
const activePrimaryName = opts?.resolveActivePrimaryName?.() ?? null;
|
|
1045
|
+
const activePrimaryRef = activePrimaryName
|
|
1046
|
+
? (resolveSelectedPrimaryAgent(activePrimaryName, opts?.teamId) ?? null)
|
|
1047
|
+
: null;
|
|
1048
|
+
const subtree = activePrimaryRef?.id
|
|
1049
|
+
? activePrimaryRef
|
|
1050
|
+
: null;
|
|
1051
|
+
// Agent listings in error messages reflect the reachable set —
|
|
1052
|
+
// subtree + system agents in scoped mode (a session under a
|
|
1053
|
+
// cloud primary can still delegate to built-in system agents),
|
|
1054
|
+
// system-first dedupe in team-wide mode — so the model only sees
|
|
1055
|
+
// what it can actually resolve.
|
|
1056
|
+
const visibleAgents = reachableAgents(agents, subtree);
|
|
1015
1057
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
1016
1058
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
1017
1059
|
const hasSingle = Boolean(params.agent && params.task);
|
|
@@ -1092,7 +1134,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1092
1134
|
}
|
|
1093
1135
|
: undefined;
|
|
1094
1136
|
if (modeCount !== 1) {
|
|
1095
|
-
const available =
|
|
1137
|
+
const available = visibleAgents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
1096
1138
|
return {
|
|
1097
1139
|
content: [
|
|
1098
1140
|
{
|
|
@@ -1132,7 +1174,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1132
1174
|
}
|
|
1133
1175
|
}
|
|
1134
1176
|
: undefined;
|
|
1135
|
-
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, signal, chainUpdate, makeDetails("chain"), _toolCallId, emitSubagentToolProgress, ext, ctx, params.maxRetries);
|
|
1177
|
+
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, subtree, step.agent, taskWithContext, step.cwd, i + 1, signal, chainUpdate, makeDetails("chain"), _toolCallId, emitSubagentToolProgress, ext, ctx, params.maxRetries);
|
|
1136
1178
|
results.push(result);
|
|
1137
1179
|
const isError = result.exitCode !== 0 ||
|
|
1138
1180
|
result.stopReason === "error" ||
|
|
@@ -1214,7 +1256,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1214
1256
|
}
|
|
1215
1257
|
};
|
|
1216
1258
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
1217
|
-
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
1259
|
+
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, subtree, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
1218
1260
|
if (partial.details?.results[0]) {
|
|
1219
1261
|
allResults[index] = partial.details.results[0];
|
|
1220
1262
|
emitParallelUpdate();
|
|
@@ -1254,7 +1296,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1254
1296
|
}
|
|
1255
1297
|
// ── Single mode ─────────────────────────────────────────────
|
|
1256
1298
|
if (params.agent && params.task) {
|
|
1257
|
-
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, signal, onUpdate, makeDetails("single"), _toolCallId, emitSubagentToolProgress, ext, ctx, params.maxRetries);
|
|
1299
|
+
const result = await runSingleAgentWithTimeout(ctx.cwd, agents, subtree, params.agent, params.task, params.cwd, undefined, signal, onUpdate, makeDetails("single"), _toolCallId, emitSubagentToolProgress, ext, ctx, params.maxRetries);
|
|
1258
1300
|
const isError = result.exitCode !== 0 ||
|
|
1259
1301
|
result.stopReason === "error" ||
|
|
1260
1302
|
result.stopReason === "aborted";
|
|
@@ -1284,7 +1326,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
1284
1326
|
details: persistDetails(makeDetails("single")([result])),
|
|
1285
1327
|
};
|
|
1286
1328
|
}
|
|
1287
|
-
const available =
|
|
1329
|
+
const available = visibleAgents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
1288
1330
|
return {
|
|
1289
1331
|
content: [
|
|
1290
1332
|
{
|
|
@@ -3,6 +3,10 @@ import type { BrowserConfig } from "./config.js";
|
|
|
3
3
|
export interface PlaywrightLaunchers {
|
|
4
4
|
chromium: {
|
|
5
5
|
launch(opts: Record<string, unknown>): Promise<Browser>;
|
|
6
|
+
connectOverCDP?(opts: {
|
|
7
|
+
endpointURL: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}): Promise<Browser>;
|
|
6
10
|
};
|
|
7
11
|
firefox: {
|
|
8
12
|
launch(opts: Record<string, unknown>): Promise<Browser>;
|
|
@@ -11,6 +15,16 @@ export interface PlaywrightLaunchers {
|
|
|
11
15
|
launch(opts: Record<string, unknown>): Promise<Browser>;
|
|
12
16
|
};
|
|
13
17
|
}
|
|
18
|
+
/** Default CDP endpoint for `browser_attach` (Chrome/Arc `--remote-debugging-port=9222`). */
|
|
19
|
+
export declare const DEFAULT_CDP_ENDPOINT = "http://127.0.0.1:9222";
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a user-provided CDP endpoint:
|
|
22
|
+
* - undefined/"" → DEFAULT_CDP_ENDPOINT
|
|
23
|
+
* - bare port ("9222") → http://127.0.0.1:9222
|
|
24
|
+
* - host:port, no scheme → http://host:port
|
|
25
|
+
* - full URL → as-is
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeCdpEndpoint(input?: string): string;
|
|
14
28
|
export interface NetworkRequest {
|
|
15
29
|
index: number;
|
|
16
30
|
url: string;
|
|
@@ -36,6 +50,10 @@ export declare class BrowserService {
|
|
|
36
50
|
private _recording;
|
|
37
51
|
private closePromise;
|
|
38
52
|
private launchLock;
|
|
53
|
+
/** CDP attach mode: connected to the user's own browser, not a launch we own. */
|
|
54
|
+
private cdpMode;
|
|
55
|
+
/** Normalized CDP endpoint of the attached browser (cdpMode only). */
|
|
56
|
+
private cdpEndpoint;
|
|
39
57
|
/** Unique marker identifying the process tree of the current launch. */
|
|
40
58
|
private launchToken;
|
|
41
59
|
/**
|
|
@@ -107,6 +125,35 @@ export declare class BrowserService {
|
|
|
107
125
|
selectTab(index: number): Promise<{
|
|
108
126
|
index: number;
|
|
109
127
|
}>;
|
|
128
|
+
/** Wire a page into the service's buffers (console, network, dialogs). */
|
|
129
|
+
private wirePage;
|
|
130
|
+
/**
|
|
131
|
+
* CDP attach guard: if we are attached and the CDP connection dropped
|
|
132
|
+
* (user closed the browser, debugging port went away), reset to default
|
|
133
|
+
* mode so the next call auto-launches the built-in browser.
|
|
134
|
+
*/
|
|
135
|
+
private checkCdpConnection;
|
|
136
|
+
/** True when attached to the user's own browser via CDP (and still connected). */
|
|
137
|
+
isAttached(): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Attach to the user's own browser over CDP (e.g. Arc/Chrome launched with
|
|
140
|
+
* `--remote-debugging-port`). Idempotent: re-attaching while already
|
|
141
|
+
* attached returns the current state without reconnecting. The attached
|
|
142
|
+
* browser is NOT ours: we never launch it, never kill it, and
|
|
143
|
+
* `close()`/`detachCdp()` only disconnect the Playwright client.
|
|
144
|
+
*/
|
|
145
|
+
attachOverCdp(endpoint?: string): Promise<{
|
|
146
|
+
endpoint: string;
|
|
147
|
+
tabCount: number;
|
|
148
|
+
titles: string[];
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* Detach from the attached CDP browser. In CDP mode `browser.close()` only
|
|
152
|
+
* disconnects the Playwright client — the user's browser keeps running.
|
|
153
|
+
* Subsequent calls auto-launch the built-in browser as usual. Returns
|
|
154
|
+
* `true` if a CDP connection was detached.
|
|
155
|
+
*/
|
|
156
|
+
detachCdp(): Promise<boolean>;
|
|
110
157
|
private installDialogInterceptor;
|
|
111
158
|
getPendingDialogs(): Array<{
|
|
112
159
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/browser-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0MjD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"browser-service.d.ts","sourceRoot":"","sources":["../../../src/extensions/browser/browser-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -205,6 +205,25 @@ function launchArgsFor(browserName, token) {
|
|
|
205
205
|
return base;
|
|
206
206
|
return [`${BROWSER_MARKER}${token}`, ...base];
|
|
207
207
|
}
|
|
208
|
+
/** Default CDP endpoint for `browser_attach` (Chrome/Arc `--remote-debugging-port=9222`). */
|
|
209
|
+
export const DEFAULT_CDP_ENDPOINT = "http://127.0.0.1:9222";
|
|
210
|
+
/**
|
|
211
|
+
* Normalize a user-provided CDP endpoint:
|
|
212
|
+
* - undefined/"" → DEFAULT_CDP_ENDPOINT
|
|
213
|
+
* - bare port ("9222") → http://127.0.0.1:9222
|
|
214
|
+
* - host:port, no scheme → http://host:port
|
|
215
|
+
* - full URL → as-is
|
|
216
|
+
*/
|
|
217
|
+
export function normalizeCdpEndpoint(input) {
|
|
218
|
+
const raw = (input ?? "").trim();
|
|
219
|
+
if (!raw)
|
|
220
|
+
return DEFAULT_CDP_ENDPOINT;
|
|
221
|
+
if (/^\d{1,5}$/.test(raw))
|
|
222
|
+
return `http://127.0.0.1:${raw}`;
|
|
223
|
+
if (!/^https?:\/\//i.test(raw))
|
|
224
|
+
return `http://${raw}`;
|
|
225
|
+
return raw;
|
|
226
|
+
}
|
|
208
227
|
let _pw = null;
|
|
209
228
|
let _pwError = null;
|
|
210
229
|
let _preflightResult = null;
|
|
@@ -333,6 +352,10 @@ export class BrowserService {
|
|
|
333
352
|
_recording = null;
|
|
334
353
|
closePromise = null;
|
|
335
354
|
launchLock = null;
|
|
355
|
+
/** CDP attach mode: connected to the user's own browser, not a launch we own. */
|
|
356
|
+
cdpMode = false;
|
|
357
|
+
/** Normalized CDP endpoint of the attached browser (cdpMode only). */
|
|
358
|
+
cdpEndpoint = null;
|
|
336
359
|
/** Unique marker identifying the process tree of the current launch. */
|
|
337
360
|
launchToken = null;
|
|
338
361
|
/**
|
|
@@ -479,6 +502,7 @@ export class BrowserService {
|
|
|
479
502
|
}
|
|
480
503
|
}
|
|
481
504
|
async ensureBrowser() {
|
|
505
|
+
this.checkCdpConnection();
|
|
482
506
|
// Serialize launches — if a launch is in progress, wait for it
|
|
483
507
|
if (this.launchLock) {
|
|
484
508
|
await this.launchLock;
|
|
@@ -517,20 +541,9 @@ export class BrowserService {
|
|
|
517
541
|
this.context = await this.browser.newContext({
|
|
518
542
|
viewport: this.config.viewport,
|
|
519
543
|
});
|
|
520
|
-
this.context.on("page", (page) =>
|
|
521
|
-
page.on("console", (msg) => {
|
|
522
|
-
this.consoleBuffer.push({
|
|
523
|
-
type: msg.type(),
|
|
524
|
-
text: msg.text(),
|
|
525
|
-
pageUrl: page.url(),
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
this.trackNetworkRequest(page);
|
|
529
|
-
this.installDialogInterceptor(page);
|
|
530
|
-
});
|
|
544
|
+
this.context.on("page", (page) => this.wirePage(page));
|
|
531
545
|
const page = await this.context.newPage();
|
|
532
|
-
this.
|
|
533
|
-
this.installDialogInterceptor(page);
|
|
546
|
+
this.wirePage(page);
|
|
534
547
|
this.pages = [page];
|
|
535
548
|
this.activePageIndex = 0;
|
|
536
549
|
};
|
|
@@ -544,16 +557,19 @@ export class BrowserService {
|
|
|
544
557
|
}
|
|
545
558
|
}
|
|
546
559
|
getBrowser() {
|
|
560
|
+
this.checkCdpConnection();
|
|
547
561
|
if (!this.browser?.isConnected())
|
|
548
562
|
throw new Error("Browser is not connected.");
|
|
549
563
|
return this.browser;
|
|
550
564
|
}
|
|
551
565
|
getActiveContext() {
|
|
566
|
+
this.checkCdpConnection();
|
|
552
567
|
if (!this.context)
|
|
553
568
|
throw new Error("No active context.");
|
|
554
569
|
return this.context;
|
|
555
570
|
}
|
|
556
571
|
getActivePage() {
|
|
572
|
+
this.checkCdpConnection();
|
|
557
573
|
if (this.pages.length === 0)
|
|
558
574
|
throw new Error("No active page.");
|
|
559
575
|
if (this.activePageIndex >= this.pages.length)
|
|
@@ -575,6 +591,7 @@ export class BrowserService {
|
|
|
575
591
|
return { url: page.url(), title: await page.title() };
|
|
576
592
|
}
|
|
577
593
|
async closePage() {
|
|
594
|
+
this.checkCdpConnection();
|
|
578
595
|
const openBlankPage = async () => {
|
|
579
596
|
if (!this.context) {
|
|
580
597
|
this.pages = [];
|
|
@@ -628,11 +645,17 @@ export class BrowserService {
|
|
|
628
645
|
this.config.viewport = { width, height };
|
|
629
646
|
}
|
|
630
647
|
listTabs() {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
648
|
+
this.checkCdpConnection();
|
|
649
|
+
return this.pages.map((p, i) => {
|
|
650
|
+
const url = p.url();
|
|
651
|
+
return {
|
|
652
|
+
index: i,
|
|
653
|
+
// `page.title()` is async, so the URL is the best synchronous
|
|
654
|
+
// stand-in; fall back to "Tab N" only when the page has no URL.
|
|
655
|
+
title: url || `Tab ${i}`,
|
|
656
|
+
url,
|
|
657
|
+
};
|
|
658
|
+
});
|
|
636
659
|
}
|
|
637
660
|
async createTab() {
|
|
638
661
|
if (!this.context)
|
|
@@ -665,6 +688,177 @@ export class BrowserService {
|
|
|
665
688
|
await page.bringToFront();
|
|
666
689
|
return { index };
|
|
667
690
|
}
|
|
691
|
+
/** Wire a page into the service's buffers (console, network, dialogs). */
|
|
692
|
+
wirePage(page) {
|
|
693
|
+
page.on("console", (msg) => {
|
|
694
|
+
this.consoleBuffer.push({
|
|
695
|
+
type: msg.type(),
|
|
696
|
+
text: msg.text(),
|
|
697
|
+
pageUrl: page.url(),
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
this.trackNetworkRequest(page);
|
|
701
|
+
this.installDialogInterceptor(page);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* CDP attach guard: if we are attached and the CDP connection dropped
|
|
705
|
+
* (user closed the browser, debugging port went away), reset to default
|
|
706
|
+
* mode so the next call auto-launches the built-in browser.
|
|
707
|
+
*/
|
|
708
|
+
checkCdpConnection() {
|
|
709
|
+
if (!this.cdpMode)
|
|
710
|
+
return;
|
|
711
|
+
if (isBrowserConnected(this.browser)) {
|
|
712
|
+
// Prune pages the user closed: a dead Page would surface as
|
|
713
|
+
// "Target closed" on the next tool call.
|
|
714
|
+
const live = this.pages.filter((p) => !p.isClosed());
|
|
715
|
+
if (live.length !== this.pages.length) {
|
|
716
|
+
this.pages = live;
|
|
717
|
+
if (this.activePageIndex >= this.pages.length)
|
|
718
|
+
this.activePageIndex = 0;
|
|
719
|
+
}
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
this.cdpMode = false;
|
|
723
|
+
this.cdpEndpoint = null;
|
|
724
|
+
this.browser = null;
|
|
725
|
+
this.context = null;
|
|
726
|
+
this.pages = [];
|
|
727
|
+
this.activePageIndex = 0;
|
|
728
|
+
this._recording = null;
|
|
729
|
+
releaseProcessCleanup(this);
|
|
730
|
+
throw new Error("CDP connection lost — call browser_attach again.");
|
|
731
|
+
}
|
|
732
|
+
/** True when attached to the user's own browser via CDP (and still connected). */
|
|
733
|
+
isAttached() {
|
|
734
|
+
return this.cdpMode && isBrowserConnected(this.browser);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Attach to the user's own browser over CDP (e.g. Arc/Chrome launched with
|
|
738
|
+
* `--remote-debugging-port`). Idempotent: re-attaching while already
|
|
739
|
+
* attached returns the current state without reconnecting. The attached
|
|
740
|
+
* browser is NOT ours: we never launch it, never kill it, and
|
|
741
|
+
* `close()`/`detachCdp()` only disconnect the Playwright client.
|
|
742
|
+
*/
|
|
743
|
+
async attachOverCdp(endpoint) {
|
|
744
|
+
const normalized = normalizeCdpEndpoint(endpoint);
|
|
745
|
+
// Serialize with an in-flight launch: an attach that tears down the
|
|
746
|
+
// browser mid-launch would leak it (pid bookkeeping and cleanup
|
|
747
|
+
// handlers would be released for a browser that is about to die).
|
|
748
|
+
if (this.launchLock)
|
|
749
|
+
await this.launchLock;
|
|
750
|
+
// Idempotent: already attached to a live CDP browser → current state.
|
|
751
|
+
if (this.cdpMode && isBrowserConnected(this.browser)) {
|
|
752
|
+
const titles = await Promise.all(this.pages.slice(0, 5).map(async (p) => {
|
|
753
|
+
try {
|
|
754
|
+
return await p.title();
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
return "";
|
|
758
|
+
}
|
|
759
|
+
}));
|
|
760
|
+
return { endpoint: this.cdpEndpoint ?? normalized, tabCount: this.pages.length, titles };
|
|
761
|
+
}
|
|
762
|
+
// Reachability probe: short-timeout GET to /json/version so a wrong
|
|
763
|
+
// endpoint fails fast with an actionable message.
|
|
764
|
+
const versionUrl = `${normalized.replace(/\/+$/, "")}/json/version`;
|
|
765
|
+
let reachable = false;
|
|
766
|
+
try {
|
|
767
|
+
const res = await fetch(versionUrl, { signal: AbortSignal.timeout(3_000) });
|
|
768
|
+
reachable = res.ok;
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
reachable = false;
|
|
772
|
+
}
|
|
773
|
+
if (!reachable) {
|
|
774
|
+
throw new Error(`Cannot reach the CDP endpoint at ${normalized} (GET ${versionUrl} failed). ` +
|
|
775
|
+
`Launch your browser with remote debugging first, e.g.: ` +
|
|
776
|
+
`open -a Arc --args --remote-debugging-port=9222 ` +
|
|
777
|
+
`(or: open -a "Google Chrome" --args --remote-debugging-port=9222), then call browser_attach again.`);
|
|
778
|
+
}
|
|
779
|
+
const pw = loadPlaywright();
|
|
780
|
+
const connect = pw.chromium.connectOverCDP?.bind(pw.chromium);
|
|
781
|
+
if (!connect) {
|
|
782
|
+
throw new Error("connectOverCDP is not available in this Playwright build.");
|
|
783
|
+
}
|
|
784
|
+
// Tear down anything we had (a launched browser, a stale CDP connection)
|
|
785
|
+
// before attaching.
|
|
786
|
+
await this.close();
|
|
787
|
+
this.browser = await connect({ endpointURL: normalized, timeout: 10_000 });
|
|
788
|
+
// Claim CDP mode IMMEDIATELY after connect(): from here on the browser
|
|
789
|
+
// is the user's, so every close/kill path must take the CDP branch
|
|
790
|
+
// (disconnect only). A wiring failure below must never leave a state
|
|
791
|
+
// where a later close() takes the non-CDP branch and would close the
|
|
792
|
+
// user's default context (all their tabs).
|
|
793
|
+
this.cdpMode = true;
|
|
794
|
+
this.cdpEndpoint = normalized;
|
|
795
|
+
try {
|
|
796
|
+
// contexts()[0] is the user's default context (their cookies/logins).
|
|
797
|
+
const context = this.browser.contexts()[0] ?? (await this.browser.newContext());
|
|
798
|
+
this.context = context;
|
|
799
|
+
context.on("page", (page) => {
|
|
800
|
+
this.wirePage(page);
|
|
801
|
+
if (!this.pages.includes(page))
|
|
802
|
+
this.pages.push(page);
|
|
803
|
+
});
|
|
804
|
+
for (const page of context.pages()) {
|
|
805
|
+
this.wirePage(page);
|
|
806
|
+
this.pages.push(page);
|
|
807
|
+
}
|
|
808
|
+
if (this.pages.length === 0) {
|
|
809
|
+
const page = await context.newPage();
|
|
810
|
+
this.wirePage(page);
|
|
811
|
+
this.pages.push(page);
|
|
812
|
+
}
|
|
813
|
+
this.activePageIndex = 0;
|
|
814
|
+
}
|
|
815
|
+
catch (err) {
|
|
816
|
+
// Wiring failed after connect(): detach (disconnect-only) so the
|
|
817
|
+
// user's browser is never closed, then surface the original error.
|
|
818
|
+
await this.detachCdp().catch(() => { });
|
|
819
|
+
throw err;
|
|
820
|
+
}
|
|
821
|
+
// We do not own this process — drop any launch-time pid evidence so no
|
|
822
|
+
// kill path can ever target it, and make sure no kill handler is attached.
|
|
823
|
+
this.browserPid = null;
|
|
824
|
+
this.browserPids = [];
|
|
825
|
+
this.verifiedBrowserPids = [];
|
|
826
|
+
this.verifiedBrowserPidsAt = 0;
|
|
827
|
+
this.verifiedBrowserPidsToken = null;
|
|
828
|
+
releaseProcessCleanup(this);
|
|
829
|
+
const titles = await Promise.all(this.pages.slice(0, 5).map(async (p) => {
|
|
830
|
+
try {
|
|
831
|
+
return await p.title();
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
return "";
|
|
835
|
+
}
|
|
836
|
+
}));
|
|
837
|
+
return { endpoint: normalized, tabCount: this.pages.length, titles };
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Detach from the attached CDP browser. In CDP mode `browser.close()` only
|
|
841
|
+
* disconnects the Playwright client — the user's browser keeps running.
|
|
842
|
+
* Subsequent calls auto-launch the built-in browser as usual. Returns
|
|
843
|
+
* `true` if a CDP connection was detached.
|
|
844
|
+
*/
|
|
845
|
+
async detachCdp() {
|
|
846
|
+
if (!this.cdpMode)
|
|
847
|
+
return false;
|
|
848
|
+
const browser = this.browser;
|
|
849
|
+
this.cdpMode = false;
|
|
850
|
+
this.cdpEndpoint = null;
|
|
851
|
+
this.browser = null;
|
|
852
|
+
this.context = null;
|
|
853
|
+
this.pages = [];
|
|
854
|
+
this.activePageIndex = 0;
|
|
855
|
+
this._recording = null;
|
|
856
|
+
releaseProcessCleanup(this);
|
|
857
|
+
if (browser) {
|
|
858
|
+
await browser.close().catch(() => { });
|
|
859
|
+
}
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
668
862
|
installDialogInterceptor(page) {
|
|
669
863
|
page.on("dialog", (dialog) => {
|
|
670
864
|
this.pendingDialogs.push({
|
|
@@ -771,6 +965,26 @@ export class BrowserService {
|
|
|
771
965
|
}
|
|
772
966
|
};
|
|
773
967
|
const closeTimeout = 5000; // 5 seconds per operation
|
|
968
|
+
// CDP attach mode: the browser belongs to the user — disconnect only.
|
|
969
|
+
// Never close their tabs, never touch the process tree.
|
|
970
|
+
if (this.cdpMode) {
|
|
971
|
+
const cdpBrowser = this.browser;
|
|
972
|
+
this.cdpMode = false;
|
|
973
|
+
this.cdpEndpoint = null;
|
|
974
|
+
if (cdpBrowser) {
|
|
975
|
+
await withTimeout(cdpBrowser.close().catch((e) => {
|
|
976
|
+
process.stderr.write(`[browser] cdp disconnect: ${e.message}\n`);
|
|
977
|
+
}), closeTimeout, "cdp browser.close() (disconnect)");
|
|
978
|
+
}
|
|
979
|
+
this.pages = [];
|
|
980
|
+
this.activePageIndex = 0;
|
|
981
|
+
this.context = null;
|
|
982
|
+
this.browser = null;
|
|
983
|
+
this.launchToken = null;
|
|
984
|
+
releaseProcessCleanup(this);
|
|
985
|
+
this._recording = null;
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
774
988
|
const browser = this.browser;
|
|
775
989
|
const context = this.context;
|
|
776
990
|
let browserCloseFailed = false;
|
|
@@ -836,6 +1050,8 @@ export class BrowserService {
|
|
|
836
1050
|
* process without waiting for a graceful close. Safe to call at any time.
|
|
837
1051
|
*/
|
|
838
1052
|
forceKillBrowserProcess() {
|
|
1053
|
+
if (this.cdpMode)
|
|
1054
|
+
return; // never signal a browser we do not own
|
|
839
1055
|
this.killBrowserProcess();
|
|
840
1056
|
}
|
|
841
1057
|
/**
|
|
@@ -850,6 +1066,8 @@ export class BrowserService {
|
|
|
850
1066
|
*/
|
|
851
1067
|
killBrowserProcess(browser = this.browser, opts) {
|
|
852
1068
|
try {
|
|
1069
|
+
if (this.cdpMode)
|
|
1070
|
+
return; // never signal a browser we do not own
|
|
853
1071
|
const token = this.launchToken ?? this.lastLaunchToken;
|
|
854
1072
|
const marker = token ? `${BROWSER_MARKER}${token}` : undefined;
|
|
855
1073
|
// `process()` only exists on BrowserServer/ElectronApplication, but keep
|
|
@@ -895,6 +1113,8 @@ export class BrowserService {
|
|
|
895
1113
|
*/
|
|
896
1114
|
killBrowserProcessSync() {
|
|
897
1115
|
try {
|
|
1116
|
+
if (this.cdpMode)
|
|
1117
|
+
return; // never signal a browser we do not own
|
|
898
1118
|
const verified = new Set(this.verifiedBrowserPids);
|
|
899
1119
|
if (verified.size === 0)
|
|
900
1120
|
return;
|
|
@@ -967,6 +1187,11 @@ export class BrowserService {
|
|
|
967
1187
|
}
|
|
968
1188
|
// ── Recording ──────────────────────────────────────────────
|
|
969
1189
|
async startRecording(opts) {
|
|
1190
|
+
if (this.cdpMode) {
|
|
1191
|
+
// Video recording needs a browser+context we own (recordVideo is set at
|
|
1192
|
+
// context creation) — the user's attached browser cannot be recorded.
|
|
1193
|
+
throw new Error("Video recording is not supported in attach mode (CDP). Use browser_detach to switch back to the built-in browser first.");
|
|
1194
|
+
}
|
|
970
1195
|
if (this._recording) {
|
|
971
1196
|
throw new Error("Recording is already active. Use stopRecording() first.");
|
|
972
1197
|
}
|
|
@@ -1025,15 +1250,10 @@ export class BrowserService {
|
|
|
1025
1250
|
},
|
|
1026
1251
|
});
|
|
1027
1252
|
this.context.on("page", (page) => {
|
|
1028
|
-
|
|
1029
|
-
this.consoleBuffer.push({ type: msg.type(), text: msg.text(), pageUrl: page.url() });
|
|
1030
|
-
});
|
|
1031
|
-
this.trackNetworkRequest(page);
|
|
1032
|
-
this.installDialogInterceptor(page);
|
|
1253
|
+
this.wirePage(page);
|
|
1033
1254
|
});
|
|
1034
1255
|
const page = await this.context.newPage();
|
|
1035
|
-
this.
|
|
1036
|
-
this.installDialogInterceptor(page);
|
|
1256
|
+
this.wirePage(page);
|
|
1037
1257
|
this.pages = [page];
|
|
1038
1258
|
this.activePageIndex = 0;
|
|
1039
1259
|
await page.goto(currentUrl, { waitUntil: "domcontentloaded", timeout: this.config.navigationTimeout }).catch(() => { });
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* control a real browser: navigate, click, type, evaluate JS, take
|
|
7
7
|
* screenshots, inspect network traffic, and more.
|
|
8
8
|
*
|
|
9
|
-
* All
|
|
9
|
+
* All 33 tools match the same API surface as playwright-mcp, but without
|
|
10
10
|
* the MCP protocol layer — they call Playwright directly from Node.js.
|
|
11
11
|
*
|
|
12
12
|
* Architecture:
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* first, then the browser, then a SIGKILL fallback for the Chromium
|
|
17
17
|
* process so a visible window is never orphaned)
|
|
18
18
|
*
|
|
19
|
-
* Registered tools (
|
|
19
|
+
* Registered tools (33):
|
|
20
20
|
* Core: browser_navigate, browser_navigate_back, browser_close, browser_resize,
|
|
21
|
-
* browser_quit
|
|
21
|
+
* browser_quit, browser_attach, browser_detach
|
|
22
22
|
* Tabs: browser_tabs
|
|
23
23
|
* Input: browser_click, browser_type, browser_press_key, browser_hover,
|
|
24
24
|
* browser_drag, browser_select_option, browser_fill_form, browser_file_upload
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* control a real browser: navigate, click, type, evaluate JS, take
|
|
7
7
|
* screenshots, inspect network traffic, and more.
|
|
8
8
|
*
|
|
9
|
-
* All
|
|
9
|
+
* All 33 tools match the same API surface as playwright-mcp, but without
|
|
10
10
|
* the MCP protocol layer — they call Playwright directly from Node.js.
|
|
11
11
|
*
|
|
12
12
|
* Architecture:
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* first, then the browser, then a SIGKILL fallback for the Chromium
|
|
17
17
|
* process so a visible window is never orphaned)
|
|
18
18
|
*
|
|
19
|
-
* Registered tools (
|
|
19
|
+
* Registered tools (33):
|
|
20
20
|
* Core: browser_navigate, browser_navigate_back, browser_close, browser_resize,
|
|
21
|
-
* browser_quit
|
|
21
|
+
* browser_quit, browser_attach, browser_detach
|
|
22
22
|
* Tabs: browser_tabs
|
|
23
23
|
* Input: browser_click, browser_type, browser_press_key, browser_hover,
|
|
24
24
|
* browser_drag, browser_select_option, browser_fill_form, browser_file_upload
|
|
@@ -150,7 +150,7 @@ function countRegisteredTools(ext) {
|
|
|
150
150
|
let count = 0;
|
|
151
151
|
const knownTools = [
|
|
152
152
|
"browser_navigate", "browser_navigate_back", "browser_close", "browser_resize",
|
|
153
|
-
"browser_quit",
|
|
153
|
+
"browser_quit", "browser_attach", "browser_detach",
|
|
154
154
|
"browser_tabs",
|
|
155
155
|
"browser_click", "browser_type", "browser_press_key", "browser_hover",
|
|
156
156
|
"browser_drag", "browser_select_option", "browser_fill_form", "browser_file_upload",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/tools/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAwBvD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/tools/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAwBvD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CA0Q7E"}
|
|
@@ -104,13 +104,37 @@ export function registerCoreTools(ext, bs) {
|
|
|
104
104
|
const quitTool = {
|
|
105
105
|
name: "browser_quit",
|
|
106
106
|
label: "Browser Quit",
|
|
107
|
-
description: "Close the WHOLE browser: closes the browser context, the browser itself and kills the Chromium process. Unlike browser_close (which only closes the current tab), this tears the browser down completely. Use it when you are done working with the browser or when the browser window does not want to close.",
|
|
107
|
+
description: "Close the WHOLE browser: closes the browser context, the browser itself and kills the Chromium process. Unlike browser_close (which only closes the current tab), this tears the browser down completely. Use it when you are done working with the browser or when the browser window does not want to close. In CDP attach mode (browser_attach) this detaches from the user's browser instead of closing it — their browser keeps running.",
|
|
108
108
|
promptSnippet: "`browser_quit` — close the whole browser and kill the Chromium process",
|
|
109
109
|
parameters: {
|
|
110
110
|
type: "object",
|
|
111
111
|
properties: {},
|
|
112
112
|
},
|
|
113
113
|
async execute() {
|
|
114
|
+
// CDP attach mode: "quit" means detach — the user's browser is not ours
|
|
115
|
+
// to close, and there is no process tree of ours to kill.
|
|
116
|
+
if (bs.isAttached()) {
|
|
117
|
+
// Stop an in-flight WebRTC stream (module-level stream state must
|
|
118
|
+
// not stay "active" after the detach — same path as
|
|
119
|
+
// `session_shutdown` in ../index.ts).
|
|
120
|
+
try {
|
|
121
|
+
await stopActiveStream();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* no stream active — ignore */
|
|
125
|
+
}
|
|
126
|
+
await bs.detachCdp();
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: "✓ Detached from your browser (CDP attach mode). Your browser keeps running — the agent never closes it. " +
|
|
132
|
+
"Subsequent browser calls will auto-launch the built-in Chromium.",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
details: { detached: true },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
114
138
|
try {
|
|
115
139
|
// Same order as the `session_shutdown` handler in ../index.ts: stream
|
|
116
140
|
// → recording → browser → SIGKILL fallback.
|
|
@@ -139,6 +163,98 @@ export function registerCoreTools(ext, bs) {
|
|
|
139
163
|
}
|
|
140
164
|
},
|
|
141
165
|
};
|
|
166
|
+
const attachTool = {
|
|
167
|
+
name: "browser_attach",
|
|
168
|
+
label: "Browser Attach",
|
|
169
|
+
description: "Connect the agent to the user's OWN browser (Arc, Chrome, Chromium, Edge, ...) over CDP. " +
|
|
170
|
+
"Call this when the user says they launched their own browser with a remote-debugging port " +
|
|
171
|
+
"(e.g. `open -a Arc --args --remote-debugging-port=9222`) and want the agent to use it. " +
|
|
172
|
+
"The agent then drives the user's real browser — its existing tabs, cookies and logins. " +
|
|
173
|
+
"Video recording is NOT available in attach mode. Use browser_detach to return to the built-in browser.",
|
|
174
|
+
promptSnippet: "`browser_attach { endpoint? }` — connect to the user's own browser via CDP (remote debugging port)",
|
|
175
|
+
parameters: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
endpoint: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "CDP endpoint URL (default http://127.0.0.1:9222). A bare port like \"9222\" is accepted and normalized to http://127.0.0.1:9222.",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
async execute(_toolCallId, params) {
|
|
185
|
+
const p = params;
|
|
186
|
+
try {
|
|
187
|
+
const { endpoint, tabCount, titles } = await bs.attachOverCdp(p.endpoint);
|
|
188
|
+
const tabList = titles.filter((t) => t.length > 0).slice(0, 5);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: [
|
|
194
|
+
`✓ Attached to your browser via CDP: ${endpoint}`,
|
|
195
|
+
`Tabs open: ${tabCount}${tabList.length ? ` — ${tabList.map((t) => `"${t}"`).join(", ")}` : ""}`,
|
|
196
|
+
"",
|
|
197
|
+
"You are now driving the user's real browser (their tabs, cookies and logins).",
|
|
198
|
+
"Video recording is not available in attach mode.",
|
|
199
|
+
].join("\n"),
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
details: { attached: true, endpoint, tabCount },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: `Browser attach failed: ${msg}` }],
|
|
209
|
+
details: { isError: true },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
const detachTool = {
|
|
215
|
+
name: "browser_detach",
|
|
216
|
+
label: "Browser Detach",
|
|
217
|
+
description: "Disconnect from the user's browser (CDP attach mode) WITHOUT closing it — the user's browser keeps running. " +
|
|
218
|
+
"After detaching, the next browser call auto-launches the built-in Chromium exactly as usual. " +
|
|
219
|
+
"Does nothing when not attached.",
|
|
220
|
+
promptSnippet: "`browser_detach` — disconnect from the user's browser (CDP) without closing it",
|
|
221
|
+
parameters: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {},
|
|
224
|
+
},
|
|
225
|
+
async execute() {
|
|
226
|
+
try {
|
|
227
|
+
// Stop an in-flight WebRTC stream before detaching (module-level
|
|
228
|
+
// stream state must not stay "active" — same path as
|
|
229
|
+
// `session_shutdown` in ../index.ts).
|
|
230
|
+
try {
|
|
231
|
+
await stopActiveStream();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
/* no stream active — ignore */
|
|
235
|
+
}
|
|
236
|
+
const detached = await bs.detachCdp();
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: detached
|
|
242
|
+
? "✓ Detached from your browser. It keeps running — the agent never closes it. Subsequent browser calls will use the built-in Chromium."
|
|
243
|
+
: "Not attached to a user browser — nothing to detach.",
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
details: { detached },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: `Browser detach failed: ${msg}` }],
|
|
253
|
+
details: { isError: true },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
};
|
|
142
258
|
const resizeTool = {
|
|
143
259
|
name: "browser_resize",
|
|
144
260
|
label: "Browser Resize",
|
|
@@ -164,7 +280,7 @@ export function registerCoreTools(ext, bs) {
|
|
|
164
280
|
}
|
|
165
281
|
},
|
|
166
282
|
};
|
|
167
|
-
for (const tool of [navigateTool, navigateBackTool, closeTool, quitTool, resizeTool]) {
|
|
283
|
+
for (const tool of [navigateTool, navigateBackTool, closeTool, quitTool, attachTool, detachTool, resizeTool]) {
|
|
168
284
|
try {
|
|
169
285
|
ext.registerTool(tool);
|
|
170
286
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -400,6 +400,16 @@ export class AgentBridge {
|
|
|
400
400
|
disabledAgents: [],
|
|
401
401
|
teamId: this.opts.teamId,
|
|
402
402
|
emitSubagentEvent: (event) => this.emitAndBuffer(event),
|
|
403
|
+
// Parent-aware subagent resolution: the tool re-resolves the
|
|
404
|
+
// session's active primary on every call, mirroring the
|
|
405
|
+
// before_agent_start injection logic exactly (session override
|
|
406
|
+
// wins over settings; disabled toggle suppresses both).
|
|
407
|
+
resolveActivePrimaryName: () => {
|
|
408
|
+
const settings = loadAgentSettings(this.opts.cwd, this.opts.agentDir);
|
|
409
|
+
if (!settings.primaryAgentEnabled)
|
|
410
|
+
return null;
|
|
411
|
+
return this.opts.activePrimaryAgent ?? settings.activePrimaryAgent;
|
|
412
|
+
},
|
|
403
413
|
});
|
|
404
414
|
});
|
|
405
415
|
extensionFactories.push(desktopScreenshotExtension);
|