agent-yes 1.205.0 → 1.206.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.
@@ -16,7 +16,29 @@ const MAX_PEER_JOIN_QUEUE = 50;
16
16
  const IDLE_RESTART_UPTIME_MS = 25 * 6e4;
17
17
  const HARD_RESTART_UPTIME_MS = 45 * 6e4;
18
18
  const IDLE_RESTART_CHECK_MS = 6e4;
19
+ const PERF_SLOW_MS = Number(process.env.AGENT_YES_WEBRTC_PERF_SLOW_MS) || 750;
20
+ const PERF_BUFFERED_BYTES = Number(process.env.AGENT_YES_WEBRTC_PERF_BUFFERED_BYTES) || 1e6;
21
+ const PERF_VERBOSE = process.env.AGENT_YES_WEBRTC_PERF === "1";
19
22
  const STUN = [{ urls: "stun:stun.l.google.com:19302" }];
23
+ function perfLog(event, data, force = false) {
24
+ if (!force && !PERF_VERBOSE) return;
25
+ process.stderr.write(`[share:perf] ${JSON.stringify({
26
+ t: Date.now(),
27
+ event,
28
+ ...data
29
+ })}\n`);
30
+ }
31
+ function maybeSlow(event, startedAt, data = {}) {
32
+ const ms = Math.round(performance.now() - startedAt);
33
+ perfLog(event, {
34
+ ms,
35
+ ...data
36
+ }, ms >= PERF_SLOW_MS);
37
+ }
38
+ function dcBufferedAmount(dc) {
39
+ const n = Number(dc?.bufferedAmount ?? 0);
40
+ return Number.isFinite(n) ? n : 0;
41
+ }
20
42
  let iceCache = null;
