browsentic 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +464 -213
- package/dist/daemon-main.js +485 -229
- package/extension/chrome-mv3/background.js +1 -1
- package/extension/chrome-mv3/chunks/{popup-D_jEQFBQ.js → popup-D1kmahbZ.js} +1 -1
- package/extension/chrome-mv3/chunks/{sidepanel-Dx3hYTrY.js → sidepanel-CREhtYV6.js} +1 -1
- package/extension/chrome-mv3/chunks/{use-voice-composer-DfBR4M8I.js → use-voice-composer-DqX_NzK_.js} +1 -1
- package/extension/chrome-mv3/manifest.json +1 -1
- package/extension/chrome-mv3/popup.html +2 -2
- package/extension/chrome-mv3/sidepanel.html +2 -2
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -3478,10 +3478,10 @@ var require_websocket_server = __commonJS({
|
|
|
3478
3478
|
process.nextTick(emitClose, this);
|
|
3479
3479
|
}
|
|
3480
3480
|
} else {
|
|
3481
|
-
const
|
|
3481
|
+
const server2 = this._server;
|
|
3482
3482
|
this._removeListeners();
|
|
3483
3483
|
this._removeListeners = this._server = null;
|
|
3484
|
-
|
|
3484
|
+
server2.close(() => {
|
|
3485
3485
|
emitClose(this);
|
|
3486
3486
|
});
|
|
3487
3487
|
}
|
|
@@ -3666,17 +3666,17 @@ var require_websocket_server = __commonJS({
|
|
|
3666
3666
|
}
|
|
3667
3667
|
};
|
|
3668
3668
|
module.exports = WebSocketServer2;
|
|
3669
|
-
function addListeners(
|
|
3670
|
-
for (const event of Object.keys(map2))
|
|
3669
|
+
function addListeners(server2, map2) {
|
|
3670
|
+
for (const event of Object.keys(map2)) server2.on(event, map2[event]);
|
|
3671
3671
|
return function removeListeners() {
|
|
3672
3672
|
for (const event of Object.keys(map2)) {
|
|
3673
|
-
|
|
3673
|
+
server2.removeListener(event, map2[event]);
|
|
3674
3674
|
}
|
|
3675
3675
|
};
|
|
3676
3676
|
}
|
|
3677
|
-
function emitClose(
|
|
3678
|
-
|
|
3679
|
-
|
|
3677
|
+
function emitClose(server2) {
|
|
3678
|
+
server2._state = CLOSED;
|
|
3679
|
+
server2.emit("close");
|
|
3680
3680
|
}
|
|
3681
3681
|
function socketOnError() {
|
|
3682
3682
|
this.destroy();
|
|
@@ -3695,11 +3695,11 @@ var require_websocket_server = __commonJS({
|
|
|
3695
3695
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
3696
3696
|
);
|
|
3697
3697
|
}
|
|
3698
|
-
function abortHandshakeOrEmitwsClientError(
|
|
3699
|
-
if (
|
|
3698
|
+
function abortHandshakeOrEmitwsClientError(server2, req, socket, code, message, headers) {
|
|
3699
|
+
if (server2.listenerCount("wsClientError")) {
|
|
3700
3700
|
const err = new Error(message);
|
|
3701
3701
|
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
|
3702
|
-
|
|
3702
|
+
server2.emit("wsClientError", err, socket, req);
|
|
3703
3703
|
} else {
|
|
3704
3704
|
abortHandshake(socket, code, message, headers);
|
|
3705
3705
|
}
|
|
@@ -7322,49 +7322,49 @@ var require_fast_uri = __commonJS({
|
|
|
7322
7322
|
schemelessOptions.skipEscape = true;
|
|
7323
7323
|
return serialize(resolved, schemelessOptions);
|
|
7324
7324
|
}
|
|
7325
|
-
function resolveComponent(base,
|
|
7325
|
+
function resolveComponent(base, relative4, options, skipNormalization) {
|
|
7326
7326
|
const target = {};
|
|
7327
7327
|
if (!skipNormalization) {
|
|
7328
7328
|
base = parse3(serialize(base, options), options);
|
|
7329
|
-
|
|
7329
|
+
relative4 = parse3(serialize(relative4, options), options);
|
|
7330
7330
|
}
|
|
7331
7331
|
options = options || {};
|
|
7332
|
-
if (!options.tolerant &&
|
|
7333
|
-
target.scheme =
|
|
7334
|
-
target.userinfo =
|
|
7335
|
-
target.host =
|
|
7336
|
-
target.port =
|
|
7337
|
-
target.path = removeDotSegments(
|
|
7338
|
-
target.query =
|
|
7332
|
+
if (!options.tolerant && relative4.scheme) {
|
|
7333
|
+
target.scheme = relative4.scheme;
|
|
7334
|
+
target.userinfo = relative4.userinfo;
|
|
7335
|
+
target.host = relative4.host;
|
|
7336
|
+
target.port = relative4.port;
|
|
7337
|
+
target.path = removeDotSegments(relative4.path || "");
|
|
7338
|
+
target.query = relative4.query;
|
|
7339
7339
|
} else {
|
|
7340
|
-
if (
|
|
7341
|
-
target.userinfo =
|
|
7342
|
-
target.host =
|
|
7343
|
-
target.port =
|
|
7344
|
-
target.path = removeDotSegments(
|
|
7345
|
-
target.query =
|
|
7340
|
+
if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
|
|
7341
|
+
target.userinfo = relative4.userinfo;
|
|
7342
|
+
target.host = relative4.host;
|
|
7343
|
+
target.port = relative4.port;
|
|
7344
|
+
target.path = removeDotSegments(relative4.path || "");
|
|
7345
|
+
target.query = relative4.query;
|
|
7346
7346
|
} else {
|
|
7347
|
-
if (!
|
|
7347
|
+
if (!relative4.path) {
|
|
7348
7348
|
target.path = base.path;
|
|
7349
|
-
if (
|
|
7350
|
-
target.query =
|
|
7349
|
+
if (relative4.query !== void 0) {
|
|
7350
|
+
target.query = relative4.query;
|
|
7351
7351
|
} else {
|
|
7352
7352
|
target.query = base.query;
|
|
7353
7353
|
}
|
|
7354
7354
|
} else {
|
|
7355
|
-
if (
|
|
7356
|
-
target.path = removeDotSegments(
|
|
7355
|
+
if (relative4.path[0] === "/") {
|
|
7356
|
+
target.path = removeDotSegments(relative4.path);
|
|
7357
7357
|
} else {
|
|
7358
7358
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
7359
|
-
target.path = "/" +
|
|
7359
|
+
target.path = "/" + relative4.path;
|
|
7360
7360
|
} else if (!base.path) {
|
|
7361
|
-
target.path =
|
|
7361
|
+
target.path = relative4.path;
|
|
7362
7362
|
} else {
|
|
7363
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
7363
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
|
|
7364
7364
|
}
|
|
7365
7365
|
target.path = removeDotSegments(target.path);
|
|
7366
7366
|
}
|
|
7367
|
-
target.query =
|
|
7367
|
+
target.query = relative4.query;
|
|
7368
7368
|
}
|
|
7369
7369
|
target.userinfo = base.userinfo;
|
|
7370
7370
|
target.host = base.host;
|
|
@@ -7372,7 +7372,7 @@ var require_fast_uri = __commonJS({
|
|
|
7372
7372
|
}
|
|
7373
7373
|
target.scheme = base.scheme;
|
|
7374
7374
|
}
|
|
7375
|
-
target.fragment =
|
|
7375
|
+
target.fragment = relative4.fragment;
|
|
7376
7376
|
return target;
|
|
7377
7377
|
}
|
|
7378
7378
|
function equal(uriA, uriB, options) {
|
|
@@ -10570,7 +10570,7 @@ var require_dist = __commonJS({
|
|
|
10570
10570
|
});
|
|
10571
10571
|
|
|
10572
10572
|
// cli.ts
|
|
10573
|
-
import { readFileSync as
|
|
10573
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
10574
10574
|
import { createInterface } from "readline/promises";
|
|
10575
10575
|
|
|
10576
10576
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
@@ -11765,8 +11765,8 @@ function prefixIssues(path, issues) {
|
|
|
11765
11765
|
function unwrapMessage(message) {
|
|
11766
11766
|
return typeof message === "string" ? message : message?.message;
|
|
11767
11767
|
}
|
|
11768
|
-
function finalizeIssue(iss, ctx,
|
|
11769
|
-
const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(
|
|
11768
|
+
function finalizeIssue(iss, ctx, config5) {
|
|
11769
|
+
const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config5.customError?.(iss)) ?? unwrapMessage(config5.localeError?.(iss)) ?? "Invalid input";
|
|
11770
11770
|
const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;
|
|
11771
11771
|
rest.path ?? (rest.path = []);
|
|
11772
11772
|
rest.message = message;
|
|
@@ -30101,7 +30101,7 @@ function tidy(schema) {
|
|
|
30101
30101
|
var isBound = (key) => key === "minimum" || key === "maximum";
|
|
30102
30102
|
|
|
30103
30103
|
// ../lib/agents/catalog.ts
|
|
30104
|
-
var AGENT_KINDS = ["claude", "codex", "antigravity", "vibe", "grok", "cursor", "qwen"];
|
|
30104
|
+
var AGENT_KINDS = ["claude", "codex", "antigravity", "vibe", "grok", "cursor", "qwen", "opencode"];
|
|
30105
30105
|
var DEFAULT_AGENT = "claude";
|
|
30106
30106
|
var AGENTS = {
|
|
30107
30107
|
claude: {
|
|
@@ -30173,6 +30173,18 @@ var AGENTS = {
|
|
|
30173
30173
|
// subcommand lists them, so these are curated.
|
|
30174
30174
|
models: ["qwen3-coder-plus", "qwen3.7-plus", "qwen3.6-plus", "qwen3-max-2026-01-23"],
|
|
30175
30175
|
beta: true
|
|
30176
|
+
},
|
|
30177
|
+
opencode: {
|
|
30178
|
+
kind: "opencode",
|
|
30179
|
+
label: "OpenCode",
|
|
30180
|
+
vendor: "Anomaly",
|
|
30181
|
+
bin: "opencode",
|
|
30182
|
+
install: "npm i -g opencode-ai",
|
|
30183
|
+
docs: "https://opencode.ai/docs/cli/",
|
|
30184
|
+
// Spelled provider/model, and reachable only through a provider OpenCode is signed in to: Zen's
|
|
30185
|
+
// free models refuse a run whose tools are narrowed to the browser.
|
|
30186
|
+
models: ["anthropic/claude-opus-5-5", "anthropic/claude-sonnet-5", "openai/gpt-5.6", "google/gemini-3.1-pro-preview"],
|
|
30187
|
+
beta: true
|
|
30176
30188
|
}
|
|
30177
30189
|
};
|
|
30178
30190
|
var AGENT_LIST = AGENT_KINDS.map((kind) => AGENTS[kind]);
|
|
@@ -30228,12 +30240,12 @@ function assertToolNamesRoundTrip(actionNames) {
|
|
|
30228
30240
|
}
|
|
30229
30241
|
|
|
30230
30242
|
// cli.ts
|
|
30231
|
-
import { basename as basename3, join as
|
|
30243
|
+
import { basename as basename3, join as join23 } from "path";
|
|
30232
30244
|
|
|
30233
30245
|
// agent/agent-skills.ts
|
|
30234
30246
|
import { createHash } from "crypto";
|
|
30235
|
-
import { readFileSync as
|
|
30236
|
-
import { join as
|
|
30247
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
30248
|
+
import { join as join15, sep } from "path";
|
|
30237
30249
|
|
|
30238
30250
|
// ../lib/skills/format.ts
|
|
30239
30251
|
var MAX_BODY_BYTES = 32 * 1024;
|
|
@@ -30333,7 +30345,7 @@ function format(detail) {
|
|
|
30333
30345
|
|
|
30334
30346
|
// agent/runners/index.ts
|
|
30335
30347
|
import { spawn as spawn2 } from "child_process";
|
|
30336
|
-
import { dirname as
|
|
30348
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
30337
30349
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30338
30350
|
|
|
30339
30351
|
// agent/config.ts
|
|
@@ -30393,9 +30405,9 @@ function writeAgentModel(kind, model) {
|
|
|
30393
30405
|
if (kind === "claude") delete next.model;
|
|
30394
30406
|
write(next);
|
|
30395
30407
|
}
|
|
30396
|
-
function write(
|
|
30408
|
+
function write(config5) {
|
|
30397
30409
|
mkdirSync3(stateDir, { recursive: true, mode: 448 });
|
|
30398
|
-
writeFileSync2(configPath, `${JSON.stringify(
|
|
30410
|
+
writeFileSync2(configPath, `${JSON.stringify(config5, null, 2)}
|
|
30399
30411
|
`, { mode: 384 });
|
|
30400
30412
|
}
|
|
30401
30413
|
function rememberExtensionDir(dir) {
|
|
@@ -30786,8 +30798,8 @@ var antigravityRunner = {
|
|
|
30786
30798
|
}
|
|
30787
30799
|
}
|
|
30788
30800
|
};
|
|
30789
|
-
function mcpConfig(
|
|
30790
|
-
const servers =
|
|
30801
|
+
function mcpConfig(server2) {
|
|
30802
|
+
const servers = server2 ? { [MCP_SERVER_NAME]: server2 } : {};
|
|
30791
30803
|
return `${JSON.stringify({ mcpServers: servers }, null, 2)}
|
|
30792
30804
|
`;
|
|
30793
30805
|
}
|
|
@@ -30844,7 +30856,7 @@ var codexRunner = {
|
|
|
30844
30856
|
skillDirs: () => [join7(homedir4(), ".codex", "skills"), join7(homedir4(), ".codex", "prompts")],
|
|
30845
30857
|
stream(context) {
|
|
30846
30858
|
const { settings, mcp, research } = context;
|
|
30847
|
-
const
|
|
30859
|
+
const server2 = `mcp_servers.${MCP_SERVER_NAME}`;
|
|
30848
30860
|
const effort = effortOf(settings, this.efforts);
|
|
30849
30861
|
return {
|
|
30850
30862
|
cwd: this.workspace("run"),
|
|
@@ -30856,16 +30868,16 @@ var codexRunner = {
|
|
|
30856
30868
|
...SANDBOX,
|
|
30857
30869
|
...QUIETED,
|
|
30858
30870
|
"-c",
|
|
30859
|
-
`${
|
|
30871
|
+
`${server2}.command=${tomlString(mcp.command)}`,
|
|
30860
30872
|
"-c",
|
|
30861
|
-
`${
|
|
30873
|
+
`${server2}.args=${tomlArray(mcp.args)}`,
|
|
30862
30874
|
"-c",
|
|
30863
|
-
`${
|
|
30875
|
+
`${server2}.env=${tomlTable(mcp.env)}`,
|
|
30864
30876
|
"-c",
|
|
30865
|
-
`${
|
|
30877
|
+
`${server2}.required=true`,
|
|
30866
30878
|
// Headless Codex refuses an MCP call it would have asked about; the daemon gates these tools itself.
|
|
30867
30879
|
"-c",
|
|
30868
|
-
`${
|
|
30880
|
+
`${server2}.default_tools_approval_mode="approve"`,
|
|
30869
30881
|
"-c",
|
|
30870
30882
|
`developer_instructions=${tomlString(`${context.systemPrompt.trim()}
|
|
30871
30883
|
|
|
@@ -31223,11 +31235,11 @@ function toolOf(event) {
|
|
|
31223
31235
|
const [key, value] = Object.entries(event.tool_call ?? {}).find(([name]) => name.endsWith("ToolCall")) ?? [];
|
|
31224
31236
|
if (!key) return void 0;
|
|
31225
31237
|
const args = value?.args;
|
|
31226
|
-
const
|
|
31227
|
-
if (typeof
|
|
31228
|
-
if (
|
|
31238
|
+
const server2 = args?.["server"];
|
|
31239
|
+
if (typeof server2 === "string") {
|
|
31240
|
+
if (server2 === MCP_SERVER_NAME) return void 0;
|
|
31229
31241
|
const tool = args?.["name"];
|
|
31230
|
-
return `${
|
|
31242
|
+
return `${server2}:${typeof tool === "string" ? tool : key}`;
|
|
31231
31243
|
}
|
|
31232
31244
|
return key.replace(/ToolCall$/, "");
|
|
31233
31245
|
}
|
|
@@ -31432,13 +31444,13 @@ function explain2(message) {
|
|
|
31432
31444
|
}
|
|
31433
31445
|
var oneLine = (message) => message.replace(/\s+/g, " ").slice(0, 240);
|
|
31434
31446
|
var sentence = (message) => /[.!?]$/.test(message.trim()) ? message.trim() : `${message.trim()}.`;
|
|
31435
|
-
function config2(
|
|
31447
|
+
function config2(server2) {
|
|
31436
31448
|
const quote = (value) => JSON.stringify(value);
|
|
31437
|
-
const env = Object.entries(
|
|
31449
|
+
const env = Object.entries(server2.env).map(([name, value]) => `${name} = ${quote(value)}`);
|
|
31438
31450
|
return [
|
|
31439
31451
|
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
31440
|
-
`command = ${quote(
|
|
31441
|
-
`args = [${
|
|
31452
|
+
`command = ${quote(server2.command)}`,
|
|
31453
|
+
`args = [${server2.args.map(quote).join(", ")}]`,
|
|
31442
31454
|
`env = { ${env.join(", ")} }`,
|
|
31443
31455
|
""
|
|
31444
31456
|
].join("\n");
|
|
@@ -31451,11 +31463,215 @@ function lastLine(stdout) {
|
|
|
31451
31463
|
return null;
|
|
31452
31464
|
}
|
|
31453
31465
|
|
|
31454
|
-
// agent/runners/
|
|
31466
|
+
// agent/runners/opencode.ts
|
|
31455
31467
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
31456
31468
|
import { readFileSync as readFileSync5 } from "fs";
|
|
31457
31469
|
import { homedir as homedir7 } from "os";
|
|
31458
|
-
import { join as join10 } from "path";
|
|
31470
|
+
import { dirname as dirname3, join as join10, relative } from "path";
|
|
31471
|
+
var AGENT = "browsentic-contained";
|
|
31472
|
+
var INSTRUCTIONS2 = "instructions.md";
|
|
31473
|
+
var WEB_TOOLS3 = ["webfetch", "websearch"];
|
|
31474
|
+
var RESULT_BYTES = 1e5;
|
|
31475
|
+
var TOOL_TIMEOUT_MS = 30 * 6e4;
|
|
31476
|
+
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.`;
|
|
31477
|
+
var SEALED2 = { OPENCODE_DISABLE_PROJECT_CONFIG: "1", OPENCODE_DISABLE_CLAUDE_CODE: "1", OPENCODE_DISABLE_SHARE: "1" };
|
|
31478
|
+
var sessionsDb = () => join10(stateDir, "agents", "opencode", "sessions.db");
|
|
31479
|
+
var configHome = () => join10(process.env.XDG_CONFIG_HOME || join10(homedir7(), ".config"), "opencode");
|
|
31480
|
+
var INSTALL = "npm i -g opencode-ai";
|
|
31481
|
+
var opencodeRunner = {
|
|
31482
|
+
kind: "opencode",
|
|
31483
|
+
versionArgs: ["--version"],
|
|
31484
|
+
// Passed as --variant, whose names each model defines; a model ignores one it lacks.
|
|
31485
|
+
efforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
|
|
31486
|
+
endsOnExit: true,
|
|
31487
|
+
workspace: (mode) => join10(stateDir, "agents", "opencode", mode),
|
|
31488
|
+
skillDirs: () => [
|
|
31489
|
+
join10(configHome(), "skills"),
|
|
31490
|
+
join10(configHome(), "skill"),
|
|
31491
|
+
join10(homedir7(), ".opencode", "skills"),
|
|
31492
|
+
join10(homedir7(), ".agents", "skills"),
|
|
31493
|
+
join10(homedir7(), ".claude", "skills")
|
|
31494
|
+
],
|
|
31495
|
+
stream(context) {
|
|
31496
|
+
const { settings, research } = context;
|
|
31497
|
+
const base = this.workspace("run");
|
|
31498
|
+
sweepRunDirs(base);
|
|
31499
|
+
const cwd = conversationDir(base, context.conversation ?? context.runId);
|
|
31500
|
+
const allowed = [...context.mcpTools.map((tool) => `${MCP_SERVER_NAME}_${tool}`), ...research ? WEB_TOOLS3 : []];
|
|
31501
|
+
const effort = effortOf(settings, this.efforts);
|
|
31502
|
+
return {
|
|
31503
|
+
cwd,
|
|
31504
|
+
env: {
|
|
31505
|
+
...SEALED2,
|
|
31506
|
+
OPENCODE_DB: sessionsDb(),
|
|
31507
|
+
OPENCODE_CONFIG_CONTENT: config3({
|
|
31508
|
+
mcp: { [MCP_SERVER_NAME]: server(context.mcp) },
|
|
31509
|
+
instructions: [join10(cwd, INSTRUCTIONS2)],
|
|
31510
|
+
permission: { "*": "deny", ...Object.fromEntries(allowed.map((tool) => [tool, "allow"])) }
|
|
31511
|
+
}),
|
|
31512
|
+
BROWSENTIC_AGENT_RUN: context.runId
|
|
31513
|
+
},
|
|
31514
|
+
files: [{ path: INSTRUCTIONS2, content: `${context.systemPrompt.trim()}
|
|
31515
|
+
|
|
31516
|
+
${TRUNCATION}
|
|
31517
|
+
` }],
|
|
31518
|
+
args: [
|
|
31519
|
+
...invocation(),
|
|
31520
|
+
...context.sessionId ? ["--session", context.sessionId] : [],
|
|
31521
|
+
...settings.model ? ["--model", settings.model] : [],
|
|
31522
|
+
...effort ? ["--variant", effort] : [],
|
|
31523
|
+
"--",
|
|
31524
|
+
context.instruction
|
|
31525
|
+
]
|
|
31526
|
+
};
|
|
31527
|
+
},
|
|
31528
|
+
reader() {
|
|
31529
|
+
let spoke = false;
|
|
31530
|
+
let generated = 0;
|
|
31531
|
+
const report = (tokens, sink) => {
|
|
31532
|
+
const made = (tokens.output ?? 0) + (tokens.reasoning ?? 0);
|
|
31533
|
+
const prompt = (tokens.input ?? 0) + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0);
|
|
31534
|
+
generated += made;
|
|
31535
|
+
sink.usage({ contextTokens: prompt + made, outputTokens: generated });
|
|
31536
|
+
};
|
|
31537
|
+
return (line, sink) => {
|
|
31538
|
+
const event = parseJsonLine(line);
|
|
31539
|
+
if (!event) return;
|
|
31540
|
+
if (event.sessionID) sink.session(event.sessionID);
|
|
31541
|
+
const part = event.part;
|
|
31542
|
+
switch (event.type) {
|
|
31543
|
+
case "text":
|
|
31544
|
+
if (!part?.text) return;
|
|
31545
|
+
sink.text(spoke ? `
|
|
31546
|
+
|
|
31547
|
+
${part.text}` : part.text);
|
|
31548
|
+
spoke = true;
|
|
31549
|
+
return;
|
|
31550
|
+
case "tool_use": {
|
|
31551
|
+
const name = part?.tool;
|
|
31552
|
+
if (!name || ownTool3(name)) return;
|
|
31553
|
+
if (WEB_TOOLS3.includes(name)) return sink.tool(part.callID ?? randomUUID5(), name);
|
|
31554
|
+
if (part.state?.status !== "completed") return;
|
|
31555
|
+
return sink.fail(
|
|
31556
|
+
"AGENT_UNSAFE",
|
|
31557
|
+
`OpenCode ran its own ${name} tool in this run, which Browsentic denied, so the run was stopped. Update OpenCode and Browsentic; if it persists, please report it.`
|
|
31558
|
+
);
|
|
31559
|
+
}
|
|
31560
|
+
case "step_finish":
|
|
31561
|
+
if (part?.tokens) report(part.tokens, sink);
|
|
31562
|
+
return;
|
|
31563
|
+
case "error":
|
|
31564
|
+
return sink.fail("AGENT_FAILED", explain3(event.error));
|
|
31565
|
+
}
|
|
31566
|
+
};
|
|
31567
|
+
},
|
|
31568
|
+
json(context) {
|
|
31569
|
+
const { settings, reads } = context;
|
|
31570
|
+
const cwd = this.workspace("task");
|
|
31571
|
+
const permission = { "*": "deny", ...reads ? { read: scratchReads(cwd) } : {} };
|
|
31572
|
+
return {
|
|
31573
|
+
cwd,
|
|
31574
|
+
env: {
|
|
31575
|
+
...SEALED2,
|
|
31576
|
+
OPENCODE_DB: sessionsDb(),
|
|
31577
|
+
// Merged over the user's servers rather than replacing them: it adds none, and the ruleset hides theirs.
|
|
31578
|
+
OPENCODE_CONFIG_CONTENT: config3({ mcp: {}, permission })
|
|
31579
|
+
},
|
|
31580
|
+
args: [...invocation(), ...settings.model ? ["--model", settings.model] : [], "--", context.prompt]
|
|
31581
|
+
};
|
|
31582
|
+
},
|
|
31583
|
+
answer(stdout) {
|
|
31584
|
+
let said = [];
|
|
31585
|
+
for (const line of stdout.split("\n")) {
|
|
31586
|
+
const event = parseJsonLine(line);
|
|
31587
|
+
if (event?.type === "error") return { error: explain3(event.error) };
|
|
31588
|
+
if (event?.type === "step_start") said = [];
|
|
31589
|
+
if (event?.type === "text" && event.part?.text) said.push(event.part.text);
|
|
31590
|
+
}
|
|
31591
|
+
return { text: said.length ? said.join("\n\n") : void 0 };
|
|
31592
|
+
},
|
|
31593
|
+
hint(stderrTail) {
|
|
31594
|
+
if (/Unknown arguments?|Not enough arguments|Invalid values/i.test(stderrTail)) {
|
|
31595
|
+
return `Your OpenCode does not understand the flags Browsentic uses. Update it (${INSTALL}), then try again. (${stderrTail.trim()})`;
|
|
31596
|
+
}
|
|
31597
|
+
return null;
|
|
31598
|
+
},
|
|
31599
|
+
async check() {
|
|
31600
|
+
if (process.env.OPENCODE_API_KEY || process.env.OPENCODE_AUTH_CONTENT || signedIn2() || declaresProvider()) return null;
|
|
31601
|
+
return {
|
|
31602
|
+
code: "AGENT_NEEDS_PERMISSION",
|
|
31603
|
+
message: "OpenCode is signed in to no model provider, and its free OpenCode Zen models refuse a run whose tools Browsentic has narrowed to the browser.",
|
|
31604
|
+
fix: "opencode auth login"
|
|
31605
|
+
};
|
|
31606
|
+
}
|
|
31607
|
+
};
|
|
31608
|
+
var invocation = () => ["run", "--format", "json", "--pure", "--agent", AGENT];
|
|
31609
|
+
var ownTool3 = (name) => name.startsWith(`${MCP_SERVER_NAME}_`);
|
|
31610
|
+
var server = (mcp) => ({
|
|
31611
|
+
type: "local",
|
|
31612
|
+
command: [mcp.command, ...mcp.args],
|
|
31613
|
+
environment: mcp.env,
|
|
31614
|
+
enabled: true,
|
|
31615
|
+
timeout: TOOL_TIMEOUT_MS
|
|
31616
|
+
});
|
|
31617
|
+
function config3({ mcp, instructions, permission }) {
|
|
31618
|
+
return JSON.stringify({
|
|
31619
|
+
share: "disabled",
|
|
31620
|
+
autoupdate: false,
|
|
31621
|
+
snapshot: false,
|
|
31622
|
+
tool_output: { max_bytes: RESULT_BYTES, max_lines: RESULT_BYTES },
|
|
31623
|
+
mcp,
|
|
31624
|
+
...instructions ? { instructions } : {},
|
|
31625
|
+
agent: { title: { disable: true }, [AGENT]: { mode: "primary", permission } }
|
|
31626
|
+
});
|
|
31627
|
+
}
|
|
31628
|
+
function scratchReads(workspace) {
|
|
31629
|
+
const scratch = join10(workspace, "tmp");
|
|
31630
|
+
const rules = { "*": "deny" };
|
|
31631
|
+
for (let root = dirname3(workspace); ; root = dirname3(root)) {
|
|
31632
|
+
rules[`${relative(root, scratch)}/*`] = "allow";
|
|
31633
|
+
if (root === dirname3(root)) return rules;
|
|
31634
|
+
}
|
|
31635
|
+
}
|
|
31636
|
+
function explain3(failure2) {
|
|
31637
|
+
const message = oneLine2(failure2?.data?.message ?? failure2?.name ?? "OpenCode reported an error");
|
|
31638
|
+
const status2 = failure2?.data?.statusCode;
|
|
31639
|
+
if (failure2?.data?.responseBody?.includes("FreeTierError")) {
|
|
31640
|
+
return `OpenCode Zen's free models refuse a run whose tools Browsentic has narrowed to the browser. Run "opencode auth login" to sign in to a provider, then pick one of its models in the Browsentic popup. (${message})`;
|
|
31641
|
+
}
|
|
31642
|
+
if (failure2?.name === "ProviderAuthError" || status2 === 401 || status2 === 403) {
|
|
31643
|
+
return `${message} If that is a login problem, run "opencode auth login" \u2014 a key kept only in an environment variable is not passed to a Browsentic run.`;
|
|
31644
|
+
}
|
|
31645
|
+
if (status2 === 429) return `The provider behind OpenCode is rate-limiting this account. Wait and try again. (${message})`;
|
|
31646
|
+
if (failure2?.name === "UnknownError") {
|
|
31647
|
+
return `OpenCode could not start this turn, most often because it does not know the model. Pick one in the Browsentic popup as "opencode models" lists it \u2014 provider/model \u2014 then try again. (${message})`;
|
|
31648
|
+
}
|
|
31649
|
+
return message;
|
|
31650
|
+
}
|
|
31651
|
+
var oneLine2 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
31652
|
+
var dataHome = () => join10(process.env.XDG_DATA_HOME || join10(homedir7(), ".local", "share"), "opencode");
|
|
31653
|
+
function signedIn2() {
|
|
31654
|
+
try {
|
|
31655
|
+
return Object.keys(JSON.parse(readFileSync5(join10(dataHome(), "auth.json"), "utf8"))).length > 0;
|
|
31656
|
+
} catch (error51) {
|
|
31657
|
+
return error51.code !== "ENOENT";
|
|
31658
|
+
}
|
|
31659
|
+
}
|
|
31660
|
+
function declaresProvider() {
|
|
31661
|
+
return ["opencode.json", "opencode.jsonc", "config.json"].some((name) => {
|
|
31662
|
+
try {
|
|
31663
|
+
return /"provider"\s*:/.test(readFileSync5(join10(configHome(), name), "utf8"));
|
|
31664
|
+
} catch {
|
|
31665
|
+
return false;
|
|
31666
|
+
}
|
|
31667
|
+
});
|
|
31668
|
+
}
|
|
31669
|
+
|
|
31670
|
+
// agent/runners/qwen.ts
|
|
31671
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
31672
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
31673
|
+
import { homedir as homedir8 } from "os";
|
|
31674
|
+
import { join as join11 } from "path";
|
|
31459
31675
|
var MACHINE = ["Bash", "exec", "Edit", "Read", "zoom_image", "monitor", "lsp", "save_memory"];
|
|
31460
31676
|
var ESCAPES = [
|
|
31461
31677
|
"skill",
|
|
@@ -31476,13 +31692,13 @@ var ESCAPES = [
|
|
|
31476
31692
|
"image_gen",
|
|
31477
31693
|
"read_mcp_resource"
|
|
31478
31694
|
];
|
|
31479
|
-
var
|
|
31695
|
+
var WEB_TOOLS4 = ["web_search", "web_fetch"];
|
|
31480
31696
|
var READ_TOOL2 = "read_file";
|
|
31481
31697
|
var OTHER_READS = ["grep_search", "glob", "list_directory"];
|
|
31482
31698
|
var APPROVAL = "default";
|
|
31483
31699
|
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_)/;
|
|
31484
|
-
var qwenHome = () => process.env.QWEN_HOME ||
|
|
31485
|
-
var
|
|
31700
|
+
var qwenHome = () => process.env.QWEN_HOME || join11(homedir8(), ".qwen");
|
|
31701
|
+
var INSTALL2 = "npm i -g @qwen-code/qwen-code";
|
|
31486
31702
|
var AUTH_KEYS = ["QWEN_API_KEY", "OPENAI_API_KEY", "DASHSCOPE_API_KEY"];
|
|
31487
31703
|
var AUTH_PREFIX = /^(QWEN_|DASHSCOPE_|BAILIAN_|OPENAI_).*(API_KEY|TOKEN)$/;
|
|
31488
31704
|
var qwenRunner = {
|
|
@@ -31492,8 +31708,8 @@ var qwenRunner = {
|
|
|
31492
31708
|
efforts: [],
|
|
31493
31709
|
// Sessions are filed under ~/.qwen/projects/<sanitized-cwd>, so a resume only finds the
|
|
31494
31710
|
// conversation it began in when this stays put.
|
|
31495
|
-
workspace: (mode) =>
|
|
31496
|
-
skillDirs: () => [
|
|
31711
|
+
workspace: (mode) => join11(stateDir, "agents", "qwen", mode),
|
|
31712
|
+
skillDirs: () => [join11(qwenHome(), "skills"), join11(homedir8(), ".agents", "skills")],
|
|
31497
31713
|
stream(context) {
|
|
31498
31714
|
const { settings, research } = context;
|
|
31499
31715
|
return {
|
|
@@ -31519,14 +31735,14 @@ var qwenRunner = {
|
|
|
31519
31735
|
// to be auto-approved by name. One entry covers every tool the server offers.
|
|
31520
31736
|
"--allowed-tools",
|
|
31521
31737
|
`mcp__${MCP_SERVER_NAME}`,
|
|
31522
|
-
...research ?
|
|
31738
|
+
...research ? WEB_TOOLS4 : [],
|
|
31523
31739
|
"--exclude-tools",
|
|
31524
31740
|
...MACHINE,
|
|
31525
31741
|
...ESCAPES,
|
|
31526
|
-
...research ? [] :
|
|
31742
|
+
...research ? [] : WEB_TOOLS4,
|
|
31527
31743
|
"--append-system-prompt",
|
|
31528
31744
|
context.systemPrompt,
|
|
31529
|
-
...context.sessionId ? ["--resume", context.sessionId] : ["--session-id",
|
|
31745
|
+
...context.sessionId ? ["--resume", context.sessionId] : ["--session-id", randomUUID6()],
|
|
31530
31746
|
...settings.model ? ["--model", settings.model] : []
|
|
31531
31747
|
]
|
|
31532
31748
|
};
|
|
@@ -31562,7 +31778,7 @@ var qwenRunner = {
|
|
|
31562
31778
|
}
|
|
31563
31779
|
if (event?.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
31564
31780
|
const name = event.content_block.name ?? "tool";
|
|
31565
|
-
if (
|
|
31781
|
+
if (WEB_TOOLS4.includes(name)) sink.tool(event.content_block.id ?? randomUUID6(), name);
|
|
31566
31782
|
}
|
|
31567
31783
|
return;
|
|
31568
31784
|
}
|
|
@@ -31576,7 +31792,7 @@ var qwenRunner = {
|
|
|
31576
31792
|
if (message.is_error) {
|
|
31577
31793
|
return sink.fail(
|
|
31578
31794
|
"AGENT_FAILED",
|
|
31579
|
-
|
|
31795
|
+
explain4(message.error?.message) ?? (message.result?.trim() || "Qwen Code reported an error")
|
|
31580
31796
|
);
|
|
31581
31797
|
}
|
|
31582
31798
|
return sink.done(message.subtype === "success" ? "end_turn" : message.subtype || "end_turn");
|
|
@@ -31604,7 +31820,7 @@ var qwenRunner = {
|
|
|
31604
31820
|
"--exclude-tools",
|
|
31605
31821
|
...denied,
|
|
31606
31822
|
...ESCAPES,
|
|
31607
|
-
...
|
|
31823
|
+
...WEB_TOOLS4,
|
|
31608
31824
|
...settings.model ? ["--model", settings.model] : []
|
|
31609
31825
|
]
|
|
31610
31826
|
};
|
|
@@ -31613,7 +31829,7 @@ var qwenRunner = {
|
|
|
31613
31829
|
const answer = lastResult2(stdout);
|
|
31614
31830
|
if (!answer) return {};
|
|
31615
31831
|
if (answer.is_error) {
|
|
31616
|
-
return { error:
|
|
31832
|
+
return { error: explain4(answer.error?.message) ?? (answer.result?.trim() || "Qwen Code reported an error") };
|
|
31617
31833
|
}
|
|
31618
31834
|
return { text: typeof answer.result === "string" ? answer.result : void 0 };
|
|
31619
31835
|
},
|
|
@@ -31626,7 +31842,7 @@ var qwenRunner = {
|
|
|
31626
31842
|
return `Qwen Code refused the session id Browsentic minted. This is a bug in Browsentic \u2014 please report it. (${tail})`;
|
|
31627
31843
|
}
|
|
31628
31844
|
if (/unknown argument|unknown option|invalid values|not a valid choice/i.test(tail)) {
|
|
31629
|
-
return `Your Qwen Code does not understand the flags Browsentic uses. Update it (${
|
|
31845
|
+
return `Your Qwen Code does not understand the flags Browsentic uses. Update it (${INSTALL2}), then try again. (${tail})`;
|
|
31630
31846
|
}
|
|
31631
31847
|
return null;
|
|
31632
31848
|
},
|
|
@@ -31649,7 +31865,7 @@ function escaped(init) {
|
|
|
31649
31865
|
if (live.length) {
|
|
31650
31866
|
return `Qwen Code registered ${live.join(", ")} for this run, which Browsentic denied, so the run was stopped before the model saw them. Update Qwen Code and Browsentic; if it persists, please report it.`;
|
|
31651
31867
|
}
|
|
31652
|
-
const others = (init.mcp_servers ?? []).map((
|
|
31868
|
+
const others = (init.mcp_servers ?? []).map((server2) => server2.name).filter((name) => name && name !== MCP_SERVER_NAME);
|
|
31653
31869
|
if (others.length) {
|
|
31654
31870
|
return `Qwen Code loaded the MCP server${others.length > 1 ? "s" : ""} ${others.join(", ")} beside Browsentic's own, which would reach the browser outside this run's gate, so the run was stopped. Update Qwen Code and Browsentic; if it persists, please report it.`;
|
|
31655
31871
|
}
|
|
@@ -31658,27 +31874,27 @@ function escaped(init) {
|
|
|
31658
31874
|
}
|
|
31659
31875
|
return void 0;
|
|
31660
31876
|
}
|
|
31661
|
-
function
|
|
31877
|
+
function explain4(message) {
|
|
31662
31878
|
if (!message) return void 0;
|
|
31663
31879
|
if (/No auth type is selected|API key not found/i.test(message)) {
|
|
31664
|
-
return `Qwen Code has no model provider configured. Run "qwen" and use /auth, or export OPENAI_API_KEY with OPENAI_BASE_URL, then try again. (${
|
|
31880
|
+
return `Qwen Code has no model provider configured. Run "qwen" and use /auth, or export OPENAI_API_KEY with OPENAI_BASE_URL, then try again. (${oneLine3(message)})`;
|
|
31665
31881
|
}
|
|
31666
31882
|
if (/qwen-oauth|Qwen OAuth/i.test(message)) {
|
|
31667
|
-
return `Qwen OAuth's free tier was discontinued, so its requests are rejected. Configure another provider with /auth. (${
|
|
31883
|
+
return `Qwen OAuth's free tier was discontinued, so its requests are rejected. Configure another provider with /auth. (${oneLine3(message)})`;
|
|
31668
31884
|
}
|
|
31669
31885
|
if (/rate limit|429|quota/i.test(message)) {
|
|
31670
|
-
return `The provider behind Qwen Code is rate-limiting this account. Wait and try again. (${
|
|
31886
|
+
return `The provider behind Qwen Code is rate-limiting this account. Wait and try again. (${oneLine3(message)})`;
|
|
31671
31887
|
}
|
|
31672
31888
|
if (/unknown model|model not found/i.test(message)) {
|
|
31673
|
-
return `${
|
|
31889
|
+
return `${oneLine3(message)} Pick another model for Qwen Code in the Browsentic popup, then try again.`;
|
|
31674
31890
|
}
|
|
31675
|
-
return
|
|
31891
|
+
return oneLine3(message);
|
|
31676
31892
|
}
|
|
31677
|
-
var
|
|
31893
|
+
var oneLine3 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
31678
31894
|
function configured() {
|
|
31679
31895
|
let parsed2;
|
|
31680
31896
|
try {
|
|
31681
|
-
parsed2 = JSON.parse(
|
|
31897
|
+
parsed2 = JSON.parse(readFileSync6(join11(qwenHome(), "settings.json"), "utf8"));
|
|
31682
31898
|
} catch (error51) {
|
|
31683
31899
|
return error51.code !== "ENOENT";
|
|
31684
31900
|
}
|
|
@@ -31699,34 +31915,34 @@ function lastResult2(stdout) {
|
|
|
31699
31915
|
}
|
|
31700
31916
|
|
|
31701
31917
|
// agent/runners/vibe.ts
|
|
31702
|
-
import { homedir as
|
|
31703
|
-
import { join as
|
|
31918
|
+
import { homedir as homedir9 } from "os";
|
|
31919
|
+
import { join as join12 } from "path";
|
|
31704
31920
|
var CONFIG2 = ".vibe/config.toml";
|
|
31705
|
-
var
|
|
31921
|
+
var INSTRUCTIONS3 = "AGENTS.md";
|
|
31706
31922
|
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";
|
|
31707
|
-
var
|
|
31923
|
+
var WEB_TOOLS5 = ["web_search", "web_fetch"];
|
|
31708
31924
|
var READ_TOOL3 = "read_file";
|
|
31709
31925
|
var NO_TOOLS = "re:^$";
|
|
31710
31926
|
var PROFILE = "ask";
|
|
31711
|
-
var vibeHome = () => process.env.VIBE_HOME ||
|
|
31927
|
+
var vibeHome = () => process.env.VIBE_HOME || join12(homedir9(), ".vibe");
|
|
31712
31928
|
var vibeRunner = {
|
|
31713
31929
|
kind: "vibe",
|
|
31714
31930
|
versionArgs: ["--version"],
|
|
31715
31931
|
efforts: [],
|
|
31716
31932
|
endsOnExit: true,
|
|
31717
|
-
workspace: (mode) =>
|
|
31718
|
-
skillDirs: () => [
|
|
31933
|
+
workspace: (mode) => join12(stateDir, "agents", "vibe", mode),
|
|
31934
|
+
skillDirs: () => [join12(vibeHome(), "skills"), join12(homedir9(), ".agents", "skills")],
|
|
31719
31935
|
stream(context) {
|
|
31720
31936
|
const base = this.workspace("run");
|
|
31721
31937
|
sweepRunDirs(base);
|
|
31722
|
-
const builtins = context.research ?
|
|
31938
|
+
const builtins = context.research ? WEB_TOOLS5 : [];
|
|
31723
31939
|
const granted = [...context.mcpTools.map((tool) => `${MCP_SERVER_NAME}_${tool}`), ...builtins];
|
|
31724
31940
|
return {
|
|
31725
31941
|
cwd: conversationDir(base, context.conversation ?? context.runId),
|
|
31726
31942
|
env: { BROWSENTIC_AGENT_RUN: context.runId },
|
|
31727
31943
|
files: [
|
|
31728
|
-
{ path: CONFIG2, content:
|
|
31729
|
-
{ path:
|
|
31944
|
+
{ path: CONFIG2, content: config4(context.settings.model, context.mcp, granted) },
|
|
31945
|
+
{ path: INSTRUCTIONS3, content: `${context.systemPrompt.trim()}
|
|
31730
31946
|
` }
|
|
31731
31947
|
],
|
|
31732
31948
|
args: [
|
|
@@ -31760,7 +31976,7 @@ ${text3}` : text3);
|
|
|
31760
31976
|
return;
|
|
31761
31977
|
}
|
|
31762
31978
|
const tool = entry.type === "effect" ? entry.detail?.toolName : void 0;
|
|
31763
|
-
if (tool && entry.id && !
|
|
31979
|
+
if (tool && entry.id && !ownTool4(tool)) sink.tool(entry.id, tool);
|
|
31764
31980
|
};
|
|
31765
31981
|
},
|
|
31766
31982
|
json(context) {
|
|
@@ -31768,8 +31984,8 @@ ${text3}` : text3);
|
|
|
31768
31984
|
return {
|
|
31769
31985
|
cwd: this.workspace("task"),
|
|
31770
31986
|
files: [
|
|
31771
|
-
{ path: CONFIG2, content:
|
|
31772
|
-
{ path:
|
|
31987
|
+
{ path: CONFIG2, content: config4(context.settings.model, null, allowed) },
|
|
31988
|
+
{ path: INSTRUCTIONS3, content: TASK_INSTRUCTIONS2 }
|
|
31773
31989
|
],
|
|
31774
31990
|
args: [
|
|
31775
31991
|
"--prompt",
|
|
@@ -31791,7 +32007,7 @@ ${text3}` : text3);
|
|
|
31791
32007
|
},
|
|
31792
32008
|
hint(stderrTail) {
|
|
31793
32009
|
if (/Missing \w+ environment variable/i.test(stderrTail)) {
|
|
31794
|
-
return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${
|
|
32010
|
+
return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${join12(vibeHome(), ".env")}, then try again. (${stderrTail.trim()})`;
|
|
31795
32011
|
}
|
|
31796
32012
|
if (/unrecognized arguments|invalid choice/i.test(stderrTail)) {
|
|
31797
32013
|
return `Your Mistral Vibe does not understand the flags Browsentic uses. Update it, then try again. (${stderrTail.trim()})`;
|
|
@@ -31801,20 +32017,20 @@ ${text3}` : text3);
|
|
|
31801
32017
|
};
|
|
31802
32018
|
var enabling = (patterns) => patterns.flatMap((pattern) => ["--enabled-tools", pattern]);
|
|
31803
32019
|
var textOf2 = (entry) => (entry.content ?? []).filter((block) => block.type === "text" && block.text).map((block) => block.text).join("\n\n");
|
|
31804
|
-
var
|
|
31805
|
-
function
|
|
32020
|
+
var ownTool4 = (name) => name.startsWith(`${MCP_SERVER_NAME}_`);
|
|
32021
|
+
function config4(model, server2, granted) {
|
|
31806
32022
|
const quote = (value) => JSON.stringify(value);
|
|
31807
32023
|
const lines = model ? [`active_model = ${quote(model)}`, ""] : [];
|
|
31808
|
-
if (
|
|
32024
|
+
if (server2) {
|
|
31809
32025
|
lines.push(
|
|
31810
32026
|
"[[mcp_servers]]",
|
|
31811
32027
|
`name = ${quote(MCP_SERVER_NAME)}`,
|
|
31812
32028
|
'transport = "stdio"',
|
|
31813
|
-
`command = [${quote(
|
|
31814
|
-
`args = [${
|
|
32029
|
+
`command = [${quote(server2.command)}]`,
|
|
32030
|
+
`args = [${server2.args.map(quote).join(", ")}]`,
|
|
31815
32031
|
"",
|
|
31816
32032
|
"[mcp_servers.env]",
|
|
31817
|
-
...Object.entries(
|
|
32033
|
+
...Object.entries(server2.env).map(([name, value]) => `${name} = ${quote(value)}`),
|
|
31818
32034
|
""
|
|
31819
32035
|
);
|
|
31820
32036
|
}
|
|
@@ -31830,26 +32046,27 @@ var RUNNERS = {
|
|
|
31830
32046
|
vibe: vibeRunner,
|
|
31831
32047
|
grok: grokRunner,
|
|
31832
32048
|
cursor: cursorRunner,
|
|
31833
|
-
qwen: qwenRunner
|
|
32049
|
+
qwen: qwenRunner,
|
|
32050
|
+
opencode: opencodeRunner
|
|
31834
32051
|
};
|
|
31835
|
-
var cliPath =
|
|
32052
|
+
var cliPath = join13(dirname4(fileURLToPath2(import.meta.url)), "cli.js");
|
|
31836
32053
|
|
|
31837
32054
|
// agent/skills.ts
|
|
31838
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31839
|
-
import { homedir as
|
|
31840
|
-
import { dirname as
|
|
32055
|
+
import { existsSync as existsSync3, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
|
|
32056
|
+
import { homedir as homedir10 } from "os";
|
|
32057
|
+
import { dirname as dirname5, isAbsolute, join as join14 } from "path";
|
|
31841
32058
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31842
|
-
var bundledDir =
|
|
31843
|
-
var userDir2 =
|
|
32059
|
+
var bundledDir = join14(dirname5(fileURLToPath3(import.meta.url)), "..", "skills");
|
|
32060
|
+
var userDir2 = join14(stateDir, "skills");
|
|
31844
32061
|
function uploadedSkillsDir() {
|
|
31845
32062
|
const configured2 = readAgentConfig().skillsDir;
|
|
31846
32063
|
if (typeof configured2 === "string" && configured2.trim()) return expandHome(configured2.trim());
|
|
31847
|
-
return
|
|
32064
|
+
return join14(homedir10(), "browsentic", "skills");
|
|
31848
32065
|
}
|
|
31849
32066
|
function expandHome(p) {
|
|
31850
|
-
if (p === "~") return
|
|
31851
|
-
if (p.startsWith("~/")) return
|
|
31852
|
-
return isAbsolute(p) ? p :
|
|
32067
|
+
if (p === "~") return homedir10();
|
|
32068
|
+
if (p.startsWith("~/")) return join14(homedir10(), p.slice(2));
|
|
32069
|
+
return isAbsolute(p) ? p : join14(homedir10(), p);
|
|
31853
32070
|
}
|
|
31854
32071
|
function skillDirs() {
|
|
31855
32072
|
return [
|
|
@@ -31875,7 +32092,7 @@ function readDir(dir, source) {
|
|
|
31875
32092
|
try {
|
|
31876
32093
|
const entries = readdirSync2(dir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith("."));
|
|
31877
32094
|
files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name);
|
|
31878
|
-
directories = entries.filter((entry) => entry.isDirectory() && existsSync3(
|
|
32095
|
+
directories = entries.filter((entry) => entry.isDirectory() && existsSync3(join14(dir, entry.name, SKILL_FILE))).map((entry) => entry.name);
|
|
31879
32096
|
} catch {
|
|
31880
32097
|
return [];
|
|
31881
32098
|
}
|
|
@@ -31886,13 +32103,13 @@ function readDir(dir, source) {
|
|
|
31886
32103
|
log(`skill "${name}" exists as both ${name}.md and ${name}/${SKILL_FILE} in ${dir}; using the file`);
|
|
31887
32104
|
continue;
|
|
31888
32105
|
}
|
|
31889
|
-
push(
|
|
32106
|
+
push(join14(dir, name, SKILL_FILE), name);
|
|
31890
32107
|
}
|
|
31891
|
-
for (const file2 of files) push(
|
|
32108
|
+
for (const file2 of files) push(join14(dir, file2), file2.replace(/\.md$/, ""));
|
|
31892
32109
|
return skills;
|
|
31893
32110
|
function push(path, fallbackName) {
|
|
31894
32111
|
try {
|
|
31895
|
-
const skill = parseSkill(
|
|
32112
|
+
const skill = parseSkill(readFileSync7(path, "utf8"), fallbackName, source);
|
|
31896
32113
|
if (!skill.body.trim()) log(`skill ${path} has no body; ignoring`);
|
|
31897
32114
|
else {
|
|
31898
32115
|
if (skill.category === "site-exploration" && !skill.domains.length) {
|
|
@@ -31929,8 +32146,8 @@ var MAX_SKILL_BYTES = 48 * 1024;
|
|
|
31929
32146
|
var TTL_MS = 3e4;
|
|
31930
32147
|
var cached2 = null;
|
|
31931
32148
|
var known = /* @__PURE__ */ new Map();
|
|
31932
|
-
function agentSkills(
|
|
31933
|
-
const agent =
|
|
32149
|
+
function agentSkills(config5, { refresh = false } = {}) {
|
|
32150
|
+
const agent = config5.agent;
|
|
31934
32151
|
const dirs = RUNNERS[agent].skillDirs?.() ?? [];
|
|
31935
32152
|
const signature = dirs.join("\n");
|
|
31936
32153
|
if (!refresh && cached2 && cached2.agent === agent && cached2.dirs === signature && Date.now() - cached2.at < TTL_MS) {
|
|
@@ -31957,11 +32174,11 @@ function scan(dir, agent, out) {
|
|
|
31957
32174
|
return;
|
|
31958
32175
|
}
|
|
31959
32176
|
for (const entry of entries) {
|
|
31960
|
-
const path = entry.name.endsWith(".md") ?
|
|
32177
|
+
const path = entry.name.endsWith(".md") ? join15(dir, entry.name) : join15(dir, entry.name, SKILL_FILE);
|
|
31961
32178
|
try {
|
|
31962
32179
|
const stats = statSync3(path);
|
|
31963
32180
|
if (!stats.isFile() || stats.size > MAX_SKILL_BYTES) continue;
|
|
31964
|
-
const { fields, body } = splitFrontMatter(
|
|
32181
|
+
const { fields, body } = splitFrontMatter(readFileSync8(path, "utf8"));
|
|
31965
32182
|
if (!body.trim()) continue;
|
|
31966
32183
|
const name = clean(unquote(fields.name) || entry.name.replace(/\.md$/, ""), MAX_NAME);
|
|
31967
32184
|
if (!name) continue;
|
|
@@ -31982,12 +32199,12 @@ function idOf(path) {
|
|
|
31982
32199
|
}
|
|
31983
32200
|
|
|
31984
32201
|
// agent/approvals.ts
|
|
31985
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as
|
|
31986
|
-
import { join as
|
|
31987
|
-
var approvalsPath =
|
|
32202
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
32203
|
+
import { join as join16 } from "path";
|
|
32204
|
+
var approvalsPath = join16(stateDir, "approvals.json");
|
|
31988
32205
|
function read() {
|
|
31989
32206
|
try {
|
|
31990
|
-
const parsed2 = JSON.parse(
|
|
32207
|
+
const parsed2 = JSON.parse(readFileSync9(approvalsPath, "utf8"));
|
|
31991
32208
|
if (!Array.isArray(parsed2.grants)) return [];
|
|
31992
32209
|
return parsed2.grants.filter(
|
|
31993
32210
|
(grant) => !!grant && typeof grant.action === "string" && typeof grant.host === "string" && typeof grant.at === "string"
|
|
@@ -32013,21 +32230,21 @@ function forgetGrants(host) {
|
|
|
32013
32230
|
}
|
|
32014
32231
|
|
|
32015
32232
|
// downloads.ts
|
|
32016
|
-
import { randomUUID as
|
|
32233
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
32017
32234
|
import {
|
|
32018
32235
|
chmodSync as chmodSync3,
|
|
32019
32236
|
copyFileSync,
|
|
32020
32237
|
existsSync as existsSync4,
|
|
32021
32238
|
mkdirSync as mkdirSync6,
|
|
32022
|
-
readFileSync as
|
|
32239
|
+
readFileSync as readFileSync10,
|
|
32023
32240
|
renameSync as renameSync2,
|
|
32024
32241
|
rmSync as rmSync3,
|
|
32025
32242
|
statSync as statSync4,
|
|
32026
32243
|
unlinkSync,
|
|
32027
32244
|
writeFileSync as writeFileSync5
|
|
32028
32245
|
} from "fs";
|
|
32029
|
-
import { homedir as
|
|
32030
|
-
import { basename, isAbsolute as isAbsolute2, join as
|
|
32246
|
+
import { homedir as homedir11 } from "os";
|
|
32247
|
+
import { basename, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
32031
32248
|
|
|
32032
32249
|
// ../lib/downloads/limits.ts
|
|
32033
32250
|
var MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
@@ -32471,8 +32688,8 @@ var DEFAULT_FENCE = {
|
|
|
32471
32688
|
// image-specific renderer instead.
|
|
32472
32689
|
except: ["page.closeTab", "page.stopMonitor", "page.screenshot"]
|
|
32473
32690
|
};
|
|
32474
|
-
function policyFrom(
|
|
32475
|
-
const overrides =
|
|
32691
|
+
function policyFrom(config5 = {}, requireApproval = [SUBMIT_ACTION]) {
|
|
32692
|
+
const overrides = config5.rules ?? {};
|
|
32476
32693
|
const rules = DEFAULT_RULES.map((rule) => {
|
|
32477
32694
|
const legacy = rule.id === "form-submission" && !requireApproval.includes(SUBMIT_ACTION) ? "allow" : rule.effect;
|
|
32478
32695
|
return { ...rule, effect: overrides[rule.id] ?? legacy };
|
|
@@ -32480,9 +32697,9 @@ function policyFrom(config4 = {}, requireApproval = [SUBMIT_ACTION]) {
|
|
|
32480
32697
|
return {
|
|
32481
32698
|
rules,
|
|
32482
32699
|
requireApproval,
|
|
32483
|
-
unattended:
|
|
32484
|
-
urlPayloadBytes: typeof
|
|
32485
|
-
fence:
|
|
32700
|
+
unattended: config5.unattended === "allow" ? "allow" : "deny",
|
|
32701
|
+
urlPayloadBytes: typeof config5.urlPayloadBytes === "number" && config5.urlPayloadBytes >= 0 ? config5.urlPayloadBytes : DEFAULT_URL_PAYLOAD_BYTES,
|
|
32702
|
+
fence: config5.fence === false ? { ...DEFAULT_FENCE, enabled: false } : DEFAULT_FENCE
|
|
32486
32703
|
};
|
|
32487
32704
|
}
|
|
32488
32705
|
var POLICY = policyFrom();
|
|
@@ -32529,6 +32746,8 @@ var RULE_IDS = new Set(DEFAULT_RULES.map((rule) => rule.id));
|
|
|
32529
32746
|
var NEVER2 = ["Bash", "Edit", "Write", "NotebookEdit", "Glob", "Grep", "Task"];
|
|
32530
32747
|
var NEVER_QWEN = ["Bash", "exec", "Edit", "agent", "skill", "monitor"];
|
|
32531
32748
|
var GROK_SEALED = { GROK_MEMORY: "0", GROK_CLAUDE_MCPS_ENABLED: "false", GROK_CURSOR_MCPS_ENABLED: "false" };
|
|
32749
|
+
var OPENCODE_SEALED = { OPENCODE_DISABLE_PROJECT_CONFIG: "1", OPENCODE_DISABLE_CLAUDE_CODE: "1", OPENCODE_DISABLE_SHARE: "1" };
|
|
32750
|
+
var OPENCODE_CONFIG = ['"*":"deny"', '"share":"disabled"'];
|
|
32532
32751
|
var CONTAINMENT = {
|
|
32533
32752
|
claude: {
|
|
32534
32753
|
localTools: "allowlist",
|
|
@@ -32698,24 +32917,55 @@ var CONTAINMENT = {
|
|
|
32698
32917
|
denies: { flag: "--exclude-tools", tools: NEVER_QWEN },
|
|
32699
32918
|
files: []
|
|
32700
32919
|
}
|
|
32920
|
+
},
|
|
32921
|
+
opencode: {
|
|
32922
|
+
localTools: "allowlist",
|
|
32923
|
+
// A key in ANTHROPIC_* or OPENAI_* would be one of a dozen providers OpenCode reads; `opencode auth login` keeps them in a file.
|
|
32924
|
+
keepsEnv: ["OPENCODE_"],
|
|
32925
|
+
// `--auto` approves whatever is not denied, `--attach` runs the turn in a server started with someone
|
|
32926
|
+
// else's config, `--dir` moves it out of the workspace, and `--share` publishes it.
|
|
32927
|
+
forbidden: [/^--auto$/i, /^--attach/i, /^--dir/i, /^--share$/i],
|
|
32928
|
+
note: 'a per-run permission ruleset opening on "*": "deny", which OpenCode applies after the user\u2019s own rules, so the model is offered no tool that was not named \u2014 built-in, custom, or another MCP server\u2019s; external plugins and project config stay off, but MCP servers the user gave OpenCode itself still start, with their tools hidden',
|
|
32929
|
+
run: {
|
|
32930
|
+
// Plugins run inside OpenCode and can add tools or answer its permission prompts.
|
|
32931
|
+
required: ["--pure"],
|
|
32932
|
+
pairs: [
|
|
32933
|
+
["--agent", "browsentic-contained"],
|
|
32934
|
+
["--format", "json"]
|
|
32935
|
+
],
|
|
32936
|
+
env: OPENCODE_SEALED,
|
|
32937
|
+
envContains: { OPENCODE_CONFIG_CONTENT: OPENCODE_CONFIG },
|
|
32938
|
+
files: ["instructions.md"]
|
|
32939
|
+
},
|
|
32940
|
+
task: {
|
|
32941
|
+
required: ["--pure"],
|
|
32942
|
+
pairs: [
|
|
32943
|
+
["--agent", "browsentic-contained"],
|
|
32944
|
+
["--format", "json"]
|
|
32945
|
+
],
|
|
32946
|
+
env: OPENCODE_SEALED,
|
|
32947
|
+
// An empty map adds no server of ours, so a one-shot has no way to the browser.
|
|
32948
|
+
envContains: { OPENCODE_CONFIG_CONTENT: [...OPENCODE_CONFIG, '"mcp":{}'] },
|
|
32949
|
+
files: []
|
|
32950
|
+
}
|
|
32701
32951
|
}
|
|
32702
32952
|
};
|
|
32703
32953
|
|
|
32704
32954
|
// downloads.ts
|
|
32705
|
-
var indexPath =
|
|
32955
|
+
var indexPath = join17(stateDir, "downloads.json");
|
|
32706
32956
|
function downloadDir() {
|
|
32707
32957
|
const configured2 = readAgentConfig().downloadDir;
|
|
32708
32958
|
if (typeof configured2 === "string" && configured2.trim()) return expandHome2(configured2.trim());
|
|
32709
|
-
return
|
|
32959
|
+
return join17(homedir11(), "browsentic", "download");
|
|
32710
32960
|
}
|
|
32711
32961
|
function expandHome2(p) {
|
|
32712
|
-
if (p === "~") return
|
|
32713
|
-
if (p.startsWith("~/")) return
|
|
32714
|
-
return isAbsolute2(p) ? p :
|
|
32962
|
+
if (p === "~") return homedir11();
|
|
32963
|
+
if (p.startsWith("~/")) return join17(homedir11(), p.slice(2));
|
|
32964
|
+
return isAbsolute2(p) ? p : join17(homedir11(), p);
|
|
32715
32965
|
}
|
|
32716
32966
|
function readIndex() {
|
|
32717
32967
|
try {
|
|
32718
|
-
const parsed2 = JSON.parse(
|
|
32968
|
+
const parsed2 = JSON.parse(readFileSync10(indexPath, "utf8"));
|
|
32719
32969
|
return Array.isArray(parsed2) ? parsed2 : [];
|
|
32720
32970
|
} catch {
|
|
32721
32971
|
return [];
|
|
@@ -32750,7 +33000,7 @@ var HEAD_BYTES = 64 * 1024;
|
|
|
32750
33000
|
// ensure-daemon.ts
|
|
32751
33001
|
import { spawn as spawn3 } from "child_process";
|
|
32752
33002
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
32753
|
-
import { dirname as
|
|
33003
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
32754
33004
|
|
|
32755
33005
|
// ports.ts
|
|
32756
33006
|
var daemonPorts = parsePorts(process.env.BROWSENTIC_PORTS) ?? DAEMON_PORTS;
|
|
@@ -32770,7 +33020,7 @@ async function ensureDaemon() {
|
|
|
32770
33020
|
const existing = await probeExisting();
|
|
32771
33021
|
if (existing) return existing;
|
|
32772
33022
|
log("no daemon reachable; spawning one");
|
|
32773
|
-
const daemonMain =
|
|
33023
|
+
const daemonMain = join18(dirname6(fileURLToPath4(import.meta.url)), "daemon-main.js");
|
|
32774
33024
|
const env = { ...process.env };
|
|
32775
33025
|
delete env.BROWSENTIC_AGENT_RUN;
|
|
32776
33026
|
delete env.CLAUDECODE;
|
|
@@ -32855,21 +33105,21 @@ import {
|
|
|
32855
33105
|
chmodSync as chmodSync4,
|
|
32856
33106
|
existsSync as existsSync5,
|
|
32857
33107
|
mkdirSync as mkdirSync7,
|
|
32858
|
-
readFileSync as
|
|
33108
|
+
readFileSync as readFileSync11,
|
|
32859
33109
|
readdirSync as readdirSync4,
|
|
32860
33110
|
renameSync as renameSync3,
|
|
32861
33111
|
rmSync as rmSync4,
|
|
32862
33112
|
statSync as statSync5,
|
|
32863
33113
|
writeFileSync as writeFileSync6
|
|
32864
33114
|
} from "fs";
|
|
32865
|
-
import { join as
|
|
33115
|
+
import { join as join19, relative as relative2 } from "path";
|
|
32866
33116
|
function walk(dir, base = dir) {
|
|
32867
33117
|
return readdirSync4(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
32868
|
-
const full =
|
|
32869
|
-
return entry.isDirectory() ? walk(full, base) : [
|
|
33118
|
+
const full = join19(dir, entry.name);
|
|
33119
|
+
return entry.isDirectory() ? walk(full, base) : [relative2(base, full)];
|
|
32870
33120
|
});
|
|
32871
33121
|
}
|
|
32872
|
-
var hash2 = (path) => createHash2("sha256").update(
|
|
33122
|
+
var hash2 = (path) => createHash2("sha256").update(readFileSync11(path)).digest("hex");
|
|
32873
33123
|
function sameContent(a, b) {
|
|
32874
33124
|
try {
|
|
32875
33125
|
if (statSync5(a).size !== statSync5(b).size) return false;
|
|
@@ -32880,7 +33130,7 @@ function sameContent(a, b) {
|
|
|
32880
33130
|
}
|
|
32881
33131
|
function readStamp(dir) {
|
|
32882
33132
|
try {
|
|
32883
|
-
return JSON.parse(
|
|
33133
|
+
return JSON.parse(readFileSync11(installStampPath(dir), "utf8"));
|
|
32884
33134
|
} catch {
|
|
32885
33135
|
return null;
|
|
32886
33136
|
}
|
|
@@ -32900,8 +33150,8 @@ function install(dir, force = false) {
|
|
|
32900
33150
|
"Reinstall with `npm i -g browsentic`, or run `yarn build` if you are in a source checkout."
|
|
32901
33151
|
);
|
|
32902
33152
|
}
|
|
32903
|
-
const manifestPath =
|
|
32904
|
-
const version2 = JSON.parse(
|
|
33153
|
+
const manifestPath = join19(packaged.dir, "manifest.json");
|
|
33154
|
+
const version2 = JSON.parse(readFileSync11(manifestPath, "utf8")).version;
|
|
32905
33155
|
const stamp = readStamp(dir);
|
|
32906
33156
|
if (!force && stamp?.version === version2 && existsSync5(manifestPath)) {
|
|
32907
33157
|
return {
|
|
@@ -32916,18 +33166,18 @@ function install(dir, force = false) {
|
|
|
32916
33166
|
const sources = walk(packaged.dir);
|
|
32917
33167
|
mkdirSync7(dir, { recursive: true, mode: 493 });
|
|
32918
33168
|
for (const stale of walk(dir).filter((f) => /\.tmp-\d+$/.test(f))) {
|
|
32919
|
-
rmSync4(
|
|
33169
|
+
rmSync4(join19(dir, stale), { force: true });
|
|
32920
33170
|
}
|
|
32921
33171
|
const ordered = [...sources.filter((f) => f !== "manifest.json"), "manifest.json"];
|
|
32922
33172
|
let changed = 0;
|
|
32923
33173
|
for (const rel of ordered) {
|
|
32924
|
-
const from =
|
|
32925
|
-
const to =
|
|
33174
|
+
const from = join19(packaged.dir, rel);
|
|
33175
|
+
const to = join19(dir, rel);
|
|
32926
33176
|
if (!force && sameContent(from, to)) continue;
|
|
32927
|
-
mkdirSync7(
|
|
33177
|
+
mkdirSync7(join19(to, ".."), { recursive: true, mode: 493 });
|
|
32928
33178
|
const tmp = `${to}.tmp-${process.pid}`;
|
|
32929
33179
|
try {
|
|
32930
|
-
writeFileSync6(tmp,
|
|
33180
|
+
writeFileSync6(tmp, readFileSync11(from), { mode: 420 });
|
|
32931
33181
|
chmodSync4(tmp, 420);
|
|
32932
33182
|
renameSync3(tmp, to);
|
|
32933
33183
|
changed++;
|
|
@@ -32946,7 +33196,7 @@ function install(dir, force = false) {
|
|
|
32946
33196
|
const wanted = new Set(sources);
|
|
32947
33197
|
for (const rel of walk(dir)) {
|
|
32948
33198
|
if (wanted.has(rel) || rel === ".browsentic-install.json") continue;
|
|
32949
|
-
rmSync4(
|
|
33199
|
+
rmSync4(join19(dir, rel), { force: true });
|
|
32950
33200
|
}
|
|
32951
33201
|
const record2 = {
|
|
32952
33202
|
version: version2,
|
|
@@ -32960,17 +33210,17 @@ function install(dir, force = false) {
|
|
|
32960
33210
|
}
|
|
32961
33211
|
|
|
32962
33212
|
// npx.ts
|
|
32963
|
-
import { existsSync as existsSync6, readFileSync as
|
|
32964
|
-
import { homedir as
|
|
32965
|
-
import { dirname as
|
|
33213
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12, readdirSync as readdirSync5, realpathSync } from "fs";
|
|
33214
|
+
import { homedir as homedir12 } from "os";
|
|
33215
|
+
import { dirname as dirname7, join as join20, resolve, sep as sep2 } from "path";
|
|
32966
33216
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
32967
|
-
var packageRoot = resolve(
|
|
33217
|
+
var packageRoot = resolve(dirname7(fileURLToPath5(import.meta.url)), "..");
|
|
32968
33218
|
var APP_MARKER = ".browsentic-app.json";
|
|
32969
33219
|
var inNpxCache = (path) => path.split(sep2).includes("_npx");
|
|
32970
33220
|
function installKind() {
|
|
32971
33221
|
if (inNpxCache(packageRoot)) return "npx";
|
|
32972
|
-
if (existsSync6(
|
|
32973
|
-
if (existsSync6(
|
|
33222
|
+
if (existsSync6(join20(packageRoot, APP_MARKER))) return "app";
|
|
33223
|
+
if (existsSync6(join20(packageRoot, "tsup.config.ts"))) return "repo";
|
|
32974
33224
|
return "global";
|
|
32975
33225
|
}
|
|
32976
33226
|
function real(path) {
|
|
@@ -32983,14 +33233,14 @@ function real(path) {
|
|
|
32983
33233
|
function cacheRoots() {
|
|
32984
33234
|
const roots = [
|
|
32985
33235
|
process.env.npm_config_cache,
|
|
32986
|
-
process.platform === "win32" ?
|
|
32987
|
-
|
|
33236
|
+
process.platform === "win32" ? join20(process.env.LOCALAPPDATA ?? homedir12(), "npm-cache") : null,
|
|
33237
|
+
join20(homedir12(), ".npm")
|
|
32988
33238
|
].filter((root) => !!root);
|
|
32989
|
-
return [...new Set(roots.map((root) =>
|
|
33239
|
+
return [...new Set(roots.map((root) => join20(root, "_npx")))];
|
|
32990
33240
|
}
|
|
32991
33241
|
function readJson(path) {
|
|
32992
33242
|
try {
|
|
32993
|
-
return JSON.parse(
|
|
33243
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
32994
33244
|
} catch {
|
|
32995
33245
|
return null;
|
|
32996
33246
|
}
|
|
@@ -32999,7 +33249,7 @@ function npxEntries() {
|
|
|
32999
33249
|
const own = inNpxCache(packageRoot) ? real(resolve(packageRoot, "..", "..")) : null;
|
|
33000
33250
|
const scanned = cacheRoots().flatMap((root) => {
|
|
33001
33251
|
try {
|
|
33002
|
-
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => real(
|
|
33252
|
+
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => real(join20(root, entry.name)));
|
|
33003
33253
|
} catch {
|
|
33004
33254
|
return [];
|
|
33005
33255
|
}
|
|
@@ -33007,9 +33257,9 @@ function npxEntries() {
|
|
|
33007
33257
|
const entries = /* @__PURE__ */ new Map();
|
|
33008
33258
|
for (const dir of [...scanned, ...own ? [own] : []]) {
|
|
33009
33259
|
if (entries.has(dir)) continue;
|
|
33010
|
-
const manifest = readJson(
|
|
33260
|
+
const manifest = readJson(join20(dir, "node_modules", "browsentic", "package.json"));
|
|
33011
33261
|
if (!manifest) continue;
|
|
33012
|
-
const requested = readJson(
|
|
33262
|
+
const requested = readJson(join20(dir, "package.json"))?._npx;
|
|
33013
33263
|
entries.set(dir, {
|
|
33014
33264
|
dir,
|
|
33015
33265
|
version: typeof manifest.version === "string" ? manifest.version : null,
|
|
@@ -33042,7 +33292,7 @@ async function signedAddonAttached(version2, timeoutMs = 4e3) {
|
|
|
33042
33292
|
}
|
|
33043
33293
|
|
|
33044
33294
|
// remote-bridge.ts
|
|
33045
|
-
import { randomUUID as
|
|
33295
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
33046
33296
|
|
|
33047
33297
|
// node_modules/ws/wrapper.mjs
|
|
33048
33298
|
var import_stream2 = __toESM(require_stream(), 1);
|
|
@@ -33076,40 +33326,40 @@ var RemoteBridge = class _RemoteBridge {
|
|
|
33076
33326
|
});
|
|
33077
33327
|
}
|
|
33078
33328
|
async describe() {
|
|
33079
|
-
const reply = await this.request({ id:
|
|
33329
|
+
const reply = await this.request({ id: randomUUID8(), op: "describe", runId: this.runId });
|
|
33080
33330
|
return reply && "tools" in reply ? { tools: reply.tools, reserved: reply.reserved } : { tools: [] };
|
|
33081
33331
|
}
|
|
33082
33332
|
async invoke(action, input2) {
|
|
33083
33333
|
const reply = await this.request(
|
|
33084
|
-
{ id:
|
|
33334
|
+
{ id: randomUUID8(), op: "invoke", action, input: input2, runId: this.runId },
|
|
33085
33335
|
invokeTimeoutFor(action, input2)
|
|
33086
33336
|
);
|
|
33087
33337
|
if (reply && "result" in reply) return reply.result;
|
|
33088
33338
|
return failure("DAEMON_UNREACHABLE", "The Browsentic daemon did not respond");
|
|
33089
33339
|
}
|
|
33090
33340
|
async status() {
|
|
33091
|
-
const reply = await this.request({ id:
|
|
33341
|
+
const reply = await this.request({ id: randomUUID8(), op: "status" });
|
|
33092
33342
|
if (reply && "status" in reply) return reply.status;
|
|
33093
33343
|
throw new Error("The Browsentic daemon did not respond to a status request");
|
|
33094
33344
|
}
|
|
33095
33345
|
async pair() {
|
|
33096
|
-
const reply = await this.request({ id:
|
|
33346
|
+
const reply = await this.request({ id: randomUUID8(), op: "pair" });
|
|
33097
33347
|
if (reply && "code" in reply) return reply;
|
|
33098
33348
|
throw new Error("The Browsentic daemon did not issue a pairing code");
|
|
33099
33349
|
}
|
|
33100
33350
|
async sessions() {
|
|
33101
|
-
const reply = await this.request({ id:
|
|
33351
|
+
const reply = await this.request({ id: randomUUID8(), op: "sessions" });
|
|
33102
33352
|
return reply && "sessions" in reply ? reply.sessions : [];
|
|
33103
33353
|
}
|
|
33104
33354
|
async agent(change) {
|
|
33105
|
-
const reply = await this.request({ id:
|
|
33355
|
+
const reply = await this.request({ id: randomUUID8(), op: "agent", ...change });
|
|
33106
33356
|
if (reply && "state" in reply) return reply.state;
|
|
33107
33357
|
throw new Error("The Browsentic daemon did not answer about its agent");
|
|
33108
33358
|
}
|
|
33109
33359
|
/** A browser is named by its session id; an origin still unpairs every browser presenting it. */
|
|
33110
33360
|
async revoke(browser) {
|
|
33111
33361
|
const named = browser?.includes("://") ? { origin: browser } : { session: browser };
|
|
33112
|
-
const reply = await this.request({ id:
|
|
33362
|
+
const reply = await this.request({ id: randomUUID8(), op: "revoke", ...named });
|
|
33113
33363
|
return reply && "revoked" in reply ? reply.revoked : 0;
|
|
33114
33364
|
}
|
|
33115
33365
|
onManifestChanged(listener) {
|
|
@@ -35089,14 +35339,14 @@ var RESERVED_TOOLS = [
|
|
|
35089
35339
|
function createMcpServer(bridge, version2, opts = {}) {
|
|
35090
35340
|
const policy = policyFrom(readAgentConfig().guardrails);
|
|
35091
35341
|
const tag2 = fenceTag();
|
|
35092
|
-
const
|
|
35342
|
+
const server2 = new Server(
|
|
35093
35343
|
{ name: "browsentic", version: version2 },
|
|
35094
35344
|
{
|
|
35095
35345
|
capabilities: { tools: { listChanged: true }, resources: {} },
|
|
35096
35346
|
instructions: "Controls the user\u2019s browser through the Browsentic extension. Tools act on the active tab. Start with page_getPageInfo (or the browsentic://page/diagram resource) to learn what is on the page and get stable selectors, then target elements by selector or visible text. page_screenshot hands the image back to you in the result and writes nothing to disk, so captures you take to see the page for yourself leave no files behind. Pass save: true only when the user asked for a picture they can keep; then read the returned savedTo path back so the image renders, and include that path in your reply. Passwords, keys, tokens and cookies are replaced in every result by a sealed placeholder such as \u27E6password:4f2a@example.com\u27E7; the real value stays in the browser. Pass a placeholder through unchanged as page_fillInput\u2019s value or page_typeText\u2019s text and it becomes the credential at the moment it reaches the field. Anywhere else it is refused, and it is never yours to read, rebuild or repeat."
|
|
35097
35347
|
}
|
|
35098
35348
|
);
|
|
35099
|
-
|
|
35349
|
+
server2.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
35100
35350
|
const { tools: actions2, reserved = opts.agentRun ? RESERVED_TOOLS.map((tool) => tool.action) : [] } = await bridge.describe();
|
|
35101
35351
|
assertToolNamesRoundTrip([...actions2.map((action) => action.name), ...RESERVED_ACTIONS]);
|
|
35102
35352
|
return {
|
|
@@ -35115,7 +35365,7 @@ function createMcpServer(bridge, version2, opts = {}) {
|
|
|
35115
35365
|
]
|
|
35116
35366
|
};
|
|
35117
35367
|
});
|
|
35118
|
-
|
|
35368
|
+
server2.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
|
|
35119
35369
|
if (params.name === STATUS_TOOL) return render(await status(bridge));
|
|
35120
35370
|
const action = actionNameFor(params.name);
|
|
35121
35371
|
const result = await bridge.invoke(action, params.arguments ?? {});
|
|
@@ -35125,8 +35375,8 @@ function createMcpServer(bridge, version2, opts = {}) {
|
|
|
35125
35375
|
if (params.name === CAPTCHA_TOOL) return renderCaptcha(result, shouldFence(action, policy) ? tag2 : void 0);
|
|
35126
35376
|
return render(result, shouldFence(action, policy) ? tag2 : void 0);
|
|
35127
35377
|
});
|
|
35128
|
-
|
|
35129
|
-
|
|
35378
|
+
server2.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [...RESOURCES] }));
|
|
35379
|
+
server2.setRequestHandler(ReadResourceRequestSchema, async ({ params }) => {
|
|
35130
35380
|
const { uri } = params;
|
|
35131
35381
|
const resource = RESOURCES.find((candidate) => candidate.uri === uri);
|
|
35132
35382
|
if (!resource) throw new Error(`Unknown resource: ${uri}`);
|
|
@@ -35146,9 +35396,9 @@ function createMcpServer(bridge, version2, opts = {}) {
|
|
|
35146
35396
|
});
|
|
35147
35397
|
bridge.onManifestChanged(() => {
|
|
35148
35398
|
log("manifest changed; notifying MCP client");
|
|
35149
|
-
void
|
|
35399
|
+
void server2.sendToolListChanged().catch((error51) => log("failed to notify tool list change", error51));
|
|
35150
35400
|
});
|
|
35151
|
-
return
|
|
35401
|
+
return server2;
|
|
35152
35402
|
}
|
|
35153
35403
|
async function status(bridge) {
|
|
35154
35404
|
const base = await bridge.status();
|
|
@@ -35275,32 +35525,32 @@ function text2(uri, mimeType, body) {
|
|
|
35275
35525
|
|
|
35276
35526
|
// uninstall.ts
|
|
35277
35527
|
import { existsSync as existsSync7, readdirSync as readdirSync6, rmSync as rmSync6 } from "fs";
|
|
35278
|
-
import { isAbsolute as isAbsolute4, join as
|
|
35528
|
+
import { isAbsolute as isAbsolute4, join as join22, relative as relative3 } from "path";
|
|
35279
35529
|
|
|
35280
35530
|
// screenshots.ts
|
|
35281
35531
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
35282
35532
|
import { chmodSync as chmodSync5, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
35283
|
-
import { homedir as
|
|
35284
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
35533
|
+
import { homedir as homedir13 } from "os";
|
|
35534
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join21, resolve as resolve2, sep as sep3 } from "path";
|
|
35285
35535
|
function screenshotDir() {
|
|
35286
35536
|
const configured2 = readAgentConfig().screenshotDir;
|
|
35287
35537
|
if (typeof configured2 === "string" && configured2.trim()) return expandHome3(configured2.trim());
|
|
35288
|
-
return
|
|
35538
|
+
return join21(homedir13(), "browsentic", "screenshot");
|
|
35289
35539
|
}
|
|
35290
35540
|
function expandHome3(p) {
|
|
35291
|
-
if (p === "~") return
|
|
35292
|
-
if (p.startsWith("~/")) return
|
|
35293
|
-
return isAbsolute3(p) ? p :
|
|
35541
|
+
if (p === "~") return homedir13();
|
|
35542
|
+
if (p.startsWith("~/")) return join21(homedir13(), p.slice(2));
|
|
35543
|
+
return isAbsolute3(p) ? p : join21(homedir13(), p);
|
|
35294
35544
|
}
|
|
35295
35545
|
|
|
35296
35546
|
// uninstall.ts
|
|
35297
35547
|
function contains(root, path) {
|
|
35298
|
-
const inside =
|
|
35548
|
+
const inside = relative3(root, path);
|
|
35299
35549
|
return !inside.startsWith("..") && !isAbsolute4(inside);
|
|
35300
35550
|
}
|
|
35301
35551
|
function planUninstall(options = {}) {
|
|
35302
|
-
const
|
|
35303
|
-
const extension2 = extensionDir(
|
|
35552
|
+
const config5 = readAgentConfig();
|
|
35553
|
+
const extension2 = extensionDir(config5.extensionDir);
|
|
35304
35554
|
const roots = [stateDir, userDir];
|
|
35305
35555
|
const removals = [];
|
|
35306
35556
|
if (!roots.some((root) => contains(root, extension2)) && existsSync7(extension2)) {
|
|
@@ -35346,7 +35596,7 @@ function keepingSome(dir, keep) {
|
|
|
35346
35596
|
const kept = [];
|
|
35347
35597
|
for (const entry of readdirSync6(dir)) {
|
|
35348
35598
|
if (keep.includes(entry)) kept.push(entry);
|
|
35349
|
-
else rmSync6(
|
|
35599
|
+
else rmSync6(join22(dir, entry), { recursive: true, force: true });
|
|
35350
35600
|
}
|
|
35351
35601
|
return kept;
|
|
35352
35602
|
}
|
|
@@ -35364,7 +35614,8 @@ function purgeNpxCache(entries) {
|
|
|
35364
35614
|
// package.json
|
|
35365
35615
|
var package_default = {
|
|
35366
35616
|
name: "browsentic",
|
|
35367
|
-
|
|
35617
|
+
mcpName: "io.github.imshaikot/browsentic",
|
|
35618
|
+
version: "0.7.5",
|
|
35368
35619
|
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.",
|
|
35369
35620
|
type: "module",
|
|
35370
35621
|
license: "MIT",
|
|
@@ -35434,7 +35685,7 @@ var USAGE = `browsentic ${package_default.version} \u2014 hand your real browser
|
|
|
35434
35685
|
browsentic revoke [id] unpair one browser by the id "sessions" prints, or all of them
|
|
35435
35686
|
|
|
35436
35687
|
browsentic agent show which agent runs the side panel, and which are installed
|
|
35437
|
-
browsentic agent <name> switch to claude, codex, antigravity, vibe, grok, cursor or
|
|
35688
|
+
browsentic agent <name> switch to claude, codex, antigravity, vibe, grok, cursor, qwen or opencode
|
|
35438
35689
|
browsentic agent fix <name> let Browsentic fix what that agent still needs
|
|
35439
35690
|
browsentic agent model <name> [model] pin that agent's model, or omit it for the CLI's default
|
|
35440
35691
|
|
|
@@ -35541,12 +35792,12 @@ ${USAGE}`);
|
|
|
35541
35792
|
async function serve() {
|
|
35542
35793
|
const lock = await ensureDaemon();
|
|
35543
35794
|
const bridge = await RemoteBridge.connect(lock.port, lock.token, process.env.BROWSENTIC_AGENT_RUN);
|
|
35544
|
-
const
|
|
35545
|
-
await
|
|
35795
|
+
const server2 = createMcpServer(bridge, package_default.version, { agentRun: !!process.env.BROWSENTIC_AGENT_RUN });
|
|
35796
|
+
await server2.connect(new StdioServerTransport());
|
|
35546
35797
|
log(`stdio MCP server attached to daemon on port ${lock.port}`);
|
|
35547
35798
|
const shutdown = async () => {
|
|
35548
35799
|
await bridge.close();
|
|
35549
|
-
await
|
|
35800
|
+
await server2.close();
|
|
35550
35801
|
process.exit(0);
|
|
35551
35802
|
};
|
|
35552
35803
|
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => void shutdown());
|
|
@@ -35565,13 +35816,13 @@ function printTools() {
|
|
|
35565
35816
|
function printSkills() {
|
|
35566
35817
|
const skills = loadSkills();
|
|
35567
35818
|
if (wantsJson) {
|
|
35568
|
-
const
|
|
35819
|
+
const config6 = readAgentConfig();
|
|
35569
35820
|
const listed = skills.map(({ body: _body, ...skill }) => ({
|
|
35570
35821
|
...skill,
|
|
35571
|
-
path: skill.provenance === "generated" ?
|
|
35822
|
+
path: skill.provenance === "generated" ? join23(uploadedSkillsDir(), skill.name) : void 0
|
|
35572
35823
|
}));
|
|
35573
|
-
const own2 = agentSkills(
|
|
35574
|
-
console.log(JSON.stringify({ skills: listed, dirs: skillDirNames(), agent:
|
|
35824
|
+
const own2 = agentSkills(config6).map(({ name, description }) => ({ name, description }));
|
|
35825
|
+
console.log(JSON.stringify({ skills: listed, dirs: skillDirNames(), agent: config6.agent, agentSkills: own2 }, null, 2));
|
|
35575
35826
|
return;
|
|
35576
35827
|
}
|
|
35577
35828
|
if (!skills.length) {
|
|
@@ -35589,15 +35840,15 @@ function printSkills() {
|
|
|
35589
35840
|
].filter(Boolean);
|
|
35590
35841
|
console.log(`${skill.name} (${tags.join(" \xB7 ")})`);
|
|
35591
35842
|
if (skill.description) console.log(` ${skill.description}`);
|
|
35592
|
-
if (skill.provenance === "generated") console.log(` ${
|
|
35843
|
+
if (skill.provenance === "generated") console.log(` ${join23(uploadedSkillsDir(), skill.name)}/`);
|
|
35593
35844
|
}
|
|
35594
35845
|
console.log(`
|
|
35595
35846
|
Read in order: ${skillDirNames().join(" \u2192 ")} (a later one shadows an earlier one by name)`);
|
|
35596
|
-
const
|
|
35597
|
-
const own = agentSkills(
|
|
35847
|
+
const config5 = readAgentConfig();
|
|
35848
|
+
const own = agentSkills(config5);
|
|
35598
35849
|
if (own.length) {
|
|
35599
35850
|
console.log(`
|
|
35600
|
-
${AGENTS[
|
|
35851
|
+
${AGENTS[config5.agent].label}'s own skills (attachable from the panel's / picker):`);
|
|
35601
35852
|
for (const skill of own) {
|
|
35602
35853
|
console.log(`${skill.name}`);
|
|
35603
35854
|
if (skill.description) console.log(` ${skill.description}`);
|
|
@@ -35659,7 +35910,7 @@ async function restart() {
|
|
|
35659
35910
|
}
|
|
35660
35911
|
function showLogs() {
|
|
35661
35912
|
try {
|
|
35662
|
-
process.stdout.write(
|
|
35913
|
+
process.stdout.write(readFileSync13(logPath, "utf8"));
|
|
35663
35914
|
} catch {
|
|
35664
35915
|
console.log(`No log at ${logPath} yet.`);
|
|
35665
35916
|
}
|