pi-agent-browser-native 0.2.76 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +10 -19
  3. package/dist/extensions/agent-browser/index.js +221 -23
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +0 -5
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +4 -5
  6. package/dist/extensions/agent-browser/lib/command-policy.js +0 -5
  7. package/dist/extensions/agent-browser/lib/command-taxonomy.js +0 -6
  8. package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
  9. package/dist/extensions/agent-browser/lib/config.js +0 -6
  10. package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
  11. package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
  12. package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
  13. package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
  14. package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
  15. package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -5
  16. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +0 -5
  17. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -6
  18. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +0 -5
  19. package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
  20. package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
  21. package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
  22. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -5
  23. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
  24. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +0 -3
  25. package/dist/extensions/agent-browser/lib/managed-session-restore.js +30 -13
  26. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +0 -5
  27. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +1 -7
  28. package/dist/extensions/agent-browser/lib/managed-session-storage.js +0 -5
  29. package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +1 -1
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +41 -30
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +4 -1
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +20 -11
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +42 -20
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +40 -9
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +17 -1
  41. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +42 -18
  42. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -1
  43. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
  44. package/dist/extensions/agent-browser/lib/parsing.js +0 -7
  45. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +4 -1
  46. package/dist/extensions/agent-browser/lib/playbook.js +4 -10
  47. package/dist/extensions/agent-browser/lib/process-identity.js +0 -5
  48. package/dist/extensions/agent-browser/lib/process.js +12 -9
  49. package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
  50. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -8
  51. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -7
  52. package/dist/extensions/agent-browser/lib/results/categories.js +1 -8
  53. package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
  54. package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
  55. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
  56. package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
  57. package/dist/extensions/agent-browser/lib/results/network.js +0 -7
  58. package/dist/extensions/agent-browser/lib/results/next-actions.js +0 -30
  59. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +1 -7
  60. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
  61. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -5
  62. package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
  63. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +0 -5
  64. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
  65. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
  66. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +0 -5
  67. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +0 -5
  68. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
  69. package/dist/extensions/agent-browser/lib/results/presentation.js +0 -5
  70. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +0 -7
  71. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
  72. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +0 -7
  73. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +0 -7
  74. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
  75. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
  76. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
  77. package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
  78. package/dist/extensions/agent-browser/lib/results/text.js +0 -7
  79. package/dist/extensions/agent-browser/lib/runtime.js +4 -14
  80. package/dist/extensions/agent-browser/lib/session-page-state.js +0 -7
  81. package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
  82. package/dist/extensions/agent-browser/lib/temp.js +0 -7
  83. package/dist/extensions/agent-browser/lib/web-search.js +0 -5
  84. package/docs/ARCHITECTURE.md +17 -16
  85. package/docs/COMMAND_REFERENCE.md +10 -9
  86. package/docs/ELECTRON.md +3 -19
  87. package/docs/RELEASE.md +5 -17
  88. package/docs/REQUIREMENTS.md +4 -4
  89. package/docs/SUPPORT_MATRIX.md +18 -17
  90. package/docs/TOOL_CONTRACT.md +23 -19
  91. package/docs/platform-smoke.md +1 -1
  92. package/package.json +5 -6
  93. package/scripts/doctor.mjs +1 -1
  94. package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
  95. package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
  96. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
  97. package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
  98. package/dist/extensions/agent-browser/lib/results.js +0 -14
@@ -1,24 +1,18 @@
1
- /**
2
- * Purpose: Register the native agent_browser tool for pi so agents can invoke agent-browser without going through bash.
3
- * Responsibilities: Define the tool schema, inject thin wrapper behavior around the upstream CLI, manage extension-owned browser session convenience, and return pi-friendly content/details.
4
- * Scope: Native tool registration and orchestration only; the wrapper intentionally stays close to the upstream agent-browser CLI.
5
- * Usage: Loaded by pi through the package manifest in this package, or explicitly via `pi --no-extensions -e .` during local checkout development.
6
- * Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
7
- */
8
1
  import { existsSync, readFileSync } from "node:fs";
9
2
  import { dirname, join } from "node:path";
10
3
  import { fileURLToPath } from "node:url";
11
4
  import { Text } from "@earendil-works/pi-tui";
12
5
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
13
6
  import { SessionPageState } from "./lib/session-page-state.js";