21
43
  async function getIceServers() {
22
44
  const keyId = process.env.CF_TURN_KEY_ID;
@@ -222,6 +244,7 @@ async function startShare(opts) {
222
244
  if (closed) return;
223
245
  const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
224
246
  currentWs = ws;
247
+ const signalStartedAt = performance.now();
225
248
  let ready = false;
226
249
  let lastRecv = Date.now();
227
250
  let hb;
@@ -237,6 +260,10 @@ async function startShare(opts) {
237
260
  }
238
261
  };
239
262
  ws.onopen = () => {
263
+ maybeSlow("signal.open", signalStartedAt, {
264
+ room,
265
+ host
266
+ });
240
267
  ws.send(JSON.stringify({
241
268
  type: "hello",
242
269
  role: "host",
@@ -271,6 +298,12 @@ async function startShare(opts) {
271
298
  const m = JSON.parse(ev.data);
272
299
  if (m.type === "pong") return;
273
300
  if (m.type === "peer-join") {
301
+ perfLog("peer.join", {
302
+ room,
303
+ peer: String(m.peer),
304
+ queue: peerJoinQueue.length,
305
+ peers: peers.size
306
+ });
274
307
  const pid = String(m.peer);
275
308
  if (!peers.has(pid) && !peerJoinQueue.includes(pid) && peerJoinQueue.length < MAX_PEER_JOIN_QUEUE) {
276
309
  peerJoinQueue.push(pid);
@@ -317,6 +350,7 @@ async function startShare(opts) {
317
350
  return ws;
318
351
  };
319
352
  async function startPeer(ws, peerId) {
353
+ const startedAt = performance.now();
320
354
  const iceServers = await getIceServers();
321
355
  const pc = new RTCPeerConnection({ iceServers });
322
356
  let resolveKeys;
@@ -354,6 +388,11 @@ async function startShare(opts) {
354
388
  dc.binaryType = "arraybuffer";
355
389
  dc.onopen = async () => {
356
390
  try {
391
+ maybeSlow("peer.dc_open", startedAt, {
392
+ room,
393
+ peer: peerId,
394
+ buffered: dcBufferedAmount(dc)
395
+ });
357
396
  await peer.keysReady;
358
397
  enqueueSeal(peerId, dc, peer, FLAG_CONFIRM, {
359
398
  t: "confirm",
@@ -387,6 +426,11 @@ async function startShare(opts) {
387
426
  sdp: pc.localDescription.sdp,
388
427
  iceServers
389
428
  }));
429
+ maybeSlow("peer.offer", startedAt, {
430
+ room,
431
+ peer: peerId,
432
+ iceServers: iceServers.length
433
+ });
390
434
  }
391
435
  function closePeer(peerId) {
392
436
  const p = peers.get(peerId);
@@ -399,8 +443,10 @@ async function startShare(opts) {
399
443
  peers.delete(peerId);
400
444
  }
401
445
  function enqueueSeal(peerId, dc, peer, flags, obj) {
446
+ const queuedAt = performance.now();
402
447
  peer.sendChain = peer.sendChain.then(async () => {
403
448
  if (dc.readyState !== "open" || !peer.keyH2C || !peer.th) return;
449
+ const queuedMs = Math.round(performance.now() - queuedAt);
404
450
  let frame;
405
451
  try {
406
452
  frame = await seal(peer.keyH2C, peer.send, flags, peer.th, packEnvelope(obj));
@@ -410,6 +456,15 @@ async function startShare(opts) {
410
456
  }
411
457
  try {
412
458
  dc.send(frame);
459
+ const buffered = dcBufferedAmount(dc);
460
+ perfLog("send", {
461
+ room,
462
+ peer: peerId,
463
+ kind: obj?.t,
464
+ queuedMs,
465
+ buffered,
466
+ bytes: frame.byteLength
467
+ }, queuedMs >= PERF_SLOW_MS || buffered >= PERF_BUFFERED_BYTES);
413
468
  } catch {}
414
469
  });
415
470
  return peer.sendChain;
@@ -452,6 +507,14 @@ async function startShare(opts) {
452
507
  }
453
508
  if (req.t !== "req") return;
454
509
  const { id, method, path: p, body } = req;
510
+ const startedAt = performance.now();
511
+ perfLog("req.start", {
512
+ room,
513
+ peer: peerId,
514
+ id,
515
+ method,
516
+ path: p
517
+ });
455
518
  const ac = new AbortController();
456
519
  peer.aborts.set(id, ac);
457
520
  try {
@@ -464,6 +527,14 @@ async function startShare(opts) {
464
527
  body: body ?? void 0,
465
528
  signal: ac.signal
466
529
  }));
530
+ maybeSlow("req.head", startedAt, {
531
+ room,
532
+ peer: peerId,
533
+ id,
534
+ method,
535
+ path: p,
536
+ status: res.status
537
+ });
467
538
  enqueueSeal(peerId, dc, peer, 0, {
468
539
  t: "res",
469
540
  id,
@@ -473,9 +544,11 @@ async function startShare(opts) {
473
544
  const reader = res.body.getReader();
474
545
  const dec = new TextDecoder();
475
546
  let seq = 0;
547
+ let bytes = 0;
476
548
  for (;;) {
477
549
  const { done, value } = await reader.read();
478
550
  if (done) break;
551
+ bytes += value.byteLength;
479
552
  const text = dec.decode(value, { stream: true });
480
553
  for (let i = 0; i < text.length; i += MAX_CHUNK) enqueueSeal(peerId, dc, peer, 0, {
481
554
  t: "data",
@@ -489,12 +562,32 @@ async function startShare(opts) {
489
562
  id,
490
563
  seq
491
564
  });
492
- } catch (e) {
493
- if (e.name !== "AbortError") enqueueSeal(peerId, dc, peer, 0, {
494
- t: "end",
565
+ maybeSlow("req.end", startedAt, {
566
+ room,
567
+ peer: peerId,
495
568
  id,
496
- error: String(e.message ?? e)
569
+ method,
570
+ path: p,
571
+ status: res.status,
572
+ chunks: seq,
573
+ bytes
497
574
  });
575
+ } catch (e) {
576
+ if (e.name !== "AbortError") {
577
+ maybeSlow("req.error", startedAt, {
578
+ room,
579
+ peer: peerId,
580
+ id,
581
+ method,
582
+ path: p,
583
+ error: String(e.message ?? e)
584
+ });
585
+ enqueueSeal(peerId, dc, peer, 0, {
586
+ t: "end",
587
+ id,
588
+ error: String(e.message ?? e)
589
+ });
590
+ }
498
591
  } finally {
499
592
  peer.aborts.delete(id);
500
593
  }
@@ -534,4 +627,4 @@ async function startShare(opts) {
534
627
 
535
628
  //#endregion
536
629
  export { shareLinkFromRoomUrl as n, startShare as r, loadOrCreateShareRoom as t };
537
- //# sourceMappingURL=share-BfIU8t_h.js.map
630
+ //# sourceMappingURL=share-vchIyVd8.js.map
@@ -2,7 +2,7 @@ import "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-CoNr7tS8.js";
3
3
  import "./messageLog-CxrKJj77.js";
4
4
  import "./configShared-aKTg-sa5.js";
5
- import { A as renderRawLog, B as submitAndConfirm, C as matchKeyword, D as recentMessageEdges, E as readPtysize, F as restartHintLines, H as writeKeysPaced, I as runSubcommand, L as snapshotStatus, M as resolveOne, N as resolveReadWindow, O as recentReadEdges, P as resolveResumeArgs, R as stdinActivityPath, S as listRecords, T as readNotes, U as writeToIpc, V as waitForLogQuiet, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as renderRawLogLines, k as renderLogTailLines, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as stopTipForCli } from "./subcommands-Bs7wJRBB.js";
5
+ import { A as renderRawLog, B as submitAndConfirm, C as matchKeyword, D as recentMessageEdges, E as readPtysize, F as restartHintLines, H as writeKeysPaced, I as runSubcommand, L as snapshotStatus, M as resolveOne, N as resolveReadWindow, O as recentReadEdges, P as resolveResumeArgs, R as stdinActivityPath, S as listRecords, T as readNotes, U as writeToIpc, V as waitForLogQuiet, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as renderRawLogLines, k as renderLogTailLines, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as stopTipForCli } from "./subcommands-DkrMRVNh.js";
6
6
  import "./e2e-jb0Hp43q.js";
7
7
  import "./webrtcLink-B7REGtK2.js";
8
8
  import "./remotes-Cim0dBU7.js";
@@ -1237,19 +1237,19 @@ async function runSubcommand(argv) {
1237
1237
  case "restart": return await cmdRestart(rest);
1238
1238
  case "note": return await cmdNote(rest);
1239
1239
  case "serve": {
1240
- const { cmdServe } = await import("./serve-IvOlaL-7.js");
1240
+ const { cmdServe } = await import("./serve-crLypY_c.js");
1241
1241
  return cmdServe(rest);
1242
1242
  }
1243
1243
  case "setup": {
1244
- const { cmdSetup } = await import("./setup-BaKdS-3i.js");
1244
+ const { cmdSetup } = await import("./setup-BPNkeSmY.js");
1245
1245
  return cmdSetup(rest);
1246
1246
  }
1247
1247
  case "ws": {
1248
- const { cmdWs } = await import("./ws-CSkP2mkD.js");
1248
+ const { cmdWs } = await import("./ws-BmHpWaG_.js");
1249
1249
  return cmdWs(rest);
1250
1250
  }
1251
1251
  case "schedule": {
1252
- const { cmdSchedule } = await import("./schedule-CBoM67Vy.js");
1252
+ const { cmdSchedule } = await import("./schedule-DJ-v4yVa.js");
1253
1253
  return cmdSchedule(rest);
1254
1254
  }
1255
1255
  case "remote": {
@@ -4269,7 +4269,7 @@ async function cmdNotify(rest) {
4269
4269
  }
4270
4270
  const ensure = async () => {
4271
4271
  if (!argv["ensure-daemon"]) return;
4272
- const { ensureDaemon } = await import("./notifyDaemon-DK1HOMjj.js");
4272
+ const { ensureDaemon } = await import("./notifyDaemon-BbVnNvlo.js");
4273
4273
  await ensureDaemon().catch(() => null);
4274
4274
  };
4275
4275
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4343,7 +4343,7 @@ async function cmdNotifyCursor(args) {
4343
4343
  }
4344
4344
  async function cmdNotifyd(rest) {
4345
4345
  const sub = rest[0] ?? "status";
4346
- const daemon = await import("./notifyDaemon-DK1HOMjj.js");
4346
+ const daemon = await import("./notifyDaemon-BbVnNvlo.js");
4347
4347
  switch (sub) {
4348
4348
  case "run": return daemon.runDaemon();
4349
4349
  case "once": return daemon.runDaemon({ once: true });
@@ -4370,4 +4370,4 @@ async function cmdNotifyd(rest) {
4370
4370
 
4371
4371
  //#endregion
4372
4372
  export { daemonLockOwnerPath as $, renderRawLog as A, submitAndConfirm as B, matchKeyword as C, recentMessageEdges as D, readPtysize as E, restartHintLines as F, gcInboxes as G, writeKeysPaced as H, runSubcommand as I, listInboxParents as J, hostId as K, snapshotStatus as L, resolveOne as M, resolveReadWindow as N, recentReadEdges as O, resolveResumeArgs as P, daemonLockDir as Q, stdinActivityPath as R, listRecords as S, readNotes as T, writeToIpc as U, waitForLogQuiet as V, appendEvent as W, readInbox as X, liveWatchers as Y, shouldStealLock as Z, isPidAlive as _, cmdHelp as a, isUserTyping as b, deriveLiveState as c, extractMenu as d, notifyDir as et, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, renderRawLogLines as j, renderLogTailLines as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, controlCodeFromName as o, extractTaskCounts as p, isTransientLockMkdirError as q, TYPING_WINDOW_MS as r, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, TYPING_BADGE as tt, extractBadges as u, isSlashCommand as v, menuSelectKeys as w, lastStdinAt as x, isSubcommand as y, stopTipForCli as z };
4373
- //# sourceMappingURL=subcommands-Bs7wJRBB.js.map
4373
+ //# sourceMappingURL=subcommands-DkrMRVNh.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BomfUpSo.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-d5c6_vgS.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-CtHb5b71.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CNMl13dC.js";
5
5
  import { t as PidStore } from "./pidStore-BIvsBQ8X.js";
@@ -1901,4 +1901,4 @@ function sleep(ms) {
1901
1901
 
1902
1902
  //#endregion
1903
1903
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1904
- //# sourceMappingURL=ts-Dm6jjLwC.js.map
1904
+ //# sourceMappingURL=ts-DnqRreF4.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.205.0";
10
+ var version = "1.206.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-BomfUpSo.js.map
218
+ //# sourceMappingURL=versionChecker-d5c6_vgS.js.map
@@ -2,7 +2,7 @@ import "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-CoNr7tS8.js";
3
3
  import "./messageLog-CxrKJj77.js";
4
4
  import "./configShared-aKTg-sa5.js";
5
- import { S as listRecords } from "./subcommands-Bs7wJRBB.js";
5
+ import { S as listRecords } from "./subcommands-DkrMRVNh.js";
6
6
  import "./e2e-jb0Hp43q.js";
7
7
  import "./webrtcLink-B7REGtK2.js";
8
8
  import "./remotes-Cim0dBU7.js";
@@ -418,4 +418,4 @@ async function cmdWs(args) {
418
418
 
419
419
  //#endregion
420
420
  export { WS_JSON_SCHEMA, cmdWs, collectWorkspaces, isPathInside, loadProvision, workspaceStatus };
421
- //# sourceMappingURL=ws-CSkP2mkD.js.map
421
+ //# sourceMappingURL=ws-BmHpWaG_.js.map
@@ -7,7 +7,7 @@
7
7
  // `now` so age() is deterministic under test.
8
8
 
9
9
  // An agent entry as surfaced by /api/ls (the fields this module reads):
10
- // { cli, cwd, title, prompt, status, started_at, pid, _host }
10
+ // { cli, cwd, title, status_text, prompt, status, started_at, pid, _host }
11
11
 
12
12
  // claude is the default CLI — show the cli name only when it differs, so the
13
13
  // common case stays uncluttered and the identity (repo/branch) leads instead.
@@ -261,6 +261,8 @@ export function matches(e, toks) {
261
261
  const hay =
262
262
  (e.title || "") +
263
263
  " " +
264
+ (e.status_text || "") +
265
+ " " +
264
266
  (e.prompt || "") +
265
267
  " " +
266
268
  e.cli +
package/lab/ui/index.html CHANGED
@@ -1325,6 +1325,14 @@
1325
1325
  text-overflow: ellipsis;
1326
1326
  white-space: nowrap;
1327
1327
  }
1328
+ .statusline {
1329
+ color: var(--muted);
1330
+ font-size: 12.5px;
1331
+ margin-top: 4px;
1332
+ overflow: hidden;
1333
+ text-overflow: ellipsis;
1334
+ white-space: nowrap;
1335
+ }
1328
1336
  /* compact view: one line per agent — dot + cli + live title (or prompt), age */
1329
1337
  .row.crow {
1330
1338
  display: flex;
@@ -1682,11 +1690,28 @@
1682
1690
  h1 {
1683
1691
  font-size: 17px;
1684
1692
  }
1685
- /* roomier tap targets for the header controls */
1693
+ /* roomier tap targets for the header controls — min 44px square so
1694
+ touch users can hit them (WCAG 2.5.5). inline-flex keeps the glyph
1695
+ centered once min-height stretches the box past its text. */
1686
1696
  .newbtn,
1687
1697
  .viewbtn {
1688
1698
  padding: 7px 12px;
1689
1699
  font-size: 12.5px;
1700
+ min-height: 44px;
1701
+ min-width: 44px;
1702
+ display: inline-flex;
1703
+ align-items: center;
1704
+ justify-content: center;
1705
+ }
1706
+ /* the filter box is the primary control — give it a full-height touch
1707
+ target too, not the 38px it collapses to from text metrics alone.
1708
+ Stretch the input itself to the box height so a tap anywhere in the
1709
+ box lands on the field, not just its 17px text line. */
1710
+ .ibox {
1711
+ min-height: 44px;
1712
+ }
1713
+ #q {
1714
+ align-self: stretch;
1690
1715
  }
1691
1716
  .meta {
1692
1717
  align-items: center;
@@ -1819,6 +1844,7 @@
1819
1844
  <input
1820
1845
  id="q"
1821
1846
  placeholder="filter… repo:agent-yes claude (space = AND)"
1847
+ aria-label="Filter agents by repo, CLI, or title"
1822
1848
  autocomplete="off"
1823
1849
  autofocus
1824
1850
  />
@@ -1831,7 +1857,14 @@
1831
1857
  <span class="metaright">
1832
1858
  <button id="foldbtn" class="viewbtn" title="fold subagent trees">⊞ subs</button>
1833
1859
  <button id="sortbtn" class="viewbtn" title="cycle sort order">⇅ state</button>
1834
- <button id="viewbtn" class="viewbtn" title="toggle compact list">☰</button>
1860
+ <button
1861
+ id="viewbtn"
1862
+ class="viewbtn"
1863
+ title="toggle compact list"
1864
+ aria-label="Toggle compact list view"
1865
+ >
1866
+
1867
+ </button>
1835
1868
  <button id="portsbtn" class="viewbtn" title="manage exposed localhost ports">
1836
1869
  ⇄ ports
1837
1870
  </button>
@@ -4832,7 +4865,7 @@
4832
4865
  .map((r) => {
4833
4866
  if (r.kind !== "agent") return headerHtml(r);
4834
4867
  const e = r.entry;
4835
- const t = e.title || e.prompt || "";
4868
+ const t = e.title || e.status_text || e.prompt || "";
4836
4869
  const id = compactIdent(e, ctx, 3, r.parentEntry);
4837
4870
  const cli = cliLabel(e);
4838
4871
  return `<div class="row crow ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">
@@ -4875,9 +4908,10 @@
4875
4908
  ${taskChipHtml(e)}
4876
4909
  ${badgeChipsHtml(e)}
4877
4910
  ${gitChipHtml(e)}
4878
- ${peerChipHtml(e)}
4879
- <span class="age">${age(e)}</span></div>
4911
+ ${peerChipHtml(e)}
4912
+ <span class="age">${age(e)}</span></div>
4880
4913
  ${e.title ? `<div class="rowtitle" title="${esc(e.title)}">${esc(e.title)}</div>` : ""}
4914
+ ${e.status_text ? `<div class="statusline" title="${esc(e.status_text)}">${esc(e.status_text)}</div>` : ""}
4881
4915
  ${
4882
4916
  e.status === "needs_input" && e.question
4883
4917
  ? `<div class="detail ask" title="${esc(e.question)}">⌨ ${esc(e.question)}</div>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.205.0",
3
+ "version": "1.206.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
package/ts/serve.spec.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import { readFile } from "fs/promises";
2
2
  import { describe, expect, it } from "vitest";
3
- import { installerArgv, isNoNodeExecError, oxmgrVersionHasWindowsFix } from "./serve.ts";
3
+ import {
4
+ installerArgv,
5
+ isNoNodeExecError,
6
+ oxmgrVersionHasWindowsFix,
7
+ portlessConsoleUrl,
8
+ } from "./serve.ts";
9
+
10
+ describe("portlessConsoleUrl", () => {
11
+ it("uses the stable local HTTPS hostname and keeps auth in the fragment", () => {
12
+ expect(portlessConsoleUrl()).toBe("https://agent-yes.localhost/");
13
+ expect(portlessConsoleUrl("a b")).toBe("https://agent-yes.localhost/#k=a%20b");
14
+ });
15
+ });
4
16
 
5
17
  // Guards the Windows daemon-manager selection: on Windows we only PREFER oxmgr
6
18
  // when the installed build carries the daemon-socket-inheritance fix. Stock