@inetafrica/open-claudia 3.5.58 → 3.5.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.60
4
+
5
+ 3.5.59 plus the Browser app work that only the Browser canary bot had (3.5.25, 3.5.26, 3.5.28). 3.5.33 was built on 3.5.24, 3.5.58 on 3.5.33 and 3.5.59 on 3.5.58, so every other bot still said "<bot>'s browser isn't open right now" and had no phone view (Sumeet, 13 Oct 17:4x EAT on sales-bot: "thought we fixed this?").
6
+
7
+ - **Always on (3.5.28).** `POST /api/browser/start`: opening the Browser app while the bot is not browsing opens the bot's own default session on `https://www.google.com/`, with the bot's agent-browser environment and socket folder, so the bot carries on in that tab. `GET /api/browser/control` says `start: true`; Agent Space's front door (223b978) asks only bots that say so.
8
+ - **The bot's tab takes the driver's screen (3.5.25).** With Chrome in its own container, a take or a `fit` fits the bot's tab to the driver's screen (`core/browser-drive.js`): phone layout on a phone, the panel's size on the web; handing back puts it back. Input goes straight to Chrome in order, scaled by the view's density; text fields open the phone's own keyboard; a hand back wakes the bot only when it waits for that person.
9
+ - **Sharp after every page (3.5.26).** After a main-frame load the window is nudged a pixel, so Chrome 150 keeps the view's density.
10
+ - **Tests:** `test-browser-drive.js` (now in `npm test`), `test-browser-control.js`, `test-web-browser-app.js`.
11
+
12
+ ## 3.5.59
13
+
14
+ 3.5.58 plus three guard fixes from kazee-tickets-bot's Ops monitoring room (13 Oct; Sumeet, 16:39 EAT: "fix the other bug also but approvals need to go to the owners direct chat"). Canary on kazee-tickets-bot first.
15
+
16
+ - **Approval cards go to each owner's direct chat, never a group.** An owner whose stored primary channel was the Ops room had every card (the held message, the draft reply, the guard's reason) posted in front of the whole room; another owner's primary was his user id, which 404s as a chat, so he never got one. Owner-only posts now take the owner's first handle that is a 1:1 chat with the bot: Kazee asks the server (a group, or a chat with more than two members, is refused; a user id opens the bot's DM with them), Telegram group ids are refused, and an owner reachable only in a group gets no card. Presses were already owner-only. Test: `test-owner-cards-dm.js` (new).
17
+ - **The reply writer reads the room's rules.** Room rules and global guardrails reached only the guard, so an unverified person tagging the bot in a room whose rules clear their question got the hello-only lane and "I'll check with the owner", and the guard then flagged the writer for deferring (also 25 Sep). The restricted-mode block now carries both layers through the guard's own loaders. Test: `test-external-rule-scopes.js`.
18
+ - **The guard retries once on Claude Code's sign-in refresh race** instead of sending the owner a card (network-mon, 12 Oct 15:47 EAT). Test: `test-limit-waits.js`.
19
+
3
20
  ## 3.5.58
4
21
 
5
22
  The call work from the owner's own bot (local builds 3.5.34-3.5.57), released for every bot (Sumeet, 13 Oct 11:53 EAT, canary pod first). Also carries 3.5.33's quiet chats and 3.5.47's guard fix.
@@ -1552,6 +1552,30 @@ class KazeeAdapter {
1552
1552
  // chatId. chat-central's createChat returns the existing DM when one
1553
1553
  // already exists, so this is idempotent. Requires chats.create on the bot
1554
1554
  // token — callers must treat null as "outreach unavailable", not fatal.
1555
+ // The 1:1 chat an owner-only post (an approval card) may go to, for an id
1556
+ // that is either a chat document or a user. A group answers null: a card
1557
+ // there shows every member what the owner is being asked, the held message
1558
+ // and the draft reply (kazee-tickets-bot, 13 Oct: an owner's primary
1559
+ // channel was the Ops room, so every card landed in front of the room). A
1560
+ // 1:1 chat is itself. An id the server doesn't know as a chat is a user,
1561
+ // opened as the bot's DM with them. Any other failure answers null, never a
1562
+ // guess.
1563
+ async directChatFor(id) {
1564
+ const chatId = String(id || "");
1565
+ if (!chatId) return null;
1566
+ try {
1567
+ const res = await this._request("GET", `/chats/${encodeURIComponent(chatId)}`);
1568
+ const chat = res?.chat || res || {};
1569
+ if (chat.error) throw new Error(String(chat.error));
1570
+ const members = Array.isArray(chat.participants) ? chat.participants.length : 0;
1571
+ return chat.isGroupChat || members > 2 ? null : chatId;
1572
+ } catch (e) {
1573
+ if (/→ 404\b/.test(String(e.message || ""))) return this.openDirectChat(chatId);
1574
+ console.error(`Kazee direct chat lookup failed (${chatId}):`, e.message);
1575
+ return null;
1576
+ }
1577
+ }
1578
+
1555
1579
  async openDirectChat(userId) {
1556
1580
  if (!this.botUserId || !userId) return null;
1557
1581
  try {
@@ -13,18 +13,30 @@
13
13
  // after the input before them, with the bot's own agent-browser environment and never a
14
14
  // launch flag (core/agent-browser-cli.js: other launch settings would restart the browser).
15
15
  // Only public internet addresses (core/browser-address.js).
16
+ // - the phone and web view (core/browser-drive.js): a take or a "fit" with the driver's screen
17
+ // makes the bot's tab take its size, phone layout and touch, and then the driver's input goes
18
+ // straight to Chrome through that view (typed text too: input_text). Only with Chrome in its
19
+ // own container; elsewhere the browser keeps its size and input goes through agent-browser.
20
+ // - always on: a person who opens the Browser app while the bot's browser is not open gets it
21
+ // opened on a start page, in the bot's own default session (the one its `agent-browser open`
22
+ // uses), with the bot's own agent-browser environment, so the bot carries on in that tab.
16
23
  // - hand back (release, a closed panel, or a lock found expired by the 30 s sweeper): the lock
17
- // goes, the socket closes, any recording stops, and the bot is woken in the person's own chat
18
- // to carry on (C6). A recording stopped at the same time folds into one wake-up.
24
+ // goes, the view and the socket close, any recording stops, and the bot is woken in the
25
+ // person's own chat (C6), but only when it waits for them: it sent them an Open Browser card,
26
+ // or one of its browser commands was refused while they drove. A recording stopped at the
27
+ // same time folds into one wake-up. Otherwise the hand back is only logged.
19
28
  //
20
29
  // Every route answers { status, body }; web.js writes it. Agent Space reaches these routes with
21
30
  // the control token only.
22
31
 
32
+ const os = require("os");
33
+ const path = require("path");
23
34
  const WebSocket = require("ws");
24
35
  const lockFile = require("./browser-lock");
25
36
  const view = require("./browser-view");
26
37
  const teach = require("./browser-teach");
27
38
  const address = require("./browser-address");
39
+ const drive = require("./browser-drive");
28
40
  const { runAgentBrowser } = require("./agent-browser-cli");
29
41
 
30
42
  const LOCK_TTL_MS = lockFile.LOCK_TTL_MS;
@@ -41,6 +53,13 @@ const ID_RE = /^[A-Za-z0-9._:-]{1,100}$/;
41
53
  // An address-bar command still running after this is killed, and the person told it failed.
42
54
  const NAVIGATE_TIMEOUT_MS = 20 * 1000;
43
55
  const NAVIGATIONS = new Set(["open", "back", "forward", "reload"]);
56
+ // Always on: the page a browser opened for a person starts on, and the session it opens (the one
57
+ // the bot's own `agent-browser` commands use when they name none).
58
+ const START_PAGE = "https://www.google.com/";
59
+ const START_SESSION = "default";
60
+
61
+ // An Open Browser card counts as a request for a person this long.
62
+ const REQUEST_MAX_AGE_MS = 24 * 60 * 60 * 1000;
44
63
 
45
64
  const HAND_BACK_REASONS = {
46
65
  closed: " (they closed the Browser)",
@@ -107,6 +126,7 @@ const MOUSE_EVENTS = new Set(["mousePressed", "mouseReleased", "mouseMoved", "mo
107
126
  const MOUSE_BUTTONS = new Set(["none", "left", "middle", "right"]);
108
127
  const KEY_EVENTS = new Set(["keyDown", "keyUp", "char"]);
109
128
  const TOUCH_EVENTS = new Set(["touchStart", "touchMove", "touchEnd", "touchCancel"]);
129
+ const TEXT_MAX = drive.TEXT_MAX;
110
130
 
111
131
  const isNumber = (v) => typeof v === "number" && Number.isFinite(v);
112
132
  const inRange = (v, lo, hi) => isNumber(v) && v >= lo && v <= hi;
@@ -163,11 +183,34 @@ function checkEvent(ev) {
163
183
  }
164
184
  const out = { type: "input_touch", eventType: ev.eventType, touchPoints };
165
185
  if (!optional(out, "modifiers", modifiers)) return null;
186
+ // When the finger moved, on the driver's device (ms): keeps a swipe's speed (core/browser-drive.js).
187
+ if (!optional(out, "t", (v) => inRange(v, 0, 1e13))) return null;
166
188
  return out;
167
189
  }
190
+ // Text the driver typed (their phone keyboard, a paste), inserted where the page's caret is.
191
+ if (ev.type === "input_text") {
192
+ if (typeof ev.text !== "string" || !ev.text || Array.from(ev.text).length > TEXT_MAX) return null;
193
+ return { type: "input_text", text: ev.text };
194
+ }
168
195
  return null;
169
196
  }
170
197
 
198
+ // input_text as key presses, for a bot without the driver's view (agent-browser's socket takes
199
+ // no text) and for Teach me, which counts typing from key presses.
200
+ function textAsKeys(text) {
201
+ const out = [];
202
+ for (const ch of Array.from(text)) {
203
+ if (ch === "\n" || ch === "\r") {
204
+ out.push({ type: "input_keyboard", eventType: "keyDown", key: "Enter", code: "Enter", text: "\r", windowsVirtualKeyCode: 13 });
205
+ out.push({ type: "input_keyboard", eventType: "keyUp", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 });
206
+ } else {
207
+ out.push({ type: "input_keyboard", eventType: "keyDown", key: ch, text: ch });
208
+ out.push({ type: "input_keyboard", eventType: "keyUp", key: ch });
209
+ }
210
+ }
211
+ return out;
212
+ }
213
+
171
214
  // ---------------------------------------------------------------------------
172
215
  // Input sockets: one per agent-browser session, opened on take (or on the first input after a
173
216
  // restart), closed on hand back.
@@ -325,7 +368,23 @@ function wakeChat({ chatId, userId }, prompt, label) {
325
368
 
326
369
  function handBackLine(name, reason) {
327
370
  const who = teach.cleanName(name) || "Someone";
328
- return `[Browser] ${who} handed the browser back${HAND_BACK_REASONS[reason] || ""}. If you were waiting on them, check the page first (it may have changed) and carry on; otherwise just note it.`;
371
+ return `[Browser] ${who} handed the browser back${HAND_BACK_REASONS[reason] || ""}. Check the page first (it may have changed), then carry on with what you were waiting on them for.`;
372
+ }
373
+
374
+ // Was the bot waiting for this person when they handed back? It asked for them with an Open
375
+ // Browser card in their chat (since the day before they took over), or one of its own browser
376
+ // commands was refused while they drove.
377
+ function botWasWaiting(lock) {
378
+ let asked = [];
379
+ try {
380
+ const since = Date.parse(lock.since);
381
+ asked = require("./cards").takeBrowserRequests(lock.chatId, (Number.isFinite(since) ? since : deps.now()) - REQUEST_MAX_AGE_MS, deps.now());
382
+ } catch (e) {
383
+ console.error(`[browser] reading the bot's Open Browser cards failed: ${e.message}`);
384
+ }
385
+ const refused = lockFile.waitedDuring(lock);
386
+ lockFile.clearWaiting();
387
+ return asked.length > 0 || refused;
329
388
  }
330
389
 
331
390
  // ---------------------------------------------------------------------------
@@ -335,7 +394,39 @@ function lockBody(lock) {
335
394
  return { driving: true, driver: { name: lock.name } };
336
395
  }
337
396
 
338
- async function take({ session, who } = {}) {
397
+ // The driver's view on this lock's session: start it (or move it to a new screen) and remember it
398
+ // in the lock, so a restarted bot can bring it back. -> the fit, or null (the browser keeps its size).
399
+ async function startView(lock, viewport) {
400
+ if (!viewport || !drive.supported()) return null;
401
+ const info = sessionInfo(lock.session);
402
+ if (!info) return null;
403
+ const r = drive.active(lock.session)
404
+ ? await drive.fit(viewport)
405
+ : await drive.start({ session: info, viewport, savedBounds: lock.bounds || null });
406
+ const now = lockFile.readLock();
407
+ // The lock changed hands (or was handed back) while the view started: undo it.
408
+ if (!now || now.since !== lock.since || now.userId !== lock.userId) {
409
+ if (r.ok && drive.active(lock.session)) await drive.stop();
410
+ return null;
411
+ }
412
+ if (!r.ok) return null;
413
+ now.fit = r.fit;
414
+ if (r.bounds) now.bounds = r.bounds;
415
+ lockFile.writeLock(now);
416
+ return r.fit;
417
+ }
418
+
419
+ // After a bot restart the lock remembers a view that is no longer live: bring it back. Once at a
420
+ // time, in the background.
421
+ let reviving = null;
422
+ function reviveView(lock) {
423
+ if (!lock || !lock.fit || reviving || drive.active(lock.session) || !drive.supported()) return;
424
+ reviving = startView(lock, lock.fit)
425
+ .catch((e) => console.error(`[browser] bringing the driver's view back failed: ${e.message}`))
426
+ .finally(() => { reviving = null; });
427
+ }
428
+
429
+ async function take({ session, who, viewport } = {}) {
339
430
  if (!validSession(session) || !validWho(who, { needChat: true })) return reply(400, { error: "bad-request" });
340
431
  // A lock that ran out but has not been swept yet is handed back first, the sweeper's way: its
341
432
  // driver's chat is woken and their recording stopped, whoever takes over now.
@@ -354,9 +445,25 @@ async function take({ session, who } = {}) {
354
445
  since: current && current.session === session ? current.since : iso(now),
355
446
  expiresAt: iso(now + LOCK_TTL_MS),
356
447
  };
448
+ // The same person taking again (a reloaded page) keeps the window's size from before their view.
449
+ if (current && current.session === session && sameUser(current, who) && current.bounds) lock.bounds = current.bounds;
357
450
  lockFile.writeLock(lock);
358
451
  openInputSocket(session);
359
- return reply(200, lockBody(lock));
452
+ if (!viewport) return reply(200, lockBody(lock));
453
+ // fit: the driver's view now live, or null (this bot's browser keeps its own size).
454
+ const fitted = await startView(lock, viewport);
455
+ return reply(200, { ...lockBody(lock), fit: fitted });
456
+ }
457
+
458
+ // The driver's screen changed (a turned phone, a resized panel): { session, who, viewport }.
459
+ async function fitView({ session, who, viewport } = {}) {
460
+ if (!validSession(session) || !validWho(who)) return reply(400, { error: "bad-request" });
461
+ if (!drive.checkViewport(viewport)) return reply(400, { error: "bad-viewport" });
462
+ const lock = extendDriverLock(session, who);
463
+ if (!lock) return reply(409, { driving: false, error: "not-driving" });
464
+ if (!drive.supported()) return reply(200, { ...lockBody(lock), fit: null });
465
+ const fitted = await startView(lock, viewport);
466
+ return reply(200, { ...lockBody(lock), fit: fitted });
360
467
  }
361
468
 
362
469
  function renew({ session, who } = {}) {
@@ -369,7 +476,8 @@ function renew({ session, who } = {}) {
369
476
  current.expiresAt = iso(now + LOCK_TTL_MS);
370
477
  lockFile.writeLock(current);
371
478
  openInputSocket(current.session); // after a restart the socket comes back here
372
- return reply(200, lockBody(current));
479
+ reviveView(current); // and the driver's view
480
+ return reply(200, current.fit ? { ...lockBody(current), fit: drive.active(current.session) ? current.fit : null } : lockBody(current));
373
481
  }
374
482
 
375
483
  // Hand the browser back: claim the lock (synchronously, so a release and the sweeper never both
@@ -383,6 +491,13 @@ async function handBack(expected, { reason = null } = {}) {
383
491
  let stopped = null;
384
492
  await runQueued(current.session, 0, async () => {
385
493
  closeInputSocket(current.session);
494
+ // The bot's tab goes back to its own size before the bot carries on.
495
+ try {
496
+ if (drive.active(current.session)) await drive.stop();
497
+ else if (current.bounds) await drive.restoreWindow(info, current.bounds);
498
+ } catch (e) {
499
+ console.error(`[browser] ending the driver's view failed: ${e.message}`);
500
+ }
386
501
  try {
387
502
  stopped = await teach.stopRecording(current.userId, { session: info });
388
503
  } catch (e) {
@@ -390,9 +505,10 @@ async function handBack(expected, { reason = null } = {}) {
390
505
  }
391
506
  });
392
507
  const line = handBackLine(current.name, reason);
508
+ const waited = botWasWaiting(current);
393
509
  if (stopped && !stopped.discarded) wakeChat(current, teach.teachPrompt(stopped, { handBack: line }), "Teach me");
394
- else wakeChat(current, line, "Browser");
395
- console.log(`[browser] ${current.userId} handed the browser back${reason ? ` (${reason})` : ""}`);
510
+ else if (waited) wakeChat(current, line, "Browser");
511
+ console.log(`[browser] ${current.userId} handed the browser back${reason ? ` (${reason})` : ""}${waited || (stopped && !stopped.discarded) ? "" : "; the bot was not waiting for them, so it is not woken"}`);
396
512
  return true;
397
513
  }
398
514
 
@@ -409,10 +525,13 @@ async function control(body = {}) {
409
525
  if (body.action === "take") return take(body);
410
526
  if (body.action === "renew") return renew(body);
411
527
  if (body.action === "release") return release(body);
528
+ if (body.action === "fit") return fitView(body);
412
529
  return reply(400, { error: "bad-action" });
413
530
  }
414
531
 
415
- // navigate: true tells the front door this bot has the address bar (POST /api/browser/navigate).
532
+ // navigate: true tells the front door this bot has the address bar (POST /api/browser/navigate);
533
+ // fit: true that a take can bring the driver's view (core/browser-drive.js) and input_text works;
534
+ // start: true that it opens its browser on a start page when there is none (POST /api/browser/start).
416
535
  function status() {
417
536
  const lock = lockFile.liveLock(deps.now());
418
537
  return reply(200, {
@@ -420,9 +539,50 @@ function status() {
420
539
  driver: lock ? { name: lock.name, userId: lock.userId } : null,
421
540
  recording: teach.activeSummary(),
422
541
  navigate: true,
542
+ fit: drive.supported(),
543
+ start: true,
423
544
  });
424
545
  }
425
546
 
547
+ // ---------------------------------------------------------------------------
548
+ // Always on: POST /api/browser/start
549
+
550
+ // The socket folder the bot's own agent-browser commands use (its agent runs without
551
+ // XDG_RUNTIME_DIR): AGENT_BROWSER_SOCKET_DIR, else ~/.agent-browser.
552
+ function agentSocketDir(env = process.env, home = os.homedir()) {
553
+ if (env.AGENT_BROWSER_SOCKET_DIR) return env.AGENT_BROWSER_SOCKET_DIR;
554
+ return home ? path.join(home, ".agent-browser") : null;
555
+ }
556
+
557
+ let starting = null;
558
+
559
+ // { who: { userId } }. A browser already open is answered as it is (the newest session); otherwise
560
+ // the bot's default session opens on the start page, as the bot's own `agent-browser open` would,
561
+ // and two people opening the app at once start one browser. { session, started }.
562
+ async function start({ who } = {}) {
563
+ if (!validWho(who)) return reply(400, { error: "bad-request" });
564
+ const open = deps.listSessions();
565
+ if (open.length) return reply(200, { session: open[0].name, started: false });
566
+ if (!starting) {
567
+ starting = (async () => {
568
+ const dir = agentSocketDir();
569
+ const run = await runAgentBrowser({ name: START_SESSION, dir }, ["open", START_PAGE], {
570
+ timeoutMs: deps.navigateTimeoutMs,
571
+ execFile: deps.execFile || undefined,
572
+ });
573
+ const listed = run.ok ? deps.listSessions().find((s) => s.name === START_SESSION) : null;
574
+ if (!listed) {
575
+ const why = run.timedOut ? `agent-browser took longer than ${Math.ceil(deps.navigateTimeoutMs / 1000)} s and was stopped` : run.ok ? "the browser did not come up" : "agent-browser failed";
576
+ console.error(`[browser] could not open the browser for ${who.userId}: ${why}`);
577
+ return reply(503, { error: "browser-unavailable" });
578
+ }
579
+ console.log(`[browser] opened the browser on the start page for ${who.userId}`);
580
+ return reply(200, { session: listed.name, started: true });
581
+ })().finally(() => { starting = null; });
582
+ }
583
+ return starting;
584
+ }
585
+
426
586
  // ---------------------------------------------------------------------------
427
587
  // C3: input
428
588
 
@@ -436,24 +596,41 @@ async function input({ session, events, who } = {}) {
436
596
  messages.push(msg);
437
597
  }
438
598
  // Every accepted input extends the lock.
439
- if (!extendDriverLock(session, who)) return reply(409, { error: "not-driving" });
599
+ const lock = extendDriverLock(session, who);
600
+ if (!lock) return reply(409, { error: "not-driving" });
440
601
  if (!messages.length) return reply(200, { ok: true, accepted: 0 });
602
+ if (lock.fit && !drive.active(session)) reviveView(lock);
441
603
  const entry = openInputSocket(session);
442
- if (!entry) return reply(404, { error: "not-running" });
604
+ if (!entry && !drive.active(session)) return reply(404, { error: "not-running" });
443
605
  const job = runQueued(session, messages.length, async () => {
444
- if (!(await socketReady(entry))) return reply(503, { error: "browser-unavailable", accepted: 0 });
445
606
  const recorder = teach.recorderFor(session, who.userId);
446
- let sent = 0;
447
- for (const msg of messages) {
448
- if (recorder) {
607
+ const record = async (msg, frame, info) => {
608
+ if (!recorder) return;
609
+ // Teach me counts typing from key presses: typed text is shown to it as keys.
610
+ for (const ev of msg.type === "input_text" ? textAsKeys(msg.text) : [msg]) {
449
611
  try {
450
- await recorder.onEvent(msg, { session: { name: entry.name, dir: entry.dir }, frame: entry.frame });
612
+ await recorder.onEvent(ev, { session: info, frame });
451
613
  } catch (e) {
452
614
  console.error(`[browser] recording a step failed: ${e.message}`);
453
615
  }
454
616
  }
617
+ };
618
+ // The driver's view: straight to Chrome, in order, without waiting for each answer.
619
+ if (drive.active(session)) {
620
+ const info = sessionInfo(session) || { name: session, dir: entry ? entry.dir : null };
621
+ for (const msg of messages) {
622
+ await record(msg, drive.lastFrame(), { name: info.name, dir: info.dir });
623
+ if (drive.dispatch([msg]) < 0) return reply(503, { error: "browser-unavailable" });
624
+ }
625
+ return reply(200, { ok: true, accepted: messages.length });
626
+ }
627
+ if (!entry || !(await socketReady(entry))) return reply(503, { error: "browser-unavailable", accepted: 0 });
628
+ let sent = 0;
629
+ for (const msg of messages) {
630
+ await record(msg, entry.frame, { name: entry.name, dir: entry.dir });
455
631
  if (!entry.open || entry.closed) return reply(503, { error: "browser-unavailable", accepted: sent });
456
- entry.ws.send(JSON.stringify(msg));
632
+ // agent-browser's socket takes no text: typed text goes as key presses.
633
+ for (const ev of msg.type === "input_text" ? textAsKeys(msg.text) : [msg]) entry.ws.send(JSON.stringify(ev));
457
634
  sent += 1;
458
635
  }
459
636
  return reply(200, { ok: true, accepted: sent });
@@ -602,6 +779,8 @@ function _reset() {
602
779
  for (const name of [...sockets.keys()]) closeInputSocket(name);
603
780
  queues.clear();
604
781
  if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
782
+ reviving = null;
783
+ drive._reset();
605
784
  deps = defaultDeps();
606
785
  }
607
786
 
@@ -609,15 +788,20 @@ module.exports = {
609
788
  LOCK_TTL_MS,
610
789
  MAX_EVENTS,
611
790
  NAVIGATE_TIMEOUT_MS,
791
+ START_PAGE,
792
+ START_SESSION,
612
793
  SWEEP_INTERVAL_MS,
794
+ agentSocketDir,
613
795
  checkEvent,
614
796
  chatOrigin,
615
797
  control,
798
+ fitView,
616
799
  handBackLine,
617
800
  input,
618
801
  navigate,
619
802
  release,
620
803
  renew,
804
+ start,
621
805
  startSweeper,
622
806
  status,
623
807
  sweepLock,