14
- import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
15
- import { extractExplicitNamespace, extractExplicitSessionName, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
7
+ import { canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
8
+ import { extractExplicitNamespace, extractExplicitSessionName, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
16
9
  import { cleanupManagedSessionRestoreConfig, ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
17
10
  import { isRecord } from "./lib/parsing.js";
18
11
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
19
12
  import { isCloseCommand } from "./lib/command-taxonomy.js";
13
+ import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
20
14
  import { cleanupSecureTempArtifacts } from "./lib/temp.js";
21
- import { AGENT_BROWSER_PARAMS, } from "./lib/input-modes.js";
15
+ import { AGENT_BROWSER_PARAMS } from "./lib/input-modes/params.js";
22
16
  import { parseAllowedDomainsPolicyFromArgs } from "./lib/navigation-policy.js";
23
17
  import { closeManagedSession, getSessionContextKey, runAgentBrowserTool } from "./lib/orchestration/browser-run/index.js";
24
18
  import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
@@ -111,6 +105,57 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
111
105
  }
112
106
  return restored;
113
107
  }
108
+ function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionName, namespace) {
109
+ let restored = false;
110
+ const targetKey = getSessionContextKey(sessionName, namespace);
111
+ for (const entry of branch) {
112
+ if (!isRecord(entry) || entry.type !== "message")
113
+ continue;
114
+ const message = isRecord(entry.message) ? entry.message : undefined;
115
+ if (!message || message.toolName !== "agent_browser")
116
+ continue;
117
+ const details = isRecord(message.details) ? message.details : undefined;
118
+ if (!details)
119
+ continue;
120
+ if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
121
+ continue;
122
+ const outcome = getManagedSessionOutcome(details);
123
+ const activeAfterFailure = outcome?.activeAfter === true
124
+ && typeof outcome.currentSessionName === "string"
125
+ && getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
126
+ if ((getSuccessfulToolResult(details, message) || activeAfterFailure) && typeof details.managedSessionHeadedAutosaveDisabled === "boolean") {
127
+ restored = details.managedSessionHeadedAutosaveDisabled;
128
+ }
129
+ }
130
+ return restored;
131
+ }
132
+ function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionName, namespace) {
133
+ let restored;
134
+ const targetKey = getSessionContextKey(sessionName, namespace);
135
+ for (const entry of branch) {
136
+ if (!isRecord(entry) || entry.type !== "message")
137
+ continue;
138
+ const message = isRecord(entry.message) ? entry.message : undefined;
139
+ if (!message || message.toolName !== "agent_browser")
140
+ continue;
141
+ const details = isRecord(message.details) ? message.details : undefined;
142
+ if (!details)
143
+ continue;
144
+ if (getSessionContextKey(typeof details.sessionName === "string" ? details.sessionName : undefined, typeof details.namespace === "string" ? details.namespace : undefined) !== targetKey)
145
+ continue;
146
+ const outcome = getManagedSessionOutcome(details);
147
+ const activeAfterFailure = outcome?.activeAfter === true
148
+ && typeof outcome.currentSessionName === "string"
149
+ && getSessionContextKey(outcome.currentSessionName, typeof outcome.currentSessionNamespace === "string" ? outcome.currentSessionNamespace : undefined) === targetKey;
150
+ if (!getSuccessfulToolResult(details, message) && !activeAfterFailure)
151
+ continue;
152
+ if (typeof details.managedSessionHeadedAutosaveInterval === "string")
153
+ restored = details.managedSessionHeadedAutosaveInterval;
154
+ else if (details.managedSessionHeadedAutosaveDisabled === true)
155
+ restored = "0";
156
+ }
157
+ return restored;
158
+ }
114
159
  function getToolResultArgs(details) {
115
160
  if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
116
161
  return details.args;
@@ -118,6 +163,42 @@ function getToolResultArgs(details) {
118
163
  return details.effectiveArgs;
119
164
  return [];
120
165
  }
166
+ function isAttachedBrowserInvocation(args, env = process.env) {
167
+ const autoConnectEnv = env.AGENT_BROWSER_AUTO_CONNECT;
168
+ return extractCommandTokens(args)[0] === "connect"
169
+ || hasLaunchScopedFlagToken(args, "--cdp")
170
+ || hasLaunchScopedFlagToken(args, "--auto-connect")
171
+ || env.AGENT_BROWSER_CDP !== undefined
172
+ || isUpstreamEnvFlagEnabled(autoConnectEnv);
173
+ }
174
+ function restoreAttachedSessionKeysFromBranch(branch) {
175
+ const attachedSessionKeys = new Set();
176
+ for (const entry of branch) {
177
+ if (!isRecord(entry) || entry.type !== "message")
178
+ continue;
179
+ const message = isRecord(entry.message) ? entry.message : undefined;
180
+ if (!message || message.toolName !== "agent_browser")
181
+ continue;
182
+ const details = isRecord(message.details) ? message.details : undefined;
183
+ if (!details)
184
+ continue;
185
+ const managedSessionOutcome = isRecord(details.managedSessionOutcome) ? details.managedSessionOutcome : undefined;
186
+ const retainedFailedAttachment = details.attachedBrowserSession === true && managedSessionOutcome?.activeAfter === true;
187
+ if (!getSuccessfulToolResult(details, message) && !retainedFailedAttachment)
188
+ continue;
189
+ const args = getToolResultArgs(details);
190
+ const sessionName = typeof details.sessionName === "string" ? details.sessionName : extractExplicitSessionName(args);
191
+ if (!sessionName)
192
+ continue;
193
+ const namespace = typeof details.namespace === "string" ? details.namespace : extractExplicitNamespace(args);
194
+ const sessionKey = getSessionContextKey(sessionName, namespace) ?? sessionName;
195
+ if (isCloseCommand(extractCommandTokens(args)[0]))
196
+ attachedSessionKeys.delete(sessionKey);
197
+ else if (details.attachedBrowserSession === true || isAttachedBrowserInvocation(args, {}))
198
+ attachedSessionKeys.add(sessionKey);
199
+ }
200
+ return attachedSessionKeys;
201
+ }
121
202
  function restoreAllowedDomainsBySessionFromBranch(branch) {
122
203
  const restoredPolicies = new Map();
123
204
  for (const entry of branch) {
@@ -179,7 +260,9 @@ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
179
260
  const key = getSessionContextKey(sessionName, options.namespace) ?? sessionName;
180
261
  const existing = sessions.get(key);
181
262
  const branchOwned = existing && !existing.branchOwned ? false : options.branchOwned === true;
182
- sessions.set(key, { branchOwned, cwd, namespace: options.namespace, sessionName });
263
+ const headedManagedAutosaveDisabled = options.headedManagedAutosaveDisabled ?? existing?.headedManagedAutosaveDisabled;
264
+ const headedManagedAutosaveInterval = options.headedManagedAutosaveInterval ?? existing?.headedManagedAutosaveInterval;
265
+ sessions.set(key, { branchOwned, cwd, headedManagedAutosaveDisabled, headedManagedAutosaveInterval, namespace: options.namespace, sessionName });
183
266
  }
184
267
  function untrackOwnedManagedSession(sessions, sessionName, namespace) {
185
268
  if (!sessionName)
@@ -210,7 +293,11 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
210
293
  const attemptedSessionName = typeof outcome.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
211
294
  if (outcome.activeAfter === true && (status === "created" || status === "replaced" || status === "unchanged")) {
212
295
  const namespace = isRecord(details) && typeof details.namespace === "string" ? details.namespace : undefined;
213
- trackOwnedManagedSession(sessions, currentSessionName, cwd, { namespace });
296
+ trackOwnedManagedSession(sessions, currentSessionName, cwd, {
297
+ headedManagedAutosaveDisabled: details?.managedSessionHeadedAutosaveDisabled === true,
298
+ headedManagedAutosaveInterval: typeof details?.managedSessionHeadedAutosaveInterval === "string" ? details.managedSessionHeadedAutosaveInterval : undefined,
299
+ namespace,
300
+ });
214
301
  }
215
302
  if (succeeded && status === "closed") {
216
303
  untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName);
@@ -345,10 +432,18 @@ function setBranchRankForString(map, value, rank) {
345
432
  if (typeof value === "string" && value.length > 0)
346
433
  map.set(value, rank);
347
434
  }
435
+ function setBranchManagedSessionActive(events, sessionName, namespace, rank) {
436
+ if (typeof sessionName !== "string" || sessionName.length === 0)
437
+ return;
438
+ const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
439
+ events.managedSessionActiveIdentities.set(key, { namespace, sessionName });
440
+ events.managedSessionActiveRanks.set(key, rank);
441
+ }
348
442
  function collectBranchManagedResourceEvents(branch) {
349
443
  const events = {
350
444
  electronLaunchActiveRanks: new Map(),
351
445
  electronLaunchCleanupRanks: new Map(),
446
+ managedSessionActiveIdentities: new Map(),
352
447
  managedSessionActiveRanks: new Map(),
353
448
  managedSessionCloseRanks: new Map(),
354
449
  };
@@ -377,17 +472,17 @@ function collectBranchManagedResourceEvents(branch) {
377
472
  const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
378
473
  const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
379
474
  if (outcomeSucceeded && outcome.activeAfter === true && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")) {
380
- setBranchRankForString(events.managedSessionActiveRanks, getSessionContextKey(outcomeCurrentSessionName, namespace), eventRank);
475
+ setBranchManagedSessionActive(events, outcomeCurrentSessionName, namespace, eventRank);
381
476
  }
382
477
  if (outcomeSucceeded && outcomeStatus === "closed") {
383
478
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, namespace), eventRank);
384
479
  }
385
- if (outcomeSucceeded && outcomeStatus === "replaced") {
480
+ if (outcome && outcomeStatus === "replaced" && outcome.replacedSessionClosed !== false) {
386
481
  const replacedSessionNamespace = typeof outcome.replacedSessionNamespace === "string" ? outcome.replacedSessionNamespace : namespace;
387
482
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined, replacedSessionNamespace), eventRank);
388
483
  }
389
- if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh")) {
390
- events.managedSessionActiveRanks.set(getSessionContextKey(sessionName, namespace) ?? sessionName, eventRank);
484
+ if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh" || details.managedSessionHeadedAutosaveDisabled === true || typeof details.managedSessionHeadedAutosaveInterval === "string")) {
485
+ setBranchManagedSessionActive(events, sessionName, namespace, eventRank);
391
486
  }
392
487
  if (succeeded && isCloseCommand(command)) {
393
488
  setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, namespace), eventRank);
