beecork 2.4.1 → 2.4.2

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 +3 -2
  2. package/dist/index.js +27 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -79,8 +79,9 @@ main conversation clean. It cannot modify anything, run commands, or recurse.
79
79
 
80
80
  ### Status line
81
81
 
82
- A live bar on the terminal's bottom row shows `model · effort · git branch · ~tokens · background tasks`,
83
- refreshed every couple seconds. Disable with `STATUSLINE=0`.
82
+ An optional bar on the terminal's bottom row shows `model · effort · git branch · ~tokens · background
83
+ tasks`. It's **off by default** while a proper pinned-input version is built; opt into the interim one
84
+ with `STATUSLINE=1`.
84
85
 
85
86
  ### Skipping permissions (danger)
86
87
 
package/dist/index.js CHANGED
@@ -114,9 +114,10 @@ var config = {
114
114
  // Sub-agent (explore tool)
115
115
  subAgentMaxSteps: num("SUBAGENT_MAX_STEPS", 15),
116
116
  // child explorer's step budget (bounds cost/latency)
117
- // Live status line (bottom row: model · effort · git branch · ~tokens · bg tasks)
118
- statuslineEnabled: !["0", "false", "off", "no"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
119
- // default on; STATUSLINE=0 disables
117
+ // Live status line (bottom row: model · effort · git branch · ~tokens · bg tasks). DEFAULT OFF —
118
+ // the interim scroll-region version clashes with the inline input (banner loss / flicker); it's
119
+ // being replaced by a proper pinned-input UI. Opt in with STATUSLINE=1.
120
+ statuslineEnabled: ["1", "true", "on", "yes"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
120
121
  statuslineRefreshMs: num("STATUSLINE_REFRESH_MS", 2e3),
121
122
  // bar refresh interval
122
123
  // Integrations / modes
@@ -1249,7 +1250,7 @@ import { homedir as homedir3 } from "node:os";
1249
1250
  import { basename } from "node:path";
1250
1251
  function pathGuard(args) {
1251
1252
  const { abs, inRoot } = resolveInRoot(String(args.path ?? "."));
1252
- return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}` };
1253
+ return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}`, cacheKey: `path:${abs}` };
1253
1254
  }
1254
1255
  var SECRET_FILE = /(^|\/)(\.env(\.[\w.-]+)?|[\w.-]*\.(env|pem|key|secret|pfx|p12|jks|keystore)|id_(rsa|ed25519|ecdsa|dsa)|credentials|\.git-credentials|\.pgpass|\.npmrc|\.netrc)$/i;
1255
1256
  function isSecretPath(userPath) {
@@ -2388,7 +2389,7 @@ async function callModel(messages, includeTools = true, signal, opts) {
2388
2389
  stopSpinner();
2389
2390
  if (!quiet) {
2390
2391
  if (!printedText) {
2391
- process.stdout.write("\n" + color.cyan("bee: "));
2392
+ process.stdout.write((printedReasoning ? "\n\n" : "\n") + color.cyan("bee: "));
2392
2393
  printedText = true;
2393
2394
  }
2394
2395
  const safe = stripControl(ev.content);
@@ -2796,7 +2797,7 @@ var SYSTEM_PROMPT = `You are beecork, a coding assistant working in a terminal o
2796
2797
  # Safety
2797
2798
  - Be careful with anything that deletes or overwrites. Don't do destructive things unless the user clearly asked.
2798
2799
  - Don't commit or push to git, publish, deploy, or take other outward or hard-to-undo actions unless the user explicitly asked.`;
