@autoclawd/autoclawd 1.1.14 → 1.1.16

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
@@ -1083,7 +1083,10 @@ var RATE_LIMIT_PATTERNS = [
1083
1083
  /\b429\b.*(?:error|too many|rate|limit|retry)/i,
1084
1084
  /(?:error|status|code|http)[:\s]*429\b/i,
1085
1085
  /(?:api|server|model)\s+(?:is\s+)?overloaded/i,
1086
- /over\s*capacity/i
1086
+ /over\s*capacity/i,
1087
+ // Claude Code subscription limit: "You've hit your limit · resets 9am (UTC)"
1088
+ /hit your (?:usage )?limit.*resets?\s+\d+/i,
1089
+ /you've\s+(?:reached|hit).*limit/i
1087
1090
  ];
1088
1091
  var COMPLETION_PATTERNS = [
1089
1092
  /\[autoclawd:done\]/i,
@@ -1096,8 +1099,30 @@ function isCompleted(output) {
1096
1099
  return COMPLETION_PATTERNS.some((p) => p.test(output));
1097
1100
  }
1098
1101
  function estimateResetMs(output) {
1099
- const match = output.match(/retry.?after[:\s]*(\d+)/i);
1100
- if (match) return parseInt(match[1]) * 1e3;
1102
+ const clockMatch = output.match(/resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(?utc\)?/i);
1103
+ if (clockMatch) {
1104
+ let targetHour = parseInt(clockMatch[1]);
1105
+ const targetMin = clockMatch[2] ? parseInt(clockMatch[2]) : 0;
1106
+ const ampm = clockMatch[3].toLowerCase();
1107
+ if (ampm === "pm" && targetHour !== 12) targetHour += 12;
1108
+ if (ampm === "am" && targetHour === 12) targetHour = 0;
1109
+ const now = /* @__PURE__ */ new Date();
1110
+ const target = new Date(Date.UTC(
1111
+ now.getUTCFullYear(),
1112
+ now.getUTCMonth(),
1113
+ now.getUTCDate(),
1114
+ targetHour,
1115
+ targetMin,
1116
+ 0,
1117
+ 0
1118
+ ));
1119
+ if (target.getTime() <= now.getTime()) {
1120
+ target.setUTCDate(target.getUTCDate() + 1);
1121
+ }
1122
+ return target.getTime() - now.getTime() + 6e4;
1123
+ }
1124
+ const retryAfter = output.match(/retry.?after[:\s]*(\d+)/i);
1125
+ if (retryAfter) return parseInt(retryAfter[1]) * 1e3;
1101
1126
  return 6e4;
1102
1127
  }
1103
1128
  var MAX_RATE_LIMIT_RETRIES = 10;
@@ -1158,12 +1183,17 @@ async function runAgentLoop(opts) {
1158
1183
  return { iterations: i, success: true, lastOutput };
1159
1184
  }
1160
1185
  if (rateLimited) {
1186
+ const waitMs = estimateResetMs(combined);
1187
+ if (waitMs > 5 * 60 * 1e3) {
1188
+ const pauseUntil = Date.now() + waitMs;
1189
+ log.ticket(ticketId, `Rate limited with long reset (${Math.round(waitMs / 1e3)}s) \u2014 pausing watcher globally`);
1190
+ return { iterations: i, success: false, lastOutput, pauseUntil };
1191
+ }
1161
1192
  rateLimitRetries++;
1162
1193
  if (rateLimitRetries > MAX_RATE_LIMIT_RETRIES) {
1163
1194
  log.ticket(ticketId, `Rate limited ${rateLimitRetries} times, giving up`);
1164
1195
  return { iterations: i, success: false, lastOutput };
1165
1196
  }
1166
- const waitMs = estimateResetMs(combined);
1167
1197
  log.ticket(ticketId, `Rate limited (${rateLimitRetries}/${MAX_RATE_LIMIT_RETRIES}) \u2014 pausing ${Math.round(waitMs / 1e3)}s`);
1168
1198
  await sleep2(waitMs);
1169
1199
  i--;
@@ -1706,6 +1736,14 @@ async function executeTicket(opts) {
1706
1736
  prompt: agentPrompt,
1707
1737
  ticketId: ticket.identifier
1708
1738
  });
1739
+ if (agentResult.pauseUntil) {
1740
+ return {
1741
+ ticketId: ticket.identifier,
1742
+ success: false,
1743
+ error: `REQUEUE_PAUSE:${agentResult.pauseUntil}`,
1744
+ iterations: agentResult.iterations
1745
+ };
1746
+ }
1709
1747
  const gitResult = await commitAndPush(container, {
1710
1748
  branchName,
1711
1749
  ticketId: ticket.identifier,
@@ -1984,6 +2022,7 @@ var WebhookServer = class {
1984
2022
  server;
1985
2023
  // Track in-memory active set for concurrency counting (fast path)
1986
2024
  activeInMemory = /* @__PURE__ */ new Set();
2025
+ pausedUntil = 0;
1987
2026
  start() {
1988
2027
  getDb();
1989
2028
  this.resumeQueue();
@@ -2140,7 +2179,15 @@ var WebhookServer = class {
2140
2179
  octokit: this.octokit
2141
2180
  }).then(
2142
2181
  (result) => {
2143
- if (result.error?.startsWith("REQUEUE:")) {
2182
+ if (result.error?.startsWith("REQUEUE_PAUSE:")) {
2183
+ const until = parseInt(result.error.slice("REQUEUE_PAUSE:".length), 10);
2184
+ this.pausedUntil = Math.max(this.pausedUntil, until);
2185
+ const waitSec = Math.round((until - Date.now()) / 1e3);
2186
+ log.ticket(ticket.identifier, `Rate limit hit \u2014 pausing webhook dispatch for ${waitSec}s`);
2187
+ finishRun(ticket.id, "failed", result.error);
2188
+ removeFromProcessed(ticket.id);
2189
+ enqueue(ticket);
2190
+ } else if (result.error?.startsWith("REQUEUE:")) {
2144
2191
  log.ticket(ticket.identifier, "Re-queued (waiting for dependency branch)");
2145
2192
  finishRun(ticket.id, "failed", result.error);
2146
2193
  removeFromProcessed(ticket.id);
@@ -2160,6 +2207,7 @@ var WebhookServer = class {
2160
2207
  });
2161
2208
  }
2162
2209
  drainQueue() {
2210
+ if (this.pausedUntil > Date.now()) return;
2163
2211
  while (this.activeInMemory.size < this.config.maxConcurrent) {
2164
2212
  const next = dequeue();
2165
2213
  if (!next) break;
@@ -2233,6 +2281,7 @@ var Watcher = class {
2233
2281
  stopped = false;
2234
2282
  pollCount = 0;
2235
2283
  rebaseRunning = false;
2284
+ pausedUntil = 0;
2236
2285
  async start(intervalSeconds, once) {
2237
2286
  getDb();
2238
2287
  this.resumeQueue();
@@ -2278,6 +2327,15 @@ var Watcher = class {
2278
2327
  }
2279
2328
  }
2280
2329
  async poll() {
2330
+ if (this.pausedUntil > Date.now()) {
2331
+ const remainingSec = Math.round((this.pausedUntil - Date.now()) / 1e3);
2332
+ log.info(`Paused (rate limit) \u2014 ${remainingSec}s remaining until retry`);
2333
+ return;
2334
+ }
2335
+ if (this.pausedUntil > 0) {
2336
+ log.info("Rate limit pause ended, resuming polls");
2337
+ this.pausedUntil = 0;
2338
+ }
2281
2339
  this.recentlyRequeued.clear();
2282
2340
  try {
2283
2341
  const tickets = await pollTickets(this.linearClient, this.config);
@@ -2351,7 +2409,15 @@ var Watcher = class {
2351
2409
  octokit: this.octokit
2352
2410
  }).then(
2353
2411
  (result) => {
2354
- if (result.error?.startsWith("REQUEUE:")) {
2412
+ if (result.error?.startsWith("REQUEUE_PAUSE:")) {
2413
+ const until = parseInt(result.error.slice("REQUEUE_PAUSE:".length), 10);
2414
+ this.pausedUntil = Math.max(this.pausedUntil, until);
2415
+ const waitSec = Math.round((until - Date.now()) / 1e3);
2416
+ log.ticket(ticket.identifier, `Rate limit hit \u2014 pausing watcher for ${waitSec}s`);
2417
+ finishRun(ticket.id, "failed", result.error);
2418
+ removeFromProcessed(ticket.id);
2419
+ enqueue(ticket);
2420
+ } else if (result.error?.startsWith("REQUEUE:")) {
2355
2421
  log.ticket(ticket.identifier, "Re-queued (waiting for dependency branch)");
2356
2422
  finishRun(ticket.id, "failed", result.error);
2357
2423
  removeFromProcessed(ticket.id);
@@ -2372,6 +2438,7 @@ var Watcher = class {
2372
2438
  });
2373
2439
  }
2374
2440
  drainQueue() {
2441
+ if (this.pausedUntil > Date.now()) return;
2375
2442
  const queued = [];
2376
2443
  let next;
2377
2444
  while (next = dequeue()) {