@@ -433,18 +528,18 @@ function syncElectronCleanupManagedSessions(sessions, cleanupResults) {
433
528
  untrackOwnedManagedSession(sessions, sessionName);
434
529
  }
435
530
  }
436
- async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessionName, timeoutMs, keepNamespace) {
531
+ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessionName, timeoutMs, attachedSessionKeys, keepNamespace) {
437
532
  const keepKey = getSessionContextKey(keepSessionName, keepNamespace);
438
533
  for (const [key, owner] of [...sessions]) {
439
534
  if (key === keepKey)
440
535
  continue;
441
- const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, restoreState, sessionName: owner.sessionName, timeoutMs });
536
+ const error = await closeManagedSession({ cwd: owner.cwd, headedManagedAutosaveInterval: owner.headedManagedAutosaveInterval, namespace: owner.namespace, preserveAttachedBrowserSession: attachedSessionKeys.has(key), restoreState, sessionName: owner.sessionName, timeoutMs });
442
537
  if (!error)
443
538
  sessions.delete(key);
444
539
  }
445
540
  }
446
- async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs) {
447
- await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs);
541
+ async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs, attachedSessionKeys) {
542
+ await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs, attachedSessionKeys);
448
543
  }
449
544
  function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
450
545
  const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
@@ -577,6 +672,8 @@ export default function agentBrowserExtension(pi) {
577
672
  let managedSessionActive = false;
578
673
  let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
579
674
  let managedSessionCompatibilityWorkaround;
675
+ let managedSessionHeadedAutosaveDisabled = false;
676
+ let managedSessionHeadedAutosaveInterval;
580
677
  let managedSessionName = managedSessionBaseName;
581
678
  let managedSessionCwd = process.cwd();
582
679
  let managedSessionNamespace;
@@ -585,6 +682,7 @@ export default function agentBrowserExtension(pi) {
585
682
  let traceOwners = new Map();
586
683
  let artifactManifest;
587
684
  let allowedDomainsBySession = new Map();
685
+ let attachedSessionKeys = new Set();
588
686
  let networkRoutesBySession = new Map();
589
687
  let electronLaunchRecords = new Map();
590
688
  let ownedElectronLaunchRecords = new Map();
@@ -600,6 +698,7 @@ export default function agentBrowserExtension(pi) {
600
698
  const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
601
699
  allowedDomainsBySession = new Map(allowedDomainsBySession);
602
700
  allowedDomainsBySession.delete(key);
701
+ attachedSessionKeys.delete(key);
603
702
  networkRoutesBySession = new Map(networkRoutesBySession);
604
703
  networkRoutesBySession.delete(key);
605
704
  sessionPageState.clearSession(key);
@@ -610,6 +709,7 @@ export default function agentBrowserExtension(pi) {
610
709
  const previousManagedSessionActive = managedSessionActive;
611
710
  const previousManagedSessionName = managedSessionName;
612
711
  const previousFreshSessionOrdinal = freshSessionOrdinal;
712
+ const previousAttachedSessionKeys = attachedSessionKeys;
613
713
  managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
614
714
  const branch = ctx.sessionManager.getBranch();
615
715
  const branchResourceEvents = collectBranchManagedResourceEvents(branch);
@@ -640,14 +740,24 @@ export default function agentBrowserExtension(pi) {
640
740
  managedSessionCompatibilityWorkaround = managedSessionActive
641
741
  ? restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, managedSessionName, managedSessionNamespace)
642
742
  : undefined;
743
+ managedSessionHeadedAutosaveDisabled = managedSessionActive
744
+ && restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, managedSessionName, managedSessionNamespace);
745
+ managedSessionHeadedAutosaveInterval = managedSessionActive
746
+ ? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
747
+ : undefined;
643
748
  managedSessionCwd = ctx.cwd;
644
749
  freshSessionOrdinal = nextFreshSessionOrdinal;
645
750
  sessionPageState = SessionPageState.fromBranch(branch);
646
751
  traceOwners = new Map();
647
752
  artifactManifest = restoreArtifactManifestFromBranch(branch);
648
753
  allowedDomainsBySession = restoreAllowedDomainsBySessionFromBranch(branch);
754
+ attachedSessionKeys = restoreAttachedSessionKeysFromBranch(branch);
649
755
  networkRoutesBySession = new Map();
650
756
  electronLaunchRecords = restoreElectronLaunchRecordsFromBranch(branch);
757
+ for (const record of getActiveElectronRecords(electronLaunchRecords)) {
758
+ if (record.sessionName)
759
+ attachedSessionKeys.add(getSessionContextKey(record.sessionName) ?? record.sessionName);
760
+ }
651
761
  if (options.resetRuntimeOwnership) {
652
762
  ownedManagedSessions.clear();
653
763
  ownedElectronLaunchRecords = new Map();
@@ -659,13 +769,57 @@ export default function agentBrowserExtension(pi) {
659
769
  }
660
770
  removeInactiveOwnedElectronLaunchRecords(ownedElectronLaunchRecords, branchOwnedElectronLaunchIds, electronLaunchRecords, branchResourceEvents.electronLaunchActiveRanks, branchResourceEvents.electronLaunchCleanupRanks);
661
771
  }
772
+ for (const [sessionKey, identity] of branchResourceEvents.managedSessionActiveIdentities) {
773
+ const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
774
+ const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
775
+ if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
776
+ continue;
777
+ if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
778
+ continue;
779
+ trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
780
+ branchOwned: true,
781
+ headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
782
+ headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, identity.sessionName, identity.namespace),
783
+ namespace: identity.namespace,
784
+ });
785
+ }
662
786
  if (restoredState.active) {
663
- trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, { branchOwned: true, namespace: restoredState.namespace });
787
+ trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
788
+ branchOwned: true,
789
+ headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
790
+ headedManagedAutosaveInterval: managedSessionHeadedAutosaveInterval,
791
+ namespace: restoredState.namespace,
792
+ });
793
+ }
794
+ for (const record of getActiveElectronRecords(electronLaunchRecords)) {
795
+ if (!record.sessionName || !isRestorableManagedSessionName(record.sessionName, managedSessionBaseName))
796
+ continue;
797
+ const sessionKey = getSessionContextKey(record.sessionName) ?? record.sessionName;
798
+ const activeRank = branchResourceEvents.managedSessionActiveRanks.get(sessionKey);
799
+ const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
800
+ if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
801
+ continue;
802
+ trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
803
+ branchOwned: true,
804
+ headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
805
+ headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
806
+ });
664
807
  }
