@inetafrica/open-claudia 3.5.26 → 3.5.28

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.28
4
+
5
+ The Browser app is always on: opening it while the bot is not browsing opens the bot's browser, instead of a dead end (Sumeet, 10 Oct: "half the time i have to ask you switch on the browser"; 11 Oct 07:43 EAT network-mon's Browser showed only "isn't open right now"). A canary build for the Browser canary bot, on 3.5.26; it does not carry 3.5.27 (calls, Sumeet's own bot).
6
+
7
+ - **`POST /api/browser/start`** (Agent Space's control token only, like every Browser app route). With no browser open, the bot's own default session opens on a start page (`https://www.google.com/`), with the bot's own agent-browser environment and socket folder (`AGENT_BROWSER_SOCKET_DIR`, else `~/.agent-browser`), exactly as its own `agent-browser open` would, so the bot carries on in that tab. A browser already open is answered as it is (the newest session), and two people opening the app at once start one browser. `GET /api/browser/control` says `start: true`; Agent Space's front door (223b978) asks only bots that say so.
8
+ - **Tests:** `test-browser-control.js`, `test-web-browser-app.js`.
9
+
3
10
  ## 3.5.26
4
11
 
5
12
  The Browser app's phone and web view stays sharp after a page load. A canary build for the Browser canary bot first; the fleet waits for Sumeet's OK.
@@ -17,6 +17,9 @@
17
17
  // makes the bot's tab take its size, phone layout and touch, and then the driver's input goes
18
18
  // straight to Chrome through that view (typed text too: input_text). Only with Chrome in its
19
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.
20
23
  // - hand back (release, a closed panel, or a lock found expired by the 30 s sweeper): the lock
21
24
  // goes, the view and the socket close, any recording stops, and the bot is woken in the
22
25
  // person's own chat (C6), but only when it waits for them: it sent them an Open Browser card,
@@ -26,6 +29,8 @@
26
29
  // Every route answers { status, body }; web.js writes it. Agent Space reaches these routes with
27
30
  // the control token only.
28
31
 
32
+ const os = require("os");
33
+ const path = require("path");
29
34
  const WebSocket = require("ws");
30
35
  const lockFile = require("./browser-lock");
31
36
  const view = require("./browser-view");
@@ -48,6 +53,10 @@ const ID_RE = /^[A-Za-z0-9._:-]{1,100}$/;
48
53
  // An address-bar command still running after this is killed, and the person told it failed.
49
54
  const NAVIGATE_TIMEOUT_MS = 20 * 1000;
50
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";
51
60
 
52
61
  // An Open Browser card counts as a request for a person this long.
53
62
  const REQUEST_MAX_AGE_MS = 24 * 60 * 60 * 1000;
@@ -521,7 +530,8 @@ async function control(body = {}) {
521
530
  }
522
531
 
523
532
  // navigate: true tells the front door this bot has the address bar (POST /api/browser/navigate);
524
- // fit: true that a take can bring the driver's view (core/browser-drive.js) and input_text works.
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).
525
535
  function status() {
526
536
  const lock = lockFile.liveLock(deps.now());
527
537
  return reply(200, {
@@ -530,9 +540,49 @@ function status() {
530
540
  recording: teach.activeSummary(),
531
541
  navigate: true,
532
542
  fit: drive.supported(),
543
+ start: true,
533
544
  });
534
545
  }
535
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
+
536
586
  // ---------------------------------------------------------------------------
537
587
  // C3: input
538
588
 
@@ -738,7 +788,10 @@ module.exports = {
738
788
  LOCK_TTL_MS,
739
789
  MAX_EVENTS,
740
790
  NAVIGATE_TIMEOUT_MS,
791
+ START_PAGE,
792
+ START_SESSION,
741
793
  SWEEP_INTERVAL_MS,
794
+ agentSocketDir,
742
795
  checkEvent,
743
796
  chatOrigin,
744
797
  control,
@@ -748,6 +801,7 @@ module.exports = {
748
801
  navigate,
749
802
  release,
750
803
  renew,
804
+ start,
751
805
  startSweeper,
752
806
  status,
753
807
  sweepLock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.5.26",
3
+ "version": "3.5.28",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -617,7 +617,11 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
617
617
  // only a waiting bot. A canary build for the Browser canary bot; the fleet waits for his OK.
618
618
  // 3.5.26: the same go-ahead; 3.5.25's pictures fell back to 1x after a page load on the
619
619
  // canary's Chrome 150, now nudged back to the view's density. Canary first, fleet on his OK.
620
- assert.strictEqual(pkg.version, "3.5.26", "release version must remain unchanged without explicit approval");
620
+ // 3.5.28 approved under Sumeet's 2026-10-11 08:21 EAT go-ahead ("Yeah, that's fine") to the
621
+ // resize fix and Chrome-style bar first, then the always-on browser with one restart of the
622
+ // Browser canary bot: the Browser app opens the bot's browser when none is open. A canary build
623
+ // for the Browser canary bot on 3.5.26; it does not carry 3.5.27 (calls, his own bot only).
624
+ assert.strictEqual(pkg.version, "3.5.28", "release version must remain unchanged without explicit approval");
621
625
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
622
626
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
623
627
  }
package/web.js CHANGED
@@ -382,9 +382,11 @@ const USER_ACCESS_ROUTES = new Set(["/api/auth/approve", "/api/auth/deny", "/api
382
382
  // POST /api/browser/navigate the driver's address bar: open <url>
383
383
  // (public addresses only), back,
384
384
  // forward, reload
385
+ // POST /api/browser/start always on: open the bot's browser on
386
+ // a start page when none is open
385
387
  // POST /api/browser/teach Teach me: start / stop / cancel (C4)
386
388
  // GET /api/browser/teach[/<id>[/pictures/<n>]]?userId= that person's recordings only
387
- const BROWSER_APP_ROUTE = /^\/api\/(?:apps\/check|browser\/(?:control|input|navigate|teach))(?:[/?]|$)/;
389
+ const BROWSER_APP_ROUTE = /^\/api\/(?:apps\/check|browser\/(?:control|input|navigate|start|teach))(?:[/?]|$)/;
388
390
  const BROWSER_APP_MAX_BODY = 64 * 1024;
389
391
  const BROWSER_APP_USER_ID = /^[A-Za-z0-9._:-]{1,100}$/;
390
392
 
@@ -408,6 +410,7 @@ async function handleBrowserApp(req, res, body) {
408
410
  if (route === "/api/browser/control" && req.method === "GET") return answer(control.status());
409
411
  if (route === "/api/browser/input" && req.method === "POST") return answer(await control.input(parsed));
410
412
  if (route === "/api/browser/navigate" && req.method === "POST") return answer(await control.navigate(parsed));
413
+ if (route === "/api/browser/start" && req.method === "POST") return answer(await control.start(parsed));
411
414
  if (route === "/api/browser/teach" && req.method === "POST") return answer(await control.teachAction(parsed));
412
415
  if (req.method === "GET" && (route === "/api/browser/teach" || route.startsWith("/api/browser/teach/"))) {
413
416
  const teach = require("./core/browser-teach");