juno-code 1.0.41 → 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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var os = require('os');
5
- var path5 = require('path');
4
+ var os2 = require('os');
5
+ var path6 = require('path');
6
6
  var process2 = require('process');
7
7
  var nodeFs = require('fs');
8
8
  var React4 = require('react');
@@ -17,11 +17,11 @@ var events = require('events');
17
17
  var execa = require('execa');
18
18
  var Table = require('cli-table3');
19
19
  var supportsColor = require('supports-color');
20
+ var child_process = require('child_process');
20
21
  var uuid = require('uuid');
21
22
  var perf_hooks = require('perf_hooks');
22
23
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
23
24
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
24
- var child_process = require('child_process');
25
25
 
26
26
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
27
27
 
@@ -43,8 +43,8 @@ function _interopNamespace(e) {
43
43
  return Object.freeze(n);
44
44
  }
45
45
 
46
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
47
- var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
46
+ var os2__namespace = /*#__PURE__*/_interopNamespace(os2);
47
+ var path6__namespace = /*#__PURE__*/_interopNamespace(path6);
48
48
  var process2__namespace = /*#__PURE__*/_interopNamespace(process2);
49
49
  var nodeFs__namespace = /*#__PURE__*/_interopNamespace(nodeFs);
50
50
  var React4__default = /*#__PURE__*/_interopDefault(React4);