665
808
  mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
666
809
  branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
667
810
  markBranchOwned: true,
668
811
  });
812
+ if (!options.resetRuntimeOwnership) {
813
+ for (const sessionKey of previousAttachedSessionKeys) {
814
+ if (ownedManagedSessions.has(sessionKey))
815
+ attachedSessionKeys.add(sessionKey);
816
+ }
817
+ for (const record of ownedElectronLaunchRecords.values()) {
818
+ const sessionKey = getSessionContextKey(record.sessionName);
819
+ if (sessionKey && previousAttachedSessionKeys.has(sessionKey))
820
+ attachedSessionKeys.add(sessionKey);
821
+ }
822
+ }
669
823
  };
670
824
  const registerWebSearchToolIfAvailable = (configState) => {
671
825
  if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
@@ -707,10 +861,12 @@ export default function agentBrowserExtension(pi) {
707
861
  ? ownedElectronLaunchRecords
708
862
  : getOffBranchOwnedElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords);
709
863
  const electronCleanupResults = await cleanupActiveElectronHostLaunches({
864
+ attachedSessionKeys,
710
865
  cwd: shutdownCwd,
711
866
  electronChildProcesses,
712
867
  electronLaunchRecords: electronRecordsToCleanup,
713
868
  managedSessionRestoreState,
869
+ ownedManagedSessions,
714
870
  timeoutMs: implicitSessionCloseTimeoutMs,
715
871
  });
716
872
  preservedElectronProfileDirs = [...new Set([
@@ -719,19 +875,22 @@ export default function agentBrowserExtension(pi) {
719
875
  ])];
720
876
  syncElectronCleanupManagedSessions(ownedManagedSessions, electronCleanupResults);
721
877
  if (quitting) {
722
- await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs);
878
+ await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs, attachedSessionKeys);
723
879
  }
