juno-code 1.0.42 → 1.0.43

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.
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import * as os from 'os';
3
- import os__default, { totalmem, freemem, EOL } from 'os';
4
- import * as path5 from 'path';
5
- import path5__default from 'path';
2
+ import * as os2 from 'os';
3
+ import os2__default, { totalmem, freemem, EOL } from 'os';
4
+ import * as path6 from 'path';
5
+ import path6__default from 'path';
6
6
  import * as process2 from 'process';
7
7
  import * as nodeFs from 'fs';
8
8
  import { promises } from 'fs';
@@ -18,11 +18,11 @@ import { EventEmitter } from 'events';
18
18
  import { execa } from 'execa';
19
19
  import Table from 'cli-table3';
20
20
  import supportsColor from 'supports-color';
21
+ import { spawn } from 'child_process';
21
22
  import { v4 } from 'uuid';
22
23
  import { PerformanceObserver, performance } from 'perf_hooks';
23
24
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
24
25
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
25
- import { spawn } from 'child_process';
26
26
 
27
27
  var __defProp = Object.defineProperty;
28
28
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -44,7 +44,7 @@ var __export = (target, all) => {
44
44
  var version;
45
45
  var init_version = __esm({
46
46
  "src/version.ts"() {
47
- version = "1.0.42";
47
+ version = "1.0.43";
48
48
  }
49
49
  });
50
50
  function isHeadlessEnvironment() {
@@ -175,14 +175,14 @@ function isInDocker() {
175
175
  }
176
176
  }
177
177
  function getPlatform() {
178
- return os.platform();
178
+ return os2.platform();
179
179
  }
180
180
  function getArchitecture() {
181
- return os.arch();
181
+ return os2.arch();
182
182
  }
183
183
  function getShell() {
184
184
  const shell = process2.env.SHELL || process2.env.ComSpec || "";
185
- const shellName = path5.basename(shell).toLowerCase();
185
+ const shellName = path6.basename(shell).toLowerCase();
186
186
  if (shellName.includes("bash")) return "bash";
187
187
  if (shellName.includes("zsh")) return "zsh";
188
188
  if (shellName.includes("fish")) return "fish";
@@ -191,21 +191,21 @@ function getShell() {
191
191
  return "unknown";
192
192
  }
193
193
  function getHomeDirectory() {
194
- return os.homedir();
194
+ return os2.homedir();
195
195
  }
196
196
  function getTempDirectory() {
197
- return os.tmpdir();
197
+ return os2.tmpdir();
198
198
  }
199
199
  function getConfigDirectory(appName = "juno-task") {
200
200
  const platform2 = getPlatform();
201
201
  const home = getHomeDirectory();
202
202
  switch (platform2) {
203
203
  case "win32":
204
- return path5.join(process2.env.APPDATA || path5.join(home, "AppData", "Roaming"), appName);
204
+ return path6.join(process2.env.APPDATA || path6.join(home, "AppData", "Roaming"), appName);
205
205
  case "darwin":
206
- return path5.join(home, "Library", "Application Support", appName);
206
+ return path6.join(home, "Library", "Application Support", appName);
207
207
  default:
208
- return path5.join(process2.env.XDG_CONFIG_HOME || path5.join(home, ".config"), appName);
208
+ return path6.join(process2.env.XDG_CONFIG_HOME || path6.join(home, ".config"), appName);
209
209
  }
210
210
  }
211
211
  function getDataDirectory(appName = "juno-task") {
@@ -213,11 +213,11 @@ function getDataDirectory(appName = "juno-task") {
213
213
  const home = getHomeDirectory();
214
214
  switch (platform2) {
215
215
  case "win32":
216
- return path5.join(process2.env.LOCALAPPDATA || path5.join(home, "AppData", "Local"), appName);
216
+ return path6.join(process2.env.LOCALAPPDATA || path6.join(home, "AppData", "Local"), appName);
217
217
  case "darwin":
218
- return path5.join(home, "Library", "Application Support", appName);
218
+ return path6.join(home, "Library", "Application Support", appName);
219
219
  default:
220
- return path5.join(process2.env.XDG_DATA_HOME || path5.join(home, ".local", "share"), appName);
220
+ return path6.join(process2.env.XDG_DATA_HOME || path6.join(home, ".local", "share"), appName);
221
221
  }
222
222
  }
223
223
  function getCacheDirectory(appName = "juno-task") {
@@ -225,11 +225,11 @@ function getCacheDirectory(appName = "juno-task") {
225
225
  const home = getHomeDirectory();
226
226
  switch (platform2) {
227
227
  case "win32":
228
- return path5.join(process2.env.TEMP || path5.join(home, "AppData", "Local", "Temp"), appName);
228
+ return path6.join(process2.env.TEMP || path6.join(home, "AppData", "Local", "Temp"), appName);
229
229
  case "darwin":
230
- return path5.join(home, "Library", "Caches", appName);
230
+ return path6.join(home, "Library", "Caches", appName);
231
231
  default:
232
- return path5.join(process2.env.XDG_CACHE_HOME || path5.join(home, ".cache"), appName);
232
+ return path6.join(process2.env.XDG_CACHE_HOME || path6.join(home, ".cache"), appName);
233
233
  }
234
234
  }
235
235
  async function createDirectoryIfNotExists(dirPath) {
@@ -279,26 +279,26 @@ async function findMCPServerPath(serverName = "mcp-server") {
279
279
  const platform2 = getPlatform();
280
280
  const searchPaths = [];
281
281
  const pathEnv = process2.env.PATH || "";
282
- searchPaths.push(...pathEnv.split(path5.delimiter));
282
+ searchPaths.push(...pathEnv.split(path6.delimiter));
283
283
  if (platform2 === "win32") {
284
284
  searchPaths.push(
285
285
  "C:\\Program Files\\MCP\\bin",
286
286
  "C:\\Program Files (x86)\\MCP\\bin",
287
- path5.join(process2.env.LOCALAPPDATA || "", "Programs", "MCP", "bin")
287
+ path6.join(process2.env.LOCALAPPDATA || "", "Programs", "MCP", "bin")
288
288
  );
289
289
  } else {
290
290
  searchPaths.push(
291
291
  "/usr/local/bin",
292
292
  "/usr/bin",
293
293
  "/opt/mcp/bin",
294
- path5.join(getHomeDirectory(), ".local", "bin"),
295
- path5.join(getHomeDirectory(), "bin")
294
+ path6.join(getHomeDirectory(), ".local", "bin"),
295
+ path6.join(getHomeDirectory(), "bin")
296
296
  );
297
297
  }
298
298
  const executable = platform2 === "win32" ? `${serverName}.exe` : serverName;
299
299
  for (const searchPath of searchPaths) {
300
300
  if (!searchPath) continue;
301
- const fullPath = path5.join(searchPath, executable);
301
+ const fullPath = path6.join(searchPath, executable);
302
302
  try {
303
303
  await promises.access(fullPath, promises.constants.F_OK | promises.constants.X_OK);
304
304
  return fullPath;
@@ -4020,6 +4020,13 @@ var DEFAULT_HOOKS = {
4020
4020
  // Use for: final cleanup, notifications, reports, post-run actions
4021
4021
  END_RUN: {
4022
4022
  commands: []
4023
+ },
4024
+ // Executes when stale iteration is detected in run_until_completion.sh
4025
+ // Use for: alerts, notifications, logging when agent is not making progress
4026
+ ON_STALE: {
4027
+ commands: [
4028
+ `./.juno_task/scripts/kanban.sh create "Warning: You haven't done anything on the kanban in the past run. You need to process a task, or if you find it unsuitable or unresolvable, you need to archive the task" --status todo`
4029
+ ]
4023
4030
  }
4024
4031
  };
4025
4032
  function getDefaultHooks() {
@@ -4073,9 +4080,9 @@ var ProfileManager = class {
4073
4080
  profileCache = /* @__PURE__ */ new Map();
4074
4081
  activeProfile = null;
4075
4082
  constructor(configDir) {
4076
- this.profilesDir = path5.join(configDir, "profiles");
4077
- this.activeProfileFile = path5.join(configDir, "active-profile.txt");
4078
- this.defaultConfigFile = path5.join(configDir, "config.json");
4083
+ this.profilesDir = path6.join(configDir, "profiles");
4084
+ this.activeProfileFile = path6.join(configDir, "active-profile.txt");
4085
+ this.defaultConfigFile = path6.join(configDir, "config.json");
4079
4086
  }
4080
4087
  /**
4081
4088
  * Initialize the profile system
@@ -4182,7 +4189,7 @@ var ProfileManager = class {
4182
4189
  profile,
4183
4190
  version: "1.0.0"
4184
4191
  };
4185
- const profilePath = path5.join(this.profilesDir, `${profile.name}.json`);
4192
+ const profilePath = path6.join(this.profilesDir, `${profile.name}.json`);
4186
4193
  await promises.writeFile(profilePath, JSON.stringify(storage, null, 2), "utf-8");
4187
4194
  this.profileCache.delete(profile.name);
4188
4195
  }
@@ -4335,7 +4342,7 @@ var ProfileManager = class {
4335
4342
  async findProfileFile(name) {
4336
4343
  const extensions = [".json", ".yaml", ".yml"];
4337
4344
  for (const ext of extensions) {
4338
- const filePath = path5.join(this.profilesDir, `${name}${ext}`);
4345
+ const filePath = path6.join(this.profilesDir, `${name}${ext}`);
4339
4346
  try {
4340
4347
  await promises.access(filePath);
4341
4348
  return filePath;
@@ -4404,7 +4411,7 @@ var ProfileManager = class {
4404
4411
  headlessMode: false,
4405
4412
  // Paths
4406
4413
  workingDirectory: process.cwd(),
4407
- sessionDirectory: path5.join(process.cwd(), ".juno_task", "sessions")
4414
+ sessionDirectory: path6.join(process.cwd(), ".juno_task", "sessions")
4408
4415
  };
4409
4416
  }
4410
4417
  };
@@ -4456,6 +4463,8 @@ var ENV_VAR_MAPPING = {
4456
4463
  JUNO_CODE_MCP_SERVER_NAME: "mcpServerName",
4457
4464
  // Hook settings
4458
4465
  JUNO_CODE_HOOK_COMMAND_TIMEOUT: "hookCommandTimeout",
4466
+ // Quota/hourly limit settings
4467
+ JUNO_CODE_ON_HOURLY_LIMIT: "onHourlyLimit",
4459
4468
  // TUI settings
4460
4469
  JUNO_CODE_INTERACTIVE: "interactive",
4461
4470
  JUNO_CODE_HEADLESS_MODE: "headlessMode",
@@ -4481,6 +4490,8 @@ var LEGACY_ENV_VAR_MAPPING = {
4481
4490
  JUNO_TASK_MCP_SERVER_NAME: "mcpServerName",
4482
4491
  // Hook settings
4483
4492
  JUNO_TASK_HOOK_COMMAND_TIMEOUT: "hookCommandTimeout",
4493
+ // Quota/hourly limit settings
4494
+ JUNO_TASK_ON_HOURLY_LIMIT: "onHourlyLimit",
4484
4495
  // TUI settings
4485
4496
  JUNO_TASK_INTERACTIVE: "interactive",
4486
4497
  JUNO_TASK_HEADLESS_MODE: "headlessMode",
@@ -4491,7 +4502,8 @@ var LEGACY_ENV_VAR_MAPPING = {
4491
4502
  var SubagentTypeSchema = z.enum(["claude", "cursor", "codex", "gemini"]);
4492
4503
  var BackendTypeSchema = z.enum(["mcp", "shell"]);
4493
4504
  var LogLevelSchema = z.enum(["error", "warn", "info", "debug", "trace"]);
4494
- var HookTypeSchema = z.enum(["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN"]);
4505
+ var OnHourlyLimitSchema = z.enum(["wait", "raise"]);
4506
+ var HookTypeSchema = z.enum(["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN", "ON_STALE"]);
4495
4507
  var HookSchema = z.object({
4496
4508
  commands: z.array(z.string()).describe("List of bash commands to execute for this hook")
4497
4509
  });
@@ -4514,6 +4526,8 @@ var JunoTaskConfigSchema = z.object({
4514
4526
  mcpServerName: z.string().optional().describe('Named MCP server to connect to (e.g., "roundtable-ai")'),
4515
4527
  // Hook settings
4516
4528
  hookCommandTimeout: z.number().int().min(1e3).max(36e5).optional().describe("Timeout for individual hook commands in milliseconds (default: 300000 = 5 minutes)"),
4529
+ // Quota/hourly limit settings
4530
+ onHourlyLimit: OnHourlyLimitSchema.describe('Behavior when Claude hourly quota limit is reached: "wait" to sleep until reset, "raise" to exit immediately'),
4517
4531
  // TUI settings
4518
4532
  interactive: z.boolean().describe("Enable interactive mode"),
4519
4533
  headlessMode: z.boolean().describe("Enable headless mode (no TUI)"),
@@ -4532,18 +4546,21 @@ var DEFAULT_CONFIG = {
4532
4546
  logLevel: "info",
4533
4547
  verbose: false,
4534
4548
  quiet: false,
4535
- // MCP settings
4536
- mcpTimeout: 864e5,
4537
- // 86400 seconds (24 hours / 1 day) - maximum safe timeout for long-running operations
4549
+ // MCP settings (also used by shell backend)
4550
+ mcpTimeout: 432e5,
4551
+ // 43200 seconds (12 hours) - default for long-running shell backend operations
4538
4552
  mcpRetries: 3,
4539
4553
  mcpServerName: "roundtable-ai",
4540
4554
  // Default to roundtable-ai server
4555
+ // Quota/hourly limit settings
4556
+ onHourlyLimit: "raise",
4557
+ // Default to exit immediately when hourly limit is reached
4541
4558
  // TUI settings
4542
4559
  interactive: true,
4543
4560
  headlessMode: false,
4544
4561
  // Paths
4545
4562
  workingDirectory: process.cwd(),
4546
- sessionDirectory: path5.join(process.cwd(), ".juno_task"),
4563
+ sessionDirectory: path6.join(process.cwd(), ".juno_task"),
4547
4564
  // Hooks configuration - populated with default hooks template
4548
4565
  hooks: getDefaultHooks()
4549
4566
  };
@@ -4557,10 +4574,10 @@ var GLOBAL_CONFIG_FILE_NAMES = [
4557
4574
  ];
4558
4575
  var PROJECT_CONFIG_FILE = ".juno_task/config.json";
4559
4576
  function resolvePath(inputPath, basePath = process.cwd()) {
4560
- if (path5.isAbsolute(inputPath)) {
4577
+ if (path6.isAbsolute(inputPath)) {
4561
4578
  return inputPath;
4562
4579
  }
4563
- return path5.resolve(basePath, inputPath);
4580
+ return path6.resolve(basePath, inputPath);
4564
4581
  }
4565
4582
  function parseEnvValue(value) {
4566
4583
  if (value === "") return value;
@@ -4615,7 +4632,7 @@ async function loadPackageJsonConfig(filePath) {
4615
4632
  }
4616
4633
  }
4617
4634
  function getConfigFileFormat(filePath) {
4618
- const ext = path5.extname(filePath).toLowerCase();
4635
+ const ext = path6.extname(filePath).toLowerCase();
4619
4636
  switch (ext) {
4620
4637
  case ".json":
4621
4638
  return "json";
@@ -4641,7 +4658,7 @@ async function loadConfigFromFile(filePath) {
4641
4658
  }
4642
4659
  switch (format) {
4643
4660
  case "json":
4644
- if (path5.basename(filePath) === "package.json") {
4661
+ if (path6.basename(filePath) === "package.json") {
4645
4662
  return loadPackageJsonConfig(resolvedPath);
4646
4663
  }
4647
4664
  return loadJsonConfig(resolvedPath);
@@ -4656,7 +4673,7 @@ async function loadConfigFromFile(filePath) {
4656
4673
  }
4657
4674
  }
4658
4675
  async function findProjectConfigFile(searchDir = process.cwd()) {
4659
- const filePath = path5.join(searchDir, PROJECT_CONFIG_FILE);
4676
+ const filePath = path6.join(searchDir, PROJECT_CONFIG_FILE);
4660
4677
  try {
4661
4678
  await promises.access(filePath, nodeFs.constants.R_OK);
4662
4679
  return filePath;
@@ -4666,7 +4683,7 @@ async function findProjectConfigFile(searchDir = process.cwd()) {
4666
4683
  }
4667
4684
  async function findGlobalConfigFile(searchDir = process.cwd()) {
4668
4685
  for (const fileName of GLOBAL_CONFIG_FILE_NAMES) {
4669
- const filePath = path5.join(searchDir, fileName);
4686
+ const filePath = path6.join(searchDir, fileName);
4670
4687
  try {
4671
4688
  await promises.access(filePath, nodeFs.constants.R_OK);
4672
4689
  return filePath;
@@ -4840,8 +4857,8 @@ function validateConfig(config) {
4840
4857
  }
4841
4858
  async function ensureHooksConfig(baseDir) {
4842
4859
  try {
4843
- const configDir = path5.join(baseDir, ".juno_task");
4844
- const configPath = path5.join(configDir, "config.json");
4860
+ const configDir = path6.join(baseDir, ".juno_task");
4861
+ const configPath = path6.join(configDir, "config.json");
4845
4862
  await fs.ensureDir(configDir);
4846
4863
  const configExists = await fs.pathExists(configPath);
4847
4864
  const allHookTypes = getDefaultHooks();
@@ -6776,7 +6793,7 @@ async function executeHooks(hookTypes, hooks, context = {}, options = {}) {
6776
6793
  function validateHooksConfig(hooks) {
6777
6794
  const issues = [];
6778
6795
  const warnings = [];
6779
- const validHookTypes = ["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN"];
6796
+ const validHookTypes = ["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN", "ON_STALE"];
6780
6797
  for (const [hookType, hook] of Object.entries(hooks)) {
6781
6798
  if (!validHookTypes.includes(hookType)) {
6782
6799
  warnings.push(`Unknown hook type: ${hookType}. Valid types are: ${validHookTypes.join(", ")}`);
@@ -6820,6 +6837,20 @@ function validateHooksConfig(hooks) {
6820
6837
  };
6821
6838
  }
6822
6839
 
6840
+ // src/core/backends/shell-backend.ts
6841
+ init_version();
6842
+ function formatDuration(ms) {
6843
+ const totalSeconds = Math.ceil(ms / 1e3);
6844
+ const hours = Math.floor(totalSeconds / 3600);
6845
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
6846
+ const seconds = totalSeconds % 60;
6847
+ const parts = [];
6848
+ if (hours > 0) parts.push(`${hours}h`);
6849
+ if (minutes > 0) parts.push(`${minutes}m`);
6850
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
6851
+ return parts.join(" ");
6852
+ }
6853
+
6823
6854
  // src/core/engine.ts
6824
6855
  var ExecutionStatus = /* @__PURE__ */ ((ExecutionStatus2) => {
6825
6856
  ExecutionStatus2["PENDING"] = "pending";
@@ -7119,6 +7150,8 @@ var ExecutionEngine = class extends EventEmitter {
7119
7150
  totalProgressEvents: 0,
7120
7151
  rateLimitEncounters: 0,
7121
7152
  rateLimitWaitTime: 0,
7153
+ quotaLimitEncounters: 0,
7154
+ quotaLimitWaitTime: 0,
7122
7155
  errorBreakdown: {},
7123
7156
  performanceMetrics: {
7124
7157
  cpuUsage: 0,
@@ -7223,7 +7256,13 @@ var ExecutionEngine = class extends EventEmitter {
7223
7256
  while (!this.shouldStopIterating(context, iterationNumber)) {
7224
7257
  this.checkAbortSignal(context);
7225
7258
  try {
7226
- await this.executeIteration(context, iterationNumber);
7259
+ const quotaLimitInfo = await this.executeIteration(context, iterationNumber);
7260
+ if (quotaLimitInfo?.detected) {
7261
+ const shouldRetry = await this.handleQuotaLimit(context, quotaLimitInfo);
7262
+ if (shouldRetry) {
7263
+ continue;
7264
+ }
7265
+ }
7227
7266
  iterationNumber++;
7228
7267
  } catch (error) {
7229
7268
  if (error instanceof MCPRateLimitError) {
@@ -7241,6 +7280,7 @@ var ExecutionEngine = class extends EventEmitter {
7241
7280
  }
7242
7281
  /**
7243
7282
  * Execute a single iteration
7283
+ * @returns QuotaLimitInfo if a quota limit was detected, null otherwise
7244
7284
  */
7245
7285
  async executeIteration(context, iterationNumber) {
7246
7286
  const iterationStart = /* @__PURE__ */ new Date();
@@ -7334,6 +7374,8 @@ var ExecutionEngine = class extends EventEmitter {
7334
7374
  } catch (error) {
7335
7375
  engineLogger.warn("Hook END_ITERATION failed", { error, iterationNumber });
7336
7376
  }
7377
+ const quotaLimitInfo = this.extractQuotaLimitInfo(toolResult);
7378
+ return quotaLimitInfo;
7337
7379
  } catch (error) {
7338
7380
  const iterationEnd = /* @__PURE__ */ new Date();
7339
7381
  const duration = iterationEnd.getTime() - iterationStart.getTime();
@@ -7427,6 +7469,100 @@ var ExecutionEngine = class extends EventEmitter {
7427
7469
  }
7428
7470
  return 6e4;
7429
7471
  }
7472
+ /**
7473
+ * Handle Claude quota limit with automatic sleep and retry
7474
+ * @returns true if we should retry the iteration, false otherwise
7475
+ */
7476
+ async handleQuotaLimit(context, quotaInfo) {
7477
+ if (!quotaInfo.detected || !quotaInfo.sleepDurationMs) {
7478
+ return false;
7479
+ }
7480
+ context.statistics.quotaLimitEncounters++;
7481
+ const onHourlyLimit = this.engineConfig.config.onHourlyLimit || "raise";
7482
+ if (onHourlyLimit === "raise") {
7483
+ const resetTimeStr2 = quotaInfo.resetTime ? quotaInfo.resetTime.toLocaleTimeString("en-US", {
7484
+ hour: "numeric",
7485
+ minute: "2-digit",
7486
+ hour12: true,
7487
+ timeZoneName: "short"
7488
+ }) : "unknown";
7489
+ engineLogger.info(`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
7490
+ engineLogger.info(`\u2551 Claude Quota Limit Reached \u2551`);
7491
+ engineLogger.info(`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`);
7492
+ engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr2.padEnd(44)}\u2551`);
7493
+ engineLogger.info(`\u2551 Behavior: raise (exit immediately) \u2551`);
7494
+ engineLogger.info(`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`);
7495
+ engineLogger.info(`\u2551 To auto-wait instead, use: --on-hourly-limit wait \u2551`);
7496
+ engineLogger.info(`\u2551 Or set: JUNO_CODE_ON_HOURLY_LIMIT=wait \u2551`);
7497
+ engineLogger.info(`\u2551 Or in config.json: { "onHourlyLimit": "wait" } \u2551`);
7498
+ engineLogger.info(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`);
7499
+ this.emit("quota-limit:raise", { context, quotaInfo });
7500
+ throw new Error(`Claude quota limit reached. Quota resets at ${resetTimeStr2}. Use --on-hourly-limit wait to auto-retry.`);
7501
+ }
7502
+ const waitTimeMs = quotaInfo.sleepDurationMs;
7503
+ const maxWaitTimeMs = 12 * 60 * 60 * 1e3;
7504
+ if (waitTimeMs > maxWaitTimeMs) {
7505
+ engineLogger.warn(`Quota limit wait time (${formatDuration(waitTimeMs)}) exceeds maximum allowed (12 hours). Will not auto-retry.`);
7506
+ return false;
7507
+ }
7508
+ context.statistics.quotaLimitWaitTime += waitTimeMs;
7509
+ const resetTimeStr = quotaInfo.resetTime ? quotaInfo.resetTime.toLocaleTimeString("en-US", {
7510
+ hour: "numeric",
7511
+ minute: "2-digit",
7512
+ hour12: true,
7513
+ timeZoneName: "short"
7514
+ }) : "unknown";
7515
+ const durationStr = formatDuration(waitTimeMs);
7516
+ engineLogger.info(`\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
7517
+ engineLogger.info(`\u2551 Claude Quota Limit Reached \u2551`);
7518
+ engineLogger.info(`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`);
7519
+ engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr.padEnd(44)}\u2551`);
7520
+ engineLogger.info(`\u2551 Sleeping for: ${durationStr.padEnd(44)}\u2551`);
7521
+ if (quotaInfo.timezone) {
7522
+ engineLogger.info(`\u2551 Timezone: ${quotaInfo.timezone.padEnd(44)}\u2551`);
7523
+ }
7524
+ engineLogger.info(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`);
7525
+ this.emit("quota-limit:start", { context, quotaInfo, waitTimeMs });
7526
+ await this.sleepWithProgress(waitTimeMs, (remaining) => {
7527
+ const remainingStr = formatDuration(remaining);
7528
+ engineLogger.info(`[Quota Wait] ${remainingStr} remaining until retry...`);
7529
+ });
7530
+ this.emit("quota-limit:end", { context });
7531
+ engineLogger.info(`Quota limit wait complete. Resuming execution...`);
7532
+ return true;
7533
+ }
7534
+ /**
7535
+ * Sleep with periodic progress updates
7536
+ */
7537
+ async sleepWithProgress(totalMs, onProgress) {
7538
+ const updateIntervalMs = 6e4;
7539
+ let remaining = totalMs;
7540
+ while (remaining > 0) {
7541
+ const sleepTime = Math.min(remaining, updateIntervalMs);
7542
+ await this.sleep(sleepTime);
7543
+ remaining -= sleepTime;
7544
+ if (remaining > 0) {
7545
+ onProgress(remaining);
7546
+ }
7547
+ }
7548
+ }
7549
+ /**
7550
+ * Check if tool result indicates a quota limit error
7551
+ */
7552
+ extractQuotaLimitInfo(toolResult) {
7553
+ const metadataQuotaInfo = toolResult.metadata?.quotaLimitInfo;
7554
+ if (metadataQuotaInfo?.detected) {
7555
+ return metadataQuotaInfo;
7556
+ }
7557
+ try {
7558
+ const content = typeof toolResult.content === "string" ? JSON.parse(toolResult.content) : toolResult.content;
7559
+ if (content?.quota_limit?.detected) {
7560
+ return content.quota_limit;
7561
+ }
7562
+ } catch {
7563
+ }
7564
+ return null;
7565
+ }
7430
7566
  /**
7431
7567
  * Handle iteration errors with recovery strategies
7432
7568
  */
@@ -7755,9 +7891,9 @@ var FileSessionStorage = class {
7755
7891
  * @param baseDir - Base directory for session storage
7756
7892
  */
7757
7893
  constructor(baseDir) {
7758
- this.baseDir = path5.resolve(baseDir);
7759
- this.sessionsDir = path5.join(this.baseDir, "sessions");
7760
- this.archiveDir = path5.join(this.baseDir, "archive");
7894
+ this.baseDir = path6.resolve(baseDir);
7895
+ this.sessionsDir = path6.join(this.baseDir, "sessions");
7896
+ this.archiveDir = path6.join(this.baseDir, "archive");
7761
7897
  }
7762
7898
  /**
7763
7899
  * Initialize storage directories
@@ -7772,7 +7908,7 @@ var FileSessionStorage = class {
7772
7908
  * @returns Full path to session file
7773
7909
  */
7774
7910
  getSessionPath(sessionId) {
7775
- return path5.join(this.sessionsDir, `${sessionId}.json`);
7911
+ return path6.join(this.sessionsDir, `${sessionId}.json`);
7776
7912
  }
7777
7913
  /**
7778
7914
  * Save session data to storage
@@ -7838,7 +7974,7 @@ var FileSessionStorage = class {
7838
7974
  const sessions = [];
7839
7975
  for (const file of sessionFiles) {
7840
7976
  try {
7841
- const sessionId = path5.basename(file, ".json");
7977
+ const sessionId = path6.basename(file, ".json");
7842
7978
  const session = await this.loadSession(sessionId);
7843
7979
  if (session) {
7844
7980
  sessions.push(session.info);
@@ -7941,7 +8077,7 @@ var FileSessionStorage = class {
7941
8077
  const session = await this.loadSession(sessionInfo.id);
7942
8078
  if (!session) continue;
7943
8079
  const archiveFileName = `${sessionInfo.id}_${sessionInfo.createdAt.toISOString().split("T")[0]}.json`;
7944
- const archivePath = path5.join(this.archiveDir, archiveFileName);
8080
+ const archivePath = path6.join(this.archiveDir, archiveFileName);
7945
8081
  if (options.includeData) {
7946
8082
  await promises.writeFile(
7947
8083
  archivePath,
@@ -9223,7 +9359,7 @@ var MetricsReporter = class {
9223
9359
  default:
9224
9360
  throw new Error(`Unsupported export format: ${options.format}`);
9225
9361
  }
9226
- await promises.mkdir(path5.dirname(options.outputPath), { recursive: true });
9362
+ await promises.mkdir(path6.dirname(options.outputPath), { recursive: true });
9227
9363
  await promises.writeFile(options.outputPath, exportData, "utf-8");
9228
9364
  }
9229
9365
  /**
@@ -9341,7 +9477,7 @@ var IterationsSchema = z.number().int("Iterations must be an integer").refine(
9341
9477
  (value) => value === -1 || value > 0,
9342
9478
  "Iterations must be a positive integer or -1 for infinite"
9343
9479
  ).transform((value) => value === -1 ? Infinity : value);
9344
- var FilePathSchema = z.string().min(1, "File path cannot be empty").transform((value) => path5.resolve(value)).refine(async (filePath) => {
9480
+ var FilePathSchema = z.string().min(1, "File path cannot be empty").transform((value) => path6.resolve(value)).refine(async (filePath) => {
9345
9481
  try {
9346
9482
  const stats = await promises.stat(filePath);
9347
9483
  return stats.isFile();
@@ -9349,7 +9485,7 @@ var FilePathSchema = z.string().min(1, "File path cannot be empty").transform((v
9349
9485
  return false;
9350
9486
  }
9351
9487
  }, "File does not exist or is not accessible");
9352
- var DirectoryPathSchema = z.string().min(1, "Directory path cannot be empty").transform((value) => path5.resolve(value)).refine(async (dirPath) => {
9488
+ var DirectoryPathSchema = z.string().min(1, "Directory path cannot be empty").transform((value) => path6.resolve(value)).refine(async (dirPath) => {
9353
9489
  try {
9354
9490
  const stats = await promises.stat(dirPath);
9355
9491
  return stats.isDirectory();
@@ -9505,7 +9641,7 @@ function isValidLogLevel(value) {
9505
9641
  }
9506
9642
  async function isValidPath(filePath, type = "file") {
9507
9643
  try {
9508
- const resolvedPath = path5.resolve(filePath);
9644
+ const resolvedPath = path6.resolve(filePath);
9509
9645
  const stats = await promises.stat(resolvedPath);
9510
9646
  return type === "file" ? stats.isFile() : stats.isDirectory();
9511
9647
  } catch {
@@ -9526,7 +9662,7 @@ function sanitizeFilePath(filePath) {
9526
9662
  if (!cleaned) {
9527
9663
  throw new ValidationError("File path cannot be empty after sanitization", "filePath", filePath);
9528
9664
  }
9529
- return path5.resolve(cleaned);
9665
+ return path6.resolve(cleaned);
9530
9666
  }
9531
9667
  function sanitizeGitUrl(url) {
9532
9668
  if (typeof url !== "string") {
@@ -9657,8 +9793,8 @@ function validateCommandOptions(options) {
9657
9793
  } catch (error) {
9658
9794
  if (error instanceof z.ZodError) {
9659
9795
  const errors = error.errors.map((err) => {
9660
- const path10 = err.path.length > 0 ? err.path.join(".") : "option";
9661
- return `--${path10}: ${err.message}`;
9796
+ const path11 = err.path.length > 0 ? err.path.join(".") : "option";
9797
+ return `--${path11}: ${err.message}`;
9662
9798
  });
9663
9799
  const suggestions = [
9664
9800
  "Check command line option syntax",
@@ -10959,7 +11095,7 @@ var MCPConfigLoader = class {
10959
11095
  const configContent = await fs.readFile(configPath, "utf-8");
10960
11096
  const config = JSON.parse(configContent);
10961
11097
  this.validateConfig(config);
10962
- const resolvedConfig = this.resolveConfigPaths(config, path5.dirname(configPath));
11098
+ const resolvedConfig = this.resolveConfigPaths(config, path6.dirname(configPath));
10963
11099
  this.configCache.set(configPath, resolvedConfig);
10964
11100
  this.lastLoadTime.set(configPath, Date.now());
10965
11101
  return resolvedConfig;
@@ -10974,14 +11110,14 @@ var MCPConfigLoader = class {
10974
11110
  * Find the mcp.json configuration file
10975
11111
  */
10976
11112
  static async findConfigFile(startDir) {
10977
- let currentDir = path5.resolve(startDir);
10978
- const rootDir = path5.parse(currentDir).root;
11113
+ let currentDir = path6.resolve(startDir);
11114
+ const rootDir = path6.parse(currentDir).root;
10979
11115
  while (currentDir !== rootDir) {
10980
- const configPath = path5.join(currentDir, ".juno_task", "mcp.json");
11116
+ const configPath = path6.join(currentDir, ".juno_task", "mcp.json");
10981
11117
  if (await fs.pathExists(configPath)) {
10982
11118
  return configPath;
10983
11119
  }
10984
- currentDir = path5.dirname(currentDir);
11120
+ currentDir = path6.dirname(currentDir);
10985
11121
  }
10986
11122
  throw new MCPConnectionError(
10987
11123
  `MCP configuration not found. Please run 'juno-task init' to create .juno_task/mcp.json`
@@ -11047,16 +11183,16 @@ var MCPConfigLoader = class {
11047
11183
  const resolvedConfig = JSON.parse(JSON.stringify(config));
11048
11184
  for (const [serverName, serverConfig] of Object.entries(resolvedConfig.mcpServers)) {
11049
11185
  serverConfig.args = serverConfig.args.map((arg) => {
11050
- if (arg.includes("/") && !path5.isAbsolute(arg)) {
11051
- return path5.resolve(configDir, "..", arg);
11186
+ if (arg.includes("/") && !path6.isAbsolute(arg)) {
11187
+ return path6.resolve(configDir, "..", arg);
11052
11188
  }
11053
11189
  return arg;
11054
11190
  });
11055
11191
  if (serverConfig.env) {
11056
11192
  for (const [envKey, envValue] of Object.entries(serverConfig.env)) {
11057
11193
  const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(envValue);
11058
- if (!isUrl && envValue.includes("/") && !path5.isAbsolute(envValue)) {
11059
- serverConfig.env[envKey] = path5.resolve(configDir, "..", envValue);
11194
+ if (!isUrl && envValue.includes("/") && !path6.isAbsolute(envValue)) {
11195
+ serverConfig.env[envKey] = path6.resolve(configDir, "..", envValue);
11060
11196
  }
11061
11197
  }
11062
11198
  }
@@ -11123,7 +11259,7 @@ var JunoLogger = class {
11123
11259
  enableConsoleLogging;
11124
11260
  logFilePath;
11125
11261
  constructor(options = {}) {
11126
- this.logDirectory = options.logDirectory || path5__default.join(process.cwd(), ".juno_task", "logs");
11262
+ this.logDirectory = options.logDirectory || path6__default.join(process.cwd(), ".juno_task", "logs");
11127
11263
  this.logLevel = options.logLevel || 1 /* INFO */;
11128
11264
  this.enableConsoleLogging = options.enableConsoleLogging ?? true;
11129
11265
  }
@@ -11138,7 +11274,7 @@ var JunoLogger = class {
11138
11274
  const timeStr = timestamp[1].split("-")[0].substring(0, 6);
11139
11275
  const fullTimestamp = `${dateStr}_${timeStr}`;
11140
11276
  const logFileName = `subagent_loop_${subagent}_${fullTimestamp}.log`;
11141
- this.logFilePath = path5__default.join(this.logDirectory, logFileName);
11277
+ this.logFilePath = path6__default.join(this.logDirectory, logFileName);
11142
11278
  const header = `# Juno-Task TypeScript Log - Started ${(/* @__PURE__ */ new Date()).toISOString()}
11143
11279
  `;
11144
11280
  await fs.writeFile(this.logFilePath, header);
@@ -12008,8 +12144,8 @@ var JunoMCPClient = class {
12008
12144
  `${serverName}`,
12009
12145
  // Assume it's in PATH
12010
12146
  `/usr/local/bin/${serverName}`,
12011
- path5__default.resolve(os__default.homedir(), `.local/bin/${serverName}`),
12012
- path5__default.resolve(this.options.workingDirectory || process.cwd(), `${serverName}`)
12147
+ path6__default.resolve(os2__default.homedir(), `.local/bin/${serverName}`),
12148
+ path6__default.resolve(this.options.workingDirectory || process.cwd(), `${serverName}`)
12013
12149
  ];
12014
12150
  for (const serverPath of possiblePaths) {
12015
12151
  try {
@@ -13935,8 +14071,8 @@ var IOError = class extends SystemError {
13935
14071
  };
13936
14072
  var InvalidPathError = class extends SystemError {
13937
14073
  code = "SYSTEM_INVALID_PATH" /* SYSTEM_INVALID_PATH */;
13938
- constructor(path10, reason, options) {
13939
- let message = `Invalid path: ${path10}`;
14074
+ constructor(path11, reason, options) {
14075
+ let message = `Invalid path: ${path11}`;
13940
14076
  if (reason) {
13941
14077
  message += ` (${reason})`;
13942
14078
  }
@@ -13945,7 +14081,7 @@ var InvalidPathError = class extends SystemError {
13945
14081
  context: {
13946
14082
  ...options?.context,
13947
14083
  metadata: {
13948
- filePath: path10,
14084
+ filePath: path11,
13949
14085
  reason,
13950
14086
  ...options?.context?.metadata
13951
14087
  },