@aspectlylabs/mikka 0.0.144 → 0.0.145

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/cli.js CHANGED
@@ -3002,7 +3002,9 @@ var RUN_STOP_REASONS = [
3002
3002
  "cancelled",
3003
3003
  "error",
3004
3004
  "timeout",
3005
- "device_lost"
3005
+ "device_lost",
3006
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
3007
+ "quota_exhausted"
3006
3008
  ];
3007
3009
  var runStopReason = z9.enum(RUN_STOP_REASONS);
3008
3010
  var shortList = z9.array(z9.string().max(1e3)).max(5e3);
@@ -3077,6 +3079,11 @@ var runReport = z9.object({
3077
3079
  nextStep: z9.string().max(2e3).nullable(),
3078
3080
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
3079
3081
  blockedOn: z9.string().max(500).optional(),
3082
+ /**
3083
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
3084
+ * reported it; null when it gave no time. Set by the daemon only.
3085
+ */
3086
+ resetsAt: z9.iso.datetime({ offset: true }).nullable().optional(),
3080
3087
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
3081
3088
  check: z9.object({
3082
3089
  sha: z9.string().regex(/^[a-f0-9]{40}$/),
@@ -4473,7 +4480,7 @@ import { join as join4 } from "node:path";
4473
4480
  // package.json
4474
4481
  var package_default = {
4475
4482
  name: "@mikka/daemon",
4476
- version: "0.0.144",
4483
+ version: "0.0.145",
4477
4484
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
4478
4485
  type: "module",
4479
4486
  private: true,
@@ -4533,7 +4540,7 @@ var package_default = {
4533
4540
  engines: {
4534
4541
  node: ">=24"
4535
4542
  },
4536
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
4543
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
4537
4544
  };
4538
4545
 
4539
4546
  // src/paths.ts
@@ -7686,6 +7693,83 @@ import { arch as arch2, hostname, platform as platform3 } from "node:os";
7686
7693
  import { join as join32 } from "node:path";
7687
7694
  import { fileURLToPath as fileURLToPath4 } from "node:url";
7688
7695
 
7696
+ // src/runs/quota-exhausted.ts
7697
+ var QUOTA_TEXT = /usage limit|hit your (?:usage )?limit|limit reached|rate limit|quota|too many requests|\b429\b/i;
7698
+ var fromEpochSeconds = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? new Date(value * 1e3).toISOString() : null;
7699
+ function mergeQuota(previous, next) {
7700
+ if (!next) return previous;
7701
+ return previous?.resetsAt && !next.resetsAt ? previous : next;
7702
+ }
7703
+ function claudeResetFromText(text3) {
7704
+ const match = /\|\s*(\d{10})\b/.exec(text3);
7705
+ return match ? fromEpochSeconds(Number(match[1])) : null;
7706
+ }
7707
+ function textOf(content) {
7708
+ if (typeof content === "string") return content;
7709
+ if (!Array.isArray(content)) return "";
7710
+ return content.map((block) => block && typeof block === "object" && block.type === "text" ? String(block.text ?? "") : "").join("\n");
7711
+ }
7712
+ function claudeQuotaSignal(message) {
7713
+ if (!message || typeof message !== "object") return null;
7714
+ const value = message;
7715
+ if (value.type === "rate_limit_event") {
7716
+ const info = value.rate_limit_info;
7717
+ if (info?.status !== "rejected") return null;
7718
+ return { resetsAt: fromEpochSeconds(info.resetsAt) ?? fromEpochSeconds(info.overageResetsAt) };
7719
+ }
7720
+ if (value.type === "assistant" && value.error === "rate_limit") {
7721
+ const text3 = textOf(value.message?.content);
7722
+ return { resetsAt: claudeResetFromText(text3) };
7723
+ }
7724
+ if (value.type === "result" && value.is_error === true) {
7725
+ const text3 = [typeof value.result === "string" ? value.result : "", ...Array.isArray(value.errors) ? value.errors.map(String) : []].join("\n");
7726
+ if (!/usage limit|hit your (?:usage )?limit|limit reached/i.test(text3)) return null;
7727
+ return { resetsAt: claudeResetFromText(text3) };
7728
+ }
7729
+ return null;
7730
+ }
7731
+ var UNIT_MS = { day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 };
7732
+ function codexResetFromText(text3, now) {
7733
+ const within = /try again in ([^.]+)/i.exec(text3);
7734
+ if (within) {
7735
+ let total = 0;
7736
+ for (const part of within[1].matchAll(/(\d+)\s*(day|hour|minute|second)s?/gi)) total += Number(part[1]) * UNIT_MS[part[2].toLowerCase()];
7737
+ return total > 0 ? new Date(now + total).toISOString() : null;
7738
+ }
7739
+ const at = /try again at ([^.]+)/i.exec(text3)?.[1]?.trim();
7740
+ if (!at) return null;
7741
+ const clock = /^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i.exec(at);
7742
+ if (clock) {
7743
+ let hours = Number(clock[1]) % (clock[3] ? 12 : 24);
7744
+ if (clock[3]?.toUpperCase() === "PM") hours += 12;
7745
+ const day = new Date(now);
7746
+ day.setHours(hours, Number(clock[2]), 0, 0);
7747
+ if (day.getTime() <= now) day.setDate(day.getDate() + 1);
7748
+ return day.toISOString();
7749
+ }
7750
+ const parsed = Date.parse(at.replace(/(\d)(st|nd|rd|th)\b/gi, "$1"));
7751
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
7752
+ }
7753
+ function codexInfoIsQuota(info) {
7754
+ if (info === "usageLimitExceeded") return true;
7755
+ if (!info || typeof info !== "object") return false;
7756
+ return Object.values(info).some((detail) => Boolean(detail && typeof detail === "object" && detail.httpStatusCode === 429));
7757
+ }
7758
+ function codexResetFromLimits(limits) {
7759
+ const full = [limits?.primary, limits?.secondary].filter((window) => window && (window.usedPercent ?? 0) >= 100);
7760
+ const resets = full.map((window) => fromEpochSeconds(window?.resetsAt)).filter((value) => Boolean(value));
7761
+ return resets.sort().at(-1) ?? null;
7762
+ }
7763
+ function codexQuotaSignal(error, limits, now = Date.now()) {
7764
+ if (!error) return null;
7765
+ const message = typeof error.message === "string" ? error.message : "";
7766
+ if (!codexInfoIsQuota(error.codexErrorInfo) && !QUOTA_TEXT.test(message)) return null;
7767
+ return { resetsAt: codexResetFromText(message, now) ?? codexResetFromLimits(limits) };
7768
+ }
7769
+ function quotaStopApplies(assignment) {
7770
+ return !assignment.modelCredential;
7771
+ }
7772
+
7689
7773
  // src/adapters/claude.ts
7690
7774
  import { spawn as spawn3 } from "node:child_process";
7691
7775
 
@@ -8435,6 +8519,7 @@ var ClaudeAdapter = class {
8435
8519
  let runtimeModel = null;
8436
8520
  let ending = false;
8437
8521
  let lastError;
8522
+ let quota;
8438
8523
  let graceTimer;
8439
8524
  let authTimer;
8440
8525
  let stderr = "";
@@ -8551,7 +8636,7 @@ var ClaudeAdapter = class {
8551
8636
  const handoff = ctx.readHandoff(log);
8552
8637
  if (handoff) return void end(handoff);
8553
8638
  void end(
8554
- lastError ? { outcome: "failed", error: lastError } : { outcome: "succeeded" }
8639
+ lastError ? { outcome: "failed", error: lastError, ...quota ? { quota } : {} } : { outcome: "succeeded" }
8555
8640
  );
8556
8641
  };
8557
8642
  const diedEarly = (error) => {
@@ -8560,7 +8645,8 @@ var ClaudeAdapter = class {
8560
8645
  queue.exit(
8561
8646
  handoff ?? {
8562
8647
  outcome: "failed",
8563
- error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`
8648
+ error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`,
8649
+ ...quota ? { quota } : {}
8564
8650
  }
8565
8651
  );
8566
8652
  };
@@ -8579,11 +8665,13 @@ var ClaudeAdapter = class {
8579
8665
  if (message.type === "assistant" || message.type === "user")
8580
8666
  clearTimeout(graceTimer);
8581
8667
  if (ending) continue;
8668
+ quota = mergeQuota(quota, claudeQuotaSignal(message));
8582
8669
  if (message.type === "tool_progress" && message.session_id === sessionId) queue.progress();
8583
8670
  if (!(externalAuth && message.type === "assistant" && message.error))
8584
8671
  for (const chunk of claudeChunks(message)) queue.chunk(chunk);
8585
8672
  if (message.type !== "result") continue;
8586
8673
  results++;
8674
+ if (!message.is_error) quota = void 0;
8587
8675
  lastError = message.is_error ? externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `Claude Code: ${resultError(message)}` : void 0;
8588
8676
  if (results >= written) {
8589
8677
  turnEnded();
@@ -8979,6 +9067,8 @@ var CodexAdapter = class {
8979
9067
  let ending = false;
8980
9068
  let cancelling = false;
8981
9069
  let lastError;
9070
+ let quota;
9071
+ let rateLimits;
8982
9072
  let usage = { inputTokens: 0, outputTokens: 0 };
8983
9073
  const pendingMcp = /* @__PURE__ */ new Map();
8984
9074
  const registeredMcpTool = (server, tool2) => server === MCP_SERVER_NAME && ["submit", "request_help", "ask_human", "report_blocked"].includes(tool2) || Boolean(computerMcp) && server === COMPUTER_MCP_SERVER_NAME && ["get_desktop_state", "click", "type_text", "press_key", "scroll"].includes(tool2) || Boolean(options.apps?.apps.some((app) => app.name === server));
@@ -9110,8 +9200,12 @@ var CodexAdapter = class {
9110
9200
  };
9111
9201
  return;
9112
9202
  }
9203
+ case "account/rateLimits/updated":
9204
+ rateLimits = params.rateLimits;
9205
+ return;
9113
9206
  case "error": {
9114
9207
  const { error, willRetry } = params;
9208
+ if (!willRetry) quota = mergeQuota(quota, codexQuotaSignal(error, rateLimits));
9115
9209
  if (!willRetry)
9116
9210
  lastError = externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : `Codex: ${error.message}`;
9117
9211
  return;
@@ -9162,8 +9256,10 @@ var CodexAdapter = class {
9162
9256
  return;
9163
9257
  }
9164
9258
  if (turn.status === "failed" || lastError) {
9259
+ quota = mergeQuota(quota, codexQuotaSignal(turn.error, rateLimits));
9165
9260
  return finish({
9166
9261
  outcome: "failed",
9262
+ ...quota ? { quota } : {},
9167
9263
  error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : turn.error?.message ? `Codex: ${turn.error.message}` : lastError
9168
9264
  });
9169
9265
  }
@@ -9173,6 +9269,7 @@ var CodexAdapter = class {
9173
9269
  let runtimeModel = null;
9174
9270
  const startTurn = async (message) => {
9175
9271
  lastError = void 0;
9272
+ quota = void 0;
9176
9273
  if (externalAuth?.verify) {
9177
9274
  try {
9178
9275
  await externalAuth.verify();
@@ -11836,6 +11933,8 @@ var MemoryServer = class {
11836
11933
  return retry("retry", MAX_CRASH_RETRIES);
11837
11934
  case "device_lost":
11838
11935
  return { owner: "external", reason: "device_offline" };
11936
+ case "quota_exhausted":
11937
+ return { owner: "external", reason: "quota_exhausted" };
11839
11938
  case "cancelled":
11840
11939
  return { owner: "human", reason: "cancelled" };
11841
11940
  }
@@ -13102,7 +13201,7 @@ var RunSupervisor = class {
13102
13201
  continue;
13103
13202
  }
13104
13203
  await this.closeMailbox(run3);
13105
- const stopReason = stopReasonOf(event, run3);
13204
+ const stopReason = stopReasonOf(event, run3, assignment);
13106
13205
  const error = event.error ?? (stopReason === "turn_ended" ? "Session ended without a submission" : stopReason === "timeout" ? "No progress before the watchdog limit" : stopReason === "device_lost" ? "Run lease expired; the server could not be reached" : void 0);
13107
13206
  if (assignment.benchmark) {
13108
13207
  const fixed = await fixedCheckSnapshot(repo.path, worktree, assignment.benchmark.sourceCommit);
@@ -13120,7 +13219,7 @@ var RunSupervisor = class {
13120
13219
  );
13121
13220
  if (this.stopping) return;
13122
13221
  const { verifyError, tree } = outcome;
13123
- let report = outcome.report;
13222
+ let report = stopReason === "quota_exhausted" ? { ...outcome.report, resetsAt: event.quota?.resetsAt ?? null } : outcome.report;
13124
13223
  const policy = runPolicy(assignment.policy);
13125
13224
  if (stopReason === "submitted" && report.verify.status !== "failed" && tree && // The draft-PR exception requires an explicit repository push
13126
13225
  // approval. It must not even create a local commit without it.
@@ -13223,11 +13322,12 @@ function pickAgentReport(agent) {
13223
13322
  }
13224
13323
  return picked;
13225
13324
  }
13226
- var KEEP_WORKTREE = ["turn_ended", "asked_human"];
13227
- function stopReasonOf(event, run3) {
13325
+ var KEEP_WORKTREE = ["turn_ended", "asked_human", "quota_exhausted"];
13326
+ function stopReasonOf(event, run3, assignment) {
13228
13327
  if (run3.forcedStop) return run3.forcedStop;
13229
13328
  if (event.outcome === "cancelled" || run3.cancelRequested) return "cancelled";
13230
13329
  if (event.stopReason) return event.stopReason;
13330
+ if (event.outcome === "failed" && event.quota && quotaStopApplies(assignment)) return "quota_exhausted";
13231
13331
  return event.outcome === "failed" ? "error" : "turn_ended";
13232
13332
  }
13233
13333
  async function unsandboxedVerify(run3) {
package/client.js CHANGED
@@ -2486,7 +2486,9 @@ var RUN_STOP_REASONS = [
2486
2486
  "cancelled",
2487
2487
  "error",
2488
2488
  "timeout",
2489
- "device_lost"
2489
+ "device_lost",
2490
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
2491
+ "quota_exhausted"
2490
2492
  ];
2491
2493
  var runStopReason = z9.enum(RUN_STOP_REASONS);
2492
2494
  var shortList = z9.array(z9.string().max(1e3)).max(5e3);
@@ -2561,6 +2563,11 @@ var runReport = z9.object({
2561
2563
  nextStep: z9.string().max(2e3).nullable(),
2562
2564
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
2563
2565
  blockedOn: z9.string().max(500).optional(),
2566
+ /**
2567
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
2568
+ * reported it; null when it gave no time. Set by the daemon only.
2569
+ */
2570
+ resetsAt: z9.iso.datetime({ offset: true }).nullable().optional(),
2564
2571
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
2565
2572
  check: z9.object({
2566
2573
  sha: z9.string().regex(/^[a-f0-9]{40}$/),
@@ -3734,7 +3741,7 @@ import { join } from "node:path";
3734
3741
  // package.json
3735
3742
  var package_default = {
3736
3743
  name: "@mikka/daemon",
3737
- version: "0.0.144",
3744
+ version: "0.0.145",
3738
3745
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
3739
3746
  type: "module",
3740
3747
  private: true,
@@ -3794,7 +3801,7 @@ var package_default = {
3794
3801
  engines: {
3795
3802
  node: ">=24"
3796
3803
  },
3797
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
3804
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
3798
3805
  };
3799
3806
 
3800
3807
  // src/paths.ts
@@ -0,0 +1,5 @@
1
+ {"t":0,"chunk":{"type":"start","messageId":"msg-quota-exhausted-1"}}
2
+ {"t":100,"chunk":{"type":"text-start","id":"txt-1"}}
3
+ {"t":160,"chunk":{"type":"text-delta","id":"txt-1","delta":"Updating the parser."}}
4
+ {"t":200,"chunk":{"type":"text-end","id":"txt-1"}}
5
+ {"t":300,"exit":{"outcome":"failed","error":"Claude Code: Claude AI usage limit reached|1762016400","quota":{"resetsAt":"2025-11-01T17:00:00.000Z"}}}
package/http-link.js CHANGED
@@ -3078,7 +3078,9 @@ var RUN_STOP_REASONS = [
3078
3078
  "cancelled",
3079
3079
  "error",
3080
3080
  "timeout",
3081
- "device_lost"
3081
+ "device_lost",
3082
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
3083
+ "quota_exhausted"
3082
3084
  ];
3083
3085
  var runStopReason = z16.enum(RUN_STOP_REASONS);
3084
3086
  var shortList = z16.array(z16.string().max(1e3)).max(5e3);
@@ -3153,6 +3155,11 @@ var runReport = z16.object({
3153
3155
  nextStep: z16.string().max(2e3).nullable(),
3154
3156
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
3155
3157
  blockedOn: z16.string().max(500).optional(),
3158
+ /**
3159
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
3160
+ * reported it; null when it gave no time. Set by the daemon only.
3161
+ */
3162
+ resetsAt: z16.iso.datetime({ offset: true }).nullable().optional(),
3156
3163
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
3157
3164
  check: z16.object({
3158
3165
  sha: z16.string().regex(/^[a-f0-9]{40}$/),
@@ -4137,7 +4144,7 @@ var InvalidIdError = class extends DaemonError {
4137
4144
  // package.json
4138
4145
  var package_default = {
4139
4146
  name: "@mikka/daemon",
4140
- version: "0.0.144",
4147
+ version: "0.0.145",
4141
4148
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
4142
4149
  type: "module",
4143
4150
  private: true,
@@ -4197,7 +4204,7 @@ var package_default = {
4197
4204
  engines: {
4198
4205
  node: ">=24"
4199
4206
  },
4200
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
4207
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
4201
4208
  };
4202
4209
 
4203
4210
  // src/paths.ts
package/login.js CHANGED
@@ -2483,7 +2483,9 @@ var RUN_STOP_REASONS = [
2483
2483
  "cancelled",
2484
2484
  "error",
2485
2485
  "timeout",
2486
- "device_lost"
2486
+ "device_lost",
2487
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
2488
+ "quota_exhausted"
2487
2489
  ];
2488
2490
  var runStopReason = z9.enum(RUN_STOP_REASONS);
2489
2491
  var shortList = z9.array(z9.string().max(1e3)).max(5e3);
@@ -2558,6 +2560,11 @@ var runReport = z9.object({
2558
2560
  nextStep: z9.string().max(2e3).nullable(),
2559
2561
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
2560
2562
  blockedOn: z9.string().max(500).optional(),
2563
+ /**
2564
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
2565
+ * reported it; null when it gave no time. Set by the daemon only.
2566
+ */
2567
+ resetsAt: z9.iso.datetime({ offset: true }).nullable().optional(),
2561
2568
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
2562
2569
  check: z9.object({
2563
2570
  sha: z9.string().regex(/^[a-f0-9]{40}$/),
@@ -3741,7 +3748,7 @@ var ServerUnreachableError = class extends DaemonError {
3741
3748
  // package.json
3742
3749
  var package_default = {
3743
3750
  name: "@mikka/daemon",
3744
- version: "0.0.144",
3751
+ version: "0.0.145",
3745
3752
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
3746
3753
  type: "module",
3747
3754
  private: true,
@@ -3801,7 +3808,7 @@ var package_default = {
3801
3808
  engines: {
3802
3809
  node: ">=24"
3803
3810
  },
3804
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
3811
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
3805
3812
  };
3806
3813
 
3807
3814
  // src/paths.ts
package/main.js CHANGED
@@ -3521,7 +3521,9 @@ var RUN_STOP_REASONS = [
3521
3521
  "cancelled",
3522
3522
  "error",
3523
3523
  "timeout",
3524
- "device_lost"
3524
+ "device_lost",
3525
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
3526
+ "quota_exhausted"
3525
3527
  ];
3526
3528
  var runStopReason = z4.enum(RUN_STOP_REASONS);
3527
3529
  var shortList = z4.array(z4.string().max(1e3)).max(5e3);
@@ -3596,6 +3598,11 @@ var runReport = z4.object({
3596
3598
  nextStep: z4.string().max(2e3).nullable(),
3597
3599
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
3598
3600
  blockedOn: z4.string().max(500).optional(),
3601
+ /**
3602
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
3603
+ * reported it; null when it gave no time. Set by the daemon only.
3604
+ */
3605
+ resetsAt: z4.iso.datetime({ offset: true }).nullable().optional(),
3599
3606
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
3600
3607
  check: z4.object({
3601
3608
  sha: z4.string().regex(/^[a-f0-9]{40}$/),
@@ -3780,7 +3787,7 @@ import { join as join5 } from "node:path";
3780
3787
  // package.json
3781
3788
  var package_default = {
3782
3789
  name: "@mikka/daemon",
3783
- version: "0.0.144",
3790
+ version: "0.0.145",
3784
3791
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
3785
3792
  type: "module",
3786
3793
  private: true,
@@ -3840,7 +3847,7 @@ var package_default = {
3840
3847
  engines: {
3841
3848
  node: ">=24"
3842
3849
  },
3843
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
3850
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
3844
3851
  };
3845
3852
 
3846
3853
  // src/paths.ts
@@ -6471,6 +6478,83 @@ import { arch as arch2, hostname, platform as platform3 } from "node:os";
6471
6478
  import { join as join32 } from "node:path";
6472
6479
  import { fileURLToPath as fileURLToPath4 } from "node:url";
6473
6480
 
6481
+ // src/runs/quota-exhausted.ts
6482
+ var QUOTA_TEXT = /usage limit|hit your (?:usage )?limit|limit reached|rate limit|quota|too many requests|\b429\b/i;
6483
+ var fromEpochSeconds = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? new Date(value * 1e3).toISOString() : null;
6484
+ function mergeQuota(previous, next) {
6485
+ if (!next) return previous;
6486
+ return previous?.resetsAt && !next.resetsAt ? previous : next;
6487
+ }
6488
+ function claudeResetFromText(text3) {
6489
+ const match = /\|\s*(\d{10})\b/.exec(text3);
6490
+ return match ? fromEpochSeconds(Number(match[1])) : null;
6491
+ }
6492
+ function textOf(content) {
6493
+ if (typeof content === "string") return content;
6494
+ if (!Array.isArray(content)) return "";
6495
+ return content.map((block) => block && typeof block === "object" && block.type === "text" ? String(block.text ?? "") : "").join("\n");
6496
+ }
6497
+ function claudeQuotaSignal(message) {
6498
+ if (!message || typeof message !== "object") return null;
6499
+ const value = message;
6500
+ if (value.type === "rate_limit_event") {
6501
+ const info = value.rate_limit_info;
6502
+ if (info?.status !== "rejected") return null;
6503
+ return { resetsAt: fromEpochSeconds(info.resetsAt) ?? fromEpochSeconds(info.overageResetsAt) };
6504
+ }
6505
+ if (value.type === "assistant" && value.error === "rate_limit") {
6506
+ const text3 = textOf(value.message?.content);
6507
+ return { resetsAt: claudeResetFromText(text3) };
6508
+ }
6509
+ if (value.type === "result" && value.is_error === true) {
6510
+ const text3 = [typeof value.result === "string" ? value.result : "", ...Array.isArray(value.errors) ? value.errors.map(String) : []].join("\n");
6511
+ if (!/usage limit|hit your (?:usage )?limit|limit reached/i.test(text3)) return null;
6512
+ return { resetsAt: claudeResetFromText(text3) };
6513
+ }
6514
+ return null;
6515
+ }
6516
+ var UNIT_MS = { day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 };
6517
+ function codexResetFromText(text3, now) {
6518
+ const within = /try again in ([^.]+)/i.exec(text3);
6519
+ if (within) {
6520
+ let total = 0;
6521
+ for (const part of within[1].matchAll(/(\d+)\s*(day|hour|minute|second)s?/gi)) total += Number(part[1]) * UNIT_MS[part[2].toLowerCase()];
6522
+ return total > 0 ? new Date(now + total).toISOString() : null;
6523
+ }
6524
+ const at = /try again at ([^.]+)/i.exec(text3)?.[1]?.trim();
6525
+ if (!at) return null;
6526
+ const clock = /^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i.exec(at);
6527
+ if (clock) {
6528
+ let hours = Number(clock[1]) % (clock[3] ? 12 : 24);
6529
+ if (clock[3]?.toUpperCase() === "PM") hours += 12;
6530
+ const day = new Date(now);
6531
+ day.setHours(hours, Number(clock[2]), 0, 0);
6532
+ if (day.getTime() <= now) day.setDate(day.getDate() + 1);
6533
+ return day.toISOString();
6534
+ }
6535
+ const parsed = Date.parse(at.replace(/(\d)(st|nd|rd|th)\b/gi, "$1"));
6536
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
6537
+ }
6538
+ function codexInfoIsQuota(info) {
6539
+ if (info === "usageLimitExceeded") return true;
6540
+ if (!info || typeof info !== "object") return false;
6541
+ return Object.values(info).some((detail) => Boolean(detail && typeof detail === "object" && detail.httpStatusCode === 429));
6542
+ }
6543
+ function codexResetFromLimits(limits) {
6544
+ const full = [limits?.primary, limits?.secondary].filter((window) => window && (window.usedPercent ?? 0) >= 100);
6545
+ const resets = full.map((window) => fromEpochSeconds(window?.resetsAt)).filter((value) => Boolean(value));
6546
+ return resets.sort().at(-1) ?? null;
6547
+ }
6548
+ function codexQuotaSignal(error, limits, now = Date.now()) {
6549
+ if (!error) return null;
6550
+ const message = typeof error.message === "string" ? error.message : "";
6551
+ if (!codexInfoIsQuota(error.codexErrorInfo) && !QUOTA_TEXT.test(message)) return null;
6552
+ return { resetsAt: codexResetFromText(message, now) ?? codexResetFromLimits(limits) };
6553
+ }
6554
+ function quotaStopApplies(assignment) {
6555
+ return !assignment.modelCredential;
6556
+ }
6557
+
6474
6558
  // src/adapters/claude.ts
6475
6559
  import { spawn as spawn3 } from "node:child_process";
6476
6560
 
@@ -7255,6 +7339,7 @@ var ClaudeAdapter = class {
7255
7339
  let runtimeModel = null;
7256
7340
  let ending = false;
7257
7341
  let lastError;
7342
+ let quota;
7258
7343
  let graceTimer;
7259
7344
  let authTimer;
7260
7345
  let stderr = "";
@@ -7371,7 +7456,7 @@ var ClaudeAdapter = class {
7371
7456
  const handoff = ctx.readHandoff(log);
7372
7457
  if (handoff) return void end(handoff);
7373
7458
  void end(
7374
- lastError ? { outcome: "failed", error: lastError } : { outcome: "succeeded" }
7459
+ lastError ? { outcome: "failed", error: lastError, ...quota ? { quota } : {} } : { outcome: "succeeded" }
7375
7460
  );
7376
7461
  };
7377
7462
  const diedEarly = (error) => {
@@ -7380,7 +7465,8 @@ var ClaudeAdapter = class {
7380
7465
  queue.exit(
7381
7466
  handoff ?? {
7382
7467
  outcome: "failed",
7383
- error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`
7468
+ error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`,
7469
+ ...quota ? { quota } : {}
7384
7470
  }
7385
7471
  );
7386
7472
  };
@@ -7399,11 +7485,13 @@ var ClaudeAdapter = class {
7399
7485
  if (message.type === "assistant" || message.type === "user")
7400
7486
  clearTimeout(graceTimer);
7401
7487
  if (ending) continue;
7488
+ quota = mergeQuota(quota, claudeQuotaSignal(message));
7402
7489
  if (message.type === "tool_progress" && message.session_id === sessionId) queue.progress();
7403
7490
  if (!(externalAuth && message.type === "assistant" && message.error))
7404
7491
  for (const chunk of claudeChunks(message)) queue.chunk(chunk);
7405
7492
  if (message.type !== "result") continue;
7406
7493
  results++;
7494
+ if (!message.is_error) quota = void 0;
7407
7495
  lastError = message.is_error ? externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `Claude Code: ${resultError(message)}` : void 0;
7408
7496
  if (results >= written) {
7409
7497
  turnEnded();
@@ -7799,6 +7887,8 @@ var CodexAdapter = class {
7799
7887
  let ending = false;
7800
7888
  let cancelling = false;
7801
7889
  let lastError;
7890
+ let quota;
7891
+ let rateLimits;
7802
7892
  let usage = { inputTokens: 0, outputTokens: 0 };
7803
7893
  const pendingMcp = /* @__PURE__ */ new Map();
7804
7894
  const registeredMcpTool = (server, tool2) => server === MCP_SERVER_NAME && ["submit", "request_help", "ask_human", "report_blocked"].includes(tool2) || Boolean(computerMcp) && server === COMPUTER_MCP_SERVER_NAME && ["get_desktop_state", "click", "type_text", "press_key", "scroll"].includes(tool2) || Boolean(options.apps?.apps.some((app) => app.name === server));
@@ -7930,8 +8020,12 @@ var CodexAdapter = class {
7930
8020
  };
7931
8021
  return;
7932
8022
  }
8023
+ case "account/rateLimits/updated":
8024
+ rateLimits = params.rateLimits;
8025
+ return;
7933
8026
  case "error": {
7934
8027
  const { error, willRetry } = params;
8028
+ if (!willRetry) quota = mergeQuota(quota, codexQuotaSignal(error, rateLimits));
7935
8029
  if (!willRetry)
7936
8030
  lastError = externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : `Codex: ${error.message}`;
7937
8031
  return;
@@ -7982,8 +8076,10 @@ var CodexAdapter = class {
7982
8076
  return;
7983
8077
  }
7984
8078
  if (turn.status === "failed" || lastError) {
8079
+ quota = mergeQuota(quota, codexQuotaSignal(turn.error, rateLimits));
7985
8080
  return finish({
7986
8081
  outcome: "failed",
8082
+ ...quota ? { quota } : {},
7987
8083
  error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : turn.error?.message ? `Codex: ${turn.error.message}` : lastError
7988
8084
  });
7989
8085
  }
@@ -7993,6 +8089,7 @@ var CodexAdapter = class {
7993
8089
  let runtimeModel = null;
7994
8090
  const startTurn = async (message) => {
7995
8091
  lastError = void 0;
8092
+ quota = void 0;
7996
8093
  if (externalAuth?.verify) {
7997
8094
  try {
7998
8095
  await externalAuth.verify();
@@ -10897,6 +10994,8 @@ var MemoryServer = class {
10897
10994
  return retry("retry", MAX_CRASH_RETRIES);
10898
10995
  case "device_lost":
10899
10996
  return { owner: "external", reason: "device_offline" };
10997
+ case "quota_exhausted":
10998
+ return { owner: "external", reason: "quota_exhausted" };
10900
10999
  case "cancelled":
10901
11000
  return { owner: "human", reason: "cancelled" };
10902
11001
  }
@@ -12163,7 +12262,7 @@ var RunSupervisor = class {
12163
12262
  continue;
12164
12263
  }
12165
12264
  await this.closeMailbox(run3);
12166
- const stopReason = stopReasonOf(event, run3);
12265
+ const stopReason = stopReasonOf(event, run3, assignment);
12167
12266
  const error = event.error ?? (stopReason === "turn_ended" ? "Session ended without a submission" : stopReason === "timeout" ? "No progress before the watchdog limit" : stopReason === "device_lost" ? "Run lease expired; the server could not be reached" : void 0);
12168
12267
  if (assignment.benchmark) {
12169
12268
  const fixed = await fixedCheckSnapshot(repo.path, worktree, assignment.benchmark.sourceCommit);
@@ -12181,7 +12280,7 @@ var RunSupervisor = class {
12181
12280
  );
12182
12281
  if (this.stopping) return;
12183
12282
  const { verifyError, tree } = outcome;
12184
- let report = outcome.report;
12283
+ let report = stopReason === "quota_exhausted" ? { ...outcome.report, resetsAt: event.quota?.resetsAt ?? null } : outcome.report;
12185
12284
  const policy = runPolicy(assignment.policy);
12186
12285
  if (stopReason === "submitted" && report.verify.status !== "failed" && tree && // The draft-PR exception requires an explicit repository push
12187
12286
  // approval. It must not even create a local commit without it.
@@ -12284,11 +12383,12 @@ function pickAgentReport(agent) {
12284
12383
  }
12285
12384
  return picked;
12286
12385
  }
12287
- var KEEP_WORKTREE = ["turn_ended", "asked_human"];
12288
- function stopReasonOf(event, run3) {
12386
+ var KEEP_WORKTREE = ["turn_ended", "asked_human", "quota_exhausted"];
12387
+ function stopReasonOf(event, run3, assignment) {
12289
12388
  if (run3.forcedStop) return run3.forcedStop;
12290
12389
  if (event.outcome === "cancelled" || run3.cancelRequested) return "cancelled";
12291
12390
  if (event.stopReason) return event.stopReason;
12391
+ if (event.outcome === "failed" && event.quota && quotaStopApplies(assignment)) return "quota_exhausted";
12292
12392
  return event.outcome === "failed" ? "error" : "turn_ended";
12293
12393
  }
12294
12394
  async function unsandboxedVerify(run3) {
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "version": 1,
3
- "sourceSha": "c2137052dcc3f840410efcd3059fc1ce04916a6f",
3
+ "sourceSha": "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96",
4
4
  "repository": "alexj11324/Mikka",
5
- "buildRunId": "41278753662",
5
+ "buildRunId": "41279158266",
6
6
  "buildKind": "github-actions-main",
7
7
  "sourceLockSha256": "c99f1e4f641d8e004cfe807653478e10f91e53fcf47102add5f4c154cdc1cb57",
8
- "createdAt": "2026-11-01T05:57:34.389Z",
8
+ "createdAt": "2026-11-01T06:03:29.691Z",
9
9
  "sbom": "sbom.cdx.json",
10
10
  "files": [
11
11
  {
@@ -40,13 +40,13 @@
40
40
  },
41
41
  {
42
42
  "path": "cli.js",
43
- "size": 643746,
44
- "sha256": "ab5227d883df710912ccd270af9ddb4b6cd4fbfc30af405640220d3aa5eb3846"
43
+ "size": 648726,
44
+ "sha256": "75bb140b3934ad48cc3309c35b8af9b35dbfd51bac9c2836748a82419d18e00c"
45
45
  },
46
46
  {
47
47
  "path": "client.js",
48
- "size": 140876,
49
- "sha256": "ff6223430bdf792dc2ae4a49c6e99c88516f0cc36c2ac0147494c413a81e1ac3"
48
+ "size": 141206,
49
+ "sha256": "78aa71d0aee9887bd1ed8a02fd06b72d024cc68581638c562af39fe745f82b08"
50
50
  },
51
51
  {
52
52
  "path": "computer/driver.mjs",
@@ -103,6 +103,11 @@
103
103
  "size": 2361,
104
104
  "sha256": "86e5681f2aad82629db9e8a4dd7bb6dcee11c008118277d47dcf0d94c8483110"
105
105
  },
106
+ {
107
+ "path": "fixtures/quota-exhausted.jsonl",
108
+ "size": 407,
109
+ "sha256": "d32b9fc7c6778db3cfa1612e80318fa09aef66eb827c1acee271b598ab4ef2fe"
110
+ },
106
111
  {
107
112
  "path": "fixtures/quota.jsonl",
108
113
  "size": 594,
@@ -170,8 +175,8 @@
170
175
  },
171
176
  {
172
177
  "path": "http-link.js",
173
- "size": 182219,
174
- "sha256": "3fcbcb5d64a05f836493c313b486722d0de45ce34fbcf692ac5ed2c3e4d70ecf"
178
+ "size": 182550,
179
+ "sha256": "822fc4b32b591cf8e82b4d8ec6c128abe804e3e2759a399580ac6072bbaf88a6"
175
180
  },
176
181
  {
177
182
  "path": "log.js",
@@ -180,13 +185,13 @@
180
185
  },
181
186
  {
182
187
  "path": "login.js",
183
- "size": 145611,
184
- "sha256": "9cb6368ff0b28fc371f2561ce390cd0a2d06a66fb55977c4c7a5b7b343b61a99"
188
+ "size": 145941,
189
+ "sha256": "10725733ab5d116ef38881e5d534931c1795e2ee256175cef91cc7d43f8ab375"
185
190
  },
186
191
  {
187
192
  "path": "main.js",
188
- "size": 589272,
189
- "sha256": "6b4e4ff1ccb8af34958bd06451f21a914bc61d61cdf44f33ac6814ea4b84f2e3"
193
+ "size": 594252,
194
+ "sha256": "e861785ccb1833941c63f3763bd037ef6546a42f80da15c6164490f6ac10f8d5"
190
195
  },
191
196
  {
192
197
  "path": "mikka-mcp.mjs",
@@ -195,23 +200,23 @@
195
200
  },
196
201
  {
197
202
  "path": "mikka.js",
198
- "size": 643762,
199
- "sha256": "1d0e8add3a587eb83d71c11590f2f4ac76a58d8354b8ebe2e2482d859a263376"
203
+ "size": 648742,
204
+ "sha256": "6a29c5f06de35e97ba744d90e2dc787ec720a3d80cde5668b90a9c9188313b6f"
200
205
  },
201
206
  {
202
207
  "path": "npm-shrinkwrap.json",
203
208
  "size": 78117,
204
- "sha256": "4266b3a63a8bf123310fc129b8a33c98abfd0850dba432d1416d98ff5e4e6e67"
209
+ "sha256": "1c5bbc1604bc104d95762ccf4c65e85dd51b3cfe2544bd0fdc9b67ebd9ed7b79"
205
210
  },
206
211
  {
207
212
  "path": "package.json",
208
213
  "size": 1247,
209
- "sha256": "f2a82c169175ead2e1a9819b22ba58709168886e7f9bede029897a233dbcc012"
214
+ "sha256": "daa3570756f78b62d93854096b5875d092b40b8546a8414669c39ace6e9c8210"
210
215
  },
211
216
  {
212
217
  "path": "paths.js",
213
218
  "size": 4138,
214
- "sha256": "05b7da125558b6c4cae1e258f34436c0f1d1d50825f4333f35ed997efb336a6d"
219
+ "sha256": "aa1694df2bdc6c01884e3e6b791d77391f6fb6af8bde0b0b46489717dd3e1cbc"
215
220
  },
216
221
  {
217
222
  "path": "sandbox.js",
@@ -221,7 +226,7 @@
221
226
  {
222
227
  "path": "sbom.cdx.json",
223
228
  "size": 144314,
224
- "sha256": "fa9eb45864a49992203db42dbb080bb97720ff44654ab3e73de5fe0328dc343f"
229
+ "sha256": "0a1db55babf766e413dcab8f751c1388fdc6b15ab8001b326708678737f6c5ef"
225
230
  }
226
231
  ]
227
232
  }
package/mikka.js CHANGED
@@ -3003,7 +3003,9 @@ var RUN_STOP_REASONS = [
3003
3003
  "cancelled",
3004
3004
  "error",
3005
3005
  "timeout",
3006
- "device_lost"
3006
+ "device_lost",
3007
+ /** The subscription's quota or rate limit ran out mid-run (see `resetsAt`). */
3008
+ "quota_exhausted"
3007
3009
  ];
3008
3010
  var runStopReason = z9.enum(RUN_STOP_REASONS);
3009
3011
  var shortList = z9.array(z9.string().max(1e3)).max(5e3);
@@ -3078,6 +3080,11 @@ var runReport = z9.object({
3078
3080
  nextStep: z9.string().max(2e3).nullable(),
3079
3081
  /** For `blocked`: the external condition, e.g. `quota_exhausted`. */
3080
3082
  blockedOn: z9.string().max(500).optional(),
3083
+ /**
3084
+ * For `quota_exhausted`: when the subscription's quota resets, as the CLI
3085
+ * reported it; null when it gave no time. Set by the daemon only.
3086
+ */
3087
+ resetsAt: z9.iso.datetime({ offset: true }).nullable().optional(),
3081
3088
  /** Daemon-owned receipt for an exact, unchanged read-only candidate. */
3082
3089
  check: z9.object({
3083
3090
  sha: z9.string().regex(/^[a-f0-9]{40}$/),
@@ -4474,7 +4481,7 @@ import { join as join4 } from "node:path";
4474
4481
  // package.json
4475
4482
  var package_default = {
4476
4483
  name: "@mikka/daemon",
4477
- version: "0.0.144",
4484
+ version: "0.0.145",
4478
4485
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
4479
4486
  type: "module",
4480
4487
  private: true,
@@ -4534,7 +4541,7 @@ var package_default = {
4534
4541
  engines: {
4535
4542
  node: ">=24"
4536
4543
  },
4537
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
4544
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
4538
4545
  };
4539
4546
 
4540
4547
  // src/paths.ts
@@ -7687,6 +7694,83 @@ import { arch as arch2, hostname, platform as platform3 } from "node:os";
7687
7694
  import { join as join32 } from "node:path";
7688
7695
  import { fileURLToPath as fileURLToPath4 } from "node:url";
7689
7696
 
7697
+ // src/runs/quota-exhausted.ts
7698
+ var QUOTA_TEXT = /usage limit|hit your (?:usage )?limit|limit reached|rate limit|quota|too many requests|\b429\b/i;
7699
+ var fromEpochSeconds = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? new Date(value * 1e3).toISOString() : null;
7700
+ function mergeQuota(previous, next) {
7701
+ if (!next) return previous;
7702
+ return previous?.resetsAt && !next.resetsAt ? previous : next;
7703
+ }
7704
+ function claudeResetFromText(text3) {
7705
+ const match = /\|\s*(\d{10})\b/.exec(text3);
7706
+ return match ? fromEpochSeconds(Number(match[1])) : null;
7707
+ }
7708
+ function textOf(content) {
7709
+ if (typeof content === "string") return content;
7710
+ if (!Array.isArray(content)) return "";
7711
+ return content.map((block) => block && typeof block === "object" && block.type === "text" ? String(block.text ?? "") : "").join("\n");
7712
+ }
7713
+ function claudeQuotaSignal(message) {
7714
+ if (!message || typeof message !== "object") return null;
7715
+ const value = message;
7716
+ if (value.type === "rate_limit_event") {
7717
+ const info = value.rate_limit_info;
7718
+ if (info?.status !== "rejected") return null;
7719
+ return { resetsAt: fromEpochSeconds(info.resetsAt) ?? fromEpochSeconds(info.overageResetsAt) };
7720
+ }
7721
+ if (value.type === "assistant" && value.error === "rate_limit") {
7722
+ const text3 = textOf(value.message?.content);
7723
+ return { resetsAt: claudeResetFromText(text3) };
7724
+ }
7725
+ if (value.type === "result" && value.is_error === true) {
7726
+ const text3 = [typeof value.result === "string" ? value.result : "", ...Array.isArray(value.errors) ? value.errors.map(String) : []].join("\n");
7727
+ if (!/usage limit|hit your (?:usage )?limit|limit reached/i.test(text3)) return null;
7728
+ return { resetsAt: claudeResetFromText(text3) };
7729
+ }
7730
+ return null;
7731
+ }
7732
+ var UNIT_MS = { day: 864e5, hour: 36e5, minute: 6e4, second: 1e3 };
7733
+ function codexResetFromText(text3, now) {
7734
+ const within = /try again in ([^.]+)/i.exec(text3);
7735
+ if (within) {
7736
+ let total = 0;
7737
+ for (const part of within[1].matchAll(/(\d+)\s*(day|hour|minute|second)s?/gi)) total += Number(part[1]) * UNIT_MS[part[2].toLowerCase()];
7738
+ return total > 0 ? new Date(now + total).toISOString() : null;
7739
+ }
7740
+ const at = /try again at ([^.]+)/i.exec(text3)?.[1]?.trim();
7741
+ if (!at) return null;
7742
+ const clock = /^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i.exec(at);
7743
+ if (clock) {
7744
+ let hours = Number(clock[1]) % (clock[3] ? 12 : 24);
7745
+ if (clock[3]?.toUpperCase() === "PM") hours += 12;
7746
+ const day = new Date(now);
7747
+ day.setHours(hours, Number(clock[2]), 0, 0);
7748
+ if (day.getTime() <= now) day.setDate(day.getDate() + 1);
7749
+ return day.toISOString();
7750
+ }
7751
+ const parsed = Date.parse(at.replace(/(\d)(st|nd|rd|th)\b/gi, "$1"));
7752
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
7753
+ }
7754
+ function codexInfoIsQuota(info) {
7755
+ if (info === "usageLimitExceeded") return true;
7756
+ if (!info || typeof info !== "object") return false;
7757
+ return Object.values(info).some((detail) => Boolean(detail && typeof detail === "object" && detail.httpStatusCode === 429));
7758
+ }
7759
+ function codexResetFromLimits(limits) {
7760
+ const full = [limits?.primary, limits?.secondary].filter((window) => window && (window.usedPercent ?? 0) >= 100);
7761
+ const resets = full.map((window) => fromEpochSeconds(window?.resetsAt)).filter((value) => Boolean(value));
7762
+ return resets.sort().at(-1) ?? null;
7763
+ }
7764
+ function codexQuotaSignal(error, limits, now = Date.now()) {
7765
+ if (!error) return null;
7766
+ const message = typeof error.message === "string" ? error.message : "";
7767
+ if (!codexInfoIsQuota(error.codexErrorInfo) && !QUOTA_TEXT.test(message)) return null;
7768
+ return { resetsAt: codexResetFromText(message, now) ?? codexResetFromLimits(limits) };
7769
+ }
7770
+ function quotaStopApplies(assignment) {
7771
+ return !assignment.modelCredential;
7772
+ }
7773
+
7690
7774
  // src/adapters/claude.ts
7691
7775
  import { spawn as spawn3 } from "node:child_process";
7692
7776
 
@@ -8436,6 +8520,7 @@ var ClaudeAdapter = class {
8436
8520
  let runtimeModel = null;
8437
8521
  let ending = false;
8438
8522
  let lastError;
8523
+ let quota;
8439
8524
  let graceTimer;
8440
8525
  let authTimer;
8441
8526
  let stderr = "";
@@ -8552,7 +8637,7 @@ var ClaudeAdapter = class {
8552
8637
  const handoff = ctx.readHandoff(log);
8553
8638
  if (handoff) return void end(handoff);
8554
8639
  void end(
8555
- lastError ? { outcome: "failed", error: lastError } : { outcome: "succeeded" }
8640
+ lastError ? { outcome: "failed", error: lastError, ...quota ? { quota } : {} } : { outcome: "succeeded" }
8556
8641
  );
8557
8642
  };
8558
8643
  const diedEarly = (error) => {
@@ -8561,7 +8646,8 @@ var ClaudeAdapter = class {
8561
8646
  queue.exit(
8562
8647
  handoff ?? {
8563
8648
  outcome: "failed",
8564
- error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`
8649
+ error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `claude exited before the turn ended: ${error}`,
8650
+ ...quota ? { quota } : {}
8565
8651
  }
8566
8652
  );
8567
8653
  };
@@ -8580,11 +8666,13 @@ var ClaudeAdapter = class {
8580
8666
  if (message.type === "assistant" || message.type === "user")
8581
8667
  clearTimeout(graceTimer);
8582
8668
  if (ending) continue;
8669
+ quota = mergeQuota(quota, claudeQuotaSignal(message));
8583
8670
  if (message.type === "tool_progress" && message.session_id === sessionId) queue.progress();
8584
8671
  if (!(externalAuth && message.type === "assistant" && message.error))
8585
8672
  for (const chunk of claudeChunks(message)) queue.chunk(chunk);
8586
8673
  if (message.type !== "result") continue;
8587
8674
  results++;
8675
+ if (!message.is_error) quota = void 0;
8588
8676
  lastError = message.is_error ? externalAuth ? SUBSCRIPTION_EXECUTION_ERROR : `Claude Code: ${resultError(message)}` : void 0;
8589
8677
  if (results >= written) {
8590
8678
  turnEnded();
@@ -8980,6 +9068,8 @@ var CodexAdapter = class {
8980
9068
  let ending = false;
8981
9069
  let cancelling = false;
8982
9070
  let lastError;
9071
+ let quota;
9072
+ let rateLimits;
8983
9073
  let usage = { inputTokens: 0, outputTokens: 0 };
8984
9074
  const pendingMcp = /* @__PURE__ */ new Map();
8985
9075
  const registeredMcpTool = (server, tool2) => server === MCP_SERVER_NAME && ["submit", "request_help", "ask_human", "report_blocked"].includes(tool2) || Boolean(computerMcp) && server === COMPUTER_MCP_SERVER_NAME && ["get_desktop_state", "click", "type_text", "press_key", "scroll"].includes(tool2) || Boolean(options.apps?.apps.some((app) => app.name === server));
@@ -9111,8 +9201,12 @@ var CodexAdapter = class {
9111
9201
  };
9112
9202
  return;
9113
9203
  }
9204
+ case "account/rateLimits/updated":
9205
+ rateLimits = params.rateLimits;
9206
+ return;
9114
9207
  case "error": {
9115
9208
  const { error, willRetry } = params;
9209
+ if (!willRetry) quota = mergeQuota(quota, codexQuotaSignal(error, rateLimits));
9116
9210
  if (!willRetry)
9117
9211
  lastError = externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : `Codex: ${error.message}`;
9118
9212
  return;
@@ -9163,8 +9257,10 @@ var CodexAdapter = class {
9163
9257
  return;
9164
9258
  }
9165
9259
  if (turn.status === "failed" || lastError) {
9260
+ quota = mergeQuota(quota, codexQuotaSignal(turn.error, rateLimits));
9166
9261
  return finish({
9167
9262
  outcome: "failed",
9263
+ ...quota ? { quota } : {},
9168
9264
  error: externalAuth ? SUBSCRIPTION_EXECUTION_ERROR2 : turn.error?.message ? `Codex: ${turn.error.message}` : lastError
9169
9265
  });
9170
9266
  }
@@ -9174,6 +9270,7 @@ var CodexAdapter = class {
9174
9270
  let runtimeModel = null;
9175
9271
  const startTurn = async (message) => {
9176
9272
  lastError = void 0;
9273
+ quota = void 0;
9177
9274
  if (externalAuth?.verify) {
9178
9275
  try {
9179
9276
  await externalAuth.verify();
@@ -11837,6 +11934,8 @@ var MemoryServer = class {
11837
11934
  return retry("retry", MAX_CRASH_RETRIES);
11838
11935
  case "device_lost":
11839
11936
  return { owner: "external", reason: "device_offline" };
11937
+ case "quota_exhausted":
11938
+ return { owner: "external", reason: "quota_exhausted" };
11840
11939
  case "cancelled":
11841
11940
  return { owner: "human", reason: "cancelled" };
11842
11941
  }
@@ -13103,7 +13202,7 @@ var RunSupervisor = class {
13103
13202
  continue;
13104
13203
  }
13105
13204
  await this.closeMailbox(run3);
13106
- const stopReason = stopReasonOf(event, run3);
13205
+ const stopReason = stopReasonOf(event, run3, assignment);
13107
13206
  const error = event.error ?? (stopReason === "turn_ended" ? "Session ended without a submission" : stopReason === "timeout" ? "No progress before the watchdog limit" : stopReason === "device_lost" ? "Run lease expired; the server could not be reached" : void 0);
13108
13207
  if (assignment.benchmark) {
13109
13208
  const fixed = await fixedCheckSnapshot(repo.path, worktree, assignment.benchmark.sourceCommit);
@@ -13121,7 +13220,7 @@ var RunSupervisor = class {
13121
13220
  );
13122
13221
  if (this.stopping) return;
13123
13222
  const { verifyError, tree } = outcome;
13124
- let report = outcome.report;
13223
+ let report = stopReason === "quota_exhausted" ? { ...outcome.report, resetsAt: event.quota?.resetsAt ?? null } : outcome.report;
13125
13224
  const policy = runPolicy(assignment.policy);
13126
13225
  if (stopReason === "submitted" && report.verify.status !== "failed" && tree && // The draft-PR exception requires an explicit repository push
13127
13226
  // approval. It must not even create a local commit without it.
@@ -13224,11 +13323,12 @@ function pickAgentReport(agent) {
13224
13323
  }
13225
13324
  return picked;
13226
13325
  }
13227
- var KEEP_WORKTREE = ["turn_ended", "asked_human"];
13228
- function stopReasonOf(event, run3) {
13326
+ var KEEP_WORKTREE = ["turn_ended", "asked_human", "quota_exhausted"];
13327
+ function stopReasonOf(event, run3, assignment) {
13229
13328
  if (run3.forcedStop) return run3.forcedStop;
13230
13329
  if (event.outcome === "cancelled" || run3.cancelRequested) return "cancelled";
13231
13330
  if (event.stopReason) return event.stopReason;
13331
+ if (event.outcome === "failed" && event.quota && quotaStopApplies(assignment)) return "quota_exhausted";
13232
13332
  return event.outcome === "failed" ? "error" : "turn_ended";
13233
13333
  }
13234
13334
  async function unsandboxedVerify(run3) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@aspectlylabs/mikka",
3
- "version": "0.0.144",
3
+ "version": "0.0.145",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@aspectlylabs/mikka",
9
- "version": "0.0.144",
9
+ "version": "0.0.145",
10
10
  "license": "AGPL-3.0-only",
11
11
  "dependencies": {
12
12
  "@agentclientprotocol/sdk": "1.9.0",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@aspectlylabs/mikka",
3
- "version": "0.0.144",
3
+ "version": "0.0.145",
4
4
  "description": "Local Mikka daemon: runs agents through the user's own CLIs",
5
- "gitHead": "c2137052dcc3f840410efcd3059fc1ce04916a6f",
5
+ "gitHead": "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96",
6
6
  "type": "module",
7
7
  "private": false,
8
8
  "license": "AGPL-3.0-only",
package/paths.js CHANGED
@@ -6,7 +6,7 @@ import { join } from "node:path";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@mikka/daemon",
9
- version: "0.0.144",
9
+ version: "0.0.145",
10
10
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
11
11
  type: "module",
12
12
  private: true,
@@ -66,7 +66,7 @@ var package_default = {
66
66
  engines: {
67
67
  node: ">=24"
68
68
  },
69
- gitHead: "c2137052dcc3f840410efcd3059fc1ce04916a6f"
69
+ gitHead: "f4c4f818ea1d78fd81b298bda2a9249c50ba6f96"
70
70
  };
71
71
 
72
72
  // src/paths.ts
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:c9c7b482-c5f8-4107-b858-b928b56f871b",
5
+ "serialNumber": "urn:uuid:8102bd92-ce90-46f0-9b6a-4ea5db0808b1",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-11-01T05:57:33.613Z",
8
+ "timestamp": "2026-11-01T06:03:29.029Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "pre-build"
@@ -19,13 +19,13 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@aspectlylabs/mikka@0.0.144",
22
+ "bom-ref": "@aspectlylabs/mikka@0.0.145",
23
23
  "type": "library",
24
- "name": ".retained-build-YD1nSr",
25
- "version": "0.0.144",
24
+ "name": ".retained-build-ZjUf7M",
25
+ "version": "0.0.145",
26
26
  "scope": "required",
27
27
  "description": "Local Mikka daemon: runs agents through the user's own CLIs",
28
- "purl": "pkg:npm/%40aspectlylabs/mikka@0.0.144",
28
+ "purl": "pkg:npm/%40aspectlylabs/mikka@0.0.145",
29
29
  "properties": [],
30
30
  "externalReferences": [
31
31
  {
@@ -4627,7 +4627,7 @@
4627
4627
  ],
4628
4628
  "dependencies": [
4629
4629
  {
4630
- "ref": "@aspectlylabs/mikka@0.0.144",
4630
+ "ref": "@aspectlylabs/mikka@0.0.145",
4631
4631
  "dependsOn": [
4632
4632
  "@agentclientprotocol/sdk@1.9.0",
4633
4633
  "@anthropic-ai/claude-agent-sdk@0.3.305",