browsentic 0.4.6 → 0.4.8

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/dist/cli.js CHANGED
@@ -28636,12 +28636,9 @@ var CURSOR_PATHS = [
28636
28636
  ];
28637
28637
  var CURSOR2 = `url("data:image/svg+xml,${encodeURIComponent(cursorSvg())}") 14 14, crosshair`;
28638
28638
  var DEFAULT_HINT = "Click the element you mean";
28639
- var picking = false;
28640
- function lensIsUp() {
28641
- return picking;
28642
- }
28639
+ var dismissCurrent = null;
28643
28640
  function pickWithLens({ hint, timeoutMs }) {
28644
- picking = true;
28641
+ dismissCurrent?.();
28645
28642
  const host = document.createElement("div");
28646
28643
  host.id = HOST_ID;
28647
28644
  host.style.cssText = "all: initial; position: static;";
@@ -28656,6 +28653,8 @@ function pickWithLens({ hint, timeoutMs }) {
28656
28653
  const chip = root.querySelector(".chip");
28657
28654
  let hovered = null;
28658
28655
  return new Promise((resolve) => {
28656
+ const dismiss = () => settle2({ cancelled: true });
28657
+ dismissCurrent = dismiss;
28659
28658
  const timer = setTimeout(() => settle2({ timedOut: true }), timeoutMs);
28660
28659
  const mute = (event) => {
28661
28660
  event.stopPropagation();
@@ -28720,7 +28719,7 @@ function pickWithLens({ hint, timeoutMs }) {
28720
28719
  }
28721
28720
  host.remove();
28722
28721
  cursor.remove();
28723
- picking = false;
28722
+ if (dismissCurrent === dismiss) dismissCurrent = null;
28724
28723
  resolve(outcome);
28725
28724
  }
28726
28725
  });
@@ -28799,18 +28798,16 @@ function styles() {
28799
28798
 
28800
28799
  // ../lib/actions/page/pick-element.ts
28801
28800
  var MAX_CONTENT = 2e4;
28801
+ var PICK_DEFAULT_TIMEOUT_MS = 6e4;
28802
28802
  var pickElement = defineAction({
28803
28803
  name: "page.pickElement",
28804
- description: "Ask the user to point at an element \u2014 A-Eye. Their cursor becomes a lens, whatever they hover is outlined, and the element they click comes back with its selector, its role and its rendered text. Use it when a target is genuinely ambiguous \u2014 several things share a label, or the user said \u201Cthis one\u201D about something you cannot see \u2014 and pointing is faster than describing. It takes over the page and waits for a person, so never call it to explore, and never call it twice in a row.",
28804
+ description: "Ask the user to point at an element \u2014 A-Eye. Their cursor becomes a lens, whatever they hover is outlined, and the element they click comes back with its selector, its role, its rendered text and a screenshot of it exactly as they saw it. Use it when a target is genuinely ambiguous \u2014 several things share a label, or the user said \u201Cthis one\u201D about something you cannot see \u2014 and pointing is faster than describing. It takes over the page and waits for a person, so never call it to explore; a new call dismisses a pick already waiting.",
28805
28805
  input: external_exports.object({
28806
28806
  hint: external_exports.string().max(120).optional().describe("One line shown over the page saying what to point at, e.g. \u201CPoint at the price you mean\u201D"),
28807
28807
  maxContentLength: external_exports.number().int().positive().max(MAX_CONTENT).default(2e3).describe('Characters of the element\u2019s rendered text to return; past that it is cut and "truncated" comes back true'),
28808
- timeoutMs: external_exports.number().int().min(5e3).max(3e5).default(6e4).describe("How long to wait for the user to click before giving up")
28808
+ timeoutMs: external_exports.number().int().min(5e3).max(3e5).default(PICK_DEFAULT_TIMEOUT_MS).describe("How long to wait for the user to click before giving up")
28809
28809
  }),
28810
28810
  async execute({ hint, maxContentLength, timeoutMs }) {
28811
- if (lensIsUp()) {
28812
- throw new ActionError("A-Eye is already waiting for the user to point at something", "ACTION_FAILED");
28813
- }
28814
28811
  const outcome = await pickWithLens({ hint, timeoutMs });
28815
28812
  if ("timedOut" in outcome) {
28816
28813
  throw new ActionError(
@@ -28824,12 +28821,18 @@ var pickElement = defineAction({
28824
28821
  const element = outcome.picked;
28825
28822
  const rendered2 = element instanceof HTMLElement ? element.innerText : element.textContent ?? "";
28826
28823
  const content = rendered2.replace(/\n{3,}/g, "\n\n").trim() || accessibleText(element);
28824
+ const rect = element.getBoundingClientRect();
28827
28825
  return {
28828
28826
  element: describeElement(element),
28829
28827
  content: content.slice(0, maxContentLength),
28830
28828
  truncated: content.length > maxContentLength,
28831
28829
  url: location.href,
28832
- title: document.title
28830
+ title: document.title,
28831
+ capture: {
28832
+ region: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
28833
+ viewport: { w: window.innerWidth, h: window.innerHeight },
28834
+ dpr: window.devicePixelRatio || 1
28835
+ }
28833
28836
  };
28834
28837
  }
28835
28838
  });
@@ -29895,7 +29898,8 @@ var AGENTS = {
29895
29898
  vendor: "Anthropic",
29896
29899
  bin: "claude",
29897
29900
  install: "npm i -g @anthropic-ai/claude-code",
29898
- docs: "https://claude.com/claude-code"
29901
+ docs: "https://claude.com/claude-code",
29902
+ models: ["claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5"]
29899
29903
  },
29900
29904
  codex: {
29901
29905
  kind: "codex",
@@ -29903,7 +29907,8 @@ var AGENTS = {
29903
29907
  vendor: "OpenAI",
29904
29908
  bin: "codex",
29905
29909
  install: "npm i -g @openai/codex",
29906
- docs: "https://developers.openai.com/codex/cli"
29910
+ docs: "https://developers.openai.com/codex/cli",
29911
+ models: ["gpt-5.6-terra", "gpt-5.1-codex-max", "gpt-5.1-codex", "gpt-5.1-codex-mini"]
29907
29912
  },
29908
29913
  antigravity: {
29909
29914
  kind: "antigravity",
@@ -29911,7 +29916,8 @@ var AGENTS = {
29911
29916
  vendor: "Google",
29912
29917
  bin: "agy",
29913
29918
  install: "https://antigravity.google/docs/cli/install",
29914
- docs: "https://antigravity.google/docs/cli"
29919
+ docs: "https://antigravity.google/docs/cli",
29920
+ models: ["gemini-3-pro", "gemini-3-flash"]
29915
29921
  }
29916
29922
  };
29917
29923
  var AGENT_LIST = AGENT_KINDS.map((kind) => AGENTS[kind]);
@@ -29925,11 +29931,13 @@ var SAVE_SITE_MAP_ACTION = `${RESERVED_PREFIX}saveSiteMap`;
29925
29931
  var START_RECORDING_ACTION = `${RESERVED_PREFIX}startRecording`;
29926
29932
  var STOP_RECORDING_ACTION = `${RESERVED_PREFIX}stopRecording`;
29927
29933
  var READ_SITEMAP_ACTION = `${RESERVED_PREFIX}readSitemap`;
29934
+ var FOCUS_SHOT_ACTION = `${RESERVED_PREFIX}focusShot`;
29928
29935
  var RESERVED_ACTIONS = [
29929
29936
  SAVE_SITE_MAP_ACTION,
29930
29937
  START_RECORDING_ACTION,
29931
29938
  STOP_RECORDING_ACTION,
29932
- READ_SITEMAP_ACTION
29939
+ READ_SITEMAP_ACTION,
29940
+ FOCUS_SHOT_ACTION
29933
29941
  ];
29934
29942
 
29935
29943
  // ../lib/actions/tool-names.ts
@@ -30243,6 +30251,15 @@ var claudeRunner = {
30243
30251
  };
30244
30252
  },
30245
30253
  reader() {
30254
+ let generated = 0;
30255
+ const report = (usage, sink) => {
30256
+ if (!usage) return;
30257
+ generated += usage.output_tokens ?? 0;
30258
+ sink.usage({
30259
+ contextTokens: (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.output_tokens ?? 0),
30260
+ outputTokens: generated
30261
+ });
30262
+ };
30246
30263
  return (line, sink) => {
30247
30264
  const message = parseJsonLine(line);
30248
30265
  if (!message) return;
@@ -30266,10 +30283,14 @@ var claudeRunner = {
30266
30283
  }
30267
30284
  return;
30268
30285
  }
30286
+ case "assistant":
30287
+ if (!message.parent_tool_use_id) report(message.message?.usage, sink);
30288
+ return;
30269
30289
  case "result":
30270
30290
  if (message.is_error) {
30271
30291
  return sink.fail("AGENT_FAILED", message.result || message.subtype || "Claude Code reported an error");
30272
30292
  }
30293
+ if (!generated) report(message.usage, sink);
30273
30294
  return sink.done(message.stop_reason || "end_turn");
30274
30295
  }
30275
30296
  };
@@ -30580,6 +30601,17 @@ var codexRunner = {
30580
30601
  return finish(msg.message, sink);
30581
30602
  case "web_search_begin":
30582
30603
  return sink.tool(randomUUID3(), WEB_TOOL);
30604
+ case "token_count": {
30605
+ const last = msg.info?.last_token_usage ?? msg.info?.total_token_usage;
30606
+ const total = msg.info?.total_token_usage ?? last;
30607
+ if (last) {
30608
+ sink.usage({
30609
+ contextTokens: (last.input_tokens ?? 0) + (last.output_tokens ?? 0),
30610
+ outputTokens: total?.output_tokens ?? 0
30611
+ });
30612
+ }
30613
+ return;
30614
+ }
30583
30615
  case "task_complete":
30584
30616
  return sink.done("end_turn");
30585
30617
  case "error":
@@ -30603,8 +30635,16 @@ var codexRunner = {
30603
30635
  if (kind === "web_search") return sink.tool(item?.id ?? randomUUID3(), WEB_TOOL);
30604
30636
  return;
30605
30637
  }
30606
- case "turn.completed":
30638
+ case "turn.completed": {
30639
+ const usage = frame.usage;
30640
+ if (usage) {
30641
+ sink.usage({
30642
+ contextTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
30643
+ outputTokens: usage.output_tokens ?? 0
30644
+ });
30645
+ }
30607
30646
  return sink.done("end_turn");
30647
+ }
30608
30648
  case "turn.failed":
30609
30649
  return sink.fail("AGENT_FAILED", frame.error?.message || "Codex could not finish the turn");
30610
30650
  case "error":
@@ -31707,6 +31747,7 @@ function invokeTimeoutFor(action, input2) {
31707
31747
  const declared = input2?.timeoutMs;
31708
31748
  if (typeof declared === "number" && declared > 0) return declared + 1e4;
31709
31749
  if (action === awaitMonitor.name) return AWAIT_DEFAULT_TIMEOUT_MS + 1e4;
31750
+ if (action === pickElement.name) return PICK_DEFAULT_TIMEOUT_MS + 1e4;
31710
31751
  return REQUEST_TIMEOUT_MS;
31711
31752
  }
31712
31753
 
@@ -33455,6 +33496,12 @@ var Server = class extends Protocol {
33455
33496
  // server.ts
33456
33497
  var STATUS_TOOL = toolNameFor(`${RESERVED_PREFIX}status`);
33457
33498
  var SCREENSHOT_TOOL = "page_screenshot";
33499
+ var PICK_TOOL = "page_pickElement";
33500
+ var FOCUS_SHOT_TOOL = {
33501
+ name: toolNameFor(FOCUS_SHOT_ACTION),
33502
+ description: "Show the screenshot of the element the user pointed at with A-Eye, taken at the instant they picked it. Only answers when the current instruction arrived with a pick attached.",
33503
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
33504
+ };
33458
33505
  var RESOURCES = [
33459
33506
  {
33460
33507
  uri: "browsentic://page/current",
@@ -33568,7 +33615,7 @@ function createMcpServer(bridge, version2, opts = {}) {
33568
33615
  description: "Report whether the Browsentic browser extension is connected, its version, and the active tab. Use this first if a page tool fails.",
33569
33616
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
33570
33617
  },
33571
- ...opts.agentRun ? [SAVE_SITE_MAP_TOOL] : []
33618
+ ...opts.agentRun ? [SAVE_SITE_MAP_TOOL, FOCUS_SHOT_TOOL] : []
33572
33619
  ]
33573
33620
  };
33574
33621
  });
@@ -33577,6 +33624,8 @@ function createMcpServer(bridge, version2, opts = {}) {
33577
33624
  const action = actionNameFor(params.name);
33578
33625
  const result = await bridge.invoke(action, params.arguments ?? {});
33579
33626
  if (params.name === SCREENSHOT_TOOL) return renderScreenshot(result);
33627
+ if (params.name === FOCUS_SHOT_TOOL.name) return renderFocusShot(result);
33628
+ if (params.name === PICK_TOOL) return renderPick(result, shouldFence(action, policy) ? tag2 : void 0);
33580
33629
  return render(result, shouldFence(action, policy) ? tag2 : void 0);
33581
33630
  });
33582
33631
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [...RESOURCES] }));
@@ -33654,6 +33703,38 @@ function renderScreenshot(result) {
33654
33703
  ]
33655
33704
  };
33656
33705
  }
