browsentic 0.7.5 → 0.7.6

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.
@@ -66,13 +66,13 @@ var require_buffer_util = __commonJS({
66
66
  "use strict";
67
67
  var { EMPTY_BUFFER } = require_constants();
68
68
  var FastBuffer = Buffer[Symbol.species];
69
- function concat(list2, totalLength) {
70
- if (list2.length === 0) return EMPTY_BUFFER;
71
- if (list2.length === 1) return list2[0];
69
+ function concat(list3, totalLength) {
70
+ if (list3.length === 0) return EMPTY_BUFFER;
71
+ if (list3.length === 1) return list3[0];
72
72
  const target = Buffer.allocUnsafe(totalLength);
73
73
  let offset = 0;
74
- for (let i = 0; i < list2.length; i++) {
75
- const buf = list2[i];
74
+ for (let i = 0; i < list3.length; i++) {
75
+ const buf = list3[i];
76
76
  target.set(buf, offset);
77
77
  offset += buf.length;
78
78
  }
@@ -313,30 +313,30 @@ var require_permessage_deflate = __commonJS({
313
313
  */
314
314
  acceptAsServer(offers) {
315
315
  const opts = this._options;
316
- const accepted = offers.find((params) => {
316
+ const accepted2 = offers.find((params) => {
317
317
  if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && !params.client_max_window_bits) {
318
318
  return false;
319
319
  }
320
320
  return true;
321
321
  });
322
- if (!accepted) {
322
+ if (!accepted2) {
323
323
  throw new Error("None of the extension offers can be accepted");
324
324
  }
325
325
  if (opts.serverNoContextTakeover) {
326
- accepted.server_no_context_takeover = true;
326
+ accepted2.server_no_context_takeover = true;
327
327
  }
328
328
  if (opts.clientNoContextTakeover) {
329
- accepted.client_no_context_takeover = true;
329
+ accepted2.client_no_context_takeover = true;
330
330
  }
331
331
  if (typeof opts.serverMaxWindowBits === "number") {
332
- accepted.server_max_window_bits = opts.serverMaxWindowBits;
332
+ accepted2.server_max_window_bits = opts.serverMaxWindowBits;
333
333
  }
334
334
  if (typeof opts.clientMaxWindowBits === "number") {
335
- accepted.client_max_window_bits = opts.clientMaxWindowBits;
336
- } else if (accepted.client_max_window_bits === true || opts.clientMaxWindowBits === false) {
337
- delete accepted.client_max_window_bits;
335
+ accepted2.client_max_window_bits = opts.clientMaxWindowBits;
336
+ } else if (accepted2.client_max_window_bits === true || opts.clientMaxWindowBits === false) {
337
+ delete accepted2.client_max_window_bits;
338
338
  }
339
- return accepted;
339
+ return accepted2;
340
340
  }
341
341
  /**
342
342
  * Accept the extension negotiation response.
@@ -1858,14 +1858,14 @@ var require_sender = __commonJS({
1858
1858
  * @param {Function} [cb] Callback
1859
1859
  * @private
1860
1860
  */
1861
- sendFrame(list2, cb) {
1862
- if (list2.length === 2) {
1861
+ sendFrame(list3, cb) {
1862
+ if (list3.length === 2) {
1863
1863
  this._socket.cork();
1864
- this._socket.write(list2[0]);
1865
- this._socket.write(list2[1], cb);
1864
+ this._socket.write(list3[0]);
1865
+ this._socket.write(list3[1], cb);
1866
1866
  this._socket.uncork();
1867
1867
  } else {
1868
- this._socket.write(list2[0], cb);
1868
+ this._socket.write(list3[0], cb);
1869
1869
  }
1870
1870
  }
1871
1871
  };
@@ -3057,16 +3057,16 @@ var require_websocket = __commonJS({
3057
3057
  process.nextTick(cb, err);
3058
3058
  }
3059
3059
  }
3060
- function receiverOnConclude(code, reason) {
3060
+ function receiverOnConclude(code, reason2) {
3061
3061
  const websocket = this[kWebSocket];
3062
3062
  websocket._closeFrameReceived = true;
3063
- websocket._closeMessage = reason;
3063
+ websocket._closeMessage = reason2;
3064
3064
  websocket._closeCode = code;
3065
3065
  if (websocket._socket[kWebSocket] === void 0) return;
3066
3066
  websocket._socket.removeListener("data", socketOnData);
3067
3067
  process.nextTick(resume, websocket._socket);
3068
3068
  if (code === 1005) websocket.close();
3069
- else websocket.close(code, reason);
3069
+ else websocket.close(code, reason2);
3070
3070
  }
3071
3071
  function receiverOnDrain() {
3072
3072
  const websocket = this[kWebSocket];
@@ -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 = 18;
3725
+ var SOCKET_PROTOCOL_VERSION = 19;
3726
3726
  var EXTERNAL_RUN_ID = "external";
3727
3727
  var DAEMON_PORTS = [8765, 8766, 8767];
3728
3728
  var EXTENSION_REQUEST_FRAMES = [
@@ -18536,13 +18536,13 @@ function scanStyleSheets(maxRules) {
18536
18536
  };
18537
18537
  for (const sheet of sheets) {
18538
18538
  if (!sheet.href) counts.inline += 1;
18539
- const list2 = readable(sheet);
18540
- if (!list2) {
18539
+ const list3 = readable(sheet);
18540
+ if (!list3) {
18541
18541
  counts.crossOrigin += 1;
18542
18542
  continue;
18543
18543
  }
18544
18544
  counts.readable += 1;
18545
- for (const rule of list2) visit(rule);
18545
+ for (const rule of list3) visit(rule);
18546
18546
  }
18547
18547
  return {
18548
18548
  sheets: counts,
@@ -19369,7 +19369,7 @@ async function nativeDrag(source, plan) {
19369
19369
  firePointer(source, "pointerdown", "mousedown", grip, 1);
19370
19370
  await settle(holdMs);
19371
19371
  const started = fireDrag(source, "dragstart", grip, dataTransfer);
19372
- let accepted = false;
19372
+ let accepted2 = false;
19373
19373
  let entered = null;
19374
19374
  await travel(grip, drop, steps, 1, (at) => {
19375
19375
  const over = elementAt(at);
@@ -19378,13 +19378,13 @@ async function nativeDrag(source, plan) {
19378
19378
  fireDrag(over, "dragenter", at, dataTransfer);
19379
19379
  entered = over;
19380
19380
  }
19381
- if (over && !fireDrag(over, "dragover", at, dataTransfer)) accepted = true;
19381
+ if (over && !fireDrag(over, "dragover", at, dataTransfer)) accepted2 = true;
19382
19382
  });
19383
19383
  await settle(settleMs);
19384
19384
  const landing = elementAt(drop);
19385
19385
  fireDrag(landing, "drop", drop, dataTransfer);
19386
19386
  fireDrag(source, "dragend", drop, dataTransfer);
19387
- return { landedOn: landing ? cssPath(landing) : void 0, started, accepted };
19387
+ return { landedOn: landing ? cssPath(landing) : void 0, started, accepted: accepted2 };
19388
19388
  }
19389
19389
  var dragElement = defineAction({
19390
19390
  name: "page.dragElement",
@@ -21723,7 +21723,8 @@ var AGENTS = {
21723
21723
  bin: "claude",
21724
21724
  install: "npm i -g @anthropic-ai/claude-code",
21725
21725
  docs: "https://claude.com/claude-code",
21726
- models: ["claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5"]
21726
+ // Claude Code resolves each alias to the newest model of that family, so this list does not age.
21727
+ models: ["fable", "opus", "sonnet", "haiku"]
21727
21728
  },
21728
21729
  codex: {
21729
21730
  kind: "codex",
@@ -21741,7 +21742,7 @@ var AGENTS = {
21741
21742
  bin: "agy",
21742
21743
  install: "https://antigravity.google/docs/cli/install",
21743
21744
  docs: "https://antigravity.google/docs/cli",
21744
- models: ["gemini-3-pro", "gemini-3-flash"]
21745
+ models: ["gemini-3.1-pro-high", "gemini-3.8-flash-high", "gemini-3.8-flash-medium"]
21745
21746
  },
21746
21747
  vibe: {
21747
21748
  kind: "vibe",
@@ -21770,8 +21771,7 @@ var AGENTS = {
21770
21771
  bin: "cursor-agent",
21771
21772
  install: "curl https://cursor.com/install -fsS | bash",
21772
21773
  docs: "https://cursor.com/docs/cli/overview",
21773
- // `cursor-agent models` lists the account's own set, but it needs a login, so these are curated.
21774
- models: ["composer-2.5", "claude-opus-4-8", "gpt-5", "sonnet-4-thinking"],
21774
+ models: ["auto", "composer-2.5", "claude-opus-5-thinking-high", "gpt-5.3-codex"],
21775
21775
  beta: true
21776
21776
  },
21777
21777
  qwen: {
@@ -21803,6 +21803,9 @@ var AGENT_LIST = AGENT_KINDS.map((kind) => AGENTS[kind]);
21803
21803
  function isAgentKind(value) {
21804
21804
  return typeof value === "string" && AGENT_KINDS.includes(value);
21805
21805
  }
21806
+ function isModelId(value) {
21807
+ return typeof value === "string" && /^[A-Za-z0-9][^\s\p{C}]{0,199}$/u.test(value);
21808
+ }
21806
21809
  function activeRunner(state) {
21807
21810
  return state?.runners.find((runner) => runner.kind === state.active);
21808
21811
  }
@@ -21814,7 +21817,7 @@ import { homedir as homedir3 } from "os";
21814
21817
  import { basename, isAbsolute as isAbsolute2, join as join5, resolve, sep } from "path";
21815
21818
 
21816
21819
  // agent/config.ts
21817
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
21820
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
21818
21821
  import { join as join3 } from "path";
21819
21822
 
21820
21823
  // ../lib/settings/guardrails.ts
@@ -21857,6 +21860,31 @@ function clearLockfile() {
21857
21860
  rmSync(lockfilePath, { force: true });
21858
21861
  }
21859
21862
 
21863
+ // log.ts
21864
+ import { appendFileSync, mkdirSync as mkdirSync2, renameSync, statSync } from "fs";
21865
+ var MAX_BYTES = 2 * 1024 * 1024;
21866
+ function log(message, detail) {
21867
+ const line2 = `${(/* @__PURE__ */ new Date()).toISOString()} ${message}${detail === void 0 ? "" : ` ${format(detail)}`}
21868
+ `;
21869
+ try {
21870
+ mkdirSync2(stateDir, { recursive: true, mode: 448 });
21871
+ rotateIfLarge();
21872
+ appendFileSync(logPath, line2);
21873
+ } catch {
21874
+ }
21875
+ if (process.env.BROWSENTIC_DEBUG) process.stderr.write(line2);
21876
+ }
21877
+ function rotateIfLarge() {
21878
+ try {
21879
+ if (statSync(logPath).size > MAX_BYTES) renameSync(logPath, `${logPath}.1`);
21880
+ } catch {
21881
+ }
21882
+ }
21883
+ function format(detail) {
21884
+ if (detail instanceof Error) return `${detail.name}: ${detail.message}`;
21885
+ return typeof detail === "string" ? detail : JSON.stringify(detail);
21886
+ }
21887
+
21860
21888
  // agent/config.ts
21861
21889
  var CONCURRENT_RUNS = { fallback: 3, max: 8 };
21862
21890
  function maxConcurrentRuns(config5) {
@@ -21883,7 +21911,7 @@ function clamp2(value, { fallback, max }) {
21883
21911
  }
21884
21912
  var configPath = join3(stateDir, "config.json");
21885
21913
  var DEFAULT_MODEL = {
21886
- claude: "claude-sonnet-5"
21914
+ claude: "sonnet"
21887
21915
  };
21888
21916
  var DEFAULT_APPROVALS = ["page.submitForm"];
21889
21917
  function readStored() {
@@ -21910,11 +21938,17 @@ function settingsFor(stored, kind) {
21910
21938
  const legacy = kind === "claude" ? { bin: stored.claudeBin, model: stored.model, effort: stored.effort } : {};
21911
21939
  return {
21912
21940
  bin: text(scoped.bin) ?? text(legacy.bin) ?? AGENTS[kind].bin,
21913
- model: text(scoped.model) ?? text(legacy.model) ?? DEFAULT_MODEL[kind],
21941
+ model: modelOf(kind, scoped.model) ?? modelOf(kind, legacy.model) ?? DEFAULT_MODEL[kind],
21914
21942
  effort: text(scoped.effort) ?? text(legacy.effort)
21915
21943
  };
21916
21944
  }
21917
21945
  var text = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
21946
+ function modelOf(kind, value) {
21947
+ const model = text(value);
21948
+ if (model === void 0 || isModelId(model)) return model;
21949
+ log(`ignoring ${kind} model ${JSON.stringify(model)} \u2014 a model id starts with a letter or digit and has no spaces`);
21950
+ return void 0;
21951
+ }
21918
21952
  function activeAgent(config5) {
21919
21953
  return { kind: config5.agent, ...config5.agents[config5.agent] };
21920
21954
  }
@@ -21923,10 +21957,11 @@ function writeActiveAgent(kind) {
21923
21957
  write({ ...stored, agent: kind });
21924
21958
  }
21925
21959
  function writeAgentModel(kind, model) {
21960
+ const value = text(model);
21961
+ if (value !== void 0 && !isModelId(value)) return false;
21926
21962
  const stored = readStored();
21927
21963
  const agents = { ...stored.agents ?? {} };
21928
21964
  const scoped = { ...agents[kind] ?? {} };
21929
- const value = text(model);
21930
21965
  if (value) scoped.model = value;
21931
21966
  else delete scoped.model;
21932
21967
  if (Object.keys(scoped).length) agents[kind] = scoped;
@@ -21936,6 +21971,7 @@ function writeAgentModel(kind, model) {
21936
21971
  else delete next.agents;
21937
21972
  if (kind === "claude") delete next.model;
21938
21973
  write(next);
21974
+ return true;
21939
21975
  }
21940
21976
  function writeGuardrailSetting(setting, value) {
21941
21977
  const stored = readStored();
@@ -21959,7 +21995,7 @@ function writeGuardrailSetting(setting, value) {
21959
21995
  write(next);
21960
21996
  }
21961
21997
  function write(config5) {
21962
- mkdirSync2(stateDir, { recursive: true, mode: 448 });
21998
+ mkdirSync3(stateDir, { recursive: true, mode: 448 });
21963
21999
  writeFileSync2(configPath, `${JSON.stringify(config5, null, 2)}
21964
22000
  `, { mode: 384 });
21965
22001
  }
@@ -22110,31 +22146,6 @@ function fail(message) {
22110
22146
  return { ok: false, message };
22111
22147
  }
22112
22148
 
22113
- // log.ts
22114
- import { appendFileSync, mkdirSync as mkdirSync3, renameSync, statSync } from "fs";
22115
- var MAX_BYTES = 2 * 1024 * 1024;
22116
- function log(message, detail) {
22117
- const line2 = `${(/* @__PURE__ */ new Date()).toISOString()} ${message}${detail === void 0 ? "" : ` ${format(detail)}`}
22118
- `;
22119
- try {
22120
- mkdirSync3(stateDir, { recursive: true, mode: 448 });
22121
- rotateIfLarge();
22122
- appendFileSync(logPath, line2);
22123
- } catch {
22124
- }
22125
- if (process.env.BROWSENTIC_DEBUG) process.stderr.write(line2);
22126
- }
22127
- function rotateIfLarge() {
22128
- try {
22129
- if (statSync(logPath).size > MAX_BYTES) renameSync(logPath, `${logPath}.1`);
22130
- } catch {
22131
- }
22132
- }
22133
- function format(detail) {
22134
- if (detail instanceof Error) return `${detail.name}: ${detail.message}`;
22135
- return typeof detail === "string" ? detail : JSON.stringify(detail);
22136
- }
22137
-
22138
22149
  // agent/skills.ts
22139
22150
  var bundledDir = join4(dirname2(fileURLToPath2(import.meta.url)), "..", "skills");
22140
22151
  var userDir2 = join4(stateDir, "skills");
@@ -23111,16 +23122,16 @@ function decide(request, policy = POLICY) {
23111
23122
  "allow"
23112
23123
  );
23113
23124
  const decisive = matched.filter((rule) => rule.effect === worst);
23114
- const reason = decisive.map((rule) => rule.reason).join(" ");
23125
+ const reason2 = decisive.map((rule) => rule.reason).join(" ");
23115
23126
  if (worst === "confirm" && request.caller === "external") {
23116
23127
  if (policy.unattended === "allow") return { effect: "allow", matched: decisive, reason: "" };
23117
23128
  return {
23118
23129
  effect: "deny",
23119
23130
  matched: decisive,
23120
- reason: `${reason} Actions that need approval are only available from the Browsentic side panel, where the user can see and answer them.`
23131
+ reason: `${reason2} Actions that need approval are only available from the Browsentic side panel, where the user can see and answer them.`
23121
23132
  };
23122
23133
  }
23123
- return { effect: worst, matched: decisive, reason };
23134
+ return { effect: worst, matched: decisive, reason: reason2 };
23124
23135
  }
23125
23136
  function describe4(decision) {
23126
23137
  return decision.matched.map((rule) => rule.id).join(", ");
@@ -23457,6 +23468,9 @@ function vetPlan(kind, mode, plan, home) {
23457
23468
  problems.push(`${label2} is spawned with ${arg}, which disables its own containment.`);
23458
23469
  }
23459
23470
  }
23471
+ for (const model of everyValueOf(plan.args, "--model")) {
23472
+ if (!isModelId(model)) problems.push(`${label2} is spawned with the model ${JSON.stringify(model)}, which it could read as a flag.`);
23473
+ }
23460
23474
  if (!within(plan.cwd, home)) problems.push(`${label2} would run in ${plan.cwd}, which is outside ${home}.`);
23461
23475
  return problems;
23462
23476
  }
@@ -23630,7 +23644,7 @@ function adoptDownload(item, hosts) {
23630
23644
  return failure("DOWNLOAD_SAVE_FAILED", error51 instanceof Error ? error51.message : String(error51));
23631
23645
  }
23632
23646
  chmodSync3(savedTo, 384);
23633
- const record2 = {
23647
+ const record3 = {
23634
23648
  id,
23635
23649
  name: item.name,
23636
23650
  mime: item.mime,
@@ -23641,9 +23655,9 @@ function adoptDownload(item, hosts) {
23641
23655
  savedTo,
23642
23656
  capturedAt: (/* @__PURE__ */ new Date()).toISOString()
23643
23657
  };
23644
- writeIndex([record2, ...readIndex()]);
23658
+ writeIndex([record3, ...readIndex()]);
23645
23659
  log(`captured ${item.name} (${describeSize(size)}) from ${item.host ?? "unknown host"} \u2192 ${savedTo}`);
23646
- return success(record2);
23660
+ return success(record3);
23647
23661
  }
23648
23662
  function relocate(from, to) {
23649
23663
  try {
@@ -23664,7 +23678,7 @@ function listDownloads2(input2) {
23664
23678
  sweepDownloads();
23665
23679
  const filter = input2?.nameContains;
23666
23680
  const needle = typeof filter === "string" ? filter.toLowerCase() : null;
23667
- const downloads = readIndex().filter((record2) => existsSync3(record2.savedTo)).filter((record2) => !needle || record2.name.toLowerCase().includes(needle)).map(({ id, name, mime, size, host, notes, savedTo, capturedAt }) => ({
23681
+ const downloads = readIndex().filter((record3) => existsSync3(record3.savedTo)).filter((record3) => !needle || record3.name.toLowerCase().includes(needle)).map(({ id, name, mime, size, host, notes, savedTo, capturedAt }) => ({
23668
23682
  id,
23669
23683
  name,
23670
23684
  mime,
@@ -23677,26 +23691,26 @@ function listDownloads2(input2) {
23677
23691
  return success({ downloads });
23678
23692
  }
23679
23693
  function readDownloadBytes(id) {
23680
- const record2 = readIndex().find((entry) => entry.id === id);
23681
- if (!record2) {
23694
+ const record3 = readIndex().find((entry) => entry.id === id);
23695
+ if (!record3) {
23682
23696
  return failure(
23683
23697
  "DOWNLOAD_NOT_FOUND",
23684
23698
  `No captured download with id "${id}". Call page.listDownloads to see what has been captured.`
23685
23699
  );
23686
23700
  }
23687
- if (!existsSync3(record2.savedTo)) {
23688
- return failure("DOWNLOAD_MISSING", `\u201C${record2.name}\u201D is no longer in the download folder \u2014 capture it again.`);
23701
+ if (!existsSync3(record3.savedTo)) {
23702
+ return failure("DOWNLOAD_MISSING", `\u201C${record3.name}\u201D is no longer in the download folder \u2014 capture it again.`);
23689
23703
  }
23690
- if (record2.size > MAX_ATTACH_BYTES) {
23704
+ if (record3.size > MAX_ATTACH_BYTES) {
23691
23705
  return failure(
23692
23706
  "DOWNLOAD_TOO_LARGE",
23693
- `\u201C${record2.name}\u201D is ${describeSize(record2.size)}; files over ${describeSize(MAX_ATTACH_BYTES)} cannot be attached to a page.`
23707
+ `\u201C${record3.name}\u201D is ${describeSize(record3.size)}; files over ${describeSize(MAX_ATTACH_BYTES)} cannot be attached to a page.`
23694
23708
  );
23695
23709
  }
23696
23710
  return success({
23697
- name: record2.name,
23698
- mime: record2.mime || "application/octet-stream",
23699
- content: readFileSync4(record2.savedTo).toString("base64")
23711
+ name: record3.name,
23712
+ mime: record3.mime || "application/octet-stream",
23713
+ content: readFileSync4(record3.savedTo).toString("base64")
23700
23714
  });
23701
23715
  }
23702
23716
  function resolveAttachment(action, input2) {
@@ -23712,10 +23726,10 @@ function resolveAttachment(action, input2) {
23712
23726
  function sweepDownloads() {
23713
23727
  const cutoff = Date.now() - ttlDays() * 24 * 60 * 60 * 1e3;
23714
23728
  const records = readIndex();
23715
- const keep = records.filter((record2) => {
23716
- if (!existsSync3(record2.savedTo)) return false;
23717
- if (Date.parse(record2.capturedAt) >= cutoff) return true;
23718
- discard(record2.savedTo);
23729
+ const keep = records.filter((record3) => {
23730
+ if (!existsSync3(record3.savedTo)) return false;
23731
+ if (Date.parse(record3.capturedAt) >= cutoff) return true;
23732
+ discard(record3.savedTo);
23719
23733
  return false;
23720
23734
  });
23721
23735
  if (keep.length !== records.length) writeIndex(keep);
@@ -24077,12 +24091,12 @@ function isMappableHost(host) {
24077
24091
 
24078
24092
  // agent/agent-skills.ts
24079
24093
  import { createHash } from "crypto";
24080
- import { readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
24081
- import { join as join19, sep as sep2 } from "path";
24094
+ import { readFileSync as readFileSync11, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
24095
+ import { join as join21, sep as sep2 } from "path";
24082
24096
 
24083
24097
  // agent/runners/index.ts
24084
- import { spawn as spawn2 } from "child_process";
24085
- import { dirname as dirname5, join as join18 } from "path";
24098
+ import { spawn as spawn4 } from "child_process";
24099
+ import { dirname as dirname6, join as join20 } from "path";
24086
24100
  import { fileURLToPath as fileURLToPath3 } from "url";
24087
24101
 
24088
24102
  // agent/runners/antigravity.ts
@@ -24120,11 +24134,11 @@ function sweepRunDirs(base, ttlMs = RUN_DIR_TTL_MS) {
24120
24134
  function conversationDir(base, key) {
24121
24135
  return join9(base, key.replace(/[^\w-]/g, "_"));
24122
24136
  }
24123
- function effortOf(settings, accepted) {
24137
+ function effortOf(settings, accepted2) {
24124
24138
  const effort = settings.effort;
24125
24139
  if (!effort) return void 0;
24126
- if (accepted.includes(effort)) return effort;
24127
- log(`ignoring effort "${effort}" \u2014 accepted values are ${accepted.join(", ")}`);
24140
+ if (accepted2.includes(effort)) return effort;
24141
+ log(`ignoring effort "${effort}" \u2014 accepted values are ${accepted2.join(", ")}`);
24128
24142
  return void 0;
24129
24143
  }
24130
24144
  function parseJsonLine(line2) {
@@ -24321,6 +24335,7 @@ var antigravityRunner = {
24321
24335
  versionArgs: ["--version"],
24322
24336
  efforts: ["low", "medium", "high"],
24323
24337
  workspace: (mode) => join11(stateDir, "agents", "antigravity", mode),
24338
+ models: { args: ["models"], parse: listedModels },
24324
24339
  skillDirs: () => {
24325
24340
  try {
24326
24341
  return readFileSync6(skillsIndexPath, "utf8").split("\n").map((line2) => line2.trim()).filter(Boolean).map((root) => join11(root, "skills"));
@@ -24500,6 +24515,13 @@ function lastFrame(stdout) {
24500
24515
  return null;
24501
24516
  }
24502
24517
  var ownTool = (name) => /browsentic|^mcp/i.test(name);
24518
+ function listedModels({ stdout, code }) {
24519
+ if (code !== 0) return null;
24520
+ return stdout.split("\n").flatMap((line2) => {
24521
+ const [id, label2] = line2.split(" ");
24522
+ return label2 !== void 0 && id.trim() ? [id.trim()] : [];
24523
+ });
24524
+ }
24503
24525
 
24504
24526
  // agent/runners/codex.ts
24505
24527
  import { randomUUID as randomUUID4 } from "crypto";
@@ -24532,6 +24554,11 @@ var codexRunner = {
24532
24554
  efforts: ["low", "medium", "high", "xhigh"],
24533
24555
  workspace: () => stateDir,
24534
24556
  skillDirs: () => [join12(homedir7(), ".codex", "skills"), join12(homedir7(), ".codex", "prompts")],
24557
+ // Codex keeps the account's models in a cache it refreshes itself, so reading them spawns nothing.
24558
+ models: {
24559
+ file: () => join12(process.env.CODEX_HOME || join12(homedir7(), ".codex"), "models_cache.json"),
24560
+ parse: listedModels2
24561
+ },
24535
24562
  stream(context) {
24536
24563
  const { settings, mcp, research } = context;
24537
24564
  const server2 = `mcp_servers.${MCP_SERVER_NAME}`;
@@ -24711,6 +24738,12 @@ function kindOf(item) {
24711
24738
  var tomlString = (value) => JSON.stringify(value);
24712
24739
  var tomlArray = (values) => `[${values.map(tomlString).join(",")}]`;
24713
24740
  var tomlTable = (values) => `{${Object.entries(values).map(([key, value]) => `${key}=${tomlString(value)}`).join(",")}}`;
24741
+ function listedModels2(content) {
24742
+ const models = parseJsonLine(content)?.models;
24743
+ if (!Array.isArray(models)) return null;
24744
+ return models.filter((model) => model.visibility === "list" && typeof model.slug === "string").sort((a, b) => rank2(a) - rank2(b)).map((model) => model.slug);
24745
+ }
24746
+ var rank2 = (model) => typeof model.priority === "number" ? model.priority : Number.MAX_SAFE_INTEGER;
24714
24747
 
24715
24748
  // agent/runners/cursor.ts
24716
24749
  import { spawn } from "child_process";
@@ -24732,6 +24765,7 @@ var cursorRunner = {
24732
24765
  efforts: [],
24733
24766
  workspace: (mode) => join13(stateDir, "agents", "cursor", mode),
24734
24767
  skillDirs: () => [join13(cursorHome(), "skills"), join13(homedir8(), ".agents", "skills")],
24768
+ models: { args: ["models"], parse: listedModels3 },
24735
24769
  stream(context) {
24736
24770
  const { settings, research } = context;
24737
24771
  const base = this.workspace("run");
@@ -24929,6 +24963,13 @@ function lastResult(stdout) {
24929
24963
  }
24930
24964
  return null;
24931
24965
  }
24966
+ function listedModels3({ stdout, code }) {
24967
+ if (code !== 0) return null;
24968
+ const lines = plain(stdout).split("\n");
24969
+ const heading = lines.findIndex((line2) => /^available models$/i.test(line2.trim()));
24970
+ if (heading === -1) return null;
24971
+ return lines.slice(heading + 1).flatMap((line2) => /^(\S+) - \S/.exec(line2.trim())?.slice(1, 2) ?? []);
24972
+ }
24932
24973
 
24933
24974
  // agent/runners/grok.ts
24934
24975
  import { randomUUID as randomUUID5 } from "crypto";
@@ -24952,6 +24993,7 @@ var grokRunner = {
24952
24993
  versionArgs: ["--version"],
24953
24994
  efforts: ["low", "medium", "high", "xhigh"],
24954
24995
  workspace: (mode) => join14(stateDir, "agents", "grok", mode),
24996
+ models: { args: ["models"], parse: listedModels4 },
24955
24997
  skillDirs: () => [join14(grokHome(), "skills"), join14(homedir9(), ".agents", "skills"), join14(homedir9(), ".claude", "skills")],
24956
24998
  stream(context) {
24957
24999
  const { settings, research } = context;
@@ -25140,12 +25182,367 @@ function lastLine(stdout) {
25140
25182
  }
25141
25183
  return null;
25142
25184
  }
25185
+ function listedModels4({ stdout, stderr, code }) {
25186
+ if (code !== 0 || /not authenticated/i.test(`${stdout}
25187
+ ${stderr}`)) return null;
25188
+ const lines = stdout.split("\n");
25189
+ const heading = lines.findIndex((line2) => /^available models:?$/i.test(line2.trim()));
25190
+ if (heading === -1) return null;
25191
+ return lines.slice(heading + 1).flatMap((line2) => /^\s+[*-]\s+(\S+)/.exec(line2)?.slice(1, 2) ?? []);
25192
+ }
25193
+
25194
+ // agent/runners/models.ts
25195
+ import { spawn as spawn3 } from "child_process";
25196
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync8, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
25197
+ import { basename as basename4, join as join16 } from "path";
25198
+
25199
+ // agent/runners/drive.ts
25200
+ import { spawn as spawn2 } from "child_process";
25201
+ import { mkdirSync as mkdirSync8, utimesSync, writeFileSync as writeFileSync7 } from "fs";
25202
+ import { dirname as dirname4, join as join15 } from "path";
25203
+ import { createInterface } from "readline";
25204
+
25205
+ // agent/runners/types.ts
25206
+ var RunError = class extends Error {
25207
+ constructor(code, message) {
25208
+ super(message);
25209
+ this.code = code;
25210
+ this.name = "RunError";
25211
+ }
25212
+ code;
25213
+ };
25214
+
25215
+ // agent/runners/drive.ts
25216
+ var KILL_GRACE_MS = 5e3;
25217
+ var STRIPPED = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", "BROWSENTIC_AGENT_RUN"];
25218
+ function launch(kind, mode, settings, plan, signal) {
25219
+ const problems = vetPlan(kind, mode, plan, stateDir);
25220
+ if (problems.length) {
25221
+ throw new RunError(
25222
+ "AGENT_UNSAFE",
25223
+ `Browsentic refused to start ${AGENTS[kind].label}: ${problems.join(" ")} This is a bug in Browsentic, not something you did \u2014 please report it.`
25224
+ );
25225
+ }
25226
+ log(describeContainment(kind));
25227
+ mkdirSync8(plan.cwd, { recursive: true, mode: 448 });
25228
+ for (const file2 of plan.files ?? []) {
25229
+ const path = join15(plan.cwd, file2.path);
25230
+ mkdirSync8(dirname4(path), { recursive: true, mode: 448 });
25231
+ writeFileSync7(path, file2.content, { mode: 384 });
25232
+ }
25233
+ const now = /* @__PURE__ */ new Date();
25234
+ utimesSync(plan.cwd, now, now);
25235
+ const dropped = sealedAway(kind, process.env);
25236
+ if (dropped.length) log(`sealed ${dropped.length} credential-shaped variables out of the ${kind} environment`);
25237
+ const env = { ...childEnv(kind), ...plan.env };
25238
+ const child = spawn2(settings.bin, plan.args, {
25239
+ cwd: plan.cwd,
25240
+ env,
25241
+ stdio: ["ignore", "pipe", "pipe"]
25242
+ });
25243
+ const kill = () => {
25244
+ child.kill("SIGTERM");
25245
+ const hardKill = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
25246
+ hardKill.unref();
25247
+ };
25248
+ if (signal.aborted) kill();
25249
+ else signal.addEventListener("abort", kill, { once: true });
25250
+ return { child, release: () => signal.removeEventListener("abort", kill), stop: kill };
25251
+ }
25252
+ function childEnv(kind) {
25253
+ const env = sealEnv(kind, process.env);
25254
+ for (const key of STRIPPED) delete env[key];
25255
+ return env;
25256
+ }
25257
+ function notInstalled(runner, settings) {
25258
+ const agent = AGENTS[runner.kind];
25259
+ return new RunError(
25260
+ "AGENT_MISSING",
25261
+ `Could not run "${settings.bin}" \u2014 ${agent.label} is not installed, or not on the daemon's PATH. Install it (${agent.install}), or set {"agents":{"${runner.kind}":{"bin":"/absolute/path/to/${agent.bin}"}}} in ${configPath}.`
25262
+ );
25263
+ }
25264
+ function spawnError(runner, settings, error51) {
25265
+ return error51.code === "ENOENT" ? notInstalled(runner, settings) : new RunError("AGENT_FAILED", error51.message);
25266
+ }
25267
+ function runStream(runner, context, signal, emit) {
25268
+ const plan = runner.stream(context);
25269
+ const label2 = AGENTS[runner.kind].label;
25270
+ return new Promise((resolve4, reject) => {
25271
+ const { child, release, stop } = launch(runner.kind, "run", context.settings, plan, signal);
25272
+ let sessionId2 = null;
25273
+ let settled = false;
25274
+ let stderrTail = "";
25275
+ const settle2 = (outcome) => {
25276
+ if (settled) return;
25277
+ settled = true;
25278
+ outcome();
25279
+ };
25280
+ const outbound = sealingStream();
25281
+ const say = (delta) => delta && emit({ kind: "text", delta });
25282
+ const flush = () => say(outbound.flush());
25283
+ const sink = {
25284
+ text: (delta) => delta && say(outbound.push(delta)),
25285
+ tool: (toolId, name) => emit({ kind: "tool", toolId, action: name, input: {} }),
25286
+ session: (id) => {
25287
+ if (id) sessionId2 = id;
25288
+ },
25289
+ usage: (usage) => emit({ kind: "usage", usage }),
25290
+ done: (stopReason) => settle2(() => {
25291
+ flush();
25292
+ resolve4({ stopReason, sessionId: sessionId2 });
25293
+ }),
25294
+ // A run the reader has failed is over, and its process would otherwise go on spending and acting.
25295
+ fail: (code, message) => settle2(() => {
25296
+ flush();
25297
+ stop();
25298
+ reject(new RunError(code, message));
25299
+ })
25300
+ };
25301
+ child.stderr.on("data", (chunk) => {
25302
+ stderrTail = (stderrTail + chunk.toString()).slice(-2e3);
25303
+ });
25304
+ const read4 = runner.reader();
25305
+ const lines = createInterface({ input: child.stdout });
25306
+ lines.on("line", (line2) => {
25307
+ if (!line2.trim()) return;
25308
+ try {
25309
+ read4(line2, sink);
25310
+ } catch (error51) {
25311
+ log(`${runner.kind} runner could not read a stream line: ${String(error51)}`);
25312
+ }
25313
+ });
25314
+ child.on(
25315
+ "error",
25316
+ (error51) => settle2(() => reject(spawnError(runner, context.settings, error51)))
25317
+ );
25318
+ child.on("close", (exitCode) => {
25319
+ release();
25320
+ if (settled) return;
25321
+ if (signal.aborted) return settle2(() => reject(new RunError("CANCELLED", "Run cancelled.")));
25322
+ if (runner.endsOnExit && exitCode === 0) return sink.done("end_turn");
25323
+ const hint = runner.hint?.(stderrTail);
25324
+ settle2(
25325
+ () => reject(
25326
+ new RunError(
25327
+ "AGENT_FAILED",
25328
+ hint ?? `${label2} exited with code ${exitCode} before finishing${tail(stderrTail)}`
25329
+ )
25330
+ )
25331
+ );
25332
+ });
25333
+ });
25334
+ }
25335
+ function runJson(runner, context, signal, { timedOut, empty, accept }) {
25336
+ const plan = runner.json(context);
25337
+ const label2 = AGENTS[runner.kind].label;
25338
+ return new Promise((resolve4, reject) => {
25339
+ const { child, release, stop } = launch(runner.kind, "task", context.settings, plan, signal);
25340
+ let stdout = "";
25341
+ let stderrTail = "";
25342
+ let settled = false;
25343
+ const settle2 = (outcome) => {
25344
+ if (settled) return;
25345
+ settled = true;
25346
+ release();
25347
+ outcome();
25348
+ };
25349
+ child.stdout.on("data", (chunk) => {
25350
+ stdout += chunk.toString();
25351
+ if (!accept || settled) return;
25352
+ const text2 = runner.answer(stdout).text?.trim();
25353
+ if (text2 && accept(text2)) {
25354
+ settle2(() => {
25355
+ stop();
25356
+ resolve4(text2);
25357
+ });
25358
+ }
25359
+ });
25360
+ child.stderr.on("data", (chunk) => {
25361
+ stderrTail = (stderrTail + chunk.toString()).slice(-2e3);
25362
+ });
25363
+ child.on("error", (error51) => settle2(() => reject(spawnError(runner, context.settings, error51))));
25364
+ child.on(
25365
+ "close",
25366
+ () => settle2(() => {
25367
+ if (signal.aborted) return reject(signal.reason instanceof RunError ? signal.reason : new RunError("TIMEOUT", timedOut));
25368
+ const answer = runner.answer(stdout);
25369
+ if (answer.error) return reject(new RunError("AGENT_FAILED", answer.error));
25370
+ const text2 = answer.text?.trim();
25371
+ if (!text2) {
25372
+ const hint = runner.hint?.(stderrTail);
25373
+ return reject(new RunError("AGENT_FAILED", hint ?? (stderrTail.trim() ? `${label2}: ${stderrTail.trim()}` : empty)));
25374
+ }
25375
+ resolve4(text2);
25376
+ })
25377
+ );
25378
+ });
25379
+ }
25380
+ var tail = (stderr) => stderr.trim() ? `: ${stderr.trim()}` : "";
25381
+
25382
+ // agent/runners/models.ts
25383
+ var FRESH_MS = 6 * 60 * 6e4;
25384
+ var RETRY_MS = 10 * 6e4;
25385
+ var LIST_TIMEOUT_MS = 1e4;
25386
+ var MAX_OUTPUT_BYTES = 512 * 1024;
25387
+ var MAX_MODELS = 500;
25388
+ var storePath = join16(stateDir, "models.json");
25389
+ var inFlight = /* @__PURE__ */ new Map();
25390
+ function modelsFor(kind) {
25391
+ const entry = readStore()[kind];
25392
+ const curated = AGENTS[kind].models;
25393
+ if (entry?.ids?.length) return { ids: suggestedFirst(entry.ids, curated), from: "cli", at: entry.at, error: entry.error };
25394
+ return { ids: curated, from: "catalog", error: entry?.error };
25395
+ }
25396
+ function refreshModels(runner, settings, version3, { force = false } = {}) {
25397
+ const lister = runner.models;
25398
+ if (!lister) return Promise.resolve(false);
25399
+ const running = inFlight.get(runner.kind);
25400
+ if (running) return running;
25401
+ if (!force && !due(readStore()[runner.kind], settings.bin, version3, lister)) return Promise.resolve(false);
25402
+ const reading = read2(runner.kind, settings.bin, lister).catch((error51) => ({ error: String(error51) })).then((outcome) => record2(runner.kind, settings.bin, version3, outcome)).catch((error51) => {
25403
+ log(`could not keep ${AGENTS[runner.kind].label}'s model list: ${String(error51)}`);
25404
+ return false;
25405
+ }).finally(() => inFlight.delete(runner.kind));
25406
+ inFlight.set(runner.kind, reading);
25407
+ return reading;
25408
+ }
25409
+ function due(entry, bin, version3, lister) {
25410
+ if (!entry || entry.bin !== bin || entry.version !== version3) return true;
25411
+ const age = Date.now() - entry.triedAt;
25412
+ if (entry.error) return age >= RETRY_MS;
25413
+ return "file" in lister || age >= FRESH_MS;
25414
+ }
25415
+ async function read2(kind, bin, lister) {
25416
+ if ("file" in lister) {
25417
+ const path = lister.file();
25418
+ let content;
25419
+ try {
25420
+ content = readFileSync8(path, "utf8");
25421
+ } catch {
25422
+ return { error: `${AGENTS[kind].label} has not written ${basename4(path)} yet. It does the first time it runs.` };
25423
+ }
25424
+ return accepted(() => lister.parse(content)) ?? { error: `${basename4(path)} holds no model Browsentic could read.` };
25425
+ }
25426
+ const listing = await list2(kind, bin, lister.args);
25427
+ if ("error" in listing) return listing;
25428
+ return accepted(() => lister.parse(listing)) ?? { error: reason(listing) };
25429
+ }
25430
+ function accepted(parse5) {
25431
+ let parsed2;
25432
+ try {
25433
+ parsed2 = parse5();
25434
+ } catch {
25435
+ return null;
25436
+ }
25437
+ const ids = [...new Set((parsed2 ?? []).filter(isModelId))].slice(0, MAX_MODELS);
25438
+ return ids.length ? { ids } : null;
25439
+ }
25440
+ function reason({ stdout, stderr, code }) {
25441
+ const lines = plain2(`${stderr}
25442
+ ${stdout}`).split("\n").map((line2) => line2.trim()).filter(Boolean);
25443
+ const said = lines.find((line2) => /error|sign in|log ?in|authenticat/i.test(line2));
25444
+ if (said) return said.replace(/^error:\s*/i, "").slice(0, 200);
25445
+ return code === 0 ? "It printed no model list Browsentic could read." : `It exited with code ${code}.`;
25446
+ }
25447
+ function list2(kind, bin, args) {
25448
+ const cwd = join16(stateDir, "agents", kind, "models");
25449
+ mkdirSync9(cwd, { recursive: true, mode: 448 });
25450
+ const command = `"${[basename4(bin), ...args].join(" ")}"`;
25451
+ const group = process.platform !== "win32";
25452
+ return new Promise((resolve4) => {
25453
+ let stdout = "";
25454
+ let stderr = "";
25455
+ let size = 0;
25456
+ let settled = false;
25457
+ const done = (result) => {
25458
+ if (settled) return;
25459
+ settled = true;
25460
+ clearTimeout(timer);
25461
+ resolve4(result);
25462
+ };
25463
+ const child = spawn3(bin, args, {
25464
+ cwd,
25465
+ env: { ...childEnv(kind), NO_COLOR: "1" },
25466
+ stdio: ["ignore", "pipe", "pipe"],
25467
+ detached: group
25468
+ });
25469
+ const stop = (error51) => {
25470
+ try {
25471
+ if (group && child.pid) process.kill(-child.pid, "SIGKILL");
25472
+ else child.kill("SIGKILL");
25473
+ } catch {
25474
+ child.kill("SIGKILL");
25475
+ }
25476
+ done({ error: error51 });
25477
+ };
25478
+ const timer = setTimeout(() => stop(`${command} did not answer within ${LIST_TIMEOUT_MS / 1e3} s.`), LIST_TIMEOUT_MS);
25479
+ timer.unref();
25480
+ const collect3 = (append) => (chunk) => {
25481
+ size += chunk.length;
25482
+ if (size > MAX_OUTPUT_BYTES) return stop(`${command} printed more than ${MAX_OUTPUT_BYTES / 1024} KB.`);
25483
+ append(chunk.toString());
25484
+ };
25485
+ child.stdout.on("data", collect3((text2) => stdout += text2));
25486
+ child.stderr.on("data", collect3((text2) => stderr += text2));
25487
+ child.on("error", (error51) => done({ error: error51.message }));
25488
+ child.on("close", (code) => done({ stdout, stderr, code }));
25489
+ });
25490
+ }
25491
+ function record2(kind, bin, version3, outcome) {
25492
+ const store = readStore();
25493
+ const before = store[kind];
25494
+ const now = Date.now();
25495
+ const entry = "ids" in outcome ? { bin, version: version3, ids: outcome.ids, at: now, triedAt: now } : { bin, version: version3, ids: before?.ids, at: before?.at, triedAt: now, error: outcome.error };
25496
+ if ("error" in outcome) log(`could not list ${AGENTS[kind].label}'s models: ${outcome.error}`);
25497
+ store[kind] = entry;
25498
+ writeStore(store);
25499
+ return JSON.stringify([before?.ids, before?.error]) !== JSON.stringify([entry.ids, entry.error]);
25500
+ }
25501
+ function readStore() {
25502
+ let parsed2;
25503
+ try {
25504
+ parsed2 = JSON.parse(readFileSync8(storePath, "utf8"));
25505
+ } catch {
25506
+ return {};
25507
+ }
25508
+ if (!parsed2 || typeof parsed2 !== "object") return {};
25509
+ const store = {};
25510
+ for (const [kind, value] of Object.entries(parsed2)) {
25511
+ const entry = entryOf(value);
25512
+ if (entry && kind in AGENTS) store[kind] = entry;
25513
+ }
25514
+ return store;
25515
+ }
25516
+ function entryOf(value) {
25517
+ if (!value || typeof value !== "object") return null;
25518
+ const raw = value;
25519
+ if (typeof raw.bin !== "string" || typeof raw.triedAt !== "number") return null;
25520
+ const ids = Array.isArray(raw.ids) ? raw.ids.filter(isModelId) : void 0;
25521
+ return {
25522
+ bin: raw.bin,
25523
+ version: typeof raw.version === "string" ? raw.version : void 0,
25524
+ ids: ids?.length ? ids : void 0,
25525
+ at: typeof raw.at === "number" ? raw.at : void 0,
25526
+ triedAt: raw.triedAt,
25527
+ error: typeof raw.error === "string" ? raw.error.slice(0, 200) : void 0
25528
+ };
25529
+ }
25530
+ function writeStore(store) {
25531
+ const temporary = `${storePath}.${process.pid}.tmp`;
25532
+ writeFileSync8(temporary, `${JSON.stringify(store, null, 2)}
25533
+ `, { mode: 384 });
25534
+ renameSync3(temporary, storePath);
25535
+ }
25536
+ function suggestedFirst(ids, curated) {
25537
+ return [...curated.filter((id) => ids.includes(id)), ...ids.filter((id) => !curated.includes(id))];
25538
+ }
25539
+ var plain2 = (text2) => text2.replace(/\u001B\[[0-9;]*[A-Za-z]/g, "");
25143
25540
 
25144
25541
  // agent/runners/opencode.ts
25145
25542
  import { randomUUID as randomUUID6 } from "crypto";
25146
- import { readFileSync as readFileSync8 } from "fs";
25543
+ import { readFileSync as readFileSync9 } from "fs";
25147
25544
  import { homedir as homedir10 } from "os";
25148
- import { dirname as dirname4, join as join15, relative } from "path";
25545
+ import { dirname as dirname5, join as join17, relative } from "path";
25149
25546
  var AGENT = "browsentic-contained";
25150
25547
  var INSTRUCTIONS2 = "instructions.md";
25151
25548
  var WEB_TOOLS3 = ["webfetch", "websearch"];
@@ -25153,8 +25550,8 @@ var RESULT_BYTES = 1e5;
25153
25550
  var TOOL_TIMEOUT_MS = 30 * 6e4;
25154
25551
  var TRUNCATION = `A tool result over ${RESULT_BYTES / 1e3} KB comes back cut, and the saved copy OpenCode points to cannot be opened in this run, so ask for less at a time \u2014 \`page_getPageInfo\` with a small \`maxPerKind\`, \`page_extractText\` with the cursor it hands back.`;
25155
25552
  var SEALED2 = { OPENCODE_DISABLE_PROJECT_CONFIG: "1", OPENCODE_DISABLE_CLAUDE_CODE: "1", OPENCODE_DISABLE_SHARE: "1" };
25156
- var sessionsDb = () => join15(stateDir, "agents", "opencode", "sessions.db");
25157
- var configHome = () => join15(process.env.XDG_CONFIG_HOME || join15(homedir10(), ".config"), "opencode");
25553
+ var sessionsDb = () => join17(stateDir, "agents", "opencode", "sessions.db");
25554
+ var configHome = () => join17(process.env.XDG_CONFIG_HOME || join17(homedir10(), ".config"), "opencode");
25158
25555
  var INSTALL = "npm i -g opencode-ai";
25159
25556
  var opencodeRunner = {
25160
25557
  kind: "opencode",
@@ -25162,13 +25559,13 @@ var opencodeRunner = {
25162
25559
  // Passed as --variant, whose names each model defines; a model ignores one it lacks.
25163
25560
  efforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
25164
25561
  endsOnExit: true,
25165
- workspace: (mode) => join15(stateDir, "agents", "opencode", mode),
25562
+ workspace: (mode) => join17(stateDir, "agents", "opencode", mode),
25166
25563
  skillDirs: () => [
25167
- join15(configHome(), "skills"),
25168
- join15(configHome(), "skill"),
25169
- join15(homedir10(), ".opencode", "skills"),
25170
- join15(homedir10(), ".agents", "skills"),
25171
- join15(homedir10(), ".claude", "skills")
25564
+ join17(configHome(), "skills"),
25565
+ join17(configHome(), "skill"),
25566
+ join17(homedir10(), ".opencode", "skills"),
25567
+ join17(homedir10(), ".agents", "skills"),
25568
+ join17(homedir10(), ".claude", "skills")
25172
25569
  ],
25173
25570
  stream(context) {
25174
25571
  const { settings, research } = context;
@@ -25184,7 +25581,7 @@ var opencodeRunner = {
25184
25581
  OPENCODE_DB: sessionsDb(),
25185
25582
  OPENCODE_CONFIG_CONTENT: config3({
25186
25583
  mcp: { [MCP_SERVER_NAME]: server(context.mcp) },
25187
- instructions: [join15(cwd, INSTRUCTIONS2)],
25584
+ instructions: [join17(cwd, INSTRUCTIONS2)],
25188
25585
  permission: { "*": "deny", ...Object.fromEntries(allowed2.map((tool) => [tool, "allow"])) }
25189
25586
  }),
25190
25587
  BROWSENTIC_AGENT_RUN: context.runId
@@ -25304,11 +25701,11 @@ function config3({ mcp, instructions, permission }) {
25304
25701
  });
25305
25702
  }
25306
25703
  function scratchReads(workspace) {
25307
- const scratch = join15(workspace, "tmp");
25704
+ const scratch = join17(workspace, "tmp");
25308
25705
  const rules = { "*": "deny" };
25309
- for (let root = dirname4(workspace); ; root = dirname4(root)) {
25706
+ for (let root = dirname5(workspace); ; root = dirname5(root)) {
25310
25707
  rules[`${relative(root, scratch)}/*`] = "allow";
25311
- if (root === dirname4(root)) return rules;
25708
+ if (root === dirname5(root)) return rules;
25312
25709
  }
25313
25710
  }
25314
25711
  function explain3(failure2) {
@@ -25327,10 +25724,10 @@ function explain3(failure2) {
25327
25724
  return message;
25328
25725
  }
25329
25726
  var oneLine2 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
25330
- var dataHome = () => join15(process.env.XDG_DATA_HOME || join15(homedir10(), ".local", "share"), "opencode");
25727
+ var dataHome = () => join17(process.env.XDG_DATA_HOME || join17(homedir10(), ".local", "share"), "opencode");
25331
25728
  function signedIn2() {
25332
25729
  try {
25333
- return Object.keys(JSON.parse(readFileSync8(join15(dataHome(), "auth.json"), "utf8"))).length > 0;
25730
+ return Object.keys(JSON.parse(readFileSync9(join17(dataHome(), "auth.json"), "utf8"))).length > 0;
25334
25731
  } catch (error51) {
25335
25732
  return error51.code !== "ENOENT";
25336
25733
  }
@@ -25338,7 +25735,7 @@ function signedIn2() {
25338
25735
  function declaresProvider() {
25339
25736
  return ["opencode.json", "opencode.jsonc", "config.json"].some((name) => {
25340
25737
  try {
25341
- return /"provider"\s*:/.test(readFileSync8(join15(configHome(), name), "utf8"));
25738
+ return /"provider"\s*:/.test(readFileSync9(join17(configHome(), name), "utf8"));
25342
25739
  } catch {
25343
25740
  return false;
25344
25741
  }
@@ -25347,9 +25744,9 @@ function declaresProvider() {
25347
25744
 
25348
25745
  // agent/runners/qwen.ts
25349
25746
  import { randomUUID as randomUUID7 } from "crypto";
25350
- import { readFileSync as readFileSync9 } from "fs";
25747
+ import { readFileSync as readFileSync10 } from "fs";
25351
25748
  import { homedir as homedir11 } from "os";
25352
- import { join as join16 } from "path";
25749
+ import { join as join18 } from "path";
25353
25750
  var MACHINE = ["Bash", "exec", "Edit", "Read", "zoom_image", "monitor", "lsp", "save_memory"];
25354
25751
  var ESCAPES = [
25355
25752
  "skill",
@@ -25375,7 +25772,7 @@ var READ_TOOL2 = "read_file";
25375
25772
  var OTHER_READS = ["grep_search", "glob", "list_directory"];
25376
25773
  var APPROVAL = "default";
25377
25774
  var DANGEROUS = /^(run_shell_command|exec|edit|write_file|notebook_edit|read_file|grep_search|glob|list_directory|agent|skill|monitor|save_memory|lsp|zoom_image|image_gen|workflow|send_message|create_sub_session|propose_goal|cron_|team_|computer_use__|omni_)/;
25378
- var qwenHome = () => process.env.QWEN_HOME || join16(homedir11(), ".qwen");
25775
+ var qwenHome = () => process.env.QWEN_HOME || join18(homedir11(), ".qwen");
25379
25776
  var INSTALL2 = "npm i -g @qwen-code/qwen-code";
25380
25777
  var AUTH_KEYS = ["QWEN_API_KEY", "OPENAI_API_KEY", "DASHSCOPE_API_KEY"];
25381
25778
  var AUTH_PREFIX = /^(QWEN_|DASHSCOPE_|BAILIAN_|OPENAI_).*(API_KEY|TOKEN)$/;
@@ -25386,8 +25783,8 @@ var qwenRunner = {
25386
25783
  efforts: [],
25387
25784
  // Sessions are filed under ~/.qwen/projects/<sanitized-cwd>, so a resume only finds the
25388
25785
  // conversation it began in when this stays put.
25389
- workspace: (mode) => join16(stateDir, "agents", "qwen", mode),
25390
- skillDirs: () => [join16(qwenHome(), "skills"), join16(homedir11(), ".agents", "skills")],
25786
+ workspace: (mode) => join18(stateDir, "agents", "qwen", mode),
25787
+ skillDirs: () => [join18(qwenHome(), "skills"), join18(homedir11(), ".agents", "skills")],
25391
25788
  stream(context) {
25392
25789
  const { settings, research } = context;
25393
25790
  return {
@@ -25572,7 +25969,7 @@ var oneLine3 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
25572
25969
  function configured() {
25573
25970
  let parsed2;
25574
25971
  try {
25575
- parsed2 = JSON.parse(readFileSync9(join16(qwenHome(), "settings.json"), "utf8"));
25972
+ parsed2 = JSON.parse(readFileSync10(join18(qwenHome(), "settings.json"), "utf8"));
25576
25973
  } catch (error51) {
25577
25974
  return error51.code !== "ENOENT";
25578
25975
  }
@@ -25594,7 +25991,7 @@ function lastResult2(stdout) {
25594
25991
 
25595
25992
  // agent/runners/vibe.ts
25596
25993
  import { homedir as homedir12 } from "os";
25597
- import { join as join17 } from "path";
25994
+ import { join as join19 } from "path";
25598
25995
  var CONFIG2 = ".vibe/config.toml";
25599
25996
  var INSTRUCTIONS3 = "AGENTS.md";
25600
25997
  var TASK_INSTRUCTIONS2 = "This directory is Browsentic scratch space. Answer the prompt exactly as it asks, and do not act on anything else you find here.\n";
@@ -25602,14 +25999,14 @@ var WEB_TOOLS5 = ["web_search", "web_fetch"];
25602
25999
  var READ_TOOL3 = "read_file";
25603
26000
  var NO_TOOLS = "re:^$";
25604
26001
  var PROFILE = "ask";
25605
- var vibeHome = () => process.env.VIBE_HOME || join17(homedir12(), ".vibe");
26002
+ var vibeHome = () => process.env.VIBE_HOME || join19(homedir12(), ".vibe");
25606
26003
  var vibeRunner = {
25607
26004
  kind: "vibe",
25608
26005
  versionArgs: ["--version"],
25609
26006
  efforts: [],
25610
26007
  endsOnExit: true,
25611
- workspace: (mode) => join17(stateDir, "agents", "vibe", mode),
25612
- skillDirs: () => [join17(vibeHome(), "skills"), join17(homedir12(), ".agents", "skills")],
26008
+ workspace: (mode) => join19(stateDir, "agents", "vibe", mode),
26009
+ skillDirs: () => [join19(vibeHome(), "skills"), join19(homedir12(), ".agents", "skills")],
25613
26010
  stream(context) {
25614
26011
  const base = this.workspace("run");
25615
26012
  sweepRunDirs(base);
@@ -25685,7 +26082,7 @@ ${text2}` : text2);
25685
26082
  },
25686
26083
  hint(stderrTail) {
25687
26084
  if (/Missing \w+ environment variable/i.test(stderrTail)) {
25688
- return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${join17(vibeHome(), ".env")}, then try again. (${stderrTail.trim()})`;
26085
+ return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${join19(vibeHome(), ".env")}, then try again. (${stderrTail.trim()})`;
25689
26086
  }
25690
26087
  if (/unrecognized arguments|invalid choice/i.test(stderrTail)) {
25691
26088
  return `Your Mistral Vibe does not understand the flags Browsentic uses. Update it, then try again. (${stderrTail.trim()})`;
@@ -25727,7 +26124,7 @@ var RUNNERS = {
25727
26124
  qwen: qwenRunner,
25728
26125
  opencode: opencodeRunner
25729
26126
  };
25730
- var cliPath = join18(dirname5(fileURLToPath3(import.meta.url)), "cli.js");
26127
+ var cliPath = join20(dirname6(fileURLToPath3(import.meta.url)), "cli.js");
25731
26128
  function mcpServerFor(runId) {
25732
26129
  return { command: process.execPath, args: [cliPath, "mcp"], env: { BROWSENTIC_AGENT_RUN: runId } };
25733
26130
  }
@@ -25751,6 +26148,13 @@ async function agentState(config5, { refresh = false } = {}) {
25751
26148
  function forgetProbes() {
25752
26149
  cached2 = null;
25753
26150
  }
26151
+ async function refreshModelLists(config5, { force = false, only } = {}) {
26152
+ const state = await agentState(config5);
26153
+ const reads = state.runners.filter((status) => (status.ready || status.problem?.code === "AGENT_NEEDS_PERMISSION") && (!only || status.kind === only)).map((status) => refreshModels(RUNNERS[status.kind], config5.agents[status.kind], status.version, { force }));
26154
+ const changed = (await Promise.all(reads)).some(Boolean);
26155
+ if (changed || force) forgetProbes();
26156
+ return changed;
26157
+ }
25754
26158
  async function grantRunner(kind) {
25755
26159
  const problem = await RUNNERS[kind].grant?.() ?? null;
25756
26160
  forgetProbes();
@@ -25794,6 +26198,7 @@ async function probe2(runner, settings) {
25794
26198
  model: settings.model,
25795
26199
  ready: !problem,
25796
26200
  version: found.detail,
26201
+ models: modelsFor(runner.kind),
25797
26202
  problem: problem ?? void 0
25798
26203
  };
25799
26204
  }
@@ -25807,7 +26212,7 @@ function version2(bin, args) {
25807
26212
  clearTimeout(timer);
25808
26213
  resolve4(result);
25809
26214
  };
25810
- const child = spawn2(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
26215
+ const child = spawn4(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
25811
26216
  const timer = setTimeout(() => {
25812
26217
  child.kill("SIGKILL");
25813
26218
  done({ ok: false, detail: "timed out" });
@@ -25872,7 +26277,7 @@ function resolveAgentSkill(id, config5) {
25872
26277
  }
25873
26278
  };
25874
26279
  }
25875
- const body = splitFrontMatter(readFileSync10(entry.path, "utf8")).body.trim();
26280
+ const body = splitFrontMatter(readFileSync11(entry.path, "utf8")).body.trim();
25876
26281
  if (!body) return unknown2();
25877
26282
  return { skill: { name: entry.name, body } };
25878
26283
  } catch {
@@ -25898,11 +26303,11 @@ function scan(dir, agent, out) {
25898
26303
  return;
25899
26304
  }
25900
26305
  for (const entry of entries) {
25901
- const path = entry.name.endsWith(".md") ? join19(dir, entry.name) : join19(dir, entry.name, SKILL_FILE);
26306
+ const path = entry.name.endsWith(".md") ? join21(dir, entry.name) : join21(dir, entry.name, SKILL_FILE);
25902
26307
  try {
25903
26308
  const stats = statSync4(path);
25904
26309
  if (!stats.isFile() || stats.size > MAX_SKILL_BYTES) continue;
25905
- const { fields, body } = splitFrontMatter(readFileSync10(path, "utf8"));
26310
+ const { fields, body } = splitFrontMatter(readFileSync11(path, "utf8"));
25906
26311
  if (!body.trim()) continue;
25907
26312
  const name = clean(unquote(fields.name) || entry.name.replace(/\.md$/, ""), MAX_NAME);
25908
26313
  if (!name) continue;
@@ -25923,13 +26328,13 @@ function idOf(path) {
25923
26328
  }
25924
26329
 
25925
26330
  // agent/approvals.ts
25926
- import { chmodSync as chmodSync5, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
25927
- import { join as join20 } from "path";
25928
- var approvalsPath = join20(stateDir, "approvals.json");
26331
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
26332
+ import { join as join22 } from "path";
26333
+ var approvalsPath = join22(stateDir, "approvals.json");
25929
26334
  var MAX_GRANTS = 200;
25930
- function read2() {
26335
+ function read3() {
25931
26336
  try {
25932
- const parsed2 = JSON.parse(readFileSync11(approvalsPath, "utf8"));
26337
+ const parsed2 = JSON.parse(readFileSync12(approvalsPath, "utf8"));
25933
26338
  if (!Array.isArray(parsed2.grants)) return [];
25934
26339
  return parsed2.grants.filter(
25935
26340
  (grant) => !!grant && typeof grant.action === "string" && typeof grant.host === "string" && typeof grant.at === "string"
@@ -25939,16 +26344,16 @@ function read2() {
25939
26344
  }
25940
26345
  }
25941
26346
  function write3(grants) {
25942
- mkdirSync8(stateDir, { recursive: true, mode: 448 });
25943
- writeFileSync7(approvalsPath, `${JSON.stringify({ grants }, null, 2)}
26347
+ mkdirSync10(stateDir, { recursive: true, mode: 448 });
26348
+ writeFileSync9(approvalsPath, `${JSON.stringify({ grants }, null, 2)}
25944
26349
  `, { mode: 384 });
25945
26350
  chmodSync5(approvalsPath, 384);
25946
26351
  }
25947
26352
  function isGranted(action, host) {
25948
- return read2().some((grant) => grant.action === action && grant.host === host);
26353
+ return read3().some((grant) => grant.action === action && grant.host === host);
25949
26354
  }
25950
26355
  function rememberGrant(action, host, at) {
25951
- const kept = read2().filter((grant) => !(grant.action === action && grant.host === host));
26356
+ const kept = read3().filter((grant) => !(grant.action === action && grant.host === host));
25952
26357
  write3([{ action, host, at }, ...kept].slice(0, MAX_GRANTS));
25953
26358
  log(`remembered approval: ${action} on ${host}`);
25954
26359
  }
@@ -26104,9 +26509,9 @@ function validateFileReport(input2) {
26104
26509
  const agent = AGENT_KINDS.find((known2) => known2 === raw.agent);
26105
26510
  const kind = FILE_KINDS.find((known2) => known2 === raw.kind);
26106
26511
  if (verdict !== "analyzed") {
26107
- const reason = reasonOf(raw.reason);
26108
- if (!reason) return { ok: false, message: "A report that did not read the file has to say why." };
26109
- return { ok: true, report: { verdict, agent, kind, reason } };
26512
+ const reason2 = reasonOf(raw.reason);
26513
+ if (!reason2) return { ok: false, message: "A report that did not read the file has to say why." };
26514
+ return { ok: true, report: { verdict, agent, kind, reason: reason2 } };
26110
26515
  }
26111
26516
  const summary2 = line(raw.summary, REPORT_LIMITS.summary);
26112
26517
  if (!summary2) return { ok: false, message: "The report has no summary." };
@@ -26379,189 +26784,7 @@ ${overlay.body.trim()}`;
26379
26784
  }
26380
26785
 
26381
26786
  // agent/runner.ts
26382
- import { join as join22 } from "path";
26383
-
26384
- // agent/runners/drive.ts
26385
- import { spawn as spawn3 } from "child_process";
26386
- import { mkdirSync as mkdirSync9, utimesSync, writeFileSync as writeFileSync8 } from "fs";
26387
- import { dirname as dirname6, join as join21 } from "path";
26388
- import { createInterface } from "readline";
26389
-
26390
- // agent/runners/types.ts
26391
- var RunError = class extends Error {
26392
- constructor(code, message) {
26393
- super(message);
26394
- this.code = code;
26395
- this.name = "RunError";
26396
- }
26397
- code;
26398
- };
26399
-
26400
- // agent/runners/drive.ts
26401
- var KILL_GRACE_MS = 5e3;
26402
- var STRIPPED = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", "BROWSENTIC_AGENT_RUN"];
26403
- function launch(kind, mode, settings, plan, signal) {
26404
- const problems = vetPlan(kind, mode, plan, stateDir);
26405
- if (problems.length) {
26406
- throw new RunError(
26407
- "AGENT_UNSAFE",
26408
- `Browsentic refused to start ${AGENTS[kind].label}: ${problems.join(" ")} This is a bug in Browsentic, not something you did \u2014 please report it.`
26409
- );
26410
- }
26411
- log(describeContainment(kind));
26412
- mkdirSync9(plan.cwd, { recursive: true, mode: 448 });
26413
- for (const file2 of plan.files ?? []) {
26414
- const path = join21(plan.cwd, file2.path);
26415
- mkdirSync9(dirname6(path), { recursive: true, mode: 448 });
26416
- writeFileSync8(path, file2.content, { mode: 384 });
26417
- }
26418
- const now = /* @__PURE__ */ new Date();
26419
- utimesSync(plan.cwd, now, now);
26420
- const dropped = sealedAway(kind, process.env);
26421
- if (dropped.length) log(`sealed ${dropped.length} credential-shaped variables out of the ${kind} environment`);
26422
- const env = sealEnv(kind, process.env);
26423
- for (const key of STRIPPED) delete env[key];
26424
- Object.assign(env, plan.env);
26425
- const child = spawn3(settings.bin, plan.args, {
26426
- cwd: plan.cwd,
26427
- env,
26428
- stdio: ["ignore", "pipe", "pipe"]
26429
- });
26430
- const kill = () => {
26431
- child.kill("SIGTERM");
26432
- const hardKill = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
26433
- hardKill.unref();
26434
- };
26435
- if (signal.aborted) kill();
26436
- else signal.addEventListener("abort", kill, { once: true });
26437
- return { child, release: () => signal.removeEventListener("abort", kill), stop: kill };
26438
- }
26439
- function notInstalled(runner, settings) {
26440
- const agent = AGENTS[runner.kind];
26441
- return new RunError(
26442
- "AGENT_MISSING",
26443
- `Could not run "${settings.bin}" \u2014 ${agent.label} is not installed, or not on the daemon's PATH. Install it (${agent.install}), or set {"agents":{"${runner.kind}":{"bin":"/absolute/path/to/${agent.bin}"}}} in ${configPath}.`
26444
- );
26445
- }
26446
- function spawnError(runner, settings, error51) {
26447
- return error51.code === "ENOENT" ? notInstalled(runner, settings) : new RunError("AGENT_FAILED", error51.message);
26448
- }
26449
- function runStream(runner, context, signal, emit) {
26450
- const plan = runner.stream(context);
26451
- const label2 = AGENTS[runner.kind].label;
26452
- return new Promise((resolve4, reject) => {
26453
- const { child, release, stop } = launch(runner.kind, "run", context.settings, plan, signal);
26454
- let sessionId2 = null;
26455
- let settled = false;
26456
- let stderrTail = "";
26457
- const settle2 = (outcome) => {
26458
- if (settled) return;
26459
- settled = true;
26460
- outcome();
26461
- };
26462
- const outbound = sealingStream();
26463
- const say = (delta) => delta && emit({ kind: "text", delta });
26464
- const flush = () => say(outbound.flush());
26465
- const sink = {
26466
- text: (delta) => delta && say(outbound.push(delta)),
26467
- tool: (toolId, name) => emit({ kind: "tool", toolId, action: name, input: {} }),
26468
- session: (id) => {
26469
- if (id) sessionId2 = id;
26470
- },
26471
- usage: (usage) => emit({ kind: "usage", usage }),
26472
- done: (stopReason) => settle2(() => {
26473
- flush();
26474
- resolve4({ stopReason, sessionId: sessionId2 });
26475
- }),
26476
- // A run the reader has failed is over, and its process would otherwise go on spending and acting.
26477
- fail: (code, message) => settle2(() => {
26478
- flush();
26479
- stop();
26480
- reject(new RunError(code, message));
26481
- })
26482
- };
26483
- child.stderr.on("data", (chunk) => {
26484
- stderrTail = (stderrTail + chunk.toString()).slice(-2e3);
26485
- });
26486
- const read3 = runner.reader();
26487
- const lines = createInterface({ input: child.stdout });
26488
- lines.on("line", (line2) => {
26489
- if (!line2.trim()) return;
26490
- try {
26491
- read3(line2, sink);
26492
- } catch (error51) {
26493
- log(`${runner.kind} runner could not read a stream line: ${String(error51)}`);
26494
- }
26495
- });
26496
- child.on(
26497
- "error",
26498
- (error51) => settle2(() => reject(spawnError(runner, context.settings, error51)))
26499
- );
26500
- child.on("close", (exitCode) => {
26501
- release();
26502
- if (settled) return;
26503
- if (signal.aborted) return settle2(() => reject(new RunError("CANCELLED", "Run cancelled.")));
26504
- if (runner.endsOnExit && exitCode === 0) return sink.done("end_turn");
26505
- const hint = runner.hint?.(stderrTail);
26506
- settle2(
26507
- () => reject(
26508
- new RunError(
26509
- "AGENT_FAILED",
26510
- hint ?? `${label2} exited with code ${exitCode} before finishing${tail(stderrTail)}`
26511
- )
26512
- )
26513
- );
26514
- });
26515
- });
26516
- }
26517
- function runJson(runner, context, signal, { timedOut, empty, accept }) {
26518
- const plan = runner.json(context);
26519
- const label2 = AGENTS[runner.kind].label;
26520
- return new Promise((resolve4, reject) => {
26521
- const { child, release, stop } = launch(runner.kind, "task", context.settings, plan, signal);
26522
- let stdout = "";
26523
- let stderrTail = "";
26524
- let settled = false;
26525
- const settle2 = (outcome) => {
26526
- if (settled) return;
26527
- settled = true;
26528
- release();
26529
- outcome();
26530
- };
26531
- child.stdout.on("data", (chunk) => {
26532
- stdout += chunk.toString();
26533
- if (!accept || settled) return;
26534
- const text2 = runner.answer(stdout).text?.trim();
26535
- if (text2 && accept(text2)) {
26536
- settle2(() => {
26537
- stop();
26538
- resolve4(text2);
26539
- });
26540
- }
26541
- });
26542
- child.stderr.on("data", (chunk) => {
26543
- stderrTail = (stderrTail + chunk.toString()).slice(-2e3);
26544
- });
26545
- child.on("error", (error51) => settle2(() => reject(spawnError(runner, context.settings, error51))));
26546
- child.on(
26547
- "close",
26548
- () => settle2(() => {
26549
- if (signal.aborted) return reject(signal.reason instanceof RunError ? signal.reason : new RunError("TIMEOUT", timedOut));
26550
- const answer = runner.answer(stdout);
26551
- if (answer.error) return reject(new RunError("AGENT_FAILED", answer.error));
26552
- const text2 = answer.text?.trim();
26553
- if (!text2) {
26554
- const hint = runner.hint?.(stderrTail);
26555
- return reject(new RunError("AGENT_FAILED", hint ?? (stderrTail.trim() ? `${label2}: ${stderrTail.trim()}` : empty)));
26556
- }
26557
- resolve4(text2);
26558
- })
26559
- );
26560
- });
26561
- }
26562
- var tail = (stderr) => stderr.trim() ? `: ${stderr.trim()}` : "";
26563
-
26564
- // agent/runner.ts
26787
+ import { join as join23 } from "path";
26565
26788
  function runInstruction(request) {
26566
26789
  const { runner, settings } = runnerFor(request.config);
26567
26790
  return runStream(
@@ -26587,13 +26810,13 @@ function runAgentJson(prompt, config5, signal, { reads = false, ...options }) {
26587
26810
  return runJson(runner, { prompt, settings, reads, workspace: runner.workspace("task") }, signal, options);
26588
26811
  }
26589
26812
  function taskDir(config5) {
26590
- return join22(runnerFor(config5).runner.workspace("task"), "tmp");
26813
+ return join23(runnerFor(config5).runner.workspace("task"), "tmp");
26591
26814
  }
26592
26815
 
26593
26816
  // agent/site-map-store.ts
26594
26817
  import { randomUUID as randomUUID9 } from "crypto";
26595
- import { existsSync as existsSync5, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync4, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "fs";
26596
- import { dirname as dirname7, join as join23, resolve as resolve2, sep as sep3 } from "path";
26818
+ import { existsSync as existsSync5, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync4, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync10 } from "fs";
26819
+ import { dirname as dirname7, join as join24, resolve as resolve2, sep as sep3 } from "path";
26597
26820
  var STAGING = ".staging";
26598
26821
  function mapTargetFor(url2) {
26599
26822
  let parsed2;
@@ -26626,33 +26849,33 @@ function mapTargetFor(url2) {
26626
26849
  return { ok: true, target: { host, origin: parsed2.origin, domain: domain2, name } };
26627
26850
  }
26628
26851
  function uniqueName(domain2, host) {
26629
- const plain2 = skillNameForHost(domain2);
26630
- if (!SKILL_NAME_RE.test(plain2)) return null;
26631
- const owner = mappedHostOf(plain2);
26632
- if (owner === null || owner === host) return plain2;
26852
+ const plain3 = skillNameForHost(domain2);
26853
+ if (!SKILL_NAME_RE.test(plain3)) return null;
26854
+ const owner = mappedHostOf(plain3);
26855
+ if (owner === null || owner === host) return plain3;
26633
26856
  const disambiguated = skillNameForHost(domain2, { disambiguate: true });
26634
26857
  return SKILL_NAME_RE.test(disambiguated) ? disambiguated : null;
26635
26858
  }
26636
26859
  function mappedHostOf(name) {
26637
26860
  try {
26638
- const meta4 = JSON.parse(readFileSync12(join23(uploadedSkillsDir(), name, "meta.json"), "utf8"));
26861
+ const meta4 = JSON.parse(readFileSync13(join24(uploadedSkillsDir(), name, "meta.json"), "utf8"));
26639
26862
  return typeof meta4.host === "string" ? meta4.host : "";
26640
26863
  } catch {
26641
- return existsSync5(join23(uploadedSkillsDir(), name, SKILL_FILE)) ? "" : null;
26864
+ return existsSync5(join24(uploadedSkillsDir(), name, SKILL_FILE)) ? "" : null;
26642
26865
  }
26643
26866
  }
26644
26867
  function prepareStaging() {
26645
26868
  const id = randomUUID9();
26646
- const dir = join23(uploadedSkillsDir(), STAGING, id);
26869
+ const dir = join24(uploadedSkillsDir(), STAGING, id);
26647
26870
  const staging = {
26648
26871
  id,
26649
26872
  dir,
26650
- screenshots: join23(dir, "screenshots"),
26651
- evidence: join23(dir, "evidence"),
26652
- pages: join23(dir, "pages")
26873
+ screenshots: join24(dir, "screenshots"),
26874
+ evidence: join24(dir, "evidence"),
26875
+ pages: join24(dir, "pages")
26653
26876
  };
26654
26877
  for (const path of [dir, staging.screenshots, staging.evidence, staging.pages]) {
26655
- mkdirSync10(path, { recursive: true, mode: 448 });
26878
+ mkdirSync11(path, { recursive: true, mode: 448 });
26656
26879
  }
26657
26880
  return staging;
26658
26881
  }
@@ -26665,7 +26888,7 @@ function stagedScreenshots(staging) {
26665
26888
  }
26666
26889
  function writeEvidence(staging, name, body) {
26667
26890
  if (!body.trim()) return;
26668
- writeFileSync9(join23(staging.evidence, name), body, { mode: 384 });
26891
+ writeFileSync10(join24(staging.evidence, name), body, { mode: 384 });
26669
26892
  }
26670
26893
  function stageSiteMap(args) {
26671
26894
  const { staging, target, report, index, background, warnings, runId } = args;
@@ -26684,17 +26907,17 @@ generatedAt: ${generatedAt}
26684
26907
  ---
26685
26908
 
26686
26909
  `);
26687
- writeFileSync9(join23(staging.dir, SKILL_FILE), markdown, { mode: 384 });
26688
- writeFileSync9(join23(staging.dir, "map.json"), JSON.stringify(report, null, 2), { mode: 384 });
26689
- writeFileSync9(
26690
- join23(staging.dir, "meta.json"),
26910
+ writeFileSync10(join24(staging.dir, SKILL_FILE), markdown, { mode: 384 });
26911
+ writeFileSync10(join24(staging.dir, "map.json"), JSON.stringify(report, null, 2), { mode: 384 });
26912
+ writeFileSync10(
26913
+ join24(staging.dir, "meta.json"),
26691
26914
  JSON.stringify({ name: target.name, host: target.host, domain: target.domain, generatedAt, runId }, null, 2),
26692
26915
  { mode: 384 }
26693
26916
  );
26694
26917
  for (const [index_, page] of report.pages.entries()) {
26695
26918
  if (!page.notes) continue;
26696
26919
  const file2 = `${String(index_ + 1).padStart(2, "0")}-${skillNameForHost(page.path) || "page"}.md`;
26697
- writeFileSync9(join23(staging.pages, file2), `# ${page.title}
26920
+ writeFileSync10(join24(staging.pages, file2), `# ${page.title}
26698
26921
 
26699
26922
  ${page.path}
26700
26923
 
@@ -26706,7 +26929,7 @@ ${page.notes}
26706
26929
  name: target.name,
26707
26930
  host: target.host,
26708
26931
  domain: target.domain,
26709
- directory: join23(uploadedSkillsDir(), target.name),
26932
+ directory: join24(uploadedSkillsDir(), target.name),
26710
26933
  markdown,
26711
26934
  pages: report.pages.length,
26712
26935
  screenshots: stagedScreenshots(staging).length,
@@ -26757,7 +26980,7 @@ function renderSiteMapBody(args) {
26757
26980
  }
26758
26981
  const shots = report.pages.filter((page) => page.screenshot).length;
26759
26982
  if (shots) {
26760
- out.push("", "## Screenshots", "", `Full-size captures: ${join23(uploadedSkillsDir(), target.name, "screenshots")}`);
26983
+ out.push("", "## Screenshots", "", `Full-size captures: ${join24(uploadedSkillsDir(), target.name, "screenshots")}`);
26761
26984
  }
26762
26985
  const body = out.join("\n");
26763
26986
  return body.length > MAX_MAP_BODY_BYTES ? `${body.slice(0, MAX_MAP_BODY_BYTES - 40)}
@@ -26769,7 +26992,7 @@ function commitStaging(stagingId, exactHost = false) {
26769
26992
  if (!staging) return failure("NOT_FOUND", "That mapping run is no longer staged.");
26770
26993
  let meta4;
26771
26994
  try {
26772
- meta4 = JSON.parse(readFileSync12(join23(staging, "meta.json"), "utf8"));
26995
+ meta4 = JSON.parse(readFileSync13(join24(staging, "meta.json"), "utf8"));
26773
26996
  } catch {
26774
26997
  return failure("NOT_FOUND", "That staged map is incomplete.");
26775
26998
  }
@@ -26781,17 +27004,17 @@ function commitStaging(stagingId, exactHost = false) {
26781
27004
  return failure("NAME_TAKEN", `"${name}" is now a skill you wrote by hand. Remove it first, or discard this map.`);
26782
27005
  }
26783
27006
  if (exactHost && host !== domain2) {
26784
- const path = join23(staging, SKILL_FILE);
26785
- writeFileSync9(path, readFileSync12(path, "utf8").replace(`domains: [${domain2}]`, `domains: [${host}]`), {
27007
+ const path = join24(staging, SKILL_FILE);
27008
+ writeFileSync10(path, readFileSync13(path, "utf8").replace(`domains: [${domain2}]`, `domains: [${host}]`), {
26786
27009
  mode: 384
26787
27010
  });
26788
27011
  }
26789
- const destination = join23(uploadedSkillsDir(), name);
27012
+ const destination = join24(uploadedSkillsDir(), name);
26790
27013
  if (!contained(destination)) return failure("INVALID_INPUT", "Refusing to write outside the skills directory.");
26791
27014
  if (existsSync5(destination)) {
26792
- renameSync3(destination, join23(uploadedSkillsDir(), STAGING, `${name}-replaced-${randomUUID9().slice(0, 8)}`));
27015
+ renameSync4(destination, join24(uploadedSkillsDir(), STAGING, `${name}-replaced-${randomUUID9().slice(0, 8)}`));
26793
27016
  }
26794
- renameSync3(staging, destination);
27017
+ renameSync4(staging, destination);
26795
27018
  log(`activated site map ${name} (${host})`);
26796
27019
  return success({ name, path: destination });
26797
27020
  }
@@ -26804,9 +27027,9 @@ function discardStaging(stagingId) {
26804
27027
  }
26805
27028
  function stagingDirFor(stagingId) {
26806
27029
  if (!/^[0-9a-f-]{36}$/i.test(stagingId)) return null;
26807
- const dir = resolve2(join23(uploadedSkillsDir(), STAGING, stagingId));
26808
- if (dirname7(dir) !== resolve2(join23(uploadedSkillsDir(), STAGING))) return null;
26809
- return existsSync5(join23(dir, SKILL_FILE)) ? dir : null;
27030
+ const dir = resolve2(join24(uploadedSkillsDir(), STAGING, stagingId));
27031
+ if (dirname7(dir) !== resolve2(join24(uploadedSkillsDir(), STAGING))) return null;
27032
+ return existsSync5(join24(dir, SKILL_FILE)) ? dir : null;
26810
27033
  }
26811
27034
  function contained(path) {
26812
27035
  const root = resolve2(uploadedSkillsDir());
@@ -26814,7 +27037,7 @@ function contained(path) {
26814
27037
  return target === root || target.startsWith(root + sep3);
26815
27038
  }
26816
27039
  function sweepStaging(maxAgeMs = 24 * 60 * 60 * 1e3, now = Date.now()) {
26817
- const root = join23(uploadedSkillsDir(), STAGING);
27040
+ const root = join24(uploadedSkillsDir(), STAGING);
26818
27041
  let entries;
26819
27042
  try {
26820
27043
  entries = readdirSync4(root);
@@ -26823,12 +27046,12 @@ function sweepStaging(maxAgeMs = 24 * 60 * 60 * 1e3, now = Date.now()) {
26823
27046
  }
26824
27047
  for (const entry of entries) {
26825
27048
  try {
26826
- const meta4 = JSON.parse(readFileSync12(join23(root, entry, "meta.json"), "utf8"));
27049
+ const meta4 = JSON.parse(readFileSync13(join24(root, entry, "meta.json"), "utf8"));
26827
27050
  const at = typeof meta4.generatedAt === "string" ? Date.parse(meta4.generatedAt) : 0;
26828
27051
  if (at && now - at < maxAgeMs) continue;
26829
27052
  } catch {
26830
27053
  }
26831
- rmSync5(join23(root, entry), { recursive: true, force: true });
27054
+ rmSync5(join24(root, entry), { recursive: true, force: true });
26832
27055
  log(`swept abandoned staging ${entry}`);
26833
27056
  }
26834
27057
  }
@@ -27687,8 +27910,8 @@ function clip2(text2) {
27687
27910
 
27688
27911
  // agent/captcha-analyst.ts
27689
27912
  import { randomUUID as randomUUID11 } from "crypto";
27690
- import { mkdirSync as mkdirSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
27691
- import { join as join24 } from "path";
27913
+ import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
27914
+ import { join as join25 } from "path";
27692
27915
  var MAX_POINTS = 16;
27693
27916
  var ANSWER_TIMEOUT_MS = 45e3;
27694
27917
  var OPEN2 = "=== ANSWER ===";
@@ -27698,9 +27921,9 @@ async function answerChallenge(challenge, config5, signal) {
27698
27921
  const picture = decodeImage(challenge.image);
27699
27922
  if (!picture || !analystSees(config5) || signal.aborted) return null;
27700
27923
  const dir = taskDir(config5);
27701
- mkdirSync11(dir, { recursive: true, mode: 448 });
27702
- const path = join24(dir, `${randomUUID11()}-captcha.${picture.extension}`);
27703
- writeFileSync10(path, picture.bytes, { mode: 384 });
27924
+ mkdirSync12(dir, { recursive: true, mode: 448 });
27925
+ const path = join25(dir, `${randomUUID11()}-captcha.${picture.extension}`);
27926
+ writeFileSync11(path, picture.bytes, { mode: 384 });
27704
27927
  const stop = new AbortController();
27705
27928
  const timer = setTimeout(() => stop.abort(new RunError("TIMEOUT", "The captcha analyst took too long.")), ANSWER_TIMEOUT_MS);
27706
27929
  const cancelled = () => stop.abort(signal.reason);
@@ -27884,8 +28107,8 @@ function handBack(result, rounds, why) {
27884
28107
 
27885
28108
  // agent/file-analyst.ts
27886
28109
  import { randomUUID as randomUUID12 } from "crypto";
27887
- import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "fs";
27888
- import { join as join25 } from "path";
28110
+ import { mkdirSync as mkdirSync13, unlinkSync as unlinkSync3, writeFileSync as writeFileSync12 } from "fs";
28111
+ import { join as join26 } from "path";
27889
28112
 
27890
28113
  // agent/file-screen.ts
27891
28114
  var SAMPLE_BYTES = 64 * 1024;
@@ -28067,9 +28290,9 @@ async function analyzeFile(req, config5, signal) {
28067
28290
  }
28068
28291
  const { kind, extension: extension2 } = screened;
28069
28292
  const tmpDir = taskDir(config5);
28070
- mkdirSync12(tmpDir, { recursive: true, mode: 448 });
28071
- const path = join25(tmpDir, `${randomUUID12()}-${safeName2(req.name)}.${extension2}`);
28072
- writeFileSync11(path, bytes, { mode: 384 });
28293
+ mkdirSync13(tmpDir, { recursive: true, mode: 448 });
28294
+ const path = join26(tmpDir, `${randomUUID12()}-${safeName2(req.name)}.${extension2}`);
28295
+ writeFileSync12(path, bytes, { mode: 384 });
28073
28296
  const stop = new AbortController();
28074
28297
  const timer = setTimeout(() => stop.abort(new RunError("TIMEOUT", "Reading the file took too long.")), ANALYZE_TIMEOUT_MS);
28075
28298
  const cancelled = () => stop.abort(signal.reason);
@@ -28161,8 +28384,8 @@ function safeName2(name) {
28161
28384
 
28162
28385
  // agent/recording.ts
28163
28386
  import { randomUUID as randomUUID13 } from "crypto";
28164
- import { mkdirSync as mkdirSync13, unlinkSync as unlinkSync4, writeFileSync as writeFileSync12 } from "fs";
28165
- import { join as join26 } from "path";
28387
+ import { mkdirSync as mkdirSync14, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "fs";
28388
+ import { join as join27 } from "path";
28166
28389
 
28167
28390
  // ../lib/recordings/workflow.ts
28168
28391
  var MAX_STEPS = 80;
@@ -28327,9 +28550,9 @@ async function analyzeRecording(req, config5) {
28327
28550
  return failure("RECORDING_TOO_LARGE", "The recorded trace is too large to summarize.");
28328
28551
  }
28329
28552
  const tmpDir = taskDir(config5);
28330
- mkdirSync13(tmpDir, { recursive: true, mode: 448 });
28331
- const path = join26(tmpDir, `${randomUUID13()}-recording.json`);
28332
- writeFileSync12(path, trace, { mode: 384 });
28553
+ mkdirSync14(tmpDir, { recursive: true, mode: 448 });
28554
+ const path = join27(tmpDir, `${randomUUID13()}-recording.json`);
28555
+ writeFileSync13(path, trace, { mode: 384 });
28333
28556
  const controller = new AbortController();
28334
28557
  const timer = setTimeout(() => controller.abort(), ANALYZE_TIMEOUT_MS2);
28335
28558
  log(`analyzing recording ${recording.name} (${recording.events.length} events on ${recording.host})`);
@@ -28440,8 +28663,8 @@ function clamp3(output) {
28440
28663
  }
28441
28664
 
28442
28665
  // agent/skill-store.ts
28443
- import { existsSync as existsSync6, mkdirSync as mkdirSync14, readdirSync as readdirSync5, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync13, chmodSync as chmodSync6 } from "fs";
28444
- import { dirname as dirname8, join as join27, resolve as resolve3 } from "path";
28666
+ import { existsSync as existsSync6, mkdirSync as mkdirSync15, readdirSync as readdirSync5, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync14, chmodSync as chmodSync6 } from "fs";
28667
+ import { dirname as dirname8, join as join28, resolve as resolve3 } from "path";
28445
28668
  var MAX_SKILL_FILES = 50;
28446
28669
  function saveSkill(draft) {
28447
28670
  const checked = validateSkillDraft(draft, { reservedNames: bundledSkillNames() });
@@ -28456,7 +28679,7 @@ function saveSkill(draft) {
28456
28679
  const dir = uploadedSkillsDir();
28457
28680
  const path = pathIn(dir, skill.name);
28458
28681
  if (!path) return failure("INVALID_INPUT", `"${skill.name}" is not a usable skill name.`);
28459
- if (existsSync6(join27(dir, skill.name, SKILL_FILE))) {
28682
+ if (existsSync6(join28(dir, skill.name, SKILL_FILE))) {
28460
28683
  return failure(
28461
28684
  "NAME_TAKEN",
28462
28685
  `"${skill.name}" is a mapped site. Remove that map first, or give this skill another name.`
@@ -28467,11 +28690,11 @@ function saveSkill(draft) {
28467
28690
  return failure("TOO_MANY_SKILLS", `There are already ${MAX_SKILL_FILES} uploaded skills. Remove one first.`);
28468
28691
  }
28469
28692
  try {
28470
- mkdirSync14(dir, { recursive: true, mode: 448 });
28693
+ mkdirSync15(dir, { recursive: true, mode: 448 });
28471
28694
  const temp = `${path}.tmp`;
28472
- writeFileSync13(temp, serializeSkillFile(skill), { mode: 384 });
28695
+ writeFileSync14(temp, serializeSkillFile(skill), { mode: 384 });
28473
28696
  chmodSync6(temp, 384);
28474
- renameSync4(temp, path);
28697
+ renameSync5(temp, path);
28475
28698
  } catch (error51) {
28476
28699
  log(`failed to save skill ${skill.name}`, error51);
28477
28700
  return failure("WRITE_FAILED", `Could not write ${path}: ${String(error51)}`);
@@ -28497,7 +28720,7 @@ function deleteSiteMap(name) {
28497
28720
  const path = pathIn(dir, name);
28498
28721
  if (!path) return failure("INVALID_INPUT", `"${name}" is not a usable skill name.`);
28499
28722
  const mapDir = path.replace(/\.md$/, "");
28500
- if (!existsSync6(join27(mapDir, SKILL_FILE))) {
28723
+ if (!existsSync6(join28(mapDir, SKILL_FILE))) {
28501
28724
  return failure("NOT_FOUND", `No mapped site called "${name}".`);
28502
28725
  }
28503
28726
  try {
@@ -28511,13 +28734,13 @@ function deleteSiteMap(name) {
28511
28734
  }
28512
28735
  function pathIn(dir, name) {
28513
28736
  if (!SKILL_NAME_RE.test(name)) return null;
28514
- const candidate = resolve3(join27(dir, `${name}.md`));
28737
+ const candidate = resolve3(join28(dir, `${name}.md`));
28515
28738
  return dirname8(candidate) === resolve3(dir) ? candidate : null;
28516
28739
  }
28517
28740
  function countSkills(dir) {
28518
28741
  try {
28519
28742
  return readdirSync5(dir, { withFileTypes: true }).filter(
28520
- (entry) => !entry.name.startsWith(".") && (entry.isFile() ? entry.name.endsWith(".md") : entry.isDirectory() && existsSync6(join27(dir, entry.name, SKILL_FILE)))
28743
+ (entry) => !entry.name.startsWith(".") && (entry.isFile() ? entry.name.endsWith(".md") : entry.isDirectory() && existsSync6(join28(dir, entry.name, SKILL_FILE)))
28521
28744
  ).length;
28522
28745
  } catch {
28523
28746
  return 0;
@@ -28585,10 +28808,10 @@ var ExtensionLink = class {
28585
28808
  log("failed to send frame to extension", error51);
28586
28809
  }
28587
28810
  }
28588
- close(reason) {
28589
- this.dispose(reason);
28811
+ close(reason2) {
28812
+ this.dispose(reason2);
28590
28813
  try {
28591
- this.socket.close(1e3, reason);
28814
+ this.socket.close(1e3, reason2);
28592
28815
  } catch {
28593
28816
  }
28594
28817
  }
@@ -28623,13 +28846,13 @@ var ExtensionLink = class {
28623
28846
  if (frame.t === "result") waiting.resolve(frame.result);
28624
28847
  else if (frame.t === "manifest") waiting.resolve({ ok: true, data: frame.tools });
28625
28848
  }
28626
- dispose(reason) {
28849
+ dispose(reason2) {
28627
28850
  if (this.closed) return;
28628
28851
  this.closed = true;
28629
28852
  clearInterval(this.ping);
28630
28853
  for (const [, waiting] of this.pending) {
28631
28854
  clearTimeout(waiting.timer);
28632
- waiting.resolve(failure("EXTENSION_OFFLINE", `Connection lost: ${reason}`));
28855
+ waiting.resolve(failure("EXTENSION_OFFLINE", `Connection lost: ${reason2}`));
28633
28856
  }
28634
28857
  this.pending.clear();
28635
28858
  this.onClose(this);
@@ -28710,8 +28933,8 @@ async function startDaemon({ version: version3, idleExit = true }) {
28710
28933
  const bundledHash = hashManifest(bundled);
28711
28934
  const bundledByHash = new Map(
28712
28935
  ["chromium", "firefox"].map((target) => {
28713
- const list2 = describeActions(target);
28714
- return [hashManifest(list2), list2];
28936
+ const list3 = describeActions(target);
28937
+ return [hashManifest(list3), list3];
28715
28938
  })
28716
28939
  );
28717
28940
  let offered = bundled;
@@ -28786,10 +29009,10 @@ async function startDaemon({ version: version3, idleExit = true }) {
28786
29009
  });
28787
29010
  }
28788
29011
  async function greet(ws, req) {
28789
- const reject = (reason, retryable) => {
28790
- log(`rejected extension: ${reason}`);
29012
+ const reject = (reason2, retryable) => {
29013
+ log(`rejected extension: ${reason2}`);
28791
29014
  try {
28792
- ws.send(JSON.stringify({ t: "unauthorized", reason, retryable }));
29015
+ ws.send(JSON.stringify({ t: "unauthorized", reason: reason2, retryable }));
28793
29016
  } catch {
28794
29017
  }
28795
29018
  ws.close(4401, "unauthorized");
@@ -28876,7 +29099,7 @@ async function startDaemon({ version: version3, idleExit = true }) {
28876
29099
  links.get(install.installId)?.close("superseded by a newer connection");
28877
29100
  const known2 = bundledByHash.get(hello.manifestHash);
28878
29101
  const manifestInSync = known2 !== void 0;
28879
- const accepted = new ExtensionLink(
29102
+ const accepted2 = new ExtensionLink(
28880
29103
  ws,
28881
29104
  { ...hello, ...install },
28882
29105
  (closing) => {
@@ -28956,21 +29179,22 @@ async function startDaemon({ version: version3, idleExit = true }) {
28956
29179
  session(source).handle(request);
28957
29180
  }
28958
29181
  );
28959
- links.set(accepted.id, accepted);
28960
- accepted.tools = known2 ?? bundled;
29182
+ links.set(accepted2.id, accepted2);
29183
+ accepted2.tools = known2 ?? bundled;
28961
29184
  settleOffer();
28962
- log(`extension ${hello.extensionVersion} connected from ${accepted.label} (manifest ${manifestInSync ? "in sync" : "DRIFTED"})`);
29185
+ log(`extension ${hello.extensionVersion} connected from ${accepted2.label} (manifest ${manifestInSync ? "in sync" : "DRIFTED"})`);
28963
29186
  const welcome = {
28964
29187
  daemonVersion: version3,
28965
29188
  manifestHash: known2 ? hello.manifestHash : bundledHash,
28966
29189
  manifestInSync,
28967
29190
  sealedSessionKey
28968
29191
  };
28969
- accepted.send({ t: "welcome", ...welcome, proof: await serverProof(secret, transcript, welcome) });
29192
+ accepted2.send({ t: "welcome", ...welcome, proof: await serverProof(secret, transcript, welcome) });
28970
29193
  scheduleIdleExit();
28971
- void pushAgentState(accepted);
28972
- pushSkillCatalog(accepted);
28973
- if (!known2) await adoptExtensionManifest(accepted);
29194
+ void pushAgentState(accepted2);
29195
+ watchModels();
29196
+ pushSkillCatalog(accepted2);
29197
+ if (!known2) await adoptExtensionManifest(accepted2);
28974
29198
  }
28975
29199
  function openLinks() {
28976
29200
  return [...links.values()].filter((link) => link.isOpen);
@@ -29008,12 +29232,21 @@ async function startDaemon({ version: version3, idleExit = true }) {
29008
29232
  }
29009
29233
  if (request.t === "setAgentModel") {
29010
29234
  const model = typeof request.model === "string" ? request.model : null;
29011
- writeAgentModel(request.agent, model);
29235
+ if (!writeAgentModel(request.agent, model)) {
29236
+ return failure("INVALID_INPUT", `"${String(model)}" is not a model id: it has to start with a letter or digit and hold no spaces.`);
29237
+ }
29012
29238
  log(`${AGENTS[request.agent].label} model set to ${model?.trim() || "the default"}`);
29013
29239
  }
29014
29240
  if (request.t === "grantAgent") await grantRunner(request.agent);
29015
- const refresh = request.t !== "agentState" || request.refresh === true;
29016
- return success(await agentState(readAgentConfig(), { refresh }));
29241
+ const recheck = request.t === "agentState" && request.refresh === true;
29242
+ const state = await agentState(readAgentConfig(), { refresh: request.t !== "agentState" || recheck });
29243
+ watchModels({ force: recheck });
29244
+ return success(state);
29245
+ }
29246
+ function watchModels({ force = false } = {}) {
29247
+ void refreshModelLists(readAgentConfig(), { force }).then((changed) => {
29248
+ if (changed || force) for (const link of openLinks()) void pushAgentState(link);
29249
+ }).catch((error51) => log(`could not refresh the model lists: ${String(error51)}`));
29017
29250
  }
29018
29251
  function settleGuardrail(request) {
29019
29252
  if (request.t === "setGuardrail") {
@@ -29161,8 +29394,11 @@ async function startDaemon({ version: version3, idleExit = true }) {
29161
29394
  log(`control ${client} set the agent to ${AGENTS[request.set].label}`);
29162
29395
  }
29163
29396
  if (request.grant) await grantRunner(request.grant);
29164
- const state = await agentState(readAgentConfig(), { refresh: !!changed });
29165
- if (changed) announceAgent();
29397
+ const listed = isAgentKind(request.models);
29398
+ if (listed) await refreshModelLists(readAgentConfig(), { force: true, only: request.models });
29399
+ const state = await agentState(readAgentConfig(), { refresh: !!changed || listed });
29400
+ if (changed || listed) announceAgent();
29401
+ else watchModels();
29166
29402
  return send2(ws, { id: request.id, op: "agent", state });
29167
29403
  }
29168
29404
  if (request.op === "revoke") {
@@ -29326,11 +29562,11 @@ function nextFrame(ws) {
29326
29562
  ws.once("message", onMessage);
29327
29563
  });
29328
29564
  }
29329
- function refuseUpgrade(socket, reason) {
29565
+ function refuseUpgrade(socket, reason2) {
29330
29566
  socket.write(`HTTP/1.1 403 Forbidden\r
29331
29567
  Content-Length: 0\r
29332
29568
  Connection: close\r
29333
- X-Browsentic-Reason: ${reason}\r
29569
+ X-Browsentic-Reason: ${reason2}\r
29334
29570
  \r
29335
29571
  `);
29336
29572
  socket.destroy();
@@ -29340,7 +29576,7 @@ X-Browsentic-Reason: ${reason}\r
29340
29576
  var package_default = {
29341
29577
  name: "browsentic",
29342
29578
  mcpName: "io.github.imshaikot/browsentic",
29343
- version: "0.7.5",
29579
+ version: "0.7.6",
29344
29580
  description: "A browser extension with an AI side panel that hands your real, logged-in browser to the AI agent you already run. Installs the extension, runs the local daemon, and optionally speaks MCP.",
29345
29581
  type: "module",
29346
29582
  license: "MIT",