724
880
  else {
725
- await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, managedSessionActive ? managedSessionNamespace : undefined);
881
+ await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, managedSessionActive ? managedSessionNamespace : undefined);
726
882
  }
727
883
  });
728
884
  managedSessionActive = false;
729
885
  managedSessionCompatibilityWorkaround = undefined;
886
+ managedSessionHeadedAutosaveDisabled = false;
887
+ managedSessionHeadedAutosaveInterval = undefined;
730
888
  managedSessionNamespace = undefined;
731
889
  sessionPageState.reset();
732
890
  traceOwners = new Map();
733
891
  artifactManifest = undefined;
734
892
  allowedDomainsBySession = new Map();
893
+ attachedSessionKeys = new Set();
735
894
  networkRoutesBySession = new Map();
736
895
  electronLaunchRecords = new Map();
737
896
  ownedElectronLaunchRecords = new Map();
@@ -822,6 +981,7 @@ export default function agentBrowserExtension(pi) {
822
981
  ownedRecords: ownedElectronLaunchRecords,
823
982
  });
824
983
  const electronHostResult = await handleElectronHostInput({
984
+ attachedSessionKeys,
825
985
  compiledElectron,
826
986
  cwd: ctx.cwd,
827
987
  electronChildProcesses,
@@ -831,6 +991,7 @@ export default function agentBrowserExtension(pi) {
831
991
  managedSessionName,
832
992
  managedSessionNamespace,
833
993
  managedSessionRestoreState,
994
+ ownedManagedSessions,
834
995
  redactedCompiledElectron,
835
996
  sessionPageState,
836
997
  signal,
@@ -858,6 +1019,8 @@ export default function agentBrowserExtension(pi) {
858
1019
  if (closedSessionName === managedSessionName) {
859
1020
  managedSessionActive = false;
860
1021
  managedSessionCompatibilityWorkaround = undefined;
1022
+ managedSessionHeadedAutosaveDisabled = false;
1023
+ managedSessionHeadedAutosaveInterval = undefined;
861
1024
  managedSessionNamespace = undefined;
862
1025
  freshSessionOrdinal += 1;
863
1026
  managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
@@ -891,6 +1054,7 @@ export default function agentBrowserExtension(pi) {
891
1054
  const browserRunState = {
892
1055
  allowedDomainsBySession,
893
1056
  artifactManifest,
1057
+ attachedSessionKeys,
894
1058
  closedManagedSessionNames: new Set(),
895
1059
  electronChildProcesses,
896
1060
  electronLaunchRecords,
@@ -899,6 +1063,8 @@ export default function agentBrowserExtension(pi) {
899
1063
  managedSessionActive,
900
1064
  managedSessionBaseName,
901
1065
  managedSessionCompatibilityWorkaround,
1066
+ managedSessionHeadedAutosaveDisabled,
1067
+ managedSessionHeadedAutosaveInterval,
902
1068
  managedSessionCwd,
903
1069
  managedSessionName,
904
1070
  managedSessionNamespace,
@@ -911,6 +1077,14 @@ export default function agentBrowserExtension(pi) {
911
1077
  const initialAllowedDomainsBySession = browserRunState.allowedDomainsBySession;
912
1078
  const initialArtifactManifest = browserRunState.artifactManifest;
913
1079
  const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1080
+ const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1081
+ || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1082
+ const allocatesFreshManagedSession = explicitSessionName === undefined
1083
+ && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1084
+ const reusableSessionKey = allocatesFreshManagedSession
1085
+ ? undefined
1086
+ : callerOwnedSessionQueueKey ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1087
+ const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
914
1088
  let result = await runAgentBrowserTool({
915
1089
  ctx,
916
1090
  cwd: ctx.cwd,
@@ -921,12 +1095,34 @@ export default function agentBrowserExtension(pi) {
921
1095
  input: resolvedInput,
922
1096
  onUpdate,
923
1097
  params,
1098
+ establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1099
+ preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
924
1100
  promptPolicy,
925
1101
  sessionPageStateUpdate,
926
1102
  signal,
927
1103
  state: browserRunState,
928
1104
  });
929
1105
  const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1106
+ if (branchRestoreStillCurrent) {
1107
+ const resultDetails = isRecord(result.details) ? result.details : undefined;
1108
+ const resultSessionName = typeof resultDetails?.sessionName === "string"
1109
+ ? resultDetails.sessionName
1110
+ : extractExplicitSessionName(toolArgs);
1111
+ const resultNamespace = typeof resultDetails?.namespace === "string"
1112
+ ? resultDetails.namespace
1113
+ : resolveAgentBrowserNamespace(toolArgs, process.env.AGENT_BROWSER_NAMESPACE);
1114
+ const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1115
+ const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1116
+ const attachedSessionRemainsActive = result.isError !== true
1117
+ || (attachedSessionRequested && managedSessionOutcome?.activeAfter === true);
1118
+ const closesAttachedSession = result.isError !== true && isCloseCommand(extractCommandTokens(toolArgs)[0]);
1119
+ if (resultSessionKey && closesAttachedSession)
1120
+ attachedSessionKeys.delete(resultSessionKey);
1121
+ else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1122
+ attachedSessionKeys.add(resultSessionKey);
1123
+ result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1124
+ }
1125
+ }
930
1126
  if (branchRestoreStillCurrent) {
931
1127
  allowedDomainsBySession = mergeBrowserRunMap(allowedDomainsBySession, initialAllowedDomainsBySession, browserRunState.allowedDomainsBySession);
932
1128
  networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
@@ -947,6 +1143,8 @@ export default function agentBrowserExtension(pi) {
947
1143
  freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
948
1144
  managedSessionActive = browserRunState.managedSessionActive;
949
1145
  managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1146
+ managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1147
+ managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
950
1148
  managedSessionCwd = browserRunState.managedSessionCwd;
951
1149
  managedSessionName = browserRunState.managedSessionName;
952
1150
  managedSessionNamespace = browserRunState.managedSessionNamespace;
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Parse raw agent-browser argv once into a stable command descriptor for planners and policy.
3
- * Responsibilities: Own command-token extraction, command/subcommand identification, and descriptor construction.
4
- * Scope: Pure argv parsing; runtime planning and session policy consume descriptors instead of re-parsing tokens.
5
- */
6
1
  import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
7
2
  import { isOpenNavigationCommand } from "./command-taxonomy.js";
8
3
  function isBooleanLiteral(token) {
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Shared argv flag-shape metadata and helpers for command discovery and sessionless policy checks.
3
- * Responsibilities: Own global/command value-flag sets and boolean/value-flag validation used during argv parsing.
4
- * Scope: Pure token grammar; command semantics and subprocess execution live elsewhere.
5
- */
6
1
  import { isKnownCommandToken } from "./command-taxonomy.js";
7
2
  export const GLOBAL_VALUE_FLAGS = [
8
3
  "--session",
@@ -125,6 +120,10 @@ export function getBooleanFlagValue(args, flag) {
125
120
  export function isBooleanFlagEnabled(args, flag) {
126
121
  return getBooleanFlagValue(args, flag) ?? false;
127
122
  }
123
+ /** Match upstream env_var_is_truthy exactly: lowercase only, without trimming or accepting "off". */
124
+ export function isUpstreamEnvFlagEnabled(value) {
125
+ return value !== undefined && !["", "0", "false", "no"].includes(value.toLowerCase());
126
+ }
128
127
  /** Mirror upstream sanitize_session_component for namespace/socket/state identity. */
129
128
  export function canonicalizeAgentBrowserNamespace(value) {
130
129
  if (value === undefined)
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Own upstream command-shape policies that decide whether the wrapper should allocate a managed browser session.
3
- * Responsibilities: Keep local/sessionless command grammar out of the runtime execution planner while preserving exact upstream shapes.
4
- * Scope: Pure argv-token policy; command discovery, subprocess execution, and presentation live in focused modules.
5
- */
6
1
  import { hasOnlyBooleanFlags, hasOnlyOptionFlags, isNonFlagToken, stripSessionlessShapeGlobalFlags } from "./argv-grammar.js";
7
2
  const SESSIONLESS_AUTH_SUBCOMMANDS = new Set(["save", "list", "show", "delete", "remove"]);
8
3
  const PLUGIN_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "add", "run"]);
@@ -1,9 +1,3 @@
1
- /**
2
- * Purpose: Centralize upstream agent-browser command capabilities that wrapper behavior depends on.
3
- * Responsibilities: Normalize command aliases once and expose capability predicates for runtime planning,
4
- * session/ref guards, result recommendations, and presentation summaries without coupling unrelated behaviors.
5
- * Scope: Static command capability taxonomy only; command-shape parsing, spawning, and formatting live elsewhere.
6
- */
7
1
  const ADDITIONAL_COMMAND_TOKENS = [
8
2
  "a11y", "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
9
3
  ];
@@ -1,9 +1,4 @@
1
1
  // @ts-check
2
- /**
3
- * Purpose: Canonical pi-agent-browser-native config policy shared by runtime and setup CLI.
4
- * Responsibilities: Own config paths, provider descriptors, credential source parsing, layer validation/merge, status projection, and redacted summaries.
5
- * Scope: Pure configuration policy plus synchronous status loading; secret command execution and browser/web-search runtime calls live elsewhere.
6
- */
7
2
  import { existsSync, readFileSync } from "node:fs";
8
3
  import { homedir } from "node:os";
9
4
  import { join, resolve } from "node:path";
@@ -1,9 +1,3 @@
1
- /**
2
- * Purpose: Load pi-agent-browser-native package configuration from Pi-scoped global, project, or explicit paths.
3
- * Responsibilities: Resolve config layers, resolve secrets without exposing values, and provide redacted status for tools/CLIs.
4
- * Scope: Package-owned configuration only; canonical config policy lives in config-policy.js, browser command execution and web-search API calls live in focused modules.
5
- * Invariants/Assumptions: Credential sources from loaded config are passed through to the runtime; command credentials are resolved lazily at execution time and displayed values stay redacted.
6
- */
7
1
  import { exec as execCallback } from "node:child_process";
8
2
  import { readFile } from "node:fs/promises";
9
3
  import { promisify } from "node:util";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Parse and fetch Chrome DevTools Protocol metadata for wrapper-owned Electron launches.
3
- * Responsibilities: Normalize CDP version/target JSON and perform bounded localhost CDP JSON fetches.
4
- * Scope: Tiny Electron CDP boundary helpers only; launch, status, cleanup, and target selection stay in their owning modules.
5
- * Usage: Imported by Electron launch and cleanup paths when polling `/json/version` and `/json/list`.
6
- * Invariants/Assumptions: Malformed or unavailable CDP endpoints return undefined/empty metadata rather than throwing, matching prior caller behavior.
7
- */
8
1
  import { isRecord } from "../parsing.js";
9
2
  const ELECTRON_CDP_FETCH_TIMEOUT_MS = 1_000;
10
3
  function asString(value) {
@@ -49,3 +42,9 @@ export async function fetchCdpJson(url, signal) {
49
42
  clearTimeout(timeout);
50
43
  }
51
44
  }
45
+ export function boundElectronProbeString(value, maxLength = 240) {
46
+ const trimmed = value?.trim();
47
+ if (!trimmed)
48
+ return undefined;
49
+ return trimmed.length > maxLength ? `${trimmed.slice(0, Math.max(0, maxLength - 3))}...` : trimmed;
50
+ }
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Inspect and clean wrapper-owned Electron launch resources.
3
- * Responsibilities: Report CDP liveness/targets without mutation and remove only tracked process/profile resources during explicit or shutdown cleanup.
4
- * Scope: Host-side Electron status and resource cleanup only; upstream managed-session close remains in the extension entrypoint.
5
- * Usage: Called by electron.status, electron.cleanup, and session_shutdown handling.
6
- * Invariants/Assumptions: Cleanup operates only on launch records produced by this wrapper and prefers partial cleanup reporting over killing or deleting untracked resources.
7
- */
8
1
  import { execFile } from "node:child_process";
9
2
  import { rm } from "node:fs/promises";
10
3
  import { promisify } from "node:util";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Discover installed Electron desktop applications without invoking upstream agent-browser.
3
- * Responsibilities: Scan bounded macOS app bundles and Linux .desktop launchers, apply Electron framework evidence gates, and return small platform-tagged app metadata.
4
- * Scope: Discovery only; launch, cleanup, status, and CDP attachment live in later Electron lifecycle work items.
5
- * Usage: Called by the agent_browser top-level electron.list shorthand and directly by tests through parameterized scan locations.
6
- * Invariants/Assumptions: Discovery is best-effort, missing scan roots are ignored, malformed .desktop files are skipped, and results are capped before they reach model-visible output.
7
- */
8
1
  import { constants as fsConstants } from "node:fs";
9
2
  import { access, readdir, readFile, realpath, stat } from "node:fs/promises";
10
3
  import { homedir } from "node:os";
@@ -1,10 +1,3 @@
1
- /**
2
- * Purpose: Launch wrapper-owned Electron applications and discover their CDP endpoint.
3
- * Responsibilities: Resolve Electron targets, enforce caller-owned allow/deny policy, create isolated userDataDir profiles, launch with remote debugging on an OS-chosen port, poll DevToolsActivePort, and read bounded CDP version/target metadata.
4
- * Scope: Host-side Electron lifecycle setup only; upstream agent-browser attach/presentation stays in the extension entrypoint.
5
- * Usage: Called by the agent_browser electron.launch shorthand before routing through upstream `connect`.
6
- * Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile, never accepts a caller-supplied remote debugging port, and cleans any spawned process/profile when cancellation interrupts readiness.
7
- */
8
1
  import { spawn } from "node:child_process";
9
2
  import { randomUUID } from "node:crypto";
10
3
  import { readFile, rm } from "node:fs/promises";
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile top-level Electron wrapper inputs into validated Electron actions.
3
- * Responsibilities: Enforce action-specific fields, launch-target rules, and wrapper-owned flag safety.
4
- * Scope: Electron input-mode validation only; launch/probe/cleanup execution stays in the extension entrypoint.
5
- */
6
1
  import { isRecord } from "../parsing.js";
7
2
  import { AGENT_BROWSER_ELECTRON_ACTIONS, AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_LIST_FIELDS, AGENT_BROWSER_ELECTRON_PROBE_FIELDS, AGENT_BROWSER_ELECTRON_RESERVED_APP_ARGS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, } from "./types.js";
8
3
  function validateOptionalNonEmptyString(input, fieldName) {
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile constrained job and lightweight QA wrapper inputs to upstream batch commands.
3
- * Responsibilities: Validate job/QA fields, produce argv/stdin, and summarize QA diagnostic results.
4
- * Scope: Job and QA modes only.
5
- */
6
1
  import { isRecord } from "../parsing.js";
7
2
  import { summarizeNetworkFailures } from "../results/network.js";
8
3
  import { getBatchResultItems, getCommandNameFromBatchItem, getSelectValues } from "./shared.js";
@@ -1,8 +1,3 @@
1
- /**
2
- * Purpose: Compile and analyze experimental DOM/source and network source lookup input modes.
3
- * Responsibilities: Build lookup batches, collect DOM/React/workspace candidates, and redact network lookup surfaces.
4
- * Scope: Source lookup and network source lookup only.
5
- */
6
1
  import { readFile, readdir } from "node:fs/promises";
7
2
  import { extname, join } from "node:path";
8
3
  import { isRecord } from "../parsing.js";