33706
+ function renderPick(result, fenceWith) {
33707
+ if (!result.ok) return render(result, fenceWith);
33708
+ const { shot, ...rest } = result.data;
33709
+ if (typeof shot?.dataUrl !== "string") return render(result, fenceWith);
33710
+ const [mimeType, base643] = splitDataUrl(shot.dataUrl);
33711
+ const rendered2 = render({ ok: true, data: rest }, fenceWith);
33712
+ return {
33713
+ content: [
33714
+ ...rendered2.content,
33715
+ { type: "image", data: base643, mimeType },
33716
+ {
33717
+ type: "text",
33718
+ text: `${IMAGE_NOTE} This is the picked element photographed at the instant the user clicked it.`
33719
+ }
33720
+ ]
33721
+ };
33722
+ }
33723
+ function renderFocusShot(result) {
33724
+ if (!result.ok) return render(result);
33725
+ const dataUrl = result.data?.dataUrl;
33726
+ if (typeof dataUrl !== "string") return render(result);
33727
+ const [mimeType, base643] = splitDataUrl(dataUrl);
33728
+ return {
33729
+ content: [
33730
+ { type: "image", data: base643, mimeType },
33731
+ {
33732
+ type: "text",
33733
+ text: `${IMAGE_NOTE} This is the element the user pointed at with A-Eye, as it stood when they picked it.`
33734
+ }
33735
+ ]
33736
+ };
33737
+ }
33657
33738
  function splitDataUrl(dataUrl) {
33658
33739
  const match = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl);
