@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.
@@ -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). Cloud agents override
37
- * system agents on name collision. Never throws and never touches the
38
- * network — offline / logged-out sessions simply get the system agents.
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,CA6EnC;AACD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,EAAE,CAMf;AACD,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAiBpB;AACD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,GAAG,IAAI,CAgBpB;AACD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,EAAE,IAAI,CAChB,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,CACT,WAAW,EACX,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAC7D,GAAG,IAAI,GACP,MAAM,CAYR"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agent/agents.ts"],"names":[],"mappings":"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"}
@@ -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). Cloud agents override
217
- * system agents on name collision. Never throws and never touches the
218
- * network — offline / logged-out sessions simply get the system agents.
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
- const agentMap = new Map();
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
- // Cloud agents override built-in system agents (same precedence as
236
- // discovery): a cloud primary always wins over the built-in ghost.
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
  *
@@ -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,EAIhB,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;AAmF1E,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;AA4lBD,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;CAChB,GACC,IAAI,CAmaN"}
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"}
@@ -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
- const agent = agents.find((a) => a.name === normalizedAgentName);
485
- if (!agent) {
486
- const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
487
- const suggestion = suggestAgentName(normalizedAgentName, agents);
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 = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
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 = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
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":"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"}
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.trackNetworkRequest(page);
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
- return this.pages.map((p, i) => ({
632
- index: i,
633
- title: p.url() ? `Tab ${i}` : p.url(),
634
- url: p.url(),
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
- page.on("console", (msg) => {
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.trackNetworkRequest(page);
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 31 tools match the same API surface as playwright-mcp, but without
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 (31):
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 31 tools match the same API surface as playwright-mcp, but without
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 (31):
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,CAoJ7E"}
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAEV,YAAY,EAQb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAQL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAatC,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AActC,OAAO,EACL,kBAAkB,IAAI,yBAAyB,EAEhD,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAI3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AA6BpE,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,UAAU,4BAA4B;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CACpC;AAED,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,0BAA0B,EAAE,CAAC,GAAG,EAAE;QAChC,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,6EAA6E;QAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,yBAAyB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AA+ED,yFAAyF;AACzF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAmB1E;AA0DD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,iCAAiC,CAAqB;IAC9D,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,OAAO,CAAC,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAqB;IACjC;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC;;;;OAIG;IACH,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAEpC,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D,OAAO,CAAC,YAAY,CAAC,CAAuB;IAC5C,8EAA8E;IAC9E,OAAO,CAAC,kBAAkB,CAAC,CAAgC;IAC3D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B,sFAAsF;IACtF,OAAO,CAAC,iBAAiB,CAMT;IAChB,OAAO,CAAC,WAAW,CACuD;IAC1E,OAAO,CAAC,mBAAmB,CAA+B;IAC1D;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB,CAAK;IAClC;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAC,CAA2B;IAClD,OAAO,CAAC,mBAAmB,CAAC,CAAgC;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB,CAO5B;IACJ;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB,CAAqC;IAChE;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAA6B;gBAEtC,IAAI,EAAE,kBAAkB;IAIpC;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAQlC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsZ5B,OAAO,CAAC,4BAA4B;IAQpC;;;;;OAKG;YACW,qBAAqB;IAcnC;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAG3C;;;;;;;;;;;;OAYG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvC;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,sBAAsB;YAahB,oBAAoB;IA8BlC;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,0BAA0B;IAoFlC;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;OAQG;IACH,wBAAwB,IAAI,MAAM,GAAG,SAAS;IAI9C;;;;;;;;;OASG;IACH,qBAAqB,IAAI,MAAM,GAAG,SAAS;IAI3C;;;;OAIG;IACH,eAAe,IACX;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxE,SAAS;IAIb,mDAAmD;IACnD,WAAW,IAAI,OAAO;IAItB,mEAAmE;IACnE,UAAU,IAAI,OAAO;IAIrB,2EAA2E;IAC3E,MAAM,IAAI,OAAO;IAIjB;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzE,gBAAgB,IAAI,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IAgBF,iBAAiB,IAAI;QACnB,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;QACtE,QAAQ,EAAE;YACR,QAAQ,EAAE,OAAO,CAAC;YAClB,UAAU,EAAE,OAAO,CAAC;YACpB,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,EAAE,OAAO,CAAC;SACjB,CAAC;KACH;IAYD,OAAO,CAAC,iBAAiB;IAmCnB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAmFpE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,iCAAiC;IAkBzC;;;;;;OAMG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAoBpD;;;;;;;;;OASG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAI7D;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CrE;;;;;;;;OAQG;YACW,iBAAiB;IAqB/B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IAoBlC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAM1B,2EAA2E;IAC3E,OAAO,CAAC,6BAA6B;IAOrC;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;;;;;OAOG;IACH,OAAO,CAAC,2BAA2B;IAYnC;;;;OAIG;IACH,OAAO,CAAC,8BAA8B;IAQtC,yEAAyE;IACzE,OAAO,CAAC,iCAAiC;IAOzC,OAAO,CAAC,iBAAiB;IAazB;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,uBAAuB;IAMzB,OAAO,CACX,OAAO,CAAC,EAAE,MAAM,GAAG,4BAA4B,GAC9C,OAAO,CAAC,gBAAgB,CAAC;IAY5B,OAAO,IAAI,IAAI;IAiDf;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAoCrB;;;;;;;;OAQG;IACH,OAAO,CAAC,6BAA6B;IAiCrC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAqB7B,uEAAuE;IACvE,OAAO,CAAC,wBAAwB;IAMhC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAqE9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI;CAsvBzC"}
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.257",
3
+ "version": "0.9.259",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,