@@ -75,7 +75,7 @@ var __export = (target, all) => {
75
75
  exports.version = void 0;
76
76
  var init_version = __esm({
77
77
  "src/version.ts"() {
78
- exports.version = "1.0.41";
78
+ exports.version = "1.0.43";
79
79
  }
80
80
  });
81
81
  function isHeadlessEnvironment() {
@@ -206,14 +206,14 @@ function isInDocker() {
206
206
  }
207
207
  }
208
208
  function getPlatform() {
209
- return os__namespace.platform();
209
+ return os2__namespace.platform();
210
210
  }
211
211
  function getArchitecture() {
212
- return os__namespace.arch();
212
+ return os2__namespace.arch();
213
213
  }
214
214
  function getShell() {
215
215
  const shell = process2__namespace.env.SHELL || process2__namespace.env.ComSpec || "";
216
- const shellName = path5__namespace.basename(shell).toLowerCase();
216
+ const shellName = path6__namespace.basename(shell).toLowerCase();
217
217
  if (shellName.includes("bash")) return "bash";
218
218
  if (shellName.includes("zsh")) return "zsh";
219
219
  if (shellName.includes("fish")) return "fish";
@@ -222,21 +222,21 @@ function getShell() {
222
222
  return "unknown";
223
223
  }
224
224
  function getHomeDirectory() {
225
- return os__namespace.homedir();
225
+ return os2__namespace.homedir();
226
226
  }
227
227
  function getTempDirectory() {
228
- return os__namespace.tmpdir();
228
+ return os2__namespace.tmpdir();
229
229
  }
230
230
  function getConfigDirectory(appName = "juno-task") {
231
231
  const platform2 = getPlatform();
232
232
  const home = getHomeDirectory();
233
233
  switch (platform2) {
234
234
  case "win32":
235
- return path5__namespace.join(process2__namespace.env.APPDATA || path5__namespace.join(home, "AppData", "Roaming"), appName);
235
+ return path6__namespace.join(process2__namespace.env.APPDATA || path6__namespace.join(home, "AppData", "Roaming"), appName);
236
236
  case "darwin":
237
- return path5__namespace.join(home, "Library", "Application Support", appName);
237
+ return path6__namespace.join(home, "Library", "Application Support", appName);
238
238
  default:
239
- return path5__namespace.join(process2__namespace.env.XDG_CONFIG_HOME || path5__namespace.join(home, ".config"), appName);
239
+ return path6__namespace.join(process2__namespace.env.XDG_CONFIG_HOME || path6__namespace.join(home, ".config"), appName);
240
240
  }
241
241
  }
242
242
  function getDataDirectory(appName = "juno-task") {
@@ -244,11 +244,11 @@ function getDataDirectory(appName = "juno-task") {
244
244
  const home = getHomeDirectory();
245
245
  switch (platform2) {
246
246
  case "win32":
247
- return path5__namespace.join(process2__namespace.env.LOCALAPPDATA || path5__namespace.join(home, "AppData", "Local"), appName);
247
+ return path6__namespace.join(process2__namespace.env.LOCALAPPDATA || path6__namespace.join(home, "AppData", "Local"), appName);
248
248
  case "darwin":
249
- return path5__namespace.join(home, "Library", "Application Support", appName);
249
+ return path6__namespace.join(home, "Library", "Application Support", appName);
250
250
  default:
251
- return path5__namespace.join(process2__namespace.env.XDG_DATA_HOME || path5__namespace.join(home, ".local", "share"), appName);
251
+ return path6__namespace.join(process2__namespace.env.XDG_DATA_HOME || path6__namespace.join(home, ".local", "share"), appName);
252
252
  }
253
253
  }
254
254
  function getCacheDirectory(appName = "juno-task") {
@@ -256,11 +256,11 @@ function getCacheDirectory(appName = "juno-task") {
256
256
  const home = getHomeDirectory();
257
257
  switch (platform2) {
258
258
  case "win32":
259
- return path5__namespace.join(process2__namespace.env.TEMP || path5__namespace.join(home, "AppData", "Local", "Temp"), appName);
259
+ return path6__namespace.join(process2__namespace.env.TEMP || path6__namespace.join(home, "AppData", "Local", "Temp"), appName);
260
260
  case "darwin":
261
- return path5__namespace.join(home, "Library", "Caches", appName);
261
+ return path6__namespace.join(home, "Library", "Caches", appName);
262
262
  default:
263
- return path5__namespace.join(process2__namespace.env.XDG_CACHE_HOME || path5__namespace.join(home, ".cache"), appName);
263
+ return path6__namespace.join(process2__namespace.env.XDG_CACHE_HOME || path6__namespace.join(home, ".cache"), appName);
264
264
  }
265
265
  }
266
266
  async function createDirectoryIfNotExists(dirPath) {
@@ -310,26 +310,26 @@ async function findMCPServerPath(serverName = "mcp-server") {
310
310
  const platform2 = getPlatform();
311
311
  const searchPaths = [];
312
312
  const pathEnv = process2__namespace.env.PATH || "";
313
- searchPaths.push(...pathEnv.split(path5__namespace.delimiter));
313
+ searchPaths.push(...pathEnv.split(path6__namespace.delimiter));
314
314
  if (platform2 === "win32") {
315
315
  searchPaths.push(
316
316
  "C:\\Program Files\\MCP\\bin",
317
317
  "C:\\Program Files (x86)\\MCP\\bin",
318
- path5__namespace.join(process2__namespace.env.LOCALAPPDATA || "", "Programs", "MCP", "bin")
318
+ path6__namespace.join(process2__namespace.env.LOCALAPPDATA || "", "Programs", "MCP", "bin")
319
319
  );
320
320
  } else {
321
321
  searchPaths.push(
322
322
  "/usr/local/bin",
323
323
  "/usr/bin",
324
324
  "/opt/mcp/bin",
325
- path5__namespace.join(getHomeDirectory(), ".local", "bin"),
326
- path5__namespace.join(getHomeDirectory(), "bin")
325
+ path6__namespace.join(getHomeDirectory(), ".local", "bin"),
326
+ path6__namespace.join(getHomeDirectory(), "bin")
327
327
  );
328
328
  }
329
329
  const executable = platform2 === "win32" ? `${serverName}.exe` : serverName;
330
330
  for (const searchPath of searchPaths) {
331
331
  if (!searchPath) continue;
332
- const fullPath = path5__namespace.join(searchPath, executable);
332
+ const fullPath = path6__namespace.join(searchPath, executable);
333
333
  try {
334
334
  await nodeFs.promises.access(fullPath, nodeFs.promises.constants.F_OK | nodeFs.promises.constants.X_OK);
335
335
  return fullPath;
@@ -4051,6 +4051,13 @@ var DEFAULT_HOOKS = {
4051
4051
  // Use for: final cleanup, notifications, reports, post-run actions
4052
4052
  END_RUN: {
4053
4053
  commands: []
4054
+ },
4055
+ // Executes when stale iteration is detected in run_until_completion.sh
4056
+ // Use for: alerts, notifications, logging when agent is not making progress
4057
+ ON_STALE: {
4058
+ commands: [
4059
+ `./.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`
4060
+ ]
4054
4061
  }
4055
4062
  };
4056
4063
  function getDefaultHooks() {
@@ -4104,9 +4111,9 @@ var ProfileManager = class {
4104
4111
  profileCache = /* @__PURE__ */ new Map();
4105
4112
  activeProfile = null;
4106
4113
  constructor(configDir) {
4107
- this.profilesDir = path5__namespace.join(configDir, "profiles");
4108
- this.activeProfileFile = path5__namespace.join(configDir, "active-profile.txt");
4109
- this.defaultConfigFile = path5__namespace.join(configDir, "config.json");
4114
+ this.profilesDir = path6__namespace.join(configDir, "profiles");
4115
+ this.activeProfileFile = path6__namespace.join(configDir, "active-profile.txt");
4116
+ this.defaultConfigFile = path6__namespace.join(configDir, "config.json");
4110
4117
  }
4111
4118
  /**
4112
4119
  * Initialize the profile system
@@ -4213,7 +4220,7 @@ var ProfileManager = class {
4213
4220
  profile,
4214
4221
  version: "1.0.0"
4215
4222
  };
4216
- const profilePath = path5__namespace.join(this.profilesDir, `${profile.name}.json`);
4223
+ const profilePath = path6__namespace.join(this.profilesDir, `${profile.name}.json`);
4217
4224
  await nodeFs.promises.writeFile(profilePath, JSON.stringify(storage, null, 2), "utf-8");
4218
4225
  this.profileCache.delete(profile.name);
4219
4226
  }
@@ -4366,7 +4373,7 @@ var ProfileManager = class {
4366
4373
  async findProfileFile(name) {
4367
4374
  const extensions = [".json", ".yaml", ".yml"];
4368
4375
  for (const ext of extensions) {
4369
- const filePath = path5__namespace.join(this.profilesDir, `${name}${ext}`);
4376
+ const filePath = path6__namespace.join(this.profilesDir, `${name}${ext}`);
4370
4377
  try {
4371
4378
  await nodeFs.promises.access(filePath);
4372
4379
  return filePath;
@@ -4435,7 +4442,7 @@ var ProfileManager = class {
4435
4442
  headlessMode: false,
4436
4443
  // Paths
4437
4444
  workingDirectory: process.cwd(),
4438
- sessionDirectory: path5__namespace.join(process.cwd(), ".juno_task", "sessions")
4445
+ sessionDirectory: path6__namespace.join(process.cwd(), ".juno_task", "sessions")
4439
4446
  };
4440
4447
  }
4441
4448
  };
@@ -4487,6 +4494,8 @@ var ENV_VAR_MAPPING = {
4487
4494
  JUNO_CODE_MCP_SERVER_NAME: "mcpServerName",
4488
4495
  // Hook settings
4489
4496
  JUNO_CODE_HOOK_COMMAND_TIMEOUT: "hookCommandTimeout",
4497
+ // Quota/hourly limit settings
4498
+ JUNO_CODE_ON_HOURLY_LIMIT: "onHourlyLimit",
4490
4499
  // TUI settings
4491
4500
  JUNO_CODE_INTERACTIVE: "interactive",
4492
4501
  JUNO_CODE_HEADLESS_MODE: "headlessMode",
@@ -4512,6 +4521,8 @@ var LEGACY_ENV_VAR_MAPPING = {
4512
4521
  JUNO_TASK_MCP_SERVER_NAME: "mcpServerName",
4513
4522
  // Hook settings
4514
4523
  JUNO_TASK_HOOK_COMMAND_TIMEOUT: "hookCommandTimeout",
4524
+ // Quota/hourly limit settings
4525
+ JUNO_TASK_ON_HOURLY_LIMIT: "onHourlyLimit",
4515
4526
  // TUI settings
4516
4527
  JUNO_TASK_INTERACTIVE: "interactive",
4517
4528
  JUNO_TASK_HEADLESS_MODE: "headlessMode",
@@ -4522,7 +4533,8 @@ var LEGACY_ENV_VAR_MAPPING = {
4522
4533
  var SubagentTypeSchema = zod.z.enum(["claude", "cursor", "codex", "gemini"]);
4523
4534
  var BackendTypeSchema = zod.z.enum(["mcp", "shell"]);
4524
4535
  var LogLevelSchema = zod.z.enum(["error", "warn", "info", "debug", "trace"]);
4525
- var HookTypeSchema = zod.z.enum(["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN"]);
4536
+ var OnHourlyLimitSchema = zod.z.enum(["wait", "raise"]);
4537
+ var HookTypeSchema = zod.z.enum(["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN", "ON_STALE"]);
4526
4538
  var HookSchema = zod.z.object({
4527
4539
  commands: zod.z.array(zod.z.string()).describe("List of bash commands to execute for this hook")
4528
4540
  });
@@ -4545,6 +4557,8 @@ var JunoTaskConfigSchema = zod.z.object({
4545
4557
  mcpServerName: zod.z.string().optional().describe('Named MCP server to connect to (e.g., "roundtable-ai")'),
4546
4558
  // Hook settings
4547
4559
  hookCommandTimeout: zod.z.number().int().min(1e3).max(36e5).optional().describe("Timeout for individual hook commands in milliseconds (default: 300000 = 5 minutes)"),
4560
+ // Quota/hourly limit settings
4561
+ onHourlyLimit: OnHourlyLimitSchema.describe('Behavior when Claude hourly quota limit is reached: "wait" to sleep until reset, "raise" to exit immediately'),
4548
4562
  // TUI settings
4549
4563
  interactive: zod.z.boolean().describe("Enable interactive mode"),
4550
4564
  headlessMode: zod.z.boolean().describe("Enable headless mode (no TUI)"),
@@ -4563,18 +4577,21 @@ var DEFAULT_CONFIG = {
4563
4577
  logLevel: "info",
4564
4578
  verbose: false,
4565
4579
  quiet: false,
4566
- // MCP settings
4567
- mcpTimeout: 864e5,
4568
- // 86400 seconds (24 hours / 1 day) - maximum safe timeout for long-running operations
4580
+ // MCP settings (also used by shell backend)
4581
+ mcpTimeout: 432e5,
4582
+ // 43200 seconds (12 hours) - default for long-running shell backend operations
4569
4583
  mcpRetries: 3,
4570
4584
  mcpServerName: "roundtable-ai",
4571
4585
  // Default to roundtable-ai server
4586
+ // Quota/hourly limit settings
4587
+ onHourlyLimit: "raise",
4588
+ // Default to exit immediately when hourly limit is reached
4572
4589
  // TUI settings
4573
4590
  interactive: true,
4574
4591
  headlessMode: false,
4575
4592
  // Paths
4576
4593
  workingDirectory: process.cwd(),
4577
- sessionDirectory: path5__namespace.join(process.cwd(), ".juno_task"),
4594
+ sessionDirectory: path6__namespace.join(process.cwd(), ".juno_task"),
4578
4595
  // Hooks configuration - populated with default hooks template
4579
4596
  hooks: getDefaultHooks()
4580
4597
  };
@@ -4588,10 +4605,10 @@ var GLOBAL_CONFIG_FILE_NAMES = [
4588
4605
  ];
4589
4606
  var PROJECT_CONFIG_FILE = ".juno_task/config.json";
4590
4607
  function resolvePath(inputPath, basePath = process.cwd()) {
4591
- if (path5__namespace.isAbsolute(inputPath)) {
4608
+ if (path6__namespace.isAbsolute(inputPath)) {
4592
4609
  return inputPath;
4593
4610
  }
4594
- return path5__namespace.resolve(basePath, inputPath);
4611
+ return path6__namespace.resolve(basePath, inputPath);
4595
4612
  }
4596
4613
  function parseEnvValue(value) {
4597
4614
  if (value === "") return value;
@@ -4646,7 +4663,7 @@ async function loadPackageJsonConfig(filePath) {
4646
4663
  }
4647
4664
  }
4648
4665
  function getConfigFileFormat(filePath) {
4649
- const ext = path5__namespace.extname(filePath).toLowerCase();
4666
+ const ext = path6__namespace.extname(filePath).toLowerCase();
4650
4667
  switch (ext) {
4651
4668
  case ".json":
4652
4669
  return "json";
@@ -4672,7 +4689,7 @@ async function loadConfigFromFile(filePath) {
4672
4689
  }
4673
4690
  switch (format) {
4674
4691
  case "json":
4675
- if (path5__namespace.basename(filePath) === "package.json") {
4692
+ if (path6__namespace.basename(filePath) === "package.json") {
4676
4693
  return loadPackageJsonConfig(resolvedPath);
4677
4694
  }
4678
4695
  return loadJsonConfig(resolvedPath);
@@ -4687,7 +4704,7 @@ async function loadConfigFromFile(filePath) {
4687
4704
  }
4688
4705
  }
4689
4706
  async function findProjectConfigFile(searchDir = process.cwd()) {
4690
- const filePath = path5__namespace.join(searchDir, PROJECT_CONFIG_FILE);
4707
+ const filePath = path6__namespace.join(searchDir, PROJECT_CONFIG_FILE);
4691
4708
  try {
4692
4709
  await nodeFs.promises.access(filePath, nodeFs__namespace.constants.R_OK);
4693
4710
  return filePath;
@@ -4697,7 +4714,7 @@ async function findProjectConfigFile(searchDir = process.cwd()) {
4697
4714
  }
4698
4715
  async function findGlobalConfigFile(searchDir = process.cwd()) {
4699
4716
  for (const fileName of GLOBAL_CONFIG_FILE_NAMES) {
4700
- const filePath = path5__namespace.join(searchDir, fileName);
4717
+ const filePath = path6__namespace.join(searchDir, fileName);
4701
4718
  try {
4702
4719
  await nodeFs.promises.access(filePath, nodeFs__namespace.constants.R_OK);
4703
4720
  return filePath;
@@ -4871,8 +4888,8 @@ function validateConfig(config) {
4871
4888
  }
4872
4889
  async function ensureHooksConfig(baseDir) {
4873
4890
  try {
4874
- const configDir = path5__namespace.join(baseDir, ".juno_task");
4875
- const configPath = path5__namespace.join(configDir, "config.json");
4891
+ const configDir = path6__namespace.join(baseDir, ".juno_task");
4892
+ const configPath = path6__namespace.join(configDir, "config.json");
4876
4893
  await fs__default.default.ensureDir(configDir);
4877
4894
  const configExists = await fs__default.default.pathExists(configPath);
4878
4895
  const allHookTypes = getDefaultHooks();
@@ -6807,7 +6824,7 @@ async function executeHooks(hookTypes, hooks, context = {}, options = {}) {
6807
6824
  function validateHooksConfig(hooks) {
6808
6825
  const issues = [];
6809
6826
  const warnings = [];
6810
- const validHookTypes = ["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN"];
6827
+ const validHookTypes = ["START_RUN", "START_ITERATION", "END_ITERATION", "END_RUN", "ON_STALE"];
6811
6828
  for (const [hookType, hook] of Object.entries(hooks)) {
6812
6829
  if (!validHookTypes.includes(hookType)) {
6813
6830
  warnings.push(`Unknown hook type: ${hookType}. Valid types are: ${validHookTypes.join(", ")}`);
@@ -6851,6 +6868,20 @@ function validateHooksConfig(hooks) {
6851
6868
  };
6852
6869
  }
6853
6870
 
6871
+ // src/core/backends/shell-backend.ts
6872
+ init_version();
6873
+ function formatDuration(ms) {
6874
+ const totalSeconds = Math.ceil(ms / 1e3);
6875
+ const hours = Math.floor(totalSeconds / 3600);
6876
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
6877
+ const seconds = totalSeconds % 60;
6878
+ const parts = [];
6879
+ if (hours > 0) parts.push(`${hours}h`);
6880
+ if (minutes > 0) parts.push(`${minutes}m`);
6881
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
6882
+ return parts.join(" ");
6883
+ }
6884
+
6854
6885
  // src/core/engine.ts
6855
6886
  var ExecutionStatus = /* @__PURE__ */ ((ExecutionStatus2) => {
6856
6887
  ExecutionStatus2["PENDING"] = "pending";
@@ -7150,6 +7181,8 @@ var ExecutionEngine = class extends events.EventEmitter {
7150
7181
  totalProgressEvents: 0,
7151
7182
  rateLimitEncounters: 0,
7152
7183
  rateLimitWaitTime: 0,
7184
+ quotaLimitEncounters: 0,
7185
+ quotaLimitWaitTime: 0,
7153
7186
  errorBreakdown: {},
7154
7187
  performanceMetrics: {
7155
7188
  cpuUsage: 0,
@@ -7254,7 +7287,13 @@ var ExecutionEngine = class extends events.EventEmitter {
7254
7287
  while (!this.shouldStopIterating(context, iterationNumber)) {
7255
7288
  this.checkAbortSignal(context);
7256
7289
  try {
7257
- await this.executeIteration(context, iterationNumber);
7290
+ const quotaLimitInfo = await this.executeIteration(context, iterationNumber);
7291
+ if (quotaLimitInfo?.detected) {
7292
+ const shouldRetry = await this.handleQuotaLimit(context, quotaLimitInfo);
7293
+ if (shouldRetry) {
7294
+ continue;
7295
+ }
7296
+ }
7258
7297
  iterationNumber++;
7259
7298
  } catch (error) {
7260
7299
  if (error instanceof MCPRateLimitError) {
@@ -7272,6 +7311,7 @@ var ExecutionEngine = class extends events.EventEmitter {
7272
7311
  }
7273
7312
  /**
7274
7313
  * Execute a single iteration
7314
+ * @returns QuotaLimitInfo if a quota limit was detected, null otherwise
7275
7315
  */
7276
7316
  async executeIteration(context, iterationNumber) {
7277
7317
  const iterationStart = /* @__PURE__ */ new Date();
@@ -7365,6 +7405,8 @@ var ExecutionEngine = class extends events.EventEmitter {
7365
7405
  } catch (error) {
7366
7406
  engineLogger.warn("Hook END_ITERATION failed", { error, iterationNumber });
7367
7407
  }
7408
+ const quotaLimitInfo = this.extractQuotaLimitInfo(toolResult);
7409
+ return quotaLimitInfo;
7368
7410
  } catch (error) {
7369
7411
  const iterationEnd = /* @__PURE__ */ new Date();
7370
7412
  const duration = iterationEnd.getTime() - iterationStart.getTime();
@@ -7458,6 +7500,100 @@ var ExecutionEngine = class extends events.EventEmitter {
7458
7500
  }
7459
7501
  return 6e4;
7460
7502
  }
7503
+ /**
7504
+ * Handle Claude quota limit with automatic sleep and retry
7505
+ * @returns true if we should retry the iteration, false otherwise
7506
+ */
7507
+ async handleQuotaLimit(context, quotaInfo) {
7508
+ if (!quotaInfo.detected || !quotaInfo.sleepDurationMs) {
7509
+ return false;
7510
+ }
7511
+ context.statistics.quotaLimitEncounters++;
7512
+ const onHourlyLimit = this.engineConfig.config.onHourlyLimit || "raise";
7513
+ if (onHourlyLimit === "raise") {
7514
+ const resetTimeStr2 = quotaInfo.resetTime ? quotaInfo.resetTime.toLocaleTimeString("en-US", {
7515
+ hour: "numeric",
7516
+ minute: "2-digit",
7517
+ hour12: true,
7518
+ timeZoneName: "short"
7519
+ }) : "unknown";
7520
+ 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`);
7521
+ engineLogger.info(`\u2551 Claude Quota Limit Reached \u2551`);
7522
+ 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`);
7523
+ engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr2.padEnd(44)}\u2551`);
7524
+ engineLogger.info(`\u2551 Behavior: raise (exit immediately) \u2551`);
7525
+ 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`);
7526
+ engineLogger.info(`\u2551 To auto-wait instead, use: --on-hourly-limit wait \u2551`);
7527
+ engineLogger.info(`\u2551 Or set: JUNO_CODE_ON_HOURLY_LIMIT=wait \u2551`);
7528
+ engineLogger.info(`\u2551 Or in config.json: { "onHourlyLimit": "wait" } \u2551`);
7529
+ 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`);
7530
+ this.emit("quota-limit:raise", { context, quotaInfo });
7531
+ throw new Error(`Claude quota limit reached. Quota resets at ${resetTimeStr2}. Use --on-hourly-limit wait to auto-retry.`);
7532
+ }
7533
+ const waitTimeMs = quotaInfo.sleepDurationMs;
7534
+ const maxWaitTimeMs = 12 * 60 * 60 * 1e3;
7535
+ if (waitTimeMs > maxWaitTimeMs) {
7536
+ engineLogger.warn(`Quota limit wait time (${formatDuration(waitTimeMs)}) exceeds maximum allowed (12 hours). Will not auto-retry.`);
7537
+ return false;
7538
+ }
7539
+ context.statistics.quotaLimitWaitTime += waitTimeMs;
7540
+ const resetTimeStr = quotaInfo.resetTime ? quotaInfo.resetTime.toLocaleTimeString("en-US", {
7541
+ hour: "numeric",
7542
+ minute: "2-digit",
7543
+ hour12: true,
7544
+ timeZoneName: "short"
7545
+ }) : "unknown";
7546
+ const durationStr = formatDuration(waitTimeMs);
7547
+ 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`);
7548
+ engineLogger.info(`\u2551 Claude Quota Limit Reached \u2551`);
7549
+ 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`);
7550
+ engineLogger.info(`\u2551 Quota resets at: ${resetTimeStr.padEnd(44)}\u2551`);
7551
+ engineLogger.info(`\u2551 Sleeping for: ${durationStr.padEnd(44)}\u2551`);
7552
+ if (quotaInfo.timezone) {
7553
+ engineLogger.info(`\u2551 Timezone: ${quotaInfo.timezone.padEnd(44)}\u2551`);
7554
+ }
7555
+ 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`);
7556
+ this.emit("quota-limit:start", { context, quotaInfo, waitTimeMs });
7557
+ await this.sleepWithProgress(waitTimeMs, (remaining) => {
7558
+ const remainingStr = formatDuration(remaining);
7559
+ engineLogger.info(`[Quota Wait] ${remainingStr} remaining until retry...`);
7560
+ });
7561
+ this.emit("quota-limit:end", { context });
7562
+ engineLogger.info(`Quota limit wait complete. Resuming execution...`);
7563
+ return true;
7564
+ }
7565
+ /**
7566
+ * Sleep with periodic progress updates
7567
+ */
7568
+ async sleepWithProgress(totalMs, onProgress) {
7569
+ const updateIntervalMs = 6e4;
7570
+ let remaining = totalMs;
7571
+ while (remaining > 0) {
7572
+ const sleepTime = Math.min(remaining, updateIntervalMs);
7573
+ await this.sleep(sleepTime);
7574
+ remaining -= sleepTime;
7575
+ if (remaining > 0) {
7576
+ onProgress(remaining);
7577
+ }
7578
+ }
7579
+ }
7580
+ /**
7581
+ * Check if tool result indicates a quota limit error
7582
+ */
7583
+ extractQuotaLimitInfo(toolResult) {
7584
+ const metadataQuotaInfo = toolResult.metadata?.quotaLimitInfo;
7585
+ if (metadataQuotaInfo?.detected) {
7586
+ return metadataQuotaInfo;
7587
+ }
7588
+ try {
7589
+ const content = typeof toolResult.content === "string" ? JSON.parse(toolResult.content) : toolResult.content;
7590
+ if (content?.quota_limit?.detected) {
7591
+ return content.quota_limit;
7592
+ }
7593
+ } catch {
7594
+ }
7595
+ return null;
7596
+ }
7461
7597
  /**
7462
7598
  * Handle iteration errors with recovery strategies
7463
7599
  */
@@ -7786,9 +7922,9 @@ var FileSessionStorage = class {
7786
7922
  * @param baseDir - Base directory for session storage
7787
7923
  */
7788
7924
  constructor(baseDir) {
7789
- this.baseDir = path5__namespace.resolve(baseDir);
7790
- this.sessionsDir = path5__namespace.join(this.baseDir, "sessions");
7791
- this.archiveDir = path5__namespace.join(this.baseDir, "archive");
7925
+ this.baseDir = path6__namespace.resolve(baseDir);
7926
+ this.sessionsDir = path6__namespace.join(this.baseDir, "sessions");
7927
+ this.archiveDir = path6__namespace.join(this.baseDir, "archive");
7792
7928
  }
7793
7929
  /**
7794
7930
  * Initialize storage directories
@@ -7803,7 +7939,7 @@ var FileSessionStorage = class {
7803
7939
  * @returns Full path to session file
7804
7940
  */
7805
7941
  getSessionPath(sessionId) {
7806
- return path5__namespace.join(this.sessionsDir, `${sessionId}.json`);
7942
+ return path6__namespace.join(this.sessionsDir, `${sessionId}.json`);
7807
7943
  }
7808
7944
  /**
7809
7945
  * Save session data to storage
@@ -7869,7 +8005,7 @@ var FileSessionStorage = class {
7869
8005
  const sessions = [];
7870
8006
  for (const file of sessionFiles) {
7871
8007
  try {
7872
- const sessionId = path5__namespace.basename(file, ".json");
8008
+ const sessionId = path6__namespace.basename(file, ".json");
7873
8009
  const session = await this.loadSession(sessionId);
7874
8010
  if (session) {
7875
8011
  sessions.push(session.info);
@@ -7972,7 +8108,7 @@ var FileSessionStorage = class {
7972
8108
  const session = await this.loadSession(sessionInfo.id);
7973
8109
  if (!session) continue;
7974
8110
  const archiveFileName = `${sessionInfo.id}_${sessionInfo.createdAt.toISOString().split("T")[0]}.json`;
7975
- const archivePath = path5__namespace.join(this.archiveDir, archiveFileName);
8111
+ const archivePath = path6__namespace.join(this.archiveDir, archiveFileName);
7976
8112
  if (options.includeData) {
7977
8113
  await nodeFs.promises.writeFile(
7978
8114
  archivePath,
@@ -8780,8 +8916,8 @@ var MetricsCollector = class extends events.EventEmitter {
8780
8916
  */
8781
8917
  initializeSystemMetrics() {
8782
8918
  const memoryUsage2 = process.memoryUsage();
8783
- const totalMem = os.totalmem();
8784
- const freeMem = os.freemem();
8919
+ const totalMem = os2.totalmem();
8920
+ const freeMem = os2.freemem();
8785
8921
  const usedMem = totalMem - freeMem;
8786
8922
  return {
8787
8923
  timestamp: /* @__PURE__ */ new Date(),
@@ -8855,8 +8991,8 @@ var MetricsCollector = class extends events.EventEmitter {
8855
8991
  */
8856
8992
  updateSystemMetrics() {
8857
8993
  const memoryUsage2 = process.memoryUsage();
8858
- const totalMem = os.totalmem();
8859
- const freeMem = os.freemem();
8994
+ const totalMem = os2.totalmem();
8995
+ const freeMem = os2.freemem();
8860
8996
  const usedMem = totalMem - freeMem;
8861
8997
  this.systemMetrics = {
8862
8998
  ...this.systemMetrics,
@@ -9254,7 +9390,7 @@ var MetricsReporter = class {
9254
9390
  default:
9255
9391
  throw new Error(`Unsupported export format: ${options.format}`);
9256
9392
  }
9257
- await nodeFs.promises.mkdir(path5__namespace.dirname(options.outputPath), { recursive: true });
9393
+ await nodeFs.promises.mkdir(path6__namespace.dirname(options.outputPath), { recursive: true });
9258
9394
  await nodeFs.promises.writeFile(options.outputPath, exportData, "utf-8");
9259
9395
  }
9260
9396
  /**
@@ -9372,7 +9508,7 @@ var IterationsSchema = zod.z.number().int("Iterations must be an integer").refin
9372
9508
  (value) => value === -1 || value > 0,
9373
9509
  "Iterations must be a positive integer or -1 for infinite"
9374
9510
  ).transform((value) => value === -1 ? Infinity : value);
9375
- var FilePathSchema = zod.z.string().min(1, "File path cannot be empty").transform((value) => path5__namespace.resolve(value)).refine(async (filePath) => {
9511
+ var FilePathSchema = zod.z.string().min(1, "File path cannot be empty").transform((value) => path6__namespace.resolve(value)).refine(async (filePath) => {
9376
9512
  try {
9377
9513
  const stats = await nodeFs.promises.stat(filePath);
9378
9514
  return stats.isFile();
@@ -9380,7 +9516,7 @@ var FilePathSchema = zod.z.string().min(1, "File path cannot be empty").transfor
9380
9516
  return false;
9381
9517
  }
9382
9518
  }, "File does not exist or is not accessible");
9383
- var DirectoryPathSchema = zod.z.string().min(1, "Directory path cannot be empty").transform((value) => path5__namespace.resolve(value)).refine(async (dirPath) => {
9519
+ var DirectoryPathSchema = zod.z.string().min(1, "Directory path cannot be empty").transform((value) => path6__namespace.resolve(value)).refine(async (dirPath) => {
9384
9520
  try {
9385
9521
  const stats = await nodeFs.promises.stat(dirPath);
9386
9522
  return stats.isDirectory();
@@ -9536,7 +9672,7 @@ function isValidLogLevel(value) {
9536
9672
  }
9537
9673
  async function isValidPath(filePath, type = "file") {
9538
9674
  try {
9539
- const resolvedPath = path5__namespace.resolve(filePath);
9675
+ const resolvedPath = path6__namespace.resolve(filePath);
9540
9676
  const stats = await nodeFs.promises.stat(resolvedPath);
9541
9677
  return type === "file" ? stats.isFile() : stats.isDirectory();
9542
9678
  } catch {
@@ -9557,7 +9693,7 @@ function sanitizeFilePath(filePath) {
9557
9693
  if (!cleaned) {
9558
9694
  throw new ValidationError("File path cannot be empty after sanitization", "filePath", filePath);
9559
9695
  }
9560
- return path5__namespace.resolve(cleaned);
9696
+ return path6__namespace.resolve(cleaned);
9561
9697
  }
9562
9698
  function sanitizeGitUrl(url) {
9563
9699
  if (typeof url !== "string") {
@@ -9688,8 +9824,8 @@ function validateCommandOptions(options) {
9688
9824
  } catch (error) {
9689
9825
  if (error instanceof zod.z.ZodError) {
9690
9826
  const errors = error.errors.map((err) => {
9691
- const path10 = err.path.length > 0 ? err.path.join(".") : "option";
9692
- return `--${path10}: ${err.message}`;
9827
+ const path11 = err.path.length > 0 ? err.path.join(".") : "option";
9828
+ return `--${path11}: ${err.message}`;
9693
9829
  });
9694
9830
  const suggestions = [
9695
9831
  "Check command line option syntax",
@@ -10990,7 +11126,7 @@ var MCPConfigLoader = class {
10990
11126
  const configContent = await fs__default.default.readFile(configPath, "utf-8");
10991
11127
  const config = JSON.parse(configContent);
10992
11128
  this.validateConfig(config);
10993
- const resolvedConfig = this.resolveConfigPaths(config, path5__namespace.dirname(configPath));
11129
+ const resolvedConfig = this.resolveConfigPaths(config, path6__namespace.dirname(configPath));
10994
11130
  this.configCache.set(configPath, resolvedConfig);
10995
11131
  this.lastLoadTime.set(configPath, Date.now());
10996
11132
  return resolvedConfig;
@@ -11005,14 +11141,14 @@ var MCPConfigLoader = class {
11005
11141
  * Find the mcp.json configuration file
11006
11142
  */
11007
11143
  static async findConfigFile(startDir) {
11008
- let currentDir = path5__namespace.resolve(startDir);
11009
- const rootDir = path5__namespace.parse(currentDir).root;
11144
+ let currentDir = path6__namespace.resolve(startDir);
11145
+ const rootDir = path6__namespace.parse(currentDir).root;
11010
11146
  while (currentDir !== rootDir) {
11011
- const configPath = path5__namespace.join(currentDir, ".juno_task", "mcp.json");
11147
+ const configPath = path6__namespace.join(currentDir, ".juno_task", "mcp.json");
11012
11148
  if (await fs__default.default.pathExists(configPath)) {
11013
11149
  return configPath;
11014
11150
  }
11015
- currentDir = path5__namespace.dirname(currentDir);
11151
+ currentDir = path6__namespace.dirname(currentDir);
11016
11152
  }
11017
11153
  throw new MCPConnectionError(
11018
11154
  `MCP configuration not found. Please run 'juno-task init' to create .juno_task/mcp.json`
@@ -11078,16 +11214,16 @@ var MCPConfigLoader = class {
11078
11214
  const resolvedConfig = JSON.parse(JSON.stringify(config));
11079
11215
  for (const [serverName, serverConfig] of Object.entries(resolvedConfig.mcpServers)) {
11080
11216
  serverConfig.args = serverConfig.args.map((arg) => {
11081
- if (arg.includes("/") && !path5__namespace.isAbsolute(arg)) {
11082
- return path5__namespace.resolve(configDir, "..", arg);
11217
+ if (arg.includes("/") && !path6__namespace.isAbsolute(arg)) {
11218
+ return path6__namespace.resolve(configDir, "..", arg);
11083
11219
  }
11084
11220
  return arg;
11085
11221
  });
11086
11222
  if (serverConfig.env) {
11087
11223
  for (const [envKey, envValue] of Object.entries(serverConfig.env)) {
11088
11224
  const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(envValue);
11089
- if (!isUrl && envValue.includes("/") && !path5__namespace.isAbsolute(envValue)) {
11090
- serverConfig.env[envKey] = path5__namespace.resolve(configDir, "..", envValue);
11225
+ if (!isUrl && envValue.includes("/") && !path6__namespace.isAbsolute(envValue)) {
11226
+ serverConfig.env[envKey] = path6__namespace.resolve(configDir, "..", envValue);
11091
11227
  }
11092
11228
  }
11093
11229
  }
@@ -11154,7 +11290,7 @@ var JunoLogger = class {
11154
11290
  enableConsoleLogging;
11155
11291
  logFilePath;
11156
11292
  constructor(options = {}) {
11157
- this.logDirectory = options.logDirectory || path5__namespace.default.join(process.cwd(), ".juno_task", "logs");
11293
+ this.logDirectory = options.logDirectory || path6__namespace.default.join(process.cwd(), ".juno_task", "logs");
11158
11294
  this.logLevel = options.logLevel || 1 /* INFO */;
11159
11295
  this.enableConsoleLogging = options.enableConsoleLogging ?? true;
11160
11296
  }
@@ -11169,7 +11305,7 @@ var JunoLogger = class {
11169
11305
  const timeStr = timestamp[1].split("-")[0].substring(0, 6);
11170
11306
  const fullTimestamp = `${dateStr}_${timeStr}`;
11171
11307
  const logFileName = `subagent_loop_${subagent}_${fullTimestamp}.log`;
11172
- this.logFilePath = path5__namespace.default.join(this.logDirectory, logFileName);
11308
+ this.logFilePath = path6__namespace.default.join(this.logDirectory, logFileName);
11173
11309
  const header = `# Juno-Task TypeScript Log - Started ${(/* @__PURE__ */ new Date()).toISOString()}
11174
11310
  `;
11175
11311
  await fs__default.default.writeFile(this.logFilePath, header);
@@ -11456,8 +11592,8 @@ var TerminalProgressWriter = class {
11456
11592
  this.options.stream.write("\r\x1B[K");
11457
11593
  if (typeof content === "string") {
11458
11594
  this.options.stream.write(content);
11459
- if (!content.endsWith("\n") && !content.endsWith(os.EOL)) {
11460
- this.options.stream.write(os.EOL);
11595
+ if (!content.endsWith("\n") && !content.endsWith(os2.EOL)) {
11596
+ this.options.stream.write(os2.EOL);
11461
11597
  }
11462
11598
  } else {
11463
11599
  console.error(content);
@@ -11465,8 +11601,8 @@ var TerminalProgressWriter = class {
11465
11601
  } else {
11466
11602
  if (typeof content === "string") {
11467
11603
  this.options.stream.write(content);
11468
- if (!content.endsWith("\n") && !content.endsWith(os.EOL)) {
11469
- this.options.stream.write(os.EOL);
11604
+ if (!content.endsWith("\n") && !content.endsWith(os2.EOL)) {
11605
+ this.options.stream.write(os2.EOL);
11470
11606
  }
11471
11607
  } else {
11472
11608
  console.error(content);
@@ -12039,8 +12175,8 @@ var JunoMCPClient = class {
12039
12175
  `${serverName}`,
12040
12176
  // Assume it's in PATH
12041
12177
  `/usr/local/bin/${serverName}`,
12042
- path5__namespace.default.resolve(os__namespace.default.homedir(), `.local/bin/${serverName}`),
12043
- path5__namespace.default.resolve(this.options.workingDirectory || process.cwd(), `${serverName}`)
12178
+ path6__namespace.default.resolve(os2__namespace.default.homedir(), `.local/bin/${serverName}`),
12179
+ path6__namespace.default.resolve(this.options.workingDirectory || process.cwd(), `${serverName}`)
12044
12180
  ];
12045
12181
  for (const serverPath of possiblePaths) {
12046
12182
  try {
@@ -13966,8 +14102,8 @@ var IOError = class extends SystemError {
13966
14102
  };
13967
14103
  var InvalidPathError = class extends SystemError {
13968
14104
  code = "SYSTEM_INVALID_PATH" /* SYSTEM_INVALID_PATH */;
13969
- constructor(path10, reason, options) {
13970
- let message = `Invalid path: ${path10}`;
14105
+ constructor(path11, reason, options) {
14106
+ let message = `Invalid path: ${path11}`;
13971
14107
  if (reason) {
13972
14108
  message += ` (${reason})`;
13973
14109
  }
@@ -13976,7 +14112,7 @@ var InvalidPathError = class extends SystemError {
13976
14112
  context: {
13977
14113
  ...options?.context,
13978
14114
  metadata: {
13979
- filePath: path10,
14115
+ filePath: path11,
13980
14116
  reason,
13981
14117
  ...options?.context?.metadata
13982
14118
  },