@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 (291) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Cross-agent, tamper-evident audit log of every dispatched run (issue #347).
3
+ *
4
+ * Every run that reaches the single dispatch chokepoint in `agents run`
5
+ * (src/commands/exec.ts) appends ONE record here. Records form a hash chain:
6
+ * each record embeds the previous record's `hash` as `prevHash`, and its own
7
+ * `hash` is `sha256(canonicalJSON(record-without-hash))`. Because `prevHash`
8
+ * is part of the hashed payload, rewriting or reordering any record breaks the
9
+ * chain from that point forward — `verifyAuditChain()` reports the first index
10
+ * that fails to reproduce.
11
+ *
12
+ * Storage is append-only JSONL under the durable-runtime bucket
13
+ * (`~/.agents/.history/audit/log.jsonl`), one record per line. `.history/` is
14
+ * machine-local, gitignored, and NEVER synced by `agents repo push/pull` — so
15
+ * the `repo` field (a git remote url that can embed an access token) never
16
+ * lands in a version-controlled DotAgents repo, and a cross-machine pull can't
17
+ * splice a foreign chain into this one. The write path is deliberately cheap
18
+ * and non-fatal: a logging failure must never crash a run (see
19
+ * `recordDispatchedRun`). Concurrent writers (parallel teams/routines dispatch)
20
+ * are serialized by an advisory lock file so the chain never forks — see
21
+ * `appendAuditRecord`.
22
+ *
23
+ * Governance context: this is the evidence trail an operator needs to answer
24
+ * "which agent, at which version, ran against which repo, and did it succeed?"
25
+ * — the kind of automated-decision logging the EU AI Act (Art. 12) expects of
26
+ * high-risk systems, kept tamper-evident so the log itself can be trusted.
27
+ */
28
+ import * as fs from 'fs';
29
+ import * as path from 'path';
30
+ import { spawnSync } from 'child_process';
31
+ import { sha256 } from '../staleness/fingerprint.js';
32
+ import { getHistoryDir } from '../state.js';
33
+ import { ensureLockTarget, withFileLock } from '../fs-atomic.js';
34
+ /** First record's `prevHash` — a fixed anchor so the chain has a root. */
35
+ export const GENESIS_HASH = 'GENESIS';
36
+ /**
37
+ * Absolute path to the append-only audit log. Lives under `.history/` — the
38
+ * durable-but-machine-local runtime bucket that is gitignored and never synced
39
+ * by `agents repo push/pull`. Keeping it here (not under a top-level, tracked
40
+ * `~/.agents/` path) means the token-bearing `repo` field can never leak into a
41
+ * version-controlled DotAgents repo, and no cross-machine pull can fork the chain.
42
+ */
43
+ export function getAuditLogPath() {
44
+ return path.join(getHistoryDir(), 'audit', 'log.jsonl');
45
+ }
46
+ /**
47
+ * Deterministic JSON: object keys sorted recursively so the same logical
48
+ * record always serializes to the same bytes (hashing must be reproducible
49
+ * across processes and machines). Values here are all primitives, but the
50
+ * recursion keeps it correct if the record ever nests.
51
+ */
52
+ function canonicalJSON(value) {
53
+ if (value === null || typeof value !== 'object')
54
+ return JSON.stringify(value);
55
+ if (Array.isArray(value))
56
+ return '[' + value.map(canonicalJSON).join(',') + ']';
57
+ const obj = value;
58
+ const keys = Object.keys(obj).sort();
59
+ return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalJSON(obj[k])).join(',') + '}';
60
+ }
61
+ /** Compute the sealing hash for a record whose `prevHash` is already set. */
62
+ function hashRecord(record) {
63
+ return sha256(canonicalJSON(record));
64
+ }
65
+ /** Read + parse every record in the log, oldest-first. Missing file → []. */
66
+ function readRecords(logPath) {
67
+ let raw;
68
+ try {
69
+ raw = fs.readFileSync(logPath, 'utf-8');
70
+ }
71
+ catch {
72
+ return []; // no log yet
73
+ }
74
+ const records = [];
75
+ for (const line of raw.split('\n')) {
76
+ const trimmed = line.trim();
77
+ if (!trimmed)
78
+ continue;
79
+ records.push(JSON.parse(trimmed));
80
+ }
81
+ return records;
82
+ }
83
+ /**
84
+ * Append one record to the hash chain and return it. Links to the previous
85
+ * record's `hash` (or `GENESIS_HASH` for the first), computes the sealing
86
+ * hash, and writes a single JSONL line. Synchronous so the record is durable
87
+ * before the caller proceeds.
88
+ *
89
+ * Read-last-hash + append run under the canonical advisory file lock
90
+ * (`withFileLock`, backed by proper-lockfile). Without it, two concurrent
91
+ * writers — the norm under parallel teams/routines dispatch — both read the
92
+ * same last hash and both write `prevHash=H`, forking the chain into a false
93
+ * "tampered" verdict. The lock forces a total order so every record links off
94
+ * the genuinely-previous one. `ensureLockTarget` creates the file first because
95
+ * proper-lockfile locks an existing path.
96
+ */
97
+ export function appendAuditRecord(entry, logPath = getAuditLogPath()) {
98
+ ensureLockTarget(logPath);
99
+ return withFileLock(logPath, () => {
100
+ const existing = readRecords(logPath);
101
+ const prevHash = existing.length ? existing[existing.length - 1].hash : GENESIS_HASH;
102
+ const unsealed = { ...entry, prevHash };
103
+ const record = { ...unsealed, hash: hashRecord(unsealed) };
104
+ fs.appendFileSync(logPath, JSON.stringify(record) + '\n');
105
+ return record;
106
+ });
107
+ }
108
+ /**
109
+ * Walk the chain and confirm every record reproduces. A record is valid when
110
+ * (a) its `prevHash` matches the prior record's `hash` (GENESIS for the first)
111
+ * AND (b) recomputing its sealing hash from its own fields reproduces the
112
+ * stored `hash`. Returns the first failing index in `brokenAt`.
113
+ */
114
+ export function verifyAuditChain(logPath = getAuditLogPath()) {
115
+ let records;
116
+ try {
117
+ records = readRecords(logPath);
118
+ }
119
+ catch (err) {
120
+ // A line that won't even parse is itself tamper evidence — the log is corrupt.
121
+ return { ok: false, brokenAt: 0 };
122
+ }
123
+ let expectedPrev = GENESIS_HASH;
124
+ for (let i = 0; i < records.length; i++) {
125
+ const r = records[i];
126
+ if (r.prevHash !== expectedPrev)
127
+ return { ok: false, brokenAt: i };
128
+ const { hash, ...unsealed } = r;
129
+ if (hashRecord(unsealed) !== hash)
130
+ return { ok: false, brokenAt: i };
131
+ expectedPrev = r.hash;
132
+ }
133
+ return { ok: true };
134
+ }
135
+ /** Read the whole chain, oldest-first. Exposed for `agents audit list`. */
136
+ export function readAuditLog(logPath = getAuditLogPath()) {
137
+ return readRecords(logPath);
138
+ }
139
+ /**
140
+ * Resolve a stable repo label for a run: the git remote origin url when the
141
+ * cwd is inside a repo with one, otherwise the cwd itself. Best-effort — any
142
+ * failure falls back to the cwd.
143
+ */
144
+ function repoLabel(cwd) {
145
+ try {
146
+ const res = spawnSync('git', ['-C', cwd, 'config', '--get', 'remote.origin.url'], {
147
+ encoding: 'utf-8',
148
+ timeout: 2000,
149
+ });
150
+ const url = res.status === 0 ? res.stdout.trim() : '';
151
+ return url || cwd;
152
+ }
153
+ catch {
154
+ return cwd;
155
+ }
156
+ }
157
+ /**
158
+ * Record ONE dispatched run at the single exec chokepoint. Non-fatal by
159
+ * contract: any failure is caught and warned, never thrown — an audit-log
160
+ * hiccup must not crash a run that already finished.
161
+ */
162
+ export function recordDispatchedRun(run) {
163
+ try {
164
+ appendAuditRecord({
165
+ ts: new Date().toISOString(),
166
+ agent: run.agent,
167
+ version: run.version,
168
+ repo: repoLabel(run.cwd),
169
+ mode: run.mode,
170
+ outcome: run.exitCode === 0 ? 'ok' : 'fail',
171
+ exit: run.exitCode,
172
+ });
173
+ }
174
+ catch (err) {
175
+ process.stderr.write(`[agents] audit log write failed: ${err.message}\n`);
176
+ }
177
+ }
@@ -14,6 +14,7 @@ import * as path from 'path';
14
14
  import { spawn } from 'child_process';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { getFetchCacheDir } from './state.js';