33659
33740
  return match ? [match[1], match[2]] : ["image/png", ""];
@@ -33679,7 +33760,7 @@ function text2(uri, mimeType, body) {
33679
33760
  // package.json
33680
33761
  var package_default = {
33681
33762
  name: "browsentic",
33682
- version: "0.4.6",
33763
+ version: "0.4.8",
33683
33764
  description: "Hand your real, logged-in browser to the AI agent you already run. Installs the browser extension, runs the local daemon, and speaks MCP.",
33684
33765
  type: "module",
33685
33766
  license: "MIT",
@@ -3722,7 +3722,7 @@ var import_websocket = __toESM(require_websocket(), 1);
3722
3722
  var import_websocket_server = __toESM(require_websocket_server(), 1);
3723
3723
 
3724
3724
  // ../lib/actions/protocol.ts
3725
- var SOCKET_PROTOCOL_VERSION = 14;
3725
+ var SOCKET_PROTOCOL_VERSION = 16;
3726
3726
  var EXTERNAL_RUN_ID = "external";
3727
3727
  var DAEMON_PORTS = [8765, 8766, 8767];
3728
3728
  var EXTENSION_REQUEST_FRAMES = [
@@ -3740,6 +3740,7 @@ var EXTENSION_REQUEST_FRAMES = [
3740
3740
  "analyzeRecording",
3741
3741
  "agentState",
3742
3742
  "setAgent",
3743
+ "setAgentModel",
3743
3744
  "grantAgent",
3744
3745
  "listSkills",
3745
3746
  "guardrails",
@@ -20228,12 +20229,9 @@ var CURSOR_PATHS = [
20228
20229
  ];
20229
20230
  var CURSOR2 = `url("data:image/svg+xml,${encodeURIComponent(cursorSvg())}") 14 14, crosshair`;
20230
20231
  var DEFAULT_HINT = "Click the element you mean";
20231
- var picking = false;
20232
- function lensIsUp() {
20233
- return picking;
20234
- }
20232
+ var dismissCurrent = null;
20235
20233
  function pickWithLens({ hint, timeoutMs }) {
20236
- picking = true;
20234
+ dismissCurrent?.();
20237
20235
  const host = document.createElement("div");
20238
20236
  host.id = HOST_ID;
20239
20237
  host.style.cssText = "all: initial; position: static;";
@@ -20248,6 +20246,8 @@ function pickWithLens({ hint, timeoutMs }) {
20248
20246
  const chip = root.querySelector(".chip");
20249
20247
  let hovered = null;
20250
20248
  return new Promise((resolve4) => {
20249
+ const dismiss = () => settle2({ cancelled: true });
20250
+ dismissCurrent = dismiss;
20251
20251
  const timer = setTimeout(() => settle2({ timedOut: true }), timeoutMs);
20252
20252
  const mute = (event) => {
20253
20253
  event.stopPropagation();
@@ -20312,7 +20312,7 @@ function pickWithLens({ hint, timeoutMs }) {
20312
20312
  }
20313
20313
  host.remove();
20314
20314
  cursor.remove();
20315
- picking = false;
20315
+ if (dismissCurrent === dismiss) dismissCurrent = null;
20316
20316
  resolve4(outcome);
20317
20317
  }
20318
20318
  });
@@ -20391,18 +20391,16 @@ function styles() {
20391
20391
 
20392
20392
  // ../lib/actions/page/pick-element.ts
20393
20393
  var MAX_CONTENT = 2e4;
20394
+ var PICK_DEFAULT_TIMEOUT_MS = 6e4;
20394
20395
  var pickElement = defineAction({
20395
20396
  name: "page.pickElement",
20396
- description: "Ask the user to point at an element \u2014 A-Eye. Their cursor becomes a lens, whatever they hover is outlined, and the element they click comes back with its selector, its role and its rendered text. Use it when a target is genuinely ambiguous \u2014 several things share a label, or the user said \u201Cthis one\u201D about something you cannot see \u2014 and pointing is faster than describing. It takes over the page and waits for a person, so never call it to explore, and never call it twice in a row.",
20397
+ description: "Ask the user to point at an element \u2014 A-Eye. Their cursor becomes a lens, whatever they hover is outlined, and the element they click comes back with its selector, its role, its rendered text and a screenshot of it exactly as they saw it. Use it when a target is genuinely ambiguous \u2014 several things share a label, or the user said \u201Cthis one\u201D about something you cannot see \u2014 and pointing is faster than describing. It takes over the page and waits for a person, so never call it to explore; a new call dismisses a pick already waiting.",
20397
20398
  input: external_exports.object({
20398
20399
  hint: external_exports.string().max(120).optional().describe("One line shown over the page saying what to point at, e.g. \u201CPoint at the price you mean\u201D"),
20399
20400
  maxContentLength: external_exports.number().int().positive().max(MAX_CONTENT).default(2e3).describe('Characters of the element\u2019s rendered text to return; past that it is cut and "truncated" comes back true'),
20400
- timeoutMs: external_exports.number().int().min(5e3).max(3e5).default(6e4).describe("How long to wait for the user to click before giving up")
20401
+ timeoutMs: external_exports.number().int().min(5e3).max(3e5).default(PICK_DEFAULT_TIMEOUT_MS).describe("How long to wait for the user to click before giving up")
20401
20402
  }),
20402
20403
  async execute({ hint, maxContentLength, timeoutMs }) {
20403
- if (lensIsUp()) {
20404
- throw new ActionError("A-Eye is already waiting for the user to point at something", "ACTION_FAILED");
20405
- }
20406
20404
  const outcome = await pickWithLens({ hint, timeoutMs });
20407
20405
  if ("timedOut" in outcome) {
20408
20406
  throw new ActionError(
@@ -20416,12 +20414,18 @@ var pickElement = defineAction({
20416
20414
  const element = outcome.picked;
20417
20415
  const rendered2 = element instanceof HTMLElement ? element.innerText : element.textContent ?? "";
20418
20416
  const content = rendered2.replace(/\n{3,}/g, "\n\n").trim() || accessibleText(element);
20417
+ const rect = element.getBoundingClientRect();
20419
20418
  return {
20420
20419
  element: describeElement(element),
20421
20420
  content: content.slice(0, maxContentLength),
20422
20421
  truncated: content.length > maxContentLength,
20423
20422
  url: location.href,
20424
- title: document.title
20423
+ title: document.title,
20424
+ capture: {
20425
+ region: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
20426
+ viewport: { w: window.innerWidth, h: window.innerHeight },
20427
+ dpr: window.devicePixelRatio || 1
20428
+ }
20425
20429
  };
20426
20430
  }
20427
20431
  });
@@ -21489,6 +21493,7 @@ var SAVE_SITE_MAP_ACTION = `${RESERVED_PREFIX}saveSiteMap`;
21489
21493
  var START_RECORDING_ACTION = `${RESERVED_PREFIX}startRecording`;
21490
21494
  var STOP_RECORDING_ACTION = `${RESERVED_PREFIX}stopRecording`;
21491
21495
  var READ_SITEMAP_ACTION = `${RESERVED_PREFIX}readSitemap`;
21496
+ var FOCUS_SHOT_ACTION = `${RESERVED_PREFIX}focusShot`;
21492
21497
 
21493
21498
  // ../lib/agents/catalog.ts
21494
21499
  var AGENT_KINDS = ["claude", "codex", "antigravity"];
@@ -21500,7 +21505,8 @@ var AGENTS = {
21500
21505
  vendor: "Anthropic",
21501
21506
  bin: "claude",
21502
21507
  install: "npm i -g @anthropic-ai/claude-code",
21503
- docs: "https://claude.com/claude-code"
21508
+ docs: "https://claude.com/claude-code",
21509
+ models: ["claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5"]
21504
21510
  },
21505
21511
  codex: {
21506
21512
  kind: "codex",
@@ -21508,7 +21514,8 @@ var AGENTS = {
21508
21514
  vendor: "OpenAI",
21509
21515
  bin: "codex",
21510
21516
  install: "npm i -g @openai/codex",
21511
- docs: "https://developers.openai.com/codex/cli"
21517
+ docs: "https://developers.openai.com/codex/cli",
21518
+ models: ["gpt-5.6-terra", "gpt-5.1-codex-max", "gpt-5.1-codex", "gpt-5.1-codex-mini"]
21512
21519
  },
21513
21520
  antigravity: {
21514
21521
  kind: "antigravity",
@@ -21516,7 +21523,8 @@ var AGENTS = {
21516
21523
  vendor: "Google",
21517
21524
  bin: "agy",
21518
21525
  install: "https://antigravity.google/docs/cli/install",
21519
- docs: "https://antigravity.google/docs/cli"
21526
+ docs: "https://antigravity.google/docs/cli",
21527
+ models: ["gemini-3-pro", "gemini-3-flash"]
21520
21528
  }
21521
21529
  };
21522
21530
  var AGENT_LIST = AGENT_KINDS.map((kind) => AGENTS[kind]);
@@ -21642,6 +21650,21 @@ function writeActiveAgent(kind) {
21642
21650
  const stored = readStored();
21643
21651
  write({ ...stored, agent: kind });
21644
21652
  }
21653
+ function writeAgentModel(kind, model) {
21654
+ const stored = readStored();
21655
+ const agents = { ...stored.agents ?? {} };
21656
+ const scoped = { ...agents[kind] ?? {} };
21657
+ const value = text(model);
21658
+ if (value) scoped.model = value;
21659
+ else delete scoped.model;
21660
+ if (Object.keys(scoped).length) agents[kind] = scoped;
21661
+ else delete agents[kind];
21662
+ const next = { ...stored };
21663
+ if (Object.keys(agents).length) next.agents = agents;
21664
+ else delete next.agents;
21665
+ if (kind === "claude") delete next.model;
21666
+ write(next);
21667
+ }
21645
21668
  function writeGuardrailSetting(setting, value) {
21646
21669
  const stored = readStored();
21647
21670
  const guardrails = { ...stored.guardrails ?? {} };
@@ -23346,6 +23369,11 @@ function revokeSessions(predicate) {
23346
23369
  // agent/service.ts
23347
23370
  import { randomUUID as randomUUID6 } from "crypto";
23348
23371
 
23372
+ // ../lib/actions/tool-names.ts
23373
+ function toolNameFor(actionName) {
23374
+ return actionName.replaceAll(".", "_");
23375
+ }
23376
+
23349
23377
  // ../lib/skills/scrub.ts
23350
23378
  var CONTROL_CHARS = new RegExp(
23351
23379
  "[\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f\\u200b-\\u200f\\u202a-\\u202e\\u2066-\\u2069\\ufeff]",
@@ -23615,6 +23643,15 @@ var claudeRunner = {
23615
23643
  };
23616
23644
  },
23617
23645
  reader() {
23646
+ let generated = 0;
23647
+ const report = (usage, sink) => {
23648
+ if (!usage) return;
23649
+ generated += usage.output_tokens ?? 0;
23650
+ sink.usage({
23651
+ contextTokens: (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.output_tokens ?? 0),
23652
+ outputTokens: generated
23653
+ });
23654
+ };
23618
23655
  return (line, sink) => {
23619
23656
  const message = parseJsonLine(line);
23620
23657
  if (!message) return;
@@ -23638,10 +23675,14 @@ var claudeRunner = {
23638
23675
  }
23639
23676
  return;
23640
23677
  }
23678
+ case "assistant":
23679
+ if (!message.parent_tool_use_id) report(message.message?.usage, sink);
23680
+ return;
23641
23681
  case "result":
23642
23682
  if (message.is_error) {
23643
23683
  return sink.fail("AGENT_FAILED", message.result || message.subtype || "Claude Code reported an error");
23644
23684
  }
23685
+ if (!generated) report(message.usage, sink);
23645
23686
  return sink.done(message.stop_reason || "end_turn");
23646
23687
  }
23647
23688
  };
@@ -23952,6 +23993,17 @@ var codexRunner = {
23952
23993
  return finish(msg.message, sink);
23953
23994
  case "web_search_begin":
23954
23995
  return sink.tool(randomUUID4(), WEB_TOOL);
23996
+ case "token_count": {
23997
+ const last = msg.info?.last_token_usage ?? msg.info?.total_token_usage;
23998
+ const total = msg.info?.total_token_usage ?? last;
23999
+ if (last) {
24000
+ sink.usage({
24001
+ contextTokens: (last.input_tokens ?? 0) + (last.output_tokens ?? 0),
24002
+ outputTokens: total?.output_tokens ?? 0
24003
+ });
24004
+ }
24005
+ return;
24006
+ }
23955
24007
  case "task_complete":
23956
24008
  return sink.done("end_turn");
23957
24009
  case "error":
@@ -23975,8 +24027,16 @@ var codexRunner = {
23975
24027
  if (kind === "web_search") return sink.tool(item?.id ?? randomUUID4(), WEB_TOOL);
23976
24028
  return;
23977
24029
  }
23978
- case "turn.completed":
24030
+ case "turn.completed": {
24031
+ const usage = frame.usage;
24032
+ if (usage) {
24033
+ sink.usage({
24034
+ contextTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
24035
+ outputTokens: usage.output_tokens ?? 0
24036
+ });
24037
+ }
23979
24038
  return sink.done("end_turn");
24039
+ }
23980
24040
  case "turn.failed":
23981
24041
  return sink.fail("AGENT_FAILED", frame.error?.message || "Codex could not finish the turn");
23982
24042
  case "error":
@@ -24087,6 +24147,7 @@ async function probe2(runner, settings) {
24087
24147
  return {
24088
24148
  kind: runner.kind,
24089
24149
  bin: settings.bin,
24150
+ model: settings.model,
24090
24151
  ready: false,
24091
24152
  problem: {
24092
24153
  code: "AGENT_MISSING",
@@ -24099,6 +24160,7 @@ async function probe2(runner, settings) {
24099
24160
  return {
24100
24161
  kind: runner.kind,
24101
24162
  bin: settings.bin,
24163
+ model: settings.model,
24102
24164
  ready: false,
24103
24165
  problem: {
24104
24166
  code: "AGENT_UNUSABLE",
@@ -24108,7 +24170,14 @@ async function probe2(runner, settings) {
24108
24170
  };
24109
24171
  }
24110
24172
  const problem = await runner.check?.(settings) ?? null;
24111
- return { kind: runner.kind, bin: settings.bin, ready: !problem, version: found.detail, problem: problem ?? void 0 };
24173
+ return {
24174
+ kind: runner.kind,
24175
+ bin: settings.bin,
24176
+ model: settings.model,
24177
+ ready: !problem,
24178
+ version: found.detail,
24179
+ problem: problem ?? void 0
24180
+ };
24112
24181
  }
24113
24182
  function version2(bin, args) {
24114
24183
  return new Promise((resolve4) => {
@@ -24614,6 +24683,7 @@ function runStream(runner, context, signal, emit) {
24614
24683
  session: (id) => {
24615
24684
  if (id) sessionId = id;
24616
24685
  },
24686
+ usage: (usage) => emit({ kind: "usage", usage }),
24617
24687
  done: (stopReason) => settle2(() => {
24618
24688
  flush();
24619
24689
  resolve4({ stopReason, sessionId });
@@ -25287,6 +25357,7 @@ function isPrivateAddress(address) {
25287
25357
  }
25288
25358
 
25289
25359
  // agent/service.ts
25360
+ var FOCUS_SHOT_TOOL_NAME = toolNameFor(FOCUS_SHOT_ACTION);
25290
25361
  var AgentSession = class {
25291
25362
  constructor(deps) {
25292
25363
  this.deps = deps;
@@ -25333,6 +25404,16 @@ var AgentSession = class {
25333
25404
  emit({ kind: "toolResult", toolId, ok: result2.ok, summary: summarize(input2, result2) });
25334
25405
  return result2;
25335
25406
  }
25407
+ if (action === FOCUS_SHOT_ACTION) {
25408
+ const result2 = run.focusShot ? success({ dataUrl: run.focusShot }) : failure("NO_FOCUS_SHOT", "No A-Eye pick came with this instruction, so nothing was photographed.");
25409
+ emit({
25410
+ kind: "toolResult",
25411
+ toolId,
25412
+ ok: result2.ok,
25413
+ summary: result2.ok ? "the picked element, as photographed" : "no pick attached"
25414
+ });
25415
+ return result2;
25416
+ }
25336
25417
  if (run.map) {
25337
25418
  const gate = gateMappingInvoke(run.map, action, input2);
25338
25419
  if (!gate.allow) {
@@ -25442,7 +25523,8 @@ var AgentSession = class {
25442
25523
  site: siteOf(context?.url),
25443
25524
  abort: new AbortController(),
25444
25525
  pending: /* @__PURE__ */ new Map(),
25445
- ownedTabIds: []
25526
+ ownedTabIds: [],
25527
+ focusShot: context?.focus?.shot
25446
25528
  };
25447
25529
  this.runs.set(runId, run);
25448
25530
  const runner = activeRunner(await agentState(config2));
@@ -25648,6 +25730,11 @@ function focusBlock(focus) {
25648
25730
  ];
25649
25731
  if (focus.label) lines.push(`- Label: ${flatten2(focus.label)}`);
25650
25732
  lines.push(`- On: ${flatten2(focus.title)} \u2014 ${flatten2(focus.url)}`);
25733
+ if (focus.shot) {
25734
+ lines.push(
25735
+ `- A screenshot of it, taken at the instant they picked it, is attached \u2014 call \`${FOCUS_SHOT_TOOL_NAME}\` once to see it.`
25736
+ );
25737
+ }
25651
25738
  const content = focus.content.slice(0, MAX_FOCUS_CONTENT);
25652
25739
  const cut = focus.truncated || content.length < focus.content.length;
25653
25740
  lines.push(
@@ -26297,6 +26384,7 @@ function timeoutFor(action, input2) {
26297
26384
  if (action === typeText.name) return typingDurationMs(input2) + DEFAULT_TIMEOUT_MS2;
26298
26385
  if (action === startMonitor.name) return DEFAULT_TIMEOUT_MS2;
26299
26386
  if (action === awaitMonitor.name) return (declaredTimeout(input2) ?? AWAIT_DEFAULT_TIMEOUT_MS) + 5e3;
26387
+ if (action === pickElement.name) return (declaredTimeout(input2) ?? PICK_DEFAULT_TIMEOUT_MS) + 5e3;
26300
26388
  const declared = declaredTimeout(input2);
26301
26389
  return declared != null ? declared + 5e3 : DEFAULT_TIMEOUT_MS2;
26302
26390
  }
@@ -26525,7 +26613,7 @@ async function startDaemon({ version: version3, idleExit = true }) {
26525
26613
  );
26526
26614
  return;
26527
26615
  }
26528
- if (request.t === "agentState" || request.t === "setAgent" || request.t === "grantAgent") {
26616
+ if (request.t === "agentState" || request.t === "setAgent" || request.t === "setAgentModel" || request.t === "grantAgent") {
26529
26617
  void settleAgent(request).then((state) => {
26530
26618
  source.send({ t: "agentInfo", id: request.id, result: state });
26531
26619
  if (request.t === "setAgent") pushSkillCatalog(source);
@@ -26584,6 +26672,11 @@ async function startDaemon({ version: version3, idleExit = true }) {
26584
26672
  agent?.handle({ t: "reset" });
26585
26673
  log(`agent set to ${AGENTS[request.agent].label}`);
26586
26674
  }
26675
+ if (request.t === "setAgentModel") {
26676
+ const model = typeof request.model === "string" ? request.model : null;
26677
+ writeAgentModel(request.agent, model);
26678
+ log(`${AGENTS[request.agent].label} model set to ${model?.trim() || "the default"}`);
26679
+ }
26587
26680
  if (request.t === "grantAgent") await grantRunner(request.agent);
26588
26681
  const refresh = request.t !== "agentState" || request.refresh === true;
26589
26682
  return success(await agentState(readAgentConfig(), { refresh }));
@@ -26874,7 +26967,7 @@ X-Browsentic-Reason: ${reason}\r
26874
26967
  // package.json
26875
26968
  var package_default = {
26876
26969
  name: "browsentic",
26877
- version: "0.4.6",
26970
+ version: "0.4.8",
26878
26971
  description: "Hand your real, logged-in browser to the AI agent you already run. Installs the browser extension, runs the local daemon, and speaks MCP.",
26879
26972
  type: "module",
26880
26973
  license: "MIT",