clawfast 2.2.2 → 2.4.1

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.
Files changed (3) hide show
  1. package/README.md +4 -6
  2. package/dist/clawfast.cjs +1589 -699
  3. package/package.json +1 -1
package/dist/clawfast.cjs CHANGED
@@ -199,10 +199,10 @@ var init_boot_ui = __esm({
199
199
  var require_main = __commonJS({
200
200
  "../node_modules/.pnpm/dotenv@17.4.2/node_modules/dotenv/lib/main.js"(exports2, module2) {
201
201
  "use strict";
202
- var fs8 = require("fs");
203
- var path12 = require("path");
204
- var os7 = require("os");
205
- var crypto2 = require("crypto");
202
+ var fs10 = require("fs");
203
+ var path14 = require("path");
204
+ var os8 = require("os");
205
+ var crypto3 = require("crypto");
206
206
  var TIPS = [
207
207
  "\u25C8 encrypted .env [www.dotenvx.com]",
208
208
  "\u25C8 secrets for agents [www.dotenvx.com]",
@@ -331,7 +331,7 @@ var require_main = __commonJS({
331
331
  if (options && options.path && options.path.length > 0) {
332
332
  if (Array.isArray(options.path)) {
333
333
  for (const filepath of options.path) {
334
- if (fs8.existsSync(filepath)) {
334
+ if (fs10.existsSync(filepath)) {
335
335
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
336
336
  }
337
337
  }
@@ -339,15 +339,15 @@ var require_main = __commonJS({
339
339
  possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
340
340
  }
341
341
  } else {
342
- possibleVaultPath = path12.resolve(process.cwd(), ".env.vault");
342
+ possibleVaultPath = path14.resolve(process.cwd(), ".env.vault");
343
343
  }
344
- if (fs8.existsSync(possibleVaultPath)) {
344
+ if (fs10.existsSync(possibleVaultPath)) {
345
345
  return possibleVaultPath;
346
346
  }
347
347
  return null;
348
348
  }
349
349
  function _resolveHome(envPath) {
350
- return envPath[0] === "~" ? path12.join(os7.homedir(), envPath.slice(1)) : envPath;
350
+ return envPath[0] === "~" ? path14.join(os8.homedir(), envPath.slice(1)) : envPath;
351
351
  }
352
352
  function _configVault(options) {
353
353
  const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
@@ -364,7 +364,7 @@ var require_main = __commonJS({
364
364
  return { parsed };
365
365
  }
366
366
  function configDotenv(options) {
367
- const dotenvPath = path12.resolve(process.cwd(), ".env");
367
+ const dotenvPath = path14.resolve(process.cwd(), ".env");
368
368
  let encoding = "utf8";
369
369
  let processEnv = process.env;
370
370
  if (options && options.processEnv != null) {
@@ -392,13 +392,13 @@ var require_main = __commonJS({
392
392
  }
393
393
  let lastError;
394
394
  const parsedAll = {};
395
- for (const path13 of optionPaths) {
395
+ for (const path15 of optionPaths) {
396
396
  try {
397
- const parsed = DotenvModule.parse(fs8.readFileSync(path13, { encoding }));
397
+ const parsed = DotenvModule.parse(fs10.readFileSync(path15, { encoding }));
398
398
  DotenvModule.populate(parsedAll, parsed, options);
399
399
  } catch (e) {
400
400
  if (debug) {
401
- _debug(`failed to load ${path13} ${e.message}`);
401
+ _debug(`failed to load ${path15} ${e.message}`);
402
402
  }
403
403
  lastError = e;
404
404
  }
@@ -411,7 +411,7 @@ var require_main = __commonJS({
411
411
  const shortPaths = [];
412
412
  for (const filePath of optionPaths) {
413
413
  try {
414
- const relative2 = path12.relative(process.cwd(), filePath);
414
+ const relative2 = path14.relative(process.cwd(), filePath);
415
415
  shortPaths.push(relative2);
416
416
  } catch (e) {
417
417
  if (debug) {
@@ -446,7 +446,7 @@ var require_main = __commonJS({
446
446
  const authTag = ciphertext.subarray(-16);
447
447
  ciphertext = ciphertext.subarray(12, -16);
448
448
  try {
449
- const aesgcm = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
449
+ const aesgcm = crypto3.createDecipheriv("aes-256-gcm", key, nonce);
450
450
  aesgcm.setAuthTag(authTag);
451
451
  return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
452
452
  } catch (error51) {
@@ -565,7 +565,7 @@ async function testNvidiaKey(key) {
565
565
  "Content-Type": "application/json"
566
566
  },
567
567
  body: JSON.stringify({
568
- model: "openai/gpt-oss-120b",
568
+ model: "mistralai/mistral-medium-3.5-128b",
569
569
  messages: [{ role: "user", content: "hi" }],
570
570
  max_tokens: 1
571
571
  })
@@ -689,7 +689,7 @@ var clawfastVersion, isDevVersion, isNewerVersion;
689
689
  var init_version = __esm({
690
690
  "src/version.ts"() {
691
691
  "use strict";
692
- clawfastVersion = () => true ? "2.2.2" : devVersionFromPackageJson();
692
+ clawfastVersion = () => true ? "2.4.1" : devVersionFromPackageJson();
693
693
  isDevVersion = () => clawfastVersion().includes("-dev");
694
694
  isNewerVersion = (a, b) => {
695
695
  const parse3 = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1138,7 +1138,9 @@ function bannerSections(opts) {
1138
1138
  }
1139
1139
  function buildBanner(_systemPromptChars, version3 = "1") {
1140
1140
  const { artRows, slogan, panel } = bannerSections({ version: version3 });
1141
- return "\n" + ["", ...artRows, "", center(slogan, vlen(panel[0])), "", ...panel].join("\n") + "\n";
1141
+ return "\n" + ["", ...artRows, "", center(slogan, vlen(panel[0])), "", ...panel].join(
1142
+ "\n"
1143
+ ) + "\n";
1142
1144
  }
1143
1145
  async function playBanner(opts = {}) {
1144
1146
  const { artRows, slogan, panel } = bannerSections(opts);
@@ -1146,7 +1148,9 @@ async function playBanner(opts = {}) {
1146
1148
  process.stdout.write("\n");
1147
1149
  await revealLines(artRows, 55);
1148
1150
  process.stdout.write("\n");
1149
- const padLeft = " ".repeat(Math.max(0, Math.floor((width - vlen(slogan)) / 2)));
1151
+ const padLeft = " ".repeat(
1152
+ Math.max(0, Math.floor((width - vlen(slogan)) / 2))
1153
+ );
1150
1154
  await typeOut(SLOGAN, {
1151
1155
  perChar: 14,
1152
1156
  render: (s) => padLeft + gradient(s, { bold: true })
@@ -1154,8 +1158,10 @@ async function playBanner(opts = {}) {
1154
1158
  process.stdout.write("\n");
1155
1159
  await sweepRule(width);
1156
1160
  for (const check2 of BOOT_CHECKS) {
1157
- process.stdout.write(` ${paint("[\u2713]", PAL.green, { bold: true })} ${dim(check2)}
1158
- `);
1161
+ process.stdout.write(
1162
+ ` ${paint("[\u2713]", PAL.green, { bold: true })} ${dim(check2)}
1163
+ `
1164
+ );
1159
1165
  await sleep(110);
1160
1166
  }
1161
1167
  process.stdout.write("\n");
@@ -1261,6 +1267,12 @@ var init_news = __esm({
1261
1267
  init_ui();
1262
1268
  init_theme();
1263
1269
  NEWS = {
1270
+ "2.3.0": [
1271
+ "Linux liberado e turbinado: o clawfast detecta Kali/Linux (e se e root) e entra em modo FULL-POWER \u2014 shell real, sockets, apt/pip/pipx, systemctl e todo o arsenal Kali (nmap, masscan, sqlmap, metasploit, hydra, hashcat, nuclei, gobuster/ffuf...).",
1272
+ "Python 100% liberado e scan a vontade: portas, faixas de IP/CIDR, servicos/versoes e ate numeros (OSINT de telefone com phoneinfoga). Tudo o que o SO permite, ele faz.",
1273
+ 'Prompt do Kali repassado pra voce: quando uma ferramenta/instalador/login para e pede algo (criar conta, y/n, senha do sudo, OTP), o clawfast avisa exatamente o que "o kali" esta pedindo; voce digita aqui e ele repassa pro programa pelo stdin e segue.',
1274
+ "Escopo liberado por padrao no Linux: http_request e o toolkit de recon nao exigem mais editar scope.txt. Volte ao restrito com CLAWFAST_STRICT_SCOPE=1; force liberado em qualquer SO com CLAWFAST_UNRESTRICTED=1."
1275
+ ],
1264
1276
  "2.1.0": [
1265
1277
  'Novo visual "Verde Matrix Elite": gradiente verde\u2192teal\u2192ciano em todo o terminal. Banner cinematografico no boot \u2014 o wordmark desce em cortina, o slogan se datilografa e o painel "ACESSO CONCEDIDO" abaixa.',
1266
1278
  "Header de cada turno animado, spinner de boot pulsando em gradiente e caixas (prompt, /model, /nov) com bordas pesadas realcadas.",
@@ -1937,10 +1949,10 @@ function mergeDefs(...defs) {
1937
1949
  function cloneDef(schema) {
1938
1950
  return mergeDefs(schema._zod.def);
1939
1951
  }
1940
- function getElementAtPath(obj, path12) {
1941
- if (!path12)
1952
+ function getElementAtPath(obj, path14) {
1953
+ if (!path14)
1942
1954
  return obj;
1943
- return path12.reduce((acc, key) => acc?.[key], obj);
1955
+ return path14.reduce((acc, key) => acc?.[key], obj);
1944
1956
  }
1945
1957
  function promiseAllObject(promisesObj) {
1946
1958
  const keys = Object.keys(promisesObj);
@@ -2268,11 +2280,11 @@ function explicitlyAborted(x, startIndex = 0) {
2268
2280
  }
2269
2281
  return false;
2270
2282
  }
2271
- function prefixIssues(path12, issues) {
2283
+ function prefixIssues(path14, issues) {
2272
2284
  return issues.map((iss) => {
2273
2285
  var _a25;
2274
2286
  (_a25 = iss).path ?? (_a25.path = []);
2275
- iss.path.unshift(path12);
2287
+ iss.path.unshift(path14);
2276
2288
  return iss;
2277
2289
  });
2278
2290
  }
@@ -2490,16 +2502,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2490
2502
  }
2491
2503
  function formatError(error51, mapper = (issue2) => issue2.message) {
2492
2504
  const fieldErrors = { _errors: [] };
2493
- const processError = (error52, path12 = []) => {
2505
+ const processError = (error52, path14 = []) => {
2494
2506
  for (const issue2 of error52.issues) {
2495
2507
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2496
- issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
2508
+ issue2.errors.map((issues) => processError({ issues }, [...path14, ...issue2.path]));
2497
2509
  } else if (issue2.code === "invalid_key") {
2498
- processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2510
+ processError({ issues: issue2.issues }, [...path14, ...issue2.path]);
2499
2511
  } else if (issue2.code === "invalid_element") {
2500
- processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2512
+ processError({ issues: issue2.issues }, [...path14, ...issue2.path]);
2501
2513
  } else {
2502
- const fullpath = [...path12, ...issue2.path];
2514
+ const fullpath = [...path14, ...issue2.path];
2503
2515
  if (fullpath.length === 0) {
2504
2516
  fieldErrors._errors.push(mapper(issue2));
2505
2517
  } else {
@@ -2526,17 +2538,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2526
2538
  }
2527
2539
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2528
2540
  const result = { errors: [] };
2529
- const processError = (error52, path12 = []) => {
2541
+ const processError = (error52, path14 = []) => {
2530
2542
  var _a25, _b18;
2531
2543
  for (const issue2 of error52.issues) {
2532
2544
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2533
- issue2.errors.map((issues) => processError({ issues }, [...path12, ...issue2.path]));
2545
+ issue2.errors.map((issues) => processError({ issues }, [...path14, ...issue2.path]));
2534
2546
  } else if (issue2.code === "invalid_key") {
2535
- processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2547
+ processError({ issues: issue2.issues }, [...path14, ...issue2.path]);
2536
2548
  } else if (issue2.code === "invalid_element") {
2537
- processError({ issues: issue2.issues }, [...path12, ...issue2.path]);
2549
+ processError({ issues: issue2.issues }, [...path14, ...issue2.path]);
2538
2550
  } else {
2539
- const fullpath = [...path12, ...issue2.path];
2551
+ const fullpath = [...path14, ...issue2.path];
2540
2552
  if (fullpath.length === 0) {
2541
2553
  result.errors.push(mapper(issue2));
2542
2554
  continue;
@@ -2568,8 +2580,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2568
2580
  }
2569
2581
  function toDotPath(_path) {
2570
2582
  const segs = [];
2571
- const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2572
- for (const seg of path12) {
2583
+ const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2584
+ for (const seg of path14) {
2573
2585
  if (typeof seg === "number")
2574
2586
  segs.push(`[${seg}]`);
2575
2587
  else if (typeof seg === "symbol")
@@ -16072,13 +16084,13 @@ function resolveRef(ref, ctx) {
16072
16084
  if (!ref.startsWith("#")) {
16073
16085
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
16074
16086
  }
16075
- const path12 = ref.slice(1).split("/").filter(Boolean);
16076
- if (path12.length === 0) {
16087
+ const path14 = ref.slice(1).split("/").filter(Boolean);
16088
+ if (path14.length === 0) {
16077
16089
  return ctx.rootSchema;
16078
16090
  }
16079
16091
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
16080
- if (path12[0] === defsKey) {
16081
- const key = path12[1];
16092
+ if (path14[0] === defsKey) {
16093
+ const key = path14[1];
16082
16094
  if (!key || !ctx.defs[key]) {
16083
16095
  throw new Error(`Reference not found: ${ref}`);
16084
16096
  }
@@ -17267,8 +17279,8 @@ var init_parseUtil = __esm({
17267
17279
  init_errors3();
17268
17280
  init_en2();
17269
17281
  makeIssue = (params) => {
17270
- const { data, path: path12, errorMaps, issueData } = params;
17271
- const fullPath = [...path12, ...issueData.path || []];
17282
+ const { data, path: path14, errorMaps, issueData } = params;
17283
+ const fullPath = [...path14, ...issueData.path || []];
17272
17284
  const fullIssue = {
17273
17285
  ...issueData,
17274
17286
  path: fullPath
@@ -17551,11 +17563,11 @@ var init_types = __esm({
17551
17563
  init_parseUtil();
17552
17564
  init_util2();
17553
17565
  ParseInputLazyPath = class {
17554
- constructor(parent, value, path12, key) {
17566
+ constructor(parent, value, path14, key) {
17555
17567
  this._cachedPath = [];
17556
17568
  this.parent = parent;
17557
17569
  this.data = value;
17558
- this._path = path12;
17570
+ this._path = path14;
17559
17571
  this._key = key;
17560
17572
  }
17561
17573
  get path() {
@@ -23594,8 +23606,8 @@ var require_auth_config = __commonJS({
23594
23606
  writeAuthConfig: () => writeAuthConfig
23595
23607
  });
23596
23608
  module2.exports = __toCommonJS(auth_config_exports);
23597
- var fs8 = __toESM2(require("fs"));
23598
- var path12 = __toESM2(require("path"));
23609
+ var fs10 = __toESM2(require("fs"));
23610
+ var path14 = __toESM2(require("path"));
23599
23611
  var import_token_util = require_token_util();
23600
23612
  function getAuthConfigPath() {
23601
23613
  const dataDir = (0, import_token_util.getVercelDataDir)();
@@ -23604,15 +23616,15 @@ var require_auth_config = __commonJS({
23604
23616
  `Unable to find Vercel CLI data directory. Your platform: ${process.platform}. Supported: darwin, linux, win32.`
23605
23617
  );
23606
23618
  }
23607
- return path12.join(dataDir, "auth.json");
23619
+ return path14.join(dataDir, "auth.json");
23608
23620
  }
23609
23621
  function readAuthConfig() {
23610
23622
  try {
23611
23623
  const authPath = getAuthConfigPath();
23612
- if (!fs8.existsSync(authPath)) {
23624
+ if (!fs10.existsSync(authPath)) {
23613
23625
  return null;
23614
23626
  }
23615
- const content = fs8.readFileSync(authPath, "utf8");
23627
+ const content = fs10.readFileSync(authPath, "utf8");
23616
23628
  if (!content) {
23617
23629
  return null;
23618
23630
  }
@@ -23623,11 +23635,11 @@ var require_auth_config = __commonJS({
23623
23635
  }
23624
23636
  function writeAuthConfig(config3) {
23625
23637
  const authPath = getAuthConfigPath();
23626
- const authDir = path12.dirname(authPath);
23627
- if (!fs8.existsSync(authDir)) {
23628
- fs8.mkdirSync(authDir, { mode: 504, recursive: true });
23638
+ const authDir = path14.dirname(authPath);
23639
+ if (!fs10.existsSync(authDir)) {
23640
+ fs10.mkdirSync(authDir, { mode: 504, recursive: true });
23629
23641
  }
23630
- fs8.writeFileSync(authPath, JSON.stringify(config3, null, 2), { mode: 384 });
23642
+ fs10.writeFileSync(authPath, JSON.stringify(config3, null, 2), { mode: 384 });
23631
23643
  }
23632
23644
  function isValidAccessToken(authConfig, expirationBufferMs = 0) {
23633
23645
  if (!authConfig.token)
@@ -23818,8 +23830,8 @@ var require_token_util = __commonJS({
23818
23830
  saveToken: () => saveToken
23819
23831
  });
23820
23832
  module2.exports = __toCommonJS(token_util_exports);
23821
- var path12 = __toESM2(require("path"));
23822
- var fs8 = __toESM2(require("fs"));
23833
+ var path14 = __toESM2(require("path"));
23834
+ var fs10 = __toESM2(require("fs"));
23823
23835
  var import_token_error = require_token_error();
23824
23836
  var import_token_io = require_token_io();
23825
23837
  var import_auth_config = require_auth_config();
@@ -23831,7 +23843,7 @@ var require_token_util = __commonJS({
23831
23843
  if (!dataDir) {
23832
23844
  return null;
23833
23845
  }
23834
- return path12.join(dataDir, vercelFolder);
23846
+ return path14.join(dataDir, vercelFolder);
23835
23847
  }
23836
23848
  async function getVercelToken2(options) {
23837
23849
  const authConfig = (0, import_auth_config.readAuthConfig)();
@@ -23907,13 +23919,13 @@ var require_token_util = __commonJS({
23907
23919
  "Unable to find project root directory. Have you linked your project with `vc link?`"
23908
23920
  );
23909
23921
  }
23910
- const prjPath = path12.join(dir, ".vercel", "project.json");
23911
- if (!fs8.existsSync(prjPath)) {
23922
+ const prjPath = path14.join(dir, ".vercel", "project.json");
23923
+ if (!fs10.existsSync(prjPath)) {
23912
23924
  throw new import_token_error.VercelOidcTokenError(
23913
23925
  "project.json not found, have you linked your project with `vc link?`"
23914
23926
  );
23915
23927
  }
23916
- const prj = JSON.parse(fs8.readFileSync(prjPath, "utf8"));
23928
+ const prj = JSON.parse(fs10.readFileSync(prjPath, "utf8"));
23917
23929
  if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") {
23918
23930
  throw new TypeError(
23919
23931
  "Expected a string-valued projectId property. Try running `vc link` to re-link your project."
@@ -23928,11 +23940,11 @@ var require_token_util = __commonJS({
23928
23940
  "Unable to find user data directory. Please reach out to Vercel support."
23929
23941
  );
23930
23942
  }
23931
- const tokenPath = path12.join(dir, "com.vercel.token", `${projectId}.json`);
23943
+ const tokenPath = path14.join(dir, "com.vercel.token", `${projectId}.json`);
23932
23944
  const tokenJson = JSON.stringify(token);
23933
- fs8.mkdirSync(path12.dirname(tokenPath), { mode: 504, recursive: true });
23934
- fs8.writeFileSync(tokenPath, tokenJson);
23935
- fs8.chmodSync(tokenPath, 432);
23945
+ fs10.mkdirSync(path14.dirname(tokenPath), { mode: 504, recursive: true });
23946
+ fs10.writeFileSync(tokenPath, tokenJson);
23947
+ fs10.chmodSync(tokenPath, 432);
23936
23948
  return;
23937
23949
  }
23938
23950
  function loadToken(projectId) {
@@ -23942,11 +23954,11 @@ var require_token_util = __commonJS({
23942
23954
  "Unable to find user data directory. Please reach out to Vercel support."
23943
23955
  );
23944
23956
  }
23945
- const tokenPath = path12.join(dir, "com.vercel.token", `${projectId}.json`);
23946
- if (!fs8.existsSync(tokenPath)) {
23957
+ const tokenPath = path14.join(dir, "com.vercel.token", `${projectId}.json`);
23958
+ if (!fs10.existsSync(tokenPath)) {
23947
23959
  return null;
23948
23960
  }
23949
- const token = JSON.parse(fs8.readFileSync(tokenPath, "utf8"));
23961
+ const token = JSON.parse(fs10.readFileSync(tokenPath, "utf8"));
23950
23962
  assertVercelOidcTokenResponse(token);
23951
23963
  return token;
23952
23964
  }
@@ -35972,7 +35984,7 @@ function createOpenRouter(options = {}) {
35972
35984
  );
35973
35985
  const createChatModel = (modelId, settings = {}) => new OpenRouterChatLanguageModel(modelId, settings, {
35974
35986
  provider: "openrouter.chat",
35975
- url: ({ path: path12 }) => `${baseURL}${path12}`,
35987
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
35976
35988
  headers: getHeaders,
35977
35989
  compatibility,
35978
35990
  fetch: options.fetch,
@@ -35980,7 +35992,7 @@ function createOpenRouter(options = {}) {
35980
35992
  });
35981
35993
  const createCompletionModel = (modelId, settings = {}) => new OpenRouterCompletionLanguageModel(modelId, settings, {
35982
35994
  provider: "openrouter.completion",
35983
- url: ({ path: path12 }) => `${baseURL}${path12}`,
35995
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
35984
35996
  headers: getHeaders,
35985
35997
  compatibility,
35986
35998
  fetch: options.fetch,
@@ -35988,21 +36000,21 @@ function createOpenRouter(options = {}) {
35988
36000
  });
35989
36001
  const createEmbeddingModel = (modelId, settings = {}) => new OpenRouterEmbeddingModel(modelId, settings, {
35990
36002
  provider: "openrouter.embedding",
35991
- url: ({ path: path12 }) => `${baseURL}${path12}`,
36003
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
35992
36004
  headers: getHeaders,
35993
36005
  fetch: options.fetch,
35994
36006
  extraBody: options.extraBody
35995
36007
  });
35996
36008
  const createImageModel = (modelId, settings = {}) => new OpenRouterImageModel(modelId, settings, {
35997
36009
  provider: "openrouter.image",
35998
- url: ({ path: path12 }) => `${baseURL}${path12}`,
36010
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
35999
36011
  headers: getHeaders,
36000
36012
  fetch: options.fetch,
36001
36013
  extraBody: options.extraBody
36002
36014
  });
36003
36015
  const createVideoModel = (modelId, settings = {}) => new OpenRouterVideoModel(modelId, settings, {
36004
36016
  provider: "openrouter.video",
36005
- url: ({ path: path12 }) => `${baseURL}${path12}`,
36017
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
36006
36018
  headers: getHeaders,
36007
36019
  fetch: options.fetch,
36008
36020
  extraBody: options.extraBody
@@ -40538,37 +40550,37 @@ function createOpenAI(options = {}) {
40538
40550
  );
40539
40551
  const createChatModel = (modelId) => new OpenAIChatLanguageModel(modelId, {
40540
40552
  provider: `${providerName}.chat`,
40541
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40553
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40542
40554
  headers: getHeaders,
40543
40555
  fetch: options.fetch
40544
40556
  });
40545
40557
  const createCompletionModel = (modelId) => new OpenAICompletionLanguageModel(modelId, {
40546
40558
  provider: `${providerName}.completion`,
40547
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40559
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40548
40560
  headers: getHeaders,
40549
40561
  fetch: options.fetch
40550
40562
  });
40551
40563
  const createEmbeddingModel = (modelId) => new OpenAIEmbeddingModel(modelId, {
40552
40564
  provider: `${providerName}.embedding`,
40553
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40565
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40554
40566
  headers: getHeaders,
40555
40567
  fetch: options.fetch
40556
40568
  });
40557
40569
  const createImageModel = (modelId) => new OpenAIImageModel(modelId, {
40558
40570
  provider: `${providerName}.image`,
40559
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40571
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40560
40572
  headers: getHeaders,
40561
40573
  fetch: options.fetch
40562
40574
  });
40563
40575
  const createTranscriptionModel = (modelId) => new OpenAITranscriptionModel(modelId, {
40564
40576
  provider: `${providerName}.transcription`,
40565
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40577
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40566
40578
  headers: getHeaders,
40567
40579
  fetch: options.fetch
40568
40580
  });
40569
40581
  const createSpeechModel = (modelId) => new OpenAISpeechModel(modelId, {
40570
40582
  provider: `${providerName}.speech`,
40571
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40583
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40572
40584
  headers: getHeaders,
40573
40585
  fetch: options.fetch
40574
40586
  });
@@ -40583,7 +40595,7 @@ function createOpenAI(options = {}) {
40583
40595
  const createResponsesModel = (modelId) => {
40584
40596
  return new OpenAIResponsesLanguageModel(modelId, {
40585
40597
  provider: `${providerName}.responses`,
40586
- url: ({ path: path12 }) => `${baseURL}${path12}`,
40598
+ url: ({ path: path14 }) => `${baseURL}${path14}`,
40587
40599
  headers: getHeaders,
40588
40600
  fetch: options.fetch,
40589
40601
  fileIdPrefixes: ["file-"]
@@ -46085,7 +46097,6 @@ var init_providers = __esm({
46085
46097
  "model-nvidia-mistral-medium-3.5": nvidia.chat(
46086
46098
  "mistralai/mistral-medium-3.5-128b"
46087
46099
  ),
46088
- "model-nvidia-gpt-oss-120b": nvidia.chat("openai/gpt-oss-120b"),
46089
46100
  "model-nvidia-glm-5.1": nvidia.chat("z-ai/glm-5.1"),
46090
46101
  "model-nvidia-qwen3.5-397b": nvidia.chat("qwen/qwen3.5-397b-a17b"),
46091
46102
  // Extra explicit keys for the CLI.
@@ -46123,8 +46134,6 @@ var init_providers = __esm({
46123
46134
  ...hasEnvValue("NVIDIA_API_KEY") ? [
46124
46135
  "model-nvidia-mistral-medium-3.5",
46125
46136
  // NVIDIA mistralai/mistral-medium-3.5-128b
46126
- "model-nvidia-gpt-oss-120b",
46127
- // NVIDIA openai/gpt-oss-120b
46128
46137
  "model-nvidia-glm-5.1",
46129
46138
  // NVIDIA z-ai/glm-5.1
46130
46139
  "model-nvidia-qwen3.5-397b"
@@ -46150,7 +46159,6 @@ var init_providers = __esm({
46150
46159
  "model-opus-4.6": "May 2025",
46151
46160
  "model-kimi-k2.6": "April 2024",
46152
46161
  "model-nvidia-mistral-medium-3.5": "Unknown",
46153
- "model-nvidia-gpt-oss-120b": "June 2024",
46154
46162
  "model-nvidia-glm-5.1": "Unknown",
46155
46163
  "model-nvidia-qwen3.5-397b": "Unknown",
46156
46164
  "model-deepseek-proxy": "July 2024",
@@ -46174,7 +46182,6 @@ var init_providers = __esm({
46174
46182
  "model-opus-4.6": "Anthropic Claude Opus 4.6",
46175
46183
  "model-kimi-k2.6": "Moonshot Kimi K2.6",
46176
46184
  "model-nvidia-mistral-medium-3.5": "NVIDIA - Mistral Medium 3.5",
46177
- "model-nvidia-gpt-oss-120b": "NVIDIA - OpenAI GPT-OSS 120B",
46178
46185
  "model-nvidia-glm-5.1": "NVIDIA - Z.ai GLM 5.1",
46179
46186
  "model-nvidia-qwen3.5-397b": "NVIDIA - Qwen3.5 397B A17B",
46180
46187
  "model-deepseek-proxy": "DeepSeek (sessao web logada / deepsproxy)",
@@ -49218,8 +49225,8 @@ var init_background_process_tracker = __esm({
49218
49225
  /**
49219
49226
  * Normalize file path for comparison
49220
49227
  */
49221
- normalizePath(path12) {
49222
- let normalized = path12.trim().replace(/\/+/g, "/");
49228
+ normalizePath(path14) {
49229
+ let normalized = path14.trim().replace(/\/+/g, "/");
49223
49230
  if (normalized.startsWith("./")) {
49224
49231
  normalized = normalized.slice(2);
49225
49232
  }
@@ -49457,8 +49464,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? (0, import_pat
49457
49464
  return decodedFile;
49458
49465
  };
49459
49466
  }
49460
- function normalizeWindowsPath(path12) {
49461
- return path12.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
49467
+ function normalizeWindowsPath(path14) {
49468
+ return path14.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
49462
49469
  }
49463
49470
  var import_path;
49464
49471
  var init_module_node = __esm({
@@ -52347,9 +52354,9 @@ async function addSourceContext(frames) {
52347
52354
  LRU_FILE_CONTENTS_CACHE.reduce();
52348
52355
  return frames;
52349
52356
  }
52350
- function getContextLinesFromFile(path12, ranges, output) {
52357
+ function getContextLinesFromFile(path14, ranges, output) {
52351
52358
  return new Promise((resolve2) => {
52352
- const stream = (0, import_node_fs5.createReadStream)(path12);
52359
+ const stream = (0, import_node_fs5.createReadStream)(path14);
52353
52360
  const lineReaded = (0, import_node_readline2.createInterface)({
52354
52361
  input: stream
52355
52362
  });
@@ -52364,7 +52371,7 @@ function getContextLinesFromFile(path12, ranges, output) {
52364
52371
  let rangeStart = range[0];
52365
52372
  let rangeEnd = range[1];
52366
52373
  function onStreamError() {
52367
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path12, 1);
52374
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path14, 1);
52368
52375
  lineReaded.close();
52369
52376
  lineReaded.removeAllListeners();
52370
52377
  destroyStreamAndResolve();
@@ -52425,8 +52432,8 @@ function clearLineContext(frame3) {
52425
52432
  delete frame3.context_line;
52426
52433
  delete frame3.post_context;
52427
52434
  }
52428
- function shouldSkipContextLinesForFile(path12) {
52429
- return path12.startsWith("node:") || path12.endsWith(".min.js") || path12.endsWith(".min.cjs") || path12.endsWith(".min.mjs") || path12.startsWith("data:");
52435
+ function shouldSkipContextLinesForFile(path14) {
52436
+ return path14.startsWith("node:") || path14.endsWith(".min.js") || path14.endsWith(".min.cjs") || path14.endsWith(".min.mjs") || path14.startsWith("data:");
52430
52437
  }
52431
52438
  function shouldSkipContextLinesForFrame(frame3) {
52432
52439
  if (void 0 !== frame3.lineno && frame3.lineno > MAX_CONTEXTLINES_LINENO) return true;
@@ -67836,8 +67843,8 @@ var init_logger2 = __esm({
67836
67843
  });
67837
67844
 
67838
67845
  // ../lib/ai/tools/file.ts
67839
- function isSpritPath(path12) {
67840
- return path12.split(/[\\/]/).some((segment) => segment.toLowerCase() === "sprit");
67846
+ function isSpritPath(path14) {
67847
+ return path14.split(/[\\/]/).some((segment) => segment.toLowerCase() === "sprit");
67841
67848
  }
67842
67849
  function getViewSandboxType(sandbox) {
67843
67850
  return isCentrifugoSandbox(sandbox) ? "centrifugo" : "e2b";
@@ -67868,7 +67875,7 @@ function captureFileViewImageUsage(args) {
67868
67875
  const {
67869
67876
  context: context2,
67870
67877
  sandbox,
67871
- path: path12,
67878
+ path: path14,
67872
67879
  outcome,
67873
67880
  durationMs,
67874
67881
  mediaType,
@@ -67886,7 +67893,7 @@ function captureFileViewImageUsage(args) {
67886
67893
  model: getActiveModelName(context2),
67887
67894
  configured_model: context2.modelName,
67888
67895
  sandbox_type: getViewSandboxType(sandbox),
67889
- file_extension: getFileExtension(path12),
67896
+ file_extension: getFileExtension(path14),
67890
67897
  outcome,
67891
67898
  success: outcome === "success",
67892
67899
  duration_ms: durationMs,
@@ -67960,22 +67967,22 @@ async function runSandboxCommand(sandbox, command, envVars, timeoutMs = 6e4) {
67960
67967
  function isWindowsSandbox(sandbox) {
67961
67968
  return isCentrifugoSandbox(sandbox) && sandbox.isWindows();
67962
67969
  }
67963
- function getWindowsNativePath(path12) {
67964
- if (/^[A-Za-z]:[\\/]/.test(path12)) return path12;
67965
- if (path12.startsWith("/tmp/")) {
67966
- return `C:\\temp${path12.slice(4).replace(/\//g, "\\")}`;
67970
+ function getWindowsNativePath(path14) {
67971
+ if (/^[A-Za-z]:[\\/]/.test(path14)) return path14;
67972
+ if (path14.startsWith("/tmp/")) {
67973
+ return `C:\\temp${path14.slice(4).replace(/\//g, "\\")}`;
67967
67974
  }
67968
- return path12.replace(/\//g, "\\");
67975
+ return path14.replace(/\//g, "\\");
67969
67976
  }
67970
- function getPythonPathForSandbox(sandbox, path12) {
67971
- return isWindowsSandbox(sandbox) ? getWindowsNativePath(path12) : path12;
67977
+ function getPythonPathForSandbox(sandbox, path14) {
67978
+ return isWindowsSandbox(sandbox) ? getWindowsNativePath(path14) : path14;
67972
67979
  }
67973
- function toWindowsBashPath(path12) {
67974
- const drive = path12.match(/^([A-Za-z]):[\\/](.*)$/);
67980
+ function toWindowsBashPath(path14) {
67981
+ const drive = path14.match(/^([A-Za-z]):[\\/](.*)$/);
67975
67982
  if (drive) {
67976
67983
  return `/${drive[1].toLowerCase()}/${drive[2].replace(/\\/g, "/")}`;
67977
67984
  }
67978
- return path12.replace(/\\/g, "/");
67985
+ return path14.replace(/\\/g, "/");
67979
67986
  }
67980
67987
  async function detectSandboxShell(sandbox) {
67981
67988
  if (!isWindowsSandbox(sandbox)) return "bash";
@@ -68014,8 +68021,8 @@ PY`;
68014
68021
  }
68015
68022
  }
68016
68023
  }
68017
- async function getSandboxFileState(sandbox, path12) {
68018
- const pythonPath = getPythonPathForSandbox(sandbox, path12);
68024
+ async function getSandboxFileState(sandbox, path14) {
68025
+ const pythonPath = getPythonPathForSandbox(sandbox, path14);
68019
68026
  const result = await runPythonScript(
68020
68027
  sandbox,
68021
68028
  FILE_STATE_SCRIPT,
@@ -68032,23 +68039,23 @@ async function getSandboxFileState(sandbox, path12) {
68032
68039
  if (result.exitCode !== 0) {
68033
68040
  return {
68034
68041
  kind: "unknown",
68035
- path: path12,
68042
+ path: path14,
68036
68043
  error: result.stderr || result.stdout || "file state command failed"
68037
68044
  };
68038
68045
  }
68039
68046
  try {
68040
68047
  const payload = JSON.parse(result.stdout.trim());
68041
68048
  if (payload.kind === "file" && typeof payload.sizeBytes === "number" && Number.isFinite(payload.sizeBytes)) {
68042
- return { ...payload, path: path12 };
68049
+ return { ...payload, path: path14 };
68043
68050
  }
68044
68051
  if (payload.kind === "missing" || payload.kind === "not_file") {
68045
- return { ...payload, path: path12 };
68052
+ return { ...payload, path: path14 };
68046
68053
  }
68047
68054
  } catch {
68048
68055
  }
68049
68056
  return {
68050
68057
  kind: "unknown",
68051
- path: path12,
68058
+ path: path14,
68052
68059
  error: result.stderr || result.stdout || "invalid file state response"
68053
68060
  };
68054
68061
  }
@@ -68080,8 +68087,8 @@ ${numberedContent}${truncatedNotice}${footerNotice}`;
68080
68087
  })
68081
68088
  };
68082
68089
  }
68083
- async function readSandboxTextFile(sandbox, path12, range) {
68084
- const pythonPath = getPythonPathForSandbox(sandbox, path12);
68090
+ async function readSandboxTextFile(sandbox, path14, range) {
68091
+ const pythonPath = getPythonPathForSandbox(sandbox, path14);
68085
68092
  const envVars = {
68086
68093
  clawfast_FILE_READ_PATH: pythonPath,
68087
68094
  clawfast_FILE_READ_RANGE_START: String(range?.[0] ?? 0),
@@ -68109,40 +68116,40 @@ async function readSandboxTextFile(sandbox, path12, range) {
68109
68116
  }
68110
68117
  return payload;
68111
68118
  }
68112
- async function readSandboxTextFileWithFallback(sandbox, path12, range) {
68119
+ async function readSandboxTextFileWithFallback(sandbox, path14, range) {
68113
68120
  try {
68114
- return await readSandboxTextFile(sandbox, path12, range);
68121
+ return await readSandboxTextFile(sandbox, path14, range);
68115
68122
  } catch (error51) {
68116
68123
  const errorMessage = error51 instanceof Error ? error51.message : String(error51);
68117
68124
  if (errorMessage.startsWith("Invalid ") || errorMessage.includes("File not found")) {
68118
68125
  throw error51;
68119
68126
  }
68120
- const state = await getSandboxFileState(sandbox, path12);
68127
+ const state = await getSandboxFileState(sandbox, path14);
68121
68128
  if (state.kind === "unknown") {
68122
68129
  throw new Error(
68123
- `Unable to determine file size for ${path12}; refusing to load the file into memory. ${state.error}`
68130
+ `Unable to determine file size for ${path14}; refusing to load the file into memory. ${state.error}`
68124
68131
  );
68125
68132
  }
68126
68133
  if (state.kind === "missing") {
68127
- throw new Error(`File not found or is not a regular file: ${path12}`);
68134
+ throw new Error(`File not found or is not a regular file: ${path14}`);
68128
68135
  }
68129
68136
  if (state.kind === "not_file") {
68130
- throw new Error(`File is not a regular file: ${path12}`);
68137
+ throw new Error(`File is not a regular file: ${path14}`);
68131
68138
  }
68132
68139
  if (state.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
68133
68140
  if (range) {
68134
68141
  throw new Error(
68135
- `Unable to perform a bounded range read for ${path12}, and the file is too large to load safely (${formatBytes(state.sizeBytes)}). Use a targeted terminal command that writes a small result to a separate file.`
68142
+ `Unable to perform a bounded range read for ${path14}, and the file is too large to load safely (${formatBytes(state.sizeBytes)}). Use a targeted terminal command that writes a small result to a separate file.`
68136
68143
  );
68137
68144
  }
68138
68145
  return {
68139
- path: path12,
68146
+ path: path14,
68140
68147
  sizeBytes: state.sizeBytes,
68141
68148
  totalLines: 0,
68142
68149
  tooLarge: true
68143
68150
  };
68144
68151
  }
68145
- const fileContent = await sandbox.files.read(path12, {
68152
+ const fileContent = await sandbox.files.read(path14, {
68146
68153
  user: "user"
68147
68154
  });
68148
68155
  const lines = fileContent.split("\n");
@@ -68166,7 +68173,7 @@ async function readSandboxTextFileWithFallback(sandbox, path12, range) {
68166
68173
  const startIndex = start - 1;
68167
68174
  const endIndex = end === -1 ? lines.length : end;
68168
68175
  return {
68169
- path: path12,
68176
+ path: path14,
68170
68177
  sizeBytes: Buffer.byteLength(fileContent),
68171
68178
  totalLines: lines.length,
68172
68179
  content: lines.slice(startIndex, endIndex).join("\n"),
@@ -68174,7 +68181,7 @@ async function readSandboxTextFileWithFallback(sandbox, path12, range) {
68174
68181
  };
68175
68182
  }
68176
68183
  return {
68177
- path: path12,
68184
+ path: path14,
68178
68185
  sizeBytes: Buffer.byteLength(fileContent),
68179
68186
  totalLines: lines.length,
68180
68187
  content: fileContent,
@@ -68182,7 +68189,7 @@ async function readSandboxTextFileWithFallback(sandbox, path12, range) {
68182
68189
  };
68183
68190
  }
68184
68191
  }
68185
- async function appendSandboxTextFile(sandbox, path12, text2) {
68192
+ async function appendSandboxTextFile(sandbox, path14, text2) {
68186
68193
  const tempPath = `/tmp/clawfast_append_${Date.now()}_${Math.random().toString(36).slice(2)}.tmp`;
68187
68194
  await sandbox.files.write(tempPath, text2, {
68188
68195
  user: "user"
@@ -68191,7 +68198,7 @@ async function appendSandboxTextFile(sandbox, path12, text2) {
68191
68198
  sandbox,
68192
68199
  APPEND_TEXT_FILE_SCRIPT,
68193
68200
  {
68194
- clawfast_FILE_APPEND_TARGET_PATH: getPythonPathForSandbox(sandbox, path12),
68201
+ clawfast_FILE_APPEND_TARGET_PATH: getPythonPathForSandbox(sandbox, path14),
68195
68202
  clawfast_FILE_APPEND_SOURCE_PATH: getPythonPathForSandbox(
68196
68203
  sandbox,
68197
68204
  tempPath
@@ -68203,13 +68210,13 @@ async function appendSandboxTextFile(sandbox, path12, text2) {
68203
68210
  throw new Error(result.stderr || result.stdout || "Failed to append file");
68204
68211
  }
68205
68212
  }
68206
- async function readSandboxFileForView(sandbox, path12, includeData) {
68213
+ async function readSandboxFileForView(sandbox, path14, includeData) {
68207
68214
  if (isCentrifugoSandbox(sandbox) && sandbox.isWindows()) {
68208
68215
  throw new Error(
68209
68216
  "The view action is not available for Windows local sandboxes yet. Use a Linux/E2B sandbox or inspect the image manually."
68210
68217
  );
68211
68218
  }
68212
- const sandboxPath = getSandboxViewPath(sandbox, path12);
68219
+ const sandboxPath = getSandboxViewPath(sandbox, path14);
68213
68220
  const viewEnvVars = {
68214
68221
  clawfast_FILE_VIEW_PATH: sandboxPath,
68215
68222
  clawfast_FILE_VIEW_INCLUDE_DATA: includeData ? "1" : "0",
@@ -68553,19 +68560,19 @@ try:
68553
68560
  except OSError:
68554
68561
  pass
68555
68562
  `;
68556
- getFilename = (path12) => path12.split("/").pop() || path12;
68557
- getFileExtension = (path12) => {
68558
- const filename = getFilename(path12);
68563
+ getFilename = (path14) => path14.split("/").pop() || path14;
68564
+ getFileExtension = (path14) => {
68565
+ const filename = getFilename(path14);
68559
68566
  const dotIndex = filename.lastIndexOf(".");
68560
68567
  if (dotIndex <= 0 || dotIndex === filename.length - 1) return void 0;
68561
68568
  return filename.slice(dotIndex + 1).toLowerCase();
68562
68569
  };
68563
- getSandboxViewPath = (sandbox, path12) => {
68570
+ getSandboxViewPath = (sandbox, path14) => {
68564
68571
  const maybeSandbox = sandbox;
68565
- if (isCentrifugoSandbox(maybeSandbox) && maybeSandbox.isWindows() && path12.startsWith("/tmp/")) {
68566
- return `C:\\temp${path12.slice(4).replace(/\//g, "\\")}`;
68572
+ if (isCentrifugoSandbox(maybeSandbox) && maybeSandbox.isWindows() && path14.startsWith("/tmp/")) {
68573
+ return `C:\\temp${path14.slice(4).replace(/\//g, "\\")}`;
68567
68574
  }
68568
- return path12;
68575
+ return path14;
68569
68576
  };
68570
68577
  stripTrailingWs = (line) => line.replace(/[ \t]+$/u, "");
68571
68578
  editSchema = external_exports.object({
@@ -68639,7 +68646,7 @@ ${instructionsDescription}`,
68639
68646
  "A list of edits to be sequentially applied to the file. Required for `edit` action."
68640
68647
  )
68641
68648
  }),
68642
- execute: async ({ action, path: path12, text: text2, range, edits }) => {
68649
+ execute: async ({ action, path: path14, text: text2, range, edits }) => {
68643
68650
  try {
68644
68651
  const { sandbox } = await sandboxManager.getSandbox();
68645
68652
  switch (action) {
@@ -68649,7 +68656,7 @@ ${instructionsDescription}`,
68649
68656
  captureFileViewImageUsage({
68650
68657
  context: context2,
68651
68658
  sandbox,
68652
- path: path12,
68659
+ path: path14,
68653
68660
  outcome: "unsupported_model",
68654
68661
  durationMs: Date.now() - viewStartedAt,
68655
68662
  failureReason: "unsupported_model"
@@ -68658,26 +68665,26 @@ ${instructionsDescription}`,
68658
68665
  }
68659
68666
  let viewPayload;
68660
68667
  try {
68661
- viewPayload = await readSandboxFileForView(sandbox, path12, false);
68668
+ viewPayload = await readSandboxFileForView(sandbox, path14, false);
68662
68669
  } catch (error51) {
68663
68670
  captureFileViewImageUsage({
68664
68671
  context: context2,
68665
68672
  sandbox,
68666
- path: path12,
68673
+ path: path14,
68667
68674
  outcome: "inspection_failed",
68668
68675
  durationMs: Date.now() - viewStartedAt,
68669
68676
  failureReason: classifyFileViewError(error51)
68670
68677
  });
68671
68678
  throw error51;
68672
68679
  }
68673
- const filename = getFilename(path12);
68680
+ const filename = getFilename(path14);
68674
68681
  let previewFiles = [];
68675
68682
  let previewUploadError;
68676
68683
  try {
68677
68684
  previewFiles = await uploadViewPreviewFiles({
68678
68685
  context: context2,
68679
68686
  sandbox,
68680
- sourcePath: path12,
68687
+ sourcePath: path14,
68681
68688
  payload: viewPayload
68682
68689
  });
68683
68690
  } catch (error51) {
@@ -68691,7 +68698,7 @@ ${instructionsDescription}`,
68691
68698
  user_id: context2.userID,
68692
68699
  sandbox_type: getViewSandboxType(sandbox),
68693
68700
  file_name: filename,
68694
- source_path: path12,
68701
+ source_path: path14,
68695
68702
  kind: viewPayload.kind,
68696
68703
  media_type: viewPayload.mediaType,
68697
68704
  size_bytes: viewPayload.sizeBytes,
@@ -68702,7 +68709,7 @@ ${instructionsDescription}`,
68702
68709
  captureFileViewImageUsage({
68703
68710
  context: context2,
68704
68711
  sandbox,
68705
- path: path12,
68712
+ path: path14,
68706
68713
  outcome: "success",
68707
68714
  durationMs: Date.now() - viewStartedAt,
68708
68715
  mediaType: viewPayload.mediaType,
@@ -68712,7 +68719,7 @@ ${instructionsDescription}`,
68712
68719
  return {
68713
68720
  action: "view",
68714
68721
  content: `Viewing image file: ${filename} (${viewPayload.mediaType}, ${viewPayload.sizeBytes} bytes).`,
68715
- path: path12,
68722
+ path: path14,
68716
68723
  filename,
68717
68724
  mediaType: viewPayload.mediaType,
68718
68725
  sizeBytes: viewPayload.sizeBytes,
@@ -68722,8 +68729,8 @@ ${instructionsDescription}`,
68722
68729
  };
68723
68730
  }
68724
68731
  case "read": {
68725
- const filename = path12.split("/").pop() || path12;
68726
- const spritChunked = isSpritPath(path12);
68732
+ const filename = path14.split("/").pop() || path14;
68733
+ const spritChunked = isSpritPath(path14);
68727
68734
  let effectiveRange = range;
68728
68735
  if (spritChunked) {
68729
68736
  const start = range && range[0] > 0 ? range[0] : 1;
@@ -68736,7 +68743,7 @@ ${instructionsDescription}`,
68736
68743
  }
68737
68744
  const readPayload = await readSandboxTextFileWithFallback(
68738
68745
  sandbox,
68739
- path12,
68746
+ path14,
68740
68747
  effectiveRange
68741
68748
  );
68742
68749
  if (readPayload.tooLarge) {
@@ -68773,46 +68780,46 @@ File is too large to read in full (${formatBytes(readPayload.sizeBytes)}, ${tota
68773
68780
  if (text2 === void 0) {
68774
68781
  return { error: "text is required for write action" };
68775
68782
  }
68776
- await sandbox.files.write(path12, text2, {
68783
+ await sandbox.files.write(path14, text2, {
68777
68784
  user: "user"
68778
68785
  });
68779
- return `File written: ${path12}`;
68786
+ return `File written: ${path14}`;
68780
68787
  }
68781
68788
  case "append": {
68782
68789
  if (text2 === void 0) {
68783
68790
  return { error: "text is required for append action" };
68784
68791
  }
68785
- const existingState = await getSandboxFileState(sandbox, path12);
68792
+ const existingState = await getSandboxFileState(sandbox, path14);
68786
68793
  if (existingState.kind === "unknown") {
68787
68794
  return {
68788
- error: `Cannot append safely because the existing file size could not be determined for ${path12}. ${existingState.error}`
68795
+ error: `Cannot append safely because the existing file size could not be determined for ${path14}. ${existingState.error}`
68789
68796
  };
68790
68797
  }
68791
68798
  if (existingState.kind === "not_file") {
68792
68799
  return {
68793
- error: `Cannot append to ${path12} because it is not a file.`
68800
+ error: `Cannot append to ${path14} because it is not a file.`
68794
68801
  };
68795
68802
  }
68796
68803
  if (existingState.kind === "file" && existingState.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
68797
- await appendSandboxTextFile(sandbox, path12, text2);
68804
+ await appendSandboxTextFile(sandbox, path14, text2);
68798
68805
  return {
68799
- content: `File appended: ${path12}
68806
+ content: `File appended: ${path14}
68800
68807
  Existing file is ${formatBytes(existingState.sizeBytes)}, so the full diff preview was skipped to avoid loading the entire file into memory.`
68801
68808
  };
68802
68809
  }
68803
68810
  let existingContent = "";
68804
68811
  try {
68805
- existingContent = await sandbox.files.read(path12, {
68812
+ existingContent = await sandbox.files.read(path14, {
68806
68813
  user: "user"
68807
68814
  });
68808
68815
  } catch {
68809
68816
  }
68810
68817
  const newContent = existingContent + text2;
68811
- await sandbox.files.write(path12, newContent, {
68818
+ await sandbox.files.write(path14, newContent, {
68812
68819
  user: "user"
68813
68820
  });
68814
68821
  return {
68815
- content: `File appended: ${path12}`,
68822
+ content: `File appended: ${path14}`,
68816
68823
  originalContent: truncateOutput({
68817
68824
  content: existingContent,
68818
68825
  mode: "read-file"
@@ -68827,31 +68834,31 @@ Existing file is ${formatBytes(existingState.sizeBytes)}, so the full diff previ
68827
68834
  if (!edits || edits.length === 0) {
68828
68835
  return { error: "edits array is required for edit action" };
68829
68836
  }
68830
- const existingState = await getSandboxFileState(sandbox, path12);
68837
+ const existingState = await getSandboxFileState(sandbox, path14);
68831
68838
  if (existingState.kind === "unknown") {
68832
68839
  return {
68833
- error: `Cannot edit ${path12} safely because the file size could not be determined. ${existingState.error}`
68840
+ error: `Cannot edit ${path14} safely because the file size could not be determined. ${existingState.error}`
68834
68841
  };
68835
68842
  }
68836
68843
  if (existingState.kind === "missing") {
68837
68844
  return {
68838
- error: `Cannot edit file ${path12} - file is empty or does not exist`
68845
+ error: `Cannot edit file ${path14} - file is empty or does not exist`
68839
68846
  };
68840
68847
  }
68841
68848
  if (existingState.kind === "not_file") {
68842
- return { error: `Cannot edit ${path12} because it is not a file.` };
68849
+ return { error: `Cannot edit ${path14} because it is not a file.` };
68843
68850
  }
68844
68851
  if (existingState.sizeBytes > MAX_TEXT_FILE_READ_BYTES) {
68845
68852
  return {
68846
- error: `File ${path12} is too large for the edit action (${formatBytes(existingState.sizeBytes)}). Use a targeted shell command, restore the file from a clean source, or replace it with the write action instead of loading the whole file into memory.`
68853
+ error: `File ${path14} is too large for the edit action (${formatBytes(existingState.sizeBytes)}). Use a targeted shell command, restore the file from a clean source, or replace it with the write action instead of loading the whole file into memory.`
68847
68854
  };
68848
68855
  }
68849
- const originalContent = await sandbox.files.read(path12, {
68856
+ const originalContent = await sandbox.files.read(path14, {
68850
68857
  user: "user"
68851
68858
  });
68852
68859
  if (!originalContent) {
68853
68860
  return {
68854
- error: `Cannot edit file ${path12} - file is empty or does not exist`
68861
+ error: `Cannot edit file ${path14} - file is empty or does not exist`
68855
68862
  };
68856
68863
  }
68857
68864
  const resolvedEdits = [];
@@ -68896,7 +68903,7 @@ ${hint}` : "")
68896
68903
  }
68897
68904
  }
68898
68905
  }
68899
- await sandbox.files.write(path12, content, {
68906
+ await sandbox.files.write(path14, content, {
68900
68907
  user: "user"
68901
68908
  });
68902
68909
  const lines = content.split("\n");
@@ -68988,419 +68995,483 @@ ${numberedLines}`,
68988
68995
  }
68989
68996
  });
68990
68997
 
68991
- // ../lib/utils/error-redaction.ts
68992
- var REDACTED_VALUE, SENSITIVE_FIELD_PATTERN, ENV_SECRET_PATTERN, redactSensitiveErrorMessage, stringifyRedactedError;
68993
- var init_error_redaction = __esm({
68994
- "../lib/utils/error-redaction.ts"() {
68995
- "use strict";
68996
- REDACTED_VALUE = "[Redacted]";
68997
- SENSITIVE_FIELD_PATTERN = /(["']?\b(?:serviceKey|service_key|apiKey|api_key|authorization|bearer|cookie|password|secret|token)\b["']?)(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,}]+)/gi;
68998
- ENV_SECRET_PATTERN = /(["']?\b(?:CONVEX_SERVICE_ROLE_KEY|POSTHOG_API_KEY|STRIPE_SECRET_KEY)\b["']?)(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,}]+)/gi;
68999
- redactSensitiveErrorMessage = (message) => message.replace(SENSITIVE_FIELD_PATTERN, (_match, key, separator) => {
69000
- return `${key}${separator}"${REDACTED_VALUE}"`;
69001
- }).replace(ENV_SECRET_PATTERN, (_match, key, separator) => {
69002
- return `${key}${separator}"${REDACTED_VALUE}"`;
69003
- });
69004
- stringifyRedactedError = (error51) => {
69005
- const message = error51 instanceof Error ? error51.message : typeof error51 === "string" ? error51 : (() => {
68998
+ // src/tools/web-research.ts
68999
+ function decodeHtmlEntities(input) {
69000
+ return input.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, code) => {
69001
+ if (code[0] === "#") {
69002
+ const num = code[1] === "x" || code[1] === "X" ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10);
69003
+ if (Number.isFinite(num) && num > 0 && num <= 1114111) {
69006
69004
  try {
69007
- return JSON.stringify(error51);
69005
+ return String.fromCodePoint(num);
69008
69006
  } catch {
69009
- return String(error51);
69007
+ return whole;
69010
69008
  }
69011
- })();
69012
- return redactSensitiveErrorMessage(message);
69013
- };
69014
- }
69015
- });
69016
-
69017
- // ../lib/ai/tools/utils/perplexity.ts
69018
- var SEARCH_RESULT_CONTENT_MAX_TOKENS, RECENCY_MAP, PerplexityApiError, ERROR_BODY_SUMMARY_MAX_LENGTH, RETRYABLE_PERPLEXITY_STATUSES, HTML_ENTITY_MAP, normalizeWhitespace, decodeBasicHtmlEntities, stripHtml, extractHtmlTagText, redactNetworkDetails, truncateSummary, isRetryablePerplexityStatus, summarizePerplexityErrorBody, buildPerplexitySearchBody, formatSearchResults;
69019
- var init_perplexity = __esm({
69020
- "../lib/ai/tools/utils/perplexity.ts"() {
69021
- "use strict";
69022
- SEARCH_RESULT_CONTENT_MAX_TOKENS = 250;
69023
- RECENCY_MAP = {
69024
- past_day: "day",
69025
- past_week: "week",
69026
- past_month: "month",
69027
- past_year: "year"
69028
- };
69029
- PerplexityApiError = class extends Error {
69030
- constructor({
69031
- status,
69032
- statusText,
69033
- bodySummary,
69034
- retryable
69035
- }) {
69036
- const statusLabel = statusText ? `${status} ${statusText}` : `${status}`;
69037
- const summary = bodySummary ? `: ${bodySummary}` : "";
69038
- super(`Perplexity API error ${statusLabel}${summary}`);
69039
- this.name = "PerplexityApiError";
69040
- this.status = status;
69041
- this.statusText = statusText;
69042
- this.bodySummary = bodySummary;
69043
- this.retryable = retryable;
69044
69009
  }
69010
+ return whole;
69011
+ }
69012
+ return NAMED_ENTITIES[code] != null ? NAMED_ENTITIES[code] : whole;
69013
+ });
69014
+ }
69015
+ function htmlToText(html) {
69016
+ let s = html;
69017
+ s = s.replace(/<!--[\s\S]*?-->/g, " ");
69018
+ s = s.replace(/<(script|style|noscript|svg|head|template)[\s\S]*?<\/\1>/gi, " ");
69019
+ s = s.replace(
69020
+ /<\/(p|div|section|article|header|footer|li|ul|ol|tr|table|h[1-6]|blockquote|pre|nav|aside)>/gi,
69021
+ "\n"
69022
+ );
69023
+ s = s.replace(/<br\s*\/?>/gi, "\n");
69024
+ s = s.replace(/<[^>]+>/g, " ");
69025
+ s = decodeHtmlEntities(s);
69026
+ s = s.replace(/[ \t\f\v\r]+/g, " ");
69027
+ s = s.replace(/\n{3,}/g, "\n\n");
69028
+ s = s.split("\n").map((line) => line.trim()).join("\n").trim();
69029
+ return s;
69030
+ }
69031
+ function extractTitle(html) {
69032
+ const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
69033
+ return m ? htmlToText(m[1]).slice(0, 200) : "";
69034
+ }
69035
+ function decodeDdgHref(href) {
69036
+ let h = (href || "").trim();
69037
+ if (!h) return "";
69038
+ if (h.startsWith("//")) h = "https:" + h;
69039
+ try {
69040
+ const u = new URL(h, "https://duckduckgo.com");
69041
+ const uddg = u.searchParams.get("uddg");
69042
+ if (uddg) return uddg;
69043
+ return u.href;
69044
+ } catch {
69045
+ return h;
69046
+ }
69047
+ }
69048
+ function parseDuckDuckGoHtml(html) {
69049
+ const snippets = [];
69050
+ const snippetRe = /<a[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
69051
+ let sm;
69052
+ while (sm = snippetRe.exec(html)) snippets.push(htmlToText(sm[1]));
69053
+ const results = [];
69054
+ const linkRe = /<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
69055
+ let m;
69056
+ let i = 0;
69057
+ while (m = linkRe.exec(html)) {
69058
+ const url2 = decodeDdgHref(m[1]);
69059
+ const title = htmlToText(m[2]);
69060
+ if (url2 && title && /^https?:\/\//i.test(url2)) {
69061
+ results.push({ title, url: url2, snippet: snippets[i] || "" });
69062
+ }
69063
+ i++;
69064
+ }
69065
+ return results;
69066
+ }
69067
+ function parseDuckDuckGoLite(html) {
69068
+ const results = [];
69069
+ const linkRe = /<a[^>]+class="[^"]*result-link[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
69070
+ let m;
69071
+ while (m = linkRe.exec(html)) {
69072
+ const url2 = decodeDdgHref(m[1]);
69073
+ const title = htmlToText(m[2]);
69074
+ if (url2 && title && /^https?:\/\//i.test(url2)) {
69075
+ results.push({ title, url: url2, snippet: "" });
69076
+ }
69077
+ }
69078
+ return results;
69079
+ }
69080
+ function canonicalUrl(raw) {
69081
+ try {
69082
+ const u = new URL(raw.trim());
69083
+ if (u.protocol !== "http:" && u.protocol !== "https:") return null;
69084
+ u.hash = "";
69085
+ let s = u.href;
69086
+ if (s.endsWith("/")) s = s.slice(0, -1);
69087
+ return s;
69088
+ } catch {
69089
+ return null;
69090
+ }
69091
+ }
69092
+ function dedupeResults(results) {
69093
+ const seen = /* @__PURE__ */ new Set();
69094
+ const out3 = [];
69095
+ for (const r2 of results) {
69096
+ const key = canonicalUrl(r2.url);
69097
+ if (!key || seen.has(key)) continue;
69098
+ seen.add(key);
69099
+ out3.push(r2);
69100
+ }
69101
+ return out3;
69102
+ }
69103
+ async function mapPool(items, concurrency, fn) {
69104
+ const results = new Array(items.length);
69105
+ if (items.length === 0) return results;
69106
+ const workers = [];
69107
+ const n = Math.min(Math.max(1, concurrency), items.length);
69108
+ let next = 0;
69109
+ for (let w = 0; w < n; w++) {
69110
+ workers.push(
69111
+ (async () => {
69112
+ for (; ; ) {
69113
+ const i = next++;
69114
+ if (i >= items.length) break;
69115
+ try {
69116
+ results[i] = await fn(items[i], i);
69117
+ } catch (err) {
69118
+ results[i] = {
69119
+ error: err instanceof Error ? err.message : String(err)
69120
+ };
69121
+ }
69122
+ }
69123
+ })()
69124
+ );
69125
+ }
69126
+ await Promise.all(workers);
69127
+ return results;
69128
+ }
69129
+ async function fetchText(url2, opts) {
69130
+ const ctrl = new AbortController();
69131
+ const onAbort = () => ctrl.abort();
69132
+ if (opts.signal) {
69133
+ if (opts.signal.aborted) ctrl.abort();
69134
+ else opts.signal.addEventListener("abort", onAbort, { once: true });
69135
+ }
69136
+ const timer2 = setTimeout(() => ctrl.abort(), opts.timeoutMs);
69137
+ try {
69138
+ const resp = await fetch(url2, {
69139
+ method: "GET",
69140
+ headers: {
69141
+ "User-Agent": UA,
69142
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
69143
+ "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8"
69144
+ },
69145
+ redirect: "follow",
69146
+ signal: ctrl.signal
69147
+ });
69148
+ let text2 = await resp.text();
69149
+ if (text2.length > MAX_RAW_BYTES) text2 = text2.slice(0, MAX_RAW_BYTES);
69150
+ return {
69151
+ status: resp.status,
69152
+ finalUrl: resp.url || url2,
69153
+ contentType: resp.headers.get("content-type") || "",
69154
+ text: text2
69045
69155
  };
69046
- ERROR_BODY_SUMMARY_MAX_LENGTH = 400;
69047
- RETRYABLE_PERPLEXITY_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
69048
- HTML_ENTITY_MAP = {
69049
- amp: "&",
69050
- gt: ">",
69051
- lt: "<",
69052
- nbsp: " ",
69053
- quot: '"',
69054
- "#160": " ",
69055
- "#39": "'"
69056
- };
69057
- normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
69058
- decodeBasicHtmlEntities = (value) => value.replace(/&([a-zA-Z0-9#]+);/g, (match, entity) => {
69059
- return HTML_ENTITY_MAP[entity] ?? match;
69156
+ } finally {
69157
+ clearTimeout(timer2);
69158
+ opts.signal?.removeEventListener("abort", onAbort);
69159
+ }
69160
+ }
69161
+ async function searchOneQuery(query, signal, df) {
69162
+ const enc = encodeURIComponent(query);
69163
+ const dfParam = df ? `&df=${df}` : "";
69164
+ try {
69165
+ const r2 = await fetchText(
69166
+ `https://html.duckduckgo.com/html/?q=${enc}${dfParam}`,
69167
+ { timeoutMs: SEARCH_TIMEOUT_MS, signal }
69168
+ );
69169
+ const parsed = parseDuckDuckGoHtml(r2.text);
69170
+ if (parsed.length) return parsed;
69171
+ } catch (err) {
69172
+ if (isAbort(err)) throw err;
69173
+ }
69174
+ try {
69175
+ const r2 = await fetchText(
69176
+ `https://lite.duckduckgo.com/lite/?q=${enc}${dfParam}`,
69177
+ { timeoutMs: SEARCH_TIMEOUT_MS, signal }
69178
+ );
69179
+ return parseDuckDuckGoLite(r2.text);
69180
+ } catch (err) {
69181
+ if (isAbort(err)) throw err;
69182
+ return [];
69183
+ }
69184
+ }
69185
+ async function searchQueries(queries, signal, df) {
69186
+ const lists = await mapPool(
69187
+ queries,
69188
+ Math.min(queries.length, 5),
69189
+ (q) => searchOneQuery(q, signal, df)
69190
+ );
69191
+ const merged = [];
69192
+ for (const item of lists) {
69193
+ if (Array.isArray(item)) merged.push(...item);
69194
+ }
69195
+ return dedupeResults(merged);
69196
+ }
69197
+ async function readPage(url2, signal) {
69198
+ const r2 = await fetchText(url2, { timeoutMs: READ_TIMEOUT_MS, signal });
69199
+ const text2 = isTextual(r2.contentType) ? htmlToText(r2.text) : "";
69200
+ return {
69201
+ url: url2,
69202
+ finalUrl: r2.finalUrl,
69203
+ status: r2.status,
69204
+ title: extractTitle(r2.text) || r2.finalUrl,
69205
+ text: text2,
69206
+ bytes: r2.text.length,
69207
+ contentType: r2.contentType
69208
+ };
69209
+ }
69210
+ async function jinaRead(url2, signal) {
69211
+ const key = process.env.JINA_API_KEY;
69212
+ if (!key) return null;
69213
+ try {
69214
+ const resp = await fetch(`https://r.jina.ai/${url2}`, {
69215
+ headers: { Authorization: `Bearer ${key}`, "X-Timeout": "25" },
69216
+ signal
69060
69217
  });
69061
- stripHtml = (value) => normalizeWhitespace(
69062
- decodeBasicHtmlEntities(
69063
- value.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ")
69064
- )
69218
+ if (!resp.ok) return null;
69219
+ return await resp.text();
69220
+ } catch {
69221
+ return null;
69222
+ }
69223
+ }
69224
+ function formatSearchResults(results) {
69225
+ if (!results.length) {
69226
+ return "Nenhum resultado encontrado. Refine a query (sin\xF4nimos, termos em ingl\xEAs, operadores site:/filetype:) e tente de novo.";
69227
+ }
69228
+ const shown = results.slice(0, SEARCH_MAX_RESULTS);
69229
+ const lines = [`${shown.length} resultado(s):`];
69230
+ shown.forEach((r2, i) => {
69231
+ lines.push("");
69232
+ lines.push(`${i + 1}. ${r2.title}`);
69233
+ lines.push(` ${r2.url}`);
69234
+ if (r2.snippet) lines.push(` ${truncate3(r2.snippet, 300)}`);
69235
+ });
69236
+ return lines.join("\n");
69237
+ }
69238
+ function formatResearchDigest(pages, stats) {
69239
+ const header = `Pesquisa profunda: ${stats.totalFound} candidato(s), ${stats.fetched} buscado(s) (${stats.conc ?? 1} em paralelo), ${stats.ok} ok, ${stats.failures} falha(s)` + (stats.capped ? `, capado em ${stats.maxPages} p\xE1ginas` : "") + ".";
69240
+ if (!pages.length) {
69241
+ return header + "\nNenhuma p\xE1gina retornou texto. Refine as queries ou forne\xE7a urls e tente de novo.";
69242
+ }
69243
+ const INLINE = 40;
69244
+ const lines = [header, ""];
69245
+ pages.slice(0, INLINE).forEach((p, i) => {
69246
+ lines.push(`### ${i + 1}. ${p.title}`);
69247
+ lines.push(`${p.finalUrl} (HTTP ${p.status})`);
69248
+ const extract = p.text.replace(/\n+/g, " ").trim();
69249
+ if (extract) lines.push(truncate3(extract, 600));
69250
+ lines.push("");
69251
+ });
69252
+ if (pages.length > INLINE) {
69253
+ lines.push(`\u2026 +${pages.length - INLINE} fonte(s) no relat\xF3rio completo.`);
69254
+ }
69255
+ if (stats.reportPath) {
69256
+ lines.push(`Relat\xF3rio completo salvo em: ${stats.reportPath}`);
69257
+ }
69258
+ return lines.join("\n");
69259
+ }
69260
+ async function saveResearchReport(workdir, queries, pages, stats) {
69261
+ if (!workdir || !pages.length) return null;
69262
+ try {
69263
+ const dir = import_node_path5.default.join(workdir, "reports");
69264
+ await import_node_fs6.promises.mkdir(dir, { recursive: true });
69265
+ const slug = (queries[0] || "research").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "research";
69266
+ const file2 = import_node_path5.default.join(dir, `research_${slug}_${Date.now()}.md`);
69267
+ const parts = [];
69268
+ parts.push(`# Pesquisa: ${queries.join(" / ") || "(seed urls)"}`);
69269
+ parts.push("");
69270
+ parts.push(
69271
+ `Candidatos: ${stats.totalFound} \xB7 Buscados: ${stats.fetched} \xB7 OK: ${stats.ok} \xB7 Falhas: ${stats.failures}` + (stats.capped ? ` \xB7 capado em ${stats.maxPages}` : "")
69065
69272
  );
69066
- extractHtmlTagText = (html, tagName) => {
69067
- const matches = html.matchAll(
69068
- new RegExp(`<${tagName}\\b[^>]*>([\\s\\S]*?)<\\/${tagName}>`, "gi")
69069
- );
69070
- return Array.from(matches).map((match) => stripHtml(match[1] ?? "")).filter(Boolean);
69071
- };
69072
- redactNetworkDetails = (value) => value.replace(
69073
- /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g,
69074
- "[Redacted IP]"
69075
- ).replace(/\bRay ID:\s*[a-f0-9]+\b/gi, "Ray ID: [Redacted]");
69076
- truncateSummary = (value) => value.length > ERROR_BODY_SUMMARY_MAX_LENGTH ? `${value.slice(0, ERROR_BODY_SUMMARY_MAX_LENGTH - 1)}\u2026` : value;
69077
- isRetryablePerplexityStatus = (status) => RETRYABLE_PERPLEXITY_STATUSES.has(status);
69078
- summarizePerplexityErrorBody = (body, contentType = "") => {
69079
- const trimmed = body.trim();
69080
- if (!trimmed) return "";
69081
- let summary = "";
69082
- if (contentType.includes("json") || trimmed.startsWith("{")) {
69083
- try {
69084
- const parsed = JSON.parse(trimmed);
69085
- const error51 = typeof parsed.error === "string" ? parsed.error : parsed.error && typeof parsed.error === "object" && "message" in parsed.error && typeof parsed.error.message === "string" ? parsed.error.message : void 0;
69086
- const message = error51 || (typeof parsed.message === "string" ? parsed.message : void 0) || (typeof parsed.detail === "string" ? parsed.detail : void 0);
69087
- summary = message || trimmed;
69088
- } catch {
69089
- summary = trimmed;
69090
- }
69091
- } else if (/<[a-z][\s\S]*>/i.test(trimmed)) {
69092
- const tagSummaries = [
69093
- ...extractHtmlTagText(trimmed, "h1"),
69094
- ...extractHtmlTagText(trimmed, "p"),
69095
- ...extractHtmlTagText(trimmed, "h2")
69096
- ];
69097
- summary = tagSummaries.length > 0 ? tagSummaries.join(". ") : stripHtml(trimmed);
69098
- } else {
69099
- summary = trimmed;
69100
- }
69101
- return truncateSummary(redactNetworkDetails(normalizeWhitespace(summary)));
69102
- };
69103
- buildPerplexitySearchBody = (query, options) => {
69104
- const searchBody = {
69105
- query,
69106
- max_results: options?.maxResults ?? 10,
69107
- max_tokens_per_page: SEARCH_RESULT_CONTENT_MAX_TOKENS
69108
- };
69109
- if (options?.country) {
69110
- searchBody.country = options.country;
69111
- }
69112
- if (options?.recency) {
69113
- searchBody.search_recency_filter = options.recency;
69114
- }
69115
- return searchBody;
69116
- };
69117
- formatSearchResults = (results) => {
69118
- return results.map((result) => ({
69119
- title: result.title,
69120
- url: result.url,
69121
- content: result.snippet,
69122
- date: result.date || null,
69123
- lastUpdated: result.last_updated || null
69124
- }));
69125
- };
69273
+ parts.push("");
69274
+ pages.forEach((p, i) => {
69275
+ parts.push(`## ${i + 1}. ${p.title}`);
69276
+ parts.push(`- URL: ${p.finalUrl}`);
69277
+ parts.push(`- HTTP: ${p.status} \xB7 ${p.bytes} bytes`);
69278
+ parts.push("");
69279
+ parts.push(truncate3(p.text, 4e3));
69280
+ parts.push("");
69281
+ });
69282
+ await import_node_fs6.promises.writeFile(file2, parts.join("\n"), "utf8");
69283
+ return file2;
69284
+ } catch {
69285
+ return null;
69126
69286
  }
69127
- });
69128
-
69129
- // ../lib/ai/tools/web-search.ts
69130
- var WEB_SEARCH_COST_PER_REQUEST, PERPLEXITY_SEARCH_URL, WEB_SEARCH_MAX_ATTEMPTS, WEB_SEARCH_RETRY_BASE_DELAY_MS, WEB_SEARCH_RETRY_JITTER_MS, PERPLEXITY_QUERY_MAX_LENGTH, EMPTY_QUERY_TOOL_ERROR, QUERY_TOO_LONG_TOOL_ERROR, webSearchQuerySchema, sleep2, getRetryDelayMs, createPerplexityApiError, formatPerplexityFailureForTool, fetchPerplexitySearch, normalizeSearchQueries, createWebSearch;
69131
- var init_web_search = __esm({
69132
- "../lib/ai/tools/web-search.ts"() {
69287
+ }
69288
+ var import_node_fs6, import_node_path5, UA, SEARCH_TIMEOUT_MS, READ_TIMEOUT_MS, MAX_RAW_BYTES, OPEN_URL_MAX_CHARS, SEARCH_MAX_RESULTS, HARD_PAGE_CEILING, TIME_TO_DDG_DF, NAMED_ENTITIES, truncate3, isAbort, isTextual, TIME_ENUM, createWebSearch, createOpenUrl, createDeepResearch;
69289
+ var init_web_research = __esm({
69290
+ "src/tools/web-research.ts"() {
69133
69291
  "use strict";
69134
69292
  init_dist5();
69135
69293
  init_zod();
69136
- init_error_redaction();
69137
- init_perplexity();
69138
- WEB_SEARCH_COST_PER_REQUEST = 5e-3;
69139
- PERPLEXITY_SEARCH_URL = "https://api.perplexity.ai/search";
69140
- WEB_SEARCH_MAX_ATTEMPTS = 3;
69141
- WEB_SEARCH_RETRY_BASE_DELAY_MS = 300;
69142
- WEB_SEARCH_RETRY_JITTER_MS = 75;
69143
- PERPLEXITY_QUERY_MAX_LENGTH = 8192;
69144
- EMPTY_QUERY_TOOL_ERROR = "Error performing web search: Provide at least one non-empty query.";
69145
- QUERY_TOO_LONG_TOOL_ERROR = `Error performing web search: Each query must be ${PERPLEXITY_QUERY_MAX_LENGTH} characters or fewer.`;
69146
- webSearchQuerySchema = external_exports.string().trim().min(1).max(PERPLEXITY_QUERY_MAX_LENGTH);
69147
- sleep2 = (delayMs, signal) => {
69148
- if (delayMs <= 0) return Promise.resolve();
69149
- if (signal?.aborted) {
69150
- return Promise.reject(new DOMException("Operation aborted", "AbortError"));
69151
- }
69152
- return new Promise((resolve2, reject) => {
69153
- const cleanup = () => signal?.removeEventListener("abort", onAbort);
69154
- const onAbort = () => {
69155
- clearTimeout(timeout);
69156
- cleanup();
69157
- reject(new DOMException("Operation aborted", "AbortError"));
69158
- };
69159
- const timeout = setTimeout(() => {
69160
- cleanup();
69161
- resolve2();
69162
- }, delayMs);
69163
- signal?.addEventListener("abort", onAbort, { once: true });
69164
- });
69165
- };
69166
- getRetryDelayMs = (attemptIndex) => {
69167
- const exponentialDelay = WEB_SEARCH_RETRY_BASE_DELAY_MS * Math.pow(2, attemptIndex);
69168
- const jitter = Math.random() * WEB_SEARCH_RETRY_JITTER_MS;
69169
- return Math.round(exponentialDelay + jitter);
69170
- };
69171
- createPerplexityApiError = async (response) => {
69172
- const errorText = await response.text();
69173
- const bodySummary = summarizePerplexityErrorBody(
69174
- errorText,
69175
- response.headers.get("content-type") || ""
69176
- );
69177
- return new PerplexityApiError({
69178
- status: response.status,
69179
- statusText: response.statusText,
69180
- bodySummary,
69181
- retryable: isRetryablePerplexityStatus(response.status)
69182
- });
69183
- };
69184
- formatPerplexityFailureForTool = (error51, attempts) => {
69185
- const statusText = error51.statusText ? ` ${error51.statusText}` : "";
69186
- if (error51.retryable) {
69187
- return `Error performing web search: Perplexity search is temporarily unavailable (HTTP ${error51.status}${statusText} after ${attempts} attempts). Please retry shortly or continue without live web results if the task can proceed.`;
69188
- }
69189
- if (error51.status === 401 || error51.status === 403) {
69190
- return `Error performing web search: Perplexity search is not authorized (HTTP ${error51.status}${statusText}). Check the Perplexity API key or account access.`;
69191
- }
69192
- return `Error performing web search: Perplexity search failed (HTTP ${error51.status}${statusText}).`;
69294
+ import_node_fs6 = require("node:fs");
69295
+ import_node_path5 = __toESM(require("node:path"));
69296
+ UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36";
69297
+ SEARCH_TIMEOUT_MS = 15e3;
69298
+ READ_TIMEOUT_MS = 25e3;
69299
+ MAX_RAW_BYTES = 15e5;
69300
+ OPEN_URL_MAX_CHARS = 8e3;
69301
+ SEARCH_MAX_RESULTS = 30;
69302
+ HARD_PAGE_CEILING = 500;
69303
+ TIME_TO_DDG_DF = {
69304
+ past_day: "d",
69305
+ past_week: "w",
69306
+ past_month: "m",
69307
+ past_year: "y"
69308
+ };
69309
+ NAMED_ENTITIES = {
69310
+ amp: "&",
69311
+ lt: "<",
69312
+ gt: ">",
69313
+ quot: '"',
69314
+ apos: "'",
69315
+ nbsp: " ",
69316
+ "#39": "'"
69193
69317
  };
69194
- fetchPerplexitySearch = async (searchBody, abortSignal) => {
69195
- for (let attemptIndex = 0; attemptIndex < WEB_SEARCH_MAX_ATTEMPTS; attemptIndex++) {
69196
- const attempt = attemptIndex + 1;
69197
- const isFinalAttempt = attempt === WEB_SEARCH_MAX_ATTEMPTS;
69318
+ truncate3 = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
69319
+ isAbort = (err) => err instanceof Error && err.name === "AbortError";
69320
+ isTextual = (ct) => !ct || /text|html|json|xml|javascript|csv/i.test(ct);
69321
+ TIME_ENUM = external_exports.enum(["all", "past_day", "past_week", "past_month", "past_year"]).optional();
69322
+ createWebSearch = (_context) => tool({
69323
+ description: `Search the web for free (no API key required, DuckDuckGo-backed). Returns ranked results (title, url, snippet).
69324
+
69325
+ <instructions>
69326
+ - Give 1\u20135 \`queries\` that are VARIANTS of the SAME intent (query expansions), not different goals. For non-English topics add one English variant.
69327
+ - Break a complex need into step-by-step searches instead of one giant query.
69328
+ - You CAN use operators: site:, filetype:, inurl:, intitle:.
69329
+ - After searching, OPEN the most relevant results with open_url to actually read them; cross-check facts across 2+ sources.
69330
+ - If results are weak, refine the wording and search again.
69331
+ </instructions>`,
69332
+ inputSchema: external_exports.object({
69333
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this search."),
69334
+ queries: external_exports.array(external_exports.string().trim().min(1).max(2e3)).min(1).max(5).describe("1\u20135 query variants of the same intent."),
69335
+ time: TIME_ENUM.describe(
69336
+ "Optional recency filter (past_day/week/month/year)."
69337
+ )
69338
+ }),
69339
+ execute: async ({ queries, time: time3 }, { abortSignal } = {}) => {
69340
+ const norm = Array.from(
69341
+ new Set(queries.map((q) => q.trim()).filter(Boolean))
69342
+ ).slice(0, 5);
69343
+ if (!norm.length) return "Erro: forne\xE7a ao menos uma query n\xE3o vazia.";
69198
69344
  try {
69199
- const response = await fetch(PERPLEXITY_SEARCH_URL, {
69200
- method: "POST",
69201
- headers: {
69202
- "Content-Type": "application/json",
69203
- Authorization: `Bearer ${process.env.PERPLEXITY_API_KEY || ""}`
69204
- },
69205
- body: JSON.stringify(searchBody),
69206
- signal: abortSignal
69207
- });
69208
- if (response.ok) {
69209
- return response;
69210
- }
69211
- const error51 = await createPerplexityApiError(response);
69212
- if (!error51.retryable || isFinalAttempt) {
69213
- throw error51;
69214
- }
69215
- const delayMs = getRetryDelayMs(attemptIndex);
69216
- console.warn("Web search provider error; retrying", {
69217
- attempt,
69218
- maxAttempts: WEB_SEARCH_MAX_ATTEMPTS,
69219
- status: error51.status,
69220
- statusText: error51.statusText,
69221
- bodySummary: error51.bodySummary,
69222
- delayMs
69223
- });
69224
- await sleep2(delayMs, abortSignal);
69225
- } catch (error51) {
69226
- if (error51 instanceof Error && error51.name === "AbortError") {
69227
- throw error51;
69228
- }
69229
- if (error51 instanceof PerplexityApiError) {
69230
- throw error51;
69231
- }
69232
- if (isFinalAttempt) {
69233
- throw error51;
69234
- }
69235
- const delayMs = getRetryDelayMs(attemptIndex);
69236
- console.warn("Web search network error; retrying", {
69237
- attempt,
69238
- maxAttempts: WEB_SEARCH_MAX_ATTEMPTS,
69239
- error: stringifyRedactedError(error51),
69240
- delayMs
69241
- });
69242
- await sleep2(delayMs, abortSignal);
69345
+ const df = time3 && time3 !== "all" ? TIME_TO_DDG_DF[time3] : void 0;
69346
+ const results = await searchQueries(norm, abortSignal, df);
69347
+ return formatSearchResults(results);
69348
+ } catch (err) {
69349
+ if (isAbort(err)) return "Erro: opera\xE7\xE3o abortada.";
69350
+ return `Erro na busca: ${err instanceof Error ? err.message : String(err)}`;
69243
69351
  }
69244
69352
  }
69245
- throw new Error("Web search failed before any Perplexity response was read");
69246
- };
69247
- normalizeSearchQueries = (rawQueries) => {
69248
- const queries = rawQueries.map((query) => query.trim()).filter(Boolean);
69249
- if (queries.length === 0) {
69250
- return { queries, error: EMPTY_QUERY_TOOL_ERROR };
69251
- }
69252
- if (queries.some((query) => query.length > PERPLEXITY_QUERY_MAX_LENGTH)) {
69253
- return { queries, error: QUERY_TOO_LONG_TOOL_ERROR };
69254
- }
69255
- return { queries: queries.slice(0, 3) };
69256
- };
69257
- createWebSearch = (context2) => {
69258
- const { userLocation, onToolCost } = context2;
69259
- return tool({
69260
- description: `Search for information across various sources.
69353
+ });
69354
+ createOpenUrl = (_opts) => tool({
69355
+ description: `Open a specific URL and return its readable text content (free, no API key). Use it to actually READ a link \u2014 the one the user pasted, or one from a prior search \u2014 before you act on it.
69261
69356
 
69262
69357
  <instructions>
69263
- - MUST use this tool to access up-to-date or external information when needed; DO NOT rely solely on internal knowledge
69264
- - Each search MUST contain exactly 1 to 3 \`queries\` (NEVER more than 3). Queries MUST be variants of the same intent (i.e., query expansions), NOT different goals
69265
- - For non-English queries, MUST include at least one English query as the final variant to expand coverage
69266
- - For complex searches, MUST break down into step-by-step searches instead of using a single complex query
69267
- - Access multiple URLs from search results for comprehensive information or cross-validation
69268
- - CAN use Google dork syntax (site:, filetype:, inurl:, intitle:, etc.) for targeted reconnaissance and pentest enumeration
69269
- - Only use \`time\` parameter when explicitly required by task, otherwise leave time range unrestricted
69270
- - Prioritize cybersecurity-relevant information: CVEs, CVSS scores, exploits, PoCs, security tools, and pentest methodologies
69271
- - Include specific versions, configurations, and technical details; cite reliable sources (NIST, OWASP, CVE databases)
69272
- - For commands/installations, prioritize Kali Linux compatibility using apt or pre-installed tools
69358
+ - The URL must be public and http(s).
69359
+ - Returns the page title + extracted text (JS is not executed; for JS-heavy pages use the recon toolkit's --browser path).
69360
+ - Read carefully, then verify important facts against another source.
69273
69361
  </instructions>`,
69274
- inputSchema: external_exports.object({
69275
- queries: external_exports.array(webSearchQuerySchema).min(1).max(3).describe(
69276
- "MAXIMUM 3 non-empty query variants (1-3 items only). Express the same search intent with different wording."
69277
- ),
69278
- time: external_exports.enum(["all", "past_day", "past_week", "past_month", "past_year"]).optional().describe(
69279
- "Optional time filter to limit results to a recent time range"
69280
- ),
69281
- brief: external_exports.string().describe(
69282
- "A one-sentence preamble describing the purpose of this operation"
69283
- )
69284
- }),
69285
- execute: async ({
69286
- queries: rawQueries,
69287
- time: time3
69288
- }, { abortSignal }) => {
69289
- try {
69290
- const { queries, error: error51 } = normalizeSearchQueries(rawQueries);
69291
- if (error51) {
69292
- return error51;
69293
- }
69294
- const searchBody = buildPerplexitySearchBody(
69295
- queries.length === 1 ? queries[0] : queries,
69296
- {
69297
- country: userLocation?.country,
69298
- recency: time3 && time3 !== "all" ? RECENCY_MAP[time3] : void 0
69299
- }
69300
- );
69301
- const response = await fetchPerplexitySearch(searchBody, abortSignal);
69302
- onToolCost?.(WEB_SEARCH_COST_PER_REQUEST);
69303
- const searchResponse = await response.json();
69304
- const isMultiQuery = queries.length > 1;
69305
- let allResults;
69306
- if (isMultiQuery && Array.isArray(searchResponse.results[0])) {
69307
- allResults = searchResponse.results.flat();
69308
- } else {
69309
- allResults = searchResponse.results;
69310
- }
69311
- return formatSearchResults(allResults);
69312
- } catch (error51) {
69313
- if (error51 instanceof Error && error51.name === "AbortError") {
69314
- return "Error: Operation aborted";
69315
- }
69316
- if (error51 instanceof PerplexityApiError) {
69317
- console.error("Web search tool error:", {
69318
- name: error51.name,
69319
- status: error51.status,
69320
- statusText: error51.statusText,
69321
- retryable: error51.retryable,
69322
- bodySummary: error51.bodySummary
69323
- });
69324
- return formatPerplexityFailureForTool(
69325
- error51,
69326
- error51.retryable ? WEB_SEARCH_MAX_ATTEMPTS : 1
69327
- );
69328
- }
69329
- const errorMessage = stringifyRedactedError(error51);
69330
- console.error("Web search tool error:", errorMessage);
69331
- return `Error performing web search: ${errorMessage}`;
69332
- }
69362
+ inputSchema: external_exports.object({
69363
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this fetch."),
69364
+ url: external_exports.string().describe("The http(s) URL to open and read.")
69365
+ }),
69366
+ execute: async ({ url: url2 }, { abortSignal } = {}) => {
69367
+ const canon = canonicalUrl(url2);
69368
+ if (!canon) return `URL inv\xE1lida: ${url2}`;
69369
+ try {
69370
+ const jina = await jinaRead(canon, abortSignal);
69371
+ if (jina) return truncate3(jina, OPEN_URL_MAX_CHARS * 2);
69372
+ const page = await readPage(canon, abortSignal);
69373
+ if (!isTextual(page.contentType)) {
69374
+ return `(${page.finalUrl}) conte\xFAdo n\xE3o textual (${page.contentType}, ${page.bytes} bytes) \u2014 n\xE3o extra\xEDdo.`;
69375
+ }
69376
+ const head = `# ${page.title}
69377
+ ${page.finalUrl} (HTTP ${page.status}, ${page.bytes} bytes)
69378
+ `;
69379
+ const body = page.text || "(sem texto extra\xEDvel \u2014 pode ser JS puro; use a recon toolkit com --browser)";
69380
+ return head + "\n" + truncate3(body, OPEN_URL_MAX_CHARS);
69381
+ } catch (err) {
69382
+ if (isAbort(err)) return "Erro: opera\xE7\xE3o abortada.";
69383
+ return `Erro ao abrir URL: ${err instanceof Error ? err.message : String(err)}`;
69333
69384
  }
69334
- });
69335
- };
69336
- }
69337
- });
69338
-
69339
- // ../lib/ai/tools/open-url.ts
69340
- var createOpenUrlTool;
69341
- var init_open_url = __esm({
69342
- "../lib/ai/tools/open-url.ts"() {
69343
- "use strict";
69344
- init_dist5();
69345
- init_zod();
69346
- init_token_utils();
69347
- createOpenUrlTool = () => {
69348
- return tool({
69349
- description: `Retrieve the full contents of a specific webpage by URL.
69385
+ }
69386
+ });
69387
+ createDeepResearch = (opts) => tool({
69388
+ description: `Deep web research: search + fetch MANY pages concurrently and return a synthesized digest with sources. Free, no API key.
69350
69389
 
69351
69390
  <instructions>
69352
- - Use to fetch and read a specific webpage, usually obtained from a prior search
69353
- - URLs must be valid and publicly accessible
69354
- - Prioritize cybersecurity-relevant information: CVEs, CVSS scores, exploits, PoCs, security tools, and pentest methodologies
69355
- - Include specific versions, configurations, and technical details; cite reliable sources (NIST, OWASP, CVE databases)
69391
+ - Provide \`queries\` (1\u20136 variants) and/or seed \`urls\`. It searches, collects candidate links, and fetches up to \`max_pages\` of them in parallel.
69392
+ - Tune to the SUBJECT: a quick fact needs a handful of pages; a broad survey can sweep up to 500. Do NOT fetch 500 for something small.
69393
+ - Returns: per-source title + url + a short extract, plus counts. A full Markdown report is saved under the workspace's reports/ when possible.
69394
+ - Use this for broad/unknown topics and cross-validation; use open_url to drill into a single page, web_search for a quick lookup.
69356
69395
  </instructions>`,
69357
- inputSchema: external_exports.object({
69358
- url: external_exports.string().describe("The URL to open and retrieve content from"),
69359
- brief: external_exports.string().describe(
69360
- "A one-sentence preamble describing the purpose of this operation"
69361
- )
69362
- }),
69363
- execute: async ({ url: url2 }, { abortSignal }) => {
69364
- try {
69365
- const jinaUrl = `https://r.jina.ai/${encodeURIComponent(url2)}`;
69366
- const response = await fetch(jinaUrl, {
69367
- method: "GET",
69368
- headers: {
69369
- Authorization: `Bearer ${process.env.JINA_API_KEY}`,
69370
- "X-Timeout": "30",
69371
- "X-Base": "final",
69372
- "X-Token-Budget": "200000"
69373
- },
69374
- signal: abortSignal
69375
- });
69376
- if (!response.ok) {
69377
- const errorBody = await response.text();
69378
- return `Error: HTTP ${response.status} - ${errorBody}`;
69379
- }
69380
- const content = await response.text();
69381
- const truncated = truncateContent(content, void 0, 2048);
69382
- return truncated;
69383
- } catch (error51) {
69384
- if (error51 instanceof Error && error51.name === "AbortError") {
69385
- return "Error: Operation aborted";
69386
- }
69387
- console.error("Open URL tool error:", error51);
69388
- const errorMessage = error51 instanceof Error ? error51.message : "Unknown error occurred";
69389
- return `Error opening URL: ${errorMessage}`;
69390
- }
69396
+ inputSchema: external_exports.object({
69397
+ brief: external_exports.string().describe("A one-sentence preamble describing the research goal."),
69398
+ queries: external_exports.array(external_exports.string().trim().min(1).max(2e3)).max(6).optional().describe("1\u20136 search query variants of the same intent."),
69399
+ urls: external_exports.array(external_exports.string()).max(HARD_PAGE_CEILING).optional().describe("Optional seed URLs to include directly."),
69400
+ max_pages: external_exports.number().int().min(1).max(HARD_PAGE_CEILING).optional().describe("Max pages to fetch (default 12, ceiling 500). Match to the subject."),
69401
+ concurrency: external_exports.number().int().min(1).max(HARD_PAGE_CEILING).optional().describe("Parallel fetches (default 8, up to 500)."),
69402
+ time: TIME_ENUM.describe("Optional recency filter for the search step.")
69403
+ }),
69404
+ execute: async (input, { abortSignal } = {}) => {
69405
+ const queries = Array.from(
69406
+ new Set((input.queries || []).map((q) => q.trim()).filter(Boolean))
69407
+ ).slice(0, 6);
69408
+ const seeds = [];
69409
+ for (const u of input.urls || []) {
69410
+ const c = canonicalUrl(u);
69411
+ if (c) seeds.push({ title: c, url: c, snippet: "" });
69412
+ }
69413
+ if (!queries.length && !seeds.length) {
69414
+ return "Erro: forne\xE7a queries e/ou urls para pesquisar.";
69415
+ }
69416
+ const maxPages = Math.min(
69417
+ Math.max(1, input.max_pages ?? 12),
69418
+ HARD_PAGE_CEILING
69419
+ );
69420
+ const requestedConc = Math.min(
69421
+ Math.max(1, input.concurrency ?? 8),
69422
+ HARD_PAGE_CEILING
69423
+ );
69424
+ try {
69425
+ const df = input.time && input.time !== "all" ? TIME_TO_DDG_DF[input.time] : void 0;
69426
+ const searched = queries.length ? await searchQueries(queries, abortSignal, df) : [];
69427
+ const candidates = dedupeResults([...seeds, ...searched]);
69428
+ const totalFound = candidates.length;
69429
+ const capped = totalFound > maxPages;
69430
+ const toFetch = candidates.slice(0, maxPages);
69431
+ const conc = Math.min(requestedConc, toFetch.length || 1);
69432
+ const fetched = await mapPool(
69433
+ toFetch,
69434
+ conc,
69435
+ (r2) => readPage(r2.url, abortSignal)
69436
+ );
69437
+ const pages = [];
69438
+ let failures = 0;
69439
+ fetched.forEach((p) => {
69440
+ if (p && "error" in p) failures++;
69441
+ else if (p) pages.push(p);
69442
+ });
69443
+ const reportPath = await saveResearchReport(
69444
+ opts.workdir,
69445
+ queries,
69446
+ pages,
69447
+ { totalFound, fetched: toFetch.length, ok: pages.length, failures, capped, maxPages }
69448
+ );
69449
+ return formatResearchDigest(pages, {
69450
+ totalFound,
69451
+ fetched: toFetch.length,
69452
+ ok: pages.length,
69453
+ failures,
69454
+ capped,
69455
+ maxPages,
69456
+ conc,
69457
+ reportPath
69458
+ });
69459
+ } catch (err) {
69460
+ if (isAbort(err)) return "Erro: opera\xE7\xE3o abortada.";
69461
+ return `Erro na pesquisa profunda: ${err instanceof Error ? err.message : String(err)}`;
69391
69462
  }
69392
- });
69393
- };
69463
+ }
69464
+ });
69394
69465
  }
69395
69466
  });
69396
69467
 
69397
69468
  // src/tools/scope.ts
69398
- var import_node_fs6, import_node_path5, import_promises, ipv4ToInt, isIpLiteral, maskForPrefix, parseCidr, ScopeImpl, scopeFileFor, loadScope, hostFromUrl;
69469
+ var import_node_fs7, import_node_path6, import_promises, ipv4ToInt, isIpLiteral, maskForPrefix, parseCidr, ScopeImpl, scopeFileFor, loadScope, hostFromUrl;
69399
69470
  var init_scope = __esm({
69400
69471
  "src/tools/scope.ts"() {
69401
69472
  "use strict";
69402
- import_node_fs6 = __toESM(require("node:fs"));
69403
- import_node_path5 = __toESM(require("node:path"));
69473
+ import_node_fs7 = __toESM(require("node:fs"));
69474
+ import_node_path6 = __toESM(require("node:path"));
69404
69475
  import_promises = __toESM(require("node:dns/promises"));
69405
69476
  ipv4ToInt = (ip) => {
69406
69477
  const parts = ip.split(".");
@@ -69498,11 +69569,11 @@ var init_scope = __esm({
69498
69569
  return false;
69499
69570
  }
69500
69571
  };
69501
- scopeFileFor = (workdir) => import_node_path5.default.join(workdir, "recon", "scope.txt");
69572
+ scopeFileFor = (workdir) => import_node_path6.default.join(workdir, "recon", "scope.txt");
69502
69573
  loadScope = (workdir) => {
69503
69574
  const file2 = scopeFileFor(workdir);
69504
69575
  try {
69505
- const text2 = import_node_fs6.default.readFileSync(file2, "utf8");
69576
+ const text2 = import_node_fs7.default.readFileSync(file2, "utf8");
69506
69577
  return ScopeImpl.parse(text2, file2);
69507
69578
  } catch {
69508
69579
  return ScopeImpl.parse("", null);
@@ -69519,7 +69590,7 @@ var init_scope = __esm({
69519
69590
  });
69520
69591
 
69521
69592
  // src/tools/http-request.ts
69522
- var FUZZ, MAX_FUZZ, BODY_PREVIEW_CHARS, DEFAULT_TIMEOUT_S, LOOPBACK, numberEnv, sleep3, sampleUrl, NOTABLE_HEADERS, applyFuzz, buildInit, collectNotableHeaders, doRequest, summarizeFuzz, scopeRefusal, createHttpRequest;
69593
+ var FUZZ, MAX_FUZZ, BODY_PREVIEW_CHARS, DEFAULT_TIMEOUT_S, LOOPBACK, numberEnv, sleep2, sampleUrl, NOTABLE_HEADERS, applyFuzz, buildInit, collectNotableHeaders, doRequest, summarizeFuzz, scopeRefusal, createHttpRequest;
69523
69594
  var init_http_request = __esm({
69524
69595
  "src/tools/http-request.ts"() {
69525
69596
  "use strict";
@@ -69535,7 +69606,7 @@ var init_http_request = __esm({
69535
69606
  const raw = Number(process.env[name25]);
69536
69607
  return Number.isFinite(raw) && raw >= 0 ? raw : void 0;
69537
69608
  };
69538
- sleep3 = (ms, signal) => new Promise((resolve2) => {
69609
+ sleep2 = (ms, signal) => new Promise((resolve2) => {
69539
69610
  if (ms <= 0 || signal?.aborted) return resolve2();
69540
69611
  const t = setTimeout(resolve2, ms);
69541
69612
  signal?.addEventListener(
@@ -69656,7 +69727,7 @@ var init_http_request = __esm({
69656
69727
  };
69657
69728
  scopeRefusal = (host, workdir) => `Recusado: o host "${host}" n\xE3o est\xE1 no escopo autorizado. Adicione-o a ${scopeFileFor(workdir)} (formatos: example.com, *.example.com, CIDR 10.0.0.0/24, ou um IP) \u2014 somente alvos que voc\xEA possui ou tem autoriza\xE7\xE3o expl\xEDcita para testar. Edite o scope.txt com a ferramenta file e tente de novo.`;
69658
69729
  createHttpRequest = (deps) => {
69659
- const { workdir, loadScopeFn = loadScope } = deps;
69730
+ const { workdir, loadScopeFn = loadScope, unrestricted = false } = deps;
69660
69731
  const envThrottle = numberEnv("clawfast_HTTP_THROTTLE_MS");
69661
69732
  const envJitter = numberEnv("clawfast_HTTP_JITTER_MS");
69662
69733
  return tool({
@@ -69670,20 +69741,30 @@ SCOPE: every request is gated by recon/scope.txt (same allowlist as the recon to
69670
69741
 
69671
69742
  USE FOR: testing a single endpoint, replaying/tampering a captured request, parameter/path/value fuzzing, verifying an exploit by observing the real response. NOT a replacement for bulk crawling \u2014 use the Python recon toolkit for that.`,
69672
69743
  inputSchema: external_exports.object({
69673
- brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this request."),
69744
+ brief: external_exports.string().describe(
69745
+ "A one-sentence preamble describing the purpose of this request."
69746
+ ),
69674
69747
  url: external_exports.string().url().describe(
69675
69748
  "Target URL. May contain the marker FUZZ (replaced by each fuzz payload)."
69676
69749
  ),
69677
69750
  method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).optional().describe("HTTP method (default GET)."),
69678
69751
  headers: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("Request headers. Values may contain FUZZ."),
69679
69752
  body: external_exports.string().optional().describe("Request body for POST/PUT/PATCH/DELETE. May contain FUZZ."),
69680
- follow_redirects: external_exports.boolean().optional().describe("Follow 3xx redirects (default true). Set false to inspect Location."),
69681
- timeout: external_exports.number().optional().describe(`Per-request timeout in seconds (default ${DEFAULT_TIMEOUT_S}).`),
69753
+ follow_redirects: external_exports.boolean().optional().describe(
69754
+ "Follow 3xx redirects (default true). Set false to inspect Location."
69755
+ ),
69756
+ timeout: external_exports.number().optional().describe(
69757
+ `Per-request timeout in seconds (default ${DEFAULT_TIMEOUT_S}).`
69758
+ ),
69682
69759
  fuzz: external_exports.array(external_exports.string()).optional().describe(
69683
69760
  `Intruder payloads \u2014 substituted into the FUZZ marker, one request each (max ${MAX_FUZZ}).`
69684
69761
  ),
69685
- throttle_ms: external_exports.number().optional().describe("OPSEC: delay between fuzz requests in ms (overrides clawfast_HTTP_THROTTLE_MS)."),
69686
- jitter_ms: external_exports.number().optional().describe("OPSEC: random extra delay 0..jitter added to each throttle.")
69762
+ throttle_ms: external_exports.number().optional().describe(
69763
+ "OPSEC: delay between fuzz requests in ms (overrides clawfast_HTTP_THROTTLE_MS)."
69764
+ ),
69765
+ jitter_ms: external_exports.number().optional().describe(
69766
+ "OPSEC: random extra delay 0..jitter added to each throttle."
69767
+ )
69687
69768
  }),
69688
69769
  execute: async (input, { abortSignal } = {}) => {
69689
69770
  const method = input.method ?? "GET";
@@ -69693,7 +69774,7 @@ USE FOR: testing a single endpoint, replaying/tampering a captured request, para
69693
69774
  if (!host) {
69694
69775
  return { error: `URL inv\xE1lida: ${input.url}` };
69695
69776
  }
69696
- if (!LOOPBACK.has(host)) {
69777
+ if (!unrestricted && !LOOPBACK.has(host)) {
69697
69778
  const scope = loadScopeFn(workdir);
69698
69779
  const allowed = await scope.allows(host);
69699
69780
  if (!allowed) {
@@ -69733,7 +69814,13 @@ USE FOR: testing a single endpoint, replaying/tampering a captured request, para
69733
69814
  abortSignal
69734
69815
  );
69735
69816
  if ("error" in res2) {
69736
- rows.push({ payload, status: "ERR", bodyLength: 0, timeMs: res2.timeMs, error: res2.error });
69817
+ rows.push({
69818
+ payload,
69819
+ status: "ERR",
69820
+ bodyLength: 0,
69821
+ timeMs: res2.timeMs,
69822
+ error: res2.error
69823
+ });
69737
69824
  } else {
69738
69825
  rows.push({
69739
69826
  payload,
@@ -69744,7 +69831,10 @@ USE FOR: testing a single endpoint, replaying/tampering a captured request, para
69744
69831
  });
69745
69832
  }
69746
69833
  if (i < list.length - 1 && (throttle > 0 || jitter > 0)) {
69747
- await sleep3(throttle + Math.floor(Math.random() * (jitter + 1)), abortSignal);
69834
+ await sleep2(
69835
+ throttle + Math.floor(Math.random() * (jitter + 1)),
69836
+ abortSignal
69837
+ );
69748
69838
  }
69749
69839
  }
69750
69840
  return { fuzz: { host, count: rows.length, rows } };
@@ -69822,12 +69912,12 @@ ${s.bodyPreview}`
69822
69912
  });
69823
69913
 
69824
69914
  // src/tools/findings.ts
69825
- var import_node_fs7, import_node_path6, SEVERITIES, STATUSES, SEVERITY_RANK, SEVERITY_EMOJI, findingsStoreFor, readAll, writeAll, nextId, sortBySeverity, renderReport, oneLine, createFindings;
69915
+ var import_node_fs8, import_node_path7, SEVERITIES, STATUSES, SEVERITY_RANK, SEVERITY_EMOJI, findingsStoreFor, readAll, writeAll, nextId, sortBySeverity, renderReport, oneLine, createFindings;
69826
69916
  var init_findings = __esm({
69827
69917
  "src/tools/findings.ts"() {
69828
69918
  "use strict";
69829
- import_node_fs7 = __toESM(require("node:fs"));
69830
- import_node_path6 = __toESM(require("node:path"));
69919
+ import_node_fs8 = __toESM(require("node:fs"));
69920
+ import_node_path7 = __toESM(require("node:path"));
69831
69921
  init_dist5();
69832
69922
  init_zod();
69833
69923
  SEVERITIES = [
@@ -69853,16 +69943,16 @@ var init_findings = __esm({
69853
69943
  info: "\u26AA"
69854
69944
  };
69855
69945
  findingsStoreFor = (workdir) => {
69856
- const findingsDir = import_node_path6.default.join(workdir, "findings");
69946
+ const findingsDir = import_node_path7.default.join(workdir, "findings");
69857
69947
  return {
69858
69948
  findingsDir,
69859
- storeFile: import_node_path6.default.join(findingsDir, "findings.json"),
69860
- reportFile: import_node_path6.default.join(findingsDir, "report.md")
69949
+ storeFile: import_node_path7.default.join(findingsDir, "findings.json"),
69950
+ reportFile: import_node_path7.default.join(findingsDir, "report.md")
69861
69951
  };
69862
69952
  };
69863
69953
  readAll = (store) => {
69864
69954
  try {
69865
- const raw = import_node_fs7.default.readFileSync(store.storeFile, "utf8");
69955
+ const raw = import_node_fs8.default.readFileSync(store.storeFile, "utf8");
69866
69956
  const parsed = JSON.parse(raw);
69867
69957
  return Array.isArray(parsed) ? parsed : [];
69868
69958
  } catch {
@@ -69870,8 +69960,8 @@ var init_findings = __esm({
69870
69960
  }
69871
69961
  };
69872
69962
  writeAll = (store, findings) => {
69873
- import_node_fs7.default.mkdirSync(store.findingsDir, { recursive: true });
69874
- import_node_fs7.default.writeFileSync(store.storeFile, JSON.stringify(findings, null, 2), "utf8");
69963
+ import_node_fs8.default.mkdirSync(store.findingsDir, { recursive: true });
69964
+ import_node_fs8.default.writeFileSync(store.storeFile, JSON.stringify(findings, null, 2), "utf8");
69875
69965
  };
69876
69966
  nextId = (findings) => {
69877
69967
  let max = 0;
@@ -69970,7 +70060,9 @@ ACTIONS
69970
70060
  Use this together with http_request: probe \u2192 observe \u2192 record evidence \u2192 mark confirmed.`,
69971
70061
  inputSchema: external_exports.object({
69972
70062
  action: external_exports.enum(["add", "update", "list", "report"]),
69973
- brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this operation."),
70063
+ brief: external_exports.string().describe(
70064
+ "A one-sentence preamble describing the purpose of this operation."
70065
+ ),
69974
70066
  id: external_exports.string().optional().describe("Finding id (e.g. F-001) \u2014 required for update."),
69975
70067
  title: external_exports.string().optional(),
69976
70068
  severity: external_exports.enum(SEVERITIES).optional(),
@@ -70043,7 +70135,9 @@ Use this together with http_request: probe \u2192 observe \u2192 record evidence
70043
70135
  if (input.action === "list") {
70044
70136
  let filtered = findings;
70045
70137
  if (input.filter_severity) {
70046
- filtered = filtered.filter((f) => f.severity === input.filter_severity);
70138
+ filtered = filtered.filter(
70139
+ (f) => f.severity === input.filter_severity
70140
+ );
70047
70141
  }
70048
70142
  if (input.filter_status) {
70049
70143
  filtered = filtered.filter((f) => f.status === input.filter_status);
@@ -70055,8 +70149,8 @@ Use this together with http_request: probe \u2192 observe \u2192 record evidence
70055
70149
  };
70056
70150
  }
70057
70151
  const md = renderReport(findings);
70058
- import_node_fs7.default.mkdirSync(store.findingsDir, { recursive: true });
70059
- import_node_fs7.default.writeFileSync(store.reportFile, md, "utf8");
70152
+ import_node_fs8.default.mkdirSync(store.findingsDir, { recursive: true });
70153
+ import_node_fs8.default.writeFileSync(store.reportFile, md, "utf8");
70060
70154
  return { report: store.reportFile, total: findings.length };
70061
70155
  } catch (err) {
70062
70156
  return {
@@ -70286,20 +70380,48 @@ var init_utils4 = __esm({
70286
70380
 
70287
70381
  // src/local-sandbox.ts
70288
70382
  function inferShellFlag(shell2) {
70289
- const base = import_node_path7.default.basename(shell2).toLowerCase();
70383
+ const base = import_node_path8.default.basename(shell2).toLowerCase();
70290
70384
  if (base === "cmd" || base === "cmd.exe") return "/C";
70291
70385
  if (base === "powershell" || base === "powershell.exe" || base === "pwsh") {
70292
70386
  return "-Command";
70293
70387
  }
70294
70388
  return "-c";
70295
70389
  }
70296
- var import_node_child_process2, import_node_fs8, import_node_path7, import_node_os3, import_node_url, import_meta, LocalSandbox;
70390
+ function detectLinux(platform) {
70391
+ if (platform !== "linux") {
70392
+ return { distroId: "", distroName: "", isKali: false };
70393
+ }
70394
+ try {
70395
+ const text2 = (0, import_node_fs9.readFileSync)("/etc/os-release", "utf8");
70396
+ const map2 = {};
70397
+ for (const raw of text2.split(/\r?\n/)) {
70398
+ const eq = raw.indexOf("=");
70399
+ if (eq === -1) continue;
70400
+ const key = raw.slice(0, eq).trim();
70401
+ const val = raw.slice(eq + 1).trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
70402
+ if (key) map2[key] = val;
70403
+ }
70404
+ const id = (map2.ID || "").toLowerCase();
70405
+ const idLike = (map2.ID_LIKE || "").toLowerCase();
70406
+ const distroName = map2.PRETTY_NAME || map2.NAME || "Linux";
70407
+ const isKali = id === "kali" || idLike.includes("kali") || /kali/i.test(distroName);
70408
+ return { distroId: id, distroName, isKali };
70409
+ } catch {
70410
+ return { distroId: "", distroName: "Linux", isKali: false };
70411
+ }
70412
+ }
70413
+ function computeUnrestricted(platform) {
70414
+ if (truthyEnv("CLAWFAST_UNRESTRICTED")) return true;
70415
+ if (truthyEnv("CLAWFAST_STRICT_SCOPE")) return false;
70416
+ return platform === "linux";
70417
+ }
70418
+ var import_node_child_process2, import_node_fs9, import_node_path8, import_node_os3, import_node_url, import_meta, LocalSandbox, truthyEnv;
70297
70419
  var init_local_sandbox = __esm({
70298
70420
  "src/local-sandbox.ts"() {
70299
70421
  "use strict";
70300
70422
  import_node_child_process2 = require("node:child_process");
70301
- import_node_fs8 = require("node:fs");
70302
- import_node_path7 = __toESM(require("node:path"));
70423
+ import_node_fs9 = require("node:fs");
70424
+ import_node_path8 = __toESM(require("node:path"));
70303
70425
  import_node_os3 = __toESM(require("node:os"));
70304
70426
  import_node_url = require("node:url");
70305
70427
  init_utils4();
@@ -70311,6 +70433,10 @@ var init_local_sandbox = __esm({
70311
70433
  // The most recent foreground child. Lets the CLI forward user-typed lines to
70312
70434
  // a command that is waiting for stdin (interactive prompts, REPLs, etc.).
70313
70435
  this.activeForegroundChild = null;
70436
+ // Host fingerprint, computed once at construction. Linux/Kali unlock the
70437
+ // "full-power" sandbox context; root status flips sudo guidance; unrestricted
70438
+ // opens the native http_request / recon scope gate (default ON for Linux).
70439
+ this.platform = import_node_os3.default.platform();
70314
70440
  this.commands = {
70315
70441
  run: (command, opts) => {
70316
70442
  return new Promise((resolve2, reject) => {
@@ -70397,15 +70523,15 @@ var init_local_sandbox = __esm({
70397
70523
  this.files = {
70398
70524
  write: async (filePath, content) => {
70399
70525
  const resolved = this.resolvePath(filePath);
70400
- await import_node_fs8.promises.mkdir(import_node_path7.default.dirname(resolved), { recursive: true });
70526
+ await import_node_fs9.promises.mkdir(import_node_path8.default.dirname(resolved), { recursive: true });
70401
70527
  const data = typeof content === "string" ? content : content instanceof ArrayBuffer ? Buffer.from(content) : content;
70402
- await import_node_fs8.promises.writeFile(resolved, data);
70528
+ await import_node_fs9.promises.writeFile(resolved, data);
70403
70529
  },
70404
70530
  read: async (filePath) => {
70405
- return import_node_fs8.promises.readFile(this.resolvePath(filePath), "utf8");
70531
+ return import_node_fs9.promises.readFile(this.resolvePath(filePath), "utf8");
70406
70532
  },
70407
70533
  remove: async (filePath) => {
70408
- await import_node_fs8.promises.rm(this.resolvePath(filePath), {
70534
+ await import_node_fs9.promises.rm(this.resolvePath(filePath), {
70409
70535
  recursive: true,
70410
70536
  force: true
70411
70537
  });
@@ -70413,8 +70539,8 @@ var init_local_sandbox = __esm({
70413
70539
  list: async (dirPath = ".") => {
70414
70540
  const resolved = this.resolvePath(dirPath);
70415
70541
  try {
70416
- const entries = await import_node_fs8.promises.readdir(resolved, { withFileTypes: true });
70417
- return entries.filter((e) => e.isFile()).map((e) => ({ name: import_node_path7.default.join(resolved, e.name) }));
70542
+ const entries = await import_node_fs9.promises.readdir(resolved, { withFileTypes: true });
70543
+ return entries.filter((e) => e.isFile()).map((e) => ({ name: import_node_path8.default.join(resolved, e.name) }));
70418
70544
  } catch {
70419
70545
  return [];
70420
70546
  }
@@ -70430,12 +70556,15 @@ var init_local_sandbox = __esm({
70430
70556
  this.shellFlag = shell2.shellFlag;
70431
70557
  }
70432
70558
  const explicit = opts?.workdir || process.env.CLI_WORKDIR;
70433
- const base = explicit || import_node_path7.default.join(process.cwd(), "SPRIT");
70559
+ const base = explicit || import_node_path8.default.join(process.cwd(), "SPRIT");
70434
70560
  this.autoCreated = !explicit;
70435
- this.workdir = import_node_path7.default.resolve(base).replace(/\\/g, "/");
70561
+ this.workdir = import_node_path8.default.resolve(base).replace(/\\/g, "/");
70562
+ this.linux = detectLinux(this.platform);
70563
+ this.root = typeof process.getuid === "function" ? process.getuid() === 0 : false;
70564
+ this.unrestricted = computeUnrestricted(this.platform);
70436
70565
  }
70437
70566
  async init() {
70438
- await import_node_fs8.promises.mkdir(this.workdir, { recursive: true });
70567
+ await import_node_fs9.promises.mkdir(this.workdir, { recursive: true });
70439
70568
  await this.seedReconToolkit();
70440
70569
  await this.seedAuditToolkit();
70441
70570
  }
@@ -70452,8 +70581,8 @@ var init_local_sandbox = __esm({
70452
70581
  const assetsDir = (0, import_node_url.fileURLToPath)(
70453
70582
  new URL("../assets/recon", import_meta.url)
70454
70583
  );
70455
- const destDir = import_node_path7.default.join(this.workdir, "recon");
70456
- await import_node_fs8.promises.mkdir(destDir, { recursive: true });
70584
+ const destDir = import_node_path8.default.join(this.workdir, "recon");
70585
+ await import_node_fs9.promises.mkdir(destDir, { recursive: true });
70457
70586
  const files = [
70458
70587
  ["recon_deep.py", true],
70459
70588
  ["recon_common.py", true],
@@ -70469,17 +70598,17 @@ var init_local_sandbox = __esm({
70469
70598
  ["scope.txt", false]
70470
70599
  ];
70471
70600
  for (const [name25, overwrite] of files) {
70472
- const dest = import_node_path7.default.join(destDir, name25);
70601
+ const dest = import_node_path8.default.join(destDir, name25);
70473
70602
  if (!overwrite) {
70474
70603
  try {
70475
- await import_node_fs8.promises.access(dest);
70604
+ await import_node_fs9.promises.access(dest);
70476
70605
  continue;
70477
70606
  } catch {
70478
70607
  }
70479
70608
  }
70480
70609
  try {
70481
- const content = await import_node_fs8.promises.readFile(import_node_path7.default.join(assetsDir, name25));
70482
- await import_node_fs8.promises.writeFile(dest, content);
70610
+ const content = await import_node_fs9.promises.readFile(import_node_path8.default.join(assetsDir, name25));
70611
+ await import_node_fs9.promises.writeFile(dest, content);
70483
70612
  } catch {
70484
70613
  }
70485
70614
  }
@@ -70497,12 +70626,12 @@ var init_local_sandbox = __esm({
70497
70626
  const assetsDir = (0, import_node_url.fileURLToPath)(
70498
70627
  new URL("../assets/audit", import_meta.url)
70499
70628
  );
70500
- const destDir = import_node_path7.default.join(this.workdir, "audit");
70501
- await import_node_fs8.promises.mkdir(destDir, { recursive: true });
70629
+ const destDir = import_node_path8.default.join(this.workdir, "audit");
70630
+ await import_node_fs9.promises.mkdir(destDir, { recursive: true });
70502
70631
  for (const name25 of ["project_audit.py", "README.md"]) {
70503
70632
  try {
70504
- const content = await import_node_fs8.promises.readFile(import_node_path7.default.join(assetsDir, name25));
70505
- await import_node_fs8.promises.writeFile(import_node_path7.default.join(destDir, name25), content);
70633
+ const content = await import_node_fs9.promises.readFile(import_node_path8.default.join(assetsDir, name25));
70634
+ await import_node_fs9.promises.writeFile(import_node_path8.default.join(destDir, name25), content);
70506
70635
  } catch {
70507
70636
  }
70508
70637
  }
@@ -70521,6 +70650,30 @@ var init_local_sandbox = __esm({
70521
70650
  isWindows() {
70522
70651
  return import_node_os3.default.platform() === "win32" && !this.isBashLikeShell();
70523
70652
  }
70653
+ /** True on any Linux host — unlocks the full-power sandbox context. */
70654
+ isLinux() {
70655
+ return this.platform === "linux";
70656
+ }
70657
+ /** True when /etc/os-release identifies this host as Kali Linux. */
70658
+ isKali() {
70659
+ return this.linux.isKali;
70660
+ }
70661
+ /** Pretty distro name (PRETTY_NAME/NAME), e.g. "Kali GNU/Linux Rolling". */
70662
+ getDistroName() {
70663
+ return this.linux.distroName;
70664
+ }
70665
+ /** True when the session runs as uid 0 (no sudo needed). */
70666
+ isRoot() {
70667
+ return this.root;
70668
+ }
70669
+ /**
70670
+ * True when the native http_request tool and the recon toolkit run WITHOUT
70671
+ * the scope.txt allowlist gate. Defaults to ON for Linux ("tudo liberado"),
70672
+ * OFF elsewhere; flip with CLAWFAST_UNRESTRICTED / CLAWFAST_STRICT_SCOPE.
70673
+ */
70674
+ isUnrestricted() {
70675
+ return this.unrestricted;
70676
+ }
70524
70677
  supportsPty() {
70525
70678
  return false;
70526
70679
  }
@@ -70529,10 +70682,12 @@ var init_local_sandbox = __esm({
70529
70682
  const shellInvocation = `${this.shellBin} ${this.shellFlag}`;
70530
70683
  const windowsNotes = import_node_os3.default.platform() === "win32" ? this.getWindowsShellNotes() : "";
70531
70684
  const pentestToolingNotes = this.getLocalPentestToolingNotes();
70532
- return `You are executing commands directly on the user's local host (${platform}, hostname "${import_node_os3.default.hostname()}") in DANGEROUS MODE: there is NO sandbox isolation.
70685
+ const linuxPowerNotes = this.isLinux() ? this.getLinuxPowerNotes() : "";
70686
+ const hostLabel = this.isLinux() ? `${this.getDistroName()} ${import_node_os3.default.arch()}, ${this.root ? "root" : "user"}@${import_node_os3.default.hostname()}` : `${platform}, hostname "${import_node_os3.default.hostname()}"`;
70687
+ return `You are executing commands directly on the user's local host (${hostLabel}) in DANGEROUS MODE: there is NO sandbox isolation. This is the operator's OWN machine and they have authorized you to use its FULL power.
70533
70688
  Commands are invoked via \`${shellInvocation}\`. The working directory is "${this.workdir}".
70534
70689
  Be careful: file system, network and process operations all affect the real host system.
70535
- A real human user is present at this terminal. When a command prompts for input \u2014 a password, a y/n confirmation, an interactive installer, a REPL prompt (python, mysql, ftp), an ssh/sudo prompt, etc. \u2014 the user types the answer and it is forwarded to the command's stdin. So you MAY run commands that prompt for input; just run the command and the user will respond when asked. Prefer non-interactive flags (like --yes) when they exist and are convenient, but interactive prompts are fully supported. Only full-screen / raw-mode TUI programs (vim, top, htop, less without \`| cat\`) are unsupported, since there is no PTY \u2014 avoid those and use line-oriented alternatives.
70690
+ A real human user is present at this terminal. When a command prompts for input \u2014 a password, a y/n confirmation, an interactive installer, a REPL prompt (python, mysql, ftp), an ssh/sudo prompt, an account/registration step, etc. \u2014 the user types the answer and it is forwarded to the command's stdin. So you MAY run commands that prompt for input; just run the command and the user will respond when asked. Prefer non-interactive flags (like --yes) when they exist and are convenient, but interactive prompts are fully supported. Only full-screen / raw-mode TUI programs (vim, top, htop, less without \`| cat\`) are unsupported, since there is no PTY \u2014 avoid those and use line-oriented alternatives.
70536
70691
 
70537
70692
  WORKSPACE - STRICT SCOPE:
70538
70693
  - Your entire workspace is the directory: ${this.workdir}
@@ -70549,7 +70704,28 @@ EDITING SCRIPTS:
70549
70704
  - When a script you wrote produces an error, fix the specific broken part.
70550
70705
  - Do not recreate or fully overwrite a file just to fix one error.
70551
70706
  - Use write only to create a brand-new file, or when changes to a short file are extensive.
70552
- - Iterate on your own existing files across steps; treat them as persistent.${windowsNotes}${pentestToolingNotes}`;
70707
+ - Iterate on your own existing files across steps; treat them as persistent.${linuxPowerNotes}${windowsNotes}${pentestToolingNotes}`;
70708
+ }
70709
+ /**
70710
+ * The Linux "full-power" block: tells the model it is on a real Linux/Kali box
70711
+ * with no capability artificially withheld, how root/sudo works here, that
70712
+ * Python and scanning are fully unlocked, and — critically — the protocol for
70713
+ * relaying a Kali tool's interactive prompt to the human and forwarding their
70714
+ * typed answer back to the program's stdin.
70715
+ */
70716
+ getLinuxPowerNotes() {
70717
+ const who = this.root ? "root (uid 0) \u2014 no sudo needed; you can install packages, bind low ports, sniff, and read/write anywhere" : "a normal user \u2014 prefix privileged actions with sudo; the human will type the sudo password when prompted";
70718
+ const apt = this.root ? "apt" : "sudo apt";
70719
+ const kaliLine = this.isKali() ? "This is Kali Linux: treat the full offensive toolset as installed and ready." : "Treat this as a capable Linux pentest host; install whatever you need.";
70720
+ return `
70721
+
70722
+ LINUX FULL-POWER MODE (${this.getDistroName()}, running as ${who}):
70723
+ - ${kaliLine} Everything the OS permits, you may do \u2014 no capability is off-limits because of the platform. This box belongs to the operator and full use is authorized.
70724
+ - You have a REAL shell with REAL reach: full filesystem, raw sockets, package managers (\`${apt} install -y <pkg>\`, \`pip install\`, \`pipx install\`, \`go install\`), service control (\`systemctl\`), and the entire installed toolset.
70725
+ - Python is 100% unlocked: \`python3\`, \`pip\`, \`venv\`, any library. Build and run whatever the task needs \u2014 there is no Python restriction here.
70726
+ - Scanning is fully allowed: port scans, host/network range sweeps, service/version detection, and number/identifier enumeration (IP ranges, port ranges, phone-number OSINT with phoneinfoga, etc.). Pick the right tool (nmap, masscan, the recon/ toolkit) and apply reasonable OPSEC throttling on noisy work.
70727
+ - KALI / INTERACTIVE PROMPTS \u2014 IMPORTANT: many tools and installers stop to ask for input mid-run (msfconsole, an \`apt\` confirmation, an account/registration/sign-up step, an ssh/sudo/password prompt, a y/n, a setup wizard, a REPL). A real human is at this terminal. When the underlying program is waiting for input, STOP and tell the user in ONE short line EXACTLY what the program ("o kali") is asking and what to type \u2014 e.g. "o kali est\xE1 pedindo o nome de usu\xE1rio da conta; digite ele aqui". The user types the answer here, the CLI forwards it to the program's stdin, and you continue. You drive the tool; the human supplies the human answers.
70728
+ - Account creation / sign-ups a tool requires: run the step, surface each prompt to the user, forward their typed answers \u2014 NEVER invent credentials, never skip the human, never fabricate the tool's output.${this.unrestricted ? "\n- UNRESTRICTED scope is ACTIVE: the native http_request tool and the recon/ Python modules run WITHOUT the scope.txt allowlist (pass `--allow-out-of-scope` to the recon scripts). Still act only against targets the operator pointed you at." : ""}`;
70553
70729
  }
70554
70730
  getOsContext() {
70555
70731
  return this.getSandboxContext();
@@ -70572,6 +70748,10 @@ EDITING SCRIPTS:
70572
70748
  getWorkdir() {
70573
70749
  return this.workdir;
70574
70750
  }
70751
+ /** The shell binary + exec flag this sandbox runs commands through. */
70752
+ getShell() {
70753
+ return { bin: this.shellBin, flag: this.shellFlag };
70754
+ }
70575
70755
  getHost(port) {
70576
70756
  return `localhost:${port}`;
70577
70757
  }
@@ -70605,9 +70785,9 @@ EDITING SCRIPTS:
70605
70785
  if (!this.autoCreated) return null;
70606
70786
  const keep = (process.env.CLAWFAST_KEEP_SPRIT || "").trim().toLowerCase();
70607
70787
  if (keep === "1" || keep === "true" || keep === "yes") return null;
70608
- if (import_node_path7.default.basename(this.workdir).toUpperCase() !== "SPRIT") return null;
70788
+ if (import_node_path8.default.basename(this.workdir).toUpperCase() !== "SPRIT") return null;
70609
70789
  try {
70610
- await import_node_fs8.promises.rm(this.workdir, { recursive: true, force: true });
70790
+ await import_node_fs9.promises.rm(this.workdir, { recursive: true, force: true });
70611
70791
  return this.workdir;
70612
70792
  } catch {
70613
70793
  return null;
@@ -70658,15 +70838,15 @@ EDITING SCRIPTS:
70658
70838
  }
70659
70839
  }
70660
70840
  resolvePath(p) {
70661
- if (import_node_path7.default.isAbsolute(p)) return p;
70662
- return import_node_path7.default.join(this.workdir, p);
70841
+ if (import_node_path8.default.isAbsolute(p)) return p;
70842
+ return import_node_path8.default.join(this.workdir, p);
70663
70843
  }
70664
70844
  isBashLikeShell() {
70665
- const base = import_node_path7.default.basename(this.shellBin).toLowerCase();
70845
+ const base = import_node_path8.default.basename(this.shellBin).toLowerCase();
70666
70846
  return base === "bash" || base === "bash.exe" || base === "sh";
70667
70847
  }
70668
70848
  isCmdShell() {
70669
- const base = import_node_path7.default.basename(this.shellBin).toLowerCase();
70849
+ const base = import_node_path8.default.basename(this.shellBin).toLowerCase();
70670
70850
  return base === "cmd" || base === "cmd.exe";
70671
70851
  }
70672
70852
  getWindowsShellNotes() {
@@ -70708,12 +70888,14 @@ LOCAL PENTEST TOOLING:
70708
70888
  - In cmd.exe, do not use raw Bash constructs like \`export\`, \`$(pwd)\`, heredocs, or single-quote escaping. Wrap those commands with \`wsl.exe ... bash -lc "..."\` or rewrite them in cmd syntax.
70709
70889
  - For OWASP ZAP on Windows without WSL, prefer Docker: \`docker pull ghcr.io/zaproxy/zaproxy:stable\` and use a cmd.exe volume mount like \`-v "%cd%:/zap/wrk/:rw"\`.`;
70710
70890
  }
70891
+ const sudo = this.root ? "" : "sudo ";
70711
70892
  return `
70712
70893
 
70713
- LOCAL PENTEST TOOLING:
70714
- - Prefer already-installed local tools, but verify first with \`command -v zaproxy nikto nuclei docker\`.
70715
- - On Kali/Debian hosts, install missing scanners with \`sudo apt update && sudo apt install -y zaproxy nikto nuclei\`; if the session is root, omit \`sudo\`.
70716
- - For OWASP ZAP Docker scans, map the current working directory with \`-v "$(pwd):/zap/wrk/:rw"\` so reports are preserved.`;
70894
+ LOCAL PENTEST TOOLING (${this.isLinux() ? this.getDistroName() : "Linux"}):
70895
+ - Treat the full Kali/Debian arsenal as available; verify a specific tool with \`command -v <tool>\` and install anything missing with \`${sudo}apt update && ${sudo}apt install -y <pkg>\` (or \`pipx install\` / \`pip install\`).
70896
+ - Common arsenal: nmap, masscan, nikto, nuclei, sqlmap, wpscan, gobuster / ffuf / feroxbuster, hydra, john, hashcat, metasploit (\`msfconsole\`), netcat, whois, dig, dnsrecon, amass, subfinder, theHarvester, wfuzz, zaproxy, burpsuite, responder, enum4linux, smbclient, phoneinfoga.
70897
+ - No PTY here: prefer line-oriented invocation \u2014 \`msfconsole -q -x "use ...; set ...; run; exit"\`, \`sqlmap --batch\`, and pipe pagers through \`| cat\`. Interactive prompts still work (the human types answers), but avoid full-screen TUIs.
70898
+ - For OWASP ZAP / containerized scans, Docker works too: \`docker run ... -v "$(pwd):/zap/wrk/:rw" ...\` so reports are preserved.`;
70717
70899
  }
70718
70900
  getWslWorkdir() {
70719
70901
  const match = this.workdir.match(/^([A-Za-z]):\/(.*)$/);
@@ -70721,6 +70903,10 @@ LOCAL PENTEST TOOLING:
70721
70903
  return `/mnt/${match[1].toLowerCase()}/${match[2]}`;
70722
70904
  }
70723
70905
  };
70906
+ truthyEnv = (name25) => {
70907
+ const v = (process.env[name25] || "").trim().toLowerCase();
70908
+ return v === "1" || v === "true" || v === "yes" || v === "on";
70909
+ };
70724
70910
  }
70725
70911
  });
70726
70912
 
@@ -70763,6 +70949,577 @@ var init_local_sandbox_manager = __esm({
70763
70949
  }
70764
70950
  });
70765
70951
 
70952
+ // src/message-tail.ts
70953
+ function ensureGeneratableTail(messages, continuation = "continue") {
70954
+ const last = messages[messages.length - 1];
70955
+ if (last && last.role === "assistant") {
70956
+ messages.push({ role: "user", content: continuation });
70957
+ }
70958
+ return messages;
70959
+ }
70960
+ var init_message_tail = __esm({
70961
+ "src/message-tail.ts"() {
70962
+ "use strict";
70963
+ }
70964
+ });
70965
+
70966
+ // src/memory.ts
70967
+ function trimHistoryForSave(history, max = MAX_SAVED_MESSAGES) {
70968
+ let slice = history.length > max ? history.slice(-max) : history.slice();
70969
+ let start = 0;
70970
+ while (start < slice.length && slice[start].role !== "user") start++;
70971
+ slice = start < slice.length ? slice.slice(start) : [];
70972
+ return slice;
70973
+ }
70974
+ var import_node_fs10, import_node_fs11, import_node_path9, import_node_crypto, FACTS_FILE, CONVO_FILE, MAX_SAVED_MESSAGES, MAX_FACTS, MAX_RENDER_PER_KIND, memoryEnabled, slugForScope, isFact, EngagementMemory;
70975
+ var init_memory = __esm({
70976
+ "src/memory.ts"() {
70977
+ "use strict";
70978
+ import_node_fs10 = require("node:fs");
70979
+ import_node_fs11 = __toESM(require("node:fs"));
70980
+ import_node_path9 = __toESM(require("node:path"));
70981
+ import_node_crypto = __toESM(require("node:crypto"));
70982
+ init_config();
70983
+ FACTS_FILE = "facts.json";
70984
+ CONVO_FILE = "conversation.json";
70985
+ MAX_SAVED_MESSAGES = 80;
70986
+ MAX_FACTS = 500;
70987
+ MAX_RENDER_PER_KIND = 50;
70988
+ memoryEnabled = () => (process.env.CLAWFAST_MEMORY || "").trim().toLowerCase() !== "off";
70989
+ slugForScope = (scope) => {
70990
+ const base = import_node_path9.default.basename(scope).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "scope";
70991
+ const hash2 = import_node_crypto.default.createHash("sha1").update(import_node_path9.default.resolve(scope)).digest("hex").slice(0, 8);
70992
+ return `${base}-${hash2}`;
70993
+ };
70994
+ isFact = (x) => !!x && typeof x.id === "string" && typeof x.kind === "string" && typeof x.title === "string";
70995
+ EngagementMemory = class {
70996
+ constructor(scope) {
70997
+ this.facts = [];
70998
+ this.seq = 0;
70999
+ this.enabled = memoryEnabled();
71000
+ this.dir = import_node_path9.default.join(clawfastHome(), "memory", slugForScope(scope));
71001
+ if (this.enabled) this.loadFactsSync();
71002
+ }
71003
+ loadFactsSync() {
71004
+ try {
71005
+ const parsed = JSON.parse(
71006
+ import_node_fs11.default.readFileSync(import_node_path9.default.join(this.dir, FACTS_FILE), "utf8")
71007
+ );
71008
+ if (Array.isArray(parsed)) {
71009
+ this.facts = parsed.filter(isFact);
71010
+ for (const f of this.facts) {
71011
+ const n = Number(String(f.id).replace(/^F-/, ""));
71012
+ if (Number.isFinite(n) && n > this.seq) this.seq = n;
71013
+ }
71014
+ }
71015
+ } catch {
71016
+ }
71017
+ }
71018
+ async persistFacts() {
71019
+ if (!this.enabled) return;
71020
+ try {
71021
+ await import_node_fs10.promises.mkdir(this.dir, { recursive: true });
71022
+ await import_node_fs10.promises.writeFile(
71023
+ import_node_path9.default.join(this.dir, FACTS_FILE),
71024
+ JSON.stringify(this.facts, null, 2),
71025
+ "utf8"
71026
+ );
71027
+ } catch {
71028
+ }
71029
+ }
71030
+ list(filter3) {
71031
+ return this.facts.filter(
71032
+ (f) => (!filter3?.kind || f.kind === filter3.kind) && (!filter3?.target || f.target === filter3.target)
71033
+ );
71034
+ }
71035
+ async add(input) {
71036
+ const key = (f) => `${f.kind}|${f.title}|${f.target || ""}`.toLowerCase();
71037
+ const existing = this.facts.find((f) => key(f) === key(input));
71038
+ if (existing) {
71039
+ if (input.detail !== void 0) existing.detail = input.detail;
71040
+ if (input.source !== void 0) existing.source = input.source;
71041
+ existing.ts = Date.now();
71042
+ await this.persistFacts();
71043
+ return existing;
71044
+ }
71045
+ const fact = {
71046
+ id: `F-${String(++this.seq).padStart(3, "0")}`,
71047
+ kind: input.kind,
71048
+ title: input.title,
71049
+ detail: input.detail,
71050
+ target: input.target,
71051
+ source: input.source,
71052
+ ts: Date.now()
71053
+ };
71054
+ this.facts.push(fact);
71055
+ if (this.facts.length > MAX_FACTS) {
71056
+ this.facts = this.facts.slice(-MAX_FACTS);
71057
+ }
71058
+ await this.persistFacts();
71059
+ return fact;
71060
+ }
71061
+ async update(id, patch) {
71062
+ const f = this.facts.find((x) => x.id === id);
71063
+ if (!f) return null;
71064
+ const target = f;
71065
+ for (const [k, v] of Object.entries(patch)) {
71066
+ if (v !== void 0 && k !== "id") target[k] = v;
71067
+ }
71068
+ f.ts = Date.now();
71069
+ await this.persistFacts();
71070
+ return f;
71071
+ }
71072
+ async remove(id) {
71073
+ const before = this.facts.length;
71074
+ this.facts = this.facts.filter((f) => f.id !== id);
71075
+ if (this.facts.length === before) return false;
71076
+ await this.persistFacts();
71077
+ return true;
71078
+ }
71079
+ /** Compact `<engagement_memory>` block for the system prompt, or "" if empty. */
71080
+ renderSection() {
71081
+ if (!this.enabled || this.facts.length === 0) return "";
71082
+ const byKind = /* @__PURE__ */ new Map();
71083
+ for (const f of this.facts) {
71084
+ const arr = byKind.get(f.kind) || [];
71085
+ arr.push(f);
71086
+ byKind.set(f.kind, arr);
71087
+ }
71088
+ const lines = [];
71089
+ for (const [kind, arr] of byKind) {
71090
+ lines.push(`[${kind}]`);
71091
+ for (const f of arr.slice(-MAX_RENDER_PER_KIND)) {
71092
+ lines.push(
71093
+ `- ${f.id} ${f.title}` + (f.target ? ` (${f.target})` : "") + (f.detail ? `: ${f.detail}` : "")
71094
+ );
71095
+ }
71096
+ }
71097
+ return `
71098
+
71099
+ <engagement_memory>
71100
+ Mem\xF3ria de engajamento PERSISTENTE deste alvo/projeto \u2014 sobrevive \xE0 troca de modelo E ao rein\xEDcio do CLI. Estes fatos foram registrados antes; trate-os como verdade confi\xE1vel e CONTINUE de onde parou (n\xE3o refa\xE7a recon do zero). Para gravar algo novo, use a ferramenta \`memory\` (action add).
71101
+ ${lines.join("\n")}
71102
+ </engagement_memory>`;
71103
+ }
71104
+ // ── Conversation persistence ────────────────────────────────────────────────
71105
+ saveConversation(history) {
71106
+ if (!this.enabled) return;
71107
+ try {
71108
+ import_node_fs11.default.mkdirSync(this.dir, { recursive: true });
71109
+ import_node_fs11.default.writeFileSync(
71110
+ import_node_path9.default.join(this.dir, CONVO_FILE),
71111
+ JSON.stringify(trimHistoryForSave(history)),
71112
+ "utf8"
71113
+ );
71114
+ } catch {
71115
+ }
71116
+ }
71117
+ loadConversation() {
71118
+ if (!this.enabled) return [];
71119
+ try {
71120
+ const parsed = JSON.parse(
71121
+ import_node_fs11.default.readFileSync(import_node_path9.default.join(this.dir, CONVO_FILE), "utf8")
71122
+ );
71123
+ return Array.isArray(parsed) ? parsed : [];
71124
+ } catch {
71125
+ return [];
71126
+ }
71127
+ }
71128
+ clearConversation() {
71129
+ try {
71130
+ import_node_fs11.default.rmSync(import_node_path9.default.join(this.dir, CONVO_FILE), { force: true });
71131
+ } catch {
71132
+ }
71133
+ }
71134
+ };
71135
+ }
71136
+ });
71137
+
71138
+ // src/tools/memory-tool.ts
71139
+ var createMemory;
71140
+ var init_memory_tool = __esm({
71141
+ "src/tools/memory-tool.ts"() {
71142
+ "use strict";
71143
+ init_dist5();
71144
+ init_zod();
71145
+ createMemory = (deps) => {
71146
+ const { memory } = deps;
71147
+ return tool({
71148
+ description: `Persistent engagement memory for THIS target/project. Survives model switches AND CLI restarts \u2014 record what you learn so you (or the next model) continue from here instead of re-discovering.
71149
+
71150
+ WHAT TO STORE (action add): hosts/IPs, open services + versions, credentials/tokens, confirmed vulns, tech stack, and especially what WORKED or what was BLOCKED (WAF, rate limit, auth). kind + title are required; detail/target/source optional.
71151
+
71152
+ ACTIONS:
71153
+ - add \u2014 store a fact.
71154
+ - update \u2014 change a fact by id.
71155
+ - list \u2014 read stored facts (optional kind/target filter). Do this before re-running recon you may have already done.
71156
+ - forget \u2014 remove a fact by id.`,
71157
+ inputSchema: external_exports.object({
71158
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose of this call."),
71159
+ action: external_exports.enum(["add", "update", "list", "forget"]),
71160
+ kind: external_exports.string().optional().describe("host|service|cred|vuln|tech|note|url|\u2026 (for add, or list filter)"),
71161
+ title: external_exports.string().optional().describe("Short label (required for add)."),
71162
+ detail: external_exports.string().optional().describe("Longer detail / evidence."),
71163
+ target: external_exports.string().optional().describe("Host/URL this fact is about."),
71164
+ source: external_exports.string().optional().describe("Where it came from."),
71165
+ id: external_exports.string().optional().describe("Fact id (required for update/forget).")
71166
+ }),
71167
+ execute: async (input) => {
71168
+ try {
71169
+ switch (input.action) {
71170
+ case "add": {
71171
+ if (!input.kind || !input.title) {
71172
+ return "Erro: 'kind' e 'title' s\xE3o obrigat\xF3rios para add.";
71173
+ }
71174
+ const f = await memory.add({
71175
+ kind: input.kind,
71176
+ title: input.title,
71177
+ detail: input.detail,
71178
+ target: input.target,
71179
+ source: input.source
71180
+ });
71181
+ return `Mem\xF3ria gravada: ${f.id} [${f.kind}] ${f.title}`;
71182
+ }
71183
+ case "update": {
71184
+ if (!input.id) return "Erro: 'id' \xE9 obrigat\xF3rio para update.";
71185
+ const f = await memory.update(input.id, {
71186
+ kind: input.kind,
71187
+ title: input.title,
71188
+ detail: input.detail,
71189
+ target: input.target,
71190
+ source: input.source
71191
+ });
71192
+ return f ? `Atualizado: ${f.id}` : `N\xE3o achei o fato ${input.id}.`;
71193
+ }
71194
+ case "forget": {
71195
+ if (!input.id) return "Erro: 'id' \xE9 obrigat\xF3rio para forget.";
71196
+ return await memory.remove(input.id) ? `Removido: ${input.id}` : `N\xE3o achei o fato ${input.id}.`;
71197
+ }
71198
+ case "list":
71199
+ default: {
71200
+ const facts = memory.list({
71201
+ kind: input.kind,
71202
+ target: input.target
71203
+ });
71204
+ if (!facts.length) {
71205
+ return "Mem\xF3ria vazia (nenhum fato gravado ainda).";
71206
+ }
71207
+ return facts.map(
71208
+ (f) => `${f.id} [${f.kind}] ${f.title}` + (f.target ? ` (${f.target})` : "") + (f.detail ? `: ${f.detail}` : "")
71209
+ ).join("\n");
71210
+ }
71211
+ }
71212
+ } catch (err) {
71213
+ return `Erro na mem\xF3ria: ${err instanceof Error ? err.message : String(err)}`;
71214
+ }
71215
+ }
71216
+ });
71217
+ };
71218
+ }
71219
+ });
71220
+
71221
+ // src/sessions.ts
71222
+ function appendRing(text2, cursor, chunk, max = MAX_BUFFER_CHARS) {
71223
+ let next = text2 + chunk;
71224
+ if (next.length <= max) return { text: next, cursor, dropped: false };
71225
+ const drop = next.length - max;
71226
+ next = next.slice(drop);
71227
+ return { text: next, cursor: Math.max(0, cursor - drop), dropped: true };
71228
+ }
71229
+ var import_node_child_process3, import_node_os4, MAX_SESSIONS, MAX_BUFFER_CHARS, SESSION_IDLE_MS, SESSION_LIFETIME_MS, shortId, sleep3, LocalSessionManager;
71230
+ var init_sessions = __esm({
71231
+ "src/sessions.ts"() {
71232
+ "use strict";
71233
+ import_node_child_process3 = require("node:child_process");
71234
+ import_node_os4 = __toESM(require("node:os"));
71235
+ MAX_SESSIONS = 10;
71236
+ MAX_BUFFER_CHARS = 2e5;
71237
+ SESSION_IDLE_MS = 15 * 6e4;
71238
+ SESSION_LIFETIME_MS = 60 * 6e4;
71239
+ shortId = (taken) => {
71240
+ for (let i = 0; i < 6; i++) {
71241
+ const id = Math.random().toString(36).slice(2, 8);
71242
+ if (!taken.has(id)) return id;
71243
+ }
71244
+ return `${Date.now().toString(36).slice(-6)}`;
71245
+ };
71246
+ sleep3 = (ms, signal) => new Promise((resolve2) => {
71247
+ if (ms <= 0 || signal?.aborted) return resolve2();
71248
+ const t = setTimeout(resolve2, ms);
71249
+ signal?.addEventListener(
71250
+ "abort",
71251
+ () => {
71252
+ clearTimeout(t);
71253
+ resolve2();
71254
+ },
71255
+ { once: true }
71256
+ );
71257
+ });
71258
+ LocalSessionManager = class {
71259
+ constructor(opts) {
71260
+ this.opts = opts;
71261
+ this.sessions = /* @__PURE__ */ new Map();
71262
+ }
71263
+ /**
71264
+ * Open a session. With `command`, the program runs through the shell
71265
+ * (`shell -c "<command>"`) with its stdin held open. Without it, a bare
71266
+ * interactive shell is spawned. Returns the new session id.
71267
+ */
71268
+ open(command, cwd) {
71269
+ if (this.sessions.size >= MAX_SESSIONS) {
71270
+ return { id: "", error: `limite de ${MAX_SESSIONS} sess\xF5es abertas atingido \u2014 feche alguma antes.` };
71271
+ }
71272
+ const id = shortId(new Set(this.sessions.keys()));
71273
+ const args = command ? [this.opts.shellFlag, command] : [];
71274
+ let child;
71275
+ try {
71276
+ child = (0, import_node_child_process3.spawn)(this.opts.shell, args, {
71277
+ cwd: cwd || this.opts.cwd,
71278
+ env: process.env,
71279
+ stdio: ["pipe", "pipe", "pipe"],
71280
+ windowsHide: true
71281
+ });
71282
+ } catch (err) {
71283
+ return { id: "", error: err instanceof Error ? err.message : String(err) };
71284
+ }
71285
+ const now2 = Date.now();
71286
+ const session = {
71287
+ id,
71288
+ command: command || `${this.opts.shell} (shell interativo)`,
71289
+ child,
71290
+ output: "",
71291
+ readCursor: 0,
71292
+ truncated: false,
71293
+ createdAt: now2,
71294
+ lastActivity: now2,
71295
+ alive: true,
71296
+ exitCode: null,
71297
+ idleTimer: null,
71298
+ lifetimeTimer: null
71299
+ };
71300
+ const onChunk = (d) => this.append(session, d.toString());
71301
+ child.stdout?.on("data", onChunk);
71302
+ child.stderr?.on("data", onChunk);
71303
+ child.on("error", (err) => {
71304
+ this.append(session, `
71305
+ [erro do processo: ${err.message}]
71306
+ `);
71307
+ });
71308
+ child.on("close", (code, sig) => {
71309
+ session.alive = false;
71310
+ session.exitCode = code != null ? code : sig ? 137 : null;
71311
+ this.clearTimers(session);
71312
+ });
71313
+ this.armIdle(session);
71314
+ session.lifetimeTimer = setTimeout(
71315
+ () => this.close(id),
71316
+ SESSION_LIFETIME_MS
71317
+ );
71318
+ session.lifetimeTimer.unref?.();
71319
+ this.sessions.set(id, session);
71320
+ return { id };
71321
+ }
71322
+ /** Write input to a session's stdin (appends a newline unless `enter` is false). */
71323
+ send(id, data, enter = true) {
71324
+ const s = this.sessions.get(id);
71325
+ if (!s) return { ok: false, error: `sess\xE3o ${id} n\xE3o existe.` };
71326
+ if (!s.alive || !s.child.stdin?.writable) {
71327
+ return { ok: false, error: `sess\xE3o ${id} n\xE3o est\xE1 mais ativa.` };
71328
+ }
71329
+ try {
71330
+ s.child.stdin.write(enter && !data.endsWith("\n") ? `${data}
71331
+ ` : data);
71332
+ s.lastActivity = Date.now();
71333
+ this.armIdle(s);
71334
+ return { ok: true };
71335
+ } catch (err) {
71336
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
71337
+ }
71338
+ }
71339
+ /**
71340
+ * Read new output, waiting until it goes quiet (no new bytes for `quietMs`)
71341
+ * or `maxMs` elapses, or the process exits. Returns the delta since the last
71342
+ * read.
71343
+ */
71344
+ async readUntilQuiet(id, opts = {}) {
71345
+ const s = this.sessions.get(id);
71346
+ if (!s) return `[sess\xE3o ${id} n\xE3o existe]`;
71347
+ const quietMs = opts.quietMs ?? 500;
71348
+ const maxMs = opts.maxMs ?? 15e3;
71349
+ const start = Date.now();
71350
+ let lastLen = s.output.length;
71351
+ while (Date.now() - start < maxMs) {
71352
+ await sleep3(quietMs, opts.signal);
71353
+ if (opts.signal?.aborted) break;
71354
+ if (!s.alive) break;
71355
+ if (s.output.length === lastLen) break;
71356
+ lastLen = s.output.length;
71357
+ }
71358
+ return this.readDelta(id);
71359
+ }
71360
+ /** Return output appended since the last read, advancing the cursor. */
71361
+ readDelta(id) {
71362
+ const s = this.sessions.get(id);
71363
+ if (!s) return `[sess\xE3o ${id} n\xE3o existe]`;
71364
+ const delta = s.output.slice(s.readCursor);
71365
+ s.readCursor = s.output.length;
71366
+ return delta;
71367
+ }
71368
+ info(id) {
71369
+ const s = this.sessions.get(id);
71370
+ if (!s) return null;
71371
+ return {
71372
+ id: s.id,
71373
+ command: s.command,
71374
+ alive: s.alive,
71375
+ exitCode: s.exitCode,
71376
+ ageMs: Date.now() - s.createdAt,
71377
+ truncated: s.truncated
71378
+ };
71379
+ }
71380
+ list() {
71381
+ return [...this.sessions.keys()].map((id) => this.info(id)).filter((x) => x !== null);
71382
+ }
71383
+ close(id) {
71384
+ const s = this.sessions.get(id);
71385
+ if (!s) return false;
71386
+ this.clearTimers(s);
71387
+ this.killTree(s.child);
71388
+ this.sessions.delete(id);
71389
+ return true;
71390
+ }
71391
+ closeAll() {
71392
+ for (const id of [...this.sessions.keys()]) this.close(id);
71393
+ }
71394
+ append(s, chunk) {
71395
+ const r2 = appendRing(s.output, s.readCursor, chunk);
71396
+ s.output = r2.text;
71397
+ s.readCursor = r2.cursor;
71398
+ if (r2.dropped) s.truncated = true;
71399
+ s.lastActivity = Date.now();
71400
+ this.armIdle(s);
71401
+ }
71402
+ armIdle(s) {
71403
+ if (s.idleTimer) clearTimeout(s.idleTimer);
71404
+ s.idleTimer = setTimeout(() => this.close(s.id), SESSION_IDLE_MS);
71405
+ s.idleTimer.unref?.();
71406
+ }
71407
+ clearTimers(s) {
71408
+ if (s.idleTimer) clearTimeout(s.idleTimer);
71409
+ if (s.lifetimeTimer) clearTimeout(s.lifetimeTimer);
71410
+ s.idleTimer = null;
71411
+ s.lifetimeTimer = null;
71412
+ }
71413
+ killTree(child) {
71414
+ const pid = child.pid;
71415
+ if (pid && import_node_os4.default.platform() === "win32") {
71416
+ try {
71417
+ (0, import_node_child_process3.spawn)("taskkill", ["/PID", String(pid), "/T", "/F"], {
71418
+ windowsHide: true
71419
+ });
71420
+ return;
71421
+ } catch {
71422
+ }
71423
+ }
71424
+ try {
71425
+ child.kill("SIGTERM");
71426
+ } catch {
71427
+ }
71428
+ }
71429
+ };
71430
+ }
71431
+ });
71432
+
71433
+ // src/tools/session-tool.ts
71434
+ var aliveTag, createTerminalSession;
71435
+ var init_session_tool = __esm({
71436
+ "src/tools/session-tool.ts"() {
71437
+ "use strict";
71438
+ init_dist5();
71439
+ init_zod();
71440
+ aliveTag = (alive, exitCode) => alive ? "[sess\xE3o ativa]" : `[sess\xE3o encerrada${exitCode != null ? `, exit ${exitCode}` : ""}]`;
71441
+ createTerminalSession = (deps) => {
71442
+ const { sessions } = deps;
71443
+ return tool({
71444
+ description: `Drive a PERSISTENT interactive process that stays alive across calls (unlike run_terminal_cmd, a fresh shell each time). Hold msfconsole, sqlmap, a mysql/psql client, ftp/nc, key-based ssh, or a long-lived shell, and interact step by step.
71445
+
71446
+ ACTIONS:
71447
+ - open \u2014 start a session. With \`command\` it runs that program (e.g. "msfconsole -q", "mysql -h 10.0.0.5 -u root", "nc -lvnp 4444"); without it you get an interactive shell. Returns a session id + initial output.
71448
+ - send \u2014 send \`input\` to the session's stdin (a newline is added unless enter=false), then return the new output once it settles.
71449
+ - read \u2014 poll for new output of a running/long command (returns the delta since last read + whether it's still alive).
71450
+ - list \u2014 list open sessions.
71451
+ - close \u2014 terminate a session.
71452
+
71453
+ NO REAL TTY: programs that read a secret straight from /dev/tty (interactive ssh/sudo passwords, full-screen TUIs) won't see piped input \u2014 use sshpass, \`sudo -S\` (reads stdin), key auth, or non-interactive flags. Most CLIs (msfconsole, sqlmap, db clients, nc, ftp) work fine.`,
71454
+ inputSchema: external_exports.object({
71455
+ brief: external_exports.string().describe("A one-sentence preamble describing the purpose."),
71456
+ action: external_exports.enum(["open", "send", "read", "list", "close"]),
71457
+ id: external_exports.string().optional().describe("Session id (for send/read/close)."),
71458
+ command: external_exports.string().optional().describe("Program to launch (for open). Omit for an interactive shell."),
71459
+ input: external_exports.string().optional().describe("Text to send to stdin (for send)."),
71460
+ enter: external_exports.boolean().optional().describe("Append a newline after input (default true)."),
71461
+ timeout: external_exports.number().optional().describe("Max seconds to wait for output to settle (send/read; default 15).")
71462
+ }),
71463
+ execute: async (input, { abortSignal } = {}) => {
71464
+ const maxMs = Math.max(1, input.timeout ?? 15) * 1e3;
71465
+ switch (input.action) {
71466
+ case "open": {
71467
+ const res = sessions.open(input.command);
71468
+ if (res.error) return `Erro ao abrir sess\xE3o: ${res.error}`;
71469
+ const out3 = await sessions.readUntilQuiet(res.id, {
71470
+ quietMs: 500,
71471
+ maxMs: Math.min(maxMs, 8e3),
71472
+ signal: abortSignal
71473
+ });
71474
+ const info = sessions.info(res.id);
71475
+ return `Sess\xE3o aberta: ${res.id}
71476
+ ${aliveTag(info?.alive ?? true, info?.exitCode ?? null)}
71477
+ ${out3 || "(sem sa\xEDda inicial)"}`;
71478
+ }
71479
+ case "send": {
71480
+ if (!input.id) return "Erro: 'id' \xE9 obrigat\xF3rio para send.";
71481
+ if (input.input == null) return "Erro: 'input' \xE9 obrigat\xF3rio para send.";
71482
+ const sent = sessions.send(input.id, input.input, input.enter ?? true);
71483
+ if (!sent.ok) return `Erro ao enviar: ${sent.error}`;
71484
+ const out3 = await sessions.readUntilQuiet(input.id, {
71485
+ quietMs: 500,
71486
+ maxMs,
71487
+ signal: abortSignal
71488
+ });
71489
+ const info = sessions.info(input.id);
71490
+ return `${aliveTag(info?.alive ?? false, info?.exitCode ?? null)}${info?.truncated ? " [sa\xEDda truncada \u2014 buffer cheio]" : ""}
71491
+ ${out3 || "(sem nova sa\xEDda)"}`;
71492
+ }
71493
+ case "read": {
71494
+ if (!input.id) return "Erro: 'id' \xE9 obrigat\xF3rio para read.";
71495
+ const out3 = await sessions.readUntilQuiet(input.id, {
71496
+ quietMs: 600,
71497
+ maxMs,
71498
+ signal: abortSignal
71499
+ });
71500
+ const info = sessions.info(input.id);
71501
+ if (!info) return `Sess\xE3o ${input.id} n\xE3o existe.`;
71502
+ return `${aliveTag(info.alive, info.exitCode)}
71503
+ ${out3 || "(sem nova sa\xEDda)"}`;
71504
+ }
71505
+ case "list": {
71506
+ const list = sessions.list();
71507
+ if (!list.length) return "Nenhuma sess\xE3o aberta.";
71508
+ return list.map(
71509
+ (s) => `${s.id} \u2014 ${s.command} \u2014 ${aliveTag(s.alive, s.exitCode)} \u2014 ${Math.round(s.ageMs / 1e3)}s`
71510
+ ).join("\n");
71511
+ }
71512
+ case "close": {
71513
+ if (!input.id) return "Erro: 'id' \xE9 obrigat\xF3rio para close.";
71514
+ return sessions.close(input.id) ? `Sess\xE3o ${input.id} encerrada.` : `Sess\xE3o ${input.id} n\xE3o existe.`;
71515
+ }
71516
+ }
71517
+ }
71518
+ });
71519
+ };
71520
+ }
71521
+ });
71522
+
70766
71523
  // src/render.ts
70767
71524
  function createRenderer() {
70768
71525
  let lastKind = null;
@@ -70833,11 +71590,11 @@ function formatToolCall(toolName, input) {
70833
71590
  case "run_terminal_cmd": {
70834
71591
  const cmd = String(i.command ?? "").trim();
70835
71592
  const bg = i.is_background ? `${C3.dim} (background)${C3.reset}` : "";
70836
- return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}exec${C3.reset} ${C3.bold}${truncate3(cmd, 400)}${C3.reset}${bg}`;
71593
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}exec${C3.reset} ${C3.bold}${truncate4(cmd, 400)}${C3.reset}${bg}`;
70837
71594
  }
70838
71595
  case "file": {
70839
- const path12 = String(i.path ?? "");
70840
- const brief = i.brief ? `${C3.dim} \u2014 ${truncate3(String(i.brief))}${C3.reset}` : "";
71596
+ const path14 = String(i.path ?? "");
71597
+ const brief = i.brief ? `${C3.dim} \u2014 ${truncate4(String(i.brief))}${C3.reset}` : "";
70841
71598
  const map2 = {
70842
71599
  write: [C3.green, "criar "],
70843
71600
  edit: [C3.yellow, "editar "],
@@ -70850,7 +71607,7 @@ function formatToolCall(toolName, input) {
70850
71607
  C3.blue,
70851
71608
  action ? `${action} `.padEnd(7) : "arquivo"
70852
71609
  ];
70853
- const target = path12 ? `${C3.bold}${path12}${C3.reset}` : "";
71610
+ const target = path14 ? `${C3.bold}${path14}${C3.reset}` : "";
70854
71611
  return ` ${col}\u276F${C3.reset} ${col}${verb}${C3.reset} ${target}${brief}`;
70855
71612
  }
70856
71613
  case "todo_write":
@@ -70860,23 +71617,40 @@ function formatToolCall(toolName, input) {
70860
71617
  const url2 = String(i.url ?? "");
70861
71618
  const fuzz = Array.isArray(i.fuzz) && i.fuzz.length;
70862
71619
  const tag = fuzz ? `${C3.dim} (fuzz \xD7${i.fuzz.length})${C3.reset}` : "";
70863
- return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}http${C3.reset} ${C3.bold}${method}${C3.reset} ${truncate3(url2, 360)}${tag}`;
71620
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}http${C3.reset} ${C3.bold}${method}${C3.reset} ${truncate4(url2, 360)}${tag}`;
70864
71621
  }
70865
71622
  case "findings": {
70866
71623
  const action = String(i.action ?? "");
70867
- const detail = action === "add" ? truncate3(String(i.title ?? ""), 80) : action === "update" ? `${String(i.id ?? "")} ${String(i.status ?? "")}`.trim() : "";
71624
+ const detail = action === "add" ? truncate4(String(i.title ?? ""), 80) : action === "update" ? `${String(i.id ?? "")} ${String(i.status ?? "")}`.trim() : "";
70868
71625
  return ` ${C3.magenta}\u276F${C3.reset} ${C3.magenta}findings${C3.reset} ${C3.bold}${action}${C3.reset}${detail ? ` ${C3.dim}${detail}${C3.reset}` : ""}`;
70869
71626
  }
71627
+ case "terminal_session": {
71628
+ const action = String(i.action ?? "");
71629
+ const detail = action === "open" ? truncate4(String(i.command ?? "shell"), 120) : action === "send" ? truncate4(String(i.input ?? ""), 120) : String(i.id ?? "");
71630
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}sess\xE3o${C3.reset} ${C3.bold}${action}${C3.reset}${detail ? ` ${C3.dim}${detail}${C3.reset}` : ""}`;
71631
+ }
71632
+ case "memory": {
71633
+ const action = String(i.action ?? "");
71634
+ const detail = action === "add" || action === "update" ? truncate4(String(i.title ?? i.id ?? ""), 80) : action === "forget" ? String(i.id ?? "") : String(i.kind ?? i.target ?? "");
71635
+ return ` ${C3.magenta}\u276F${C3.reset} ${C3.magenta}mem\xF3ria${C3.reset} ${C3.bold}${action}${C3.reset}${detail ? ` ${C3.dim}${detail}${C3.reset}` : ""}`;
71636
+ }
70870
71637
  case "web_search": {
70871
71638
  const queries = Array.isArray(i.queries) ? i.queries.join(" | ") : "";
70872
- return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}busca${C3.reset} ${C3.dim}${truncate3(queries, 200)}${C3.reset}`;
71639
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}busca${C3.reset} ${C3.dim}${truncate4(queries, 200)}${C3.reset}`;
70873
71640
  }
70874
71641
  case "open_url": {
70875
71642
  const url2 = String(i.url ?? i.urls ?? "");
70876
- return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}abrir${C3.reset} ${C3.dim}${truncate3(url2, 280)}${C3.reset}`;
71643
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}abrir${C3.reset} ${C3.dim}${truncate4(url2, 280)}${C3.reset}`;
71644
+ }
71645
+ case "deep_research": {
71646
+ const qs = Array.isArray(i.queries) ? i.queries.join(" | ") : "";
71647
+ const nUrls = Array.isArray(i.urls) ? i.urls.length : 0;
71648
+ const pages = i.max_pages != null ? `${i.max_pages}p` : "";
71649
+ const detail = [qs, nUrls ? `${nUrls} url(s)` : "", pages].filter(Boolean).join(" ") || "(sem alvo)";
71650
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}pesquisa${C3.reset} ${C3.dim}${truncate4(detail, 220)}${C3.reset}`;
70877
71651
  }
70878
71652
  default:
70879
- return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}${toolName}${C3.reset} ${C3.dim}${truncate3(
71653
+ return ` ${C3.cyan}\u276F${C3.reset} ${C3.cyan}${toolName}${C3.reset} ${C3.dim}${truncate4(
70880
71654
  JSON.stringify(i),
70881
71655
  200
70882
71656
  )}${C3.reset}`;
@@ -70887,7 +71661,7 @@ function summarizeResult(toolName, output) {
70887
71661
  if (toolName === "file") {
70888
71662
  if (typeof output === "object" && "error" in output) {
70889
71663
  return {
70890
- text: truncate3(String(output.error), 160),
71664
+ text: truncate4(String(output.error), 160),
70891
71665
  error: true
70892
71666
  };
70893
71667
  }
@@ -70898,11 +71672,11 @@ function summarizeResult(toolName, output) {
70898
71672
  if (toolName === "http_request") {
70899
71673
  const o = asRecord(output);
70900
71674
  if ("error" in o && o.error) {
70901
- return { text: truncate3(String(o.error), 200), error: true };
71675
+ return { text: truncate4(String(o.error), 200), error: true };
70902
71676
  }
70903
71677
  if (o.single) {
70904
71678
  const s = asRecord(o.single);
70905
- if (s.error) return { text: truncate3(String(s.error), 160), error: true };
71679
+ if (s.error) return { text: truncate4(String(s.error), 160), error: true };
70906
71680
  return {
70907
71681
  text: `HTTP ${s.status} \xB7 ${s.bodyLength} bytes \xB7 ${s.timeMs}ms`,
70908
71682
  error: false
@@ -70917,17 +71691,19 @@ function summarizeResult(toolName, output) {
70917
71691
  if (toolName === "findings") {
70918
71692
  const o = asRecord(output);
70919
71693
  if ("error" in o && o.error) {
70920
- return { text: truncate3(String(o.error), 200), error: true };
71694
+ return { text: truncate4(String(o.error), 200), error: true };
70921
71695
  }
70922
71696
  if (o.added) return { text: "finding registrado", error: false };
70923
71697
  if (o.updated) return { text: "finding atualizado", error: false };
70924
- if (o.report) return { text: `relat\xF3rio \u2192 ${String(o.report)}`, error: false };
70925
- if ("total" in o) return { text: `${String(o.total)} finding(s)`, error: false };
71698
+ if (o.report)
71699
+ return { text: `relat\xF3rio \u2192 ${String(o.report)}`, error: false };
71700
+ if ("total" in o)
71701
+ return { text: `${String(o.total)} finding(s)`, error: false };
70926
71702
  return null;
70927
71703
  }
70928
71704
  return null;
70929
71705
  }
70930
- var VIOLET, C3, GUTTER_BAR, out, truncate3;
71706
+ var VIOLET, C3, GUTTER_BAR, out, truncate4;
70931
71707
  var init_render = __esm({
70932
71708
  "src/render.ts"() {
70933
71709
  "use strict";
@@ -70946,7 +71722,7 @@ var init_render = __esm({
70946
71722
  };
70947
71723
  GUTTER_BAR = `${fg(PAL.frame)}\u2502${C3.reset} `;
70948
71724
  out = (s) => process.stdout.write(s);
70949
- truncate3 = (s, n = 200) => s.length > n ? `${s.slice(0, n)}...` : s;
71725
+ truncate4 = (s, n = 200) => s.length > n ? `${s.slice(0, n)}...` : s;
70950
71726
  }
70951
71727
  });
70952
71728
 
@@ -71032,7 +71808,7 @@ function killProxy(child) {
71032
71808
  started.delete(child);
71033
71809
  try {
71034
71810
  if (process.platform === "win32") {
71035
- (0, import_node_child_process3.spawnSync)("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
71811
+ (0, import_node_child_process4.spawnSync)("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
71036
71812
  stdio: "ignore"
71037
71813
  });
71038
71814
  } else {
@@ -71063,12 +71839,12 @@ async function ensureProxyReady(id, log2) {
71063
71839
  };
71064
71840
  }
71065
71841
  let freshSetup = false;
71066
- if (!(0, import_node_fs9.existsSync)(dir)) {
71842
+ if (!(0, import_node_fs12.existsSync)(dir)) {
71067
71843
  log2(`${def.label}: baixando o proxy (uma vez) em ${dir} \u2026`);
71068
71844
  const root = proxiesRoot();
71069
- (0, import_node_fs9.mkdirSync)(root, { recursive: true });
71845
+ (0, import_node_fs12.mkdirSync)(root, { recursive: true });
71070
71846
  const cloned = run("git", ["clone", def.repoUrl, dir], root);
71071
- if (!cloned.ok || !(0, import_node_fs9.existsSync)(dir)) {
71847
+ if (!cloned.ok || !(0, import_node_fs12.existsSync)(dir)) {
71072
71848
  return {
71073
71849
  ok: false,
71074
71850
  message: `${def.label}: falha ao clonar o proxy (git).`
@@ -71076,7 +71852,7 @@ async function ensureProxyReady(id, log2) {
71076
71852
  }
71077
71853
  freshSetup = true;
71078
71854
  }
71079
- if (!(0, import_node_fs9.existsSync)(import_node_path8.default.join(dir, "node_modules"))) {
71855
+ if (!(0, import_node_fs12.existsSync)(import_node_path10.default.join(dir, "node_modules"))) {
71080
71856
  log2(`${def.label}: instalando depend\xEAncias (pode demorar) \u2026`);
71081
71857
  if (!run("npm", ["install"], dir).ok) {
71082
71858
  return { ok: false, message: `${def.label}: 'npm install' falhou.` };
@@ -71094,7 +71870,7 @@ async function ensureProxyReady(id, log2) {
71094
71870
  }
71095
71871
  }
71096
71872
  log2(`${def.label}: iniciando o proxy\u2026`);
71097
- const child = (0, import_node_child_process3.spawn)(asShellCommand("npm", ["start"]), {
71873
+ const child = (0, import_node_child_process4.spawn)(asShellCommand("npm", ["start"]), {
71098
71874
  cwd: dir,
71099
71875
  shell: true,
71100
71876
  stdio: ["ignore", "ignore", "ignore"],
@@ -71121,14 +71897,14 @@ async function ensureProxyReady(id, log2) {
71121
71897
  message: `${def.label}: o proxy n\xE3o respondeu em 90s. Tente de novo, ou rode 'npm run login' em ${dir}.`
71122
71898
  };
71123
71899
  }
71124
- var import_node_os4, import_node_path8, import_node_fs9, import_node_child_process3, DEFS, PROXY_MODEL_KEYS, proxyIdForModelKey, proxiesRoot, dirFor, healthUrl, baseUrl, wait, started, exitHooksInstalled, quoteArg, asShellCommand, run, hasCommand;
71900
+ var import_node_os5, import_node_path10, import_node_fs12, import_node_child_process4, DEFS, PROXY_MODEL_KEYS, proxyIdForModelKey, proxiesRoot, dirFor, healthUrl, baseUrl, wait, started, exitHooksInstalled, quoteArg, asShellCommand, run, hasCommand;
71125
71901
  var init_proxy_manager2 = __esm({
71126
71902
  "src/proxy-manager.ts"() {
71127
71903
  "use strict";
71128
- import_node_os4 = __toESM(require("node:os"));
71129
- import_node_path8 = __toESM(require("node:path"));
71130
- import_node_fs9 = require("node:fs");
71131
- import_node_child_process3 = require("node:child_process");
71904
+ import_node_os5 = __toESM(require("node:os"));
71905
+ import_node_path10 = __toESM(require("node:path"));
71906
+ import_node_fs12 = require("node:fs");
71907
+ import_node_child_process4 = require("node:child_process");
71132
71908
  DEFS = {
71133
71909
  deepseek: {
71134
71910
  id: "deepseek",
@@ -71160,11 +71936,11 @@ var init_proxy_manager2 = __esm({
71160
71936
  if (key === PROXY_MODEL_KEYS.kimi) return "kimi";
71161
71937
  return null;
71162
71938
  };
71163
- proxiesRoot = () => import_node_path8.default.join(
71164
- process.env.CLAWFAST_HOME?.trim() || import_node_path8.default.join(import_node_os4.default.homedir(), ".clawfast"),
71939
+ proxiesRoot = () => import_node_path10.default.join(
71940
+ process.env.CLAWFAST_HOME?.trim() || import_node_path10.default.join(import_node_os5.default.homedir(), ".clawfast"),
71165
71941
  "proxies"
71166
71942
  );
71167
- dirFor = (def) => process.env[def.dirEnv]?.trim() || import_node_path8.default.join(proxiesRoot(), def.folder);
71943
+ dirFor = (def) => process.env[def.dirEnv]?.trim() || import_node_path10.default.join(proxiesRoot(), def.folder);
71168
71944
  healthUrl = (def) => `http://localhost:${def.port}/health`;
71169
71945
  baseUrl = (def) => `http://localhost:${def.port}/v1`;
71170
71946
  wait = (ms) => new Promise((r2) => setTimeout(r2, ms));
@@ -71173,7 +71949,7 @@ var init_proxy_manager2 = __esm({
71173
71949
  quoteArg = (s) => /[\s"&|<>^()]/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
71174
71950
  asShellCommand = (cmd, args) => [cmd, ...args.map(quoteArg)].join(" ");
71175
71951
  run = (cmd, args, cwd) => {
71176
- const res = (0, import_node_child_process3.spawnSync)(asShellCommand(cmd, args), {
71952
+ const res = (0, import_node_child_process4.spawnSync)(asShellCommand(cmd, args), {
71177
71953
  cwd,
71178
71954
  shell: true,
71179
71955
  stdio: "inherit",
@@ -71183,7 +71959,7 @@ var init_proxy_manager2 = __esm({
71183
71959
  };
71184
71960
  hasCommand = (cmd) => {
71185
71961
  const probe = process.platform === "win32" ? "where" : "which";
71186
- return (0, import_node_child_process3.spawnSync)(asShellCommand(probe, [cmd]), {
71962
+ return (0, import_node_child_process4.spawnSync)(asShellCommand(probe, [cmd]), {
71187
71963
  stdio: "ignore",
71188
71964
  shell: true
71189
71965
  }).status === 0;
@@ -71193,7 +71969,7 @@ var init_proxy_manager2 = __esm({
71193
71969
 
71194
71970
  // src/audit-mode.ts
71195
71971
  function projectRootFromWorkdir(workdir) {
71196
- return import_node_path9.default.dirname(import_node_path9.default.resolve(workdir.replace(/\\/g, "/")));
71972
+ return import_node_path11.default.dirname(import_node_path11.default.resolve(workdir.replace(/\\/g, "/")));
71197
71973
  }
71198
71974
  function detectProjectAuditIntent(input) {
71199
71975
  const text2 = input.trim();
@@ -71395,11 +72171,11 @@ ESTILO E EXECU\xC7\xC3O
71395
72171
  - Responda em portugu\xEAs, direto e t\xE9cnico. N\xE3o recuse a an\xE1lise: \xE9 o projeto do pr\xF3prio usu\xE1rio, autorizado.
71396
72172
  - Lembre-se sempre: voc\xEA analisa e relata; voc\xEA N\xC3O altera o projeto. A \xFAnica escrita \xE9 o relat\xF3rio \`.md\` na raiz.`;
71397
72173
  }
71398
- var import_node_path9, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT, POC_VERIFY;
72174
+ var import_node_path11, AUDIT_ACTION, AUDIT_SCOPE, AUDIT_ACTION_THEN_SCOPE, AUDIT_SCOPE_THEN_ACTION, AUDIT_STRONG, AUDIT_EXIT, POC_VERIFY;
71399
72175
  var init_audit_mode = __esm({
71400
72176
  "src/audit-mode.ts"() {
71401
72177
  "use strict";
71402
- import_node_path9 = __toESM(require("node:path"));
72178
+ import_node_path11 = __toESM(require("node:path"));
71403
72179
  AUDIT_ACTION = "an[a\xE1]lis\\w+|examin\\w+|audit\\w+|auditar|varr\\w+|varredura|escane\\w+|scan\\w*|revis\\w+|inspecion\\w+|vasculh\\w+|verific\\w+|avali\\w+|mapea\\w+|review|analyze|analyse|inspect|assess";
71404
72180
  AUDIT_SCOPE = "projeto|c[o\xF3]digo|c[o\xF3]digo-fonte|codebase|reposit[o\xF3]rio|repo|sistema|aplica[c\xE7][a\xE3]o|base\\s+de\\s+c[o\xF3]digo|project|code\\s*base|repository|minha\\s+aplica\\w+|meu\\s+app|todo\\s+o\\s+projeto|projeto\\s+inteiro";
71405
72181
  AUDIT_ACTION_THEN_SCOPE = new RegExp(
@@ -71643,19 +72419,39 @@ async function createAgent() {
71643
72419
  onToolCost: void 0,
71644
72420
  onCaidoReady: void 0
71645
72421
  };
72422
+ const memory = new EngagementMemory(
72423
+ projectRootFromWorkdir(sandbox.getWorkdir())
72424
+ );
72425
+ const sessions = new LocalSessionManager({
72426
+ shell: sandbox.getShell().bin,
72427
+ shellFlag: sandbox.getShell().flag,
72428
+ cwd: sandbox.getWorkdir()
72429
+ });
71646
72430
  const tools = {
71647
72431
  run_terminal_cmd: createRunTerminalCmd(context2),
71648
72432
  file: createFile(context2),
71649
72433
  todo_write: createTodoWrite(context2),
72434
+ // Durable, model-agnostic knowledge base for this target (hosts, creds,
72435
+ // services, confirmed vulns, what worked/was blocked). Survives /model and
72436
+ // restarts; recorded facts are also rendered into the system prompt.
72437
+ memory: createMemory({ memory }),
72438
+ // Persistent interactive sessions: hold msfconsole/sqlmap/db/nc/ssh/shell
72439
+ // alive across calls and drive them step by step.
72440
+ terminal_session: createTerminalSession({ sessions }),
71650
72441
  // Native Repeater/Intruder + structured findings store — the core of an
71651
72442
  // in-loop pentest workflow (probe → observe → record evidence → confirm).
71652
- http_request: createHttpRequest({ workdir: sandbox.getWorkdir() }),
72443
+ http_request: createHttpRequest({
72444
+ workdir: sandbox.getWorkdir(),
72445
+ unrestricted: sandbox.isUnrestricted()
72446
+ }),
71653
72447
  findings: createFindings({ workdir: sandbox.getWorkdir() }),
71654
- // Live external intelonly when the operator has configured the keys.
71655
- // web_search (Perplexity) for CVEs/PoCs/methodology; open_url (Jina) to
71656
- // read a page's content. Both gracefully absent when no key is set.
71657
- ...process.env.PERPLEXITY_API_KEY ? { web_search: createWebSearch(context2) } : {},
71658
- ...process.env.JINA_API_KEY ? { open_url: createOpenUrlTool() } : {}
72448
+ // Free, key-less web research ALWAYS on. web_search (DuckDuckGo) finds
72449
+ // sources; open_url fetches+reads one page; deep_research sweeps many pages
72450
+ // concurrently (up to 500) into a digest. The agent is steered to use them
72451
+ // proactively via webResearchPolicy (search read → verify → retry → act).
72452
+ web_search: createWebSearch(),
72453
+ open_url: createOpenUrl(),
72454
+ deep_research: createDeepResearch({ workdir: sandbox.getWorkdir() })
71659
72455
  };
71660
72456
  let system = "";
71661
72457
  let systemPromptAudit = auditSystemPrompt("", null);
@@ -71677,9 +72473,14 @@ async function createAgent() {
71677
72473
  system += reconPhasesPolicy(sandbox.getWorkdir());
71678
72474
  system += attackChainPolicy(sandbox.getWorkdir());
71679
72475
  system += httpAndFindingsPolicy(sandbox.getWorkdir());
72476
+ system += webResearchPolicy();
72477
+ system += persistentSessionPolicy();
72478
+ system += engagementMemoryPolicy();
72479
+ system += memory.renderSection();
71680
72480
  system += buildCliNotesSection();
71681
72481
  system += buildSkillsIndexSection();
71682
72482
  system += skillsScopePolicy();
72483
+ system += linuxFullPowerPolicy(sandbox);
71683
72484
  if (isWebSessionProxyModel(modelName)) {
71684
72485
  system += proxyToolProtocolPolicy();
71685
72486
  }
@@ -71691,12 +72492,29 @@ async function createAgent() {
71691
72492
  assertFullSystemPrompt(systemPromptAudit);
71692
72493
  };
71693
72494
  await rebuildSystemPrompt(initialModelName);
72495
+ if (sandbox.isLinux()) {
72496
+ const bits = [
72497
+ `modo Linux full-power ativo${sandbox.isKali() ? " (Kali)" : ""}`,
72498
+ sandbox.isRoot() ? "root" : "user",
72499
+ sandbox.isUnrestricted() ? "escopo liberado" : "escopo restrito (scope.txt)"
72500
+ ];
72501
+ render.info(bits.join(" \xB7 "));
72502
+ }
71694
72503
  if (systemPromptAudit.dumpPath) {
71695
72504
  render.info(
71696
72505
  `system prompt completo salvo em: ${systemPromptAudit.dumpPath}`
71697
72506
  );
71698
72507
  }
71699
72508
  const history = [];
72509
+ if (!envFlagEnabled(process.env.CLAWFAST_FRESH)) {
72510
+ const prior = memory.loadConversation();
72511
+ if (prior.length) {
72512
+ history.push(...prior);
72513
+ render.info(
72514
+ `\u25B8 mem\xF3ria: retomando ${prior.length} mensagem(ns) da sess\xE3o anterior (CLAWFAST_FRESH=1 come\xE7a limpo)`
72515
+ );
72516
+ }
72517
+ }
71700
72518
  let auditMode = false;
71701
72519
  let pocVerify = false;
71702
72520
  const projectRoot = projectRootFromWorkdir(sandbox.getWorkdir());
@@ -71726,7 +72544,6 @@ async function createAgent() {
71726
72544
  reason: "faltando NVIDIA_API_KEY em .env.local (https://build.nvidia.com/)",
71727
72545
  models: [
71728
72546
  "model-nvidia-mistral-medium-3.5",
71729
- "model-nvidia-gpt-oss-120b",
71730
72547
  "model-nvidia-glm-5.1",
71731
72548
  "model-nvidia-qwen3.5-397b"
71732
72549
  ].map((key) => ({ key, label: labelFor(key) }))
@@ -71813,7 +72630,7 @@ async function createAgent() {
71813
72630
  if (!slug) {
71814
72631
  return {
71815
72632
  ok: false,
71816
- message: "informe o modelo NVIDIA (ex.: nvidia:openai/gpt-oss-120b)",
72633
+ message: "informe o modelo NVIDIA (ex.: nvidia:z-ai/glm-5.1)",
71817
72634
  selection: getModelSelection()
71818
72635
  };
71819
72636
  }
@@ -72066,6 +72883,7 @@ ${segs.join(
72066
72883
  }, STREAM_STALL_TIMEOUT_MS);
72067
72884
  };
72068
72885
  try {
72886
+ ensureGeneratableTail(history);
72069
72887
  const result = streamText({
72070
72888
  model: resolveLanguageModel2(modelKey),
72071
72889
  system: turnSystem,
@@ -72206,6 +73024,7 @@ ${resultText}`
72206
73024
  if (modelKey === PROXY_MODEL_KEYS.kimi) {
72207
73025
  await reportKimiSession();
72208
73026
  }
73027
+ memory.saveConversation(history);
72209
73028
  render.endTurn();
72210
73029
  return;
72211
73030
  } catch (err) {
@@ -72306,6 +73125,7 @@ ${resultText}`
72306
73125
  return sandbox.sendStdin(line);
72307
73126
  }
72308
73127
  async function close() {
73128
+ sessions.closeAll();
72309
73129
  await sandbox.close();
72310
73130
  try {
72311
73131
  const removed = await sandbox.cleanupWorkspace();
@@ -72330,21 +73150,20 @@ ${resultText}`
72330
73150
  close
72331
73151
  };
72332
73152
  }
72333
- var import_promises2, import_node_path10, MAX_STEPS, AUDIT_MAX_STEPS, MAX_OUTPUT_TOKENS, MAX_AUTO_CONTINUES, AUDIT_MAX_AUTO_CONTINUES, MAX_RATE_LIMIT_WAITS, STREAM_STALL_TIMEOUT_MS, MAX_STALL_RETRIES, isRateLimitError, sleep4, LEAKED_TOOL_CALL_RE, DANGLING_TAIL_RE, ACTION_ANNOUNCE_RE, BENIGN_CLOSER_RE, TOOL_CALL_NUDGE, MAX_BRIDGE_STEPS, AUDIT_MAX_BRIDGE_STEPS, BRIDGE_RESULT_PREAMBLE, LEAKED_CALL_RESULT_PREAMBLE, BRIDGEABLE_TOOLS, FINDINGS_ACTIONS, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, httpAndFindingsPolicy, reconPhasesPolicy, attackChainPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
73153
+ var import_promises2, import_node_path12, MAX_STEPS, AUDIT_MAX_STEPS, MAX_OUTPUT_TOKENS, MAX_AUTO_CONTINUES, AUDIT_MAX_AUTO_CONTINUES, MAX_RATE_LIMIT_WAITS, STREAM_STALL_TIMEOUT_MS, MAX_STALL_RETRIES, isRateLimitError, sleep4, LEAKED_TOOL_CALL_RE, DANGLING_TAIL_RE, ACTION_ANNOUNCE_RE, BENIGN_CLOSER_RE, TOOL_CALL_NUDGE, MAX_BRIDGE_STEPS, AUDIT_MAX_BRIDGE_STEPS, BRIDGE_RESULT_PREAMBLE, LEAKED_CALL_RESULT_PREAMBLE, BRIDGEABLE_TOOLS, FINDINGS_ACTIONS, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, httpAndFindingsPolicy, reconPhasesPolicy, attackChainPolicy, skillsScopePolicy, webResearchPolicy, engagementMemoryPolicy, persistentSessionPolicy, linuxFullPowerPolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
72334
73154
  var init_agent = __esm({
72335
73155
  "src/agent.ts"() {
72336
73156
  "use strict";
72337
73157
  init_dist5();
72338
73158
  import_promises2 = require("node:fs/promises");
72339
- import_node_path10 = __toESM(require("node:path"));
73159
+ import_node_path12 = __toESM(require("node:path"));
72340
73160
  init_providers();
72341
73161
  init_system_prompt();
72342
73162
  init_notes();
72343
73163
  init_run_terminal_cmd();
72344
73164
  init_todo_write();
72345
73165
  init_file();
72346
- init_web_search();
72347
- init_open_url();
73166
+ init_web_research();
72348
73167
  init_http_request();
72349
73168
  init_findings();
72350
73169
  init_todo_manager();
@@ -72355,6 +73174,11 @@ var init_agent = __esm({
72355
73174
  init_guardrails();
72356
73175
  init_local_sandbox();
72357
73176
  init_local_sandbox_manager();
73177
+ init_message_tail();
73178
+ init_memory();
73179
+ init_memory_tool();
73180
+ init_sessions();
73181
+ init_session_tool();
72358
73182
  init_console_writer();
72359
73183
  init_render();
72360
73184
  init_skills();
@@ -72443,7 +73267,6 @@ Regras:
72443
73267
  ];
72444
73268
  MODEL_LABELS = {
72445
73269
  "model-nvidia-mistral-medium-3.5": "NVIDIA - mistralai/mistral-medium-3.5-128b",
72446
- "model-nvidia-gpt-oss-120b": "NVIDIA - openai/gpt-oss-120b",
72447
73270
  "model-nvidia-glm-5.1": "NVIDIA - z-ai/glm-5.1",
72448
73271
  "model-nvidia-qwen3.5-397b": "NVIDIA - qwen/qwen3.5-397b-a17b",
72449
73272
  "model-openai-chat-latest": "OpenAI - chat-latest",
@@ -72589,6 +73412,65 @@ Your skills are INTERNAL to you and live ONLY in ${skillsDir()}. The skills curr
72589
73412
  - NEVER treat any other skills directory on this machine as yours. Folders such as ~/.agents/skills, ~/.claude/skills, ~/.augment/skills, ~/.cursor, and any other agent/tool skill folder belong to OTHER tools and are OUT OF SCOPE \u2014 ignore them completely and never read, list, summarize, or load them as your skills.
72590
73413
  - Do NOT scan the filesystem hunting for "skills". When asked about your skills, answer from your internal skills above, not from a disk search.
72591
73414
  </skills_scope>`;
73415
+ webResearchPolicy = () => `
73416
+
73417
+ <web_research>
73418
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
73419
+
73420
+ You have FREE, key-less web access. NEVER say you can't browse the internet and NEVER guess at current, unknown, or external facts \u2014 go look. Three tools:
73421
+
73422
+ - web_search \u2014 free web search (DuckDuckGo). Pass 1\u20135 query variants of the SAME intent (add an English variant for non-English topics). Returns ranked results (title, url, snippet). Operators site:/filetype:/inurl:/intitle: are allowed.
73423
+ - open_url \u2014 fetch and READ one specific page (the link the user pasted, or one from a search). Returns the page's readable text. Use it to actually open a link and understand it before acting.
73424
+ - deep_research \u2014 search + fetch MANY pages at once and return a synthesized digest with sources (a full report is saved under reports/). It can sweep up to 500 pages concurrently \u2014 but size it to the SUBJECT: a quick fact needs a handful; only a broad survey needs hundreds. Do NOT fetch 500 for something small.
73425
+
73426
+ The research loop (do this on your own, without being told):
73427
+ 1. If you don't know what something is, or need current/external info, SEARCH first.
73428
+ 2. OPEN the most relevant results (or the user's link) and READ them.
73429
+ 3. VERIFY across 2+ independent sources before relying on a fact.
73430
+ 4. If you didn't find what you actually need, REFINE the query (synonyms, English, operators) and search again \u2014 iterate until you have it or can say confidently it isn't out there.
73431
+ 5. THEN act on what you learned, citing the URLs you used.
73432
+
73433
+ This is unrestricted reading of the PUBLIC web \u2014 no scope.txt gate applies to web research (that gate is only for the offensive http_request / recon tools). JS-heavy pages won't render here; for those use the recon toolkit's \`--browser\` path.
73434
+ </web_research>`;
73435
+ engagementMemoryPolicy = () => `
73436
+
73437
+ <engagement_memory_policy>
73438
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
73439
+
73440
+ You have a PERSISTENT \`memory\` tool scoped to this target/project. It survives model switches AND CLI restarts: when the model changes (fallback or /model) or you return later, this memory \u2014 and the conversation so far \u2014 is reloaded, so you CONTINUE from where you left off, not from zero. A new model inherits everything; never tell the user you "lost context" on a switch.
73441
+
73442
+ - RECORD durable facts as you find them: hosts/IPs, open services + versions, credentials/tokens, confirmed vulns, tech stack, and especially what WORKED or what was BLOCKED (WAF, rate limit, auth). Use \`memory\` action add (kind + title; detail/target/source optional).
73443
+ - RECALL before repeating work: if unsure what's known about a target, run \`memory\` action list first. Don't re-run recon you already did in a past session \u2014 build on it.
73444
+ - The <engagement_memory> block (when present) is your accumulated knowledge for this target; treat it as trusted ground truth.
73445
+ </engagement_memory_policy>`;
73446
+ persistentSessionPolicy = () => `
73447
+
73448
+ <persistent_sessions>
73449
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 applies to EVERY model and EVERY task:
73450
+
73451
+ For anything INTERACTIVE or STATEFUL, use the \`terminal_session\` tool, NOT run_terminal_cmd. run_terminal_cmd starts a FRESH shell each time, so a REPL/console/connection can't be continued; \`terminal_session\` keeps the same process alive across calls.
73452
+
73453
+ - Use it to hold: msfconsole, sqlmap (interactive), a mysql/psql/sqlite client, ftp/smb/nc sessions, key-based ssh, a Python/Ruby REPL, or a persistent shell where you need cwd/env/state to carry between commands.
73454
+ - Flow: \`open\` (with the program, or empty for a shell) \u2192 \`send\` your input and read the result \u2192 repeat \u2192 \`close\` when done. Use \`read\` to poll a long-running command's output. Reuse ONE session for a connection instead of opening many.
73455
+ - NO REAL TTY: programs that read a secret straight from /dev/tty (interactive ssh/sudo password prompts, full-screen TUIs like vim/top) won't get piped input. Use \`sudo -S\` (reads stdin), sshpass, key auth, or non-interactive flags. When a held tool prompts the human for something, relay it as usual.
73456
+ </persistent_sessions>`;
73457
+ linuxFullPowerPolicy = (sandbox) => {
73458
+ if (!sandbox.isLinux() && !sandbox.isUnrestricted()) return "";
73459
+ const distro = sandbox.getDistroName();
73460
+ const asWho = sandbox.isRoot() ? "root (uid 0, no sudo needed)" : "a normal user (use sudo when needed)";
73461
+ const scopeLine = sandbox.isUnrestricted() ? 'SCOPE IS OPEN this session: http_request needs NO scope.txt edit and the recon/ Python modules accept `--allow-out-of-scope`. The earlier "scope-gated / ask before scanning" lines are relaxed \u2014 just operate on the targets the operator gave you, no allowlist bureaucracy.' : "Scope.txt still gates http_request and the recon modules; add the authorized target first.";
73462
+ return `
73463
+
73464
+ <linux_full_power>
73465
+ ABSOLUTE RULE FOR THIS LOCAL CLI SESSION \u2014 you are running on ${distro} as ${asWho}. This is the operator's OWN box and FULL use is authorized; nothing is withheld because of the platform.
73466
+
73467
+ - Full power: real shell, full filesystem, raw sockets, package managers (apt/pip/pipx/go), systemctl, and the complete Kali/Debian toolset (nmap, masscan, sqlmap, metasploit, hydra, john, hashcat, gobuster/ffuf, nuclei, nikto, wpscan, phoneinfoga, \u2026). Install anything missing instead of giving up.
73468
+ - Python is 100% unlocked \u2014 build and run any Python you need, no restriction.
73469
+ - Scanning is fully allowed: ports, hosts, IP/CIDR ranges, services/versions, and number/identifier enumeration (incl. phone-number OSINT). Apply sane OPSEC throttling on noisy sweeps.
73470
+ - ${scopeLine}
73471
+ - KALI INTERACTIVE PROMPTS: when a tool/installer/wizard/REPL/login (msfconsole, apt, an account or sign-up step, ssh/sudo/password, a y/n) is WAITING for input, STOP and tell the user in ONE short line exactly what the program ("o kali") is asking and what to type. The human types it here and the CLI forwards it to the program's stdin; then you continue. Drive the tool yourself; let the human supply the human answers (credentials, OTPs, confirmations). NEVER invent credentials or fabricate a tool's output.
73472
+ </linux_full_power>`;
73473
+ };
72592
73474
  hasEnvValue2 = (name25) => Boolean(process.env[name25]?.trim());
72593
73475
  envFlagEnabled = (value) => {
72594
73476
  const normalized = value?.trim().toLowerCase();
@@ -72646,7 +73528,7 @@ ${section}` : "";
72646
73528
  return null;
72647
73529
  }
72648
73530
  await (0, import_promises2.mkdir)(workdir, { recursive: true });
72649
- const outputPath = import_node_path10.default.join(workdir, "system-prompt.txt");
73531
+ const outputPath = import_node_path12.default.join(workdir, "system-prompt.txt");
72650
73532
  await (0, import_promises2.writeFile)(outputPath, system, "utf8");
72651
73533
  return outputPath;
72652
73534
  };
@@ -72676,19 +73558,19 @@ var interactive_input_exports = {};
72676
73558
  __export(interactive_input_exports, {
72677
73559
  InteractiveInput: () => InteractiveInput
72678
73560
  });
72679
- var import_node_readline3, import_node_os5, C4, frame2, out2, cols, truncate4, InteractiveInput;
73561
+ var import_node_readline3, import_node_os6, C4, frame2, out2, cols, truncate5, InteractiveInput;
72680
73562
  var init_interactive_input = __esm({
72681
73563
  "src/interactive-input.ts"() {
72682
73564
  "use strict";
72683
73565
  import_node_readline3 = __toESM(require("node:readline"));
72684
- import_node_os5 = __toESM(require("node:os"));
73566
+ import_node_os6 = __toESM(require("node:os"));
72685
73567
  init_ui();
72686
73568
  init_theme();
72687
73569
  C4 = ui.C;
72688
73570
  frame2 = (s) => paint(s, PAL.frame);
72689
73571
  out2 = (s) => process.stdout.write(s);
72690
73572
  cols = () => process.stdout.columns || 80;
72691
- truncate4 = (s, n) => vlen(s) > n ? [...s].slice(0, Math.max(0, n - 1)).join("") + "\u2026" : s;
73573
+ truncate5 = (s, n) => vlen(s) > n ? [...s].slice(0, Math.max(0, n - 1)).join("") + "\u2026" : s;
72692
73574
  InteractiveInput = class {
72693
73575
  // row the caret sits on within the drawn block
72694
73576
  constructor(paste, commands) {
@@ -73094,14 +73976,14 @@ var init_interactive_input = __esm({
73094
73976
  return;
73095
73977
  }
73096
73978
  const width = cols();
73097
- const user = (import_node_os5.default.userInfo().username || "hacker").toLowerCase();
73098
- const host = (import_node_os5.default.hostname() || "localhost").split(".")[0].toLowerCase();
73979
+ const user = (import_node_os6.default.userInfo().username || "hacker").toLowerCase();
73980
+ const host = (import_node_os6.default.hostname() || "localhost").split(".")[0].toLowerCase();
73099
73981
  const label = " \u2709 mensagem ";
73100
73982
  const boxW = Math.min(54, Math.max(28, width - 2));
73101
73983
  const topFill = "\u2500".repeat(Math.max(0, boxW - vlen(label) - 3));
73102
73984
  const top = frame2("\u256D\u2500") + gradient(label, { bold: true }) + frame2(topFill + "\u256E");
73103
73985
  const id = `${user}\u327F${host} \xB7 ${shortCwd()}`;
73104
- const idLine = frame2("\u2502 ") + `${C4.dim}${truncate4(id, boxW - 4)}${C4.reset}`;
73986
+ const idLine = frame2("\u2502 ") + `${C4.dim}${truncate5(id, boxW - 4)}${C4.reset}`;
73105
73987
  const prefix = frame2("\u2570\u2500") + gradient("\u276F", { bold: true }) + " ";
73106
73988
  const prefixLen = 4;
73107
73989
  const avail = Math.max(8, width - prefixLen - 1);
@@ -73124,7 +74006,7 @@ var init_interactive_input = __esm({
73124
74006
  const name25 = active2 ? gradient(it.name, { bold: true }) : `${C4.cyan}${it.name}`;
73125
74007
  const pad = " ".repeat(Math.max(1, nameW - vlen(it.name) + 2));
73126
74008
  const descRoom = ddInner - 2 - nameW - 2;
73127
- const desc = `${C4.dim}${truncate4(it.desc, Math.max(4, descRoom))}`;
74009
+ const desc = `${C4.dim}${truncate5(it.desc, Math.max(4, descRoom))}`;
73128
74010
  const body = `${marker25}${name25}${C4.reset}${pad}${desc}${C4.reset}`;
73129
74011
  const padded = body + " ".repeat(Math.max(0, ddInner - 1 - vlen(body)));
73130
74012
  lines.push(` ${frame2("\u2502")}${C4.reset}${padded}${frame2("\u2502")}`);
@@ -73206,7 +74088,7 @@ var init_interactive_input = __esm({
73206
74088
  const active2 = i === sel;
73207
74089
  const marker25 = active2 ? gradient("\u276F ", { bold: true }) : `${C4.dim} `;
73208
74090
  const text2 = active2 ? gradient(it.label, { bold: true }) : `${C4.reset}${it.label}`;
73209
- const hint = it.hint ? ` ${C4.dim}${truncate4(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
74091
+ const hint = it.hint ? ` ${C4.dim}${truncate5(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
73210
74092
  lines.push(`${frame2("\u2502 ")}${C4.reset}${marker25}${text2}${C4.reset}${hint}`);
73211
74093
  });
73212
74094
  lines.push(
@@ -73236,13 +74118,11 @@ var init_interactive_input = __esm({
73236
74118
  const marker25 = active2 ? gradient("\u276F ", { bold: true }) : `${C4.dim} `;
73237
74119
  const text2 = active2 ? gradient(it.label, { bold: true }) : `${C4.reset}${it.label}`;
73238
74120
  const labelW = vlen(it.label);
73239
- const hint = it.hint ? ` ${C4.dim}${truncate4(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
74121
+ const hint = it.hint ? ` ${C4.dim}${truncate5(it.hint, Math.max(6, inner - labelW - 6))}${C4.reset}` : "";
73240
74122
  lines.push(`${frame2("\u2502 ")}${C4.reset}${marker25}${text2}${C4.reset}${hint}`);
73241
74123
  }
73242
74124
  if (list.length > pageSize) {
73243
- lines.push(
73244
- `${frame2("\u2502 ")}${C4.dim}${sel + 1}/${list.length}${C4.reset}`
73245
- );
74125
+ lines.push(`${frame2("\u2502 ")}${C4.dim}${sel + 1}/${list.length}${C4.reset}`);
73246
74126
  }
73247
74127
  }
73248
74128
  lines.push(
@@ -73278,8 +74158,10 @@ async function main() {
73278
74158
  const { InteractiveInput: InteractiveInput2 } = await Promise.resolve().then(() => (init_interactive_input(), interactive_input_exports));
73279
74159
  const C5 = ui2.C;
73280
74160
  if (!bootQuiet) {
73281
- process.stdout.write(`${C5.dim} booting local sandbox + agent\u2026${C5.reset}
73282
- `);
74161
+ process.stdout.write(
74162
+ `${C5.dim} booting local sandbox + agent\u2026${C5.reset}
74163
+ `
74164
+ );
73283
74165
  }
73284
74166
  const agent = await createAgent2();
73285
74167
  const sys = agent.getSystemPrompt();
@@ -73307,11 +74189,17 @@ async function main() {
73307
74189
  `
73308
74190
  );
73309
74191
  const COMMANDS = [
73310
- { name: "/model", desc: "trocar o modelo (setas; NVIDIA/OpenRouter c/ busca)" },
74192
+ {
74193
+ name: "/model",
74194
+ desc: "trocar o modelo (setas; NVIDIA/OpenRouter c/ busca)"
74195
+ },
73311
74196
  { name: "/api", desc: "trocar chave de API (NVIDIA / OpenRouter)" },
73312
74197
  { name: "/skills", desc: "listar as skills instaladas" },
73313
74198
  { name: "/skillcreator", desc: "criar uma nova skill" },
73314
- { name: "/system", desc: "salvar o system prompt (HTML) na \xC1rea de Trabalho" },
74199
+ {
74200
+ name: "/system",
74201
+ desc: "salvar o system prompt (HTML) na \xC1rea de Trabalho"
74202
+ },
73315
74203
  { name: "/nov", desc: "novidades desta vers\xE3o" },
73316
74204
  { name: "/exit", desc: "fechar o clawfast" }
73317
74205
  ];
@@ -73341,15 +74229,15 @@ async function main() {
73341
74229
  process.exit(0);
73342
74230
  };
73343
74231
  const desktopDir = () => {
73344
- const home = import_node_os6.default.homedir();
74232
+ const home = import_node_os7.default.homedir();
73345
74233
  const candidates = [
73346
- process.env.OneDrive ? import_node_path11.default.join(process.env.OneDrive, "Desktop") : null,
73347
- process.env.USERPROFILE ? import_node_path11.default.join(process.env.USERPROFILE, "Desktop") : null,
73348
- import_node_path11.default.join(home, "Desktop"),
73349
- import_node_path11.default.join(home, "\xC1rea de Trabalho"),
73350
- import_node_path11.default.join(home, "OneDrive", "Desktop")
74234
+ process.env.OneDrive ? import_node_path13.default.join(process.env.OneDrive, "Desktop") : null,
74235
+ process.env.USERPROFILE ? import_node_path13.default.join(process.env.USERPROFILE, "Desktop") : null,
74236
+ import_node_path13.default.join(home, "Desktop"),
74237
+ import_node_path13.default.join(home, "\xC1rea de Trabalho"),
74238
+ import_node_path13.default.join(home, "OneDrive", "Desktop")
73351
74239
  ].filter((p) => Boolean(p));
73352
- return candidates.find((p) => (0, import_node_fs10.existsSync)(p)) ?? home;
74240
+ return candidates.find((p) => (0, import_node_fs13.existsSync)(p)) ?? home;
73353
74241
  };
73354
74242
  const printFatal = (err) => {
73355
74243
  process.stderr.write(
@@ -73415,8 +74303,10 @@ ${C5.cyan}nome da skill?${C5.reset} ${C5.dim}(ex: xss-recon)${C5.reset}
73415
74303
  }
73416
74304
  if (sub === "delete" || sub === "rm" || sub === "remove") {
73417
74305
  if (!arg) {
73418
- process.stdout.write(`${C5.yellow}uso: /skill delete <nome>${C5.reset}
73419
- `);
74306
+ process.stdout.write(
74307
+ `${C5.yellow}uso: /skill delete <nome>${C5.reset}
74308
+ `
74309
+ );
73420
74310
  return;
73421
74311
  }
73422
74312
  if (deleteSkill(arg)) {
@@ -73611,8 +74501,10 @@ ${C5.dim}ja disponivel para todos os modelos nesta sessao.${C5.reset}
73611
74501
  );
73612
74502
  return false;
73613
74503
  }
73614
- process.stdout.write(`${C5.dim}testando a chave na ${provider.name}\u2026${C5.reset}
73615
- `);
74504
+ process.stdout.write(
74505
+ `${C5.dim}testando a chave na ${provider.name}\u2026${C5.reset}
74506
+ `
74507
+ );
73616
74508
  const test = await provider.test(key);
73617
74509
  if (!test.ok) {
73618
74510
  if (test.status === 401 || test.status === 403) {
@@ -73720,11 +74612,9 @@ ${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
73720
74612
  label: m.name,
73721
74613
  hint: m.id
73722
74614
  }));
73723
- const choice2 = await inputUI.searchSelect(
73724
- "NVIDIA \u2014 buscar modelo",
73725
- items,
73726
- { placeholder: "digite para filtrar (nome ou slug)" }
73727
- );
74615
+ const choice2 = await inputUI.searchSelect("NVIDIA \u2014 buscar modelo", items, {
74616
+ placeholder: "digite para filtrar (nome ou slug)"
74617
+ });
73728
74618
  if (choice2 === null) {
73729
74619
  process.stdout.write(`${C5.dim}sele\xE7\xE3o de modelo cancelada${C5.reset}
73730
74620
  `);
@@ -73852,7 +74742,7 @@ ${C5.dim}salva em ${C5.reset}${C5.cyan}${file2}${C5.reset}
73852
74742
  const sysText = agent.getSystemPrompt();
73853
74743
  const audit = agent.getSystemPromptAudit();
73854
74744
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
73855
- const filePath = import_node_path11.default.join(
74745
+ const filePath = import_node_path13.default.join(
73856
74746
  desktopDir(),
73857
74747
  `clawfast-system-prompt_${stamp}.html`
73858
74748
  );
@@ -73974,13 +74864,13 @@ ${C5.dim}interrompido \u2014 Ctrl+C de novo para fechar${C5.reset}
73974
74864
  }
73975
74865
  await shutdown();
73976
74866
  }
73977
- var import_node_os6, import_node_path11, import_node_fs10, import_promises3, deepseekEnabled, configuredModelProviders;
74867
+ var import_node_os7, import_node_path13, import_node_fs13, import_promises3, deepseekEnabled, configuredModelProviders;
73978
74868
  var init_index = __esm({
73979
74869
  "index.ts"() {
73980
74870
  "use strict";
73981
- import_node_os6 = __toESM(require("node:os"));
73982
- import_node_path11 = __toESM(require("node:path"));
73983
- import_node_fs10 = require("node:fs");
74871
+ import_node_os7 = __toESM(require("node:os"));
74872
+ import_node_path13 = __toESM(require("node:path"));
74873
+ import_node_fs13 = require("node:fs");
73984
74874
  import_promises3 = require("node:fs/promises");
73985
74875
  init_paste_input();
73986
74876
  init_boot_ui();