@bunny-agent/runner-cli 0.9.59-beta.7 → 0.9.59-beta.8

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/bundle.mjs +118 -11
  2. package/package.json +6 -6
package/dist/bundle.mjs CHANGED
@@ -330,6 +330,16 @@ var createLegacyAgentTurnInput = (userInput) => ({
330
330
  execution: {}
331
331
  });
332
332
 
333
+ // ../../packages/manager/dist/approval-file.js
334
+ import { createHash } from "node:crypto";
335
+ var LEGACY_APPROVAL_FILE_STEM_MAX_BYTES = 200;
336
+ var SAFE_APPROVAL_FILE_STEM = /^[A-Za-z0-9._-]+$/;
337
+ function approvalFileName(toolCallId) {
338
+ const useLegacyName = SAFE_APPROVAL_FILE_STEM.test(toolCallId) && Buffer.byteLength(toolCallId, "utf8") <= LEGACY_APPROVAL_FILE_STEM_MAX_BYTES;
339
+ const stem = useLegacyName ? toolCallId : `hashed-${createHash("sha256").update(toolCallId).digest("hex")}`;
340
+ return `${stem}.json`;
341
+ }
342
+
333
343
  // src/env-payload.ts
334
344
  function takeAgentInputFromEnv(env = process.env) {
335
345
  const raw = env.BUNNY_AGENT_INPUT_JSON;
@@ -14716,6 +14726,9 @@ function errorResult(text) {
14716
14726
  }
14717
14727
 
14718
14728
  // ../../packages/runner-claude/dist/claude-runner.js
14729
+ var DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS = 6e4;
14730
+ var ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY = "__bunny_agent_timeout__";
14731
+ var ASK_USER_QUESTION_TIMEOUT_MESSAGE = "The user did not answer before the interactive question timed out. Continue the original task without waiting, using reasonable assumptions when safe, and provide a user-visible response. Do not call AskUserQuestion again during this turn.";
14719
14732
  function buildUserMessage(userInput) {
14720
14733
  const parsedContent = typeof userInput === "string" ? userInput : buildStructuredClaudeContent(userInput);
14721
14734
  return {
@@ -14749,8 +14762,20 @@ async function* buildSDKUserMessageIterable(userInput) {
14749
14762
  yield buildUserMessage(userInput);
14750
14763
  }
14751
14764
  function createCanUseToolCallback(claudeOptions) {
14765
+ let questionTimedOut = false;
14752
14766
  return async (toolName, input, options) => {
14753
14767
  const { toolUseID } = options;
14768
+ if (toolName === "AskUserQuestion" && questionTimedOut) {
14769
+ return {
14770
+ behavior: "allow",
14771
+ updatedInput: {
14772
+ questions: input?.questions,
14773
+ answers: {
14774
+ [ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY]: ASK_USER_QUESTION_TIMEOUT_MESSAGE
14775
+ }
14776
+ }
14777
+ };
14778
+ }
14754
14779
  if (claudeOptions.yolo && toolName !== "AskUserQuestion") {
14755
14780
  return {
14756
14781
  behavior: "allow",
@@ -14762,7 +14787,7 @@ function createCanUseToolCallback(claudeOptions) {
14762
14787
  const fs3 = await import("node:fs");
14763
14788
  const path3 = await import("node:path");
14764
14789
  const approvalDir = path3.join(cwd, ".bunny-agent", "approvals");
14765
- const approvalFile = path3.join(approvalDir, `${toolUseID}.json`);
14790
+ const approvalFile = path3.join(approvalDir, approvalFileName(toolUseID));
14766
14791
  fs3.mkdirSync(approvalDir, { recursive: true });
14767
14792
  if (!fs3.existsSync(approvalFile)) {
14768
14793
  fs3.writeFileSync(approvalFile, JSON.stringify({
@@ -14773,7 +14798,10 @@ function createCanUseToolCallback(claudeOptions) {
14773
14798
  answers: {}
14774
14799
  }));
14775
14800
  }
14776
- while (true) {
14801
+ const timeoutMs = toolName === "AskUserQuestion" ? claudeOptions.askUserQuestionTimeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS : Number.POSITIVE_INFINITY;
14802
+ const deadline = Date.now() + Math.max(0, timeoutMs);
14803
+ let lastApproval = null;
14804
+ while (Date.now() < deadline) {
14777
14805
  if (options.signal?.aborted) {
14778
14806
  try {
14779
14807
  fs3.unlinkSync(approvalFile);
@@ -14787,6 +14815,7 @@ function createCanUseToolCallback(claudeOptions) {
14787
14815
  try {
14788
14816
  const data = fs3.readFileSync(approvalFile, "utf-8");
14789
14817
  const approval = JSON.parse(data);
14818
+ lastApproval = approval;
14790
14819
  if (approval.status === "completed") {
14791
14820
  try {
14792
14821
  fs3.unlinkSync(approvalFile);
@@ -14804,6 +14833,21 @@ function createCanUseToolCallback(claudeOptions) {
14804
14833
  }
14805
14834
  await new Promise((resolve5) => setTimeout(resolve5, 500));
14806
14835
  }
14836
+ try {
14837
+ fs3.unlinkSync(approvalFile);
14838
+ } catch {
14839
+ }
14840
+ questionTimedOut = true;
14841
+ return {
14842
+ behavior: "allow",
14843
+ updatedInput: {
14844
+ questions: lastApproval?.questions ?? input?.questions,
14845
+ answers: {
14846
+ ...lastApproval?.answers ?? {},
14847
+ [ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY]: ASK_USER_QUESTION_TIMEOUT_MESSAGE
14848
+ }
14849
+ }
14850
+ };
14807
14851
  } catch (error48) {
14808
14852
  console.error("Failed to handle approval flow:", error48);
14809
14853
  return {
@@ -20951,6 +20995,11 @@ function getUsageFromAgentEndMessages(messages) {
20951
20995
  }
20952
20996
 
20953
20997
  // ../../packages/runner-pi/dist/stream-converter.js
20998
+ var ASK_USER_QUESTION_TOOL_NAMES = /* @__PURE__ */ new Set([
20999
+ "AskUserQuestion",
21000
+ "ask_user_question"
21001
+ ]);
21002
+ var ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY2 = "__bunny_agent_timeout__";
20954
21003
  function emitStreamError(errorText) {
20955
21004
  const errorLine = "data: " + JSON.stringify({ type: "error", errorText }) + "\n\n";
20956
21005
  const finishLine = "data: " + JSON.stringify({ type: "finish", finishReason: "error" }) + "\n\n";
@@ -20973,6 +21022,23 @@ function extractToolResultText(result) {
20973
21022
  return String(result);
20974
21023
  }
20975
21024
  }
21025
+ function extractAskUserQuestionOutput(toolName, result) {
21026
+ if (!ASK_USER_QUESTION_TOOL_NAMES.has(toolName))
21027
+ return void 0;
21028
+ if (result === null || typeof result !== "object")
21029
+ return void 0;
21030
+ const details = result.details;
21031
+ if (details === null || typeof details !== "object" || Array.isArray(details)) {
21032
+ return void 0;
21033
+ }
21034
+ const answers = details.answers;
21035
+ if (answers === null || typeof answers !== "object" || Array.isArray(answers)) {
21036
+ return void 0;
21037
+ }
21038
+ return {
21039
+ answers: Object.fromEntries(Object.entries(answers).filter(([key]) => key !== ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY2))
21040
+ };
21041
+ }
20976
21042
  function sseData4(obj) {
20977
21043
  return "data: " + JSON.stringify(obj) + "\n\n";
20978
21044
  }
@@ -21034,7 +21100,7 @@ var PiAISDKStreamConverter = class {
21034
21100
  return chunks;
21035
21101
  }
21036
21102
  if (event.type === "tool_execution_end") {
21037
- const output = this.options.normalizeToolOutput(event.result);
21103
+ const textOutput = this.options.normalizeToolOutput(event.result);
21038
21104
  const raw = event.result?.details?.usage?.raw;
21039
21105
  if (raw != null)
21040
21106
  accumulateToolUsage(this.toolUsageTally, raw);
@@ -21042,11 +21108,12 @@ var PiAISDKStreamConverter = class {
21042
21108
  chunks.push(sseData4({
21043
21109
  type: "tool-output-error",
21044
21110
  toolCallId: event.toolCallId,
21045
- errorText: output,
21111
+ errorText: textOutput,
21046
21112
  dynamic: true,
21047
21113
  providerExecuted: true
21048
21114
  }));
21049
21115
  } else {
21116
+ const output = extractAskUserQuestionOutput(event.toolName, event.result) ?? textOutput;
21050
21117
  chunks.push(sseData4({
21051
21118
  type: "tool-output-available",
21052
21119
  toolCallId: event.toolCallId,
@@ -23755,12 +23822,15 @@ var Type = type_exports2;
23755
23822
  // ../../packages/runner-pi/dist/tool-approval.js
23756
23823
  var ASK_USER_QUESTION_TOOL_NAME = "ask_user_question";
23757
23824
  var LEGACY_ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
23825
+ var DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS2 = 6e4;
23826
+ var ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3 = "__bunny_agent_timeout__";
23827
+ var ASK_USER_QUESTION_TIMEOUT_MESSAGE2 = "The user did not answer before the interactive question timed out. Do not call ask_user_question again during this turn.";
23758
23828
  function isAskUserQuestionToolName(toolName) {
23759
23829
  return toolName === ASK_USER_QUESTION_TOOL_NAME || toolName === LEGACY_ASK_USER_QUESTION_TOOL_NAME;
23760
23830
  }
23761
23831
  var DEFAULT_POLL_INTERVAL_MS = 500;
23762
23832
  function approvalFilePath(cwd, toolCallId) {
23763
- return path2.join(cwd, ".bunny-agent", "approvals", `${toolCallId}.json`);
23833
+ return path2.join(cwd, ".bunny-agent", "approvals", approvalFileName(toolCallId));
23764
23834
  }
23765
23835
  async function waitForApproval(options) {
23766
23836
  const { cwd, toolCallId, toolName, input, signal, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, timeoutMs } = options;
@@ -23811,12 +23881,15 @@ async function waitForApproval(options) {
23811
23881
  await sleepWithAbort(pollIntervalMs, signal);
23812
23882
  }
23813
23883
  cleanup();
23814
- if (lastApproval && Object.keys(lastApproval.answers ?? {}).length > 0) {
23884
+ if (isAskUserQuestionToolName(toolName)) {
23815
23885
  return {
23816
23886
  behavior: "allow",
23817
23887
  updatedInput: {
23818
- questions: lastApproval.questions,
23819
- answers: lastApproval.answers
23888
+ questions: lastApproval?.questions ?? input?.questions,
23889
+ answers: {
23890
+ ...lastApproval?.answers ?? {},
23891
+ [ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3]: ASK_USER_QUESTION_TIMEOUT_MESSAGE2
23892
+ }
23820
23893
  }
23821
23894
  };
23822
23895
  }
@@ -23857,6 +23930,14 @@ var askUserQuestionParameters = Type.Object({
23857
23930
  }), { description: "Questions to present to the user." })
23858
23931
  });
23859
23932
  function formatAskUserQuestionResult(updatedInput) {
23933
+ if (ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3 in updatedInput.answers) {
23934
+ const availableAnswers = Object.fromEntries(Object.entries(updatedInput.answers).filter(([key]) => key !== ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3));
23935
+ return [
23936
+ ASK_USER_QUESTION_TIMEOUT_MESSAGE2,
23937
+ `Available answers: ${JSON.stringify(availableAnswers)}`,
23938
+ "Continue the original task without waiting. Use any available answers, make reasonable assumptions when safe, and provide a user-visible response. Do not call ask_user_question again during this turn."
23939
+ ].join("\n");
23940
+ }
23860
23941
  return [
23861
23942
  "The user answered the interactive questions.",
23862
23943
  `Answers: ${JSON.stringify(updatedInput.answers)}`,
@@ -23864,12 +23945,30 @@ function formatAskUserQuestionResult(updatedInput) {
23864
23945
  ].join("\n");
23865
23946
  }
23866
23947
  function buildAskUserQuestionTool(options) {
23948
+ let questionTimedOut = false;
23867
23949
  return {
23868
23950
  name: ASK_USER_QUESTION_TOOL_NAME,
23869
23951
  label: "Ask User Question",
23870
23952
  description: "Ask the user one or more questions and wait for their answers. Use this when you need clarification, a decision between options, or missing information required to continue the task.",
23871
23953
  parameters: askUserQuestionParameters,
23872
23954
  async execute(toolCallId, params, signal) {
23955
+ if (questionTimedOut) {
23956
+ const updatedInput = {
23957
+ questions: params?.questions,
23958
+ answers: {
23959
+ [ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3]: ASK_USER_QUESTION_TIMEOUT_MESSAGE2
23960
+ }
23961
+ };
23962
+ return {
23963
+ content: [
23964
+ {
23965
+ type: "text",
23966
+ text: formatAskUserQuestionResult(updatedInput)
23967
+ }
23968
+ ],
23969
+ details: updatedInput
23970
+ };
23971
+ }
23873
23972
  const decision = await waitForApproval({
23874
23973
  cwd: options.cwd,
23875
23974
  toolCallId,
@@ -23877,11 +23976,12 @@ function buildAskUserQuestionTool(options) {
23877
23976
  input: params,
23878
23977
  signal: signal ?? options.fallbackSignal,
23879
23978
  pollIntervalMs: options.pollIntervalMs,
23880
- timeoutMs: options.timeoutMs
23979
+ timeoutMs: options.timeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS2
23881
23980
  });
23882
23981
  if (decision.behavior === "deny") {
23883
23982
  throw new Error(decision.message);
23884
23983
  }
23984
+ questionTimedOut = ASK_USER_QUESTION_TIMEOUT_ANSWER_KEY3 in decision.updatedInput.answers;
23885
23985
  return {
23886
23986
  content: [
23887
23987
  {
@@ -24836,7 +24936,13 @@ function createPiRunner(options = {}) {
24836
24936
  resourceLoader,
24837
24937
  thinkingLevel: options.effort ? options.effort : void 0,
24838
24938
  tools: allowedTools,
24839
- customTools: [...gatedTools, buildAskUserQuestionTool(approvalGate)]
24939
+ customTools: [
24940
+ ...gatedTools,
24941
+ buildAskUserQuestionTool({
24942
+ ...approvalGate,
24943
+ timeoutMs: options.askUserQuestionTimeoutMs ?? DEFAULT_ASK_USER_QUESTION_TIMEOUT_MS2
24944
+ })
24945
+ ]
24840
24946
  });
24841
24947
  try {
24842
24948
  await session.bindExtensions({ mode: "print" });
@@ -25028,7 +25134,8 @@ function createRunner(options) {
25028
25134
  resume,
25029
25135
  yolo: options.yolo,
25030
25136
  env,
25031
- abortController
25137
+ abortController,
25138
+ askUserQuestionTimeoutMs: options.askUserQuestionTimeoutMs
25032
25139
  };
25033
25140
  const input = options.input ? parseAgentTurnInputV1(options.input) : createLegacyAgentTurnInput(options.userInput ?? "");
25034
25141
  const rawStream = dispatchRunner(options.runner, base, cwd, options, input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunny-agent/runner-cli",
3
- "version": "0.9.59-beta.7",
3
+ "version": "0.9.59-beta.8",
4
4
  "description": "BunnyAgent Runner CLI - Like gemini-cli or claude-code, runs in your local terminal with AI SDK UI streaming",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,14 +60,14 @@
60
60
  "typescript": "^5.3.0",
61
61
  "vitest": "^1.6.1",
62
62
  "@bunny-agent/apply-patch": "0.1.0",
63
- "@bunny-agent/manager": "0.9.59-beta.7",
63
+ "@bunny-agent/manager": "0.9.59-beta.8",
64
64
  "@bunny-agent/runner-claude": "0.6.2",
65
- "@bunny-agent/runner-codex": "0.6.2",
66
- "@bunny-agent/runner-gemini": "0.6.2",
67
- "@bunny-agent/runner-harness": "0.1.1-beta.0",
68
65
  "@bunny-agent/runner-copilot": "0.1.0",
66
+ "@bunny-agent/runner-codex": "0.6.2",
69
67
  "@bunny-agent/runner-pi": "0.6.4-beta.0",
70
- "@bunny-agent/runner-opencode": "0.6.2"
68
+ "@bunny-agent/runner-opencode": "0.6.2",
69
+ "@bunny-agent/runner-harness": "0.1.1-beta.0",
70
+ "@bunny-agent/runner-gemini": "0.6.2"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "tsc && pnpm bundle",