@protolabsai/proto 0.48.0 → 0.49.0

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/cli.js +175 -9
  2. package/package.json +2 -2
package/cli.js CHANGED
@@ -19092,7 +19092,7 @@ function parseResponseData(error40) {
19092
19092
  }
19093
19093
  return error40.response?.data;
19094
19094
  }
19095
- var FatalError, FatalAuthenticationError, FatalInputError, FatalSandboxError, FatalConfigError, FatalTurnLimitedError, FatalToolExecutionError, FatalCancellationError, ForbiddenError, UnauthorizedError, BadRequestError;
19095
+ var FatalError, FatalAuthenticationError, FatalInputError, FatalSandboxError, FatalConfigError, FatalTurnLimitedError, FatalToolExecutionError, FatalBudgetExceededError, FatalCancellationError, ForbiddenError, UnauthorizedError, BadRequestError;
19096
19096
  var init_errors = __esm({
19097
19097
  "packages/core/dist/src/utils/errors.js"() {
19098
19098
  "use strict";
@@ -19160,6 +19160,14 @@ var init_errors = __esm({
19160
19160
  super(message, 54);
19161
19161
  }
19162
19162
  };
19163
+ FatalBudgetExceededError = class extends FatalError {
19164
+ static {
19165
+ __name(this, "FatalBudgetExceededError");
19166
+ }
19167
+ constructor(message) {
19168
+ super(message, 55);
19169
+ }
19170
+ };
19163
19171
  FatalCancellationError = class extends FatalError {
19164
19172
  static {
19165
19173
  __name(this, "FatalCancellationError");
@@ -168676,7 +168684,7 @@ __export(geminiContentGenerator_exports, {
168676
168684
  createGeminiContentGenerator: () => createGeminiContentGenerator
168677
168685
  });
168678
168686
  function createGeminiContentGenerator(config2, gcConfig) {
168679
- const version2 = "0.48.0";
168687
+ const version2 = "0.49.0";
168680
168688
  const userAgent2 = config2.userAgent || `QwenCode/${version2} (${process.platform}; ${process.arch})`;
168681
168689
  const baseHeaders = {
168682
168690
  "User-Agent": userAgent2
@@ -275236,6 +275244,7 @@ var init_config3 = __esm({
275236
275244
  folderTrust;
275237
275245
  ideMode;
275238
275246
  maxSessionTurns;
275247
+ maxToolCalls;
275239
275248
  sessionTokenLimit;
275240
275249
  listExtensions;
275241
275250
  overrideExtensions;
@@ -275348,6 +275357,7 @@ var init_config3 = __esm({
275348
275357
  this.fileDiscoveryService = params.fileDiscoveryService ?? null;
275349
275358
  this.bugCommand = params.bugCommand;
275350
275359
  this.maxSessionTurns = params.maxSessionTurns ?? -1;
275360
+ this.maxToolCalls = params.maxToolCalls ?? -1;
275351
275361
  this.sessionTokenLimit = params.sessionTokenLimit ?? -1;
275352
275362
  this.experimentalZedIntegration = params.experimentalZedIntegration ?? false;
275353
275363
  this.cronEnabled = params.cronEnabled ?? false;
@@ -275776,6 +275786,9 @@ var init_config3 = __esm({
275776
275786
  getMaxSessionTurns() {
275777
275787
  return this.maxSessionTurns;
275778
275788
  }
275789
+ getMaxToolCalls() {
275790
+ return this.maxToolCalls;
275791
+ }
275779
275792
  getSessionTokenLimit() {
275780
275793
  return this.sessionTokenLimit;
275781
275794
  }
@@ -288111,6 +288124,7 @@ __export(dist_exports, {
288111
288124
  ExtensionUpdateState: () => ExtensionUpdateState,
288112
288125
  FILE_MUTATING_TOOLS: () => FILE_MUTATING_TOOLS,
288113
288126
  FatalAuthenticationError: () => FatalAuthenticationError,
288127
+ FatalBudgetExceededError: () => FatalBudgetExceededError,
288114
288128
  FatalCancellationError: () => FatalCancellationError,
288115
288129
  FatalConfigError: () => FatalConfigError,
288116
288130
  FatalError: () => FatalError,
@@ -405844,6 +405858,15 @@ var SETTINGS_SCHEMA = {
405844
405858
  description: "Maximum number of user/model/tool turns to keep in a session. -1 means unlimited.",
405845
405859
  showInDialog: false
405846
405860
  },
405861
+ maxToolCalls: {
405862
+ type: "number",
405863
+ label: "Max Tool Calls",
405864
+ category: "Model",
405865
+ requiresRestart: false,
405866
+ default: -1,
405867
+ description: "Maximum number of tool calls for a headless run before aborting (exit code 55). -1 means unlimited.",
405868
+ showInDialog: false
405869
+ },
405847
405870
  chatCompression: {
405848
405871
  type: "object",
405849
405872
  label: "Chat Compression",
@@ -408902,6 +408925,17 @@ function handleMaxTurnsExceededError(config2) {
408902
408925
  }
408903
408926
  }
408904
408927
  __name(handleMaxTurnsExceededError, "handleMaxTurnsExceededError");
408928
+ function handleBudgetExceededError(config2, exceeded) {
408929
+ const budgetError = new FatalBudgetExceededError(exceeded.message);
408930
+ if (config2.getOutputFormat() === OutputFormat.JSON) {
408931
+ const formatter = new JsonFormatter();
408932
+ writeStderrLine(formatter.formatError(budgetError, budgetError.exitCode));
408933
+ } else {
408934
+ writeStderrLine(budgetError.message);
408935
+ }
408936
+ process.exit(budgetError.exitCode);
408937
+ }
408938
+ __name(handleBudgetExceededError, "handleBudgetExceededError");
408905
408939
 
408906
408940
  // packages/cli/src/commands/extensions/consent.ts
408907
408941
  init_esbuild_shims();
@@ -415376,6 +415410,7 @@ async function handleQwenAuth(command2, options2) {
415376
415410
  resume: void 0,
415377
415411
  sessionId: void 0,
415378
415412
  maxSessionTurns: void 0,
415413
+ maxToolCalls: void 0,
415379
415414
  coreTools: void 0,
415380
415415
  excludeTools: void 0,
415381
415416
  disabledSlashCommands: void 0,
@@ -416526,7 +416561,7 @@ __name(getPackageJson, "getPackageJson");
416526
416561
  // packages/cli/src/utils/version.ts
416527
416562
  async function getCliVersion() {
416528
416563
  const pkgJson = await getPackageJson();
416529
- return "0.48.0";
416564
+ return "0.49.0";
416530
416565
  }
416531
416566
  __name(getCliVersion, "getCliVersion");
416532
416567
 
@@ -420634,6 +420669,104 @@ function buildWebSearchConfig(argv, settings2, authType) {
420634
420669
  }
420635
420670
  __name(buildWebSearchConfig, "buildWebSearchConfig");
420636
420671
 
420672
+ // packages/cli/src/utils/runBudget.ts
420673
+ init_esbuild_shims();
420674
+ var SECOND = 1e3;
420675
+ var MAX_TIMEOUT_MS = 2147483647;
420676
+ var MAX_WALL_TIME_SECONDS = Math.floor(MAX_TIMEOUT_MS / SECOND);
420677
+ var MAX_TOOL_CALLS = 1e6;
420678
+ function validateMaxToolCalls(value) {
420679
+ if (value === -1) return -1;
420680
+ if (!Number.isFinite(value)) {
420681
+ throw new Error(`maxToolCalls must be a finite number; got ${value}.`);
420682
+ }
420683
+ if (!Number.isInteger(value)) {
420684
+ throw new Error(
420685
+ `maxToolCalls must be an integer (or -1 for unlimited); got ${value}.`
420686
+ );
420687
+ }
420688
+ if (value < 0) {
420689
+ throw new Error(
420690
+ `maxToolCalls must be >= 0 (or -1 for unlimited); got ${value}. Use -1 to disable, not a negative number.`
420691
+ );
420692
+ }
420693
+ if (value > MAX_TOOL_CALLS) {
420694
+ throw new Error(
420695
+ `maxToolCalls ${value} exceeds the supported ceiling (${MAX_TOOL_CALLS}). Likely a typo \u2014 use a smaller value or -1 for unlimited.`
420696
+ );
420697
+ }
420698
+ return value;
420699
+ }
420700
+ __name(validateMaxToolCalls, "validateMaxToolCalls");
420701
+ var RunBudgetEnforcer = class {
420702
+ static {
420703
+ __name(this, "RunBudgetEnforcer");
420704
+ }
420705
+ maxWallTimeSeconds;
420706
+ maxToolCalls;
420707
+ abortController;
420708
+ wallTimer = null;
420709
+ toolCallCount = 0;
420710
+ exceeded = null;
420711
+ constructor(opts, abortController) {
420712
+ this.maxWallTimeSeconds = opts.maxWallTimeSeconds ?? -1;
420713
+ this.maxToolCalls = opts.maxToolCalls ?? -1;
420714
+ this.abortController = abortController;
420715
+ }
420716
+ /**
420717
+ * Starts the wall-clock timer (if configured). Idempotent so callers
420718
+ * don't need to thread "did I already start?" state.
420719
+ */
420720
+ start() {
420721
+ if (this.wallTimer !== null) return;
420722
+ if (this.maxWallTimeSeconds <= 0) return;
420723
+ this.wallTimer = setTimeout(() => {
420724
+ this.markExceeded({
420725
+ kind: "wall-time",
420726
+ limit: this.maxWallTimeSeconds,
420727
+ observed: this.maxWallTimeSeconds,
420728
+ message: `Run aborted: wall-clock budget of ${this.maxWallTimeSeconds}s exceeded (--max-wall-time).`
420729
+ });
420730
+ }, this.maxWallTimeSeconds * SECOND);
420731
+ this.wallTimer.unref?.();
420732
+ }
420733
+ /** Records one tool execution and enforces `maxToolCalls`. */
420734
+ tickToolCall() {
420735
+ this.toolCallCount += 1;
420736
+ if (this.maxToolCalls >= 0 && this.toolCallCount > this.maxToolCalls) {
420737
+ this.markExceeded({
420738
+ kind: "tool-calls",
420739
+ limit: this.maxToolCalls,
420740
+ observed: this.toolCallCount,
420741
+ message: `Run aborted: tool-call budget of ${this.maxToolCalls} exceeded (--max-tool-calls); observed ${this.toolCallCount}.`
420742
+ });
420743
+ }
420744
+ }
420745
+ /**
420746
+ * Returns the budget-exceeded record if one fired, else null. The
420747
+ * non-interactive loop checks this after `abortController.signal`
420748
+ * fires to distinguish "budget abort" from "user SIGINT" so it can
420749
+ * emit a structured-error envelope with the right reason.
420750
+ */
420751
+ getExceeded() {
420752
+ return this.exceeded;
420753
+ }
420754
+ /** Cancels the wall-clock timer. Safe to call multiple times. */
420755
+ stop() {
420756
+ if (this.wallTimer !== null) {
420757
+ clearTimeout(this.wallTimer);
420758
+ this.wallTimer = null;
420759
+ }
420760
+ }
420761
+ markExceeded(record2) {
420762
+ if (this.exceeded !== null) return;
420763
+ if (this.abortController.signal.aborted) return;
420764
+ this.exceeded = record2;
420765
+ this.stop();
420766
+ this.abortController.abort();
420767
+ }
420768
+ };
420769
+
420637
420770
  // packages/cli/src/config/config.ts
420638
420771
  var SESSION_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}(-agent-[a-zA-Z0-9_.-]+)?$/i;
420639
420772
  function isValidSessionId(value) {
@@ -420913,6 +421046,9 @@ async function parseArguments() {
420913
421046
  }).option("max-session-turns", {
420914
421047
  type: "number",
420915
421048
  description: "Maximum number of session turns"
421049
+ }).option("max-tool-calls", {
421050
+ type: "number",
421051
+ description: "Maximum number of tool calls for a headless run before aborting (exit code 55). Defaults to unlimited."
420916
421052
  }).option("core-tools", {
420917
421053
  type: "array",
420918
421054
  string: true,
@@ -421308,6 +421444,9 @@ async function loadCliConfig(settings2, argv, cwd6 = process.cwd(), overrideExte
421308
421444
  outputLanguageFilePath,
421309
421445
  sessionTokenLimit: settings2.model?.sessionTokenLimit ?? -1,
421310
421446
  maxSessionTurns: argv.maxSessionTurns ?? settings2.model?.maxSessionTurns ?? -1,
421447
+ maxToolCalls: validateMaxToolCalls(
421448
+ argv.maxToolCalls ?? settings2.model?.maxToolCalls ?? -1
421449
+ ),
421311
421450
  experimentalZedIntegration: argv.acp || argv.experimentalAcp || false,
421312
421451
  cronEnabled: settings2.experimental?.cron ?? false,
421313
421452
  listExtensions: argv.listExtensions || false,
@@ -421331,7 +421470,13 @@ async function loadCliConfig(settings2, argv, cwd6 = process.cwd(), overrideExte
421331
421470
  useRipgrep: settings2.tools?.useRipgrep,
421332
421471
  useBuiltinRipgrep: settings2.tools?.useBuiltinRipgrep,
421333
421472
  shouldUseNodePtyShell: settings2.tools?.shell?.enableInteractiveShell,
421334
- skipNextSpeakerCheck: settings2.model?.skipNextSpeakerCheck,
421473
+ // Autonomous sessions (SDK / headless / non-TTY) default to RUNNING the
421474
+ // next-speaker check so the loop nudges the model to continue after a turn
421475
+ // that states intent without a tool call, instead of silently ending
421476
+ // (#307). Interactive REPL keeps the check skipped by default for snappy
421477
+ // responses — a human (or an active /goal) drives continuation there.
421478
+ // An explicit `model.skipNextSpeakerCheck` setting overrides either way.
421479
+ skipNextSpeakerCheck: settings2.model?.skipNextSpeakerCheck ?? interactive,
421335
421480
  skipLoopDetection: settings2.model?.skipLoopDetection ?? true,
421336
421481
  skipStartupContext: settings2.model?.skipStartupContext ?? false,
421337
421482
  truncateToolOutputThreshold: settings2.tools?.truncateToolOutputThreshold,
@@ -424534,7 +424679,7 @@ var formatDuration = /* @__PURE__ */ __name((milliseconds) => {
424534
424679
 
424535
424680
  // packages/cli/src/generated/git-commit.ts
424536
424681
  init_esbuild_shims();
424537
- var GIT_COMMIT_INFO = "3dda9bec1";
424682
+ var GIT_COMMIT_INFO = "58b119575";
424538
424683
 
424539
424684
  // packages/cli/src/utils/systemInfo.ts
424540
424685
  async function getNpmVersion() {
@@ -436694,6 +436839,17 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436694
436839
  }, "stdoutErrorHandler");
436695
436840
  const geminiClient = config2.getGeminiClient();
436696
436841
  const abortController = options2.abortController ?? new AbortController();
436842
+ const budgetEnforcer = new RunBudgetEnforcer(
436843
+ { maxToolCalls: config2.getMaxToolCalls() },
436844
+ abortController
436845
+ );
436846
+ const routeAbort = /* @__PURE__ */ __name(() => {
436847
+ const exceeded = budgetEnforcer.getExceeded();
436848
+ if (exceeded) {
436849
+ handleBudgetExceededError(config2, exceeded);
436850
+ }
436851
+ handleCancellationError(config2);
436852
+ }, "routeAbort");
436697
436853
  const authError = await checkAuthPreflight(config2);
436698
436854
  if (authError) {
436699
436855
  if (outputFormat === OutputFormat.JSON || outputFormat === OutputFormat.STREAM_JSON) {
@@ -436803,6 +436959,7 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436803
436959
  }
436804
436960
  const initialParts = normalizePartList(initialPartList);
436805
436961
  let currentMessages = [{ role: "user", parts: initialParts }];
436962
+ budgetEnforcer.start();
436806
436963
  let isFirstTurn = true;
436807
436964
  while (true) {
436808
436965
  turnCount++;
@@ -436823,7 +436980,7 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436823
436980
  adapter.startAssistantMessage();
436824
436981
  for await (const event of responseStream) {
436825
436982
  if (abortController.signal.aborted) {
436826
- handleCancellationError(config2);
436983
+ routeAbort();
436827
436984
  }
436828
436985
  adapter.processEvent(event);
436829
436986
  if (event.type === GeminiEventType.ToolCallRequest) {
@@ -436856,6 +437013,10 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436856
437013
  finalRequestInfo.callId,
436857
437014
  adapter
436858
437015
  ) : createToolProgressHandler(finalRequestInfo, adapter);
437016
+ budgetEnforcer.tickToolCall();
437017
+ if (abortController.signal.aborted) {
437018
+ routeAbort();
437019
+ }
436859
437020
  const toolResponse = await executeToolCall(
436860
437021
  config2,
436861
437022
  finalRequestInfo,
@@ -436944,6 +437105,10 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436944
437105
  requestInfo.callId,
436945
437106
  adapter
436946
437107
  ) : createToolProgressHandler(requestInfo, adapter);
437108
+ budgetEnforcer.tickToolCall();
437109
+ if (abortController.signal.aborted) {
437110
+ routeAbort();
437111
+ }
436947
437112
  const toolResponse = await executeToolCall(
436948
437113
  config2,
436949
437114
  requestInfo,
@@ -437022,6 +437187,7 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
437022
437187
  });
437023
437188
  handleError(error40, config2);
437024
437189
  } finally {
437190
+ budgetEnforcer.stop();
437025
437191
  process.stdout.removeListener("error", stdoutErrorHandler);
437026
437192
  process.removeListener("SIGINT", shutdownHandler);
437027
437193
  process.removeListener("SIGTERM", shutdownHandler);
@@ -461449,7 +461615,7 @@ var getStatusText = /* @__PURE__ */ __name((status) => {
461449
461615
  return "Unknown";
461450
461616
  }
461451
461617
  }, "getStatusText");
461452
- var MAX_TOOL_CALLS = 5;
461618
+ var MAX_TOOL_CALLS2 = 5;
461453
461619
  var MAX_VERBOSE_TOOL_CALLS = 12;
461454
461620
  var MAX_TASK_PROMPT_LINES = 5;
461455
461621
  var DEFAULT_DETAIL_HEIGHT = 18;
@@ -461504,7 +461670,7 @@ var AgentExecutionDisplay = /* @__PURE__ */ __name(({
461504
461670
  Math.floor((renderableBudget - promptBudget) / ROWS_PER_TOOL_CALL)
461505
461671
  );
461506
461672
  const maxTaskPromptLines = displayMode === "verbose" ? Math.min(8, promptBudget) : Math.min(MAX_TASK_PROMPT_LINES, promptBudget);
461507
- const maxToolCalls = displayMode === "verbose" ? Math.min(MAX_VERBOSE_TOOL_CALLS, toolBudget) : Math.min(MAX_TOOL_CALLS, toolBudget);
461673
+ const maxToolCalls = displayMode === "verbose" ? Math.min(MAX_VERBOSE_TOOL_CALLS, toolBudget) : Math.min(MAX_TOOL_CALLS2, toolBudget);
461508
461674
  const agentColor = (0, import_react62.useMemo)(() => {
461509
461675
  const colorOption = COLOR_OPTIONS.find(
461510
461676
  (option2) => option2.name === data.subagentColor
@@ -493281,7 +493447,7 @@ var QwenAgent = class {
493281
493447
  async initialize(args2) {
493282
493448
  this.clientCapabilities = args2.clientCapabilities;
493283
493449
  const authMethods = buildAuthMethods();
493284
- const version2 = "0.48.0";
493450
+ const version2 = "0.49.0";
493285
493451
  return {
493286
493452
  protocolVersion: PROTOCOL_VERSION,
493287
493453
  agentInfo: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protolabsai/proto",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "proto - AI-powered coding agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  "bundled"
22
22
  ],
23
23
  "config": {
24
- "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.48.0"
24
+ "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.49.0"
25
25
  },
26
26
  "dependencies": {},
27
27
  "optionalDependencies": {