@protolabsai/proto 0.49.0 → 0.51.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 +161 -9
  2. package/package.json +2 -2
package/cli.js CHANGED
@@ -168684,7 +168684,7 @@ __export(geminiContentGenerator_exports, {
168684
168684
  createGeminiContentGenerator: () => createGeminiContentGenerator
168685
168685
  });
168686
168686
  function createGeminiContentGenerator(config2, gcConfig) {
168687
- const version2 = "0.49.0";
168687
+ const version2 = "0.51.0";
168688
168688
  const userAgent2 = config2.userAgent || `QwenCode/${version2} (${process.platform}; ${process.arch})`;
168689
168689
  const baseHeaders = {
168690
168690
  "User-Agent": userAgent2
@@ -196940,6 +196940,7 @@ var init_GoalManager = __esm({
196940
196940
  }
196941
196941
  active;
196942
196942
  lastAchieved;
196943
+ lastFailed;
196943
196944
  /**
196944
196945
  * Set a new goal. Replaces any active goal. Returns the created state.
196945
196946
  * Throws if the condition is empty or exceeds the length limit.
@@ -196991,6 +196992,24 @@ var init_GoalManager = __esm({
196991
196992
  debugLogger39.info(`Goal achieved after ${achieved.turnCount} turns: "${truncate2(achieved.condition, 60)}".`);
196992
196993
  return achieved;
196993
196994
  }
196995
+ /**
196996
+ * Mark the active goal as abandoned because the condition is impossible, and
196997
+ * move it to `lastFailed`. Records the evaluator's reason so `/goal` can
196998
+ * report why it stopped. Returns the failed record. No-op if no goal active.
196999
+ */
197000
+ markImpossible(reason) {
197001
+ if (!this.active)
197002
+ return void 0;
197003
+ const failed = {
197004
+ ...this.active,
197005
+ lastReason: reason,
197006
+ failedAt: Date.now()
197007
+ };
197008
+ this.lastFailed = failed;
197009
+ this.active = void 0;
197010
+ debugLogger39.info(`Goal abandoned as impossible after ${failed.turnCount} turns: "${truncate2(failed.condition, 60)}" (${truncate2(reason, 80)}).`);
197011
+ return failed;
197012
+ }
196994
197013
  /** Record that the agent completed a turn while the goal was active. */
196995
197014
  recordTurn() {
196996
197015
  if (this.active) {
@@ -197016,10 +197035,14 @@ var init_GoalManager = __esm({
197016
197035
  getLastAchievedGoal() {
197017
197036
  return this.lastAchieved ? { ...this.lastAchieved } : void 0;
197018
197037
  }
197019
- /** Reset both active and achieved state. Used on session clear/end. */
197038
+ getLastFailedGoal() {
197039
+ return this.lastFailed ? { ...this.lastFailed } : void 0;
197040
+ }
197041
+ /** Reset active, achieved, and failed state. Used on session clear/end. */
197020
197042
  reset() {
197021
197043
  this.active = void 0;
197022
197044
  this.lastAchieved = void 0;
197045
+ this.lastFailed = void 0;
197023
197046
  }
197024
197047
  };
197025
197048
  __name(truncate2, "truncate");
@@ -197091,7 +197114,8 @@ function parseEvaluatorJson(text) {
197091
197114
  const parsed = JSON.parse(jsonObj);
197092
197115
  const met = parsed.met === true;
197093
197116
  const reason = typeof parsed.reason === "string" && parsed.reason.trim().length > 0 ? parsed.reason.trim() : met ? "Condition met." : "No reason provided.";
197094
- return { met, reason };
197117
+ const impossible = !met && parsed.impossible === true;
197118
+ return impossible ? { met, reason, impossible: true } : { met, reason };
197095
197119
  } catch {
197096
197120
  return {
197097
197121
  met: false,
@@ -197159,13 +197183,17 @@ var init_goalEvaluator = __esm({
197159
197183
 
197160
197184
  You only see what the assistant has surfaced in the transcript -- tool calls it ran, their outcomes, and the final assistant message. You do not run commands yourself.
197161
197185
 
197162
- Respond ONLY with valid JSON matching exactly this schema:
197163
- {"met": boolean, "reason": "one short sentence explaining why"}
197186
+ Respond ONLY with valid JSON matching one of these shapes:
197187
+ {"met": true, "reason": "one short sentence quoting the satisfying evidence"}
197188
+ {"met": false, "reason": "one short sentence on what is missing or blocking"}
197189
+ {"met": false, "impossible": true, "reason": "one short sentence on why it can never be satisfied"}
197164
197190
 
197165
197191
  Rules:
197166
197192
  - Set "met": true only when the condition is clearly demonstrated by the visible evidence (a test output, an exit code, a file count, an empty queue, etc.).
197167
197193
  - If the assistant only claims the work is done without showing evidence, set "met": false and ask for the missing evidence in "reason".
197168
197194
  - If the condition cannot be verified from what is shown, set "met": false and explain what would prove it in "reason".
197195
+ - Only set "impossible": true when the condition is genuinely unachievable in this session: it is self-contradictory, depends on an unavailable resource or capability, or the assistant has exhausted reasonable approaches and the transcript confirms there is no path forward. The assistant claiming the goal is impossible is evidence, not proof -- independently confirm it rather than deferring to the assistant's self-assessment. Do NOT set it just because progress is slow or evidence is currently missing. When in doubt, return {"met": false} without "impossible".
197196
+ - "impossible" is only meaningful alongside "met": false. Never set it when "met" is true.
197169
197197
  - Keep "reason" to one short sentence. It will be shown to the assistant as guidance for the next turn.`;
197170
197198
  __name(evaluateGoal, "evaluateGoal");
197171
197199
  __name(buildEvaluatorPrompt, "buildEvaluatorPrompt");
@@ -197801,6 +197829,9 @@ Please address these issues before finishing.`;
197801
197829
  goalManager.recordEvaluation(evalResult);
197802
197830
  if (evalResult.met) {
197803
197831
  goalManager.markAchieved();
197832
+ } else if (evalResult.impossible) {
197833
+ goalManager.markImpossible(evalResult.reason);
197834
+ this.config.getDebugLogger().info(`[goal] abandoned as impossible: ${evalResult.reason.slice(0, 120)}`);
197804
197835
  } else {
197805
197836
  const continueReason = `Goal not yet met. Evaluator: ${evalResult.reason}
197806
197837
 
@@ -275245,6 +275276,7 @@ var init_config3 = __esm({
275245
275276
  ideMode;
275246
275277
  maxSessionTurns;
275247
275278
  maxToolCalls;
275279
+ maxWallTimeSeconds;
275248
275280
  sessionTokenLimit;
275249
275281
  listExtensions;
275250
275282
  overrideExtensions;
@@ -275358,6 +275390,7 @@ var init_config3 = __esm({
275358
275390
  this.bugCommand = params.bugCommand;
275359
275391
  this.maxSessionTurns = params.maxSessionTurns ?? -1;
275360
275392
  this.maxToolCalls = params.maxToolCalls ?? -1;
275393
+ this.maxWallTimeSeconds = params.maxWallTimeSeconds ?? -1;
275361
275394
  this.sessionTokenLimit = params.sessionTokenLimit ?? -1;
275362
275395
  this.experimentalZedIntegration = params.experimentalZedIntegration ?? false;
275363
275396
  this.cronEnabled = params.cronEnabled ?? false;
@@ -275789,6 +275822,9 @@ var init_config3 = __esm({
275789
275822
  getMaxToolCalls() {
275790
275823
  return this.maxToolCalls;
275791
275824
  }
275825
+ getMaxWallTimeSeconds() {
275826
+ return this.maxWallTimeSeconds;
275827
+ }
275792
275828
  getSessionTokenLimit() {
275793
275829
  return this.sessionTokenLimit;
275794
275830
  }
@@ -405867,6 +405903,15 @@ var SETTINGS_SCHEMA = {
405867
405903
  description: "Maximum number of tool calls for a headless run before aborting (exit code 55). -1 means unlimited.",
405868
405904
  showInDialog: false
405869
405905
  },
405906
+ maxWallTimeSeconds: {
405907
+ type: "number",
405908
+ label: "Max Wall Time (seconds)",
405909
+ category: "Model",
405910
+ requiresRestart: false,
405911
+ default: -1,
405912
+ description: "Wall-clock budget in seconds for a headless run before aborting (exit code 55). -1 means unlimited.",
405913
+ showInDialog: false
405914
+ },
405870
405915
  chatCompression: {
405871
405916
  type: "object",
405872
405917
  label: "Chat Compression",
@@ -415411,6 +415456,7 @@ async function handleQwenAuth(command2, options2) {
415411
415456
  sessionId: void 0,
415412
415457
  maxSessionTurns: void 0,
415413
415458
  maxToolCalls: void 0,
415459
+ maxWallTime: void 0,
415414
415460
  coreTools: void 0,
415415
415461
  excludeTools: void 0,
415416
415462
  disabledSlashCommands: void 0,
@@ -416561,7 +416607,7 @@ __name(getPackageJson, "getPackageJson");
416561
416607
  // packages/cli/src/utils/version.ts
416562
416608
  async function getCliVersion() {
416563
416609
  const pkgJson = await getPackageJson();
416564
- return "0.49.0";
416610
+ return "0.51.0";
416565
416611
  }
416566
416612
  __name(getCliVersion, "getCliVersion");
416567
416613
 
@@ -420674,6 +420720,81 @@ init_esbuild_shims();
420674
420720
  var SECOND = 1e3;
420675
420721
  var MAX_TIMEOUT_MS = 2147483647;
420676
420722
  var MAX_WALL_TIME_SECONDS = Math.floor(MAX_TIMEOUT_MS / SECOND);
420723
+ var MIN_WALL_TIME_SECONDS = 1;
420724
+ function parseDurationSeconds(input) {
420725
+ const trimmed2 = input.trim().toLowerCase();
420726
+ if (trimmed2.length === 0) {
420727
+ throw new Error("Invalid duration: empty string");
420728
+ }
420729
+ const match2 = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/.exec(trimmed2);
420730
+ if (!match2) {
420731
+ throw new Error(
420732
+ `Invalid duration "${input}". Use a positive number of seconds (e.g. 90) or a duration with unit (e.g. 30s, 5m, 1h, 500ms).`
420733
+ );
420734
+ }
420735
+ const value = Number.parseFloat(match2[1]);
420736
+ const unit = match2[2] ?? "s";
420737
+ let seconds;
420738
+ switch (unit) {
420739
+ case "ms":
420740
+ seconds = value / 1e3;
420741
+ break;
420742
+ case "s":
420743
+ seconds = value;
420744
+ break;
420745
+ case "m":
420746
+ seconds = value * 60;
420747
+ break;
420748
+ case "h":
420749
+ seconds = value * 3600;
420750
+ break;
420751
+ default:
420752
+ throw new Error(`Invalid duration unit "${unit}"`);
420753
+ }
420754
+ if (seconds <= 0) {
420755
+ throw new Error(
420756
+ `Invalid duration "${input}": must be greater than zero. Omit the flag entirely if you don't want a wall-clock budget.`
420757
+ );
420758
+ }
420759
+ if (seconds < MIN_WALL_TIME_SECONDS) {
420760
+ const hint = /ms\b/i.test(trimmed2) ? ` (probably a typo \u2014 did you mean ${input.replace(/ms\b/i, "s")}?)` : "";
420761
+ throw new Error(
420762
+ `Invalid duration "${input}": below the ${MIN_WALL_TIME_SECONDS}s minimum${hint}. Sub-second wall-clock budgets fire before any model round-trip can complete.`
420763
+ );
420764
+ }
420765
+ if (seconds > MAX_WALL_TIME_SECONDS) {
420766
+ throw new Error(
420767
+ `Invalid duration "${input}": exceeds the maximum supported wall-clock budget (${MAX_WALL_TIME_SECONDS}s \u2248 24 days). Use a smaller value.`
420768
+ );
420769
+ }
420770
+ return seconds;
420771
+ }
420772
+ __name(parseDurationSeconds, "parseDurationSeconds");
420773
+ function validateMaxWallTimeSetting(value) {
420774
+ if (value === -1) return -1;
420775
+ if (!Number.isFinite(value)) {
420776
+ throw new Error(
420777
+ `model.maxWallTimeSeconds must be a finite number; got ${value}.`
420778
+ );
420779
+ }
420780
+ if (value <= 0) {
420781
+ throw new Error(
420782
+ `model.maxWallTimeSeconds must be > 0 (or -1 for unlimited); got ${value}. Use -1 to disable, not 0.`
420783
+ );
420784
+ }
420785
+ if (value < MIN_WALL_TIME_SECONDS) {
420786
+ throw new Error(
420787
+ `model.maxWallTimeSeconds ${value} is below the ${MIN_WALL_TIME_SECONDS}s minimum. Sub-second budgets fire before any model round-trip can complete.`
420788
+ );
420789
+ }
420790
+ if (value > MAX_WALL_TIME_SECONDS) {
420791
+ throw new Error(
420792
+ `model.maxWallTimeSeconds ${value} exceeds the maximum supported wall-clock budget (${MAX_WALL_TIME_SECONDS}s \u2248 24 days).`
420793
+ );
420794
+ }
420795
+ return value;
420796
+ }
420797
+ __name(validateMaxWallTimeSetting, "validateMaxWallTimeSetting");
420677
420798
  var MAX_TOOL_CALLS = 1e6;
420678
420799
  function validateMaxToolCalls(value) {
420679
420800
  if (value === -1) return -1;
@@ -421049,6 +421170,9 @@ async function parseArguments() {
421049
421170
  }).option("max-tool-calls", {
421050
421171
  type: "number",
421051
421172
  description: "Maximum number of tool calls for a headless run before aborting (exit code 55). Defaults to unlimited."
421173
+ }).option("max-wall-time", {
421174
+ type: "string",
421175
+ description: "Wall-clock budget for a headless run before aborting (exit code 55). Accepts seconds or a unit suffix (e.g. 90, 30s, 5m, 1h). Defaults to unlimited."
421052
421176
  }).option("core-tools", {
421053
421177
  type: "array",
421054
421178
  string: true,
@@ -421447,6 +421571,10 @@ async function loadCliConfig(settings2, argv, cwd6 = process.cwd(), overrideExte
421447
421571
  maxToolCalls: validateMaxToolCalls(
421448
421572
  argv.maxToolCalls ?? settings2.model?.maxToolCalls ?? -1
421449
421573
  ),
421574
+ // The CLI flag is a duration string (parsed/validated by
421575
+ // parseDurationSeconds); the settings entry is a plain number of seconds
421576
+ // (validated by validateMaxWallTimeSetting). Flag wins when both are set.
421577
+ maxWallTimeSeconds: argv.maxWallTime !== void 0 ? parseDurationSeconds(argv.maxWallTime) : validateMaxWallTimeSetting(settings2.model?.maxWallTimeSeconds ?? -1),
421450
421578
  experimentalZedIntegration: argv.acp || argv.experimentalAcp || false,
421451
421579
  cronEnabled: settings2.experimental?.cron ?? false,
421452
421580
  listExtensions: argv.listExtensions || false,
@@ -424679,7 +424807,7 @@ var formatDuration = /* @__PURE__ */ __name((milliseconds) => {
424679
424807
 
424680
424808
  // packages/cli/src/generated/git-commit.ts
424681
424809
  init_esbuild_shims();
424682
- var GIT_COMMIT_INFO = "58b119575";
424810
+ var GIT_COMMIT_INFO = "922a31b17";
424683
424811
 
424684
424812
  // packages/cli/src/utils/systemInfo.ts
424685
424813
  async function getNpmVersion() {
@@ -428990,6 +429118,20 @@ function statusText(manager) {
428990
429118
  return lines.join("\n");
428991
429119
  }
428992
429120
  const achieved = manager.getLastAchievedGoal();
429121
+ const failed = manager.getLastFailedGoal();
429122
+ const achievedAt = achieved?.achievedAt ?? 0;
429123
+ const failedAt = failed?.failedAt ?? 0;
429124
+ if (failed && failedAt >= achievedAt && failedAt > 0) {
429125
+ const elapsed = formatElapsed(failedAt - failed.startedAt);
429126
+ const lines = [
429127
+ `Last goal abandoned as impossible after ${elapsed} (${failed.turnCount} turn(s), ${failed.tokensSpent} eval tokens):`,
429128
+ ` ${failed.condition}`
429129
+ ];
429130
+ if (failed.lastReason) {
429131
+ lines.push(`Reason: ${failed.lastReason}`);
429132
+ }
429133
+ return lines.join("\n");
429134
+ }
428993
429135
  if (achieved && achieved.achievedAt) {
428994
429136
  const elapsed = formatElapsed(achieved.achievedAt - achieved.startedAt);
428995
429137
  return [
@@ -436840,7 +436982,10 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436840
436982
  const geminiClient = config2.getGeminiClient();
436841
436983
  const abortController = options2.abortController ?? new AbortController();
436842
436984
  const budgetEnforcer = new RunBudgetEnforcer(
436843
- { maxToolCalls: config2.getMaxToolCalls() },
436985
+ {
436986
+ maxToolCalls: config2.getMaxToolCalls(),
436987
+ maxWallTimeSeconds: config2.getMaxWallTimeSeconds()
436988
+ },
436844
436989
  abortController
436845
436990
  );
436846
436991
  const routeAbort = /* @__PURE__ */ __name(() => {
@@ -436966,6 +437111,9 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
436966
437111
  if (config2.getMaxSessionTurns() >= 0 && turnCount > config2.getMaxSessionTurns()) {
436967
437112
  handleMaxTurnsExceededError(config2);
436968
437113
  }
437114
+ if (abortController.signal.aborted) {
437115
+ routeAbort();
437116
+ }
436969
437117
  const toolCallRequests = [];
436970
437118
  const apiStartTime = Date.now();
436971
437119
  const responseStream = geminiClient.sendMessageStream(
@@ -437081,6 +437229,10 @@ async function runNonInteractive(config2, settings2, input, prompt_id, options2
437081
437229
  adapter.startAssistantMessage();
437082
437230
  for await (const event of cronStream) {
437083
437231
  if (abortController.signal.aborted) {
437232
+ if (budgetEnforcer.getExceeded()) {
437233
+ scheduler.stop();
437234
+ routeAbort();
437235
+ }
437084
437236
  const summary = scheduler.getExitSummary();
437085
437237
  scheduler.stop();
437086
437238
  if (summary) {
@@ -493447,7 +493599,7 @@ var QwenAgent = class {
493447
493599
  async initialize(args2) {
493448
493600
  this.clientCapabilities = args2.clientCapabilities;
493449
493601
  const authMethods = buildAuthMethods();
493450
- const version2 = "0.49.0";
493602
+ const version2 = "0.51.0";
493451
493603
  return {
493452
493604
  protocolVersion: PROTOCOL_VERSION,
493453
493605
  agentInfo: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protolabsai/proto",
3
- "version": "0.49.0",
3
+ "version": "0.51.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.49.0"
24
+ "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.51.0"
25
25
  },
26
26
  "dependencies": {},
27
27
  "optionalDependencies": {