clawfast 2.1.0 → 2.1.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 (2) hide show
  1. package/dist/clawfast.cjs +109 -29
  2. package/package.json +1 -1
package/dist/clawfast.cjs CHANGED
@@ -662,7 +662,7 @@ var clawfastVersion, isDevVersion, isNewerVersion;
662
662
  var init_version = __esm({
663
663
  "src/version.ts"() {
664
664
  "use strict";
665
- clawfastVersion = () => true ? "2.1.0" : "0.0.0-dev";
665
+ clawfastVersion = () => true ? "2.1.1" : "0.0.0-dev";
666
666
  isDevVersion = () => clawfastVersion().includes("-dev");
667
667
  isNewerVersion = (a, b) => {
668
668
  const parse3 = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -715,7 +715,7 @@ var init_update = __esm({
715
715
  if (isDevVersion()) return null;
716
716
  const cache = readCache();
717
717
  if (cache && isNewerVersion(cache.latest, current)) {
718
- return `nova versao ${cache.latest} disponivel (voce tem ${current}) \u2014 rode: clawfast update`;
718
+ return `nova versao ${cache.latest} disponivel (voce tem ${current}) \u2014 rode: clawfast update e depois clawfast para usar a nova versao`;
719
719
  }
720
720
  return null;
721
721
  };
@@ -71335,12 +71335,23 @@ function balancedSpan(text2, open, close) {
71335
71335
  function entryToBridgedCall(entry) {
71336
71336
  if (!entry || typeof entry !== "object") return null;
71337
71337
  const e = entry;
71338
+ const brief = typeof e.brief === "string" ? e.brief : "";
71339
+ const explicit = typeof e.tool === "string" ? e.tool : typeof e.tool_name === "string" ? e.tool_name : null;
71340
+ if (explicit && BRIDGEABLE_TOOLS.has(explicit)) {
71341
+ const nested = [e.input, e.args, e.parameters].find(
71342
+ (v) => v && typeof v === "object" && !Array.isArray(v)
71343
+ );
71344
+ const input = { brief, ...nested ?? e };
71345
+ delete input.tool;
71346
+ delete input.tool_name;
71347
+ return { toolName: explicit, input };
71348
+ }
71338
71349
  if (typeof e.command === "string" && e.command.trim()) {
71339
71350
  return {
71340
71351
  toolName: "run_terminal_cmd",
71341
71352
  input: {
71342
71353
  command: e.command,
71343
- brief: typeof e.brief === "string" ? e.brief : "",
71354
+ brief,
71344
71355
  is_background: e.is_background === true,
71345
71356
  interactive: e.interactive === true,
71346
71357
  timeout: typeof e.timeout === "number" ? e.timeout : 60
@@ -71353,13 +71364,25 @@ function entryToBridgedCall(entry) {
71353
71364
  input: {
71354
71365
  action: e.action,
71355
71366
  path: e.path,
71356
- brief: typeof e.brief === "string" ? e.brief : "",
71367
+ brief,
71357
71368
  ...typeof e.text === "string" ? { text: e.text } : {},
71358
71369
  ...Array.isArray(e.range) ? { range: e.range } : {},
71359
71370
  ...Array.isArray(e.edits) ? { edits: e.edits } : {}
71360
71371
  }
71361
71372
  };
71362
71373
  }
71374
+ if (typeof e.action === "string" && FINDINGS_ACTIONS.has(e.action)) {
71375
+ return { toolName: "findings", input: { ...e, brief } };
71376
+ }
71377
+ if (typeof e.url === "string" && e.url.trim()) {
71378
+ return { toolName: "http_request", input: { ...e, brief } };
71379
+ }
71380
+ if (Array.isArray(e.todos)) {
71381
+ return {
71382
+ toolName: "todo_write",
71383
+ input: { merge: e.merge === true, todos: e.todos }
71384
+ };
71385
+ }
71363
71386
  return null;
71364
71387
  }
71365
71388
  function validateBatch(raw) {
@@ -71396,12 +71419,27 @@ function parseProxyToolCalls(text2) {
71396
71419
  }
71397
71420
  function summarizeBridgedCalls(calls) {
71398
71421
  return calls.map((call) => {
71399
- if (call.toolName === "run_terminal_cmd") {
71400
- return `$ ${truncateBridgeSummary(String(call.input.command ?? ""))}`;
71422
+ const i = call.input;
71423
+ switch (call.toolName) {
71424
+ case "run_terminal_cmd":
71425
+ return `$ ${truncateBridgeSummary(String(i.command ?? ""))}`;
71426
+ case "file":
71427
+ return `${String(i.action ?? "file")} ${truncateBridgeSummary(
71428
+ String(i.path ?? ""),
71429
+ 120
71430
+ )}`.trim();
71431
+ case "http_request":
71432
+ return `${String(i.method ?? "GET")} ${truncateBridgeSummary(
71433
+ String(i.url ?? ""),
71434
+ 120
71435
+ )}`.trim();
71436
+ case "findings":
71437
+ return `findings ${String(i.action ?? "")}`.trim();
71438
+ case "todo_write":
71439
+ return `todo_write (${Array.isArray(i.todos) ? i.todos.length : 0} itens)`;
71440
+ default:
71441
+ return call.toolName;
71401
71442
  }
71402
- const action = String(call.input.action ?? "file");
71403
- const target = String(call.input.path ?? "");
71404
- return `${action} ${truncateBridgeSummary(target, 120)}`.trim();
71405
71443
  }).join("; ");
71406
71444
  }
71407
71445
  async function createAgent() {
@@ -71724,6 +71762,31 @@ ${seen}`);
71724
71762
  }
71725
71763
  let streamError = null;
71726
71764
  let stalled = false;
71765
+ let turnText = "";
71766
+ const turnToolSummaries = [];
71767
+ const preservePartialWork = () => {
71768
+ const segs = [];
71769
+ const text2 = turnText.trim();
71770
+ if (text2) {
71771
+ segs.push(text2.length > 4e3 ? `...${text2.slice(-4e3)}` : text2);
71772
+ }
71773
+ if (turnToolSummaries.length) {
71774
+ segs.push(
71775
+ "A\xE7\xF5es j\xE1 executadas neste turno:\n" + turnToolSummaries.map((s) => `- ${s}`).join("\n")
71776
+ );
71777
+ }
71778
+ if (!segs.length) return;
71779
+ history.push({
71780
+ role: "assistant",
71781
+ content: `[Trabalho parcial de ${labelFor(
71782
+ modelKey
71783
+ )} antes de falhar \u2014 N\xC3O recomece do zero; continue exatamente a partir daqui:]
71784
+
71785
+ ${segs.join(
71786
+ "\n\n"
71787
+ )}`
71788
+ });
71789
+ };
71727
71790
  const stallController = new AbortController();
71728
71791
  const onUserAbort = () => stallController.abort();
71729
71792
  if (signal) {
@@ -71758,9 +71821,8 @@ ${seen}`);
71758
71821
  }
71759
71822
  });
71760
71823
  let lastStepText = "";
71761
- let bufferedProxyText = "";
71762
71824
  let turnToolCalls = 0;
71763
- const shouldBufferProxyText = isWebSessionProxyModel(modelKey);
71825
+ const isProxyModel = isWebSessionProxyModel(modelKey);
71764
71826
  let pendingTools = 0;
71765
71827
  const noteActivity = () => {
71766
71828
  if (pendingTools === 0) armStallTimer();
@@ -71772,11 +71834,8 @@ ${seen}`);
71772
71834
  case "text-delta": {
71773
71835
  const delta = part.text ?? part.delta ?? "";
71774
71836
  lastStepText += delta;
71775
- if (shouldBufferProxyText) {
71776
- bufferedProxyText += delta;
71777
- } else {
71778
- render.text(delta);
71779
- }
71837
+ turnText += delta;
71838
+ render.text(delta);
71780
71839
  noteActivity();
71781
71840
  break;
71782
71841
  }
@@ -71784,12 +71843,16 @@ ${seen}`);
71784
71843
  render.reasoning(part.text ?? part.delta ?? "");
71785
71844
  noteActivity();
71786
71845
  break;
71787
- case "tool-call":
71846
+ case "tool-call": {
71788
71847
  turnToolCalls++;
71789
71848
  pendingTools++;
71790
71849
  clearStallTimer();
71791
- render.toolCall(part.toolName, part.input ?? part.args);
71850
+ const input = part.input ?? part.args ?? {};
71851
+ const brief = typeof input.command === "string" ? `$ ${input.command}` : typeof input.path === "string" ? `${String(input.action ?? "file")} ${input.path}` : String(part.toolName);
71852
+ turnToolSummaries.push(truncateBridgeSummary(brief, 160));
71853
+ render.toolCall(part.toolName, input);
71792
71854
  break;
71855
+ }
71793
71856
  case "tool-result":
71794
71857
  if (pendingTools > 0) pendingTools--;
71795
71858
  render.toolResult(part.toolName, part.output ?? part.result);
@@ -71801,7 +71864,6 @@ ${seen}`);
71801
71864
  break;
71802
71865
  case "start-step":
71803
71866
  lastStepText = "";
71804
- bufferedProxyText = "";
71805
71867
  noteActivity();
71806
71868
  break;
71807
71869
  case "error":
@@ -71834,8 +71896,8 @@ ${seen}`);
71834
71896
  const finishReason = String(
71835
71897
  await result.finishReason.catch(() => "unknown")
71836
71898
  );
71837
- const leakedCall = !shouldBufferProxyText && hasLeakedToolCallMarker(lastStepText);
71838
- if (finishReason === "stop" && turnToolCalls === 0 && bridgeSteps < MAX_BRIDGE_STEPS && (shouldBufferProxyText || leakedCall)) {
71899
+ const leakedCall = !isProxyModel && hasLeakedToolCallMarker(lastStepText);
71900
+ if (finishReason === "stop" && turnToolCalls === 0 && bridgeSteps < MAX_BRIDGE_STEPS && (isProxyModel || leakedCall)) {
71839
71901
  const bridged = parseProxyToolCalls(lastStepText);
71840
71902
  if (bridged) {
71841
71903
  bridgeSteps++;
@@ -71877,9 +71939,6 @@ ${resultText}`
71877
71939
  idx--;
71878
71940
  continue;
71879
71941
  }
71880
- if (shouldBufferProxyText && bufferedProxyText) {
71881
- render.text(bufferedProxyText);
71882
- }
71883
71942
  if (finishReason !== "stop") {
71884
71943
  render.info(
71885
71944
  `\u25B8 fim do turno: ${finishReason}` + (autoContinues >= MAX_AUTO_CONTINUES ? " (limite de retomadas autom\xE1ticas atingido)" : "")
@@ -71902,6 +71961,7 @@ ${resultText}`
71902
71961
  while (history.length && history[history.length - 1]?.role !== "user") {
71903
71962
  history.pop();
71904
71963
  }
71964
+ preservePartialWork();
71905
71965
  const secs = Math.round(STREAM_STALL_TIMEOUT_MS / 1e3);
71906
71966
  lastError = new Error(
71907
71967
  `stream travou (${secs}s sem dados) em ${labelFor(modelKey)}`
@@ -71945,6 +72005,7 @@ ${resultText}`
71945
72005
  while (history.length && history[history.length - 1]?.role !== "user") {
71946
72006
  history.pop();
71947
72007
  }
72008
+ preservePartialWork();
71948
72009
  if (rateLimited && rateLimitWaits < MAX_RATE_LIMIT_WAITS) {
71949
72010
  const waitMs = Math.min(3e3 * 2 ** rateLimitWaits, 15e3);
71950
72011
  rateLimitWaits++;
@@ -71998,7 +72059,7 @@ ${resultText}`
71998
72059
  close
71999
72060
  };
72000
72061
  }
72001
- var import_promises2, import_node_path10, MAX_STEPS, MAX_OUTPUT_TOKENS, 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, BRIDGE_RESULT_PREAMBLE, LEAKED_CALL_RESULT_PREAMBLE, truncateBridgeSummary, isWebSessionProxyModel, proxyToolProtocolPolicy, SYSTEM_PROMPT_SOURCE, REQUIRED_SYSTEM_PROMPT_MARKERS, MODEL_LABELS, labelFor, loginRequiredHint, CLI_PYTHON_ONLY_POLICY, pythonOnlyPolicy, scriptFilePolicy, deepReconPolicy, httpAndFindingsPolicy, reconPhasesPolicy, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
72062
+ var import_promises2, import_node_path10, MAX_STEPS, MAX_OUTPUT_TOKENS, 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, 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, skillsScopePolicy, hasEnvValue2, envFlagEnabled, CLI_PERSONALITIES, buildCliUserCustomization, buildCliNotesSection, cliGuardrailsConfig, maybeDumpSystemPrompt, auditSystemPrompt, assertFullSystemPrompt;
72002
72063
  var init_agent = __esm({
72003
72064
  "src/agent.ts"() {
72004
72065
  "use strict";
@@ -72060,6 +72121,14 @@ var init_agent = __esm({
72060
72121
  MAX_BRIDGE_STEPS = 50;
72061
72122
  BRIDGE_RESULT_PREAMBLE = "Voc\xEA roda via proxy de sess\xE3o web (sem function-calling nativo), ent\xE3o o runtime do CLI consumiu o bloco JSON anterior como chamada interna e EXECUTOU de verdade. N\xE3o repita o bloco nem o texto anterior. Os resultados REAIS est\xE3o abaixo. Continue a tarefa: se precisar de mais a\xE7\xF5es, emita SOMENTE o PR\xD3XIMO bloco ```json (um array de {brief, command, timeout} para terminal, ou {action, path, text} para arquivo). Um bloco por vez. Quando a tarefa terminar, responda em texto normal SEM bloco json.";
72062
72123
  LEAKED_CALL_RESULT_PREAMBLE = "Sua chamada de ferramenta anterior chegou como TEXTO (n\xE3o como function call nativo), ent\xE3o o runtime do CLI a executou mesmo assim. Os resultados REAIS est\xE3o abaixo. Continue a tarefa chamando as ferramentas NORMALMENTE (run_terminal_cmd, file, todo_write) como function calls de verdade \u2014 N\xC3O cole markup de tool call (`call_tool_function`, `<tool_call>`, etc.) nem JSON de chamada como texto na resposta.";
72124
+ BRIDGEABLE_TOOLS = /* @__PURE__ */ new Set([
72125
+ "run_terminal_cmd",
72126
+ "file",
72127
+ "findings",
72128
+ "http_request",
72129
+ "todo_write"
72130
+ ]);
72131
+ FINDINGS_ACTIONS = /* @__PURE__ */ new Set(["add", "update", "list", "report"]);
72063
72132
  truncateBridgeSummary = (value, max = 180) => value.length > max ? `${value.slice(0, max)}...` : value;
72064
72133
  isWebSessionProxyModel = (modelName) => modelName === "model-deepseek-proxy" || modelName === "model-kimi-proxy";
72065
72134
  proxyToolProtocolPolicy = () => `
@@ -72067,14 +72136,17 @@ var init_agent = __esm({
72067
72136
  <web_session_tool_protocol>
72068
72137
  ATIVO NESTA SESS\xC3O: o modelo atual roda por um proxy de sess\xE3o web e N\xC3O tem function-calling nativo. Para EXECUTAR qualquer a\xE7\xE3o voc\xEA n\xE3o chama ferramentas de fun\xE7\xE3o \u2014 voc\xEA EMITE um \xFAnico bloco de c\xF3digo \`\`\`json com um array de chamadas, e o runtime do CLI executa de verdade e te devolve os resultados como uma mensagem do usu\xE1rio. Repita o ciclo at\xE9 concluir.
72069
72138
 
72070
- Formato EXATO (sempre um array, mesmo para uma \xFAnica a\xE7\xE3o):
72139
+ Formato EXATO (sempre um array, mesmo para uma \xFAnica a\xE7\xE3o). Voc\xEA pode SEMPRE incluir "tool": "<nome>" para deixar expl\xEDcito qual ferramenta chamar (recomendado para findings/http_request/todo_write):
72071
72140
  - Terminal: {"brief": "uma frase", "command": "<comando shell>", "timeout": 60, "is_background": false}
72072
72141
  - Arquivo: {"action": "write"|"edit"|"append"|"read", "path": "<caminho absoluto>", "brief": "uma frase", "text": "<conte\xFAdo completo para write/append>"}
72142
+ - HTTP (Repeater/Intruder): {"tool": "http_request", "brief": "uma frase", "url": "https://alvo/...", "method": "GET", "headers": {"X":"y"}, "body": "...", "fuzz": ["a","b"]}
72143
+ - Findings (mem\xF3ria estruturada de vulns): {"tool": "findings", "action": "add"|"update"|"list"|"report", "brief": "uma frase", "title": "...", "severity": "critical"|"high"|"medium"|"low"|"info", "evidence": "...", "id": "F-001"}
72144
+ - Tarefas (todo): {"tool": "todo_write", "merge": true, "todos": [{"id": "1", "content": "...", "status": "pending"|"in_progress"|"completed"}]}
72073
72145
 
72074
72146
  Regras:
72075
72147
  1. Para agir, a resposta DEVE conter um bloco \`\`\`json com o array. Esse bloco ser\xE1 consumido pelo CLI como chamada interna e n\xE3o deve ser tratado como texto de terminal.
72076
72148
  2. UM bloco por turno; aguarde os resultados antes de emitir o pr\xF3ximo.
72077
- 3. Cada entrada do array deve mapear para uma a\xE7\xE3o (tem "command", ou tem "action"+"path"). N\xE3o misture com dados que n\xE3o s\xE3o a\xE7\xF5es.
72149
+ 3. Cada entrada do array deve mapear para uma a\xE7\xE3o: terminal ("command"), arquivo ("action"+"path"), ou outra ferramenta via "tool" ("http_request" com "url", "findings" com "action" add/update/list/report, "todo_write" com "todos"). N\xE3o misture com dados que n\xE3o s\xE3o a\xE7\xF5es.
72078
72150
  4. Crie/edite scripts e c\xF3digo-fonte SOMENTE via {"action":"write"} / {"action":"edit"} \u2014 nunca por redirecionamento de shell (echo >, cat <<EOF, tee, Out-File...).
72079
72151
  5. Durante a\xE7\xF5es, coloque o conte\xFAdo operacional apenas dentro do JSON. Fora do JSON use no m\xE1ximo uma frase curta.
72080
72152
  6. Quando a tarefa estiver conclu\xEDda, responda em texto normal SEM bloco json.
@@ -72435,10 +72507,14 @@ var init_interactive_input = __esm({
72435
72507
  * forwarded to an interactive command's stdin. No box/dropdown — just echoes
72436
72508
  * and collects until Enter. Ctrl+C invokes `onSigint`.
72437
72509
  */
72438
- beginTurn(onLine, onSigint) {
72510
+ beginTurn(onLine, onSigint, isActive) {
72439
72511
  let buf = "";
72440
72512
  this.onKey = (str, key) => {
72441
72513
  if (key.ctrl && key.name === "c") return onSigint();
72514
+ if (!isActive()) {
72515
+ buf = "";
72516
+ return;
72517
+ }
72442
72518
  if (key.name === "return") {
72443
72519
  out2("\r\n");
72444
72520
  const line = this.paste.expand(buf);
@@ -73218,7 +73294,10 @@ ${C5.dim}interrompido \u2014 Ctrl+C de novo para fechar${C5.reset}
73218
73294
  );
73219
73295
  }
73220
73296
  armExit();
73221
- }
73297
+ },
73298
+ // Only echo/forward typed input while a foreground command is waiting on
73299
+ // stdin; otherwise type-ahead during the model's reply is dropped silently.
73300
+ () => agent.isRunningCommand()
73222
73301
  );
73223
73302
  try {
73224
73303
  await agent.send(input, activeAbort.signal);
@@ -73337,6 +73416,7 @@ async function handleSubcommand() {
73337
73416
  process.stdout.write(
73338
73417
  `
73339
73418
  \x1B[92m\x1B[1mSeja bem vindo ao submundo.\x1B[0m
73419
+ \x1B[2mRode \x1B[0m\x1B[36mclawfast\x1B[0m\x1B[2m para usar a nova versao.\x1B[0m
73340
73420
  `
73341
73421
  );
73342
73422
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawfast",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "CLAWFAST — agente de pentest no terminal. Com o clawfast você faz tudo.",
5
5
  "bin": {
6
6
  "clawfast": "dist/clawfast.cjs"