2799
- async function askApproval(ask, call, reason) {
2800
+ async function askApproval(ask, call, reason, offerAlways = true) {
2800
2801
  let args = {};
2801
2802
  try {
2802
2803
  args = JSON.parse(call.function.arguments);
@@ -2818,8 +2819,9 @@ async function askApproval(ask, call, reason) {
2818
2819
  } else {
2819
2820
  console.log(color.yellow(` ${stripControl(call.function.arguments)}`));
2820
2821
  }
2821
- const answer = (await ask(color.yellow(" allow? [y]es / [n]o / [a]lways: "))).trim().toLowerCase();
2822
- if (answer === "a" || answer === "always") return "always";
2822
+ const prompt = offerAlways ? " allow? [y]es / [n]o / [a]lways: " : " allow? [y]es / [n]o: ";
2823
+ const answer = (await ask(color.yellow(prompt))).trim().toLowerCase();
2824
+ if (offerAlways && (answer === "a" || answer === "always")) return "always";
2823
2825
  if (answer === "y" || answer === "yes") return "once";
2824
2826
  return "deny";
2825
2827
  }
@@ -2830,8 +2832,9 @@ function decideApproval(tool, args, ctx) {
2830
2832
  if (ctx.dangerouslySkip) return { action: "run" };
2831
2833
  const guard = tool?.guard?.(args);
2832
2834
  if (guard?.needsApproval) {
2835
+ if (guard.cacheKey && ctx.approvedGuardKeys?.has(guard.cacheKey)) return { action: "run" };
2833
2836
  if (ctx.autoApprove) return { action: "deny", kind: "headless", reason: guard.reason ?? "blocked" };
2834
- return { action: "ask", cacheable: false, reason: guard.reason };
2837
+ return guard.cacheKey ? { action: "ask", cacheable: true, reason: guard.reason, guardKey: guard.cacheKey } : { action: "ask", cacheable: false, reason: guard.reason };
2835
2838
  }
2836
2839
  if (tool?.needsApproval && !ctx.autoApprove && ctx.mode !== "auto") {
2837
2840
  if (tool.alwaysAsk) return { action: "ask", cacheable: false };
@@ -2867,7 +2870,7 @@ async function handleAskUser(args) {
2867
2870
  return askUserMessage(question, choice, true);
2868
2871
  }
2869
2872
  async function handleToolCall(call, messages, step, deps) {
2870
- const { approvedTools, callCounts, ask, signal } = deps;
2873
+ const { approvedTools, approvedGuardKeys, callCounts, ask, signal } = deps;
2871
2874
  const pushTool = (content) => messages.push({ role: "tool", tool_call_id: call.id, content });
2872
2875
  const sig = `${call.function.name}:${call.function.arguments}`;
2873
2876
  const seen = (callCounts.get(sig) ?? 0) + 1;
@@ -2892,6 +2895,7 @@ async function handleToolCall(call, messages, step, deps) {
2892
2895
  mode: state.mode,
2893
2896
  autoApprove: config.autoApprove,
2894
2897
  approvedTools,
2898
+ approvedGuardKeys,
2895
2899
  toolName: call.function.name,
2896
2900
  dangerouslySkip: config.dangerouslySkipPermissions
2897
2901
  });
@@ -2906,15 +2910,19 @@ async function handleToolCall(call, messages, step, deps) {
2906
2910
  return;
2907
2911
  }
2908
2912
  if (decision.action === "ask") {
2909
- const answer = await askApproval(ask, call, decision.cacheable ? void 0 : decision.reason);
2913
+ const answer = await askApproval(ask, call, decision.reason, decision.cacheable);
2910
2914
  if (answer === "deny") {
2911
2915
  console.log(color.red(" \u21B3 denied") + "\n");
2912
- pushTool(decision.cacheable ? "The user DENIED permission to run this tool. Do not retry it." : `The user DENIED this (${decision.reason}). Do not retry, and do not route around it with run_bash/cat or another tool.`);
2916
+ pushTool(decision.reason ? `The user DENIED this (${decision.reason}). Do not retry, and do not route around it with run_bash/cat or another tool.` : "The user DENIED permission to run this tool. Do not retry it.");
2913
2917
  return;
2914
2918
  }
2915
- if (decision.cacheable && answer === "always") {
2916
- approvedTools.add(call.function.name);
2917
- void addProjectApproval(call.function.name);
2919
+ if (answer === "always" && decision.cacheable) {
2920
+ if (decision.guardKey) {
2921
+ approvedGuardKeys.add(decision.guardKey);
2922
+ } else {
2923
+ approvedTools.add(call.function.name);
2924
+ void addProjectApproval(call.function.name);
2925
+ }
2918
2926
  }
2919
2927
  }
2920
2928
  if (config.traceFile) trace.push({ tool: call.function.name, args: call.function.arguments, step });
@@ -2970,13 +2978,13 @@ ${content}` }];
2970
2978
  }
2971
2979
  return [...messages, { role: "user", content }];
2972
2980
  }
2973
- async function runTurn(messages, userInput, ask, approvedTools, signal, steering) {
2981
+ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal, steering) {
2974
2982
  messages.push({ role: "user", content: userInput });
2975
2983
  const snapshot = messages.slice();
2976
2984
  try {
2977
2985
  let answered = false;
2978
2986
  const callCounts = /* @__PURE__ */ new Map();
2979
- const deps = { approvedTools, callCounts, ask, signal };
2987
+ const deps = { approvedTools, approvedGuardKeys, callCounts, ask, signal };
2980
2988
  for (let step = 0; step < config.maxSteps && !answered && !signal?.aborted; step++) {
2981
2989
  try {
2982
2990
  messages = await compactIfNeeded(messages, signal);
@@ -3469,6 +3477,7 @@ ${instr.trusted}`;
3469
3477
  }
3470
3478
  let messages = [{ role: "system", content: systemContent }];
3471
3479
  const approvedTools = /* @__PURE__ */ new Set();
3480
+ const approvedGuardKeys = /* @__PURE__ */ new Set();
3472
3481
  for (const t of settings.alwaysAllow) approvedTools.add(t);
3473
3482
  for (const t of projectApprovals) approvedTools.add(t);
3474
3483
  let saved = false;
@@ -3641,7 +3650,7 @@ ${instr.trusted}`;
3641
3650
  }) : () => {
3642
3651
  };
3643
3652
  try {
3644
- messages = await runTurn(messages, userInput, ask, approvedTools, activeTurn.signal, steering);
3653
+ messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering);
3645
3654
  } finally {
3646
3655
  restoreKeys();
3647
3656
  setSteeringActive(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beecork",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "beecork — a from-scratch CLI coding agent: multi-model (OpenRouter), BYOK, path-confined tools, built part by part.",
5
5
  "type": "module",
6
6
  "bin": {