17
+ import { backgroundSpawnOptions } from './platform/process.js';
17
18
  /** Where lock files and per-repo status markers live. */
18
19
  function fetchStateDir() {
19
20
  return getFetchCacheDir();
@@ -38,7 +39,7 @@ export function spawnDetachedSync() {
38
39
  return;
39
40
  try {
40
41
  const child = spawn(process.execPath, [workerPath], {
41
- detached: true,
42
+ ...backgroundSpawnOptions(),
42
43
  stdio: 'ignore',
43
44
  env: process.env,
44
45
  });
@@ -219,7 +219,11 @@ export function verifyBrowserIdentity(reported, expected, port, host = 'localhos
219
219
  // to a Comet instance doesn't trip a false "identity mismatch".
220
220
  comet: ['comet', 'chrome'],
221
221
  brave: ['brave', 'brave-browser', 'chrome'],
222
- edge: ['edge', 'microsoft-edge', 'msedge', 'chrome'],
222
+ // Windows/Chromium Edge reports its /json/version "Browser" field as
223
+ // "Edg/<version>" (the `Edg` token, not `Edge`), which normalizeBrowserName
224
+ // reduces to "edg". Accept it so attaching to a real Edge doesn't trip a
225
+ // false identity mismatch (the profile enum only allows "edge").
226
+ edge: ['edge', 'edg', 'microsoft-edge', 'msedge', 'chrome'],
223
227
  };
224
228
  const accepted = matches[expected] || [expected];
225
229
  if (accepted.includes(reported))
@@ -36,6 +36,16 @@ export declare function getRunningChromeInfo(profileName: string): {
36
36
  pid: number;
37
37
  port: number;
38
38
  } | null;
39
+ /**
40
+ * Is a TCP port currently bound? `lsof` on POSIX, `netstat -ano` on Windows
41
+ * (lsof doesn't exist there). Returns false on any tooling error so port
42
+ * allocation degrades to "assume free" rather than throwing.
43
+ *
44
+ * Exported as the canonical cross-platform probe — `findFreeProfilePort`
45
+ * (profiles.ts) must use this rather than shelling out to lsof directly,
46
+ * or every port scans as free on Windows.
47
+ */
48
+ export declare function isPortInUse(port: number): boolean;
39
49
  export declare function allocatePort(): number;
40
50
  export interface PortOccupant {
41
51
  pid: number;
@@ -39,7 +39,11 @@ const BROWSER_PATHS = {
39
39
  `${WIN_PROGRAMFILES_X86}\\Google\\Chrome\\Application\\chrome.exe`,
40
40
  `${WIN_LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
41
41
  ],
42
- comet: [],
42
+ comet: [
43
+ `${WIN_PROGRAMFILES}\\Perplexity\\Comet\\Application\\comet.exe`,
44
+ `${WIN_PROGRAMFILES_X86}\\Perplexity\\Comet\\Application\\comet.exe`,
45
+ `${WIN_LOCALAPPDATA}\\Perplexity\\Comet\\Application\\comet.exe`,
46
+ ],
43
47
  chromium: [
44
48
  `${WIN_LOCALAPPDATA}\\Chromium\\Application\\chrome.exe`,
45
49
  ],
@@ -155,8 +159,8 @@ export function findBrowserPath(browserType, customBinary) {
155
159
  return resolveBrowserBinary(p);
156
160
  }
157
161
  }
158
- if (browserType === 'comet' && platform !== 'darwin') {
159
- throw new Error('Browser "comet" is macOS-only (Comet is a macOS Chromium fork). Use chrome, chromium, brave, or edge on this platform.');
162
+ if (browserType === 'comet' && platform === 'linux') {
163
+ throw new Error('Browser "comet" is not available on Linux (Comet ships macOS and Windows builds only). Use chrome, chromium, brave, or edge on this platform.');
160
164
  }
161
165
  throw new Error(`Browser "${browserType}" not found. Install it first.`);
162
166
  }
@@ -171,7 +175,7 @@ const DEFAULT_BROWSER_PRIORITY = {
171
175
  linux: ['chrome', 'chromium', 'brave', 'edge'],
172
176
  // Windows: Edge is preinstalled on every supported build, so it's the
173
177
  // reliable always-there default.
174
- win32: ['edge', 'chrome', 'brave'],
178
+ win32: ['edge', 'chrome', 'brave', 'comet'],
175
179
  };
176
180
  /**
177
181
  * Walk the per-platform priority list and return the first browser that's
@@ -290,11 +294,11 @@ export function killChrome(pid) {
290
294
  // stop first (no /F → posts WM_CLOSE), and only force-kill the tree if that
291
295
  // fails or the process is still around.
292
296
  try {
293
- execFileSync('taskkill', ['/pid', String(pid)], { stdio: 'ignore' });
297
+ execFileSync('taskkill', ['/pid', String(pid)], { stdio: 'ignore', windowsHide: true });
294
298
  }
295
299
  catch {
296
300
  try {
297
- execFileSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
301
+ execFileSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore', windowsHide: true });
298
302
  }
299
303
  catch {
300
304
  // Process already dead
@@ -345,13 +349,18 @@ function sleep(ms) {
345
349
  * Is a TCP port currently bound? `lsof` on POSIX, `netstat -ano` on Windows
346
350
  * (lsof doesn't exist there). Returns false on any tooling error so port
347
351
  * allocation degrades to "assume free" rather than throwing.
352
+ *
353
+ * Exported as the canonical cross-platform probe — `findFreeProfilePort`
354
+ * (profiles.ts) must use this rather than shelling out to lsof directly,
355
+ * or every port scans as free on Windows.
348
356
  */
349
- function isPortInUse(port) {
357
+ export function isPortInUse(port) {
350
358
  if (process.platform === 'win32') {
351
359
  try {
352
360
  const out = execFileSync('netstat', ['-ano', '-p', 'TCP'], {
353
361
  encoding: 'utf8',
354
362
  stdio: ['ignore', 'pipe', 'ignore'],
363
+ windowsHide: true,
355
364
  });
356
365
  // Lines look like: " TCP 0.0.0.0:9200 0.0.0.0:0 LISTENING 1234"
357
366
  return out.split('\n').some((line) => {
@@ -393,6 +402,7 @@ export function getPortOccupant(port) {
393
402
  const out = execFileSync('netstat', ['-ano', '-p', 'TCP'], {
394
403
  encoding: 'utf8',
395
404
  stdio: ['ignore', 'pipe', 'ignore'],
405
+ windowsHide: true,
396
406
  });
397
407
  let pid = 0;
398
408
  for (const line of out.split('\n')) {
@@ -410,6 +420,7 @@ export function getPortOccupant(port) {
410
420
  const tl = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
411
421
  encoding: 'utf8',
412
422
  stdio: ['ignore', 'pipe', 'ignore'],
423
+ windowsHide: true,
413
424
  });
414
425
  const m = tl.match(/^"([^"]+)"/);
415
426
  if (m)
@@ -53,7 +53,15 @@ export declare function buildLaunchCmd(remoteOs: RemoteOs, browserType: string,
53
53
  * (Get-NetTCPConnection → Stop-Process).
54
54
  */
55
55
  export declare function buildKillCmd(remoteOs: RemoteOs, port: number): string;
56
+ export declare function ensureRemoteBrowser(user: string, host: string, browserType: string, port: number, remoteOs: RemoteOs, customBinary?: string): Promise<void>;
56
57
  export declare function restartRemoteBrowser(user: string, host: string, browserType: string, port: number, remoteOs: RemoteOs, customBinary?: string): Promise<void>;
58
+ /**
59
+ * Kill the remote browser holding the CDP port. Invoked on stop/cleanup so the
60
+ * WMI-spawned (Windows) / detached (posix) browser process is not orphaned when
61
+ * the ssh tunnel is torn down — killing only the tunnel left it running on
62
+ * win-mini after every `browser stop`. Best-effort: never rejects.
63
+ */
64
+ export declare function killRemoteBrowser(user: string, host: string, remoteOs: RemoteOs, port: number): Promise<void>;
57
65
  /**
58
66
  * Identify whether a pid listening on our target local port is an SSH
59
67
  * tunnel WE would have spawned for `host:remotePort`. Used so that two
@@ -48,12 +48,12 @@ export async function connectSSH(endpoint, profile) {
48
48
  `is already in use by ${occupant.command} (pid ${occupant.pid}). ` +
49
49
  `Either kill that process (\`kill ${occupant.pid}\`) or change the profile's port.`);
50
50
  }
51
- try {
52
- await ensureRemoteBrowser(user, host, profile.browser, remotePort, remoteOs, profile.binary);
53
- }
54
- catch {
55
- // Browser may already be running, continue
56
- }
51
+ // A non-zero remote exit (missing exe, auth failure, bad launch) now rejects
52
+ // with the captured stderr and propagates. An already-running browser exits 0
53
+ // (the launch backgrounds/WMI-spawns regardless), so this does not spuriously
54
+ // fail the reconnect path — only genuine launch failures reach the caller
55
+ // instead of being swallowed and mis-reported later as a tunnel timeout.
56
+ await ensureRemoteBrowser(user, host, profile.browser, remotePort, remoteOs, profile.binary);
57
57
  let tunnel;
58
58
  if (occupant) {
59
59
  // Reuse the existing tunnel rather than spawning a duplicate.
@@ -102,6 +102,11 @@ export async function connectSSH(endpoint, profile) {
102
102
  pid: tunnelPid,
103
103
  cleanup: () => {
104
104
  cdp.close();
105
+ // Kill the remote browser BEFORE tearing down the tunnel. It runs on a
106
+ // separate ssh connection (independent of the tunnel we're about to
107
+ // kill), so tunnel teardown can't cut it off. Fire-and-forget — cleanup
108
+ // stays synchronous, and killRemoteBrowser never rejects.
109
+ killRemoteBrowser(user, host, remoteOs, remotePort).catch(() => { });
105
110
  tunnel.kill();
106
111
  clearProfileRuntime(profile.name);
107
112
  },
@@ -177,9 +182,9 @@ export function encodePowerShell(script) {
177
182
  * registry at runtime rather than relying on a bare `msedge` name.
178
183
  */
179
184
  export function buildWindowsLaunchScript(browserType, port, customBinary) {
180
- let exeExpr;
185
+ let exeStmts;
181
186
  if (customBinary) {
182
- exeExpr = psSingleQuote(customBinary);
187
+ exeStmts = [`$exe = ${psSingleQuote(customBinary)}`];
183
188
  }
184
189
  else if (browserType === 'custom') {
185
190
  throw new Error('browser: custom requires a binary path in the profile');
@@ -188,13 +193,24 @@ export function buildWindowsLaunchScript(browserType, port, customBinary) {
188
193
  const exeKey = WIN_BROWSER_APPPATH[browserType];
189
194
  if (!exeKey)
190
195
  throw new Error(`Unknown browser type for windows remote: ${browserType}`);
191
- exeExpr =
192
- `(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${exeKey}').'(default)'`;
196
+ const appPath = `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${exeKey}`;
197
+ // Per-user installs — the DEFAULT for Edge and Chrome — register App Paths
198
+ // under HKCU, not HKLM, so an HKLM-only lookup silently missed them and the
199
+ // launch failed. Resolve from HKLM first, then fall through to HKCU.
200
+ // Windows PowerShell 5.1 (what `powershell.exe` is) has no `??`, so the
201
+ // fallback is an explicit `if`. A missing exe `throw`s so the failure rides
202
+ // out as a non-zero ssh exit (surfaced by ensureRemoteBrowser) instead of a
203
+ // silent empty launch.
204
+ exeStmts = [
205
+ `$exe = (Get-ItemProperty 'HKLM:\\${appPath}' -EA SilentlyContinue).'(default)'`,
206
+ `if (-not $exe) { $exe = (Get-ItemProperty 'HKCU:\\${appPath}' -EA SilentlyContinue).'(default)' }`,
207
+ `if (-not $exe) { throw 'browser exe not found in HKLM/HKCU App Paths: ${exeKey}' }`,
208
+ ];
193
209
  }
194
210
  // Keep the `--remote-allow-origins=http://127.0.0.1:${port}` literal in
195
211
  // source — a test asserts CDP is never opened to `*`.
196
212
  return [
197
- `$exe = ${exeExpr}`,
213
+ ...exeStmts,
198
214
  `$cl = '"' + $exe + '" --remote-debugging-port=${port}` +
199
215
  ` --remote-allow-origins=http://127.0.0.1:${port}` +
200
216
  ` --disable-background-timer-throttling --user-data-dir="' + $env:TEMP + '\\agents-browser-${port}"'`,
@@ -242,21 +258,56 @@ export function buildKillCmd(remoteOs, port) {
242
258
  }
243
259
  return `pids=$(lsof -ti ${shellQuote(`:${port}`)} 2>/dev/null); [ -z "$pids" ] || kill -9 $pids 2>/dev/null || true`;
244
260
  }
245
- async function ensureRemoteBrowser(user, host, browserType, port, remoteOs, customBinary) {
261
+ export async function ensureRemoteBrowser(user, host, browserType, port, remoteOs, customBinary) {
246
262
  const remoteCmd = buildLaunchCmd(remoteOs, browserType, port, customBinary);
247
263
  return new Promise((resolve, reject) => {
264
+ // Capture stderr so a real launch failure (missing exe, auth denied, bad
265
+ // CreateProcess) surfaces here as a clear error. Previously stdio was
266
+ // ignored and this resolved on close regardless of exit code, so failures
267
+ // were swallowed and only re-emerged 8s later as a generic
268
+ // "SSH tunnel failed to establish".
248
269
  const child = spawn('ssh', [
249
270
  `${user}@${host}`,
250
271
  '-o',
251
272
  'BatchMode=yes',
252
273
  remoteCmd,
253
- ], { stdio: 'ignore' });
254
- child.on('close', () => resolve());
255
- child.on('error', reject);
256
- setTimeout(() => {
274
+ ], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
275
+ let stderr = '';
276
+ child.stderr?.on('data', (d) => {
277
+ stderr += d.toString();
278
+ });
279
+ let settled = false;
280
+ const timer = setTimeout(() => {
281
+ if (settled)
282
+ return;
283
+ settled = true;
284
+ // ssh is still running the backgrounding launch command past our window.
285
+ // Killing the local ssh client does NOT reap the WMI/detached remote
286
+ // browser, so treat this as launched and let waitForPort confirm.
257
287
  child.kill();
258
288
  resolve();
259
289
  }, 2000);
290
+ child.on('close', (code) => {
291
+ if (settled)
292
+ return;
293
+ settled = true;
294
+ clearTimeout(timer);
295
+ if (code && code !== 0) {
296
+ const detail = stderr.trim();
297
+ reject(new Error(`Remote browser launch failed on ${host} (ssh exit ${code})` +
298
+ (detail ? `: ${detail}` : '')));
299
+ }
300
+ else {
301
+ resolve();
302
+ }
303
+ });
304
+ child.on('error', (err) => {
305
+ if (settled)
306
+ return;
307
+ settled = true;
308
+ clearTimeout(timer);
309
+ reject(err);
310
+ });
260
311
  });
261
312
  }
262
313
  export async function restartRemoteBrowser(user, host, browserType, port, remoteOs, customBinary) {
@@ -266,10 +317,20 @@ export async function restartRemoteBrowser(user, host, browserType, port, remote
266
317
  await ensureRemoteBrowser(user, host, browserType, port, remoteOs, customBinary);
267
318
  await sleep(1500);
268
319
  }
320
+ /**
321
+ * Kill the remote browser holding the CDP port. Invoked on stop/cleanup so the
322
+ * WMI-spawned (Windows) / detached (posix) browser process is not orphaned when
323
+ * the ssh tunnel is torn down — killing only the tunnel left it running on
324
+ * win-mini after every `browser stop`. Best-effort: never rejects.
325
+ */
326
+ export function killRemoteBrowser(user, host, remoteOs, port) {
327
+ return runSSHCommand(user, host, buildKillCmd(remoteOs, port));
328
+ }
269
329
  function runSSHCommand(user, host, cmd) {
270
330
  return new Promise((resolve) => {
271
331
  const child = spawn('ssh', [`${user}@${host}`, '-o', 'BatchMode=yes', cmd], {
272
332
  stdio: 'ignore',
333
+ windowsHide: true,
273
334
  });
274
335
  child.on('close', () => resolve());
275
336
  child.on('error', () => resolve());
@@ -0,0 +1,84 @@
1
+ import type { NetworkRequest } from './types.js';
2
+ /**
3
+ * Minimal HAR 1.2 shapes. We only produce what our request buffer can back —
4
+ * fields the buffer doesn't populate (headers, cookies, bodies, timings) are
5
+ * emitted with the spec-mandated empty defaults or `-1` sentinels so validators
6
+ * still accept the document.
7
+ *
8
+ * Spec: http://www.softwareishard.com/blog/har-12-spec/
9
+ */
10
+ export interface HarLog {
11
+ version: '1.2';
12
+ creator: {
13
+ name: string;
14
+ version: string;
15
+ };
16
+ pages: HarPage[];
17
+ entries: HarEntry[];
18
+ }
19
+ export interface HarPage {
20
+ startedDateTime: string;
21
+ id: string;
22
+ title: string;
23
+ pageTimings: {
24
+ onContentLoad: number;
25
+ onLoad: number;
26
+ };
27
+ }
28
+ export interface HarEntry {
29
+ startedDateTime: string;
30
+ time: number;
31
+ request: HarRequest;
32
+ response: HarResponse;
33
+ cache: Record<string, never>;
34
+ timings: HarTimings;
35
+ }
36
+ export interface HarRequest {
37
+ method: string;
38
+ url: string;
39
+ httpVersion: string;
40
+ cookies: never[];
41
+ headers: never[];
42
+ queryString: Array<{
43
+ name: string;
44
+ value: string;
45
+ }>;
46
+ headersSize: number;
47
+ bodySize: number;
48
+ }
49
+ export interface HarResponse {
50
+ status: number;
51
+ statusText: string;
52
+ httpVersion: string;
53
+ cookies: never[];
54
+ headers: never[];
55
+ content: {
56
+ size: number;
57
+ mimeType: string;
58
+ text?: string;
59
+ };
60
+ redirectURL: string;
61
+ headersSize: number;
62
+ bodySize: number;
63
+ }
64
+ export interface HarTimings {
65
+ send: number;
66
+ wait: number;
67
+ receive: number;
68
+ }
69
+ export interface BuildHarOptions {
70
+ creatorName?: string;
71
+ creatorVersion?: string;
72
+ }
73
+ /**
74
+ * Serialize a buffered `NetworkRequest[]` into a HAR 1.2 document.
75
+ *
76
+ * The buffer only records `url`, `method`, `status`, `mimeType`, `timestamp`.
77
+ * Everything downstream — cookies, headers, bodies, per-phase timings — is
78
+ * unknown and must be emitted as the spec's "unknown" sentinels: `-1` for
79
+ * sizes / timings, empty arrays for cookies / headers. HAR validators accept
80
+ * this shape (browsers do the same for opaque cross-origin responses).
81
+ */
82
+ export declare function buildHar(requests: NetworkRequest[], opts?: BuildHarOptions): {
83
+ log: HarLog;
84
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Serialize a buffered `NetworkRequest[]` into a HAR 1.2 document.
3
+ *
4
+ * The buffer only records `url`, `method`, `status`, `mimeType`, `timestamp`.
5
+ * Everything downstream — cookies, headers, bodies, per-phase timings — is
6
+ * unknown and must be emitted as the spec's "unknown" sentinels: `-1` for
7
+ * sizes / timings, empty arrays for cookies / headers. HAR validators accept
8
+ * this shape (browsers do the same for opaque cross-origin responses).
9
+ */
10
+ export function buildHar(requests, opts = {}) {
11
+ const creator = {
12
+ name: opts.creatorName ?? 'agents-cli',
13
+ version: opts.creatorVersion ?? '',
14
+ };
15
+ const startedAt = requests.length > 0 ? Math.min(...requests.map((r) => r.timestamp)) : Date.now();
16
+ const page = {
17
+ startedDateTime: new Date(startedAt).toISOString(),
18
+ id: 'page_1',
19
+ title: 'agents browser session',
20
+ pageTimings: { onContentLoad: -1, onLoad: -1 },
21
+ };
22
+ const entries = requests.map((r) => {
23
+ const queryString = extractQueryString(r.url);
24
+ const mimeType = r.mimeType ?? '';
25
+ return {
26
+ startedDateTime: new Date(r.timestamp).toISOString(),
27
+ time: -1,
28
+ request: {
29
+ method: r.method,
30
+ url: r.url,
31
+ httpVersion: 'HTTP/1.1',
32
+ cookies: [],
33
+ headers: [],
34
+ queryString,
35
+ headersSize: -1,
36
+ bodySize: -1,
37
+ },
38
+ response: {
39
+ status: r.status ?? 0,
40
+ statusText: '',
41
+ httpVersion: 'HTTP/1.1',
42
+ cookies: [],
43
+ headers: [],
44
+ content: { size: -1, mimeType },
45
+ redirectURL: '',
46
+ headersSize: -1,
47
+ bodySize: -1,
48
+ },
49
+ cache: {},
50
+ timings: { send: -1, wait: -1, receive: -1 },
51
+ };
52
+ });
53
+ return {
54
+ log: {
55
+ version: '1.2',
56
+ creator,
57
+ pages: [page],
58
+ entries,
59
+ },
60
+ };
61
+ }
62
+ /**
63
+ * Pull `?a=1&b=2` off a URL into HAR's `queryString` array. Returns `[]` when
64
+ * the URL has no query or fails to parse (opaque URLs like `data:` shouldn't
65
+ * abort the whole HAR).
66
+ */
67
+ function extractQueryString(url) {
68
+ try {
69
+ const u = new URL(url);
70
+ const out = [];
71
+ u.searchParams.forEach((value, name) => out.push({ name, value }));
72
+ return out;
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ }
@@ -8,6 +8,9 @@ export declare class BrowserDaemonNotRunningError extends Error {
8
8
  }
9
9
  export declare function formatBrowserDaemonNotRunningError(): string;
10
10
  export declare function getSocketPath(): string;
11
+ /** Is the daemon reachable? A real connect probe on every platform — a socket
12
+ * file existing on disk is not proof a daemon is listening on it. */
13
+ export declare function isDaemonReachable(): Promise<boolean>;
11
14
  export declare class BrowserIPCServer {
12
15
  private server;
13
16
  private service;